feat(api): package revisions — immutable issuance snapshots (ADR-015 D5) - #168
Conversation
Immutable issuance snapshots: each revision freezes every member section's SpecTree as JSONB. UNIQUE (package_id, label); snapshot rows cascade with their revision; spec_id FK stays NO ACTION per ADR-015 D5 so custody blocks ad-hoc spec deletion but project-delete cascades remain safe. Reversible (paired up/down, verified both directions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
POST /packages/:id/revisions freezes every member section's SpecTree as JSONB inside one REPEATABLE READ transaction (consistent point-in-time view across all members); GET /revisions/:id returns the frozen trees in membership order, Zod-validated against SpecTreeSchema on read. Trees are also validated at write — issuance fails (422) rather than freezing a snapshot that cannot round-trip. buildNodeTree is exported from queries/specs.ts for intra-db-module reuse (not on the barrel). Duplicate label → 409 via the UNIQUE (package_id, label) constraint; unknown package → 404. lifecycle_state hook deliberately deferred (ADR-018). Closes #96 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
POST /packages/{id}/revisions and GET /revisions/{id}, with
RevisionSummary / RevisionSpecEntry / RevisionWithTrees component
schemas (frozen trees reference the existing SpecTree schema).
Validated with redocly lint (pre-existing warnings only).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (11)
📝 WalkthroughWalkthroughThis PR implements immutable package revision snapshots per ADR-015 D5. It adds a database migration for ChangesPackage Revisions — Immutable Issuance Snapshots
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install timed out. The project may have too many dependencies for the sandbox. Comment |
Why
"50% DD" / "100% CD" / "Addendum 2" must be reproducible point-in-time records of exactly what was issued — liability-grade chain of custody at the issuance boundary (ADR-015 D5). Until now a package was only a live view over the project TOC: any later paragraph edit silently changed what "the issued set" looked like.
What
package_revisions(UNIQUE (package_id, label)) +package_revision_specs(frozenSpecTreeJSONB per member section), exactly per the ADR-015 D5 schema. Reversible; up → down → up verified.POST /packages/:id/revisions { label }freezes every member section's full tree inside one transaction;GET /revisions/:idreturns the frozen trees in membership order, Zod-validated againstSpecTreeSchemaon read.SpecTreeSchema.parse.Design decisions
REPEATABLE READfor the snapshot transaction. All member trees come from a single consistent DB snapshot, so a concurrent edit mid-issuance can't produce a torn revision.package_revision_specs.spec_idkeeps the FK default (NO ACTION), exactly as ADR-015 D5 writes it: checked at end of statement, so project-delete cascades (specs + packages + revisions in one statement) stay safe, while ad-hoc deletion of a spec that appears in an issued revision is blocked — custody survives TOC churn.specCount: 0). The revision record is still a meaningful issuance event; rejecting would be policy, not data integrity.specCount), not the trees — trees can be large;GET /revisions/:idis the read surface.lifecycle_state='issued'hook deliberately absent — deferred to the document-concurrency work (ADR-018), per the issue.buildNodeTreeis now exported fromqueries/specs.tsfor intra-db-module reuse (not on the barrel) instead of duplicating the row→forest assembly.Testing
pnpm test— 733 passed)pnpm test:integration— 264 passed, isolated PG 16)pnpm migrate→pnpm migrate:down→pnpm migrateverifiedpnpm lintgreen (eslint + tsc + prettier);redocly lint openapi.yamlvalid (pre-existing warnings only)🤖 Co-authored by Claude (Fable 5). Closes #96.
Summary by CodeRabbit