28 lines
1.8 KiB
MySQL
28 lines
1.8 KiB
MySQL
|
|
-- KB spine: mail_sync_state — per-folder IMAP sync cursor for jobs/mail-imap-sync
|
||
|
|
-- Additive: does not modify 001_envelope.sql .. 004_summaries.sql.
|
||
|
|
-- Version: 005 — mail_sync_state (faza mailowa Krok 7, recon kb/audits/mail-sync-2026-08-06.md
|
||
|
|
-- §2.2 wariant A + Decyzja (f), zatwierdzona przez operatora 2026-08-06)
|
||
|
|
--
|
||
|
|
-- Why a table and not a file in /opt/homelab/state/: the sync cursor and the envelopes it
|
||
|
|
-- describes must restore together or not at all. A state file surviving a DB restore makes the
|
||
|
|
-- poller silently skip everything between the restored envelope set and the file's last_uid —
|
||
|
|
-- a failure with no symptom, noticed months later as missing mail. Backing it into the same
|
||
|
|
-- database buys that invariant for the price of this one migration.
|
||
|
|
--
|
||
|
|
-- Keyed (account, folder) because the folder scope is per account (Decyzja (e)): gmail syncs a
|
||
|
|
-- single SPECIAL-USE \All folder, fastmail syncs INBOX + Archive + Sent. Adding a folder is
|
||
|
|
-- then a config change, not a migration — Message-ID dedup absorbs any overlap.
|
||
|
|
--
|
||
|
|
-- account matches envelope.source ('gmail' | 'fastmail'); folder is the IMAP mailbox name
|
||
|
|
-- exactly as the server returned it in LIST (never a hardcoded literal — Gmail localizes
|
||
|
|
-- '[Gmail]/All Mail', so the name is resolved by SPECIAL-USE attribute at runtime).
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS mail_sync_state (
|
||
|
|
account TEXT NOT NULL, -- == envelope.source: gmail | fastmail
|
||
|
|
folder TEXT NOT NULL, -- IMAP mailbox name as returned by LIST
|
||
|
|
uidvalidity BIGINT NOT NULL, -- server's UIDVALIDITY; a change invalidates last_uid
|
||
|
|
last_uid BIGINT NOT NULL, -- highest UID processed to completion (archive + envelope durable)
|
||
|
|
last_sync_ts TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
|
|
PRIMARY KEY (account, folder)
|
||
|
|
);
|