Referential integrity for the WORM tables, ON DELETE RESTRICT only (nothing cascades into or out of evidence). New changesets worm-audit:11 (indexes) and worm-audit:12 (one idempotent DO block) in the template and the rendered docs/ddl/worm-audit-postgres.sql.
Child column
Parent
Why it is safe
worm_audit_chain.audit_id
core audit header
The chainer only links committed headers. worm_audit_purge deletes details, then links and headers (one CTE statement); the RI check runs at end of statement. Verified by the existing purge acceptance test and on a kof22-shaped schema.
Checkpoints are never deleted. An export is only stored after inspect passes, which requires real checkpoint ids (the 0 sentinel can never be persisted).
worm_audit_verification.export_id
worm_audit_export
Exports are never deleted; SealedExportVerifier.verify saves its run for a stored export.
Not FKs, by design (listed with reasons in the guardrail test): checkpoint/export/verification link ranges, first_break_link_id and worm_audit_purge.anchor_link_id. The retention purge deletes the sealed prefix of links these rows outlive, and the anchor is the last link that same purge deleted.
Orphans on upgrade are evidence: they are copied to qqq_fk_quarantine (action KEPT, full row JSON), never updated or deleted, and that one constraint is left NOT VALID so new rows are still checked. The quarantine DDL matches the other qbits'.
Behaviour note: a plain TRUNCATE audit (or checkpoints or exports) is now refused by PostgreSQL's FK check before the guard trigger runs; TRUNCATE ... CASCADE reaches the guards, which refuse it. grantsTriggersLookupRulesAndSpoofedFlag now asserts both.
Tests
everyReferenceHasARestrictForeignKeyOrAReason: every possible-value reference of the WORM tables, plus the chain's audit header, has a validated RESTRICT FK or an exemption with a reason. Red without worm-audit:11-12 (4 references reported). Green with them.
orphanEvidenceIsKeptAndTheConstraintIsNotValidated: a link whose header is gone is quarantined as KEPT and left unchanged; the constraint is NOT VALID and still refuses a new orphan; re-applying the DDL is a no-op.
All PostgreSQL acceptance tests (purge, refusal, anchor, tamper, concurrent writers and chainers) and the MinIO export acceptance test pass with the FKs. ./mvnw verify green locally.
## Summary
Referential integrity for the WORM tables, `ON DELETE RESTRICT` only (nothing cascades into or out of evidence). New changesets `worm-audit:11` (indexes) and `worm-audit:12` (one idempotent `DO` block) in the template and the rendered `docs/ddl/worm-audit-postgres.sql`.
| Child column | Parent | Why it is safe |
|---|---|---|
| `worm_audit_chain.audit_id` | core `audit` header | The chainer only links committed headers. `worm_audit_purge` deletes details, then links and headers (one CTE statement); the RI check runs at end of statement. Verified by the existing purge acceptance test and on a kof22-shaped schema. |
| `worm_audit_export.from_checkpoint_id`, `to_checkpoint_id` | `worm_audit_checkpoint` | Checkpoints are never deleted. An export is only stored after `inspect` passes, which requires real checkpoint ids (the `0` sentinel can never be persisted). |
| `worm_audit_verification.export_id` | `worm_audit_export` | Exports are never deleted; `SealedExportVerifier.verify` saves its run for a stored export. |
**Not FKs, by design** (listed with reasons in the guardrail test): checkpoint/export/verification link ranges, `first_break_link_id` and `worm_audit_purge.anchor_link_id`. The retention purge deletes the sealed prefix of links these rows outlive, and the anchor is the last link that same purge deleted.
**Orphans on upgrade** are evidence: they are copied to `qqq_fk_quarantine` (action `KEPT`, full row JSON), never updated or deleted, and that one constraint is left `NOT VALID` so new rows are still checked. The quarantine DDL matches the other qbits'.
**Behaviour note:** a plain `TRUNCATE audit` (or checkpoints or exports) is now refused by PostgreSQL's FK check before the guard trigger runs; `TRUNCATE ... CASCADE` reaches the guards, which refuse it. `grantsTriggersLookupRulesAndSpoofedFlag` now asserts both.
## Tests
- `everyReferenceHasARestrictForeignKeyOrAReason`: every possible-value reference of the WORM tables, plus the chain's audit header, has a validated RESTRICT FK or an exemption with a reason. Red without `worm-audit:11-12` (4 references reported). Green with them.
- `orphanEvidenceIsKeptAndTheConstraintIsNotValidated`: a link whose header is gone is quarantined as KEPT and left unchanged; the constraint is NOT VALID and still refuses a new orphan; re-applying the DDL is a no-op.
- All PostgreSQL acceptance tests (purge, refusal, anchor, tamper, concurrent writers and chainers) and the MinIO export acceptance test pass with the FKs. `./mvnw verify` green locally.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Referential integrity for the WORM tables,
ON DELETE RESTRICTonly (nothing cascades into or out of evidence). New changesetsworm-audit:11(indexes) andworm-audit:12(one idempotentDOblock) in the template and the rendereddocs/ddl/worm-audit-postgres.sql.worm_audit_chain.audit_idauditheaderworm_audit_purgedeletes details, then links and headers (one CTE statement); the RI check runs at end of statement. Verified by the existing purge acceptance test and on a kof22-shaped schema.worm_audit_export.from_checkpoint_id,to_checkpoint_idworm_audit_checkpointinspectpasses, which requires real checkpoint ids (the0sentinel can never be persisted).worm_audit_verification.export_idworm_audit_exportSealedExportVerifier.verifysaves its run for a stored export.Not FKs, by design (listed with reasons in the guardrail test): checkpoint/export/verification link ranges,
first_break_link_idandworm_audit_purge.anchor_link_id. The retention purge deletes the sealed prefix of links these rows outlive, and the anchor is the last link that same purge deleted.Orphans on upgrade are evidence: they are copied to
qqq_fk_quarantine(actionKEPT, full row JSON), never updated or deleted, and that one constraint is leftNOT VALIDso new rows are still checked. The quarantine DDL matches the other qbits'.Behaviour note: a plain
TRUNCATE audit(or checkpoints or exports) is now refused by PostgreSQL's FK check before the guard trigger runs;TRUNCATE ... CASCADEreaches the guards, which refuse it.grantsTriggersLookupRulesAndSpoofedFlagnow asserts both.Tests
everyReferenceHasARestrictForeignKeyOrAReason: every possible-value reference of the WORM tables, plus the chain's audit header, has a validated RESTRICT FK or an exemption with a reason. Red withoutworm-audit:11-12(4 references reported). Green with them.orphanEvidenceIsKeptAndTheConstraintIsNotValidated: a link whose header is gone is quarantined as KEPT and left unchanged; the constraint is NOT VALID and still refuses a new orphan; re-applying the DDL is a no-op../mvnw verifygreen locally.🤖 Generated with Claude Code