Adds PHASE6E_ROOT_FOUNDATIONS_COMPLETION.md (the phase's own record: what was authored, the dash-vs-bash scenario bug found and fixed, the pre-existing lock-enforcement gap found and fixed, the regression fallout that fix revealed in earlier suites and why each was a stale test assumption rather than a platform bug, and honest test/rehearsal/regression results). Updates ROOT_FOUNDATIONS_PILOT_READINESS.md (full re-audit: all 4 ROOT Foundations courses now READY, not inherited blindly from Phase 6D), ROOT_PROGRAM_DURATION_MODEL.md (real ROOT 02-04 duration data: 24.7 combined hours, landing inside Phase 6D's own estimated range; 67.9 hours across the full authored program), ROOT_PILOT_CURRICULUM.md (ROOT 02-04 move from NOT IN PILOT to IN PILOT with full module-level detail; the pilot is now genuinely self-paced-capable for the entire foundational half, not just instructor-led), ROOT_PROGRAM_ARCHITECTURE.md and ROOT_CURRICULUM_COVERAGE_MATRIX.md (course/module/scenario totals), and ROOT_FOUNDATIONS_ARCHITECTURE.md (marked as-built, not just as-designed). Co-Authored-By: Claude Sonnet 5 <[email protected]>
11 KiB
Phase 6E — ROOT Foundations Completion
Phase 6E's mission: finish what Phase 6D deliberately left for a future phase — ROOT 02 (Linux Administration & Networking), ROOT 03 (Git & Collaborative Development), and ROOT 04 (Containers & Docker) — completing the real ROOT 01→02→03→04→05 foundational path.
Baseline verification
Confirmed at start: branch main, clean tree, HEAD = origin/main = Phase 6D's
b6516a74db2d5a5d53d2f1f76c5f2d74e4ed35eb.
ROOT 02, 03, 04 — architecture, content, labs
All three courses were authored to the exact same gold-module standard already proven in ROOT
01: real lessons/demonstrations/knowledge-checks/guided-exercises/reflections, real hands-on labs
using the existing generic scenario/validation engine, and a wired course assessment. See
docs/ROOT_FOUNDATIONS_PILOT_READINESS.md for the full module-by-module evidence and
docs/ROOT_PILOT_CURRICULUM.md for the pilot manifest detail.
ROOT 02 (6 modules, 37 activities): the Networking Mental Model module (M3) received
deliberately extra depth per its "critical module" designation — no lab by design (conceptual
mastery via knowledge check and guided exercise), with every later module building on it
explicitly. Real Labs: a nginx-inspection task that never touches the platform's own reverse
proxy, a controllable start/stop service lifecycle, disk-usage forensics, ip/ss/getent
diagnostics, and a genuine DNS-resolves-but-port-is-wrong diagnose-and-fix.
ROOT 03 (6 modules, 31 activities): uses only real LOCAL Git repositories — a real merge conflict caused on purpose and resolved by the student, and a real local bare repository standing in for a remote (clone/commit/push, verified independently on the "remote" side). No Gitea account, no network access, no RootNode credential of any kind ever reaches a student Lab.
ROOT 04 (8 modules, 43 activities): uses the nested Docker Engine Phase 6D built and tested.
Real Labs: docker run/exec/inspect fluency, a genuine "works internally, unreachable from
host" port-publishing diagnosis and fix, a real destroy/recreate volume-persistence proof, a
real Dockerfile build, and a real two-service Compose application.
The central architectural finding, confirmed again
Every new scenario this phase (Linux, Git, Docker) uses the exact same generic scenario/
validation engine ROOT 01 proved — runCommand() executes an arbitrary shell command and checks
its output, nothing Kubernetes-specific. No new activity type, no new mechanism.
A real, reproducible dash-vs-bash bug, found and fixed
&>file (bash's combined-redirect shorthand) silently misparses under /bin/sh (dash, this
image's actual shell, confirmed via readlink -f /bin/sh), which is what
backend/server.js's runCommand() uses — a scenario's setup_commands using &> captured
the WRONG process's PID via $!, breaking a later kill $(cat pid) step with "No such
process." Found while testing linux-service-lifecycle and linux-dns-http-diagnosis against
the real API (not assumed). Fixed by using the POSIX-portable >file 2>&1 form in both affected
scenarios' setup_commands/hints.
A real, pre-existing security gap, found and fixed
Testing the real ROOT 01→02→03→04→05 chain via direct API (Step 11's explicit requirement)
revealed that POST /api/student/activities/:activityId/complete
(platform/api/src/course-routes.js) checked only that the activity exists and the student
holds an active PROGRAM enrollment — never whether the activity's own course/module was
actually unlocked for that student. A direct POST could mark any manual_marker activity
complete regardless of the prerequisite chain. This is the exact class of gap Phase 6B already
fixed once for the capstone's own start route, just never closed for this more general route.
Proven to predate Phase 6E, not introduced by it: reproduced against an unrelated ROOT 05/06 pair before touching any Phase 6E content, confirming the gap was real and pre-existing, just never triggered before because ROOT 01-04 (and therefore any REAL prerequisite gate before ROOT 05) didn't exist with real content until this phase and Phase 6D.
Fix: the route now calls the same buildCourseTree()/lock computation every other route
already trusts, resolves the activity's own course and module within that tree, and returns
403 { error: 'locked' } if either is locked — reusing the exact mechanism, not a second,
duplicated check. Verified live: both a ROOT 02 bypass attempt (behind the new chain) and a
ROOT 06 bypass attempt (a completely unrelated, pre-existing course pair) are now correctly
rejected, while legitimate unlocked completions still succeed.
Regression fallout from a genuine, correct fix — and what it revealed
Enforcing this lock for real broke several EARLIER e2e suites' own historical shortcuts: tests written under the old (buggy) permissive behavior had accumulated small ordering gaps — completing a course's modules out of the order its own real prerequisite chain requires, or skipping a scenario the ALL_STAGES/FOUNDATION lists never included because the corresponding activity was added in a later phase. None of these were platform bugs; each was a stale test assumption the missing enforcement had been silently covering for. Found and fixed, with the underlying scenario gap named explicitly in each case:
e2e-phase6a.js/e2e-phase6b.js/e2e-phase6c.js/e2e-phase6d.js: needed the real ROOT 01-04 completion sequence injected before touching ROOT 05+ content, since ROOT 05's real prerequisite (ROOT 04) is no longer vacuously satisfied. Extracted into a new sharedplatform/test/foundations-helper.js(completeRootFoundations01Through04,completeThroughRoot06,completeThroughRoot10) rather than duplicating ~30-80 lines four times.e2e-phase6b.js'sbypassstudent flow: neededdeployment-rollback,job-task,cronjob-task, andcontainer-logging-mcqscenarios completed explicitly — all four exist in the real curriculum but were added in phases after the originalALL_STAGESlist was written, so completing "every stage scenario" via that stale list never actually finishedroot-06/root-10the COURSE, only their originally-scoped subset.e2e-phase6a.js's ande2e-phase6d.js's own ROOT 01/05 completion tests needed each module completed fully (lesson→demo→kc→guided→lab→reflection) before moving to the next, since ROOT 01's (and ROOT 05's) own modules carry real intra-course prerequisites — completing every module's front half first and circling back for labs/reflections later is exactly the ordering the newly-enforced lock now correctly rejects.- Two now-obsolete Phase-6D-era assertions in
e2e-phase6d.js("ROOT 02-04 stay PLANNED," "ROOT 03-05 stay vacuously unlocked") were updated to assert the opposite, with a comment explaining Phase 6E intentionally changed that fact.
Testing
platform/test/e2e-phase6e.js — 23/23 passing (fresh stack): full ROOT 02/03/04 hierarchy,
content, and assessment-pool verification; the complete ROOT 01→02→03→04→05 chain proven on one
student (each course's completion unlocking exactly the next, nothing further); the direct-API
lock bypass proven blocked on both a Phase 6E course pair and a pre-existing, unrelated pair;
existing ROOT 06+/Capstone/is_required behavior confirmed unaffected; self-paced no-instructor-
deferral scan; instructor-notes exclusion and RBAC; logout/login persistence; cross-student
isolation; content-version stability; content validation.
Real-Lab rehearsal
infra/scripts/drills/test-phase6e-foundations-rehearsal.js — 13/13 passing against a real,
freshly-built stack: real Platform API/Gateway/Provisioner/disposable Lab, real shell, real
nested Docker, real Git, real k3s (left completely untouched, still healthy at the end). Walks
one genuinely representative real-Lab task per new course (a real nginx-inspection Lab for ROOT
02, a real merge-conflict resolution for ROOT 03, a real container-networking diagnose-and-fix
for ROOT 04) plus the full ROOT 01-04 chain to a genuine ROOT 05 unlock, with an instructor
narrative confirming the resulting learning state is visible.
Regression
Full fresh-stack reruns after every fix: 6A (23/23), 6B (26/26), 6C (17/17), 6D (20/20), 6E
(23/23) — all green. 2A/2B/3A/3B/5A/5B/5C not re-run: git diff for this phase touches only
platform/api/src/course-routes.js (the one real application-code change) plus content/
migrations/tests/docs — confirmed by grep that none of those earlier suites reference the
/api/student/activities/:id/complete route this phase's fix touched.
Frontend, Lab image, nested Docker, k3s
No frontend change was needed — CourseView.jsx/CurriculumView.jsx already generalize
correctly to every ROOT 02-04 activity/status shape (proven by the rehearsal and e2e suite, not
assumed). Frontend build: clean, unchanged this phase. Lab image: rebuilt with the dash-fix and
new scenario files, standalone-smoke-tested and exercised extensively via the rehearsal (real
nested Docker + real k3s side by side, k3s health reconfirmed untouched at the rehearsal's end).
New application code and why necessary
platform/api/src/course-routes.js — the lock-enforcement fix described above. This is the
ONLY application code changed this phase; every course/content requirement was satisfiable with
the existing course_modules/module_activities/assessment_definitions/scenario-engine
model.
Content validation
infra/scripts/validate-course-content.js — 0 errors, all courses/modules with real content,
the full activity/scenario set including this phase's 17 new scenario files (6 Linux task
scenarios, 2 Linux MCQs, 3 Git task scenarios, 2 Git MCQs, 5 Docker task scenarios, 2 Docker
MCQs — see the migrations for the exact counts per course).
Known limitations
- Instructor-led vs. self-paced timing remains prose in Instructor Notes, not a separate database field (unchanged from Phase 6D).
- Three practice-bank modules from earlier phases (root-06-m01, root-07-m01, root-10-m02) remain instructor-led-only this pilot — unrelated to Phase 6E's own scope, unchanged.
- No placement testing or instructor-override UI (explicitly out of scope, as in every prior
phase) — the extension point (
courses.prerequisitesas a plain array) remains documented indocs/ROOT_FOUNDATIONS_ARCHITECTURE.md.
Recommended Phase 6F scope
With the entire ROOT_ foundational-to-capstone path now genuinely READY, natural next directions: author ROOT 08 (Storage & Scheduling) to close the last PARTIAL Kubernetes course; build the future lab infrastructure ROOT 11 modules 2-7 need (a Git+ArgoCD environment for GitOps, a CI runner for CI/CD) and author that content; or invest in self-paced teaching layers for the three remaining instructor-led-only practice-bank modules.