fix(tomesync): strict filename resolve + position uniqueness + unread reset#119
Merged
Merged
Conversation
…read reset Server-side correctness fixes from the 2026-07-03 audit (no plugin change): - resolve (C1-C4): the filename→book fallback that fires for never-served files no longer guesses. Whole-phrase title matching with a minimum length (a bare "It" can't match "The Italian Job"), a volume number in the name must match the book's actual series_index (a "Foo v2" no longer lands on a standalone "Foo"), and the volume regex is bounded so a 4-digit year isn't read as a volume and a half-volume (2.5) parses. Ambiguous → 404, never a silent mis-map onto another book's progress/annotations. - position uniqueness (B2): add UNIQUE(user_id, book_id) to tome_sync_positions + a dedupe migration (keep freshest). Both writers (device PUT, web set_book_status) go through a shared upsert helper that absorbs the insert race via a SAVEPOINT, so a device+web first-write can't fork duplicate rows that flap on read and double-count stats. - unread reset (B3): marking a book unread on the web now clears its TomeSyncPosition, so the device can't re-pull the stale position and undo the reset on next open. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ee78a27 to
f8a6016
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.
Server-side TomeSync correctness fixes from the 2026-07-03 sync audit. No plugin change — all in the backend.
Fixes
Foo v2filename landed on a standalone "Foo", and a year (… - 1984 - …) was misread as "volume 1984" — each silently writing one book's position, sessions and highlights onto another. Matching is now strict (whole-word titles, minimum length, volume number must match the book's index) and declines (404, device keeps local-only tracking) rather than guessing. Tome-served downloads are unaffected — they match by content hash.tome_sync_positionshad no uniqueness guard, so a device sync and a web save landing together could each insert a row for the same book, after which reads picked an arbitrary one (and stats double-counted). AddsUNIQUE(user_id, book_id)+ a dedupe migration; both writers go through a shared SAVEPOINT-guarded upsert.Tests
tests/test_tomesync_correctness.py— 19 new tests (C1–C4 resolve cases, position uniqueness/convergence, unread clears position).Verification (headless browser + emulator, branch-code instance on an isolated port)
Berserk, Vol. 99404s instead of hijacking Vol.1,Dune - 1984 -resolves to Dune (year not read as volume), tiny stems 404.PUT /status200, DB position row dropped 1→0, deviceGET /positionthen 404 (nothing to re-pull).