## Context Issue #8197 identified recurring Conversations bugs caused by ambiguous ownership between SwiftUI state, SQLite, and server responses. The 80/20 fix is in #9354: one desktop repository, Firestore-backed revisions, explicit cache completeness, canonical mutation responses, and transactional preservation of user-owned fields. That deliberately stops short of a full sync protocol. This issue tracks the platonic long-term architecture. ## Goal Make conversation sync an explicit, account-scoped replicated-state protocol that remains correct across offline edits, retries, process crashes, concurrent post-processing, deletes, multiple devices, and large histories. ## Proposed architecture ### 1. Server-owned revision and field ownership contract - define a documented ownership table for every conversation field - expose an opaque monotonic revision on every snapshot and mutation response - enforce user-owned vs processing-owned fields in one database write boundary - prohibit direct conversation document writes outside that boundary with a guard test ### 2. Idempotent mutation API Every user mutation accepts: - `client_mutation_id` - `base_revision` - the typed operation/payload The server deduplicates retries and returns either: - the exact canonical post-write snapshot/revision, or - a typed conflict containing current canonical state Deletes use durable tombstones so a concurrent processing retry cannot resurrect a conversation. ### 3. Cursored change feed Add an account-scoped endpoint such as: `GET /v1/conversations/changes?cursor=...` It returns ordered upserts, tombstones, the next cursor, and an explicit reset/full-resync response when history is no longer available. Normal startup/activation should consume deltas rather than repeatedly scanning the newest 50 rows. ### 4. Desktop projection + durable outbox SQLite becomes a materialized projection with: - server revision - projection completeness - tombstone state - last applied change cursor - durable pending mutation outbox Applying a change page and advancing its cursor must be one SQLite transaction. Optimistic UX is derived from the durable outbox, survives restart/offline periods, and disappears only after canonical acknowledgement or a surfaced conflict. ### 5. Separate local capture lifecycle Split local recording/upload state from cached server conversation state: - `LocalCaptureSession`: recording, upload, retry, local segments - `CachedConversation`: server projection and revision - explicit linkage from capture to canonical conversation ID This removes the current overloaded transcription row whose local lifecycle timestamps can be mistaken for server freshness. ### 6. Observability and rollout Add bounded metrics/events for: - change-feed lag and reset rate - mutation retry/conflict/exhaustion - tombstone application - projection repair - cursor age Roll out with a dual-read comparison period and a repair command before removing the full-list fallback. ## Acceptance criteria - [ ] offline title/star/folder mutations survive app restart and settle exactly once after reconnect - [ ] duplicate mutation delivery is idempotent - [ ] concurrent processing cannot overwrite a user mutation - [ ] delete racing with processing cannot resurrect a conversation - [ ] multiple devices converge from the same change stream - [ ] account switch/sign-out cannot leak rows, cursors, or pending mutations across users - [ ] list and detail projections preserve completeness while respecting revision order - [ ] steady-state refresh uses deltas, not a newest-50 full reconciliation - [ ] cursor expiration triggers a safe, observable full rebuild - [ ] schema migration and rollback paths are documented and tested ## Required confidence suite ### Backend contract tests - idempotent retry and exact response replay - base-revision conflict - processing/user mutation race - delete/processing race with tombstone - ordered pagination, duplicate page delivery, cursor expiration, and reset ### Desktop deterministic sync tests - cache/outbox/change-feed state-machine tests with controllable ordering - crash between page apply and cursor advance - crash after request send but before acknowledgement - offline queue, reconnect, conflict, and exact rollback - stale detail vs newer list and newer detail vs stale list - sign-out/account-switch cancellation and isolation ### UX/E2E tests - cached first paint followed by canonical update without blanking - offline mutation remains visible through relaunch - processing completion updates summary/status without reverting title/star/folder - delete remains deleted after relaunch and background processing - injected latency, 5xx, connection reset, duplicate delivery, and out-of-order responses ## Non-goals Do not introduce a general CRDT or realtime socket protocol unless measured product requirements demand it. A durable outbox plus ordered change feed should be the simplest evolvable architecture.