Invariants / DC-EPOCH-14

DC-EPOCH-14

DC true enforced

Atomic epoch-authority transition + recovery. The node holds exactly ONE owned ActiveEpochAuthority -- the SOLE leadership + header-validation view source -- and crosses an epoch boundary as ONE authoritative state transition. (a) ONE HOLDER: header validation AND leadership/forge resolve the SAME holder, FRESH per authoritative decision (authority.ledger_view() / .pool_distr_view()), never a separately-built view nor a reference stored across the swap. (b) ATOMIC SWAP: at the boundary the holder is promoted IN PLACE (Seed -> Promoted) by the SAME path that derives the bound candidate, verifies the activation predicate, and writes the durable activation WAL record BEFORE the promotion is visible; a failure after the predicate is a terminal halt, never a seed fallback. (c) EPOCH-MATCH GUARD (slot-aware, mode-aware): at a forge decision the resolved authority's epoch MUST relate to the slot's protocol epoch (the SAME EraSchedule::locate the decision uses) per the CANONICAL EpochAuthorityMode -- recovered identically from durable state, NEVER an ambient runtime flag: authority_epoch > slot => terminal PrematurePromotion (both modes); == => proceed; < + ContinuityRequired => terminal MissingPromotion; < + SeedOnly => a graceful no-forge (ForgeNotLeader; the follow loop stays alive). Header validation rejects a wrong-epoch block through the SAME epoch-gated holder (a seed view answers None for an N+1 query -> reject before acceptance). (d) PROMOTION LINEARITY: <= 1 promoted authority per target epoch -- an identical re-promotion is idempotent, a different binding terminal. (e) CROSS-CONSUMER IDENTITY: at a slot, validation + forge resolve the SAME authority epoch AND the SAME active-view canonical hash (epoch alone is insufficient; two N+1 candidates with different bindings both report N+1). (f) RECOVERY EXACTNESS: warm-start reconstructs the promoted authority by RE-DERIVING the candidate from the durable tuple (activation-WAL record + v4 seed sidecar + bootstrap checkpoint + canonical selected-chain window) and promoting ONLY if the re-derived candidate reproduces the record's ENTIRE identity -- a record that merely parses but cannot be RECOMPUTED IDENTICALLY is a terminal halt; no record => the seed stays; never a fall back to the epoch-wrong seed view. Every replay of the same durable inputs makes the same decision and recovers the same authority. This is a hermetic guarantee: it does not prove unattended public Preview continuity (ECA-5).

Source

docs/clusters/EPOCH-CONSENSUS-VIEW/SLICE-ECA-2-3-4-atomic-epoch-authority-transition.md; user directive 2026-06-22 (ECA-2/3/4 ship as ONE mergeable atomic-epoch-authority-transition slice -- construct deterministic inputs -> durably record activation -> atomically publish the authority -> recover the same authority after crash is ONE authoritative state transition; ECA-4 recovery is the second half of the authority contract, not cleanup); user 2026-06-23 (the slot-aware bidirectional + mode-aware guard via a canonical EpochAuthorityMode, not an ambient flag; header validation stays gated-fail-closed, the structured AuthorityEpochMismatch classification deferred as diagnostic refinement)

Enforcement trace

Tests 11

  • authority_epoch_guard_is_mode_aware_and_identity_is_exact
  • cross_consumer_identity_validation_and_forge_resolve_one_authority_view
  • seed_only_sole_view_cannot_validate_n1_header_rejects_before_acceptance
  • forge_continuity_required_missing_promotion_at_n1_is_terminal
  • node_forge_off_epoch_slot_fails_closed
  • recover_at_boundary_round_trips_the_durable_record_and_rejects_a_tamper
  • happy_path_promotes_after_durable_wal
  • crash_before_durable_wal_keeps_seed
  • crash_after_wal_republishes_same_view
  • recovered_view_mismatch_is_terminal
  • recover_at_boundary_wrong_cli_network_magic_is_terminal_no_partial_recovery

Cross-references

Strengthened in