What
Four Stage D readers gate on collector_line_ocr but read the fields of six extractors ungated.
That is: the guard checks that one extractor ran, then the body reads fields belonging to five others without checking whether they ran. A missing field from an unchecked extractor is read as if it were a measured absence.
Two of the four degrade in the STRICT direction — the missing input makes the reader more confident, not less.
The permanent-wrong-answer case
The worst instance: a missing artist_ocr makes a real old-frame card fail a frame-mismatch veto. The card is skipped. And that skip reason is deliberately not rescannable — by design, so the pipeline does not churn on it.
So the wrong answer is PERMANENT for that content hash. Re-running the pipeline does not fix it. Fixing the extractor later does not fix it. The card stays wrong until someone finds it by hand.
This is the difference between a bug and an unsound reader: a lenient degradation produces a recoverable miss; a strict degradation behind a non-rescannable skip produces an irrecoverable one.
The fix already exists in this codebase
The correct pattern is REQUIRED_EXTRACTOR_KEYS — declare the extractors a reader depends on, and abstain (rather than decide) when any of them did not run. Three other modules already implement it. These four readers simply do not use it.
What would close it
- Each of the four readers declares its real extractor dependency set via
REQUIRED_EXTRACTOR_KEYS — all six extractors whose fields it reads, not just collector_line_ocr.
- Missing dependency ⇒ abstain, never a strict verdict.
- A test per reader asserting that with a dependency absent the reader abstains, specifically covering the
artist_ocr-absent / old-frame / frame-mismatch-veto path.
- A remediation sweep for cards already carrying the permanent non-rescannable skip issued under the strict degradation — a one-off retraction, since the normal rescan cannot reach them. Size the affected population as part of the fix; it is currently unmeasured.
LIVE vs LATENT
LIVE in code on master. The permanent skips are latent in proportion to how often the unchecked extractors have failed — which is exactly the number item 4 above must measure. Note 10,127 cards have no ImageEvidence row at all (filed separately), so "extractor did not run" is not a rare state.
Refs
What
Four Stage D readers gate on
collector_line_ocrbut read the fields of six extractors ungated.That is: the guard checks that one extractor ran, then the body reads fields belonging to five others without checking whether they ran. A missing field from an unchecked extractor is read as if it were a measured absence.
Two of the four degrade in the STRICT direction — the missing input makes the reader more confident, not less.
The permanent-wrong-answer case
The worst instance: a missing
artist_ocrmakes a real old-frame card fail a frame-mismatch veto. The card is skipped. And that skip reason is deliberately not rescannable — by design, so the pipeline does not churn on it.So the wrong answer is PERMANENT for that content hash. Re-running the pipeline does not fix it. Fixing the extractor later does not fix it. The card stays wrong until someone finds it by hand.
This is the difference between a bug and an unsound reader: a lenient degradation produces a recoverable miss; a strict degradation behind a non-rescannable skip produces an irrecoverable one.
The fix already exists in this codebase
The correct pattern is
REQUIRED_EXTRACTOR_KEYS— declare the extractors a reader depends on, and abstain (rather than decide) when any of them did not run. Three other modules already implement it. These four readers simply do not use it.What would close it
REQUIRED_EXTRACTOR_KEYS— all six extractors whose fields it reads, not justcollector_line_ocr.artist_ocr-absent / old-frame / frame-mismatch-veto path.LIVE vs LATENT
LIVE in code on master. The permanent skips are latent in proportion to how often the unchecked extractors have failed — which is exactly the number item 4 above must measure. Note 10,127 cards have no
ImageEvidencerow at all (filed separately), so "extractor did not run" is not a rare state.Refs