Overview
:::info Source
Sourced from services/assignment-service/SERVICE_OVERVIEW.md in the documentation repo.
:::
Companion: 03 assignment-service · 04 Event-Driven · 02 DDD & Bounded Contexts · 01 Enterprise Architecture
1. Mission Statement
The assignment-service is the single owner of compliance-driven course assignments on the Ghasi-edTech platform. It is responsible for translating business policy ("All new-hire nurses must complete Fire Safety within 30 days, annually thereafter, with 7-day grace") into deterministic, timeboxed, audit-grade ComplianceWindow instances that drive enrollments, reminders, escalations, and compliance reporting.
It is classified as a Core Domain because:
- It is the primary competitive differentiator for regulated verticals (healthcare, finance, aviation, pharma).
- It is where the "assigned learning" business process is codified; no off-the-shelf replacement can capture tenant-specific compliance semantics.
- It fuses deterministic time logic (RRULE), policy (escalation, grace), saga orchestration (window lifecycle), and explainable AI (suggested assignments).
2. Bounded Context
| Attribute | Value |
|---|---|
| Context name | Assignment |
| Domain class | Core |
| Upstream contexts | Identity (users), Tenant (org units, dynamic groups), Catalog (course metadata), AI Gateway (suggestions) |
| Downstream contexts | Enrollment (creates enrollments), Notification (reminders, escalations), Progress (consumes completion), Analytics (compliance rollups), Certification (triggers cert on completion) |
| Shared kernel | @ghasi/domain-primitives — TenantId, UserId, CourseId, CourseVersionId, EnrollmentId, I18nString, ISODate, ISODuration, RRULEString, AIProvenance |
3. Aggregate Roots
| Aggregate | Cluster | Consistency boundary |
|---|---|---|
| Assignment | targets, escalation, reminderPolicy, rrule | Single transactional aggregate per assignment |
| ComplianceWindow | (standalone) | One window = one saga instance; materialised from Assignment.rrule |
Why ComplianceWindow is its own aggregate: Windows have an independent lifecycle (open → in_progress → completed / closed_missed). A single Assignment may spawn tens of thousands of windows (10,000 nurses × 10 annual occurrences). Keeping them as a separate aggregate avoids a monstrous root and enables per-window concurrency.
4. Service Responsibilities
┌────────────────────────────────────────────────────────────────────────┐
│ assignment-service │
│ │
│ ┌─────────────────┐ ┌───────────────────┐ ┌──────────────────────┐ │
│ │ Assignment CRUD │ │ Target Resolver │ │ RRULE Materializer │ │
│ │ + state machine │ │ (user|OU|group) │ │ (cron, horizon=90d) │ │
│ └─────────────────┘ └───────────────────┘ └──────────────────────┘ │
│ ┌─────────────────┐ ┌───────────────────┐ ┌──────────────────────┐ │
│ │ Window Saga │ │ Escalation Engine │ │ Reminder Scheduler │ │
│ │ (open→closed) │ │ (policy-driven) │ │ (batch + per-window) │ │
│ └─────────────────┘ └───────────────────┘ └──────────────────────┘ │
│ ┌─────────────────┐ ┌───────────────────┐ ┌──────────────────────┐ │
│ │ Compliance │ │ AI Suggested │ │ GDPR Subject Request │ │
│ │ Reporting API │ │ Assignments (S5) │ │ Handler │ │
│ └─────────────────┘ └───────────────────┘ └──────────────────────┘ │
└────────────────────────────────────────────────────────────────────────┘
4.1 Owns (read + write)
Assignmentaggregate (title, courseId, targets, rrule, policies, state)ComplianceWindowinstances (per user × occurrence)- Window state machine transitions
- Escalation policy evaluation
- Reminder scheduling plans
- Course version policy (
pinvslatest) at the assignment level - AI-suggested-assignment provenance
- Compliance report query layer (read-model over windows)
4.2 Reads (via events / projections)
enrollment.created.v1— transition window toin_progressprogress.completion.recorded.v1— transition window tocompletedtenant.dynamic_group.evaluated.v1— recompute targetstenant.membership_activated.v1— attach new members to running assignmentsgdpr.subject_request.received.v1— purge/anonymize subjectcatalog.course_version.published.v1— pick up latest version whencourseVersionPolicy='latest'
4.3 Does NOT own
- The actual enrollment record (enrollment-service)
- Progress tracking (progress-service)
- Notification delivery (notification-service — we publish intent, they deliver)
- Certification issuance (certification-service)
- User identity and group membership (identity + tenant services)
- Course content (content-service)
5. Technology Stack
| Concern | Choice | Rationale |
|---|---|---|
| Runtime | Node.js 22 LTS / TypeScript 5.6 | Monorepo alignment, strong typing for RRULE and policy |
| HTTP | Fastify 5 | Performance, schema validation, hook lifecycle |
| ORM | Drizzle ORM (pg driver) | Type-safe SQL, RLS-friendly |
| DB | PostgreSQL 16 + pg_partman (windows partitioned by month) | Volume: millions of windows/tenant/year |
| Events | NATS JetStream (subject assignment.*) | At-least-once, durable consumers, per-tenant queue groups |
| Scheduler | croner + rrule.js + in-DB jobs table | Deterministic, tenant-scoped, restartable |
| Cache | Redis (L1) | Target-resolution cache, dynamic group snapshots |
| Observability | OpenTelemetry SDK → OTLP → SigNoz | Traces, metrics, logs unified |
| AI | AI Gateway client (@ghasi/ai-gateway-client) | No direct model access |
6. Position in M-Milestones and Slices
| Milestone | Readiness | Scope |
|---|---|---|
| M1 | — | Not in scope |
| M2 | L1 | Skeleton, domain model compiles |
| M3 | L3 | S4: Assignments MVP — CRUD, RRULE, windows, escalation, reminders, compliance report |
| M4 | L4 | S5: AI-suggested assignments, dynamic-group rebinding, replay-complete saga |
| M5 | L4+ | Offline authoring of assignments by admins (sync projection) |
Freeze points (from 14 Risks & Tradeoffs):
- F25 — RRULE semantics frozen at start of M3
- F26 — ComplianceWindow state machine frozen at start of M3
7. Non-Functional Targets
| Metric | Target |
|---|---|
| Write latency (assignment create) p95 | < 250 ms |
| Write latency (window materialization batch of 10k) | < 60 s |
| Event fan-out freshness (window.opened → enrollment.created) p95 | < 2 s |
| Compliance report query p95 (100k windows) | < 1.5 s |
| Availability | 99.9% |
| Data durability | RPO ≤ 5 min, RTO ≤ 30 min |
8. Key Design Decisions (ADR index)
| ADR | Decision |
|---|---|
| ADR-AS-001 | ComplianceWindow is a separate aggregate, not child of Assignment |
| ADR-AS-002 | RRULE horizon capped at 90 days (rolling materialization) |
| ADR-AS-003 | Escalation is policy-driven, never hard-coded |
| ADR-AS-004 | Dynamic-group rebinding happens on tenant.dynamic_group.evaluated.v1 only |
| ADR-AS-005 | AI-suggested assignments always require human approval before activate |
| ADR-AS-006 | Per-tenant partitioning of compliance_window table |
9. Success Signals
- Business: Auditor produces compliance report in < 2 minutes for 100k users.
- Engineering: Zero "phantom overdue" incidents (false-positive
overdueevents) in staging for 30 days pre-GA. - Product: ≥ 60% of active enterprise tenants use at least one recurring (
rrule-based) assignment by M5.
10. Out of Scope (explicitly)
- Grading or assessment logic → assessment-service
- Cert issuance → certification-service
- Direct notification delivery → notification-service
- Learner-initiated enrollment → enrollment-service (
/self-enroll) - Payment-driven enrollment → marketplace + enrollment services