Skip to content

fix(tomesync): strict filename resolve + position uniqueness + unread reset#119

Merged
bndct-devops merged 1 commit into
mainfrom
fix/tomesync-server-correctness
Jul 5, 2026
Merged

fix(tomesync): strict filename resolve + position uniqueness + unread reset#119
bndct-devops merged 1 commit into
mainfrom
fix/tomesync-server-correctness

Conversation

@bndct-devops

Copy link
Copy Markdown
Owner

Server-side TomeSync correctness fixes from the 2026-07-03 sync audit. No plugin change — all in the backend.

Fixes

  • A sideloaded book can no longer hijack another book's reading progress (C1–C4). The filename→book fallback (used only for files Tome never served) was too eager: a short title like "It" matched The Italian Job, a Foo v2 filename 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.
  • Reading position no longer flaps between two values (B2). tome_sync_positions had 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). Adds UNIQUE(user_id, book_id) + a dedupe migration; both writers go through a shared SAVEPOINT-guarded upsert.
  • Marking a book "unread" on the web now sticks on your device (B3). The reset left the synced position untouched, so KOReader re-pulled it on next open and flipped the book back to "reading". Unread now also clears the synced position.

Tests

  • tests/test_tomesync_correctness.py — 19 new tests (C1–C4 resolve cases, position uniqueness/convergence, unread clears position).
  • Full backend suite green (824 passed).

Verification (headless browser + emulator, branch-code instance on an isolated port)

  • Emulator: real plugin open-book resolved Frankenstein → book 37; endpoint checks against real data — legit files resolve, a non-existent Berserk, Vol. 99 404s instead of hijacking Vol.1, Dune - 1984 - resolves to Dune (year not read as volume), tiny stems 404.
  • Headless browser (Playwright): clicked Unread on a book with a synced position → PUT /status 200, DB position row dropped 1→0, device GET /position then 404 (nothing to re-pull).

…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>
@bndct-devops bndct-devops force-pushed the fix/tomesync-server-correctness branch from ee78a27 to f8a6016 Compare July 5, 2026 09:40
@bndct-devops bndct-devops merged commit ee7a6fb into main Jul 5, 2026
2 checks passed
@bndct-devops bndct-devops deleted the fix/tomesync-server-correctness branch July 5, 2026 09:46
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