KB-5CDA

RS4A-07 — Replay / Nonce / Attempt Contract v0.2 — 2026-06-21

8 min read Revision 1
rs4areplayauthorization-noncelogical-request-keyattempt-idv0.2fail-closeddesign-only2026-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) (equivalently UNIQUE(replay_key) where replay_key = H(protocol_version, logical_request_key, canonical_operation, canonical_target, deployed_artifact_hash, owner_or_approval_binding, run_id)attempt_id is 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 with attempt_no(key,1),(key,2)… all insert ⇒ incrementing attempt_no reuses the same key (REPLAY_ATTEMPT_NO_BYPASS); the opposite of single-use.
  • Wrong domain: IU message routing (route_kind inbound/outbound), not DOT registration.
  • Binds no logical_request_key/authorization_nonce/run_id/operation/canonical_target/deployed_artifact_hash/owner-approval columns; 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 + separate authorization_nonce_unique + attempt_id_not_unique_for_effectdesign-complete.
  • iu_route_attempt rejected; REPLAY_SURFACE_REQUIRED_NOT_PRESENTREPLAY_DOMAIN_FAIL_CLOSED.
  • G5 remains OPEN. No table created. Gate REGISTRATION_HOLD · CAN_PROCEED = NO.