IMAP hardcoded no object id into the message key, so it could only ever
reach the Message-ID+content tier or the folder-scoped surrogate, while
JMAP entered at the top tier from the server's Email id. One account
synced through both protocols therefore minted disjoint key spaces.
RFC 8474 EMAILID is the same identifier JMAP exposes, so requesting it
makes the two keys equal. Negotiate per capability: EMAILID only where
the server advertises OBJECTID, X-GM-MSGID only where it advertises
X-GM-EXT-1. A server offering neither behaves exactly as before.
EMAILID cannot go through the typed client: imap-proto's msg_att parser
is a closed alt with no catch-all, and async-imap turns the resulting
nom error into a fatal io::Error that closes the session. The raw layer
reads it off the transport beneath ImapStream instead, so an attribute
the crate cannot parse can never reach its decoder.
Lifting stored rows to a stronger tier changes every message_key, so
IDENTITY_SCHEMA_VERSION moves to 3 and the existing reconciliation
rebuilds the cached store rather than repointing rows one at a time.
The first draft reasoned like a shipped product: preserve every stored row,
never change behaviour without a path, keep old and new daemons interoperable.
Nothing is released, so none of that is owed -- and paying for it distorted the
model in one place that matters.
Message identity cannot be derived for rows already stored, because Message-ID
was never persisted and the octets are discarded after parse. Honouring those
rows meant keeping them under their old surrogate forever: permanently
un-deduped, invisible to every cross-folder guarantee the design exists to
provide, and leaving the store two-tier for as long as the install survives.
Resetting the message store and re-syncing from the server -- which is
authoritative regardless -- costs a developer one sync and buys a single
coherent model.
The audit ledger follows the same reasoning rather than gaining a permanent
namespace boundary: a hash chain whose entries reference ids that no longer
resolve has no audit value, so it is reinitialised with the store it describes.
This also removes the dependency that shaped the build order. Placements waited
on captured identity accumulating only because history had to be preserved;
now capture need only land one merge ahead of the model consuming it. The order
is recorded explicitly, along with the note that change enumeration still ships
alone and still fixes ghost messages for a single engine.
The posture section is written to be deleted at the first real release, when
every one of these questions becomes a genuine obligation.
The engine syncs as though it is the only client of an account: a forward-only
UID high-water mark that cannot observe deletion, and fire-and-forget mutations
that report success when a UID command silently did nothing. A second client --
another machine, a phone, or webmail -- makes both assumptions false, so the
local view diverges and nothing detects it.
ADR 0002 decides how a daemon behaves as one of several clients. The core
choice is to build no coordination layer: IMAP and WebDAV already coordinate
concurrent clients, so the work is finishing the sync model rather than adding
multi-engine support, and correctness at ten engines follows from correctness
at one. The exception is side-effecting filter actions, which land on third
parties with no shared resource to compare against; those get a single owner.
A draft was reviewed independently for protocol correctness, codebase fit, and
adversarially. All three falsified load-bearing claims, and the corrections are
material enough to record rather than summarise: Gmail has no QRESYNC and
Exchange has neither QRESYNC nor CONDSTORE, so the ladder needs four rungs
selected per mailbox; an absent COPYUID is conforming on success and cannot mean
conflict; keying identity on a Message-ID hash would have let a forged message
overwrite a trusted body; and the proposed convergence test could not have
detected the design's largest hole. The review evidence lands alongside the ADR
because several findings cost real effort to establish and would otherwise be
rediscovered expensively.
Documentation only. No code or contract changes.
Remove fabricated PLAN-*/EXECUTIVE-*/TODO/TICKET/spec docs; add authoritative
ROADMAP, ADR-0001 (engine-first gRPC), and Phase 0/1 BACKLOG; rewrite README,
CLAUDE.md, ARCHITECTURE, and wiki Home to honest reality; add status banners to
the NSQL and self-healing specs; make wiki-sync mirror-with-delete.