Refuse to publish a database that disagrees with its own source (W1d) - #5
Merged
Merged
Conversation
The gate added in 0.3.2 compares the written database against what insert_playlists intended, and both sides of that comparison derive from track_id_map. A mapping fault is therefore invisible to it: the intent and the rows agree with each other while both disagree with the source. The playlist is internally coherent and points at the wrong track. This adds a second, independent check at the same pre-publish point. It recomputes every expected Engine track id from the source track itself, through map_track and the database's own path index, and never consults track_id_map — so it fails exactly where the intent gate cannot. Demonstrated, not asserted: with insert_tracks returning a swapped map, the intent gate passes and the build publishes. Stub out only the new check and the same corrupt build publishes again; leave it in and the conversion is refused with "1 entries written, 1 expected from the source (unexpected track ids [2]; absent track ids [1])". Placement and blast radius -------------------------- It runs on the staged copy before os.replace, so a refusal leaves the previous m.db byte-for-byte intact — asserted on the bytes, not on the absence of an exception. build_library wraps the failure as FatalError, which convert maps to exit 2. One implementation ------------------ The comparison moved to playlist_check.py, which verify.py also calls; verify records findings and keeps checking, the writer aborts. They can no longer disagree about what is wrong. map_track is resolved at call time, matching build.py, because binding it at import made the checker able to run a different mapper than the writer just used — that showed up as an order-dependent test failure and would have made the comparison meaningless rather than independent. What it cannot do ----------------- It compares a parse against itself, so it can never detect that the source file was misread. That is the reader's job (G1d). It is playlist-scoped; verify remains the field-level check. Verified on the real 3,673-track stick, read-only: 45 playlists, 0 problems, 1.4 s. 704 tests, 88% branch coverage, playlist_check at 100%, ruff + mypy clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3aF4y4GdsuJta2bfYUZ8w
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.
Implements W1d from
.omc/plans/playlist-phantom-entry-root-cause.md. Stacks on #4 (G1d torn-file gate).The blind spot this closes
The gate added in 0.3.2 compares the database against what
insert_playlistsintended — and both sides of that comparison derive fromtrack_id_map. A mapping fault agrees with itself and sails through: the playlist is internally coherent and points at the wrong track.This adds an independent check at the same pre-publish point, recomputing every expected Engine track id from the source through
map_trackand the database's own path index, never consulting that map.Demonstrated, not asserted
With
insert_tracksreturning a swapped map:1 entries written, 1 expected from the source (unexpected track ids [2]; absent track ids [1])That counter-probe is what proves the test isn't passing for a trivial reason.
Placement
Runs on the staged copy before
os.replace, so a refusal leaves the previousm.dbbyte-for-byte intact — asserted on the bytes, not on the absence of an exception.build_librarywraps it asFatalError;convertmaps that to exit 2.One implementation, two reactions
The comparison moved into
playlist_check.py, whichverify.pyalso calls. verify records findings and keeps checking; the writer aborts. They can no longer disagree about what is wrong.map_trackis resolved at call time, matchingbuild.py. Binding it at import let the checker run a different mapper than the writer had just used — it surfaced as an order-dependent test failure (passed alone, failed whentest_verify.pywas imported first) and would have made the comparison meaningless rather than independent.What it cannot do
It compares a parse against itself, so it can never detect that the source file was misread — that is the reader's job (#4). It is playlist-scoped;
rb2engine verifyremains the field-level check.Verification
playlist_check.pyat 100%, ruff + mypy cleanwriter/playlists.py— skipped tracks, dropped duplicates, renamed playlists, empty folders and all-skipped playlists all compare clean, so no legitimate conversion is refused🤖 Generated with Claude Code
https://claude.ai/code/session_01V3aF4y4GdsuJta2bfYUZ8w