Prove the import against a copy of the real library #25
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent
#18
What to build
Retire the biggest risk in this whole migration — losing the owner's reading history — as early as possible, on a copy, while the rest of the work is still in flight.
Build the throwaway generator, run it against a copy of the real backup into a scratch database, and verify the result exactly. Production is not touched by this ticket.
Acceptance criteria
[x] A throwaway generator reads a SQLite snapshot and emits plain SQL: the distinct Series first, then the Bookmarks referencing them, all owned by the seeded Reader.
[x] It is run against a copy of the real backup into a scratch Postgres database. Production is untouched.
[x] Verified: 29 Bookmarks total; 18 reading and 11 archived; Series count equal to the number of distinct site and series-ID pairs; every Bookmark owned by the seeded Reader.
[x] Spot-checked against the source for a sample of rows: read position, favourite flag, and latest-chapter values all match.
[x] Neither the generator nor any of the owner's reading history is committed to the repository.
[x] The procedure is written down well enough to be repeated at cutover against a fresh export.
[x] No SQLite dependency is reintroduced into the backend module — the generator lives outside it.
Blocked by
#22
Done — PR #33 (
feat/prove-import-real-library).Run on 2026-08-08. A throwaway generator (python3 stdlib
sqlite3, ~20 lines, never committed) read a copy ofbookmarks-20260807-213515.dband emitted 29INSERT INTO seriesfollowed by 29INSERT INTO bookmarks ... SELECT id FROM owner, wrapped in one transaction. The target was a scratch Postgres container whose schema and owner Reader were built by the real binary rather than hand-written DDL, so the import was proven against exactly the schema the migration runner produces. Production untouched.Verified
reading, 11archived, 0 anything else.(site, series_id)count in the source.OWNER_DISCORD_ID.GET /bookmarksover the real read path returned the same 29 with matching values — catches a correct import sitting behind a broken join, which the table counts would not.TRUNCATE bookmarks, series;then re-apply: clean, 29 again. That is the abort path in the runbook, so it is exercised rather than asserted.Committed:
CUTOVER.mdand two cross-links inREDEPLOY.md. Nothing else. The generator stays out of the repo — its output is the owner's reading history, and it reads SQLite, which ADR-0001 removed from the backend module.backend/go.modis unchanged and has no SQLite dependency.Found while reviewing, now in the runbook. Six SQLite columns (
title,series_url,cover,last_chapter,last_chapter_url,last_chapter_num) are nullable in the old schema butNOT NULLin Postgres, so oneNULLaborts the whole import — the exact opposite oflatest_chapter_num, whereNULLis meaningful and must survive. The 2026-08-07 export happens to have none, so this would only have surfaced at cutover against a fresh export. Also fixed: the temp table must be created inside the transaction or psql's autocommit drops it immediately, and the ownership check now resolves the Reader by Discord id instead ofORDER BY id LIMIT 1, which was true by construction and could not have failed.Unblocks the cutover step of #18.