feat: conflict resolution hardening — server-wins policy, max-retry eviction

- Check notUpdated/notDestroyed per-item errors in Email/set; throw
  JmapSetItemException for permanent failures (notFound, forbidden) so
  they are discarded immediately rather than retried
- Add _maxChangeAttempts=5 constant; _recordChangeError() evicts the
  pending-change row when attempts reach the limit, preventing unbounded
  queue growth from transient errors
- Both IMAP and JMAP flush paths now use _recordChangeError() consistently
- Document server-wins conflict-resolution policy in DB-SYNC.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas Güttler
2026-04-19 21:05:48 +02:00
co-authored by Claude Sonnet 4.6
parent 795001d268
commit 93ac5afbcf
4 changed files with 168 additions and 28 deletions
+8 -4
View File
@@ -68,7 +68,11 @@ This document covers the mail-to-database sync layer only, not the UI.
### Shared / cross-protocol
- **Conflict-resolution hardening**: document and enforce the server-wins policy
consistently — check `notUpdated`/`notDestroyed` per-item errors in JMAP `Email/set`
responses, handle IMAP `NO`/`BAD` gracefully, and evict changes that exceed a
maximum retry threshold (e.g. 5 attempts) to prevent queues from growing unboundedly.
- 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.