test(phase6d): acceptance suite, real-Lab rehearsal, and 6A/6B resume-point fixes

Adds platform/test/e2e-phase6d.js (20 checks): ROOT 01 hierarchy, ROOT 02-04 honestly empty
(not padded), activity ordering, lab-cannot-be-manually-completed, full course completion via
lesson/lab/assessment, the prerequisite chain proven on two students (one who completed ROOT 01,
one fresh), existing ROOT 05+/Capstone state unaffected, is_required unaffected, self-paced
no-instructor-deferral check, instructor-notes exclusion, RBAC, logout/login persistence,
cross-student isolation, version-bump stability, and content validation. 20/20 pass against both
a warm and a freshly-reset dev stack.

Adds infra/scripts/drills/{run,test}-phase6d-foundations-rehearsal — the real disposable-Lab
dress rehearsal: login -> enroll -> lesson/guided-exercise -> real Lab launch -> real bash
hardware inspection (nproc/free/df, written to a real file) -> real validation -> a genuine
permission denial (a second Linux user actually denied via `su -`) then a real chmod fix ->
remaining modules -> assessment -> course completion -> ROOT 02 unlock -> logout/login
persistence -> instructor visibility of the resulting learning state. 11/11 pass against a real
Lab container, no mocking. (First run caught a real triple-escaping bug in the hardware-report
step's inline heredoc — fixed by writing the script to a file and docker-cp-ing it in instead.)

Fixes two stale hardcoded assumptions in e2e-phase6a.js and e2e-phase6b.js that this phase's own
ROOT 01 addition broke: both suites asserted `resume` landed on ROOT 05's gold module as "the
first real content," which was true before this phase and is now ROOT 01. Both suites' original
intent (resume must never land on a bare-lab bank) is preserved, just against the current front
door. Both suites re-verified green (21/21 and 24/24) against a fresh stack.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-26 13:00:17 -05:00
co-authored by Claude Sonnet 5
parent 15b616274b
commit f5379afd5c
5 changed files with 496 additions and 8 deletions
+9 -6
View File
@@ -122,7 +122,7 @@ async function main() {
assert.equal(status, 200);
tree = body.courses;
const root05 = courseById(tree, 'root-05-kubernetes-foundations');
assert.equal(root05.locked, false); // the first real course is never locked
assert.equal(root05.locked, false); // root-04 (its real Phase 6D prerequisite) has zero activities — vacuously satisfied
assert.equal(root05.complete, false);
const root06 = courseById(tree, 'root-06-kubernetes-application-workloads');
assert.equal(root06.locked, true, 'root-06 must be locked until root-05 is complete');
@@ -130,11 +130,14 @@ async function main() {
await test('resume points at the first activity of the first unlocked course', async () => {
const { body } = await casey.request('GET', `/api/student/programs/${enrollmentId}/courses`);
assert.equal(body.resume.course_id, 'root-05-kubernetes-foundations');
// Phase 6B added root-05-m00 (the mental-model module) as the new front door, sequenced
// before the pre-existing bare-lab bank — see docs/PHASE6B_ROOT_CURRICULUM_EXPANSION.md.
assert.equal(body.resume.module_id, 'root-05-m00-kubernetes-mental-model');
assert.equal(body.resume.activity_id, 'root-05-m00-a01-lesson-mental-model');
// Phase 6D authored ROOT 01 (Computing & Linux Foundations) as a real, non-PLANNED course
// sequenced before ROOT 05 — it is now genuinely the program's front door, not ROOT 05's
// own gold module (see docs/PHASE6D_ROOT_FOUNDATIONS.md). This test's original intent —
// "resume lands on the first REAL content, not a bare-lab bank" — is still what's being
// proven, just against the current front door.
assert.equal(body.resume.course_id, 'root-01-computing-linux-foundations');
assert.equal(body.resume.module_id, 'root-01-m01-how-computers-run-software');
assert.equal(body.resume.activity_id, 'root-01-m01-a01-lesson');
});
await test('a student cannot mark a lab activity complete manually — only real scenario completion counts', async () => {