DC-NODE-10
DC derived enforcedAfter the feed validation/admission advances the node spine (a block ingested -> state.receive evolved), the next forge MUST derive the successor header position -- block_no (and the chain_dep/ledger it self-accepts against) -- from the EVOLVED admitted node-spine state (state.receive: the evolved chain_dep + ledger), NOT the stale WarmStart baseline (recovered.chain_dep / recovered.ledger). The successor block_no = the evolved chain_dep.last_block_no + 1; the prev_hash = the durable selected tip's hash. RecoveredTipMissingBlockNo is reserved for a genuinely malformed recovered state and MUST NOT fire for a feed-advanced tip (the feed sets the evolved block_no). No guessed block_no, no unwrap_or(1), no synthetic numbering. The genesis-successor cold-start (BOTH tips None -> block 0 + PrevHash::Genesis) is UNCHANGED (DC-NODE-08). The seed-epoch PoolDistr + eta0 (DC-CINPUT-02b / DC-CINPUT-03) are per-epoch and unchanged -- valid for the in-epoch successor; cross-epoch is off-epoch fail-closed (DC-EPOCH-03). NARROW: selects the evolved admitted chain state for the forge; no other forge-semantics change, no durable-recovery / WAL change (the ChainBreak-on-restart is a SEPARATE N-U durability concern).
- Source
docs/clusters/PHASE4-N-F-G-Q/cluster.md
- Cluster
- PHASE4-N-F-G-Q
- Introduced in
- PHASE4-N-F-G-Q
Enforcement trace
Tests 2
- forge_successor_reads_evolved_spine_block_no_not_stale_baseline_g_q
- forge_one_from_recovered_cold_start_is_block_zero_genesis
Cross-references
Evidence notes
PHASE4-N-F-G-Q S1 (2026-06-04). Capture-first: with G-P in, the feed ingests block 0 (slot 107405) + advances the node-spine tip; the forge then failed relay run-loop sync step failed (RecoveredTipMissingBlockNo). FORGE-TIP-DIAG (instrument, reverted) PROVED the desync: tick 2 (post-ingest) selected_tip=Some(107405), baseline_last_block_no=None (forge_header_position read recovered.chain_dep.last_block_no, node_sync.rs:586), spine_last_block_no=Some(BlockNo(0)) + spine_last_slot=Some(SlotNo(107405)) (the evolved state.receive.chain_dep the feed advanced). The durable ChainTip = {hash, slot} (no block_no), so the durable tip can't supply it. FIX: the relay loop threads the evolved state.receive (chain_dep + ledger) into forge_one_from_recovered; the forge derives the successor block_no + self-accept chain-state from the evolved spine, not the WarmStart baseline; the recovered seed sidecar still supplies the per-epoch PoolDistr (DC-CINPUT-02b). The relay-loop forge tests now build the spine from the recovered base (l5_forge_spine, mirroring the real node) -- a latent test inconsistency (genesis spine vs block-0 recovered) the fix exposed. forge_successor_reads_evolved_spine_block_no_not_stale_baseline_g_q proves: forge_header_position(Some(tip), Some(0)) -> block 1; the forge reads the evolved block_no (Some(0) -> NOT RecoveredTipMissingBlockNo) vs the stale baseline (None + tip -> RecoveredTipMissingBlockNo, the pre-G-Q bug). No VRF/eta0/Step-5/6/7/durable-recovery change. CE-G-Q-2 (live C1 past RecoveredTipMissingBlockNo; block 1+) stays operator-gated; no RO-LIVE flip; no acceptance claim without correlate. The ChainBreak-on-restart (the feed-ingested block 0 does not chain to the seed WAL entry) is a SEPARATE N-U durability slice (dirty store preserved at ade-inputs/{snap,wal}.go-p-ingested-dirty). LIVE-CONFIRMED 2026-06-04 14:28Z: the C1 rerun shows RecoveredTipMissingBlockNo count 0; 97 forge ticks, clean exit 0 (the FIRST non-crashing run); 2 blocks produced; the follower connected to Ade's :3002 serve + ChainSync.Client.DownloadedHeader. The next failure is SEPARATE (the follower REJECTS Ade's served chain: HeaderEnvelopeError UnexpectedBlockNo) -> PHASE4-N-F-G-R (served-chain header-sequence / follower-intersection fidelity), NOT a DC-NODE-10 gap; no acceptance claim (follower rejected, correlate = no adoption).