Classify Engine-added playlists as external edits, not corruption (W3) - #10
Merged
Conversation
verify used to report an Engine DJ desktop-library merge with the same playlist_count discrepancy as a corrupt conversion (measured 2026-07-31: a clean 45-playlist stick became "expected=45 actual=48" after merely opening Engine DJ with a populated desktop library). Training users to read a legitimate Engine feature as corruption teaches them to ignore verify. The discriminator is lastEditTime, now exported from the writer as PINNED_LAST_EDIT_TIME: every playlist row we write carries exactly 1970-01-01 00:00:00, so an extra playlist with any other value was written by Engine DJ. Verify names those rows in an informational "External edits" section — telling the user the conversion is not corrupt and to re-run convert for an exact rekordbox match — and excludes them from playlist_count, so the exit code stays 0. Deliberately NOT keyed on the Playlist.id watermark: the same experiment showed Engine reassigns ids across a merge (ids ran to 84 against our contiguous 1..45), so id position alone would mass-misclassify our own renumbered rows. max(id) survives only as corroborating detail per entry. The classifier cannot launder defects: an extra playlist carrying our pinned epoch, or one of ours with wrong membership, still reports at full severity — covered by tests, alongside the id-reassignment trap. Ships informational (never exit 1) until an Engine in-place migration is confirmed to preserve lastEditTime; a migration rewriting every row would otherwise mass-report. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3aF4y4GdsuJta2bfYUZ8w
jrgutier
force-pushed
the
feat/external-edit-classifier
branch
from
August 1, 2026 05:08
40aba59 to
334897d
Compare
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 W3 from
.omc/plans/playlist-phantom-entry-root-cause.md. Independent of #9 — nothing here reads a fingerprint or journal, so the two can merge in either order.Why
verifyreported a database Engine DJ had legitimately modified exactly the way it reports corruption:Measured on a real 3,673-track stick this session, with the conversion held identical and only Engine's desktop library varied:
Those extras were Engine syncing its own desktop library — a legitimate feature. Reporting it identically to a corrupt write trains the operator to ignore verify, which is the failure mode this whole line of work exists to prevent.
The discriminator, and why it isn't the id watermark
The plan originally proposed classifying by id watermark — we allocate
Playlist.idcontiguously1..N, so anything aboveNis external. Real hardware disproved that: after the desktop merge, ids ran to 84 against our contiguous 1..45. Engine reassigns ids, it does not merely append.writer/playlists.pypinslastEditTimeto1970-01-01 00:00:00on every playlist row it writes. So:and that survives id reassignment. The constant is now public (
PINNED_LAST_EDIT_TIME) and imported by verify rather than duplicated as a magic string — re-deriving writer behaviour by hand in verify is what produced the pairing defects in #3.beyond_watermarkis computed for display corroboration only and never gates classification.Behaviour
lastEditTimeare reported as informational external edits, naming each with its id and timestamp.okstill counts onlydiscrepancies; the plan's caveat stands, since an Engine in-place migration rewriting every row would otherwise mass-report.playlist_countcomparesdb_count - len(external_edits), so a merge fully explained by Engine emits no discrepancy — while an impostor carrying our epoch still lands in the count.No laundering — the tests that matter
All five were confirmed to fail against unmodified source before the change:
lastEditTime→ external edit, exit code unchangedplaylist_countdiscrepancyplaylist[...].track_orderok=TrueVerification
709 tests (+5), coverage 88.22% (floor 85), ruff + mypy clean. The 0.4.0 gates (G1d in
reader/pdb.py, the pre-publish oracle inwriter/build.py) are untouched and their tests pass.🤖 Generated with Claude Code
https://claude.ai/code/session_01V3aF4y4GdsuJta2bfYUZ8w