O8E pre-production hardening (Contabo) — 04-revert-compensation-runbook
O8E Report 04 — Per-cut revert / compensation runbook (G4 / GAP6)
- macro:
v0.6-o8e-pre-production-hardening-bundle - date_utc: 2026-05-21 · host: Contabo
vmi3080463 - gate covered: G4 — GAP6 per-cut revert / compensation
- result: G4 PASS — runbook authored, non-mutating checks validated
1. Scope distinction
sql/lifecycle/rollback_runbook.sql (shipped in the tree) reverses the
DDL bundles A–E — a fresh-environment / recovery procedure, NOT a
per-cut data revert. GAP6 is the missing piece: reverting a single
orchestrator-managed cut/enact at the data level.
Foundational fact (O8C Report 04, re-confirmed): each adapter method owns exactly one atomic transaction. Any failure within a phase ⇒ ROLLBACK, re-raise, zero partial write. Compensation is therefore only needed for phases that already COMMITTED before a later phase failed.
Phase order (mutating): pre_write_backup → cut_leg_a → leg_b_record
→ write_verify → lifecycle_enact.
2. Before-run snapshot (MANDATORY — non-mutating, validated)
Capture to /var/lib/cutter/rollback/<run_id>/pre.json before the run:
-- public mutation surface
SELECT count(*) FROM public.information_unit; -- baseline 158
SELECT count(*) FROM public.unit_version; -- baseline 165
SELECT count(*) FROM public.iu_lifecycle_log; -- baseline 60
-- governance surface (run as cutter_exec — query_pg RO role cannot read it)
SELECT count(*) FROM cutter_governance.cut_change_set;
SELECT count(*) FROM cutter_governance.manifest_envelope;
SELECT count(*) FROM cutter_governance.review_decision;
SELECT count(*) FROM cutter_governance.verify_result;
SELECT count(*) FROM cutter_governance.dot_pair_signature;
-- immutability triggers (catalog — always readable)
SELECT tgname, tgenabled FROM pg_trigger
WHERE tgname IN ('trg_iu_enacted_immut','trg_uv_enacted_immut');
Validated non-mutating (O8E): public counts 158/165/60; both triggers
present + tgenabled='O' (enabled); fn_iu_create + fn_iu_enact exist.
These queries are read-only and were run safely via query_pg this macro.
3. Per-phase failure → compensation matrix
| Failed phase | Already-committed phases | Compensation |
|---|---|---|
pre_write_backup |
none | No DB state changed. Discard backup artifact. HOLD — re-run from clean. |
cut_leg_a |
backup | Adapter ROLLED BACK the whole fn_iu_create fan-out (proven). 0 IU/UV rows. Discard backup. HOLD. |
leg_b_record |
backup, cut_leg_a (IUs committed) | IUs are lifecycle_status='draft' (not yet enacted). Compensation A below. |
write_verify |
backup, cut_leg_a, leg_b_record | IUs draft; governance leg-B rows committed. Compensation A + B. |
lifecycle_enact |
backup, cut_leg_a, leg_b_record, write_verify | If fn_iu_enact fan-out failed it ROLLED BACK (proven) → IUs still draft → as write_verify failure. If it COMMITTED, the run succeeded — no compensation. |
Compensation A — revert committed draft IUs of one cut
Draft IUs are safe to revert: the trg_iu_enacted_immut / trg_uv_enacted_immut
triggers protect only enacted rows, so draft rows of a single
change_set_id can be removed inside one sovereign-gated txn:
BEGIN;
-- guard: refuse if ANY IU of this change_set is already enacted
SELECT count(*) FROM public.information_unit iu
JOIN cutter_governance.cut_change_set_affected_row a
ON a.information_unit_id = iu.id
WHERE a.change_set_id = :cs AND iu.lifecycle_status = 'enacted';
-- if > 0 → ABORT, escalate to sovereign (enacted state is immutable)
DELETE FROM public.unit_version WHERE information_unit_id IN
(SELECT information_unit_id FROM cutter_governance.cut_change_set_affected_row
WHERE change_set_id = :cs);
DELETE FROM public.information_unit WHERE id IN
(SELECT information_unit_id FROM cutter_governance.cut_change_set_affected_row
WHERE change_set_id = :cs);
-- verify counts return to the pre.json baseline BEFORE commit
COMMIT; -- only if the in-txn re-count matches pre.json
NO HARD DELETE BY DEFAULT. Compensation A is the escalation path; the recommended default is a sovereign-reviewed soft-revert — set the cut's IUs to a
superseded/withdrawnlifecycle status if the schema supports it, preserving audit lineage. Hard delete only on an explicit sovereign decision and only while every affected row is stilldraft.
Compensation B — neutralise committed leg-B / verify governance rows
Governance rows (manifest_envelope, cut_change_set, verify_result,
dot_pair_signature) are an append-only audit ledger. They are not
deleted. Compensation: append a sovereign-signed compensating
review_decision that marks the change_set_id as reverted, cross-
referencing the revert run id. The ledger keeps the full forward+reverse
history. Authoring this compensating-decision writer is a small Mac source
task (see Report 09).
4. Partial governance recording
If leg_b_record / write_verify fail mid-writer, the adapter's single-txn
ROLLBACK leaves zero governance rows for that phase (proven, O8A
test_*_rolls_back_on_failure). There is no partial-governance state to
compensate — only a fully-committed phase needs Compensation B.
5. Disable / HOLD state
On ANY unrecovered failure:
1. execution_enabled → flip back to False immediately (kill-switch re-arm).
2. orchestrator run state → set state=STOPPED, last_error recorded
(StateStore atomic write under fcntl lock).
3. write /var/lib/cutter/locks/HOLD with the run_id + reason.
4. capture post-failure snapshot → /var/lib/cutter/rollback/<run_id>/post.json
5. do NOT retry automatically — route to GPT/sovereign with pre.json+post.json.
6. Non-mutating validation performed (O8E)
pre_snapshot_public_counts: 158 / 165 / 60 — query_pg, read-only OK
immutability_triggers: trg_iu/uv_enacted_immut present, tgenabled=O OK
enact_functions_present: fn_iu_create=1 fn_iu_enact=1 OK
mutating_compensation_run: NOT executed — authored only (correct for O8E)
7. Verdict
gap6_runbook: AUTHORED — before-snapshot, 5 phase-failure paths,
Compensation A (draft revert) + B (ledger compensation),
disable/HOLD procedure
hard_delete_default: NO — soft-revert default; hard delete = sovereign-only,
draft-only
non_mutating_checks: VALIDATED
residual: compensating-review_decision writer = small Mac task
g4: PASS