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>
This commit is contained in:
Sacha Al Himdani
2026-04-29 18:11:19 +02:00
parent 01bc3ac696
commit 553901e4ad
93 changed files with 2384 additions and 5741 deletions

View File

@@ -57,12 +57,6 @@ export {
type ControlMaturityLevel,
} from "./controls";
export { getAssetTypeVariant } from "./assets";
export {
getSnapshotTypeLabel,
getSnapshotTypeUrlPath,
snapshotTypes,
validateSnapshotConsistency,
} from "./snapshots";
export {
getAuditStateLabel,
getAuditStateVariant,

View File

@@ -1,62 +0,0 @@
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
type Translator = (s: string) => string;
export const snapshotTypes = [
"RISKS",
] as const;
export function getSnapshotTypeLabel(__: Translator, type: string | null | undefined) {
if (!type) {
return __("Unknown");
}
switch (type) {
case "RISKS":
return __("Risks");
case "VENDORS":
return __("Vendors");
case "PROCESSING_ACTIVITIES":
return __("Processing Activities");
default:
return __("Unknown");
}
}
export function getSnapshotTypeUrlPath(type?: string): string {
switch (type) {
case "RISKS":
return "/risks";
case "VENDORS":
return "/vendors";
case "PROCESSING_ACTIVITIES":
return "/processing-activities";
default:
return "";
}
}
export interface SnapshotableResource {
snapshotId?: string | null | undefined;
}
export function validateSnapshotConsistency(
resource: SnapshotableResource | null | undefined,
urlSnapshotId?: string | null | undefined
): void {
if (resource && resource.snapshotId !== (urlSnapshotId ?? null)) {
throw new Error("PAGE_NOT_FOUND");
}
}