Files
probo/pkg/server/api/console/v1/graphql/control.graphql
Sacha Al Himdani 553901e4ad Add risk publish to document system
Replace the old snapshot-based system for risks with the publish
document system, mirroring the prior vendor / processing activity / DPIA
/ TIA migration. Includes the GraphQL mutation, MCP tool, CLI command,
n8n operation, frontend publish dialog, e2e tests, and a prosemirror
register template covering name, description, category, treatment,
owner, inherent and residual scoring, and notes.

The risk register lives as a generated DocumentTypeRegister document on
the organization, reused across publishes (the major version bumps on
every republish). Approvers can be passed in to create a draft pending
approval; otherwise the version is published immediately. The frontend
Risks page exposes a Publish button and a Document link button when the
document exists, and pre-fills the previous default approvers.

Risks was the last remaining snapshot type, so this commit also removes
the entire snapshot system: drop snapshotId from the Risk GraphQL type
and RiskFilter; remove RiskSnapshotter, Risks.Snapshot,
InsertRiskSnapshots, and the SnapshotID/SourceID fields on Risk; delete
Snapshot, ControlSnapshot, SnapshotsType, SnapshotOrderField,
Snapshottable, the SnapshotService, the Snapshot console resolvers and
GraphQL schema, the Snapshot MCP types and operations
(list/get/take/listControlSnapshots), the snapshot CLI (prb snapshot),
the snapshot frontend pages, routes, banner, LinkedSnapshotsCard,
SnapshotGraph, snapshot helpers, and the snapshot n8n resource and
control link/unlink snapshot operations. The snapshot_id columns remain
in the database but are now filtered out with snapshot_id IS NULL.

Add Get/Upsert/Clear GeneratedDocumentID methods on Risk backed by a new
risks_document_id column on generated_documents, matching the
ProcessingActivity/Finding/Vendor pattern. The migration command
migrate-risk-snapshots-to-documents uses raw SQL queries instead of the
Go snapshot types, since those are gone.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-05-04 14:13:42 +02:00

460 lines
12 KiB
GraphQL

enum ControlMaturityLevel
@goModel(model: "go.probo.inc/probo/pkg/coredata.ControlMaturityLevel") {
NONE
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ControlMaturityLevelNone"
)
INITIAL
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ControlMaturityLevelInitial"
)
MANAGED
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ControlMaturityLevelManaged"
)
DEFINED
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ControlMaturityLevelDefined"
)
QUANTITATIVELY_MANAGED
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ControlMaturityLevelQuantitativelyManaged"
)
OPTIMIZING
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ControlMaturityLevelOptimizing"
)
}
enum ControlOrderField
@goModel(model: "go.probo.inc/probo/pkg/coredata.ControlOrderField") {
CREATED_AT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldCreatedAt"
)
SECTION_TITLE
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldSectionTitle"
)
}
enum ApplicabilityStatementOrderField
@goModel(model: "go.probo.inc/probo/pkg/coredata.ApplicabilityStatementOrderField") {
CREATED_AT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ApplicabilityStatementOrderFieldCreatedAt"
)
CONTROL_SECTION_TITLE
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.ApplicabilityStatementOrderFieldControlSectionTitle"
)
}
enum StatementOfApplicabilityOrderField
@goModel(
model: "go.probo.inc/probo/pkg/coredata.StatementOfApplicabilityOrderField"
) {
NAME
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.StatementOfApplicabilityOrderFieldName"
)
CREATED_AT
@goEnum(
value: "go.probo.inc/probo/pkg/coredata.StatementOfApplicabilityOrderFieldCreatedAt"
)
}
input ControlOrder
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ControlOrderBy"
) {
direction: OrderDirection!
field: ControlOrderField!
}
input ApplicabilityStatementOrder
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ApplicabilityStatementOrderBy"
) {
direction: OrderDirection!
field: ApplicabilityStatementOrderField!
}
input StatementOfApplicabilityOrder
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.StatementOfApplicabilityOrderBy"
) {
direction: OrderDirection!
field: StatementOfApplicabilityOrderField!
}
input ControlFilter {
query: String
}
type Control implements Node {
id: ID!
organization: Organization @goField(forceResolver: true)
sectionTitle: String!
name: String!
description: String
bestPractice: Boolean!
notImplementedJustification: String
maturityLevel: ControlMaturityLevel!
regulatory: Boolean! @goField(forceResolver: true)
contractual: Boolean! @goField(forceResolver: true)
riskAssessment: Boolean! @goField(forceResolver: true)
framework: Framework! @goField(forceResolver: true)
measures(
first: Int
after: CursorKey
last: Int
before: CursorKey
orderBy: MeasureOrder
filter: MeasureFilter
): MeasureConnection! @goField(forceResolver: true)
documents(
first: Int
after: CursorKey
last: Int
before: CursorKey
orderBy: DocumentOrder
filter: DocumentFilter
): DocumentConnection! @goField(forceResolver: true)
audits(
first: Int
after: CursorKey
last: Int
before: CursorKey
orderBy: AuditOrder
): AuditConnection! @goField(forceResolver: true)
obligations(
first: Int
after: CursorKey
last: Int
before: CursorKey
orderBy: ObligationOrder
): ObligationConnection! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
permission(action: String!): Boolean! @goField(forceResolver: true)
}
type ControlConnection
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ControlConnection"
) {
totalCount: Int! @goField(forceResolver: true)
edges: [ControlEdge!]!
pageInfo: PageInfo!
}
type ControlEdge {
cursor: CursorKey!
node: Control!
}
type StatementOfApplicability implements Node {
id: ID!
name: String!
document: Document @goField(forceResolver: true)
organization: Organization @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
applicabilityStatements(
first: Int
after: CursorKey
last: Int
before: CursorKey
orderBy: ApplicabilityStatementOrder
): ApplicabilityStatementConnection! @goField(forceResolver: true)
permission(action: String!): Boolean! @goField(forceResolver: true)
}
type ApplicabilityStatementConnection
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ApplicabilityStatementConnection"
) {
totalCount: Int! @goField(forceResolver: true)
edges: [ApplicabilityStatementEdge!]!
pageInfo: PageInfo!
}
type ApplicabilityStatementEdge {
cursor: CursorKey!
node: ApplicabilityStatement!
}
type ApplicabilityStatement implements Node {
id: ID!
statementOfApplicability: StatementOfApplicability! @goField(forceResolver: true)
control: Control! @goField(forceResolver: true)
applicability: Boolean!
justification: String!
createdAt: Datetime!
updatedAt: Datetime!
permission(action: String!): Boolean! @goField(forceResolver: true)
}
type StatementOfApplicabilityConnection
@goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.StatementOfApplicabilityConnection"
) {
totalCount: Int! @goField(forceResolver: true)
edges: [StatementOfApplicabilityEdge!]!
pageInfo: PageInfo!
}
type StatementOfApplicabilityEdge {
cursor: CursorKey!
node: StatementOfApplicability!
}
extend type Mutation {
createControl(input: CreateControlInput!): CreateControlPayload!
updateControl(input: UpdateControlInput!): UpdateControlPayload!
deleteControl(input: DeleteControlInput!): DeleteControlPayload!
createControlMeasureMapping(
input: CreateControlMeasureMappingInput!
): CreateControlMeasureMappingPayload!
createControlDocumentMapping(
input: CreateControlDocumentMappingInput!
): CreateControlDocumentMappingPayload!
deleteControlMeasureMapping(
input: DeleteControlMeasureMappingInput!
): DeleteControlMeasureMappingPayload!
deleteControlDocumentMapping(
input: DeleteControlDocumentMappingInput!
): DeleteControlDocumentMappingPayload!
createApplicabilityStatement(
input: CreateApplicabilityStatementInput!
): CreateApplicabilityStatementPayload!
updateApplicabilityStatement(
input: UpdateApplicabilityStatementInput!
): UpdateApplicabilityStatementPayload!
deleteApplicabilityStatement(
input: DeleteApplicabilityStatementInput!
): DeleteApplicabilityStatementPayload!
createControlAuditMapping(
input: CreateControlAuditMappingInput!
): CreateControlAuditMappingPayload
deleteControlAuditMapping(
input: DeleteControlAuditMappingInput!
): DeleteControlAuditMappingPayload
createControlObligationMapping(
input: CreateControlObligationMappingInput!
): CreateControlObligationMappingPayload!
deleteControlObligationMapping(
input: DeleteControlObligationMappingInput!
): DeleteControlObligationMappingPayload!
createStatementOfApplicability(
input: CreateStatementOfApplicabilityInput!
): CreateStatementOfApplicabilityPayload!
updateStatementOfApplicability(
input: UpdateStatementOfApplicabilityInput!
): UpdateStatementOfApplicabilityPayload!
deleteStatementOfApplicability(
input: DeleteStatementOfApplicabilityInput!
): DeleteStatementOfApplicabilityPayload!
publishStatementOfApplicability(
input: PublishStatementOfApplicabilityInput!
): PublishStatementOfApplicabilityPayload!
}
input CreateControlInput {
frameworkId: ID!
sectionTitle: String!
name: String!
description: String
bestPractice: Boolean!
maturityLevel: ControlMaturityLevel!
notImplementedJustification: String
}
input UpdateControlInput {
id: ID!
sectionTitle: String
name: String
description: String @goField(omittable: true)
bestPractice: Boolean
maturityLevel: ControlMaturityLevel
notImplementedJustification: String @goField(omittable: true)
}
input DeleteControlInput {
controlId: ID!
}
input CreateControlMeasureMappingInput {
controlId: ID!
measureId: ID!
}
input CreateControlDocumentMappingInput {
controlId: ID!
documentId: ID!
}
input DeleteControlMeasureMappingInput {
controlId: ID!
measureId: ID!
}
input DeleteControlDocumentMappingInput {
controlId: ID!
documentId: ID!
}
input CreateApplicabilityStatementInput {
statementOfApplicabilityId: ID!
controlId: ID!
applicability: Boolean!
justification: String
}
input UpdateApplicabilityStatementInput {
applicabilityStatementId: ID!
applicability: Boolean!
justification: String
}
input DeleteApplicabilityStatementInput {
applicabilityStatementId: ID!
}
input CreateControlAuditMappingInput {
controlId: ID!
auditId: ID!
}
input DeleteControlAuditMappingInput {
controlId: ID!
auditId: ID!
}
input CreateControlObligationMappingInput {
controlId: ID!
obligationId: ID!
}
input DeleteControlObligationMappingInput {
controlId: ID!
obligationId: ID!
}
input CreateStatementOfApplicabilityInput {
organizationId: ID!
name: String!
}
input UpdateStatementOfApplicabilityInput {
id: ID!
name: String
}
input ApplicabilityStatementInput {
controlId: ID!
applicability: Boolean!
justification: String
}
input DeleteStatementOfApplicabilityInput {
statementOfApplicabilityId: ID!
}
input PublishStatementOfApplicabilityInput {
statementOfApplicabilityId: ID!
approverIds: [ID!]
}
type CreateControlPayload {
controlEdge: ControlEdge!
}
type UpdateControlPayload {
control: Control!
}
type DeleteControlPayload {
deletedControlId: ID!
}
type CreateControlMeasureMappingPayload {
controlEdge: ControlEdge!
measureEdge: MeasureEdge!
}
type CreateControlDocumentMappingPayload {
controlEdge: ControlEdge!
documentEdge: DocumentEdge!
}
type DeleteControlMeasureMappingPayload {
deletedControlId: ID!
deletedMeasureId: ID!
}
type DeleteControlDocumentMappingPayload {
deletedControlId: ID!
deletedDocumentId: ID!
}
type CreateApplicabilityStatementPayload {
applicabilityStatementEdge: ApplicabilityStatementEdge!
}
type UpdateApplicabilityStatementPayload {
applicabilityStatement: ApplicabilityStatement!
}
type DeleteApplicabilityStatementPayload {
deletedApplicabilityStatementId: ID!
}
type CreateControlAuditMappingPayload {
controlEdge: ControlEdge
auditEdge: AuditEdge
}
type DeleteControlAuditMappingPayload {
deletedControlId: ID
deletedAuditId: ID
}
type CreateControlObligationMappingPayload {
controlEdge: ControlEdge!
obligationEdge: ObligationEdge!
}
type DeleteControlObligationMappingPayload {
deletedControlId: ID!
deletedObligationId: ID!
}
type CreateStatementOfApplicabilityPayload {
statementOfApplicabilityEdge: StatementOfApplicabilityEdge!
}
type UpdateStatementOfApplicabilityPayload {
statementOfApplicability: StatementOfApplicability!
}
type DeleteStatementOfApplicabilityPayload {
deletedStatementOfApplicabilityId: ID!
}
type PublishStatementOfApplicabilityPayload {
documentEdge: DocumentEdge!
documentVersionEdge: DocumentVersionEdge!
}