Re-audit round 3: 5 blind reviewers, 15 fixes, +18 tests — converging

Round 3's two HIGHs: _fill_version resolved versions with the LAST
same-title entry's cues (photo-aware lookup existed since round 1 but
this caller never used it), and the round-2 diff rework let an earlier
row's disagreement consume the exact-version copy a later row matched.
Diff claims now settle strongest-first across all rows (exact matches,
then versionless upgrades, then disagreement/second-copy), unvetoed
bare duplicates stay owned per spec, and updates are withheld with a
manual-fix note whenever any copy of the game already carries a version
(the row edit targets by name and could hit the wrong copy).

Also: entry-to-row pairing matches by photo overlap before position
(titles.json order churn from reshoot filenames could swap editions);
BGGQueueTimeout defers a title like a missing token; DismissStore
writes atomically, mutates memory only after the write, and
quarantines a torn file instead of bricking the server; version-picker
page-limit exhaustion stays retryable; verify's copy-count shortfall
reports once per game (the old guard was dead code); the upload log
header is created atomically; transient version-lookup failures record
a retryable version_error, not terminal version_unknown; extract
isolates per-photo failures and salvages JSON followed by prose; a
state revision counter stops stale poll responses reverting decisions;
plus the shared-predicate/fsio/docstring consolidation and CLI wiring,
live-diff, verify-wiring, and search-guard tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-02 14:55:04 -04:00
parent 65d4cdd5ec
commit 92aaa91a49
24 changed files with 719 additions and 187 deletions
+33
View File
@@ -631,3 +631,36 @@ def test_truncation_separator_chosen_by_position():
heads = _truncation_heads("Blorvath: Quest of the Zzyzx - 2nd Edition")
assert heads[0] == "Blorvath"
assert "Blorvath: Quest" not in heads # the comment's guarantee, now true
def test_reordered_titles_json_cannot_mispair_editions(client, tmp_path):
# run 1 resolves "Catan" from b.jpg; run 2 prepends a NEW conflicting-cue
# "Catan" sighting from a.jpg (sorts earlier). Photo-overlap pairing must
# keep the b.jpg row glued to the b.jpg entry — not hand its resolution
# (and photos) to the newcomer positionally.
data_dir = tmp_path / "data"
data_dir.mkdir()
entry_b = {
"title_raw": "Catan",
"language_hint": "English", # conflicts with a.jpg's German; language
"source_photos": ["b.jpg"], # alone never triggers a versions fetch
}
(data_dir / "titles.json").write_text(json.dumps([entry_b]))
cfg = Config(data_dir=data_dir)
run_resolve(cfg, client=client)
(row,) = read_matches(cfg.matches_path)
assert row["source_photos"] == "b.jpg"
entry_a = {
"title_raw": "Catan",
"language_hint": "German",
"source_photos": ["a.jpg"],
}
(data_dir / "titles.json").write_text(json.dumps([entry_a, entry_b]))
run_resolve(cfg, client=client)
rows = read_matches(cfg.matches_path)
by_photos = {r["source_photos"]: r for r in rows}
assert by_photos["b.jpg"]["match_status"] == "auto" # kept its resolution
assert "a.jpg" in by_photos # newcomer resolved as its own row
assert len(rows) == 2