Wire the attribute-chip casters into an engine; strip the vote from the uncalled wrapper - #654
Merged
Merged
Conversation
…he uncalled wrapper The 2026-07-29 composition audit measured `Old Border`, `Modern Border` and `appropriate-bleed` at ZERO machine rows with no substitute. Cause: the only code that ever cast them was `local_identify_printing_tags.run_pilot` (a live-FETCH pilot, one completed run in its history) and `image_evidence.extract_card_evidence`, which has ZERO production callers - both engines call `compute_card_evidence` + `persist_evidence` directly. Border colour survived the same purge only because `local_layout_class_cast` independently re-derives it, and even that was reachable only from its own standalone command. NEW `local_attribute_chip_cast` (`frame-style-cast-v1`/`bleed-edge-cast-v1`): reads stored `ImageEvidence`, casts frame-style and bleed-edge chips, fetches NOTHING. Modelled on `local_layout_class_cast`, not on the pilot. Reuses `classify_frame_style`/`FRAME_STYLE_TO_TAG`/`FRAME_VOTE_CONFIDENCE`/ `BLEED_EDGE_TAG_NAME`/`BLEED_EDGE_VOTE_CONFIDENCE` verbatim so it cannot drift from the pilot on what a frame class is or what it is worth. Derivable populations, measured read-only 2026-07-29: 133,627 + 9,006 + 2,786 = 145,419 votes, none of which needs an image fetch. It does NOT cast border - that would be a THIRD border channel, and the duplication is the audit's own cull recommendation, not a pattern to extend. TWO IDENTITIES, not one. The bleed chip is negative-only, so under a shared identity a card's frame vote would read as "handled" and permanently strand its bleed chip. REQUIRED_EXTRACTOR_KEYS PER CHIP FAMILY. The frame chip gates on `artist_ocr` as well as `collector_line_ocr`: `illus_anchor_fired` is nullable and `bool(None)` is False, which without the gate classifies every card with no anchor evidence as `modern`. This is item 4's bug class, applied correctly from the start. WIRED INTO THE CONVEYOR. `stage_e_dispatch._run_stage_d` now runs both this caster and `local_layout_class_cast`, so all three chip families are reachable from an engine for the first time. Zero fetches means no envelope/fetch-budget cost. A missing tag seed is caught and logged at ERROR rather than marking a dispatch FAILED over an advisory chip after the printing votes already landed - the counters staying at zero is the observable, not a silent swallow. `extract_card_evidence` -> `fetch_and_compute_card_evidence_for_tests`, and its `cast_border_attribute_vote(...).save()` is deleted. Its test class inverted with it: `test_classified_border_casts_one_vote_per_card` was a green test over a channel that had not existed since the 2026-07-20 fetch/compute decoupling, which is precisely how the hole stayed invisible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN
WilfordGrimley
force-pushed
the
feat/attribute-chip-evidence-caster
branch
from
July 30, 2026 11:11
526d50f to
3a2484a
Compare
This was referenced Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Item 2 of the "essential major blockers" brief, sourced from PR #616 /
docs/reports/2026-07-29-pipeline-coverage-composition-audit.md§1 Q1 and §6.The defect
cast_border_attribute_vote,cast_frame_style_vote,cast_bleed_edge_voteinlocal_fallback.pyare called from exactly two places:local_identify_printing_tags.run_pilot— the live-FETCH pilot, with one completed run in its entire history (2026-07-16).image_evidence.extract_card_evidence— which has ZERO production callers. Both engines callcompute_card_evidence+persist_evidencedirectly and have since the 2026-07-20 fetch/compute decoupling.So no engine could cast any attribute chip. Measured consequence: frame-style and bleed-edge chips at zero rows with no substitute. Border chips survived only because
layout-class-cast-v1independently computes the same thing — and that caster was reachable only from its own standalone management command, so the conveyor could not produce a border chip either.The fix
New
cardpicker/local_attribute_chip_cast.py— an evidence-reading caster, modelled onlocal_layout_class_cast(which reads evidence) and not on the pilot (which fetches). Zero image fetches, booby-trapped in test. Reusesclassify_frame_style/FRAME_STYLE_TO_TAG/FRAME_VOTE_CONFIDENCE/BLEED_EDGE_TAG_NAME/BLEED_EDGE_VOTE_CONFIDENCEverbatim so it cannot drift from the pilot on what a frame class is or what it is worth.Derivable populations (read-only, 2026-07-29):
Modern Border133,627 +Old Border9,006 +appropriate-bleed2,786 = 145,419 votes, none needing a fetch.It does not cast border. That would be a third border channel, and the duplication is the audit's own §6 cull recommendation, not a pattern to extend.
local_layout_class_castremains the border caster; this PR makes it conveyor-reachable instead of duplicating it.Two identities (
frame-style-cast-v1,bleed-edge-cast-v1) written from one pass. The bleed chip is negative-only (~98.7% of cards get a frame vote and no bleed vote), so under one shared identity that frame vote would read as "handled" and permanently strand the bleed chip.REQUIRED_EXTRACTOR_KEYSper chip family. The frame chip gates onartist_ocras well ascollector_line_ocr, becauseillus_anchor_firedis nullable andbool(None)isFalse— indistinguishable from "the extractor ran and found no anchor". Without the gate every card missingartist_ocrreadsmodern. That is item 4's bug class, applied correctly from the start.Wired into the conveyor
stage_e_dispatch._run_stage_dnow runs both casters after the four printing calculators. Zero fetches means no envelope or fetch-budget cost. Order is irrelevant here (neither reads another calculator's output) and the docstring says so.A missing tag seed is caught and logged at ERROR rather than propagating: by that point the printing votes are already written, and letting a
RuntimeErrorout would mark the whole dispatch FAILED over an advisory chip. Not a silent swallow — the counters staying at zero is the audit's own "this channel never ran" signal, and the log names the fix.extract_card_evidenceresolvedRenamed
fetch_and_compute_card_evidence_for_testsand itscast_border_attribute_vote(...).save()deleted, per the brief's first option. The docstring now carries a DO NOT ADD A WRITE HERE note explaining why.Its test class inverted with it.
test_classified_border_casts_one_vote_per_cardwas a green test over a channel that had not existed for four months — a vote cast in an uncalled function looks like a wired channel to any grep, and a passing test looks like proof it works. That is how this stayed invisible.Verification
artist_ocrgate (readbool(None)as a realFalse)_run_stage_dtest_image_evidenceclassanonymous_idRuntimeErrorescapeThe third mutation initially came back green — the test stubbed the fetch to
None, solayout_classwas""and the deleted cast would have returnedNoneanyway. The test was rewritten to force a confidentblackreading, and only then did the mutation go red. Noted because a vacuous test is the same class of problem this whole PR is about.pytest cardpicker/tests/— 3444 passed, 11 skipped (full suite)docs_lint.py --strict— cleanTwo existing tethers fired and were satisfied, not bypassed: the calculator roster tether (
docs/pipeline-fidelity-gate.md) andtest_skip_reason_roster.py. Both new identities and all four new skip-reason constants are registered.Docs (living, per
.github/wiki-publish-map.json— no dated report)docs/features/printing-tags.md— new "Who actually casts the attribute chips" section with the caster/identity table.docs/pipeline-fidelity-gate.md— both new identities in the calculator roster, marked 0 rows / never run.docs/reference/skip-reasons.md— new section for the caster's four values.🤖 Generated with Claude Code
https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN