four fixes from the review (and a self-correction):
1) close the connection. reconcile_unverified_history_from_tracks opened a connection with no
finally/close. runs once per boot so GC reclaimed it, but now it's consistent + robust.
2) scope the tracks scan to the review queue. it built lookup dicts from EVERY verified/
human_verified track (~350k on a large library) on every boot while anything is unverified
(the normal state). now it loads the stuck rows first and skips verified tracks whose path
AND basename can't match any queued row, so dicts stay proportional to the queue, not the
library. behaviour identical (all 13 PR reconcile tests still pass).
3) close the title-less basename collision. a title-less history row fell back to filename-only
matching with no ambiguity check, so a generic name like "01 - Intro.flac" could heal a
DIFFERENT song to verified. now a title-less basename heal only fires when that basename is
unique among verified tracks; unique-basename rows still heal (recall preserved).
4) "Clean orphaned" protects force_imported rows (deliberate user decision, keep for human
approval) without weakening the mount-down safety gate. CRUCIAL self-correction: filtering
them out BEFORE the orphan check (my first cut) shrank the checked count below the threshold
and would have let a few unverified orphans be deleted during a mount outage. instead,
find_orphan_history_ids now takes a deletable predicate: protected rows still count toward
checked / all-missing (gate stays strong) but never enter the orphan_ids delete set.
3 new regression tests (title-less collision; deletable protects from delete; protected rows
still count toward the gate). 936 verification/acoustid/history/downloads tests green. builds
on nick2000713's #938.
Second-pass audit of the startup reconcile found a real correctness bug in it:
the basename fallback had NO title guard, so a shared track-number filename
("01 - Intro.flac" in different albums) would heal the WRONG song — marking an
actually-unverified file 'verified' and silently dropping it from the review
queue. This is the exact collision the scan-time matcher (history_match.py)
guards against; the reconcile now mirrors it.
- basename match now requires the history row's title and the candidate track's
title to agree (alphanumeric-lowercase), only when BOTH are present (legacy
rows without a title still fall back to filename-only, like the matcher).
- exact-path matches stay unguarded (same path = same file, unambiguous).
- cheap early-out: skip the tracks scan entirely when no 'unverified' rows exist
(keeps the every-boot cost ~nil on healthy libraries).
3 new tests (collision must-not-heal, titles-agree heals, missing-title falls
back). 8 reconcile tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWJk7EuM7YktQeNyqQwTZY
Complements the AcoustID-scan-time heal: re-links library_history rows still
showing 'unverified' to the verified/human_verified status their file already
carries in the tracks table — matching exact path AND basename, so a file that
moved (media-server import / reorganize) heals even though the stored history
path is frozen. Upgrade-only and non-destructive (no deletes, no bulk
migration).
Why this is needed on top of the scan-time fix:
- It clears the EXISTING backlog (e.g. 5551 rows) on the next restart with NO
re-fingerprinting and no AcoustID API calls — the file's status is already in
tracks from the prior scan; this just propagates it to the frozen history row.
- It covers human_verified files, which the AcoustID scan skips entirely
(file_verif_status == 'human_verified' returns early), so their stale history
rows would otherwise never heal.
Runs once on DB init (cheap, idempotent). 5 real-sqlite tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWJk7EuM7YktQeNyqQwTZY