Files
rootnode-academy/docs/PHASE6E_ROOT_FOUNDATIONS_COMPLETION.md
ced4568andClaude Sonnet 5 87ad1251ce docs(phase6e): document the ROOT Foundations Completion milestone
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]>
2026-08-26 17:09:02 -05:00

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 shared platform/test/foundations-helper.js (completeRootFoundations01Through04, completeThroughRoot06, completeThroughRoot10) rather than duplicating ~30-80 lines four times.
  • e2e-phase6b.js's bypass student flow: needed deployment-rollback, job-task, cronjob-task, and container-logging-mcq scenarios completed explicitly — all four exist in the real curriculum but were added in phases after the original ALL_STAGES list was written, so completing "every stage scenario" via that stale list never actually finished root-06/root-10 the COURSE, only their originally-scoped subset.
  • e2e-phase6a.js's and e2e-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.prerequisites as a plain array) remains documented in docs/ROOT_FOUNDATIONS_ARCHITECTURE.md.

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.