-- finished_at is "the owner marked this Series finished" (#157): epoch ms, -- zero means not finished, and it doubles as the undo (write zero). The poll -- gate reads it — a Series is polled only while finished_at = 0 — never a -- bookmark's status. ALTER TABLE series ADD COLUMN finished_at bigint NOT NULL DEFAULT 0; -- Column first, seed second, flip third — the order is load-bearing: a seed -- that ran after the flip would read the buckets it just destroyed, declare -- nothing finished, and silently resume polling on Series nobody chose to -- resume. The seed mirrors the pre-cutover due gate exactly: a Series stays -- polled while any bookmark is outside the finished bucket, so a Series whose -- every bookmark sits in it is stamped, one click from being read again -- afterwards. The stamp is the only memory of the bucket the flip is about to -- erase. UPDATE series s SET finished_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint WHERE EXISTS (SELECT 1 FROM bookmarks b WHERE b.site = s.site AND b.series_id = s.series_id) AND NOT EXISTS (SELECT 1 FROM bookmarks b WHERE b.site = s.site AND b.series_id = s.series_id AND b.status <> 'finished'); -- The Lifecycle bucket is gone; a finished bookmark is an archived one. The -- flip must come after the seed, which still reads the bucket. UPDATE bookmarks SET status = 'archived' WHERE status = 'finished';