IU Core 8000x — Compensation Primitives Design (fn_iu_supersede + fn_iu_retire)
IU Core 8000x — Compensation Primitives Design Note
Why this matters
Constitution rule: reversible by default. Every irreversible action
must be paired with a documented compensation primitive that can put the
system back to a known-good state without pg_dump restore.
public.fn_iu_enact (M3a-2026-05-20) advances draft → enacted and writes
to three tables in one transaction: information_unit, unit_version,
iu_lifecycle_log. The FSM defined inside fn_iu_enact recognises three
additional transitions but its body returns
'transition_not_yet_implemented':
enacted → superseded (transition_type='supersede')
enacted → retired (transition_type='retire_from_enacted')
draft → retired (transition_type='retire_from_draft')
superseded → retired (transition_type='retire_from_superseded')
Without these, every fn_iu_enact call is a one-way door: the only way
back is a pg_dump restore, which loses everything else committed since.
What the 8000x primitives provide
| Primitive | Signature | What it does | Why safe |
|---|---|---|---|
fn_iu_supersede(canonical, actor, rd_id, cs_id?, reason?, tool_rev?, superseded_by_canonical?, dry_run) |
(text,text,uuid,uuid,text,text,text,boolean) → jsonb |
enacted → superseded for one IU; logs transition_type='supersede' with optional successor pointer |
Same SECURITY DEFINER pattern as fn_iu_enact, refuses NULL rd_id, probes cutter_governance.review_decision FK, sets app.canonical_writer='fn_iu_supersede' so fn_iu_enacted_immut trigger permits the body update, post-write read-back assertion |
fn_iu_retire(canonical, actor, rd_id, cs_id?, reason?, tool_rev?, dry_run) |
(text,text,uuid,uuid,text,text,boolean) → jsonb |
`{draft | enacted |
Safety pattern (mirrored from fn_iu_enact)
- Input validation → returns
{status: invalid_input}on missing canonical_address / actor / review_decision_id. SELECT … FROM public.information_unit … FOR UPDATE(row-level lock).- FSM legality check vs
iu_lifecycle_vocab(implicit viatransition_type CASE). cutter_governance.review_decisionFK probe — returns{status: review_decision_not_found}if missing.- Optional
cutter_governance.cut_change_setFK probe. - Dry-run early return with
{status: plan_ok, would_write_rows:{…}}. pg_advisory_xact_lock(hashtext('iu_supersede:'||iu_id))/'iu_retire:'||iu_id.set_config('app.canonical_writer', '<fn_name>', true)so thefn_iu_enacted_immuttrigger permits the body update.- Dual-table UPDATE (
information_unit+unit_version). - INSERT into
iu_lifecycle_logwithtransition_type+ metadata{warnings, app_canonical_writer, compensation_function_version}. - Post-write read-back assertion (defence-in-depth — RAISE on mismatch).
- Return
{status: superseded|retired, iu_id, log_id, …}.
What it does NOT do
- No automatic FSM "unretire" path — retirement is itself one-way. The compensation primitives close the original gap (no compensation) but do NOT introduce infinite reversibility. If both fn_iu_enact and fn_iu_retire have been applied wrongly, the only rollback is full pg_dump restore.
- No bulk variant — one IU per call so that any single failure rolls only that IU; the package files iterate inside their own
DOblock so the wrapping transaction fails atomically on the first error. - No gate gating — the primitives are inert by construction (refuse without sovereign-authored review_decision_id). No new
dot_configrow is needed.
Test coverage
tests/test_iu_core_8000x_compensation_primitives.py(25 TestCases, all PASS):- migration file shape: ON_ERROR_STOP, BEGIN/COMMIT
- both functions SECURITY DEFINER, pinned search_path
- both refuse NULL review_decision_id
- both probe cutter_governance.review_decision FK
- supersede only allows from_enacted
- retire allows draft / enacted / superseded with correct transition_type
- both set the app.canonical_writer marker
- both INSERT iu_lifecycle_log
- both have post-write read-back assertions
- dry-run path exists for both
- no DML outside function bodies
- both functions documented via COMMENT ON FUNCTION
- rollback drops both with matching signatures
- sandbox/250 covers every refusal branch
- DOT scan registers both new function names + bumps function count to 54
Sandbox coverage
sql/iu-core/sandbox/250_compensation_primitives_probe.sql:
| Step | Probe |
|---|---|
| 250.1 | fn_iu_supersede('ICX-CONST/x', 'sandbox/250', NULL) → status='invalid_input' |
| 250.2 | fn_iu_supersede('no/such/canonical', actor, rd_uuid) → status='iu_not_found' |
| 250.3 | fn_iu_supersede(<draft canonical>, actor, rd_uuid) → status='fsm_denied' |
| 250.4 | fn_iu_supersede(<enacted canonical>, actor, fake_rd_uuid, dry_run=true) → status='review_decision_not_found' |
| 250.5 | fn_iu_retire('ICX-CONST/x', 'sandbox/250', NULL) → status='invalid_input' |
| 250.6 | fn_iu_retire('no/such/canonical', actor, rd_uuid) → status='iu_not_found' |
| 250.7 | fn_iu_retire(<retired canonical>, actor, fake_rd_uuid) → status='already_retired' (skipped if no retired corpus) |
| 250.8 | SELECT count(*) … pg_proc WHERE proname IN ('fn_iu_supersede','fn_iu_retire') = 2 |
Whole script wraps BEGIN; … ROLLBACK;. Nothing committed.
Application order
psql -d directus -f sql/iu-core/026_compensation_primitives.sql(migration)psql -d directus -f sql/iu-core/sandbox/250_compensation_primitives_probe.sql(sandbox proof)psql -d directus -f sql/iu-core/runtime/110_iu_core_dot_conformance_scan.sql(DOT conformance — D9 row forfunctionmust report 54/54, ok=true)- Then proceed to
ops/governance-promotion-package-8000x/for the actual DIEU promotion.
Rollback
psql -d directus -f sql/iu-core/rollback/026_compensation_primitives.rollback.sql
Safe at any time:
- Both functions are NOT called by triggers or generated columns.
iu_lifecycle_logrows written by them remain in place (metadata-only retention).- DOT scan after rollback will report
functioncount = 52 (back to 144 total) — re-running the macro re-applies migration 026 without loss.