Skip to main content

Communication Service — Sync Contract

Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · 16 offline-first

1. Per-aggregate conflict policy

AggregatePolicyRationale
MessageThreadserver_authoritativeThread participant list and escalation are centrally governed; clients never mutate metadata offline
Messageappend_onlyMessages are immutable once sent; offline composition queues with clientMutationId then server assigns messageId + sentAt
ReadReceiptlww (last-write-wins) on (messageId, userId)Reading the same message from two devices converges to latest read time
NotificationIntentserver_authoritativeIntents are created server-side in response to events; clients never author
DispatchRecordappend_onlyDLR callbacks arrive in any order; each attempt becomes a new row
VirtualSessionserver_authoritativeState machine centrally controlled; clients suggest transitions (end, cancel) but server decides
VirtualParticipant.admitStateserver_authoritativeOnly providers can admit; racing admits resolve server-side

2. Offline client behavior

ScenarioClient behavior
Compose message offlineQueue with clientMutationId; send when online; on reconnect server returns messageId
Mark read offlineQueue POST /read with readAt; server resolves by LWW
View cached threadStale-while-revalidate; show "offline - latest messages at {ts}" banner
Create virtual session offlineNot supported (server is authoritative and realtime provider is cloud-dependent)
Receive push while app offlineOS-level delivery; deep link opens thread once reconnected

3. Conflict resolution examples

Two devices mark the same messages read

  • Client A: { messageIds: [m1, m2], readAt: 10:00 }
  • Client B: { messageIds: [m2, m3], readAt: 10:05 }
  • Server: stores per (messageId, userId); m2 LWW keeps 10:05; m1 and m3 inserted.

Offline compose arrives after thread archived

  • Client sends message with clientMutationId; server responds 409 THREAD_ARCHIVED; client shows error and offers to reopen thread.

4. Clocks & ordering

  • All timestamps server-assigned on write; client-supplied clientSentAt is informational only.
  • Messages ordered by (sent_at, message_id) to disambiguate same-millisecond inserts.