# 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`. ## 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.