Skip to main content

Events

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

Companion: 04 Event-Driven Architecture · APPLICATION_LOGIC


1. Transport

PropertyValue
BusNATS JetStream
StreamASSIGNMENT (subjects assignment.*)
Retentionlimits — 30 days
StorageFile, replicated 3x
Ack policyExplicit
Max in-flight1000 per consumer
Schema registry@ghasi/event-schemas/assignment/*.json (JSON Schema 2020-12)
Payload formatJSON (UTF-8). Envelope = CloudEvents 1.0 binding: v1 events.

2. CloudEvents Envelope

{
"specversion": "1.0",
"id": "01HXYZ…", // ULID
"type": "assignment.created.v1",
"source": "urn:ghasi:assignment-service",
"subject": "asn_01JA…",
"time": "2026-04-15T10:22:31.102Z",
"datacontenttype": "application/json",
"tenantid": "tnt_…", // extension
"traceparent": "00-…", // extension
"data": { /* payload */ }
}

Standard headers on every message:

  • ce-id, ce-type, ce-source, ce-time, ce-tenantid, traceparent, correlationid

3. Published Events

3.1 assignment.created.v1

{
"assignmentId": "asn_01JA…",
"tenantId": "tnt_…",
"createdBy": "usr_…",
"title": { "en": "Fire Safety 2026" },
"courseId": "crs_…",
"courseVersionPolicy": "latest",
"rrule": "FREQ=YEARLY;BYMONTH=1;BYMONTHDAY=15",
"startDate": "2026-01-15",
"dueOffset": "P30D",
"gracePeriod": "P7D",
"state": "draft",
"aiSuggested": false,
"createdAt": "2026-04-15T10:22:31.102Z"
}

Consumers: analytics-service, notification-service (for admin confirmation), audit-log.

3.2 assignment.activated.v1

{
"assignmentId": "asn_…",
"tenantId": "tnt_…",
"activatedAt": "2026-04-15T10:24:55.000Z",
"horizonUntil": "2026-07-14",
"estimatedWindowCount": 1230
}

3.3 assignment.paused.v1

{
"assignmentId": "asn_…",
"tenantId": "tnt_…",
"pausedAt": "2026-04-15T10:24:55.000Z",
"reason": "compliance_officer_review"
}

3.4 assignment.window.opened.v1

{
"windowId": "win_01JAA…",
"assignmentId": "asn_…",
"tenantId": "tnt_…",
"userId": "usr_…",
"courseId": "crs_…",
"resolvedVersionId":"crsv_…",
"occurrenceStart": "2026-01-15",
"dueAt": "2026-02-14T00:00:00.000Z",
"graceUntil": "2026-02-21T00:00:00.000Z",
"emittedAt": "2026-01-15T00:00:05.412Z"
}

Consumers: enrollment-service (MUST create enrollment), notification-service (welcome), analytics.

Note: The implementation may also emit assignment.compliance_window.opened.v1 with the same windowId, courseId, userId, dueAt, and occurrenceStart (plus optional reissue fields) for compliance-oriented projections. Callers that subscribe for enrollment integration should use assignment.window.opened.v1 (see enrollment-service EVENT_SCHEMAS Consumed list).

3.5 assignment.window.in_progress.v1

{
"windowId": "win_…",
"assignmentId": "asn_…",
"tenantId": "tnt_…",
"userId": "usr_…",
"enrollmentId": "enr_…",
"transitionedAt":"2026-01-15T08:10:00.000Z"
}

3.6 assignment.window.overdue.v1

{
"windowId": "win_…",
"assignmentId": "asn_…",
"tenantId": "tnt_…",
"userId": "usr_…",
"dueAt": "2026-02-14T00:00:00.000Z",
"overdueAt": "2026-02-14T00:00:07.210Z",
"graceUntil": "2026-02-21T00:00:00.000Z"
}

3.7 assignment.window.completed.v1

{
"windowId": "win_…",
"assignmentId": "asn_…",
"tenantId": "tnt_…",
"userId": "usr_…",
"enrollmentId": "enr_…",
"completedAt": "2026-02-10T16:42:11.000Z",
"late": false,
"dueAt": "2026-02-14T00:00:00.000Z"
}

3.8 assignment.window.closed_missed.v1

{
"windowId": "win_…",
"assignmentId": "asn_…",
"tenantId": "tnt_…",
"userId": "usr_…",
"graceUntil": "2026-02-21T00:00:00.000Z",
"closedAt": "2026-02-21T00:00:05.111Z",
"reason": "grace_expired"
}

3.9 assignment.escalation.triggered.v1

{
"windowId": "win_…",
"assignmentId": "asn_…",
"tenantId": "tnt_…",
"userId": "usr_…",
"level": 2,
"trigger": { "kind": "afterDueOffset", "offset": "P3D" },
"action": { "kind": "notify_manager", "channel": "email" },
"firedAt": "2026-02-17T08:00:00.000Z",
"dedupKey": "win_:__lvl2"
}

3.10 assignment.recommendation.generated.v1 (EP-15 US-74)

Emitted when an admin requests POST /api/v1/assignments/suggest (or /recommend alias) and a draft curriculum recommendation is produced for human review (HITL). Does not imply an assignment row was created.

{
"suggestionId": "sug_01H…",
"tenantId": "tnt_…",
"requestedBy": "usr_…",
"courseIds": ["crs_…"],
"roleId": "role_…",
"lookbackDays": 365,
"generatedAt": "2026-04-23T12:00:00.000Z"
}

Consumers: analytics-service (funnel metrics), notification-service (optional digest), audit projections.

4. Consumed Events

4.1 enrollment.created.v1 (from enrollment-service)

Relevant fields:

{
"enrollmentId": "enr_…",
"userId": "usr_…",
"courseId": "crs_…",
"source": { "kind": "assignment", "ref": "win_…" },
"enrolledAt": "2026-01-15T08:10:00.000Z"
}

Action: transition window → in_progress (only if source.kind === 'assignment' and source.ref maps to our window).

4.2 progress.completion.recorded.v1 (from progress-service)

{
"enrollmentId": "enr_…",
"userId": "usr_…",
"passed": true,
"score": 92,
"recordedAt": "2026-02-10T16:42:11.000Z"
}

Action: transition in_progress|overdue window → completed.

4.3 tenant.dynamic_group.evaluated.v1

{
"groupId": "dg_nurses_all",
"tenantId": "tnt_…",
"memberIds": ["usr_…", "…"],
"previousMemberIds": ["usr_…", "…"],
"evaluatedAt": "…"
}

Action: rebind targets (add/remove windows per delta).

4.4 tenant.membership_activated.v1

{
"userId": "usr_…",
"orgUnitIds":["ou_…"],
"activatedAt":"…"
}

Action: synthesise windows for active assignments targeting the new user's orgs (within horizon).

4.5 gdpr.subject_request.received.v1

{
"requestId":"gdr_…",
"subjectUserId":"usr_…",
"kind":"erasure|export",
"receivedAt":"…"
}

Action: see SECURITY_MODEL §7.

5. Delivery & Ordering Guarantees

PropertyGuarantee
At-least-once✅ JetStream + outbox
Ordered per partition✅ per tenantid (subject-token partitioning)
Exactly-once effect✅ via consumer idempotency keys
Schema evolutionAdditive only within major version; breaking → new .vN

6. Dead Letter Handling

Consumer failures after 5 redeliveries → DLQ subject assignment.dlq.<originalSubject>. Operator dashboard in SigNoz.

7. Versioning Policy

  • Additive fields → MINOR bump (handled automatically by consumers ignoring unknown fields).
  • Field rename / removal / type change → MAJOR bump (.v2). Both published in parallel for 12 months; .v1 then retired via a formal deprecation in docs/04-event-driven-architecture.md.

8. Schema Registry Entries

All schemas under @ghasi/event-schemas/assignment/*:

assignment.created.v1.json
assignment.activated.v1.json
assignment.paused.v1.json
assignment.archived.v1.json
assignment.window.opened.v1.json
assignment.window.in_progress.v1.json
assignment.window.overdue.v1.json
assignment.window.completed.v1.json
assignment.window.closed_missed.v1.json
assignment.escalation.triggered.v1.json
assignment.recommendation.generated.v1.json

CI enforces: every published event has a registered schema, every handler has a test that validates a real message against the schema.