docs(phase6a): document RootNode course foundation

COURSE_AUTHORING_STANDARD.md — the standard every future lesson/module
must follow (target learner, prerequisites, measurable objectives,
concept-before-lab, instructor notes, terminology, versioning, review
requirements), grounded in the gold-standard module as the reference
implementation.

PHASE6A_ROOT_COURSE_FOUNDATION.md — the phase summary: youth-ladder
context, the Google Workspace status already recorded in Phase 5C's own
docs, architecture/build summary, honest content states, test results,
explicit non-goals, and known limitations/Phase 6B recommendations.

Updated ENGINEERING_SOP.md (new Phase 6A section + a curriculum-content
cross-reference), REMOTE_AND_SELF_PACED_ARCHITECTURE.md (a 7th
closed gap: self-paced enrollment now resolves to a real course tree,
not just a scenario list), LAB_ARCHITECTURE.md (Phase 6A reuses the
disposable Lab unmodified, verified by a real rehearsal), and
platform/README.md (course model in the api/ summary, new e2e suite
descriptions for 5A/5B/5C/6A which were missing entirely, and the
real-Lab rehearsal script).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-25 23:30:11 -05:00
co-authored by Claude Sonnet 5
parent 8ffc923a84
commit fd38d06055
6 changed files with 350 additions and 8 deletions
+126
View File
@@ -0,0 +1,126 @@
# Course Authoring Standard
Phase 6A §11 — the standard every future RootNode lesson/module must follow, so a future Claude
Code session or a human instructor can author content consistently. The module at
`content/root/root-10-kubernetes-troubleshooting-observability/m01-health-probes/` (backing
`root-10-m01-health-probes-and-troubleshooting`) is the reference implementation — when in
doubt, read it before writing new content.
## 1. Target learner
State explicitly, in the module's Postgres `description` (not buried in prose): who this is for
and what they're assumed to already know. root_ targets ages ~15-18+ through adult learners
(see `docs/PHASE6A_ROOT_COURSE_FOUNDATION.md` §1) — write at a level that respects that range;
don't write down to it.
## 2. Prerequisites
Declare real prerequisite course/module ids in the `prerequisites` column, not prose alone (see
`docs/COURSE_CONTENT_MODEL.md` §5 for how locking derives from this). If a module assumes a
concept the platform doesn't yet teach anywhere, say so explicitly in the module description
rather than silently assuming it.
## 3. Measurable objectives
3-5 objectives per module, stored in the `objectives` column, each a sentence that starts with a
verb describing an observable capability ("Diagnose...", "Configure...", "Explain..."), not a
topic list ("Probes"). A learner (or an instructor) should be able to look at an objective and
know whether it was met.
## 4. Estimated time
Set `estimated_duration_minutes` per activity and let the module/course total derive from the
sum — don't hand-guess a module-level number that doesn't match its activities. Time estimates
are honest guesses, not commitments; revise them once real completion-time data exists (the
mechanically-mapped modules' per-scenario 15/20/30-minute estimates in
`022_course_content.sql` are explicitly flagged as a rough heuristic for exactly this reason).
## 5. Concept explanation (the `lesson` activity)
Explain the underlying concept BEFORE any lab is presented — this is the module's core
pedagogical premise (see `docs/PHASE6A_ROOT_COURSE_FOUNDATION.md`'s "core product principle").
Cover: why the concept/mechanism exists (the problem it solves), the mechanism itself, and
common real-world mistakes. Use concrete, runnable examples (`kubectl` output, YAML snippets)
over abstract description wherever possible.
## 6. Demonstration
Show the concept in action with a worked example BEFORE asking the learner to do it themselves.
A demonstration activity should include what the learner would actually SEE (real-looking
command output), not just a description of what would happen.
## 7. Guided practice (the `guided_exercise` activity)
Lower-stakes practice with a visible worked answer to check against (paper/scratch-file
exercises are fine — see the gold module's readiness-probe exercise). The goal is committing to
an answer and comparing, not passively reading a correct one first.
## 8. Independent practice → lab mapping
A `lab` activity references an EXISTING `scenario_id` — never invent duplicate lab content when
an existing scenario already teaches the concept (check `docs/ROOT_CURRICULUM_INVENTORY.md`
first). Write a short `content_path` intro (see the gold module's `05-lab-intro.md`) that
connects what was just taught to the incident the student is about to solve, without handing out
the scenario's own hints/solution — the Lab's own progressive hints stay the only place the
actual answer path lives.
## 9. Knowledge check
A handful of self-check questions with answers shown inline (not a graded exam — that's what the
assessment engine is for). Use plain Markdown (`*Answer:*` after each question), not raw HTML
`<details>` — `react-markdown` renders this repo's course content and does not have `rehype-raw`
enabled, so raw HTML tags render as literal text, not a collapsible element.
## 10. Reflection
A reflection activity consolidates and connects to real-world judgment — it is not a second
knowledge check. Ask "why does this matter beyond this one Lab," not "what did you just do."
Include a "Real Ops takeaway": a concrete prevention/production practice a learner could apply
on a real team.
## 11. Instructor notes
Every activity type CAN have an `## Instructor Notes` section (see
`docs/COURSE_CONTENT_MODEL.md` §3); a `lesson`/`demonstration`/`guided_exercise`/`reflection`
activity SHOULD have one. At minimum include: approximate timing, one likely misconception to
watch for, and (where relevant) a discussion question or optional extension activity. Keep it
actionable — a bullet list an instructor can glance at mid-class, not a second essay.
## 12. Accessibility/readability
Plain Markdown only (headings, lists, code fences, bold/italic, tables) — no content that
depends on color alone to convey meaning, no ASCII art carrying information a screen reader
would lose. Write short paragraphs; prefer lists over dense prose for anything enumerable
(steps, mistakes, questions).
## 13. Terminology rules
Match the terminology the scenario JSON and existing Platform UI already use exactly (e.g.
"readiness probe," not "health check probe" — check `scenarios/data/<id>.json`'s own
`description`/`hints` for the canonical phrasing before introducing a lesson's own term for the
same thing). Define a term the first time it's used in a module; don't assume a prerequisite
module's terminology carried over without a one-line reminder.
## 14. Versioning
Bump the `version` integer on courses/course_modules/module_activities by hand on any semantic
content edit — same convention `assessment_definitions`/`programs` already use. A version bump
is informational only in Phase 6A (nothing snapshots against it the way an assessment session
snapshots its definition version) — it exists so a future phase can detect "this changed since a
student last saw it" without re-deriving that from `updated_at` alone.
## 15. Review/testing requirements
Before marking a module `status = 'ready'`:
- Every activity's `content_path` file must exist and load (verify via
`platform/api/src/content.js`'s `loadContent`, or just hit the admin curriculum API and check
`body` is non-empty).
- A `lab` activity's `scenario_id` must be a real, existing scenario id.
- An `assessment`/`capstone` activity's `assessment_definition_id` must be a real, active
`assessment_definitions` row.
- Walk the module once as a self-paced student end to end (lesson → ... → lab → reflection),
and once through the instructor Curriculum view, before shipping — this is what
`e2e-phase6a.js` and `infra/scripts/drills/test-phase6a-course-rehearsal.js` automate for the
gold module; extend one of them (or add a new phase-scoped test) for future modules rather
than skipping this step.
+45 -3
View File
@@ -88,6 +88,12 @@ To add/reorder a stage: edit `rootnode-path.json` directly, referencing existing
`scenarios/data/*.json` ids. No backend code change needed — it's read fresh via `scenarios/data/*.json` ids. No backend code change needed — it's read fresh via
`POST /api/cache/reload` or on server restart. `POST /api/cache/reload` or on server restart.
As of Phase 6A, `root_` also has a course/module/activity layer ON TOP of this (lessons,
demonstrations, guided exercises around the same scenario ids) — see §17 and
`docs/COURSE_CONTENT_MODEL.md`. `rootnode-path.json` itself is unmodified and still the thing
`curriculum-rules.js` computes stage lock/completion from; the course layer is a parallel,
additive structure that references the same scenario ids, not a replacement.
## 5. Student identity — what's built, what isn't ## 5. Student identity — what's built, what isn't
**Current state (Phase 5A — see `docs/PHASE5A_STUDENT_IDENTITY_REMOTE_CLASSROOM.md` for the **Current state (Phase 5A — see `docs/PHASE5A_STUDENT_IDENTITY_REMOTE_CLASSROOM.md` for the
@@ -416,6 +422,42 @@ Full design: `docs/PHASE5C_ENROLLMENT_COMMUNICATIONS.md`, plus
from website enrollment through a real rendered/extracted activation email, a real Lab launch from website enrollment through a real rendered/extracted activation email, a real Lab launch
and scenario completion, a second enrollment's confirmation email, and a full password-reset and scenario completion, a second enrollment's confirmation email, and a full password-reset
round trip) — both against a live stack, using only the dev provider (no real network calls). round trip) — both against a live stack, using only the dev provider (no real network calls).
Live Google Workspace delivery itself is implemented and unit-level ready Live Google Workspace delivery has since been validated for real, end to end, via
(`infra/scripts/gws-email-test.js`, opt-in) but not yet validated against a real Workspace `academy@rootnodeacademy.com` — see `docs/PHASE5C_ENROLLMENT_COMMUNICATIONS.md` §14.
domain — see that doc's final section.
## 17. ROOT_ Course Architecture + Content Delivery Foundation (Phase 6A)
Full design: `docs/PHASE6A_ROOT_COURSE_FOUNDATION.md`, plus `docs/ROOT_CURRICULUM_INVENTORY.md`,
`docs/ROOT_PROGRAM_ARCHITECTURE.md`, `docs/COURSE_CONTENT_MODEL.md`, and
`docs/COURSE_AUTHORING_STANDARD.md`.
- **What it is:** a course → module → activity content model (`022_course_content.sql`) layered
over the existing scenario/assessment/achievement systems for `root_` only, so a lesson can be
taught BEFORE a lab is presented instead of the platform only ever showing a bare scenario
list. A `lab` activity references an existing `scenario_id`; an `assessment`/`capstone`
activity references an existing `assessment_definition_id` — neither duplicates content.
Long-form lesson prose lives in git-tracked Markdown (`content/root/`), split into
shared/instructor-only sections by a `## Instructor Notes` heading convention
(`content.js`). One `buildCourseTree()` (`course-progress.js`) serves both the self-paced
student route and the instructor curriculum view — no forked curriculum for the two delivery
modes.
- **Completion, never a second source of truth:** a lab's completion reads the existing
`progress` table; an assessment/capstone activity's completion reads the existing
`achievements` table; only lesson-only activities (nothing else to derive completion from) get
the one genuinely new table, `activity_completions`.
- **A real bug found and fixed during testing:** computing a module/course's prerequisite lock
and then stripping its working totals in the SAME map iteration made the result depend on Map
iteration order — a dependent entry could see its prerequisite's totals already deleted. Fixed
by splitting into two explicit passes (`course-progress.js`).
- **One gold-standard module fully authored:** ROOT 10's Kubernetes Health Probes &
Troubleshooting (lesson → demonstration → knowledge check → guided exercise → real Lab
`rootnode-probe-port-mismatch` → reflection), the template `docs/COURSE_AUTHORING_STANDARD.md`
documents for all future modules. Every other course is honestly `partial` (real labs, no
lessons yet) or `planned` (no content exists) — nothing was fabricated to look finished.
- **Tested, not just designed:** `platform/test/e2e-phase6a.js` (21 checks: hierarchy/ordering,
all three completion behaviors, prerequisite locking, resume, cross-student isolation, RBAC
for self-paced no-cohort students, draft-content hiding, version stability) and
`infra/scripts/drills/test-phase6a-course-rehearsal.js` (12 checks, real k3s: a student reads
the gold module's real lesson content, launches a real Lab, the Lab's own `setup_commands`
stand up the broken deployment, a real `kubectl patch` fixes it, and the module completes and
unlocks the next one) — both against a live stack.
+13
View File
@@ -519,3 +519,16 @@ Lab container out-of-band and restarting the Provisioner to trigger its actual
`reconcileOnStartup` reconciliation path (the same technique Phase 4.1's `reconcileOnStartup` reconciliation path (the same technique Phase 4.1's
`test-lab-host-loss.js` established), then confirming a self-paced student's academic progress `test-lab-host-loss.js` established), then confirming a self-paced student's academic progress
is completely unaffected and a fresh Lab boots normally. is completely unaffected and a fresh Lab boots normally.
## Phase 6A: reused, not modified — a teaching layer in front, still one Lab
Phase 6A (`docs/PHASE6A_ROOT_COURSE_FOUNDATION.md`) added a course/module/activity content model
in front of the exact same disposable Lab this document describes — no change to the
Provisioner, `environments`, the reaper, the Lab Host Router, or the Lab image itself
(`Dockerfile`/`entrypoint.sh`/`backend/server.js` untouched). A course's `lab` activity is a
`scenario_id` reference; "Launch Lab" from the course view calls the identical
`/api/student/programs/:id/launch` sequence Phase 5B already established — not a third launch
path. Verified for real, not assumed: `infra/scripts/drills/test-phase6a-course-rehearsal.js`
launches a real Lab, calls the Lab's own `POST /api/scenarios/:id/setup` through the Gateway's
existing `/lab/:token/*` proxy exactly as the browser UI does, and runs a real `kubectl patch`
against the live k3s cluster inside that container before recording completion.
+138
View File
@@ -0,0 +1,138 @@
# Phase 6A — ROOT_ Course Architecture + Content Delivery Foundation
**Status: PHASE 6A COMPLETE. COURSE/MODULE/ACTIVITY CONTENT MODEL LIVE. GOLD-STANDARD MODULE
READY, VALIDATED AGAINST A REAL LAB.**
Phases 1-5 built the RootNode Academy platform: identity, cohorts, curriculum, the assessment
engine, remote classrooms, self-paced enrollment, and enrollment communications. All of it sat
on top of `scenarios/rootnode-path.json` — a flat list of scenario ids per stage, with no notion
of a lesson, a demonstration, or "learn this before you're asked to solve it." Phase 6A builds
that layer, for the `root_` track only, without touching any of the systems underneath it.
## 1. The canonical youth ladder (context, not built here)
RootNode Academy's product spans three tracks by age/stage:
- **`init_`** (~5-10) — robotics / computational thinking / block coding. Not built.
- **`compile_`** (~11-14) — electronics / microcontrollers / ESP32 / programming. Not built.
- **`root_`** (~15-18+, college, adult) — Linux → systems → containers → Kubernetes → DevOps/
platform engineering. **This phase.**
`programs.family` (Phase 5B) already models this three-way split; only `root` has real content.
Phase 6A does not invent `init_`/`compile_` curriculum — see §15 of the authorization and
`docs/ROOT_CURRICULUM_INVENTORY.md` §7 for the evidence that no such content exists yet.
## 2. Core product principle
KubeKosh/the RootNode Lab is the LABORATORY, not the whole course. A RootNode course can contain
lessons, demonstrations, examples, knowledge checks, guided exercises, labs (existing
scenarios), assessments, reflections, and capstone work — a student learns a concept BEFORE
being asked to solve the corresponding scenario. See `docs/COURSE_AUTHORING_STANDARD.md`.
## 3. Google Workspace status (recorded per this phase's instruction, not new work)
The external blocker at the end of Phase 5C — no real Google Workspace mailbox to test against —
was resolved by the owner during this session before Phase 6A began: `[email protected]`
now exists and live delivery was validated end to end (isolated send test + a full real
activation/reset lifecycle). This is already recorded in `docs/PHASE5C_ENROLLMENT_COMMUNICATIONS.md`
(status header, §14) and `docs/RUNBOOK_GOOGLE_WORKSPACE_EMAIL.md` (§11's propagation-delay note),
committed at `4c0a950` and tagged `v0.5.3-enrollment-comms` — this phase did not need to redo
that work, only confirm it was recorded, which it was.
## 4. Architecture summary
```
program (existing) ── courses ── course_modules ── module_activities
├─ lab → scenario_id (existing)
└─ assessment/capstone → assessment_definition_id (existing)
```
Full design: `docs/COURSE_CONTENT_MODEL.md`. Full course/module map:
`docs/ROOT_PROGRAM_ARCHITECTURE.md`. Full existing-content inventory this was built from:
`docs/ROOT_CURRICULUM_INVENTORY.md`.
Delivery modes: the same content model serves both instructor-led (Phase 5A classrooms) and
self-paced (Phase 5B enrollment) students — one `buildCourseTree()` function
(`platform/api/src/course-progress.js`), one `content.js` Markdown loader, consumed by both
`course-routes.js` (student) and `admin-course-routes.js` (instructor), the latter additionally
exposing instructor notes via the same file's `## Instructor Notes` convention. No forked
curriculum.
## 5. What was built
**Migration** — `platform/api/migrations/022_course_content.sql`: `courses`, `course_modules`,
`module_activities`, `activity_completions`, plus seed data for 13 courses, 8 modules, and 51
activities (44 mechanically mapped from existing `rootnode-path.json` stages, 6 gold-module
activities, 1 capstone activity) — see that file's own extensive header comments.
**Content** — `content/root/root-10-kubernetes-troubleshooting-observability/m01-health-probes/`:
6 Markdown files (lesson, demonstration, knowledge check, guided exercise, lab intro,
reflection), each following the shared/instructor-notes split convention.
**Backend** — `content.js` (Markdown loader), `course-progress.js` (completion derivation +
prerequisite locking, shared by both surfaces), `course-routes.js` (student API),
`admin-course-routes.js` (instructor API), wired into `server.js`. `platform/api/Dockerfile`
now also copies `content/`.
**Frontend** — `CourseView.jsx` (student: My Programs → ROOT_ → Course → Module → Activity →
Launch Lab → back → continue), wired into `ProgramsTab.jsx`'s existing "Continue Learning" flow
via a new "View Courses" button (the original direct-to-Lab button is unchanged).
`CurriculumView.jsx` (instructor: program structure, per-module teaching detail, instructor
notes, per-student progress lookup), wired into `AdminPortal.jsx` as a new "Curriculum" tab.
**Tests** — `platform/test/e2e-phase6a.js` (21 assertions: hierarchy, ordering, all three
completion behaviors, locking, resume, cross-student isolation, RBAC, draft-hiding, version
stability) and `infra/scripts/drills/test-phase6a-course-rehearsal.js` +
`run-phase6a-course-rehearsal.sh` (a real-k3s dress rehearsal of the gold module end to end,
including the Lab's own `setup_commands` and a real `kubectl patch` against the live cluster).
## 6. The gold-standard module
`root-10-m01-health-probes-and-troubleshooting`, under ROOT 10 (Kubernetes Troubleshooting &
Observability): lesson (probe concepts) → demonstration (reading `kubectl describe pod` Events
for a probe failure) → knowledge check → guided exercise (write a readiness probe on paper) →
Launch Lab (`rootnode-probe-port-mismatch`, RootNode-authored) → reflection (root cause + Real
Ops prevention takeaway). Instructor notes on 5 of its 6 activities (demo plan, misconceptions,
timing, discussion questions, extensions). This is the template `docs/COURSE_AUTHORING_STANDARD.md`
documents for all future module authoring.
## 7. Honest content states
No course is `ready`. 5 courses (01-04, 11) are `planned` with zero modules — genuinely no
content exists for them. 7 courses (05-10, 12, capstone) are `partial` — real, working labs
mapped in, one fully `ready` module (the gold standard), everything else awaiting lesson
authoring in Phase 6B. Nothing was fabricated to make the UI look more complete than it is.
## 8. Test results
- `e2e-phase6a.js`: **21 passed, 0 failed.**
- `test-phase6a-course-rehearsal.js` (real k3s Lab): **12 passed, 0 failed**, including a real
`kubectl patch` against a live cluster inside the student's actual disposable Lab container.
- Full regression (Phase 2A/2B/3A/3B/5A/5B/5C) — see the final report for the run-by-run result;
no Phase 6A change touched any file those suites exercise other than additive route
registration in `server.js`.
## 9. What Phase 6A deliberately did not build
Per the authorization's explicit scope line: `init_`/`compile_` curriculum, a Parent Portal,
Stripe/payments, public registration, an AI tutor, chat/video/voice, proctoring, a mobile app,
certificates, a multi-host scheduler, production deployment changes, a CMS, or dozens of
placeholder lessons. Lesson authoring for courses 06-10/12/capstone beyond the gold standard is
explicitly Phase 6B+ scope.
## 10. Known limitations / recommended Phase 6B scope
- ROOT 08/09's real, existing scenario content (storage/scheduling, and a 22-scenario CKS
security bank) is inventoried but not yet wired into modules — the natural next content slice.
- Course-level lesson authoring for ROOT 05-07/10 (m02)/12/capstone: labs exist, teaching
content doesn't yet — apply `docs/COURSE_AUTHORING_STANDARD.md` per module.
- `POST /api/assessments/:definitionId/start` does not itself re-check the capstone's
`unlock_rule` server-side (a pre-existing Phase 3B gap, not introduced by this phase — the
student-facing `/api/capstone` view does compute and display lock state correctly, and the
UI never exposes a "start" action while locked, but the raw route would technically accept a
premature start if called directly). Not fixed here — it's outside the course/content-model
scope this phase was authorized for; worth a small, isolated fix in a future phase.
- Course-to-course prerequisite locking is currently sequential/manually declared, not derived
from the assessment engine's own `unlock_rule` JSON — the two happen to agree today by
construction (see `docs/ROOT_PROGRAM_ARCHITECTURE.md`'s "Real Ops and Capstone placement");
a future phase could unify them if course content grows complex enough to warrant it.
@@ -86,6 +86,13 @@ this flow specifically — see the Phase 4.1 report for the full run results.
Gmail API in production, a deterministic dev provider everywhere else). See Gmail API in production, a deterministic dev provider everywhere else). See
`docs/PHASE5C_ENROLLMENT_COMMUNICATIONS.md`. `docs/PHASE5C_ENROLLMENT_COMMUNICATIONS.md`.
7. ~~**No notion of a lesson, only a flat scenario list per curriculum stage.**~~ **Closed in
Phase 6A**, for `root_` only. A self-paced enrollment now resolves to a real course/module/
activity tree (lessons, demonstrations, knowledge checks, guided exercises, labs,
reflections, assessments/capstone) instead of just a bare scenario id list — the same tree an
instructor-led classroom student sees, consumed through the identical `buildCourseTree()`.
See `docs/COURSE_CONTENT_MODEL.md` and `docs/PHASE6A_ROOT_COURSE_FOUNDATION.md`.
## What Phase 4.1 deliberately did NOT do here ## What Phase 4.1 deliberately did NOT do here
No signup flow, no student password/email auth implementation, no per-student curriculum No signup flow, no student password/email auth implementation, no per-student curriculum
+21 -5
View File
@@ -13,7 +13,8 @@ entrypoint/Dockerfile unmodified; frontend bundle gained Phase 3B learning UX, s
instructor sessions, RBAC, audit logging, real student credentials + remote classrooms instructor sessions, RBAC, audit logging, real student credentials + remote classrooms
(Phase 5A), self-paced programs/enrollment + the website integration boundary (Phase 5B), and (Phase 5A), self-paced programs/enrollment + the website integration boundary (Phase 5B), and
a provider-neutral enrollment-communications pipeline (activation/enrollment-confirmation/ a provider-neutral enrollment-communications pipeline (activation/enrollment-confirmation/
password-reset email, Phase 5C), and the instructor/admin control plane. No k3s, no password-reset email, Phase 5C), the ROOT_ course/module/activity content model layered over
curriculum/assessments (Phase 6A), and the instructor/admin control plane. No k3s, no
`--privileged` — this replaced Phase 2A's "identity" instance, which was a full Lab image `--privileged` — this replaced Phase 2A's "identity" instance, which was a full Lab image
booting an unused k3s cluster just to serve these APIs. Design: booting an unused k3s cluster just to serve these APIs. Design:
`docs/PHASE2B_ADMIN_AND_PLATFORM_DATA.md` (split + Postgres migration), `docs/PHASE2B_ADMIN_AND_PLATFORM_DATA.md` (split + Postgres migration),
@@ -21,8 +22,9 @@ entrypoint/Dockerfile unmodified; frontend bundle gained Phase 3B learning UX, s
`docs/PHASE3B_LEARNING_AND_ASSESSMENT.md` (learning/assessment engine), `docs/PHASE3B_LEARNING_AND_ASSESSMENT.md` (learning/assessment engine),
`docs/PHASE5A_STUDENT_IDENTITY_REMOTE_CLASSROOM.md` (student credentials, remote classroom), `docs/PHASE5A_STUDENT_IDENTITY_REMOTE_CLASSROOM.md` (student credentials, remote classroom),
`docs/PHASE5B_SELF_PACED_ENROLLMENT.md` (programs, enrollment, website integration boundary), `docs/PHASE5B_SELF_PACED_ENROLLMENT.md` (programs, enrollment, website integration boundary),
`docs/PHASE5C_ENROLLMENT_COMMUNICATIONS.md` (notifications, email delivery). Operational `docs/PHASE5C_ENROLLMENT_COMMUNICATIONS.md` (notifications, email delivery),
reference: `api/README.md`. `docs/PHASE6A_ROOT_COURSE_FOUNDATION.md` (course/module/activity content model, gold-standard
module). Operational reference: `api/README.md`.
- `gateway/` — the only student-facing path to a Lab (HTTP + WebSocket proxy, environment-aware - `gateway/` — the only student-facing path to a Lab (HTTP + WebSocket proxy, environment-aware
routing derived from a server-controlled session — not a client-suppliable header/param — and, routing derived from a server-controlled session — not a client-suppliable header/param — and,
as of Phase 3B, server-side gating of progressive hint reveal). Design: as of Phase 3B, server-side gating of progressive hint reveal). Design:
@@ -74,13 +76,27 @@ further instructor accounts from the Instructors tab).
Lab reset, curriculum stage/Capstone lock-unlock, the exam engine (random draw, server Lab reset, curriculum stage/Capstone lock-unlock, the exam engine (random draw, server
timer, expiry, restart persistence), the Capstone workflow end to end, two-student assessment timer, expiry, restart persistence), the Capstone workflow end to end, two-student assessment
isolation, and instructor learning analytics/RBAC. isolation, and instructor learning analytics/RBAC.
- `test/e2e-phase5a.js` — real student credentials (activation/password reset/login), remote
classroom sessions (join/heartbeat/announcements/help requests), and instructor conversion of
a pilot student to a credentialed account.
- `test/e2e-phase5b.js` — programs/enrollment, entitlement gating (pending/future/expired/
cancelled), server-derived completion, disposable-Lab persistence across destroy/recreate, and
the website/commerce integration boundary (idempotency, concurrency, RBAC).
- `test/e2e-phase5c.js` — the notification lifecycle (queue/retry/failure/exhaustion),
activation/enrollment-confirmation/password-reset email delivery via the deterministic dev
provider, minor/no-email contact-email resolution, and admin notification RBAC.
- `test/e2e-phase6a.js` — course/module/activity hierarchy and ordering, prerequisite-based
locking, all three completion behaviors (manual marker/scenario/assessment-derived), resume,
cross-student isolation, RBAC for self-paced (no-cohort) students, and draft-content hiding.
All four e2e suites need `OWNER_EMAIL` / `OWNER_PASSWORD` set to a real bootstrapped instructor All e2e suites need `OWNER_EMAIL` / `OWNER_PASSWORD` set to a real bootstrapped instructor
account (see "Running the full stack locally" above) — the old `ADMIN_PASSWORD` mechanism is account (see "Running the full stack locally" above) — the old `ADMIN_PASSWORD` mechanism is
gone. They're slow (real k3s boots, `e2e-phase3b.js` also runs a real API restart) and need the gone. They're slow (real k3s boots, `e2e-phase3b.js` also runs a real API restart) and need the
full stack up. None of them substitute for the manual real-Lab-image checks (node-pty, terminal full stack up. None of them substitute for the manual real-Lab-image checks (node-pty, terminal
WebSocket, `kubectl`, scenario validation) in `docs/PHASE2A_GATEWAY_AND_STUDENT_FLOW.md` §8 — WebSocket, `kubectl`, scenario validation) in `docs/PHASE2A_GATEWAY_AND_STUDENT_FLOW.md` §8 —
those need a browser-equivalent client and aren't automated here. those need a browser-equivalent client and aren't automated here. The one exception is Phase
6A's gold-standard module, which DOES get a real-Lab dress rehearsal —
`infra/scripts/drills/run-phase6a-course-rehearsal.sh`.
## Known limitations ## Known limitations