Events
:::info Source
Sourced from services/delivery-service/EVENT_SCHEMAS.md in the documentation repo.
:::
Companion: 04 Event-Driven Architecture · DOMAIN_MODEL · APPLICATION_LOGIC
All events use the platform EventEnvelope<T> defined in doc 04. This document specifies the payload: T for each delivery-service event.
1. NATS Stream Configuration
| Property | Value |
|---|---|
| Stream name | DELIVERY |
| Retention | 30 days hot (JetStream), 13 months cold (S3/R2) |
| Retention class | operational |
| Subject prefix | delivery.* |
| Partition key | playSessionId (for session events), mountId (for offline events) |
2. Published Events
2.1 delivery.play_session.started.v1
Subject: delivery.play_session.started.v1
Trigger: PlaySession transitions from init to active
interface PlaySessionStartedPayload {
sessionId: PlaySessionId;
tenantId: TenantId;
userId: UserId;
enrollmentId: EnrollmentId;
courseVersionId: CourseVersionId;
deviceId: DeviceId;
attemptNumber: number;
cursor: NavigationCursor;
isOffline: boolean;
offlineMountId?: string;
startedAt: ISODate;
}
Consumers: progress-service (open attempt), analytics-service, sync-service
2.2 delivery.play_session.navigated.v1
Subject: delivery.play_session.navigated.v1
Trigger: Cursor moves to a new position
interface PlaySessionNavigatedPayload {
sessionId: PlaySessionId;
tenantId: TenantId;
userId: UserId;
enrollmentId: EnrollmentId;
fromCursor: NavigationCursor;
toCursor: NavigationCursor;
navigationType: 'next' | 'prev' | 'jump' | 'branch';
durationOnPreviousMs: number; // time spent on previous position
navigatedAt: ISODate;
}
Consumers: progress-service (record navigation statement), analytics-service
2.3 delivery.play_session.completed.v1
Subject: delivery.play_session.completed.v1
Trigger: PlaySession transitions to completed
interface PlaySessionCompletedPayload {
sessionId: PlaySessionId;
tenantId: TenantId;
userId: UserId;
enrollmentId: EnrollmentId;
courseVersionId: CourseVersionId;
attemptNumber: number;
durationSeconds: number;
lessonsVisited: number;
completedAt: ISODate;
}
Consumers: progress-service (close attempt, compute completion), analytics-service, notification-service
2.4 delivery.play_session.abandoned.v1
Subject: delivery.play_session.abandoned.v1
Trigger: PlaySession transitions to abandoned
interface PlaySessionAbandonedPayload {
sessionId: PlaySessionId;
tenantId: TenantId;
userId: UserId;
enrollmentId: EnrollmentId;
courseVersionId: CourseVersionId;
attemptNumber: number;
reason: 'user_initiated' | 'inactivity_timeout' | 'enrollment_revoked' | 'tamper_detected';
durationSeconds: number;
lastCursor: NavigationCursor;
abandonedAt: ISODate;
}
Consumers: progress-service (close attempt as abandoned), analytics-service, notification-service (re-engagement)
2.5 delivery.play_session.tutor_turn.completed.v1
Subject: delivery.play_session.tutor_turn.completed.v1
Trigger: AI tutor completes a response
interface TutorTurnCompletedPayload {
sessionId: PlaySessionId;
tenantId: TenantId;
userId: UserId;
turnId: string;
lessonId: string;
promptTokens: number;
completionTokens: number;
aiProvenance: {
model: string;
local: boolean;
traceId: string;
cost?: { microUSD: number };
};
durationMs: number;
rating?: 'helpful' | 'unhelpful';
completedAt: ISODate;
}
Consumers: analytics-service (AI usage analytics), ai-gateway-service (cost tracking)
2.6 delivery.offline_mounted.v1
Subject: delivery.offline.mounted.v1
Trigger: OfflineMount created successfully
interface OfflineMountedPayload {
mountId: string;
tenantId: TenantId;
userId: UserId;
deviceId: DeviceId;
bundleId: BundleId;
courseVersionId: CourseVersionId;
expiresAt: ISODate;
mountedAt: ISODate;
}
Consumers: sync-service (register device for sync), analytics-service, content-service (track bundle distribution)
2.7 delivery.offline_unmounted.v1
Subject: delivery.offline.unmounted.v1
Trigger: OfflineMount terminated
interface OfflineUnmountedPayload {
mountId: string;
tenantId: TenantId;
userId: UserId;
deviceId: DeviceId;
bundleId: BundleId;
reason: 'user_initiated' | 'expired' | 'tamper_detected' | 'license_revoked';
unmountedAt: ISODate;
}
Consumers: sync-service (deregister device), analytics-service, content-service
3. Consumed Events
3.1 content.play_package.built.v1
Producer: content-service
Consumer group: delivery-play-package-projector
interface PlayPackageBuiltPayload {
courseVersionId: CourseVersionId;
playPackageId: string;
manifestUrl: string;
manifestChecksum: SHA256;
builtAt: ISODate;
}
Handler: Cache manifest; invalidate stale versions.
3.2 enrollment.created.v1
Producer: enrollment-service
Consumer group: delivery-enrollment-projector
interface EnrollmentCreatedPayload {
enrollmentId: EnrollmentId;
tenantId: TenantId;
userId: UserId;
courseVersionId: CourseVersionId;
createdAt: ISODate;
}
Handler: Store enrollment projection; pre-warm manifest cache.
3.3 content.play_package.bundle.published.v1
Producer: content-service
Consumer group: delivery-bundle-projector
interface BundlePublishedPayload {
bundleId: BundleId;
courseVersionId: CourseVersionId;
checksum: SHA256;
sizeBytes: number;
publishedAt: ISODate;
}
Handler: Mark bundle available for offline mount.
3.4 content.bundle.tamper_detected.v1
Producer: content-service
Consumer group: delivery-tamper-handler
interface BundleTamperDetectedPayload {
bundleId: BundleId;
deviceId: DeviceId;
tenantId: TenantId;
userId: UserId;
detectedAt: ISODate;
evidence: string;
}
Handler: Force-unmount affected mounts; pause active sessions; emit unmount events; log security alert.
4. Event Versioning Strategy
- All events start at
v1. - Additive changes (new optional fields) do NOT bump version.
- Breaking changes (field removal, type change, semantic change) require a new
v2subject. - Dual-publish window: minimum 2 release cycles where both
v1andv2are published. - Consumers must ignore unknown fields (forward-compatible).
5. Schema Registry
Event schemas are registered in the platform schema registry:
schemas://delivery/play_session/started/v1#sha256-XXXX
schemas://delivery/play_session/navigated/v1#sha256-XXXX
schemas://delivery/play_session/completed/v1#sha256-XXXX
schemas://delivery/play_session/abandoned/v1#sha256-XXXX
schemas://delivery/play_session/tutor_turn_completed/v1#sha256-XXXX
schemas://delivery/offline/mounted/v1#sha256-XXXX
schemas://delivery/offline/unmounted/v1#sha256-XXXX
6. Dead Letter Queue
- DLQ subject:
DELIVERY.dlq - Poison messages routed after 5 retry attempts with exponential backoff (1s, 2s, 4s, 8s, 16s).
- Alert on non-empty DLQ (PagerDuty P3 after 10 messages, P2 after 100).
- DLQ messages include original event, consumer group, error reason, and attempt count.