Skip to main content

Sync Contract

:::info Source Sourced from services/assignment-service/SYNC_CONTRACT.md in the documentation repo. :::

Companion: 03 sync-service · 01 Enterprise Architecture (Offline-first)


1. Offline Relevance

Assignment-service participates in offline-first flows in two distinct directions:

DirectionWho caches?Purpose
Learner-facingLearner deviceShow "upcoming / due / overdue" list without network
Admin-facing (S5)Admin deviceView compliance report snapshot offline; author assignments offline, sync on reconnect

We produce read-only projections for learners. Admin authoring is full bidirectional (CRDT-less; last-writer-wins on draft-only assignments).

2. Projected Entities

2.1 LearnerWindowProjection

Per-user denormalized window record; the learner's slice.

interface LearnerWindowProjection {
windowId: string;
assignmentId: string;
assignmentTitle: I18nString;
courseId: string;
courseVersionId: string;
occurrenceStart: ISODate;
dueAt: ISODate;
graceUntil: ISODate;
state: 'open'|'in_progress'|'completed'|'overdue'|'closed_missed';
lastKnownAt: ISODate; // server snapshot time
version: number;
}

2.2 AssignmentDraftProjection (admin, S5)

Mirror of Assignment aggregate in draft state only. Locked via lease for offline edit.

3. Sync API (delta)

Implemented by sync-service as a thin façade in front of assignment-service's read projections. Protocol per 03 sync-service:

3.1 Pull

GET /sync/v1/assignments/windows?userId=<me>&since=<cursor>

Response:

{
"items": [
{ "op": "upsert", "entity": "LearnerWindowProjection", "data": {} },
{ "op": "delete", "entity": "LearnerWindowProjection", "id": "win_…" }
],
"nextCursor": "op=…",
"serverTime": "…"
}

3.2 Push (admin only, drafts)

POST /sync/v1/assignments/drafts

{
"ops": [
{ "op": "upsert", "entity": "AssignmentDraftProjection", "version": 3, "data": {} }
]
}

Conflict strategy for drafts: last-writer-wins on server with If-Match: <version> semantic. Loser's edits returned as conflicts[] for client-side reconciliation UI.

4. Change Feed

Sync-service consumes these events to update its sync log:

  • assignment.created.v1 → admin projection upsert (if draft) or nothing (until activated)
  • assignment.activated.v1 → trigger downstream re-materialize per-user projections
  • assignment.window.* → upsert or delete LearnerWindowProjection rows

Mapping table:

Source eventSync opEntity
assignment.window.opened.v1upsertLearnerWindowProjection
assignment.window.in_progress.v1upsertsame
assignment.window.overdue.v1upsertsame
assignment.window.completed.v1upsertsame
assignment.window.closed_missed.v1upsertsame
assignment.archived.v1delete (per user)same

5. Offline Mutations (learner)

Learners do not mutate windows directly. All "progress" happens through progress-service; that service has its own sync contract. Assignment-service's projection is fully server-authoritative for learners.

6. Offline Mutations (admin, S5)

Admin mutation scope limited to:

  • Create draft assignment
  • Edit draft (any field)
  • Delete draft
  • Cannot activate / pause / archive offline — those require server-side validation against live tenant and catalog projections.

Sync strategy:

  • Monotonic local ULID ids (asn_local_01…) resolved at sync.
  • version column advances on each offline edit.
  • Server merges with LWW and returns rewritten ids.

7. Security & Tenancy

Every sync request is bound to:

  • User's tenant (X-Tenant-Id + JWT claim)
  • User's role (learner scope enforces userId === me)
  • Device binding (sync-service vouches; we trust its X-Sync-Device-Id when forwarded via internal service auth)

8. Conflict Taxonomy

ConflictHandling
Server-side window deleted while offlineClient reconciles — removes local row
Draft edited both placesLWW on server; conflicts[] with server version returned
Tenant user removed during offline periodProjection no longer served; client must purge locally on 403 response

9. Bandwidth Budget

Projection row ~350 bytes gzipped. Typical learner cohort: 20–80 active windows. ≤ 30 KB per full cold sync.

10. Freshness SLO

  • p95 from window.opened.v1 to learner projection visible via sync pull: < 10 s while online.
  • Admin compliance-report offline snapshot: generated daily at 04:00 tenant-local.

11. Interaction with Progress Sync

Learners complete training offline → progress-service buffers progress.completion.recorded.v1 → on reconnect publishes → assignment-service transitions window → learner sees updated state next sync. Assignment projection and Progress projection MUST reconcile independently (no cross-dependency in sync log).