Invariants / DC-LEDGER-PHASE2-04

DC-LEDGER-PHASE2-04

DC derived enforced

The UTxO authority RETAINS what it destroys on another reader's behalf. A collateral value is authoritative only within [create(x), B), where B is the block whose phase-2-invalid transaction spends x -- so the LAST instant any authority can answer for it is the moment it applies B. The reduced UTxO checkpoint records that TxIn -> Coin binding there, in the SAME durable write transaction that advances its cursor, and the answer is POSITION-INVARIANT thereafter: TxIn -> Coin is immutable over the chain (a TxIn is (blake2b_256(tx_body_bytes), index) and the output it names is created once, by those bytes, with one value), so no cursor position could yield a different value. The retention is scoped to exactly the bindings destroyed over (seed, cursor] -- cleared with the live table by reset_to_bootstrap and seal_bootstrap -- and is invisible to the checkpoint commitment, which names the reduced UTxO. A binding neither the block nor the authority holds is retained as NOTHING: the resolver answers None and the accumulator refuses, naming the input. The reader never depends on where the authority's cursor happens to sit.

Source

docs/clusters/PREPROD-LIVE-2-FORGE-READINESS/SLICE-BND-2d-collateral-positioning-contract.md (INV-BND-2d)

Cluster
PREPROD-LIVE-2-FORGE-READINESS
Introduced in
PREPROD-LIVE-2-BND-2d
Authority surface
the reduced UTxO authority's answer to the BLUE collateral rule; GREEN/RED storage only -- the accumulator still consumes one resolved Coin and owns no UTxO

Enforcement trace

Tests 10

  • the_resolver_answers_after_the_authority_has_already_spent_the_collateral
  • the_accumulator_walk_resolves_collateral_the_authority_already_spent
  • without_the_retention_the_same_walk_still_refuses_and_pins
  • reset_to_bootstrap_clears_the_retention_and_the_replay_rederives_it
  • the_retention_does_not_move_the_checkpoint_commitment
  • an_unheld_collateral_binding_is_retained_as_nothing_never_as_zero
  • an_intra_block_created_collateral_binding_is_retained_from_the_block
  • an_intra_block_created_collateral_binding_is_valued_by_the_block_itself
  • the_real_failing_block_names_its_collateral_binding_for_retention
  • an_ordinary_spend_is_never_named_for_retention

Cross-references

Evidence notes

LIVE BAR MET, so enforced. The decisive line, from a fresh v6 bootstrap plus a warm restart (preprod 2026-08-16, binary 56e0a4e4): cursor 130350114 -> 130550399, epoch_fees delta +4,431,065,989, acc_fp 6e88b763 -> 001e4bf6, cp_cursor=130550441. That last field is the one this slice added and it is what makes the claim a MEASUREMENT rather than an inference: the UTxO authority stood 200,308 slots PAST 130,350,133, and under DC-LEDGER-PHASE2-01 it SPENDS 0326ab20...#1 at that very block, so its live table provably could not hold the entry -- the answer came from the retention. The refusal is also proven still REACHABLE: earlier in the same run, with the authority at 130,118,424 (BEHIND the block), NEITHER source held the binding (it was created after 130,118,424) and the transition refused, named the exact input and left the cursor pinned without halting the node; the trailing forward advance then walked the authority past the block (checkpoint_forward_ms=48774) and the next pass resolved it. That is exactly the bounded, self-clearing residual the slice documents in section 4.3, observed live. THE TEST GAP THIS CLOSES was the reason BND-2c reached the venue at all: every unit resolver in the tree ANSWERS (FixedResolver always returns a value, EmptyResolver proves the refusal path), so nothing covered 'the live authority answers AT WALK TIME'. the_accumulator_walk_resolves_collateral_the_authority_already_spent now drives the REAL production checkpoint advancer over the REAL failing block FIRST -- putting the authority in exactly the position the live co-advancer puts it -- asserts the live-failure condition (cp.get(...) == None) rather than assuming it, and only then runs the REAL accumulator walk, requiring the fee pot to gain exactly the resolved collateral (not the declared fee 904,638), because cursor movement alone would not distinguish the intended transition from a control-flow path that merely stopped refusing. Its paired control, without_the_retention_the_same_walk_still_refuses_and_pins, advances the same checkpoint past the same block the pre-BND-2d way and requires the typed refusal NAMING 0326ab20 with the cursor pinned -- so the refusal is proven REACHABLE rather than retired. NEGATIVE-TESTED, with the required mutations each breaking something: dropping the retention write from advance_block, writing it in a SECOND transaction, retaining AFTER the removals, defaulting an unheld binding to Coin(0), removing the phase-2 scope so every spend is retained, keeping the retention across reset_to_bootstrap, folding it into compute_fingerprint, and making BLUE discard the intra-block value. NOTE ON THE GATE'S OWN BLAST RADIUS: reduced_utxo_checkpoint.rs was ADDED to the store-semantics hashed surface by this slice, so any edit to it also trips ci_check_store_semantics_lock.sh -- the mutation testing therefore scored this gate on its OWN structural failures only, never on the lock confound. SCOPE: this rule is about POSITIONING. It re-opens nothing in DC-LEDGER-PHASE2-01/02/03 -- the Conway rule, the collAdaBalance scalar and the accumulator transition are unchanged and their tests untouched -- and it changes no co-advance pass order, no boundary positioner (DC-EPOCH-32 keeps its separate duty) and no leadership behaviour.

Evidence

  • LIVE PROOF: docs/evidence/run-stores/preprod-live2c/bnd2d-v6-live-proof-collateral-retention.txt (fresh v6 bootstrap + warm restart; cp_cursor=130550441 proves the retention, not the live table, resolved it).

  • The FAILED live run this rule answers: docs/evidence/run-stores/preprod-live2c/bnd2c-v5-live-FAILED-unresolved-collateral.txt -- checkpoint at ~130,550,441, accumulator at 130,350,114, CollateralBalance(UnresolvedCollateralInput{0326ab20...#1}), cursor pinned.

  • Reference rule from Cardano.Ledger.Babbage.Rules.Utxo (Phase2Invalid) + Babbage.Collateral -- docs/evidence/run-stores/preprod-live2c/bnd2-oracle-extraction.md.