RS4A-08 — Durable Failure-Audit Sink Contract v0.2 — 2026-06-21
RS4A-08 — Durable Failure-Audit Sink Contract v0.2 — 2026-06-21
Macro: RS4A · Mục tiêu H
Deliverable: 08 of 14 · design-only (does NOT patch event_outbox, does NOT create a ledger)
Builds on: RS3B-06 sink selection (event_outbox lead, SINK_CANDIDATE_SELECTED_FAIL_CLOSED).
Gate: REGISTRATION_HOLD · REGISTRATION_CAN_PROCEED = NO
Status: SINK_CANDIDATE_SELECTED_FAIL_CLOSED — event_outbox remains the lead reuse candidate; live reads confirm no enforced immutability/retention, so the sink stays fail-closed. No new ledger.
1. Requirement (from the phase model)
RS4A-04 Phase 5 requires that a registration failure — especially a rolled-back Phase-3 transaction — leaves a durable audit record written outside the rolled-back transaction (RS2-PATCH1 Codex R3). The registrar source writes no audit at all (D20). The sink must be reused from an existing surface (no new ledger until reuse is exhausted).
2. Live candidate evidence (my own read-only reads, 2026-06-21)
| Candidate | Live fact | Immutability | Verdict |
|---|---|---|---|
event_outbox |
215,647 rows; only one user trigger: trg_event_outbox_type_validate BEFORE INSERT → fn_event_type_validate(); has safe_payload jsonb + payload_classification (payload-safety); normal heap (writable after a Phase-3 rollback) |
none (no UPDATE/DELETE-blocking trigger/constraint) | lead candidate, fail-closed |
registry_changelog |
mutable by design (resolve columns resolved/resolved_by/resolved_at); birth/label AFTER INSERT triggers |
none (explicitly mutable) | rejected as immutable sink |
governance_audit_log |
1 row; no triggers | none | rejected (no immutability, weak payload safety) |
iu_route_attempt |
retry ledger UNIQUE(idempotency_key,attempt_no) |
n/a | rejected (wrong domain) |
event_outbox scores highest on schema fit + payload safety + post-rollback writability + already-exists (no new ledger). But it fails immutability and retention/idempotency-dedup — the same as every candidate.
3. Required sink properties (criteria the selected sink must satisfy)
| Property | Requirement | Live status on event_outbox |
|---|---|---|
| Append-only / immutability | UPDATE/DELETE blocked by trigger or revoked grants | absent — only BEFORE INSERT validate |
| Restricted writer | enumerable, restricted grant for the registrar role | not enumerable from read-only |
| Retention | covers the replay horizon; no silent purge | unproven |
| Idempotency / dedup | a dedup key for failure records (one per (logical_request_key, phase, run_id)) |
absent (only PK on id) |
| Payload safety | classified payload, no secrets, forbidden-key guard | present (safe_payload + payload_classification) |
| Post-rollback writability | writable outside a rolled-back txn | present (heap table) |
| No-activation lane | the audit write must NOT use an event_type/delivery_lane that triggers downstream execution |
must be proven per chosen lane |
4. Failure-audit envelope shape (design)
failure_audit_envelope {
event_domain: "dot_registration",
event_type: "registration_failure", # MUST be a non-executing audit type (no downstream automation)
delivery_lane:"audit", # MUST NOT be an execution lane
severity: "warn" | "error",
subject_table:"dot_tools",
subject_ref: <dot_code | null>,
actor_ref: <registrar run identity>, # event_outbox.actor_ref is NOT NULL
correlation_id:<run_id>,
safe_payload: { phase, reject_codes[], logical_request_key, attempt_id,
artifact_hash_attested?: <trusted_attested only>, owner_envelope_ref?, apr_ref? },
payload_classification: "governance_audit",
dedup_key: H(logical_request_key, phase, run_id) # required for idempotent failure records
}
Rules: never write request_proposed.* secrets; artifact_hash in payload is the attested value or omitted; the envelope is advisory forensics, not an authority record.
5. Decision — harden by contract, or stay candidate?
Decision: event_outbox is SELECTED as the lead reuse candidate but STAYS FAIL-CLOSED until an append-only/immutability + retention + dedup hardening is separately designed and Owner-approved. It can be hardened by contract (an UPDATE/DELETE-blocking trigger + restricted writer grant + dedup unique on dedup_key + retention policy) without a new ledger — but that hardening is its own separately-born, separately-tested, separately-rollback-able block (no mega-coupling), and RS4A does not author it (no patch, no DDL). Until then, AUDIT_SINK_UNAVAILABLE fires whenever durable append-only audit cannot be guaranteed, and registration must not report success with audit unwritten.
6. Status
- Sink contract v0.2:
SINK_CANDIDATE_SELECTED_FAIL_CLOSED—event_outboxlead; immutability/retention/dedup must be proven before use. - G6 remains OPEN. No
event_outboxpatch, no new ledger, no DDL. GateREGISTRATION_HOLD·CAN_PROCEED = NO.