feat(store): gate the identity schema and tag sync tokens with their scheme #400

Merged
KofTwentyTwo merged 1 commits from story/schema-version-and-token-scheme into dev 2026-08-14 19:55:25 +00:00
1 Commits
Author SHA1 Message Date
james.maes 1ec1e90772 feat(store): gate the identity schema and tag sync tokens with their scheme
Two silent-failure paths, both cheap to close and both load-bearing for the
sync work that follows.

The schema is built entirely from CREATE TABLE IF NOT EXISTS, so an older
binary opens a newer database successfully and writes the old model into it.
Nothing detects that; the file ends up holding two mutually invisible truths.
A user_version generation now gates opening: newer than this build is a
fail-closed error, and older rebuilds the cached message store.

Rebuilding rather than migrating is the pre-release decision recorded in ADR
0002. Message identity cannot be recomputed for rows already stored, so the
alternative is keeping them under the old model forever -- a store where half
the mail is invisible to every guarantee the new model provides. Accounts,
credentials and rules are kept; cached mail, checkpoints and the execution
ledger are rebuilt from the server, which is authoritative regardless. The
ledger goes with them because its hash chain is keyed on message ids that would
no longer resolve, and a chain pointing at absent rows proves nothing.

Separately, folder checkpoints now carry a scheme tag. The stored value was
"{uidvalidity}:{uidnext}" and the QRESYNC checkpoint that replaces it is
"{uidvalidity}:{highestmodseq}" -- identical shape, different meaning. An
upgraded engine reading one as the other hands the server a UIDNEXT where it
expects a MODSEQ; UIDNEXT is normally the larger number, so the server answers
"nothing changed since" and every message below that point is never enumerated
again. That is silent, permanent mail loss with no error anywhere. Any token
whose scheme this build does not recognise, including an untagged one, now
forces a full re-fetch: over-fetching is recoverable, misreading is not.

Refs #387
2026-08-10 15:34:24 -05:00