DC-NODE-41
DC derived enforcedMissing-bridge range re-fetch for winner-descendant recovery (PHASE4-N-AO S14). When a post-ForkChoiceWin WINNING peer (the peer Ade just adopted from) presents a descendant whose parent chain is missing, Ade must EITHER (a) actively re-fetch the missing range from the adopted tip to that descendant via BlockFetch RequestRange(X+1..descendant) to that winning peer and admit it IN PARENT-LINK ORDER through pump_block (each body parent-link + body-hash validated before admit; X+1 then X+2 ... then the descendant), OR (b) remain fail-closed with a structured MissingBridge (closed failure code). It must NOT passively stall forever (the DC-NODE-39 floor alone cannot recover a bridge ChainSync already streamed past -- each block is delivered once, never re-sent), and must NOT admit out of order. The re-fetch is byte-only (BlockFetch transports bytes, NOT truth -- a lying/short/unservable range leaves the structured hold, no admit, no mutation), targets ONLY the winning peer (a non-winning-peer gap takes the unchanged floor path -- no fetch spam on loser orphans), is bounded-retry (deterministic, no spin), and clears the hold + fence ONLY on real admitted progress. S14 is RECOVERY, not selection: S3 (select_best_chain, DC-CONS-03) already decided the winner; S14 never decides a branch wins. pump_block remains the sole admit (BLUE unchanged); the BlockFetch byte machinery is reused from S6 (prefetch_branch_bodies). REPLAY-EQUIVALENT: same served range -> same admitted post-state.
- Source
docs/planning/phase4-n-ao-ce-ao-6-live-gap.md (S11 Fault-2 finding) + docs/clusters/PHASE4-N-AO/S14-missing-bridge-range-refetch.md
- Introduced in
- PHASE4-N-AO
Enforcement trace
Code
Tests 6
- refetched_bridge_admits_in_order
- refetch_failure_structured
- short_refetch_keeps_hold
- lying_refetch_body_rejected
- missing_bridge_triggers_range_refetch
- bounded_retry
Cross-references
Attack rationale
The DC-NODE-39 floor is safe (no mis-admit, no silent stall) but a passive hold cannot recover a bridge ChainSync already passed: a winning-peer descendant that arrived before its never-received bridge strands Ade fail-closed forever -- a liveness denial that, under adversarial or merely unlucky timing, prevents convergence onto the selected winning chain. Active range re-fetch closes it WITHOUT widening trust: the fetch is byte-only (a lying/short range is rejected by parent-link + body-hash validation before any pump_block admit, so a Byzantine peer cannot inject an unvalidated chain), winning-peer-only (S3 already selected, so no loser-orphan turns into a fetch-spam amplifier and no second selector is introduced), bounded-retry (no spin / no resource exhaustion), and clears the hold only on REAL admitted progress (an attempted-but-failed fetch never clears the fence). The floor remains the fail-closed fallback when the range cannot be served.
Evidence notes
Declared at PHASE4-N-AO S14. Motivated by the S12-harness-driven Fault-2 finding: late_bridge_recovers_on_progress admits the late bridge (tip==y_hash) but the un-bridgeable descendant is NEVER admitted (tip!=z_hash), and the dispatch MissingBridge arm issues NO re-fetch -- combined with ChainSync single-delivery, the passive floor cannot converge to a winner-descendant whose bridge was never received (run-1's stall). Enforced at S14 close via 6 hermetic tests (missing_bridge_triggers_range_refetch, refetched_bridge_admits_in_order, short_refetch_keeps_hold, lying_refetch_body_rejected, refetch_failure_structured, bounded_retry) + ci_check_missing_bridge_refetch.sh + a live CE-AO-6 run showing range_refetch_started/completed -> bridge+descendant admitted -> convergence. CN-CONS-03 flips only after the live run.