Series becomes a shared row keyed (site, series_id) owning title, cover,
canonical URL, kind, latest chapter and last-checked time (ADR-0003).
A bookmark keeps only progress, favourite, lifecycle bucket, updated_at.
Store.Upsert decomposes one flat body across both tables in one
transaction: client title/series_url/cover apply only when the series row
is new, then only the poll may change them (security boundary — the row
is shared and the values are scraped page content). Reads join series
back in, so GET/PUT emit and accept exactly the flat field set they did
before (ADR-0004), asserted by TestFlatWireFieldSet.
The poller walks Series instead of Bookmarks: one fetch per shared
series per due cycle, due queue ordered reader_count DESC then
latest_checked_at ASC, orphaned series never due and never deleted, row
still stamped before the fetch. Batch/stagger/interval unchanged.
Migration 0002 backfills series from existing bookmarks; verified by
TestMigration0002BackfillsExistingBookmarks.