Skip to content

Stage C: run-scope the resume filter so a bulk run redoes work from scratch - #645

Merged
WilfordGrimley merged 1 commit into
masterfrom
fix/stage-c-run-scoped-resume
Jul 30, 2026
Merged

Stage C: run-scope the resume filter so a bulk run redoes work from scratch#645
WilfordGrimley merged 1 commit into
masterfrom
fix/stage-c-run-scoped-resume

Conversation

@WilfordGrimley

Copy link
Copy Markdown

Item 1 of the "essential major blockers" brief, sourced from PR #616 / docs/reports/2026-07-29-pipeline-coverage-composition-audit.md.

The defect

run_image_evidence_cohort.handle() skipped a card when
MANIFEST_EXTRACTOR_CURRENT_VERSIONS.items() <= extractor_versions.items().

That is identity-scopedhas this card ever been extracted at these versions — not run-scoped. Two consequences:

  1. A run cannot redo work from scratch. The owner's ruling is "a bulk run redoes everything from scratch; flags tell it to narrow" / "prior runs cannot pollute, but the current run can be resumed." PR Run-scoped eligibility, the value-comparing split, and an archive for superseded votes #604 implemented exactly this for Stage D (local_calculate_verdicts._eligible_cards_queryset) and never reached Stage C.
  2. A field added without a version bump is permanently unreachable. ImageEvidence.bleed_diff_mm is NULL on 215,921 of 220,579 rows (97.9%), and 213,131 of those sit on rows whose bleed_class is a confident bleed — so compute_bleed_diff_mm's abstain path does not explain it. All rows carry the single version geometry-bleed-v1. The field and the line writing it were added without bumping it, so every historical row reads as current and is skipped forever.

The fix

already_extracted_card_ids(run_id, only_never_extracted=False) — one predicate, both selection paths. Default scopes to ImageEvidence.run_id (already stamped unconditionally by persist_evidence and transfer_evidence, already db_index=True, so the default path filters in SQL instead of streaming all 220,579 extractor_versions blobs through the process).

  • --run-id defaults to a fresh timestamp ⇒ a plain invocation redoes the catalogue from scratch.
  • Within-run resume is preserved: re-invoke with the killed run's own --run-id and it skips exactly what that run finished. The command now prints its resume line at startup, and --max-rss-mb's help no longer claims a bare re-invocation resumes.
  • --only-never-extracted narrows back to the old identity-scoped predicate, per "default the default things, disable them with flags".

--card-ids-file generalised, not duplicated

The brief noted this flag already forces re-extraction but is "the wrong shape". Forcing re-extraction is now what the default does, so the flag's hardcoded resume-filter bypass is deleted and it reduces to the pure scope narrowing its name promises. Strict gain: a killed targeted re-extraction now resumes, which under the bypass it could never do.

Its existing test (test_card_ids_file_bypasses_the_resume_filter) claimed to prove that bypass while seeding {key: "v1"} — a value that fails the version-aware filter regardless, so the bypass was never actually exercised. Renamed, reseeded with the CURRENT version map, and it now proves the run-scoped mechanism that replaced it.

Does bleed_diff_mm need a version bump?

No — run-scoping alone makes it reachable, because the next from-scratch bulk run re-extracts every card. But that costs ~220k image fetches. compute_bleed_diff_mm reads only card_image.size, and ImageEvidence.width/height are stored and 0% NULL, so a no-fetch arithmetic recompute is identical to re-extraction and is the cheaper close. Recommended as a separate backfill (a write — needs authorisation). A bump remains the correct tool for the --only-never-extracted path, where the stale-v1 trap still applies; it is not needed to close this field.

Verification

mutation expected red result
strip rows.filter(run_id=run_id) (restore identity scoping) prior-run card is redone; null-run_id row; card-ids-file re-extract; end-to-end reprocess 4 failed, 12 passed
ignore only_never_extracted (always run-scope) flag restores identity scoping; null-run_id row 2 failed, 14 passed
drop the manifest-completeness check partial extraction; stale version (×2) 3 failed, 13 passed
restored 16 passed
  • pytest cardpicker/tests/test_run_image_evidence_cohort.py — 67 passed
  • pytest cardpicker/tests/test_stage_e_dispatch.py — 74 passed (the other consumer of the manifest constants)
  • docs_lint.py --strict — clean
  • check_extractor_manifest_sync.py — clean
  • pre-commit (ruff/isort/black/mypy/prettier) — clean
  • No migration.

Docs

docs/features/stage-e-operations.md "Bulk run preconditions" item 3 rewritten (living wiki doc per .github/wiki-publish-map.json). No dated report.

🤖 Generated with Claude Code

https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN

…cratch

`run_image_evidence_cohort`'s resume filter asked an IDENTITY-scoped question -
"has this card ever been extracted at these manifest versions" - so a run could
never redo work, only ever see a shrinking subset of the previous run's pool.
PR #604 established the owner's ruling for Stage D ("a bulk run redoes
everything from scratch; flags tell it to narrow"; "prior runs cannot pollute,
but the current run can be resumed") and never reached Stage C. This applies the
same predicate one layer up, via `ImageEvidence.run_id` (already stamped
unconditionally by `persist_evidence` and `transfer_evidence`, already indexed).

The second consequence this closes: a field added WITHOUT a version bump was
permanently unreachable. `bleed_diff_mm` is NULL on 215,921/220,579 rows (97.9%),
213,131 of them on rows whose `bleed_class` is a confident `bleed`, so the
extractor's abstain path does not explain it - every row carries the one
`geometry-bleed-v1` version, so a v1 manifest written before the field existed
reads as current and is skipped forever.

Within-run resume is preserved and tested: `--run-id <the killed run's id>`
skips exactly what that run finished. The command prints its own resume line at
startup, and `--max-rss-mb`'s help no longer claims a bare re-invocation resumes.

`--only-never-extracted` narrows back to the old identity-scoped predicate as an
opt-in flag, per "default the default things, disable them with flags".

`--card-ids-file` is GENERALISED rather than duplicated: forcing re-extraction is
now what the default does, so the flag's hardcoded resume-filter bypass is gone
and it reduces to the pure scope narrowing its name promises. Strict gain - a
killed targeted re-extraction now resumes, which under the bypass it could not.

Its existing test claimed to prove that bypass while seeding `{key: "v1"}`, which
fails the version-aware filter regardless; it now seeds the CURRENT version map
and proves the run-scoped mechanism that replaced the bypass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN
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.

1 participant