feat(isms): document versioning and history (CS-701)
Why
Approving an ISMS document made it published, but any subsequent edit reverted the whole document to unpublished and lost the approved version — no history, no way to draft an update without losing what's live. That's an audit exposure (auditors expect the exact version that was live when they sampled) and it discourages customers from keeping documents current.
What this delivers
Brings the six ISMS documents (Context 4.1, Interested Parties Register 4.2a, Requirements 4.2b/c, Scope 4.3, Leadership 5.1, Objectives 6.2) onto the same versioning model the Policies module already uses — published version + working draft + version history.
- Approving freezes an immutable published version (v1) — content snapshot + rendered PDF/DOCX retained.
- Editing an approved document creates a draft without touching the published version. v1 stays live and exportable while the draft accumulates.
- Re-approving publishes the next version (v2); v1 moves into history.
- Version history on every document detail page: date, approver, and per-version PDF/DOCX download.
- Historical exports are byte-identical to what was approved (stored file), with a snapshot re-render fallback.
How it works (design)
The schema was already shaped for this but only ever kept one mutable version. Mirroring Policies' draftContent + currentVersionId pattern:
- The live register tables +
IsmsDocument.draftNarrativeare the editable draft;IsmsDocumentVersionrows are published-only immutable snapshots, pointed to byIsmsDocument.currentVersionId. - On approve,
IsmsVersionServicefreezes a full content snapshot into a new version inside the txn, then renders + uploads PDF/DOCX to S3 after the txn (so an S3 hiccup never orphans a row) — the Policies publish pattern. - Editing reverts
statusto draft but never touchescurrentVersion, so the published version stays live.
Scope of changes
- schema + migration —
IsmsDocument.{currentVersion,draftNarrative,draftSnapshot},IsmsDocumentVersion.{contentSnapshot,changelog,publishedBy}; defensive backfill (feature is pre-GA / flag-gated). - api —
IsmsVersionService, sharedexport-payloadutil, reworkedapprove(),AttachmentsModulewired in,GET /v1/isms/documents/:id/versions, optionalversionIdon export. - app —
useIsmsDocumentVersionshook,IsmsVersionHistorysection (one choke-point → all six documents inherit it), per-version download, "Published vN / editing creates a new draft" signal.
Testing
- API: 312 tests pass (
npx jest src/isms); new specs for the version lifecycle, snapshot, export routing, and best-effort renders. - App: 119 tests pass (
npx vitest run …/documents/isms); new component + hook tests, permission-gated coverage retained. - Typecheck clean across all ISMS files (api + app).
- Not yet exercised against a live seeded org (feature is flag-gated behind
is-isms-enabled); covered by unit/integration tests.
Closes CS-701.
🤖 Generated with Claude Code
https://claude.ai/code/session_012CweXoSSEP89mX93u3Bdcp
Summary by cubic
Adds versioning and history to ISMS documents (CS-701) so approved versions stay live while edits happen in a draft. Migration preserves existing approvals; exports default to the current published artifact with reliable, org‑scoped per‑version downloads.
-
New Features
- Approving freezes a published version; editing creates a draft; re-approve publishes the next version.
- Per-version exports retained (PDF/DOCX) with snapshot re-render fallback;
versionIdsupported on export. - Version history via
GET /v1/isms/documents/:id/versionsand a new UI section with per-version downloads. - Schema:
IsmsDocumentgainscurrentVersionId,draftNarrative,draftSnapshot;IsmsDocumentVersiongainscontentSnapshot,changelog, and publisher linkage. NewIsmsVersionServicefreezes versions and renders/uploads exports after the DB transaction.
-
Bug Fixes
- Export integrity: versioned exports never read live/draft; default export serves the current published version; snapshot re-render fallback; errors surface via toast; unavailable formats are hidden; org-scoped requests; spinner states remain correct with overlapping downloads.
- Consistency: approval snapshot reads the org profile through the same transaction; approval success is decoupled from post-approval refresh; history preserves approver via frozen metadata.
- Migration safety: approvals are preserved; legacy snapshot-less version rows are dropped;
currentVersionIdis only set by the approve flow; foreign keys are added after legacy cleanup.