diff --git a/reader/scripts/notify-probe.ts b/reader/scripts/notify-probe.ts index 874f308..509fe9f 100644 --- a/reader/scripts/notify-probe.ts +++ b/reader/scripts/notify-probe.ts @@ -15,6 +15,9 @@ * that is not ours turns off nothing. * Q6 The backfill guard holds: indexing a book the way the startup * crawl does records no new-book event. + * Q7 Releases that predate delivery are never mailed. A backlog whose + * fanned_out_at is NULL only because the column is new must not + * reach the first reader who confirms an address. * * Runs against a scratch database with no network and no mail: the SMTP * transport is never configured here, so notify's own "not configured @@ -69,7 +72,7 @@ async function main(): Promise { // NOTIFY_MIGRATION is the index this feature's migration occupies; the // assertion is "at least that many have run", not an exact match, so // adding the next migration doesn't break the probe. - const NOTIFY_MIGRATION = 13 + const NOTIFY_MIGRATION = 14 const before = from ? 'copied' : 'fresh' db.getDb() const version = ( @@ -98,6 +101,20 @@ async function main(): Promise { check(`Q1 table ${t} exists`, tables.has(t)) } + // --- Q7: the backlog is stamped, not delivered --- + // Only meaningful with --from, where real historic releases exist. On a + // fresh database this asserts the migration is harmless. + const unstamped = ( + db + .getDb() + .prepare('SELECT COUNT(*) AS n FROM releases WHERE fanned_out_at IS NULL') + .get() as unknown as { n: number } + ).n + check( + `Q7 no release predating delivery is left unfanned (${unstamped} unstamped)`, + unstamped === 0, + ) + // --- Q2: double opt-in --- // requestEmail needs a configured transport; with none, it refuses // rather than silently recording an unconfirmed address. diff --git a/reader/src/server/db.ts b/reader/src/server/db.ts index 5d1de37..5564690 100644 --- a/reader/src/server/db.ts +++ b/reader/src/server/db.ts @@ -345,6 +345,22 @@ const MIGRATIONS: string[] = [ -- the per-recipient UNIQUE above. ALTER TABLE releases ADD COLUMN fanned_out_at TEXT; `, + ` + -- History is not news. Every release recorded before delivery existed + -- has a NULL fanned_out_at purely because the column was just added, + -- and the sweep cannot tell that from a release that happened a minute + -- ago — so the first reader to confirm an address would be mailed about + -- chapters that dropped weeks earlier, as if they were new. + -- + -- Stamping them closes that. On a fresh database this updates nothing + -- (no releases exist yet); on an upgrade it stamps exactly the backlog, + -- which is right, because at the moment of upgrade nothing has ever + -- been delivered and nothing should be. It is the delivery-side twin of + -- the NULL-prior guard in shelfIndex upsert(), which stops the same + -- migration-day storm one step earlier. + UPDATE releases SET fanned_out_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') + WHERE fanned_out_at IS NULL; + `, ] let db: DatabaseSync | undefined