test(phase6e): acceptance suite, real-Lab rehearsal, and regression fixes for the lock fix

Adds platform/test/e2e-phase6e.js (23 checks): full ROOT 02/03/04 hierarchy/content/assessment-
pool verification, the complete ROOT 01->02->03->04->05 chain proven on one student (each
course's completion unlocking exactly the next), 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, RBAC, persistence, isolation, version stability, content validation.
23/23 pass against both a warm and a freshly-reset stack.

Adds platform/test/foundations-helper.js — a shared completeRootFoundations01Through04/
completeThroughRoot06/completeThroughRoot10 helper, extracted once multiple suites needed the
identical real-completion sequence as test setup, rather than duplicating it four times.

Adds infra/scripts/drills/{run,test}-phase6e-foundations-rehearsal — the real Platform API/
Gateway/Provisioner/disposable-Lab/nested-Docker/Git/k3s dress rehearsal: one real Lab task per
new course (nginx inspection, a real merge conflict, a real container-networking fix) plus the
full chain to a genuine ROOT 05 unlock, with k3s reconfirmed healthy and untouched at the end.
13/13 pass against a real, freshly-built stack.

Fixes the regression fallout from the (correct) course-routes.js lock fix: several earlier
suites' own historical shortcuts had accumulated small ordering/scenario gaps the missing
enforcement had been silently covering for — none were platform bugs. e2e-phase6a/6b/6c/6d.js
now inject the real ROOT 01-04 (and where needed ROOT 05/06/10) completion sequence via
foundations-helper.js before touching ROOT 05+ content; e2e-phase6b.js's bypass-student flow now
completes deployment-rollback/job-task/cronjob-task/container-logging-mcq explicitly (real
scenarios added in later phases that its original ALL_STAGES list never included); e2e-phase6a/
6d.js's own ROOT 01/05 completion tests now complete each module fully (through its lab and
reflection) before moving to the next, since those courses' own modules carry real intra-course
prerequisites. Two now-obsolete Phase-6D-era assertions in e2e-phase6d.js (ROOT 02-04 stay
PLANNED / stay vacuously unlocked) are updated to assert the opposite, with a comment explaining
Phase 6E intentionally changed that fact. All four suites re-verified green fresh.

Also fixes a real, reproducible dash-vs-bash bug caught while testing the new ROOT 01
rehearsal's ordering fix: `&>file` misparses under this image's actual /bin/sh (dash), breaking
a later `$!`-based kill — the rehearsal script now writes multi-line completion loops with
explicit status assertions, matching the discipline the other suites already use.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-26 17:08:48 -05:00
co-authored by Claude Sonnet 5
parent a09b7aaa53
commit 59edceea1f
9 changed files with 922 additions and 27 deletions
+9
View File
@@ -15,6 +15,7 @@
const assert = require('assert');
const { execSync } = require('child_process');
const { makeSession } = require('./session-helper');
const { completeRootFoundations01Through04, completeThroughRoot06, completeThroughRoot10 } = require('./foundations-helper');
const GW = process.env.GATEWAY_URL || 'http://localhost:4200';
const OWNER_EMAIL = process.env.OWNER_EMAIL;
@@ -104,6 +105,9 @@ async function main() {
// ── 3/18: required/optional — the newly-wired optional readiness-probe lab ───────────────
await test('the new optional readiness-probe lab does not block root-10-m01 completion when skipped', async () => {
const student = await newStudent(owner, 'OptSkip');
// Phase 6E made root-06 a real, non-vacuous prerequisite for root-10 — test setup, not this
// test's own subject.
await completeThroughRoot06(student.session);
// Complete only the REQUIRED activities in root-10-m01, deliberately skipping the optional lab.
for (const suffix of ['a01-lesson-health-check-concepts', 'a02-demonstration-probes-in-action', 'a03-knowledge-check', 'a04-guided-exercise-readiness-probe']) {
const { status } = await student.session.request('POST', `/api/student/activities/root-10-m01-${suffix}/complete`);
@@ -133,6 +137,9 @@ async function main() {
// ── 14: idempotency — completing the same activity twice never errors or double-counts ──
await test('completing the same activity twice is idempotent', async () => {
const student = await newStudent(owner, 'Idem');
// root-05-m01's real prerequisite is root-05-m00 complete (course-level: root-04 complete)
// — test setup, not this test's own subject.
await completeThroughRoot06(student.session);
const first = await student.session.request('POST', '/api/student/activities/root-05-m01-a00-lesson-kubectl-fluency/complete');
assert.equal(first.status, 200);
const second = await student.session.request('POST', '/api/student/activities/root-05-m01-a00-lesson-kubectl-fluency/complete');
@@ -214,6 +221,8 @@ async function main() {
await test('a student can complete ROOT 11 module 1 end-to-end (lesson-only path)', async () => {
const student = await newStudent(owner, 'Root11');
// ROOT 11's real prerequisite is ROOT 10 complete — test setup, not this test's own subject.
await completeThroughRoot10(student.session);
for (const suffix of ['a01-lesson-devops-platform-thinking', 'a02-knowledge-check', 'a03-reflection']) {
const { status } = await student.session.request('POST', `/api/student/activities/root-11-m01-${suffix}/complete`);
assert.equal(status, 200);