SRV-23 — Offline sync — batch replay, delta pulls, idempotency
Idempotency records for every mutation, /sync/batch replay with per-item results and conflict codes, /sync/delta cursors for the delegate's offline scopes, occurredAt plausibility rules.
| Field | Details |
|---|---|
| Task code | SRV-23 |
| Phase | P1 — V1 build & integration |
| Product scope | V1 |
| Status | Done (Mon 19 Oct 2026) |
| Start | Mon 19 Oct 2026 |
| Due | Mon 19 Oct 2026 |
| Responsible | Anis — Full-stack developer |
| Co-responsible | — |
| Effort | 1.25 person-day(s) planned · 5 from scratch · size S |
| Area · app | Server · server |
| Depends on | SRV-21 |
| Unblocks | MOB-12 |
| Milestones | — |
Summary
Idempotency records for every mutation, /sync/batch replay with per-item results and conflict codes, /sync/delta cursors for the delegate's offline scopes, occurredAt plausibility rules.
Description
Delegates work under weak network, so their app keeps the tour, visits, notes and photos locally and replays actions later (cahier §7.3; SYN-01…SYN-05). This task implements the server side of offline sync: idempotency records for every mutation, sync.batch that replays an ordered list of queued actions (each with its client UUIDv7 id, op, Idempotency-Key, occurredAt and body) and returns per-item results (SyncItemResult: APPLIED, REPLAYED, CONFLICT, REJECTED, RETRY) with stable conflict codes, and sync.delta that serves changes since a cursor for the delegate's offline scopes (today's and upcoming tours and visits, resellers in scope, tasks, visit types and checklists, reference lists, catalog subset for assisted orders) including tombstones when something leaves the scope. occurredAt plausibility follows AUTHORING §3 (future > 5 min or older than 72 h → OCCURRED_AT_OUT_OF_RANGE). Out of scope: the Flutter outbox (hoco_offline, Imad) and module business rules (SRV-21).
Subtasks
| # | Subtask | Owner |
|---|---|---|
| 1 | Batch envelope schema (max 100 items / 5 MB), sequential execution per item in its own transaction, results array aligned with input order | Anis |
| 2 | Op registry mapping batch ops to module procedures (delegate.visits., delegate.tours., delegate.resellers., delegate.tasks., delegate.assisted.*) with the same validation | Anis |
| 3 | Idempotent replay per item (REPLAYED with the original result) and client-id upserts for offline-creatable records | Anis |
| 4 | Dependency handling: item referencing a client id created earlier in the batch or in a previous batch; RETRY when the parent is missing | Anis |
| 5 | Conflict rules and codes: VISIT_LOCKED, TOUR_CLOSED, RESELLER_MERGED, OUT_OF_SCOPE, STALE_VERSION with the current server state in details | Anis |
| 6 | occurredAt / receivedAt storage and plausibility checks (OCCURRED_AT_OUT_OF_RANGE) | Anis |
| 7 | sync.delta: per-scope monotonic cursors (updatedAt + id), page size, tombstones for deletions and scope exits (e.g. reseller reassigned) | Anis |
| 8 | Metrics: batch sizes, conflict rates, replay counts per delegate for support | Anis |
| 9 | Conflict-handling and retry policy alignment with hoco_offline | Imad |
Deliverables
- apps/server/src/modules/sync/{batch.ts,op-registry.ts,delta.ts,cursors.ts,router.ts}
- packages/contracts/src/sync.ts with item result and conflict schemas
- /docs/srs/offline-sync-design updated with the final conflict codes
Acceptance criteria
- Replaying the same batch twice returns REPLAYED for every item and creates no duplicate visit, photo, note or prospect (SYN-05)
- An item with occurredAt 4 days in the past is REJECTED with OCCURRED_AT_OUT_OF_RANGE
- Ending a visit that the back-office already locked returns CONFLICT VISIT_LOCKED with the server state
- A reseller reassigned to another delegate appears as a tombstone in the next sync.delta of the previous delegate
- A batch of 100 items completes within 5 seconds on staging
Requirements covered
| Requirement | Title | Release |
|---|---|---|
| SYN-01 | Back-office → apps flows: accounts, roles, zones, tours, objectives, tasks, catalog, prices, stock, promotions, order statuses, invoices, notifications, support replies | V1 |
| SYN-02 | Apps → back-office flows: sign-ups, profiles, orders, addresses, payments, claims, positions, visits, photos, checklists, reports, installs, conversions | V1 |
| SYN-03 | Critical data sent immediately when a connection is available | V1 |
| SYN-04 | Delegate app keeps the tour, visits, notes and photos locally under weak network | V1 |
| SYN-05 | Sync queue handling retries, duplicates (idempotency), errors and conflicts | V1 |
API surface
| Endpoint group | Operations |
|---|---|
| Offline sync & location | 3 |
Test measures
- DB tests for replay, dependency ordering and conflicts
- Fuzz test: random interleaving of offline actions and back-office edits keeps invariants (no duplicates, no lost reports)
- Joint test with Imad: airplane-mode tour on a device, then sync
Risks
- Clock skew on delegate phones — keep both occurredAt and receivedAt, use receivedAt for ordering conflicts and occurredAt only for display and plausibility
KPI
- Delivered by Mon 19 Oct 2026 within 1.25 person-day(s).
- Every acceptance criterion above demonstrated before hand-off; linked requirements' acceptance criteria pass.
- Zero open critical defects, permission gaps or data-integrity regressions at sign-off.
Generated from content/plan/tasks.json by scripts/generate-team-tasks.mjs. Dates are computed by the scheduler (Sunday–Thursday weeks, holidays excluded) — change the plan, not this page.
SRV-22 — Invitations & attribution module
Invitation codes, signed links and QR payloads, share texts, Play install-referrer attribution, funnel events up to the first order.
SRV-17 — Preparation & delivery module
Delivery records per order, picking list, carrier assignment and tracking number, proof of delivery, delivery-fee policy.