Invariants / DC-NODE-14

DC-NODE-14

DC derived enforced

Every claimed forge parent must be servable or peer-intersectable in the durable served lineage. A --mode node forge may only build on a parent a Haskell peer can FindIntersect: the followed peer tip (a durably-stored StoredBlock written by pump_block, AE.A) or a recovered anchor made intersectable (AE.B). The served chain must expose that parent as an intersect point from which the peer rolls forward onto the forged successor; the recovered snapshot anchor is never served as a chain head a peer cannot intersect. PARTIAL after AE.A (followed-tip lineage clause enforced); ENFORCED after AE.B (recovered-anchor clause).

Source

docs/clusters/PHASE4-N-AE/cluster.md; docs/planning/phase4-n-ae-slice-a-invariants.md; docs/planning/c2-local-discovered-gaps.md

Introduced in
PHASE4-N-AE

Enforcement trace

Tests 4

  • served_chain_intersects_at_followed_tip_and_rolls_to_forged
  • recovered_anchor_is_not_peer_intersectable
  • forged_successor_on_recovered_anchor_is_not_peer_adoptable
  • recover_follow_serve_forged_parent_intersectable

Cross-references

Evidence notes

PHASE4-N-AE.A (2026-06-06): PARTIAL -- followed-tip lineage clause enforced (served_chain_intersects_at_followed_tip_and_rolls_to_forged: intersect([T])==Some(T), next_after(T)==forged T+1). PHASE4-N-AE.B (2026-06-07): ENFORCED -- the recovered-anchor/forge-parent clause closes the umbrella rule. Root cause confirmed LIVE (CE-A5 c2ae8: the relay rejected HeaderEnvelopeError (UnexpectedBlockNo (BlockNo 19) (BlockNo 0)) and fell back to Origin because the forged successor's parent was a snapshot-only point with NO servable StoredBlock; seed_to_snapshot persists a snapshot keyed by slot only). Fix = Option B (FindIntersect-ONLY, proof-gated): ChainDbServedSource::intersect projects the prev_hash of the EARLIEST servable StoredBlock (the forge parent) as a FindIntersect point IFF a real servable successor exists; it NEVER serves bytes for it (get_block_by_hash/serve_range stay empty -> BlockFetch refuses structurally; no synthetic StoredBlock). recover-only (no successor) -> no projection (recovered_anchor_is_not_peer_intersectable: intersect==None, fail-closed). forged_successor_on_recovered_anchor_is_not_peer_adoptable: intersect(parent)==Some + next_after==forged successor + parent never a StoredBlock. recover_follow_serve_forged_parent_intersectable (live-style run_relay_loop follow -> serve): the live follow DOES store the followed lineage as a servable StoredBlock AND the parent is FindIntersect-able via the projection (resolves open-obligation #2). Additive BLUE: ade_ledger::block_validity::DecodedBlock.prev_hash exposed (already parsed) for the proof. Gate ci_check_recovered_anchor_intersectable.sh fences FindIntersect-only + proof-gated + no synthetic bytes. PHASE4-N-AE.E (2026-06-07): the recover->follow->forge->serve->ADOPT path is now proven END-TO-END LIVE -- a real cardano-node 11.0.1 relay AddedToCurrentChain an Ade-forged successor (CE-A5 manifest, venue c2ae18, block 17 @ slot 421). The final closer was the DC-PROTO-10 chain-sync-server FindIntersect cursor fix (the relay's FindIntersect resolved at the followed/forged parent but the server failed to thread the intersect into its read cursor -> served block 0 -> UnexpectedBlockNo); ChainDbServedSource::intersect/next_after (this rule) were verified correct on the live store. Evidence: docs/evidence/phase4-n-ae-ce-a5-relay-adoption.{md,jsonl}.