fix(rank): expire preserved past deadlines before shortlisting
What changed and why
A valid scored result with deadline: null preserves the stored deadline. If that deadline has already passed, apply currently ranks the job, and the following sweep cannot expire it because rescored keys are excluded.
Expire the job before writing ranking metadata. Preserve the existing behavior for today's deadline, future dates, explicit extensions, invalid dates, scoring errors, and dry runs. Includes three regression methods and an Unreleased changelog entry.
Failing case / reproduction
The regression drives the documented candidates → apply → sweep --write --exclude path. A stored deadline of 2026-09-02, --today 2026-09-03, and a schema-valid scored result with deadline: null reproduce the problem for new, ranked, and expired entries selected with --all. The scoring contract permits a null deadline; absence preserves the stored date. The replay uses neutral state and scoring JSON, rather than a live agent or portal call.
test_scored_keys_keep_past_deadlines_out_of_the_shortlist exercises those CLI calls. The additional boundary/dry-run test supplies the fourth failing baseline subtest; the error-handling test is a passing control.
Verification
Validation: hosted baseline/fixed qualification passed all 18 native jobs and the strict comparison. Python 3.10–3.14 preserved all 552 original case identities and outcomes; fixed runs contain 555 cases, including three new methods, with the same 13 existing skips. The literal new tests produce four failing baseline subtests; the 29 original owning methods remain unchanged. Both LaTeX environments, repository guards, and all six Bun typechecks and suites passed, preserving 326 test identities and outcomes (all pass; no skips). Bun suites ran under identical loopback-only Linux network namespaces with the original tests and inner bun test command unchanged; this validates offline fixtures and argument handling, without contacting portals.
Only tools/rank_state.py, its owning test file, and CHANGELOG.md are included. The qualification harness remains on a separate personal-fork branch.