ADR-0030: Review & Life-Cycle Realization Model¶
- Status: accepted
- Refined by: ADR-0046 (four-eyes: submitter also excluded), ADR-0047 (four-eyes: submitter-only, author exclusion removed), ADR-0066 (amends point 1's state machine: IN_REVIEW gains a second exit besides reject — WITHDRAW), ADR-0132 (point 4's "a single user satisfies at most one required role-slot" becomes a declared policy choice, default off)
- Superseded in part by: ADR-0096 — transition semantics. Point 3's built-in default policy when none is configured, and point 5's policy-permitted-role reading, are abolished: an unconfigured transition denies everyone ("empty means nobody"), seeds and presets write explicit grants, and policy existence stops carrying semantics. Everything else in this ADR stands.
- Date: 2026-06-30
- Deciders: Nicolas Burri
- Refines: ADR-0003 (fixed states + configurable reviews)
- Relates to: ADR-0012 (transition vs functional permissions), ADR-0024 (cancellation), ADR-0027 (RLS plane), ADR-0004 (catalog roles)
Context¶
Phase 4 realizes ADR-0003's "fixed life-cycle states with configurable review processes". The
state set already exists on document_version (CHECK: DRAFT/IN_REVIEW/RELEASED/REVOKED/CANCELLED).
This ADR fixes the concrete review/lifecycle model: the state machine, the review-policy schema and
its RLS plane, approvals & four-eyes, the no-policy default, concurrency, and where transitions are
authorized.
Decision¶
-
State machine in a
LifecycleService— transition legality is enforced server-side (the DB CHECK guards only the value domain, per the V009 stance). Transitions (REQ-LC-002, ADR-0024):submit(DRAFT→IN_REVIEW, calls the existing content-freeze first so review binds to frozen content),reject(IN_REVIEW→DRAFT — nullsmanifest_hashand drops that version'scontent_partso the author can edit and re-freeze),approve,release(AUTOMATIC|EXPLICIT — setseffective_from),revoke(RELEASED→REVOKED, terminal, REQ-LC-012),cancel(DRAFT/IN_REVIEW→CANCELLED, terminal, REQ-LC-015), and new version (RELEASED→a new DRAFT row,version_number+1; the released row is never mutated). All transitions write anaudit_eventwithfrom_state/to_state. -
Review policy is per (scope × document_type) and CONTROL-PLANE — scope-keyed but permission-governed (
CONFIGURE_REVIEW_POLICY), not under per-request RLS, exactly like the V005 authorization tables and catalogs (ADR-0027's content-vs-governing-config boundary; the lifecycle decision path must read it the way V005 is read). Model: approval requirements as a set of (role, min_count) that must all be satisfied (REQ-LC-004);four_eyesflag default true (REQ-LC-005);release_modeAUTOMATIC|EXPLICIT (REQ-LC-007); per-transition permitted roles (REQ-LC-006). -
Built-in default policy when none is configured for a (scope, type): four-eyes on, one approval by any APPROVE-role, explicit release — applied explicitly and audited, so onboarding is not blocked while remaining a defined process.
-
Approvals and review comments are CONTENT-PLANE (RLS-forced, append-only, insert+select only) — they are records permanently attached to a version (REQ-LC-008/009), carrying the version's content hash at decision time (binds the approval to exact content; e-signature hook for ADR-0014). Four-eyes = approver ≠ the version author unless
four_eyes=false; a single user satisfies at most one required role-slot. -
Transitions are authorized by holding a policy-permitted role in the scope, not by a functional-permission enum value — ADR-0012's explicit boundary ("life-cycle transitions remain governed by review policies; the permission model governs all other actions").
CREATE_DOCUMENTstill gates starting a new version (a creation);CONFIGURE_REVIEW_POLICYgates policy edits. -
One non-terminal version per document — a new revision may begin only after the current DRAFT/IN_REVIEW version reaches RELEASED/REVOKED/CANCELLED.
-
Pending items are a derived query under RLS ("versions awaiting my approval"), not a task table — the MVP needs only the approver's list; reminders/escalation (REQ-NOT) stay deferred.
Consequences¶
- A review policy reveals a project's approval structure but is permission-filtered at the app layer (accepted, consistent with the V005 authz-table treatment).
rejectdeletes the rejected version'scontent_part(re-freeze on resubmit), avoiding the freeze-once uniqueness collision.- The default policy means release works out-of-the-box with four-eyes; a DB-trigger transition
guard is the documented hardening (deferred, as for
audit_event). - E-signature on transitions and "revoke requires approval" (REQ-LC-014) are deferred with ADR-0014 (e-sig is out of the MVP); MVP revoke is transition-permission only.
Requirements¶
Realizes REQ-LC-001..015 and the REQ-DOC-017 change-description obligation; relates to REQ-NOT-002/003 (pending-items slice). No new requirement IDs.