Skip to Content
ConceptsLearning & Course DeliveryCourse Delivery & Retakes (LMS)

Course Delivery & Retakes (LMS)

No code, proto, schema, or service for this exists anywhere in the codebase today. This is a design spec for review, not a build in progress — nothing described here should be treated as implemented or scheduled until it is picked up as a backlog item in CLAUDE.md’s Roadmap Context.

The problem this closes

Custodian Workforce Compliance already tracks that a credential lapsed and creates a DUE re-performance obligation on the Schedule tab. But today a schedule closes only one way: a custodian re-attests in person, after the fact, with nothing in between. For the slice of credentials that are genuinely online-deliverable — refresher assessments, onboarding modules, compliance quizzes — the holder has no in-app way to actually do the thing the schedule is asking for. Not every credential is like this: a great deal of real-world credentialing has an irreducible practical component (a site visit, a proctor, a physical skills test), and this design must never let an online pass silently stand in for that.

Design principle: two paths, one completion contract

Institutions and custodians arrive with wildly different training stacks — some run Moodle, Cornerstone, or an in-house system; some have nothing. Forcing everyone onto one path either locks out custodians with an existing LMS investment, or leaves custodians with nothing unable to offer online retakes at all. So this is deliberately dual-path, converging on one shared contract:

  • Path A — BYO-LMS bridge: the custodian/issuer keeps their existing LMS; Attest only receives a signed completion signal.
  • Path B — Attest-native: Attest hosts a minimal authoring + delivery engine for custodians/issuers with no LMS of their own.
  • CompletionEvent is the one contract every downstream consumer reacts to, regardless of which path produced it — this is what keeps the design modular instead of forking logic per protocol.

Where this fits in the architecture

Per Key Architectural Decision #1 (Core Engine is the only service that knows multi-service workflows) and the single-responsibility principle, this is a new microservice, not an extension of an existing one:

Candidate homeWhy it’s the wrong fit
Learner RecordsDeliberately narrow and AVETMISS-shaped (Learner/Enrolment/Outcome for regulatory export). Records that training happened; was never meant to deliver it.
Custodian RegistryOwns DID/key custody for custodians only — no business-data storage responsibility.
Core EngineOrchestrates; explicitly must not own service-specific business storage.

Proposed: Learning & Course Delivery Service, :50057 (next open port after Custodian Registry’s :50056), own PostgreSQL schema, own gRPC contract — the 9th proto in backends/shared/src/main/proto/, following the existing Modify a gRPC Contract workflow. Core Engine orchestrates it the same way it orchestrates every other service today; it never talks to Credential Issuance or the custodian schedule directly.

Core data model

EntityScopeKey fields
CourseDefinitionBoth pathsowner_id (issuer or custodian), delivery_mode (NATIVE|BYO_LTI|BYO_SCORM|BYO_WEBHOOK), requires_practical_component, linked_policy_id (Program Catalog)
CourseVersionNative onlyVersioned content; holders always take the currently published version
Module / LessonNative onlyOrdered content blocks: markdown, video URL/embed, quiz block
AssessmentNative onlyPass threshold, attempt limit, question bank, shuffle
EnrollmentBoth pathsholder_key, course_id, source (self_serve | custodian_schedule), optional schedule_id link
AttemptBoth pathsNative: per-attempt score/answers. Bridged: opaque — just records launch + result.
CompletionEventBoth pathscourse_id, holder_key, result (PASS|FAIL), evidence_ref, occurred_at, source (NATIVE|LTI|SCORM|WEBHOOK)

Path A — BYO-LMS bridge

Ordered by integration effort, all three optional per CourseDefinition — a custodian/issuer picks whichever matches what they already run:

  1. LTI 1.3 launch (deep linking + resource link, with the standard signed return payload) — the industry-standard way institutional LMSes (Moodle, Canvas, Cornerstone, etc.) already expose external tool launch and grade return. Lowest lift for institutions that already support it.
  2. SCORM/xAPI (Tin Can) ingestion — for institutions that can only export a package or emit xAPI statements rather than support a live launch.
  3. Signed webhook fallback — for bespoke/in-house systems supporting neither: the custodian registers an endpoint + shared secret and POSTs a CompletionEvent-shaped payload. Reuses the same HMAC-signing pattern already established for issuer API keys, not a new auth primitive.

Path B — Attest-native authoring & delivery

Minimal viable slice, deliberately small:

  • Authoring: lesson blocks (markdown / video URL / embed) + one assessment block per course (multiple-choice/true-false, pass threshold, shuffle, attempt limit). Draft → published versioning; holders only ever see published content.
  • Delivery: the credential-wallet holder sees an “available to retake” / “assigned by custodian” list, launches the native player, submits an attempt, gets an immediate result. A CompletionEvent is emitted internally — no network hop, same service.

Explicitly not in this first cut: cohort/instructor-led or synchronous delivery, discussion forums, gradebook/analytics dashboards, exporting native content as a portable SCORM package.

The practical-component guardrail

CourseDefinition.requires_practical_component is the one flag that keeps this design honest. If set, a CompletionEvent from the online portion alone can never auto-close a schedule or auto-issue a credential — it can only satisfy a sub-requirement, and the schedule stays DUE/SCHEDULED pending the existing in-person attestation. This preserves Custodian Compliance’s existing invariant — only a fresh attestation closes a schedule — unchanged; it adds a new way to produce that attestation, it does not weaken what counts as one.

Wiring into the existing custodian Schedule tab

  • When a custodian’s DUE obligation (CustodianReperformanceScheduleEntity) maps, via its policy, to a CourseDefinition, the Schedule tab gains an “Assign online course” action next to today’s Schedule / Reschedule / Cancel.
  • Assigning creates an Enrollment with schedule_id set. A CompletionEvent with result: PASS (and no unmet practical component) drives the existing CustodianReperformanceService closing path automatically — a new caller into that service, not a change to its contract.
  • First-time (never-held) obligations from FirstTimeScheduleDialog work identically: an Enrollment with no prior credential, same as today’s schedule entity already models.

Auto-credentialing on pass

A CompletionEvent with result: PASS is the trigger for Core Engine to call Credential Issuance and issue/renew the W3C VC — the same issuance path used everywhere else. Course delivery never signs anything itself; private-key handling stays exactly as documented today (KMS/HSM-bound, Data Models & Crypto).

Reconciling the existing “never an LMS” line

Custodian Workforce Compliance currently states gap tracking is “never a payroll/rostering/LMS function… HRIS/LMS integration is inbound and advisory only.” That remains true and is not contradicted by this doc: workforce-compliance still doesn’t roster people or run payroll, and an external HRIS/LMS is still inbound-only to it. What changes is that course delivery and retake is now a first-class Attest capability in its own right, documented here — workforce-compliance becomes one more consumer of it (a gap can now resolve via an assigned course, not only a proof link or a waiver) without owning it.

Open questions — require an explicit decision, not implementation detail

  • Proctoring/anti-cheat. None is proposed here. Is an unproctored native online pass acceptable for regulated credentials, or must every native assessment be flagged advisory/low-stakes only until a proctoring answer exists?
  • Content authoring ownership. Do issuers/custodians author native courses themselves via a builder UI, or does content get onboarded on their behalf initially?
  • Native content portability. Should Attest-native content be exportable as SCORM so it isn’t a walled garden? Not designed here.
  • Attempt fraud/collusion detection. The platform already has statistical anomaly detection for credentials (CLAUDE.md Roadmap Context) — whether that extends to assessment attempts is unresolved.
  • Hosting/entitlement model for native video/content storage is a commercial question, out of this doc’s scope.

Explicitly out of scope (first cut)

  • Any practical/in-person assessment delivery — stays 100% the existing custodian attestation flow.
  • Payroll and rostering — named product direction, not scoped here; see Workforce Platform Expansion.
  • Cohort/instructor-led/synchronous delivery.
  • Cross-institution course marketplace or discovery.

Next step, if this gets prioritized

Add as a new backlog item in CLAUDE.md’s Roadmap Context (not launch-blocking — the current launch blockers are staging/alerting/tracing/security-audit/legal/backup-drill/load-testing/incident runbook and holder claim integrity), draft the 9th gRPC proto contract, and decide which BYO protocol ships first (LTI is the standard-effort default; the signed webhook is the fastest-to-build fallback). Do not implement from this doc alone without that backlog decision.