feat(ddl): RESTRICT foreign keys for chain, export and verification #11

Merged
KofTwentyTwo merged 2 commits from feature/foreign-keys into develop 2026-10-14 23:00:29 +00:00
2 Commits
Author SHA1 Message Date
james.maesandClaude Opus 5.5 2b66492571 Merge origin/develop into feature/foreign-keys
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:48:16 -05:00
james.maesandClaude Opus 5.5 4194837610 feat(ddl): RESTRICT foreign keys for chain, export and verification
worm-audit:11 indexes and worm-audit:12 adds foreign keys, ON DELETE
RESTRICT only, so nothing cascades into or out of audit evidence:

- worm_audit_chain.audit_id -> core audit header. worm_audit_purge deletes
  details, then links, then headers (links and headers in one statement),
  which the constraint allows; the purge acceptance test passes with it.
- worm_audit_export.from/to_checkpoint_id -> worm_audit_checkpoint and
  worm_audit_verification.export_id -> worm_audit_export: neither parent is
  ever deleted, and both are written before the row that names them.

References to chain links (checkpoint/export/verification ranges,
first_break_link_id, the purge anchor) stay unconstrained: the retention
purge deletes the sealed prefix those rows outlive. The guardrail test
lists them with reasons and fails on any new unconstrained reference.

Rows whose parent is already missing are evidence: they are copied to
qqq_fk_quarantine (action KEPT), never changed, and leave that constraint
NOT VALID (still enforced for new rows). The block is idempotent, as the
rendered DDL is applied more than once.

With the key, a plain TRUNCATE of a referenced table is refused by
PostgreSQL before the guard trigger; TRUNCATE ... CASCADE reaches the
guards. The grants test now checks both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:33:18 -05:00