Skip to content

Authority Doc / Public V1 Mobile Sync

The Offline Sync Protocol keeps client and coach mobile useful offline without treating local state as accepted history.

This page is the Public V1 source of truth for mobile device registration, SQLite cache partitions, local outbox operations, operation ids, sync receipts, replay, conflict responses, canonical id mapping, media retry, cache purge, pruning, diagnostics, and evidence gates.

AuthorityOffline SyncClient MobileCoach MobilePublic V1

Note

Mobile apps can work offline, but the server decides what becomes accepted platform history. REST contracts belong to api-contract-standard; backend events and server outbox behavior belong to event-and-outbox-standard.

Mobile Scope

Public V1 offline support is intentionally narrow: enough local persistence to keep mobile execution and lightweight coach actions moving, without standardizing broad offline history or merge semantics.

This Protocol Owns

  • Mobile device registration, valid-authentication-context binding, and identifier persistence across app restarts.
  • SQLite cache partitions and local schema baseline.
  • Mobile local outbox operation envelope.
  • Stable clientOperationId and deviceId rules.
  • Ordered batch sync request and response shape.
  • Operation receipts and idempotent replay.
  • Canonical id mapping for client-generated records.
  • Conflict categories and UX states.
  • Upload retry and upload-session refresh behavior.
  • Local encryption, cache purge, pruning, device/operation/receipt diagnostic source facts, and bounded client-summary delivery.
  • Offline sync test gates.

This Protocol Does Not Own

  • REST route shape, error envelope, pagination, and OpenAPI rules owned by api-contract-standard.
  • Backend domain events, server transactional outbox, publishers, consumers, and dead letters owned by event-and-outbox-standard.
  • Feature-specific business validation owned by each technical feature page.
  • Post-V1 collaborative editing, broad multi-device merge, full coach builder caches, wearable sync, provider sync, billing sync, analytics warehouse sync, or external provider replication.
  • Privacy windows, purge triggers, export/delete, and legal-review gates owned by privacy-and-retention.
  • Telemetry names, metric conversion, aggregation, dashboards, and alerts owned by observability-plan.

Note

Mobile local outbox is not the same thing as the server transactional outbox.

Invariants

These rules are non-negotiable across client mobile and coach mobile. Feature pages can extend payload details, but they cannot bypass this contract.

  • Every offline-capable mobile mutation writes to SQLite first, then enters the local outbox.
  • Every local operation has a stable deviceId, clientOperationId, operationType, actor context, owner context, clientRecordedAt, and payload checksum.
  • Server acceptance is idempotent by the narrowest safe owner scope, usually (workspaceId, relationshipId, deviceId, clientOperationId) or the equivalent personal or coach workspace scope.
  • A concurrent duplicate of the same idempotency key never overwrites the committed receipt; the loser replays it.
  • Optimistic UI must show a separate state from accepted history: LOCAL_DRAFT, QUEUED, SYNCING, ACCEPTED, REJECTED, or CONFLICT.
  • Accepted server results return receipts, canonical ids, accepted revisions, and refetch hints.
  • Rejected or conflicted operations remain diagnosable and are not silently dropped.
  • Mobile never invents final adherence, completion, risk, or coach-review facts before server acceptance.
  • The server may accept, reject, mark conflict, or require refetch per operation inside the same sync batch.
  • Sync is at-least-once from mobile to server. Duplicate submission is expected and safe.
  • Feature pages may define payload shape, but they must use the common operation envelope and receipt rules.

Device Registration

Device identity is a server-registered app installation bound to a valid authenticated session, not just a hardware fingerprint.

Rules

  • Each installed app surface receives a server-issued deviceId after authenticated registration.
  • deviceId is scoped to user account, app surface, installation, and valid authentication context, and remains stable across app restarts while that context remains valid.
  • Authentication session identifiers may persist across restart but are not exported as Launch telemetry.
  • Client mobile and coach mobile use separate device registrations even on the same physical phone.
  • Account switch, confirmed logout, device reset, or compromised session invalidates local sync eligibility until re-registration.
  • The server can revoke a device and reject pending operations with DEVICE_REVOKED.
  • Mobile must never trust a locally stored deviceId without a valid current session.
  • Device status is ACTIVE or REVOKED. Revocation is terminal and idempotent: revoking again returns the same record, and a re-registered install receives a new deviceId.

Implementation note (M1)

Registration, listing, and revocation are user-scoped and need no workspace context: POST /api/v1/client/devices, GET /api/v1/client/devices, and POST /api/v1/client/devices/{deviceId}/revoke.

Tracked Fields

  • deviceId
  • userId
  • appSurface
  • App version and build number.
  • Platform and OS version.
  • Push token reference, if available.
  • Last successful sync time.
  • Revocation state.
  • Created and updated timestamps.

SQLite Baseline

The local schema defines shared protocol tables first. Feature-owned tables may mirror only the approved Public V1 offline slices that sit on top of this base.

TablePurposeRequired Shape
mobile_devicesRegistered local device and app-surface metadata.device_id, user_id, app_surface, registration_status, last_sync_at, schema_version.
local_cache_scopesCache partitions by owner context.scope_id, scope_type, workspace_id, relationship_id, personal_workspace_id, status, last_cursor, purge_required_at.
local_sync_operationsLocal outbox for queued operations.client_operation_id, scope_id, operation_type, payload_json, payload_checksum, status, retry_count, next_retry_at, timestamps.
local_sync_receiptsServer results retained for replay and support.receipt_id, client_operation_id, server_status, accepted_revision, rejection_code, conflict_code, created_at.
local_canonical_id_mapMaps mobile-generated ids to canonical server ids.local_id, canonical_id, entity_type, scope_id, mapped_at.
local_sync_conflictsUser-visible unresolved conflict records.client_operation_id, conflict_code, latest_snapshot_ref, resolution_status, created_at.
local_cache_metadataSchema, pruning, encryption, and diagnostics metadata.key, value, updated_at.

Client Mobile Slices

  • Today cache.
  • Quick logs.
  • Workout drafts.
  • Form and check-in drafts.
  • Progress upload drafts.
  • Nutrition execution slices.
  • Voice-note upload drafts.

Coach Mobile Slices

  • Task inbox cache.
  • Client profile summary cache.
  • Review and action drafts.
  • Message and voice-note drafts.
  • Progress and nutrition review cache.
  • Risk summary cache.
  • No full coach builder caches or broad historical mirror in Public V1.

Operation Envelope

Every queued mutation uses one stable envelope so batching, retry, replay, support diagnosis, and feature-specific payload contracts all line up.

FieldRequirementRule
clientOperationIdRequiredStable id generated once on device for the user action and never changed on retry; carried as an opaque string of at most 100 characters, for which a UUID qualifies.
deviceIdRequiredServer-issued device registration id for the current app surface, installation, user, and valid authentication context.
operationTypeRequiredStable feature operation name such as today.task.complete.
operationVersionRequiredInteger payload contract version owned by the feature page.
actorRequiredUser id, role, and app surface.
ownerContextRequiredWorkspace, relationship, personal workspace, or conversation context.
clientRecordedAtRequiredUTC timestamp from the mobile device.
localSequenceRequiredMonotonic sequence per device or scope; helps diagnostics but does not imply cross-scope acceptance order.
expectedRevisionConditionalOptional generally, but required for contested objects and features that depend on revision safety.
payloadChecksumRequiredStable checksum over normalized payload; must remain identical across retries.
payloadRequiredFeature-specific operation body.
attachmentsConditionalUpload or session references when media is involved.
metadataRequiredApp version, timezone, locale, and safe diagnostics.

Implementation note (M1)

deviceId, actor, and ownerContext are carried once per batch rather than repeated on every operation: device id in the request body, actor from the authenticated session, owner context from the sync route.

Note

Operation payloads must not include bearer tokens, signed playback URLs, raw auth material, or unnecessary health or media content.

Sync Flow

Public V1 sync is batch-oriented. The server can accept, reject, conflict, or require refetch per operation within the same request.

1. Refresh auth and device session

Mobile confirms the auth context and registered device are still valid before sending work.

Prepare

2. Select one scope and send ordered operations

Mobile chooses a single cache scope and submits its pending operations in local order.

Batch

3. Validate the request

Server validates device, actor, owner context, permissions, lifecycle state, expected revisions, checksums, and feature rules.

Validate

4. Process idempotently

Server processes each operation idempotently using the narrowest safe owner scope.

Apply

5. Return results and cursors

Server returns per-operation results plus canonical id maps, receipts, latest cursors, and refetch hints.

Respond

6. Reconcile locally

Mobile updates local rows without hiding rejected or conflicted work.

Reconcile

Sync also has a read half on the same scope route. The read carries the same deviceId, so a revoked device is stopped on read as well as on write, and both halves record the device's last successful sync time.

Note

Endpoint ownership may remain feature-specific in Public V1, but request and response bodies must follow this protocol.

Sync Shape

Batch payloads stay compact and feature-agnostic. The feature owns the operation type and payload details; this page owns the transport shell.

Scope is carried by the sync route rather than the body. A malformed envelope fails the whole request with validation fields; business validation always answers per operation with a receipt.

Request

json
{
  "deviceId": "dev_...",
  "clientCursor": "cursor_or_null",
  "operations": [
    {
      "clientOperationId": "uuid",
      "operationType": "today.task.complete",
      "operationVersion": 1,
      "localSequence": 42,
      "expectedRevision": 7,
      "clientRecordedAt": "2026-07-08T08:30:00Z",
      "payloadChecksum": "sha256:...",
      "payload": {}
    }
  ]
}

Response

json
{
  "data": {
    "results": [
      {
        "clientOperationId": "uuid",
        "status": "ACCEPTED",
        "receiptId": "rcpt_...",
        "acceptedRevision": 8,
        "canonicalIds": [
          { "localId": "local_session_1", "canonicalId": "session_123", "entityType": "WorkoutSession" }
        ],
        "refetch": []
      }
    ],
    "serverCursor": "cursor_...",
    "scopeState": "ACTIVE"
  },
  "meta": {
    "requestId": "req_...",
    "serverTime": "2026-07-08T08:30:03Z"
  }
}

scopeState echoes the owning scope's lifecycle state — ONBOARDING, ACTIVE, or PAUSED for a coach-client relationship. A scope the caller can no longer reach fails the request instead of returning a state. serverCursor is a server-time watermark; paging the read half uses the API-contract list cursor and is not interchangeable with it.

Implementation note (M1)

The relationship sync route is POST /api/v1/coach-client-relationships/{relationshipId}/sync under the selected workspace, with the read half on GET of the same path. A batch carries between one and fifty operations, and up to five hundred sets per payload; structural violations fail the whole request with VALIDATION_FAILED and field details.

Per-Operation Statuses

ACCEPTEDALREADY_ACCEPTEDREJECTEDCONFLICTREFETCH_REQUIREDSCOPE_LOCKED

Device revocation is a batch-level failure rather than a per-operation status: the server rejects the whole sync request with DEVICE_REVOKED and processes no operations. An unknown or non-owned device id fails the same way.

Receipts

Receipts are the server-side evidence that an operation was accepted, rejected, or conflicted. They support replay, crash recovery, and audited diagnosis.

A server receipt exposes its id, device, operation type and version, status, canonical record id, rejection or conflict code, a user-safe message, and a created timestamp. The retained raw request payload stays server-side. Mobile refetches the newest receipts for its own device on the read half; older receipts are reached by replaying the operation.

Accepted And Replay Rules

  • Every submitted operation receives a receipt or explicit batch-level failure.
  • Retrying an already accepted operation returns the same accepted result or an equivalent receipt while retained.
  • Accepted receipts include canonical ids, accepted revision, accepted timestamp, and projection or refetch hints.
  • A retry that reuses a clientOperationId with a different payloadChecksum is not accepted; the response carries the retained receipt's id, and the retained receipt is never overwritten.
  • Receipts are retained long enough to support app crash recovery, retry windows, and support diagnosis.

Rejected And Conflict Rules

  • Rejected receipts include a stable rejection code, user-safe message key, and diagnostics reference.
  • Conflict receipts include conflict code, latest known revision, refetch target, and allowed resolution posture.
  • Rejected or conflicted operations remain locally diagnosable and are never silently dropped.
  • Support and admin access to raw receipt payloads requires explicit audited grant.

Conflicts

Feature pages may add narrower codes, but every rejection or conflict must map cleanly into one of these standard categories.

CodeMeaningExpected Client Behavior
STALE_REVISIONClient edited an old version.Keep local operation, fetch latest canonical data, let feature resubmit if safe.
SUPERSEDED_ASSIGNMENTCoach changed or removed assigned work while user was offline.Mark affected operation conflicted and refetch assignment or task.
PERMISSION_REVOKEDRelationship ended, role changed, account switched, or access was removed.Lock or purge affected scope after minimal explanation. Where naming the scope would leak it, the server fails the batch as not-found instead, and mobile treats that as PERMISSION_REVOKED for the scope.
LIFECYCLE_CLOSEDObject no longer accepts the attempted mutation.Reject operation, preserve receipt, show needs-attention state.
DUPLICATE_OPERATIONRetry of retained accepted operation.Reconcile from existing receipt.
PAYLOAD_INVALIDPayload fails current validation.Show rejected state with user-safe field or action message.
UPLOAD_EXPIREDSigned upload or upload session expired.Refresh upload intent or session and retry where allowed.
DEVICE_REVOKEDServer rejected the registered device.Stop sync, clear eligible queues after safe handoff, require re-auth and registration.
SERVER_REQUIRES_REFETCHServer cannot safely merge from submitted snapshot.Refetch canonical snapshot before retry.

The conflictCode or rejectionCode on the wire may be the feature's narrower code; the feature page records which standard category it maps to. Mobile falls back to the category behavior when it does not recognize the narrow code.

UX States

Optimistic state must never masquerade as accepted history. Mobile surfaces use one common state language so users and support can reason about what happened.

LOCAL_DRAFT

Saved locally but not queued for server acceptance yet.

QUEUED

Queued for sync.

SYNCING

Currently submitted.

ACCEPTED

Server accepted and local state reconciled.

REJECTED

Server rejected; user or support can understand why.

CONFLICT

Server could not safely accept without user action or refetch.

Rules

  • Optimistic state must be visually distinct from accepted history.
  • Accepted operations replace optimistic state with canonical state.
  • Rejected operations preserve enough local evidence for user action and support diagnosis.
  • Conflict states keep the local operation, fetch latest canonical data, and guide retry, discard, or resubmit only where the feature allows it.
  • Permission-revoked states lock or purge the affected scope without exposing unrelated data.

Media Retry

Progress photos, voice notes, and attachments follow the same receipt and retry posture as other offline mutations, with extra care for expiring upload sessions and local file handling.

Retry Rules

  • Mobile stores local file references, upload session ids, checksums, asset draft ids, and retry metadata.
  • Upload intent requests follow api-contract-standard.
  • Expired upload sessions return UPLOAD_EXPIRED with refresh instructions.
  • Server creates canonical media records only after upload completion and policy validation.

Storage And Purge Rules

  • Mobile does not store long-lived signed playback URLs in SQLite.
  • Pending media operations can remain queued, but the user must see pending or failed state rather than accepted history.
  • Local media drafts are purged when the owning scope is purged or when retention windows expire, subject to user-safe retry handling.

Security And Pruning

Public V1 protects local data by keeping offline windows deliberately small, partitioning cache correctly, and removing local state promptly when trust boundaries change.

Protection Rules

  • Use platform secure storage for auth tokens and encryption material.
  • Sensitive SQLite payloads must be encrypted at rest where platform support allows it.
  • Cache scopes are partitioned by user, device, app surface, workspace, relationship, personal workspace, or conversation as applicable.
  • Client mobile and coach mobile cannot share local cache across app surfaces.
  • Raw payload diagnostics are not exposed to product users and require audited support or admin access.

Purge And Pruning Rules

  • Confirmed logout, account switch, relationship end, permission revocation, workspace suspension, account deletion, and device reset trigger cache invalidation or protected purge.
  • Pruning removes old accepted snapshots and stale receipts after the configured retention window while preserving unresolved conflicts and required audit references.
  • Client mobile caches current or near-term execution work and active drafts.
  • Coach mobile caches operational review and read slices plus queued lightweight actions.
  • No broad historical mirror, analytics warehouse sync, external provider replication, or full offline workspace copy exists in Public V1.

Diagnostics

Durable sync receipts and rejected-operation evidence record what the app attempted and the server decided. The separate bounded client-health summary is ephemeral, coalesced, and never a new durable product record.

Required Logs And Correlation

  • Sync request id and correlation id.
  • Device id and app surface.
  • Scope id without leaking unrelated data.
  • Operation type and version.
  • Operation status.
  • Rejection or conflict category.
  • Retry count and oldest queued age.
  • Receipt id.
  • Upload or session refresh failures.
  • Purge and device revocation events.

Bounded Client Diagnostic Summary

  • Store only the latest bounded counters/status in existing protected metadata and overwrite or coalesce older unsent summaries.
  • Send on the next successful sync or health exchange using the API-owned fields for app release, platform, connectivity, operation family, local queue depth/oldest age, restart recovery, and cache purge.
  • Never queue content-rich diagnostic history. Summary acceptance never determines sync-operation acceptance.
  • Last reported means the latest summary from a device that reconnected; disconnected devices remain unknowable. No per-device operational gauge/store is created.

Metrics And Support Boundaries

  • This protocol supplies bounded sync, replay, queue, connectivity, and recovery facts. Observability Plan owns metric/event names, aggregation, dashboards, and alerts.
  • Support tooling should let an authorized operator inspect sync status and receipt summaries for a named user, device, or scope.
  • Raw payload access remains gated by explicit audited permission.

Feature Adapters

Public V1 mobile surfaces reuse one protocol. Feature pages still own operation payloads, local slice details, and deeper validation rules.

SurfaceOffline ScopeAllowed Public V1 OperationsNotes
Client TodayCurrent and near-term agenda snapshots.Start, complete, skip, refresh task status.Final adherence waits for server acceptance.
Client Quick LogsTask items and lightweight values.Complete item, record value, skip, undo where feature allows.Uses task or item revisions.
Client Workout LoggingCurrent workout, one next workout, up to two unsynced session drafts.Start, save, submit, same-day correct, pain note.PR and adherence facts wait for accepted sync.
Client Forms/Check-insAssigned active form and check-in drafts.Save section, submit, attach allowed media.Uses expected form and version metadata.
Client Nutrition ExecutionActive daily nutrition tasks and lightweight meal or log slices.Log meal or task, note, swap request where V1 supports it.No broad offline food database mirror unless feature scope approves it.
Client Progress/MediaPending progress entries and local media drafts.Create progress entry, request or complete upload, retry upload.Signed URLs are not retained long term.
Client Messages/Voice NotesActive relationship conversations and local send drafts.Send message, send voice note, retry upload, read receipt where V1 supports it.No offline access after relationship lock or revocation beyond retained read-only policy.
Coach Task InboxOperational task summaries and lightweight action drafts.Mark reviewed, resolve, create follow-up where feature allows.No offline bulk reassignment or admin action.
Coach Client ContextCached profile, progress, and nutrition summaries.Mostly read-only; limited notes or actions only if feature explicitly allows.No broad historical mirror.
Coach Messaging/Voice NotesActive relationship conversations and local send drafts.Send message, send voice note, retry upload.Same upload and permission rules as client mobile.
Coach Risk SignalsCached deterministic summaries.Read-only in Public V1.No offline risk recomputation.

Feature-Page Rules

Every offline-capable feature page must document the local slice and operation contract it adds on top of this shared protocol.

Required Checklist

  • Offline-capable operations.
  • Operation type names and versions.
  • Local cache slice.
  • Expected revision rules.
  • Canonical id mapping needs.
  • Accepted receipt fields.
  • Rejection and conflict mappings.
  • Purge triggers.
  • Tests for replay, conflict, app restart, and permission revocation.

Testing Gate

Any Public V1 feature that permits offline mobile mutation or persisted local drafts is release-gated on these checks.

Required EvidenceWhat Must Pass
Duplicate operation replayRetrying the same accepted operation returns the same accepted receipt or equivalent retained evidence.
App restart recoveryQueued operations and local drafts survive restart and continue through normal reconciliation.
Stale revision conflictOld expected revisions produce STALE_REVISION or the feature-mapped conflict.
Permission revocationRevoked access blocks sync and locks or purges the affected cache scope.
Upload expiry refreshExpired upload sessions refresh cleanly without creating duplicate media records.
Canonical id map updateAccepted operations update local_canonical_id_map correctly.
Rejected operation diagnosticsRejected operations remain diagnosable and do not mutate accepted server state.
Conflict refetchConflicted operations fetch the latest canonical data before retry.
Client and coach mobile offline smoke testsBoth app surfaces pass end-to-end offline sync smoke coverage for their approved Public V1 slices.
Audited support or admin raw receipt accessRaw receipt payload access requires explicit audited grant.
Cache purgeLogout, account switch, relationship end, and device revocation purge or invalidate local cache as required.
Diagnostic field validationInvalid or unbounded diagnostic values map to safe bounded fallbacks or reject without affecting accepted sync work.
Diagnostic delivery and storageThe latest coalesced summary sends on the next successful exchange and creates no durable product record.
Diagnostic/export failure isolationSummary or telemetry-export failure cannot fail an accepted sync operation.

This protocol hands off REST details, server events, privacy posture, observability, and release proof to adjacent authorities.

Workflow State Map

Lifecycle states that determine whether offline operations can be accepted.

Roles And Permissions

Cache boundaries, permission revocation, and support or admin diagnostics.

API Contract Standard

REST request shape, upload intents, errors, and OpenAPI rules.

Event And Outbox Standard

Server-side events emitted after accepted sync operations.

Observability Plan

Owns sync metric/log/error contracts, aggregation, alerts, and operational diagnosis while this protocol retains device, queue, receipt, replay, and delivery facts.

Test And Release Gate

Defines when offline, mobile end-to-end, and security evidence blocks merge or release.

Privacy And Retention

Defines privacy windows, export/delete, media purge, local cache purge triggers, and legal-review gates.

CoachMe internal planning documentation.