Invariants / DC-VIEW-01

DC-VIEW-01

DC derived enforced

LiveLedgerView determinism + epoch-window guard. The view is constructed deterministically from LiveConsensusInputsCanonical.

Two guards on every LedgerView query: (a) Queried epoch != canonical.epoch_no → return None (BLUE then fails closed via MissingConsensusInput). (b) Block slot outside [epoch_start_slot, epoch_end_slot] → runner intercepts before admit and emits AdmissionHalted { reason: CrossEpochUse } (DC-ADMIT-11).

No ambient default view; no cross-epoch silent use.

Source

docs/planning/phase4-n-m-c-operator-pass-invariants.md §1 (I-C4)

Enforcement trace

Tests 5

  • consensus_inputs::view::tests::out_of_window_epoch_returns_none
  • consensus_inputs::view::tests::in_window_epoch_answers_total_active_stake
  • consensus_inputs::view::tests::in_window_per_pool_lookups_return_imported_values
  • consensus_inputs::view::tests::in_window_unknown_pool_returns_none
  • admission::bootstrap::tests::imported_window_schedule_uses_bundle_epoch

Cross-references

Strengthened in

Evidence

  • PHASE4-N-M-SCHED (2026-05-27) strengthens this guard by ensuring the era-schedule reports the bundle's actual epoch_no — without this, LiveLedgerView's correct epoch-window guard would return None for every block in the imported window (because the schedule reported epoch=0 for all slots, never matching inputs.epoch_no). Fix: make_schedule_for_imported_window(epoch_start_slot, epoch_no) now takes the bundle's epoch as a parameter and wires it into EraSummary::start_epoch. Regression test asserts schedule.locate(slot_in_window).epoch == bundle_epoch for non-zero epoch_no.