Ruling needed
Deductive backfill's D1 rule claims its name-uniqueness deduction is "cross-verified against Scryfall". It is not. The second half of the check is a count over our own rows, and it has rejected zero candidates in its lifetime.
Fix the check to count from the Scryfall bulk file, or drop the second conjunct and restate the rule honestly?
This matters now: PR #570's re-scoping restored ordinary machine weight to future deductive-backfill runs, so the next run casts weighted votes behind a check that does nothing.
The claim
MPCAutofill/cardpicker/deductive_backfill.py describes the D1 rule as:
name matches exactly one CanonicalCard, cross-verified against Scryfall's own printings_count (not just "our table happens to have one row")
The implementation is:
if len(matches) == 1:
...
if printings_count == 1:
Why the second conjunct is implied by the first
printings_count is "our table happens to have N rows". Test 1 is "one row with this name"; test 2 is "one row with this oracle id". Rows sharing an oracle id share a name, so test 1 implies test 2.
Measured 2026-07-29 (read-only, production 85d88bfe): of the 14,940 CanonicalCards whose name is unique in the table, 14,940 have printings_count == 1. Zero have >1. Zero have 0. Zero lack a sidecar row.
The second conjunct has never excluded anything and structurally cannot.
Why it survived review — the test proves the wrong thing
MPCAutofill/cardpicker/tests/test_deductive_backfill.py contains test_printings_count_greater_than_one_excludes_from_d1, whose comment reads:
printings_count says there are more real printings we haven't imported yet
It builds a fixture with one name row AND printings_count=2 — a state the real importer cannot produce. The test passes, and it certifies a behaviour that can never occur in production. A green test over an unreachable fixture is not coverage.
The root cause: printings_count is a counter over our own rows
MPCAutofill/cardpicker/printing_metadata_import.py.
Measured: the stored column vs a live COUNT(*) GROUP BY canonical_id over cardpicker_canonicalcard — 113,143 agree, 0 disagree. It is a pure denormalisation of our own row count, everywhere in the table. Nothing from the Scryfall bulk file contributes. The fallback branch, else 1, asserts that a row with a null oracle id has exactly one printing in existence.
The field reads as a fact about the world — how many printings exist — and is a fact about our import completeness — how many rows our importer landed. Those differ precisely when the import is incomplete, which is the only case any consumer cares about. It cannot detect its own failure mode, and the D1 gate inherits that blindness.
The docs state the false version
docs/features/printing-tags.md lists printings_count among "the Scryfall fields CanonicalCard doesn't hold", and separately repeats the D1 cross-verification claim. Both sentences are false as written and should be corrected in whichever PR resolves this.
Compounding: there is no import timestamp anywhere
Verified against MPCAutofill/cardpicker/models.py and against information_schema: the four canonical tables and cardpicker_card carry no created_at / updated_at / imported_at column. The only date column is released_at, which is Scryfall's card release date, not an import date.
So "is our reference data stale?" — the question that determines whether printings_count is trustworthy — is unanswerable from the database. Combined with the fact that no run records which reference-data version it computed against (no stamp on PilotRunLedger, CardScanLog, or any vote row), a past verdict's reference frame is unrecoverable.
Options
- Make the check real. Import Scryfall's actual printing count from the bulk file into a separate column with a distinct name, and gate D1 on that. Gives the rule the property it claims. Cost: an import change plus a migration, and it only helps once the bulk file is re-imported.
- Drop the second conjunct and restate D1 honestly as "our catalog holds exactly one printing of this name". One line of code, one line of comment, and the rule stops claiming external verification it does not have. The deduction is then explicitly only as good as our import completeness — which is already the truth.
- Rename
printings_count (e.g. local_printing_row_count) regardless of which of the above is chosen, so the next reader cannot mistake it for a fact about the world, and delete the unreachable test fixture or replace it with one the importer can actually produce.
Option 3 should happen either way. Option 2 is honest immediately; option 1 is what the comment currently promises.
Related
Provenance
Found by a read-only audit for self-referential reasoning, 2026-07-29. Code read at origin/master 6bc3e16; production 85d88bfe; deductive_backfill.py is byte-identical between the two. Every figure is from a read-only SELECT/aggregate.
Full write-up, including the general shape ("a stored field that reads as a fact about the world but is a fact about our own completeness"): docs/reference/self-referential-reasoning.md, findings C3 and C4.
Ruling needed
Deductive backfill's D1 rule claims its name-uniqueness deduction is "cross-verified against Scryfall". It is not. The second half of the check is a count over our own rows, and it has rejected zero candidates in its lifetime.
Fix the check to count from the Scryfall bulk file, or drop the second conjunct and restate the rule honestly?
This matters now: PR #570's re-scoping restored ordinary machine weight to future deductive-backfill runs, so the next run casts weighted votes behind a check that does nothing.
The claim
MPCAutofill/cardpicker/deductive_backfill.pydescribes the D1 rule as:The implementation is:
Why the second conjunct is implied by the first
printings_countis "our table happens to have N rows". Test 1 is "one row with this name"; test 2 is "one row with this oracle id". Rows sharing an oracle id share a name, so test 1 implies test 2.Measured 2026-07-29 (read-only, production
85d88bfe): of the 14,940CanonicalCards whose name is unique in the table, 14,940 haveprintings_count == 1. Zero have >1. Zero have 0. Zero lack a sidecar row.The second conjunct has never excluded anything and structurally cannot.
Why it survived review — the test proves the wrong thing
MPCAutofill/cardpicker/tests/test_deductive_backfill.pycontainstest_printings_count_greater_than_one_excludes_from_d1, whose comment reads:It builds a fixture with one name row AND
printings_count=2— a state the real importer cannot produce. The test passes, and it certifies a behaviour that can never occur in production. A green test over an unreachable fixture is not coverage.The root cause:
printings_countis a counter over our own rowsMPCAutofill/cardpicker/printing_metadata_import.py.Measured: the stored column vs a live
COUNT(*) GROUP BY canonical_idovercardpicker_canonicalcard— 113,143 agree, 0 disagree. It is a pure denormalisation of our own row count, everywhere in the table. Nothing from the Scryfall bulk file contributes. The fallback branch,else 1, asserts that a row with a null oracle id has exactly one printing in existence.The field reads as a fact about the world — how many printings exist — and is a fact about our import completeness — how many rows our importer landed. Those differ precisely when the import is incomplete, which is the only case any consumer cares about. It cannot detect its own failure mode, and the D1 gate inherits that blindness.
The docs state the false version
docs/features/printing-tags.mdlistsprintings_countamong "the Scryfall fieldsCanonicalCarddoesn't hold", and separately repeats the D1 cross-verification claim. Both sentences are false as written and should be corrected in whichever PR resolves this.Compounding: there is no import timestamp anywhere
Verified against
MPCAutofill/cardpicker/models.pyand againstinformation_schema: the four canonical tables andcardpicker_cardcarry nocreated_at/updated_at/imported_atcolumn. The only date column isreleased_at, which is Scryfall's card release date, not an import date.So "is our reference data stale?" — the question that determines whether
printings_countis trustworthy — is unanswerable from the database. Combined with the fact that no run records which reference-data version it computed against (no stamp onPilotRunLedger,CardScanLog, or any vote row), a past verdict's reference frame is unrecoverable.Options
printings_count(e.g.local_printing_row_count) regardless of which of the above is chosen, so the next reader cannot mistake it for a fact about the world, and delete the unreachable test fixture or replace it with one the importer can actually produce.Option 3 should happen either way. Option 2 is honest immediately; option 1 is what the comment currently promises.
Related
Provenance
Found by a read-only audit for self-referential reasoning, 2026-07-29. Code read at
origin/master6bc3e16; production85d88bfe;deductive_backfill.pyis byte-identical between the two. Every figure is from a read-onlySELECT/aggregate.Full write-up, including the general shape ("a stored field that reads as a fact about the world but is a fact about our own completeness"):
docs/reference/self-referential-reasoning.md, findingsC3andC4.