Skip to content

Refuse to publish a database that disagrees with its own source (W1d) - #5

Merged
jrgutier merged 1 commit into
fix/pdb-torn-file-gatefrom
fix/prepublish-playlist-oracle
Jul 31, 2026
Merged

Refuse to publish a database that disagrees with its own source (W1d)#5
jrgutier merged 1 commit into
fix/pdb-torn-file-gatefrom
fix/prepublish-playlist-oracle

Conversation

@jrgutier

Copy link
Copy Markdown
Owner

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_playlists intended — and both sides of that comparison derive from track_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_track and the database's own path index, never consulting that map.

Demonstrated, not asserted

With insert_tracks returning a swapped map:

Configuration Result
Intent gate only (new check stubbed out) publishes the corrupt database
Both gates refused — 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 previous m.db byte-for-byte intact — asserted on the bytes, not on the absence of an exception. build_library wraps it as FatalError; convert maps that to exit 2.

One implementation, two reactions

The comparison moved into 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. 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 when test_verify.py was 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 verify remains the field-level check.

Verification

  • Real 3,673-track stick, read-only: 45 playlists, 0 problems, 1.4 s (the incident convert took ~2.5 min, so the cost is negligible)
  • 704 tests, 88% branch coverage, playlist_check.py at 100%, ruff + mypy clean
  • Drop rules traced against writer/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

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
@jrgutier
jrgutier changed the base branch from main to fix/pdb-torn-file-gate July 31, 2026 20:19
@jrgutier
jrgutier merged commit c93cc4e into fix/pdb-torn-file-gate Jul 31, 2026
9 checks passed
@jrgutier
jrgutier deleted the fix/prepublish-playlist-oracle branch July 31, 2026 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant