Skip to content

feat(dlv): an unresolved settlement fences the trader parent, and a lost commit stays fenced - #774

Merged
cryptskii merged 1 commit into
mainfrom
feat/quorumbind-fence-and-recovery
Sep 6, 2026
Merged

feat(dlv): an unresolved settlement fences the trader parent, and a lost commit stays fenced#774
cryptskii merged 1 commit into
mainfrom
feat/quorumbind-fence-and-recovery

Conversation

@cryptskii

Copy link
Copy Markdown
Collaborator

PR 3 of the QuorumBind restoration. The initiating-trader parent fence (Req 6.23), durable INDETERMINATE (Req 16.4), and the restart-recovery primitives (Req 16.5) that the one-shot settlement-register path never had. Builds on the PR 2 engine (#773).

What the fence is

Before Class K issues the first mutating binding op for a bundle B, it durably records a local settlement fence over the initiating trader's own sovereign chain:

F_B = (trader_chain_id, trader_parent_state_commitment, b, tx_id)

While the DLV transaction is unresolved, no different successor may advance from the fenced trader parent, even under a fresh intent or nonce. Tripwire remains the underlying one-successor rule; the fence stops a conforming client from accidentally advancing the parent while the quorum result is in doubt (Theorem 18.2).

The load-bearing rule is Req 6.23 (4): COMMITTED(B) fixes the permitted continuation to the exact committed trader successor, and the quorum result alone does not consume the fence — it is released only when that exact successor is accepted through ordinary DSM bilateral advancement. A different successor can never consume the fence.

Shape (same split as PR 2)

  • Pure core (dsm::dlv::trader_fence) — the fence's states, the legal next_state transitions, and the verdict a caller consults. No I/O, no clock. This holds the safety-relevant transitions, including the refusal (WrongSuccessor) that stops a different successor from consuming the fence.
  • Durable adapter (dsm_sdk::storage::client_db::trader_parent_fence) — the fence's rows, modelled on dlv_close_intent: place_fence writes it before anything mutates (INSERT OR IGNORE, so a retry keeps the first frozen inputs); record_event applies the core transition and persists the new state, permitted successor, and ballot; active_verdict is the advancement gate; list_unresolved_fences is the restart work list. The persistence reuses next_state, so it cannot legalize a transition the core forbids.
  • Orchestration (quorum_bind_runner::run_fenced) — places the fence before the first mutating op (FenceNotPersisted if it cannot persist, so the transaction never begins), drives the engine, and records the outcome as a FenceEvent: COMMITTED fixes the successor, ABORTED/CONFLICT_FINAL release without advancing, an unresolved run keeps the parent fenced as INDETERMINATE with the ballot persisted so restart never reuses one.

Conformance

  • trader-parent-fence/ (Req 21.6) — the fence is persisted before the first mutating call; a fresh tx_id (fresh intent) on the same parent does not lift it; a committed fence permits only the exact successor; that exact acceptance releases it and the gate goes clear; abort releases without advancing and leaves no unresolved work.
  • bind-indeterminate/ (Req 21.3) — a transport that lands the accept on a quorum but loses the response and goes dark. Run 1 reports the transaction unresolved (mutated: true) and the fence stays FENCED — a fresh intent cannot pass. The value is in fact chosen on the members. A resumed run for the same transaction, above the persisted ballot, discovers the chosen value and reaches COMMITTED — completion, not a second value (Theorem 18.4).

Tests

  • Core (dsm::dlv::trader_fence, 7): unresolved keeps fenced; commit permits exactly the committed successor and no other; only the exact successor consumes the fence; abort/conflict release without advancing; acceptance-before-commit is illegal; terminal states do not regress; unresolved states are the recovery work list.
  • Durable (trader_parent_fence, 5): a fenced parent blocks every successor and a fresh intent does not lift it; commit permits only the exact successor and acceptance releases; INDETERMINATE keeps the fence and restart recovery restores it with the bumped ballot and recovery inputs; abort leaves no unresolved work; the first placement freezes the recovery inputs.
  • Runner (quorum_bind_runner, +2): run_fenced commits and the fence permits only that successor, then acceptance clears it; the bind-indeterminate end-to-end above.

Verification (Rust 1.98.0)

  • root make lintexit 0
  • workspace board (--workspace --exclude dsm_storage_node --release) — 4015 passed, 0 failed (75 suites)
  • production_safety_checks (all-features clippy + TLA+) — exit 0 (TLA+ included)

Not in this PR (by design)

  • Wiring the gate into the settle path. active_verdict is the gate a trader-successor creation must consult; the live call site is the settle path rewritten to QuorumBind — PR 5.
  • The production restart driver. list_unresolved_fences + run_fenced resume are the primitives; the startup loop that resolves each fence's storage set from the catalog and retrieves the immutable bundle bytes via GetImmutable lands with the HTTP transport — PR 4.
  • Switching the three settle paths and deleting the settlement-slot register — PR 5.

…ost commit stays fenced

Rev 15 Req 6.23 / 16.4 / 16.5, Theorem 18.2: the initiating-trader parent
fence, durable INDETERMINATE, and the restart-recovery primitives the one-shot
settlement-register path never had. Builds on the PR 2 engine.

Before Class K issues the first mutating binding op for a bundle B, it durably
records a fence over the initiating trader's own chain. While the DLV
transaction is unresolved, no DIFFERENT successor may advance from the fenced
parent, even under a fresh intent or nonce. The load-bearing rule (Req 6.23(4)):
COMMITTED fixes the permitted continuation to the EXACT committed trader
successor, and the quorum result alone does not consume the fence — only that
exact successor, accepted through ordinary DSM bilateral advancement, does. A
different successor can never consume it. Tripwire remains the underlying rule.

Same split as PR 2:
- Pure core (dsm::dlv::trader_fence): FenceState/FenceEvent/next_state/verdict.
  Holds the safety-relevant transitions, including the WrongSuccessor refusal.
- Durable adapter (client_db::trader_parent_fence), modelled on dlv_close_intent:
  place_fence writes before anything mutates (INSERT OR IGNORE keeps the first
  frozen inputs); record_event applies the CORE transition and persists state,
  permitted successor, and ballot; active_verdict is the advancement gate;
  list_unresolved_fences is the restart work list. Persistence reuses next_state,
  so it cannot legalize a transition the core forbids.
- Orchestration (quorum_bind_runner::run_fenced): places the fence before the
  first mutating op (FenceNotPersisted if it cannot persist), drives, and records
  the outcome; an unresolved run keeps the parent fenced as INDETERMINATE with
  the ballot persisted so restart never reuses one.

bind-indeterminate (Req 21.3): a transport lands the accept on a quorum but loses
the response and goes dark. Run 1 is unresolved and the fence stays FENCED; the
value is chosen on the members; a resumed run above the persisted ballot
discovers it and reaches COMMITTED — completion, not a second value.

Rust 1.98.0: workspace board 4015/0, make lint 0, production_safety_checks 0.

Wiring the gate into the settle path (PR 5) and the GetImmutable restart driver
(PR 4) are not in this change.
@cryptskii
cryptskii merged commit 68783fc into main Sep 6, 2026
16 checks passed
@cryptskii
cryptskii deleted the feat/quorumbind-fence-and-recovery branch September 6, 2026 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant