docs(phase5b): self-paced enrollment documentation + website integration contract

- PHASE5B_SELF_PACED_ENROLLMENT.md: the full design doc.
- RUNBOOK_SELF_PACED_ENROLLMENT.md: operational runbook for /admin -> Programs.
- WEBSITE_ENROLLMENT_INTEGRATION.md: the complete contract for the separate website team —
  endpoint, auth, request/response schema, idempotency/retry rules, student matching, minor/
  no-email behavior, error responses, what not to send, local testing, production-secret
  handling. Written to be usable without reading the Platform API source.
- REMOTE_AND_SELF_PACED_ARCHITECTURE.md: closes the self-registration/curriculum-assignment/
  delivery-mode/enrollment-model gaps this doc previously flagged as Phase 5+ scope.
- ENGINEERING_SOP.md: new §15. LAB_ARCHITECTURE.md, PRODUCTION_VALIDATION_CHECKLIST.md,
  README.md, platform/README.md: cross-references and a Phase 5B production-validation section.
This commit is contained in:
2026-08-25 12:10:13 -05:00
parent 23f881884d
commit 1c08a30c7f
9 changed files with 558 additions and 28 deletions
+31
View File
@@ -357,3 +357,34 @@ Full design: `docs/PHASE5A_STUDENT_IDENTITY_REMOTE_CLASSROOM.md`, plus
students and confirming zero orphaned data) — both against a live `docker-compose.dev.yml`
stack, not mocks. The full pre-existing regression suite was re-run given how much shared code
this phase touched (`server.js`, `admin-routes.js`, `sessions.js`, `rbac.js`).
## 15. Self-Paced Learning & Enrollment Foundation (Phase 5B)
Full design: `docs/PHASE5B_SELF_PACED_ENROLLMENT.md`, plus
`docs/RUNBOOK_SELF_PACED_ENROLLMENT.md` and `docs/WEBSITE_ENROLLMENT_INTEGRATION.md`.
- **What it is:** `programs` (a first-class learning product, distinct from curriculum JSON —
`curriculum_path` is just what it points at) and `enrollments` (a student's academic access to
a program, independent of cohort membership — a self-paced enrollment has no cohort at all).
Server-enforced entitlement (`enrollment.deriveEnrollmentAccess`, mirroring `classroom.js`'s
derived-not-stored `derivePresence` pattern from Phase 5A) gates a new
`POST /api/student/programs/:id/launch` in front of the exact same disposable-Lab sequence
`/api/environment/launch` already used (extracted into `environment-view.js` so both share one
implementation) — pending/future/expired/cancelled enrollments cannot launch, server-side, not
just hidden in the UI. Completion is entirely server-derived from the existing capstone
achievement mechanism (Phase 3B, unmodified) and idempotent. A new, separate
`POST /api/integrations/enrollments` boundary (its own pre-shared-secret auth, no session
cookie involved — same trust-boundary pattern as Gateway↔Provisioner's
`PROVISIONER_SHARED_SECRET`) is what the *separate* public website will eventually call;
idempotent via a DB unique constraint on the caller's own event id, proven safe under real
concurrent duplicate requests, not just sequential retries.
- **A real bug found and fixed:** `POST /api/progress/record` predated the `wrap()`
async-error-handling convention — an unexpected internal error there (a malformed
`environment_id`) crashed the entire Platform API process, not just that one request. Fixed
(wrapped + input-validated) and regression-tested.
- **Tested, not just designed:** `platform/test/e2e-phase5b.js` (32 checks) and
`infra/scripts/drills/test-phase5b-selfpaced-rehearsal.js` (15 checks) — both against a live
stack, including real Lab destroy/reaper-reconciliation/recreate persistence proofs, a full
real capstone completion, integration-endpoint concurrency, and a real Platform API restart.
Migration safety proven explicitly against a fresh Postgres seeded with realistic pre-5B data
(see that doc §14).