fix(isms): centrally serialize all content edits against approve (CS-701)
Why
Follow-up to #3366. On the production-deploy review (#3364), cubic correctly flagged (P1) that #3366's approve-race fix was incomplete: the advisory lock only protected paths that also take it, so a register update/delete (or narrative save / control link / regenerate) could still interleave while approve() loads EXPORT_DOCUMENT_INCLUDE and freezes the version โ leaving an approved version that omits the edit, or an edit that never invalidated the approval.
Fix โ enforced centrally (as cubic suggested)
Rather than add the lock to ~12 scattered transactions, invalidateApprovalIfNeeded now takes the per-document advisory lock as its first action. Every content-mutation path already calls it โ register create/update/delete, narrative save, control add/remove, and regenerate โ so all edits now serialize against approve() (which takes the same lock). Either the edit is frozen into the version, or it correctly reverts the document to draft; no interleaving can do neither.
The lock is transaction-scoped and re-entrant, so the register create paths that already acquire it for max(position) allocation are unaffected.
Tests
- Adds
utils/approval.spec.ts(lock-taken-before-status-read ordering + revert / no-op behavior). $executeRawadded to the two tx mocks that exercise the realinvalidateApprovalIfNeeded.- 324 API ISMS tests passing, typecheck clean, on top of current
main(incl. #3366).
Closes the residual flagged on #3364; completes #3366.
๐ค Generated with Claude Code
https://claude.ai/code/session_012CweXoSSEP89mX93u3Bdcp
Summary by cubic
Centralizes the per-document advisory lock so all ISMS content edits serialize against approval, fixing a race that could freeze an incomplete version or let edits bypass invalidation. Supports CS-701 by ensuring published version snapshots are always accurate.
-
Bug Fixes
invalidateApprovalIfNeedednow acquires the per-document advisory lock first, so every edit path (register update/delete, narrative save, control link, regenerate) serializes againstapprove().- Prevents interleaving under READ COMMITTED: either the edit is included in the approved version or the document cleanly reverts to draft.
- Lock is transaction-scoped and re-entrant, so existing create paths using the lock for position allocation are unaffected.
-
Tests
- Added
utils/approval.spec.tsto assert lock-before-status ordering and revert/no-op behavior. - Updated test transaction mocks to include
$executeRawin@db-backed specs.
- Added