Skip to content

Extract shared crown-eligibility predicates so scoring and live-crown can't drift#454

Merged
entrius merged 3 commits into
testfrom
refactor/crown-eligibility-predicates-450
Jun 8, 2026
Merged

Extract shared crown-eligibility predicates so scoring and live-crown can't drift#454
entrius merged 3 commits into
testfrom
refactor/crown-eligibility-predicates-450

Conversation

@anderdc

@anderdc anderdc commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

What

The per-block scoring replay (replay_crown_time_window) and the per-forward-step live-crown snapshot (snapshot_current_crown_holders) each hand-copied the same two crown-eligibility predicates — executable_check and can_fund. The code documents that these paths must return identical verdicts (otherwise the dashboard's live crown holder diverges from who the ledger actually rewards), but that invariant was enforced only by copy-paste.

This extracts both into a single make_crown_predicates(...) factory in scoring.py, so the invariant is structural rather than convention.

Why

Not cosmetic dedup — the duplication backed a documented correctness invariant. A one-sided edit to the fail-open-on-unknown-collateral rule or the bounds gate would silently desync the live view from the rewarded ledger. This subnet has already been bitten by live-crown-vs-ledger divergence (the v1.0.9 collateral-zeroing mass-recycle, #443).

Changes

  • allways/validator/scoring.py: add make_crown_predicates; both functions call it. Calling it once per direction also removes the loop-variable default-arg binding the snapshot copy needed (eliminates a late-binding footgun).
  • tests/test_scoring_v1.py: new TestCrownPredicateParity — matrix parity test locking the factory to is_executable_rate / min_executable_tao_leg (both directions × bounds set/unset × edge rates × collateral configs), plus explicit fail-open-on-absent and boundary-squat-drop cases.

Scope / risk

  • Pure refactor — no behavior change. Blast radius is emissions weight + dashboard accuracy only; the swap pipeline (vote_initiate / confirm / fulfillment / reservation / slash / timeout) never touches this code.
  • Live-dashboard write cadence unchanged — snapshot_current_crown_holders is still called unconditionally every forward step; only its internals change.

Verification

  • tests/test_scoring_v1.py: 151 passed (incl. 3 new), before and after ruff format.
  • ruff check: clean.
  • Pre-existing embit collection errors in chain-provider test files are unrelated (this diff touches neither chain providers nor their tests).

Fixes #450

anderdc added 3 commits June 8, 2026 12:36
The scoring replay (replay_crown_time_window) and the live-crown snapshot
(snapshot_current_crown_holders) each hand-copied the executable_check and
can_fund eligibility predicates. The two paths must return identical verdicts
or the dashboard's live crown holder diverges from who the ledger actually
rewards, but that invariant was enforced only by copy-paste.

Consolidate both into a single make_crown_predicates factory so the invariant
is structural. Calling the factory once per direction also removes the
loop-variable default-arg binding the snapshot copy needed.

Pure refactor, no behavior change. Adds a parity test locking the factory's
semantics to is_executable_rate / min_executable_tao_leg.

Fixes #450
End-to-end guard for the #450 invariant: feed both replay_crown_time_window
and snapshot_current_crown_holders identical state and assert they resolve
the crown to the same holder (squatter dropped by both via the funding gate).
Catches a future one-sided divergence even if it bypassed the factory.
@entrius entrius merged commit 9a164e0 into test Jun 8, 2026
3 checks passed
@anderdc anderdc deleted the refactor/crown-eligibility-predicates-450 branch June 8, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactor] Extract shared crown-eligibility predicates so the scoring path and live-crown snapshot can't drift

2 participants