// Shared test helper — completes the real ROOT 01-04 foundations sequence for a given session. // Phase 6E gave ROOT 01-04 real content, so ROOT 05's own prerequisite (root-04) is no longer // vacuously satisfied: any e2e suite that drives a student into ROOT 05+ content now needs this // as real test setup first (not this file's own subject — see e2e-phase6e.js for the actual // ROOT 01-04 acceptance coverage). Extracted here once other suites needed the identical // sequence, rather than duplicating it in e2e-phase6a/6b/6c/6d.js. async function completeRootFoundations01Through04(session) { async function act(id) { const r = await session.request('POST', `/api/student/activities/${id}/complete`); if (r.status !== 200) throw new Error(`completing ${id} failed: ${r.status} ${JSON.stringify(r.body)}`); } async function scen(sid) { const r = await session.request('POST', '/api/progress/record', { scenario_id: sid, status: 'completed' }); if (r.status !== 200) throw new Error(`completing scenario ${sid} failed: ${r.status}`); } // Phase 6F — the ROOT_ V1 orientation module (Steps 9-11) is a real, required front-door // module now gating root-01-m01, same as root-05-m00 already gates root-05-m01. await act('root-01-m00-a01-lesson-welcome'); await act('root-01-m00-a02-lesson-disposable-lab'); await act('root-01-m00-a03-knowledge-check'); await act('root-01-m01-a01-lesson'); await act('root-01-m01-a02-guided-exercise'); await scen('linux-inspect-hardware'); await act('root-01-m01-a04-reflection'); const l1 = { m02: 'linux-shell-basics', m03: 'linux-filesystem-navigation', m04: 'linux-files-and-text', m05: 'linux-process-management', m06: 'linux-permissions-foundations' }; for (const m of ['m02', 'm03', 'm04', 'm05', 'm06']) { await act(`root-01-${m}-a01-lesson`); await act(`root-01-${m}-a02-demonstration`); await act(`root-01-${m}-a03-knowledge-check`); await act(`root-01-${m}-a04-guided-exercise`); await scen(l1[m]); await act(`root-01-${m}-a06-reflection`); } let r = await session.request('POST', '/api/assessments/rootnode-linux-foundations-assessment/start'); for (const s of r.body.scenarios) await scen(s.scenario_id); await act('root-02-m01-a01-lesson'); await act('root-02-m01-a02-demonstration'); await act('root-02-m01-a03-knowledge-check'); await act('root-02-m01-a04-guided-exercise'); await scen('linux-service-inspection'); await scen('linux-service-lifecycle'); await act('root-02-m01-a07-reflection'); await act('root-02-m02-a01-lesson'); await act('root-02-m02-a02-demonstration'); await act('root-02-m02-a03-knowledge-check'); await act('root-02-m02-a04-guided-exercise'); await scen('linux-storage-inspection'); await act('root-02-m02-a06-reflection'); await act('root-02-m03-a01-lesson'); await act('root-02-m03-a02-demonstration'); await act('root-02-m03-a03-knowledge-check'); await act('root-02-m03-a04-guided-exercise'); await act('root-02-m03-a05-reflection'); await act('root-02-m04-a01-lesson'); await act('root-02-m04-a02-demonstration'); await act('root-02-m04-a03-knowledge-check'); await act('root-02-m04-a04-guided-exercise'); await scen('linux-network-inspection'); await act('root-02-m04-a06-reflection'); await act('root-02-m05-a01-lesson'); await act('root-02-m05-a02-demonstration'); await act('root-02-m05-a03-knowledge-check'); await act('root-02-m05-a04-guided-exercise'); await scen('linux-dns-http-diagnosis'); await act('root-02-m05-a06-reflection'); await act('root-02-m06-a01-lesson'); await act('root-02-m06-a02-demonstration'); await act('root-02-m06-a03-knowledge-check'); await act('root-02-m06-a04-guided-exercise'); await scen('linux-troubleshooting-framework'); await act('root-02-m06-a06-reflection'); r = await session.request('POST', '/api/assessments/rootnode-linux-networking-assessment/start'); for (const s of r.body.scenarios) await scen(s.scenario_id); await act('root-03-m01-a01-lesson'); await act('root-03-m01-a02-knowledge-check'); await act('root-03-m01-a03-reflection'); await act('root-03-m02-a01-lesson'); await act('root-03-m02-a02-demonstration'); await act('root-03-m02-a03-knowledge-check'); await act('root-03-m02-a04-guided-exercise'); await act('root-03-m02-a05-reflection'); await act('root-03-m03-a01-lesson'); await act('root-03-m03-a02-demonstration'); await act('root-03-m03-a03-knowledge-check'); await act('root-03-m03-a04-guided-exercise'); await scen('git-everyday-basics'); await act('root-03-m03-a06-reflection'); await act('root-03-m04-a01-lesson'); await act('root-03-m04-a02-demonstration'); await act('root-03-m04-a03-knowledge-check'); await act('root-03-m04-a04-guided-exercise'); await scen('git-branching-and-merge'); await act('root-03-m04-a06-reflection'); await act('root-03-m05-a01-lesson'); await act('root-03-m05-a02-demonstration'); await act('root-03-m05-a03-knowledge-check'); await act('root-03-m05-a04-guided-exercise'); await scen('git-remote-clone-push'); await act('root-03-m05-a06-reflection'); await act('root-03-m06-a01-lesson'); await act('root-03-m06-a02-demonstration'); await act('root-03-m06-a03-knowledge-check'); await act('root-03-m06-a04-reflection'); r = await session.request('POST', '/api/assessments/rootnode-git-fundamentals-assessment/start'); for (const s of r.body.scenarios) await scen(s.scenario_id); await act('root-04-m01-a01-lesson'); await act('root-04-m01-a02-demonstration'); await act('root-04-m01-a03-knowledge-check'); await act('root-04-m01-a04-reflection'); await act('root-04-m02-a01-lesson'); await act('root-04-m02-a02-demonstration'); await act('root-04-m02-a03-knowledge-check'); await act('root-04-m02-a04-guided-exercise'); await act('root-04-m02-a05-reflection'); const l4 = { m03: 'docker-cli-basics', m04: 'docker-container-networking', m05: 'docker-container-storage', m06: 'docker-build-image', m07: 'docker-compose-multiservice' }; for (const m of ['m03', 'm04', 'm05', 'm06', 'm07']) { await act(`root-04-${m}-a01-lesson`); await act(`root-04-${m}-a02-demonstration`); await act(`root-04-${m}-a03-knowledge-check`); await act(`root-04-${m}-a04-guided-exercise`); await scen(l4[m]); await act(`root-04-${m}-a06-reflection`); } await act('root-04-m08-a01-lesson'); await act('root-04-m08-a02-knowledge-check'); await act('root-04-m08-a03-reflection'); r = await session.request('POST', '/api/assessments/rootnode-containers-assessment/start'); for (const s of r.body.scenarios) await scen(s.scenario_id); } // Completes ROOT 01-04, then ROOT 05 (m00 + m01), then ROOT 06 (m01/m02/m03) fully — the // complete real prerequisite chain any suite needs before touching ROOT 09/10/11 content // (their course-level prerequisite is root-06 complete, or a course chained behind it). async function completeThroughRoot06(session) { await completeRootFoundations01Through04(session); async function act(id) { const r = await session.request('POST', `/api/student/activities/${id}/complete`); if (r.status !== 200) throw new Error(`completing ${id} failed: ${r.status} ${JSON.stringify(r.body)}`); } async function scen(sid) { const r = await session.request('POST', '/api/progress/record', { scenario_id: sid, status: 'completed' }); if (r.status !== 200) throw new Error(`completing scenario ${sid} failed: ${r.status}`); } await act('root-05-m00-a01-lesson-mental-model'); await act('root-05-m00-a02-demonstration-desired-state'); await act('root-05-m00-a03-knowledge-check'); await act('root-05-m00-a04-guided-exercise-namespaces'); await scen('namespaces-basics'); await act('root-05-m00-a06-reflection'); const root05m01Scenarios = ['pod-basics-mcq', 'kubectl-essentials-mcq', 'labels-selectors-mcq', 'init-containers-mcq', 'namespaces-basics', 'multi-container-pod-basics', 'pod-labeling-basics', 'jsonpath-basics', 'dry-run-manifest-basics', 'delete-by-label-basics', 'annotate-resource-basics', 'init-container-task-basics', 'restart-policy-mcq', 'kubectl-output-format-mcq', 'kubectl-explain-basics', 'kubectl-patch-basics', 'field-selector-basics', 'sort-by-basics', 'kubectl-diff-basics', 'static-pods-mcq']; for (const sid of root05m01Scenarios) await scen(sid); await act('root-05-m01-a00-lesson-kubectl-fluency'); await act('root-05-m01-a21-reflection'); for (const sid of ['deploy-nginx', 'scale-deployment', 'rolling-update-task', 'liveness-probe-task', 'jobs-cronjobs-mcq', 'daemonset-mcq', 'edit-deployment-basics', 'deployment-strategy-mcq', 'deployment-rollback']) await scen(sid); await act('root-06-m02-a01-lesson-deployments'); await act('root-06-m02-a02-demonstration-rollout'); await act('root-06-m02-a03-knowledge-check'); await act('root-06-m02-a04-guided-exercise-rollout-strategy'); await act('root-06-m02-a06-reflection'); // Phase 6F gave root-06-m01 (a bare practice bank) a real intro-lesson activity — required, // prerequisite root-06-m02 complete (just finished above). await act('root-06-m01-a00-lab-bank-intro'); for (const sid of ['job-task', 'cronjob-task']) await scen(sid); await act('root-06-m03-a01-lesson-jobs-cronjobs-daemonsets'); await act('root-06-m03-a01b-demonstration-job-lifecycle'); await act('root-06-m03-a02-knowledge-check'); await act('root-06-m03-a02b-guided-exercise-choosing-primitive'); await act('root-06-m03-a05-reflection'); } // Completes through ROOT 06, then ROOT 10 (m01/m02/m03) fully — the real prerequisite ROOT 11 // (and anything else chained behind ROOT 10) needs. async function completeThroughRoot10(session) { await completeThroughRoot06(session); async function act(id) { const r = await session.request('POST', `/api/student/activities/${id}/complete`); if (r.status !== 200) throw new Error(`completing ${id} failed: ${r.status} ${JSON.stringify(r.body)}`); } async function scen(sid) { const r = await session.request('POST', '/api/progress/record', { scenario_id: sid, status: 'completed' }); if (r.status !== 200) throw new Error(`completing scenario ${sid} failed: ${r.status}`); } await act('root-10-m01-a01-lesson-health-check-concepts'); await act('root-10-m01-a02-demonstration-probes-in-action'); await act('root-10-m01-a03-knowledge-check'); await act('root-10-m01-a04-guided-exercise-readiness-probe'); await scen('rootnode-probe-port-mismatch'); await act('root-10-m01-a06-reflection-real-ops-takeaway'); await act('root-10-m03-a01-lesson-framework'); await act('root-10-m03-a02-demonstration-crashloop'); await act('root-10-m03-a03-knowledge-check'); await act('root-10-m03-a03b-guided-exercise-endpoint-mismatch'); await scen('crashloop-fix'); await scen('endpoint-fix-task'); await act('root-10-m03-a06-reflection'); // Phase 6F gave root-10-m02 (a bare practice bank) a real intro-lesson activity — required, // prerequisite root-10-m03 complete (just finished above). await act('root-10-m02-a00-lab-bank-intro'); for (const sid of ['extract-logs-basics', 'kubectl-cp-basics', 'exec-command-basics', 'node-cordon-basics', 'pod-node-assignment-mcq', 'container-logging-mcq']) await scen(sid); } // Phase 6F — completes through ROOT 10, then ROOT 07 (m03/m04, needed by root-09-m03's // cross-course prerequisite), ROOT 09 (all 4 modules + its assessment), and ROOT 12, leaving // the student's root-capstone COURSE genuinely unlocked (both its real prerequisites — // root-12-real-ops and root-09-kubernetes-security — satisfied) without touching the Capstone // itself. See docs/ROOT_V1_CURRICULUM_MANIFEST.md Step 7 for why root-09 is now required. async function completeThroughCapstoneEligible(session) { await completeThroughRoot10(session); async function act(id) { const r = await session.request('POST', `/api/student/activities/${id}/complete`); if (r.status !== 200) throw new Error(`completing ${id} failed: ${r.status} ${JSON.stringify(r.body)}`); } async function scen(sid) { const r = await session.request('POST', '/api/progress/record', { scenario_id: sid, status: 'completed' }); if (r.status !== 200) throw new Error(`completing scenario ${sid} failed: ${r.status}`); } for (const suffix of ['a01-lesson-services', 'a02-demonstration-zero-endpoints', 'a03-knowledge-check', 'a04-guided-exercise-service-type', 'a06-reflection']) await act(`root-07-m03-${suffix}`); await scen('expose-service-basics'); // root-07-m03's own lab — its OWN prerequisite for root-07-m04, easy to miss since ROOT 07's practice bank reuses the same scenario id. for (const suffix of ['a01-lesson-ingress-networkpolicy', 'a01b-demonstration-ingress-routing', 'a02-knowledge-check', 'a02b-guided-exercise-network-isolation', 'a05-reflection']) await act(`root-07-m04-${suffix}`); await scen('ingress-task'); await scen('network-policy'); for (const suffix of ['a01-lesson-security-model', 'a02-demonstration-securitycontext', 'a03-knowledge-check', 'a04-guided-exercise-hardening-plan', 'a09-reflection']) await act(`root-09-m01-${suffix}`); for (const sid of ['cks-non-root-enforce', 'cks-drop-capabilities', 'cks-readonly-filesystem', 'cks-pod-security-context']) await scen(sid); for (const suffix of ['a01-lesson-serviceaccounts-rbac', 'a01b-demonstration-rbac-denial', 'a02-knowledge-check', 'a02b-guided-exercise-least-privilege-role', 'a09-reflection']) await act(`root-09-m02-${suffix}`); for (const sid of ['serviceaccount-pod', 'cks-automount-token', 'rbac-role', 'cks-rbac-clusterrole', 'cks-rbac-least-privilege']) await scen(sid); for (const suffix of ['a01-lesson-network-supplychain', 'a01b-demonstration-default-deny', 'a02-knowledge-check', 'a02b-guided-exercise-egress-plan', 'a09-reflection']) await act(`root-09-m03-${suffix}`); for (const sid of ['cks-network-policy', 'cks-egress-namespace', 'cks-tls-ingress', 'cks-image-pull-secret']) await scen(sid); await act('root-09-m04-a01-lesson-cluster-hardening'); for (const sid of ['cks-psa-namespace', 'cks-immutable-secret']) await scen(sid); const r = await session.request('POST', '/api/assessments/rootnode-security-assessment/start'); for (const s of r.body.scenarios) await scen(s.scenario_id); await act('root-12-m01-a00-lesson-incident-response'); for (const sid of ['rootnode-runaway-monitor', 'broken-deployment', 'rootnode-enforce-limits']) await scen(sid); await act('root-12-m01-a99-reflection-post-incident'); } module.exports = { completeRootFoundations01Through04, completeThroughRoot06, completeThroughRoot10, completeThroughCapstoneEligible };