RS4A-07 — Replay / Nonce / Attempt Contract v0.2 — 2026-06-21
RS4A-07 — Replay / Nonce / Attempt Contract v0.2 — 2026-06-21
Macro: RS4A · Mục tiêu G
Deliverable: 07 of 14 · design-only (does NOT create any table/surface)
Builds on: RS3C-07 (Codex C1 resolved at design) + RS3B-05 v0.1.
Gate: REGISTRATION_HOLD · REGISTRATION_CAN_PROCEED = NO
Status: REPLAY_DOMAIN_FAIL_CLOSED + REPLAY_SURFACE_REQUIRED_NOT_PRESENT — the three-identity model is design-complete; no existing surface fits; iu_route_attempt is rejected; no table is created.
1. Three identities (Codex C1) — must never be conflated
| Identity | Meaning | Durable constraint REQUIRED | Must NOT |
|---|---|---|---|
logical_request_key |
what effect is requested — canonical (operation=register_dot, canonical_target, trusted_attested.artifact_hash, owner/approval binding) |
logical_request_key_unique — one committed effect per logical key |
be bypassed by a new attempt or a fresh nonce |
authorization_nonce |
this single authorization grant — issued by the authority, consumed once | authorization_nonce_unique — a separate durable consume row, one consume per nonce |
be reused across two requests; be inferred from the logical key |
attempt_id |
this execution attempt — retry/runtime identity | attempt_id_not_unique_for_effect — attempts may recur; attempt_id is NON-keying for the effect |
ever be the uniqueness axis that admits an effect |
Two independent durable unique constraints are required (the C1 fix vs v0.1's single replay_key):
- Effect axis:
UNIQUE(logical_request_key)(equivalentlyUNIQUE(replay_key)wherereplay_key = H(protocol_version, logical_request_key, canonical_operation, canonical_target, deployed_artifact_hash, owner_or_approval_binding, run_id)—attempt_idis NON-keying). - Authorization axis:
UNIQUE(authorization_nonce)on a dedicated consume record.
A fresh authorization_nonce must not buy a duplicate logical effect: the nonce authorizes; the logical_request_key is single-use. A hash that includes the nonce proves it was present, not consumed once — single-use is a state transition (issued → consumed), so it needs its own constrained record.
2. Why the live surface is NOT fit — iu_route_attempt rejected
Fresh constraint read (consistent with my live checks and RS3C-07 §4):
iu_route_attempt_pkey PRIMARY KEY (id)
iu_route_attempt_idem_uniq UNIQUE (idempotency_key, attempt_no)
iu_route_attempt_no_chk CHECK (attempt_no >= 1)
iu_route_attempt_kind_chk CHECK (route_kind IN ('inbound','outbound'))
iu_route_attempt_status_chk CHECK (status IN ('pending','dry_run','sent','skipped','failed','disabled'))
UNIQUE(idempotency_key, attempt_no)is a retry ledger keyed withattempt_no—(key,1),(key,2)… all insert ⇒ incrementingattempt_noreuses the same key (REPLAY_ATTEMPT_NO_BYPASS); the opposite of single-use.- Wrong domain: IU message routing (
route_kindinbound/outbound), not DOT registration. - Binds no
logical_request_key/authorization_nonce/run_id/operation/canonical_target/deployed_artifact_hash/owner-approvalcolumns; no triggers; writer authority unproven.
Verdict: iu_route_attempt is REJECTED as the registrar replay/authorization store (must-not-do: do not use iu_route_attempt; attempt_id must not bypass logical_request_key). Retry-ledger precedent only.
3. State machine (atomic Phase-1 consume + write together — Codex C2)
A single atomic Phase-3 transaction (RS4A-04) consumes the logical key and the nonce and writes the inert registration together — no "committed consume + failed same-txn registration" split.
| State | Condition | Consume row | Registration row | Retry behavior |
|---|---|---|---|---|
| S0 issue | request arrives | — | — | check consumed-state first |
| S1 pre-commit failure | commit fails / crash before commit | rolled back | rolled back | uncertain-commit recovery: client must re-resolve commit status before issuing a new logical request |
| S2 commit success | atomic commit | durable | durable inert | exact retry returns the durable prior result (idempotent replay) |
| S3 post-commit verify fail | verifier rejects committed row (RS4A-04 P4) | durable (stays consumed) | durable inert (flagged) | same logical key returns failed/compensating; new nonce ⇒ compensation only, no new registration effect |
| S4 concurrent attempt | second attempt_id on same logical key during S0–S2 |
one wins via ON CONFLICT | one row | loser ⇒ ATTEMPT_COLLISION, reads winner's result |
4. Freshness vs consumed-state (Codex C3)
Request/envelope TTL governs whether a new request is admissible; it does not make an already-consumed logical_request_key reusable. The consumed record is retained/tombstoned for the replay horizon (not deleted on staleness). A stale request is rejected as inadmissible; the stale consume row still blocks replay. Consumed = permanently blocking for the horizon. CONSUMED_STATE_ERASED_BY_STALE_REQUEST is a forbidden fail-open (must-not-do: consumed-state must not expire because the request is stale).
5. Failure / adversarial table
| ID | Bad input/state | Expected | Layer | Forbidden fail-open |
|---|---|---|---|---|
| RP-01 | same logical_request_key, new attempt_id |
REPLAY_ATTEMPT_NO_BYPASS / return prior |
R | accepting because attempt differs |
| RP-02 | fresh authorization_nonce, same logical effect |
REPLAY_DUPLICATE (reject duplicate effect) |
R | nonce freshness re-authorizing effect |
| RP-03 | reused (already-consumed) nonce | REPLAY_NONCE_CONSUMED |
R | accepting a consumed nonce |
| RP-04 | nonce not bound to envelope/window | NONCE_UNBOUND |
V/R | accepting a free-floating nonce |
| RP-05 | exact client retry after S2 | return durable prior result | R | creating a 2nd effect |
| RP-06 | crash between insert and commit (S1) | both roll back; uncertain-commit recovery | R | re-issuing without recovery → double effect |
| RP-07 | post-commit verify fail (S3) | logical key + nonce stay consumed; compensation only | R | recreating registration under a new nonce |
| RP-08 | stale request, consume row exists | request inadmissible; replay still blocked | V/R | erasing consumed meaning on staleness |
| RP-09 | two concurrent attempts (S4) | one wins (ON CONFLICT), other ATTEMPT_COLLISION |
R | both commit |
| RP-10 | use iu_route_attempt as the store |
REPLAY_SURFACE_NOT_FIT (reject surface) |
R | treating attempt-ledger as single-use |
6. Required future surface (defined, NOT created)
Must prove ALL: (a) single-use UNIQUE(logical_request_key) (and UNIQUE(replay_key)), with attempt_no/attempt_id non-keying; (b) a separate UNIQUE(authorization_nonce) consume record (C1); (c) columns binding operation, canonical_target, run_id, deployed_artifact_hash, owner_or_approval_binding; (d) registration domain (not IU routing); (e) atomic in-Phase-3 consume with ON-CONFLICT reject; (f) proven registrar writer + append-only/immutability; (g) durable failure audit outside the rolled-back txn (RS4A-08); (h) exact-retry returns prior decision; (i) retention/tombstone for the replay horizon (C3). No table created (reuse-first; if none fits, the surface is REQUIRED_NOT_PRESENT, not invented).
The pure validator is not the replay-state owner (the registrar/Phase-3 owns consume; the validator stays pure).
7. Status
- Replay contract v0.2:
logical_request_key_unique+ separateauthorization_nonce_unique+attempt_id_not_unique_for_effect— design-complete. iu_route_attemptrejected;REPLAY_SURFACE_REQUIRED_NOT_PRESENT⇒REPLAY_DOMAIN_FAIL_CLOSED.- G5 remains OPEN. No table created. Gate
REGISTRATION_HOLD·CAN_PROCEED = NO.