From 650c7a70f5cfd3b781cc721e47a5d5199e4f0136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Mon, 20 Apr 2026 06:14:29 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20update=20DB-SYNC.md=20=E2=80=94=20all?= =?UTF-8?q?=20planned=20features=20now=20implemented?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move JMAP send, push, and conflict-resolution items from Next steps into Implemented features. Replace the next-steps section with optional future work (CONDSTORE, blob expiry, UI for stuck mutations). Co-Authored-By: Claude Sonnet 4.6 --- DB-SYNC.md | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/DB-SYNC.md b/DB-SYNC.md index f5bf9d7..c8a76e7 100644 --- a/DB-SYNC.md +++ b/DB-SYNC.md @@ -31,6 +31,13 @@ This document covers the mail-to-database sync layer only, not the UI. a separate on-demand fetch. - `flushPendingChanges` passes `ifInState` to every `Email/set`; a `stateMismatch` response clears the local checkpoint and triggers a full re-sync before retrying. +- **JMAP send**: outgoing mail uses `EmailSubmission/set` when the server advertises + the submission capability; falls back to SMTP otherwise. +- **JMAP push**: `_JmapAccountSync._wait()` subscribes to the server's SSE + `eventSourceUrl` via `watchJmapPush`; falls back to 30 s polling when push + is unavailable or the server does not advertise the URL. +- `notUpdated`/`notDestroyed` per-item errors from `Email/set` are treated as + permanent failures and discarded immediately (no retry). ### IMAP @@ -53,26 +60,26 @@ This document covers the mail-to-database sync layer only, not the UI. - `sync_log` table records each sync cycle's account, result (ok / error), error message, start time, and finish time. Used for debugging and "last synced" UI. - ---- - -## Next steps - -### JMAP hardening - -- **JMAP send**: implement outgoing mail via `EmailSubmission/set` in addition to the - current SMTP path. -- **Push instead of polling**: upgrade `_JmapAccountSync._wait()` to use an - `EventSource` connection to the JMAP push URL when the server advertises push - capability. Fall back to 30 s polling when push is unavailable. - -### Shared / cross-protocol - -- Conflict-resolution policy: **server-wins**. The next sync cycle always +- **Conflict-resolution policy: server-wins.** The next sync cycle always overwrites local state with server values. Outbound mutations in `pending_changes` are retried up to 5 times before being evicted, preventing unbounded queue growth. Permanent per-item JMAP errors (`notFound`, `forbidden`) are discarded immediately; transient errors (network, 500) are retried up to the limit. -- `ifInState` guard on every JMAP `Email/set` call; `notUpdated`/`notDestroyed` - per-item errors are detected and treated as permanent failures. +- Integration test (`test/integration/concurrent_sync_test.dart`) concurrently + syncs an IMAP account (alice) and a JMAP account (bob) against a real Stalwart + server and verifies the Drift DB cache is consistent (no duplicates, correct + counts, no pending changes). + +--- + +## Next steps + +All planned sync-layer features are implemented. Possible future work: + +- **IMAP CONDSTORE / QRESYNC**: use `MODSEQ` for faster incremental sync on + servers that support RFC 7162. +- **JMAP blob expiry**: detect and re-fetch body blobs that the server has + purged (currently the cache is assumed permanent). +- **Offline compose queue**: surface `pending_changes` failures in the UI so + the user can retry or discard stuck outbound mutations.