Specification: Offline-First and Client Synchronization (Platform)
Domain: Platform cross-cutting (client ↔ API ↔ services)
Version: 1.0
Date: 2026-03-28
Status: Normative (extends OFFLINE_FIRST_AND_CLIENT_SYNC.md)
Authority: Per-domain behavior remains in each module SPEC.md; this document defines platform-wide expectations for disconnected workflows.
Related documents:
- OFFLINE_FIRST_AND_CLIENT_SYNC.md (canonical short spine)
- TECHNICAL_REQUIREMENTS.md
- SOLUTION_DESIGN.md
- TRACEABILITY_MATRIX.md
- ARCHITECTURE_BASELINE.md §13
- MODULE_SHARED_STANDARDS.md §12
- COMPLIANCE_SECURITY.md
- TESTING_STANDARDS.md
1. Purpose and scope
1.1 Purpose
Enable authorized clinical and administrative users to capture work locally when connectivity is absent or degraded, then synchronize to the platform without violating tenant isolation, licensing, auditability, or patient-safety rules. The server remains authoritative for global invariants (duplicate detection, identifier uniqueness, licensing, access-policy outcomes).
1.2 In scope (platform)
- Client local persistence and outbound mutation queue patterns (technology-agnostic).
- Mutation identity and idempotent replay expectations for APIs that adopt offline support.
- Conflict visibility and resolution policies at a high level (module-specific rules in module specs).
- Security and compliance for PHI on device and on the wire during sync.
- Testing obligations for modules that claim offline-capable workflows.
1.3 Out of scope
- Choice of concrete client database (IndexedDB, SQLite, Realm, etc.) unless recorded as a deployment decision in TECHNICAL_REQUIREMENTS.md appendix.
- Per-module domain rules (merge, duplicate scoring, note signing) — see
specs/modules/<module>/SPEC.md. - A dedicated platform
EVENT_MODEL.mdfor sync telemetry; domain events remain in each module’sEVENT_MODEL.mdafter successful server commit. Audit obligations reference the audit service and COMPLIANCE_SECURITY.md.
1.4 Current platform maturity (honest baseline)
| Layer | State (2026) |
|---|---|
| Server idempotency for selected writes | Partial: registration implements Idempotency-Key / clientMutationId for POST /v1/patients (see registration API_DOCS). |
| Next.js EHR client outbox (IndexedDB queue, background sync) | Not implemented in ehr-web as a unified platform component; modules may add drafts ad hoc per spec. |
| BFF routes | Forward cookies/tokens to upstream services; must preserve idempotency headers when adopted. |
| Desktop Electron client | Target client for a first-class local store + outbound queue per desktop-electron/SPEC.md (implementation tracks apps/ehr-desktop when added). |
Module specs MUST state phase (none / server-idempotency only / client queue planned / implemented).
2. Actors and roles
| Actor | Responsibility |
|---|---|
| End user | Capture data offline; resolve conflicts when prompted; must not bypass tenant context. |
| Client application | Local store, queue, retry with stable mutation ids; refresh auth per IAM; never merge cross-tenant data. |
| API gateway (Kong) | Route, rate-limit, authenticate; forward Idempotency-Key where applicable. |
| Owning service | Validate RBAC, licensing, module entitlements, business rules; idempotent commit; emit audit and NATS events server-side only. |
3. Functional requirements (FR-OFF-###)
| ID | Requirement |
|---|---|
| FR-OFF-001 | The platform SHALL allow clients to persist drafts and queued mutations locally when offline or degraded, scoped to the authenticated tenant and user session. |
| FR-OFF-002 | Clients SHALL attach a stable mutation identifier (clientMutationId and/or Idempotency-Key) to eligible writes so that retries and replay do not create duplicate server-side effects where the module has adopted idempotency. |
| FR-OFF-003 | On reconnect, clients SHALL upload queued operations in an order consistent with module rules (e.g. per patient aggregate) and SHALL reconcile server responses (success, structured error, conflict) into the local store. |
| FR-OFF-004 | The system SHALL surface conflicts to the user when server validation fails or version mismatch occurs; silent overwrite of regulated PHI SHALL NOT occur. |
| FR-OFF-005 | Offline-capable modules SHALL document which workflows are online-only vs offline-capable in their SPEC.md and TECHNICAL_REQUIREMENTS.md per MODULE_SHARED_STANDARDS.md §12. |
| FR-OFF-006 | Successful server-side commits SHALL continue to drive audit and domain events (NATS) from trusted services only; clients SHALL NOT publish to NATS from the public internet. |
4. Non-functional requirements (NFR-OFF-###)
| ID | Requirement |
|---|---|
| NFR-OFF-001 | Local storage of PHI SHALL meet encryption at rest, session timeout, and wipe expectations in COMPLIANCE_SECURITY.md and deployment policy. |
| NFR-OFF-002 | Logs and diagnostics SHALL minimize PHI; use correlation and tenant ids without raw demographics in shared logs (MODULE_SHARED_STANDARDS.md). |
| NFR-OFF-003 | Modules claiming offline support SHALL maintain automated tests for queue replay, idempotency, conflict paths, and tenant isolation on replay per TESTING_STANDARDS.md. |
| NFR-OFF-004 | Sync retries SHALL use bounded backoff and SHALL remain safe under duplicate delivery (idempotent server handling). |
5. Workflows (summary)
- Capture — User edits; client writes to local store; optional draft vs committed-outbox separation per module.
- Queue — Mutation recorded with
clientMutationId, target resource type, payload snapshot or reference, ordering key (e.g. patient id). - Connect — Network available; token valid or refreshed.
- Upload — HTTPS to Kong; headers include auth and idempotency.
- Validate — Service enforces policy; returns 2xx, 4xx with structured code, or 409 conflict.
- Reconcile — Client updates local state; user resolves conflicts if required.
Detailed sequencing: SOLUTION_DESIGN.md.
6. Edge cases
| Scenario | Expected behavior |
|---|---|
| Duplicate POST with same idempotency key | Same 201 body as first success (where implemented). |
Stale version on PUT | 409 optimistic lock; client surfaces conflict. |
| Duplicate patient detected on sync registration | 409 DUPLICATE_DETECTED; user reviews merge path per registration module. |
| Token expired mid-sync | Client refreshes or re-authenticates; retries with same mutation id. |
| Two devices edit same record | Resolved per module (versioning, merge, or manual). |
7. Acceptance criteria (Gherkin)
Feature: Idempotent offline replay
Scenario: Queued registration succeeds on replay with same mutation id
Given a client queued POST /v1/patients with clientMutationId "m1"
When the client retries the same request with "m1" after a timeout
Then the server returns the same patient id without creating a second patient
And only one REGISTRATION.patient.created event is observed for that id
Feature: Conflict visibility
Scenario: Stale update rejected after concurrent edit
Given local draft version 1
When the server has advanced the record to version 2
And the client submits an update with version 1
Then the client receives a conflict or precondition failure
And the user can refresh and merge per module policy
Feature: Tenant isolation
Scenario: Local store does not leak across tenants
Given user switched tenant context on the server
When the client flushes its queue
Then mutations are rejected or dropped for wrong tenant
And no cross-tenant data appears in the local database
8. Open questions
- Client storage technology for the primary EHR web shell (e.g. IndexedDB + wrapper vs embedded SQLite via WASM): product decision per deployment.
- Priority order of modules for full client outbox (registration vs notes vs scheduling): roadmap.
- Background sync in browser (Wake Lock, Service Worker constraints): non-functional design tradeoffs.
9. Mapping to consolidated packages
Narrative packages under specs/reference/consolidated-packages/ (e.g. patient registration FR-006) MUST align with this platform spec; where they differ, module SPEC.md wins after explicit update.