e2e-phase9.js: one new deterministic regression test proving the webhook-correlation fix — a test event referencing a transaction's own UUID (the exact shape a real Stripe payment_intent.payment_failed/charge.refunded event now produces) is still matched. 20/20. stripe-integration-test.js: configurable price/currency (STRIPE_TEST_PRICE_CENTS/ STRIPE_TEST_CURRENCY) and a STRIPE_TEST_MINOR=true guardian+minor path. Once a real payment lands, now fully automates deep post-payment verification: exactly-one payment_transactions record, registration/enrollment/cohort/guardian reconciliation, a genuine duplicate-delivery proof (re-signing the REAL event fetched via stripe.events.retrieve — never a fabricated one), real student activation + program-visibility, and a real refund with the non-cascade rule reconfirmed — plus a fully automated, browser-free abandoned-checkout proof via Stripe's own sessions.expire() API. Still opt-in, still a guaranteed no-op by default, still refuses live-mode keys.
RootNode Academy — Platform
The full multi-student platform, layered on top of the Lab image at the repo root (backend/
entrypoint/Dockerfile unmodified; frontend bundle gained Phase 3B learning UX, see
docs/PHASE3B_LEARNING_AND_ASSESSMENT.md §10–11). Start here, then go to the component you need:
provisioner/— Postgres-backed environment registry and lifecycle. Design:docs/LAB_ARCHITECTURE.md,docs/PHASE1_PLATFORM_FOUNDATION.md. Operational reference:provisioner/README.md.api/— the lightweight Platform API: students, cohorts and cohort membership, curriculum, detailed scenario attempt telemetry, progressive hints, curriculum completion rules, the exam + capstone assessment engine, achievements, per-instructor accounts, persistent student/ instructor sessions, RBAC, audit logging, real student credentials + remote classrooms (Phase 5A), self-paced programs/enrollment + the website integration boundary (Phase 5B), and a provider-neutral enrollment-communications pipeline (activation/enrollment-confirmation/ password-reset email, Phase 5C), the ROOT_ course/module/activity content model layered over curriculum/assessments (Phase 6A) with real teaching content across most of the Kubernetes courses and a server-side capstone unlock enforcement fix (Phase 6B), and the instructor/admin control plane. No k3s, no--privileged— this replaced Phase 2A's "identity" instance, which was a full Lab image booting an unused k3s cluster just to serve these APIs. Design:docs/PHASE2B_ADMIN_AND_PLATFORM_DATA.md(split + Postgres migration),docs/PHASE3A_IDENTITY_AND_INSTRUCTOR_ACCESS.md(sessions, accounts, RBAC, audit),docs/PHASE3B_LEARNING_AND_ASSESSMENT.md(learning/assessment engine),docs/PHASE5A_STUDENT_IDENTITY_REMOTE_CLASSROOM.md(student credentials, remote classroom),docs/PHASE5B_SELF_PACED_ENROLLMENT.md(programs, enrollment, website integration boundary),docs/PHASE5C_ENROLLMENT_COMMUNICATIONS.md(notifications, email delivery),docs/PHASE6A_ROOT_COURSE_FOUNDATION.md(course/module/activity content model, gold-standard module),docs/PHASE6B_ROOT_CURRICULUM_EXPANSION.md(curriculum authoring across ROOT 05-10/12/Capstone, the capstone unlock fix). Operational reference:api/README.md.gateway/— the only student-facing path to a Lab (HTTP + WebSocket proxy, environment-aware routing derived from a server-controlled session — not a client-suppliable header/param — and, as of Phase 3B, server-side gating of progressive hint reveal). Design:docs/PHASE2A_GATEWAY_AND_STUDENT_FLOW.md(routing),docs/PHASE3A_IDENTITY_AND_INSTRUCTOR_ACCESS.md(persistent sessions, CSRF),docs/PHASE3B_LEARNING_AND_ASSESSMENT.md§3 (hint gating).egress-proxy/— shared allowlisting proxy every Lab's outbound traffic passes through.lab-router/— Phase 4.1 addition. Runs on the Lab Host VM (ordev-local's stand-in in local dev, thoughdirectmode there never exercises it); resolves an opaque routing token to a local Lab container purely from Docker state, over mTLS, so the Gateway never needs a Docker-internal address. Design:docs/PHASE4_1_PREPRODUCTION_HARDENING.md§1-3.docker-compose.dev.yml— the full stack (Postgres, egress proxy, Provisioner, Platform API, Gateway) on one machine. This is the Phase 1/2A/2B local dev exception — production is the two-VM Proxmox split described indocs/LAB_ARCHITECTURE.md§1.
Running the full stack locally
cd platform
echo "PROVISIONER_SHARED_SECRET=$(openssl rand -hex 32)" > .env
docker compose -f docker-compose.dev.yml build
docker compose -f docker-compose.dev.yml up -d
# First run only — create the owner account (no ADMIN_PASSWORD anymore, see
# docs/PHASE3A_IDENTITY_AND_INSTRUCTOR_ACCESS.md):
cd api
PLATFORM_DB_URL=postgres://postgres:postgres@localhost:5433/rootnode_platform \
node bootstrap-owner.js [email protected] "Your Name" "a-real-password-12-chars-plus"
No k3s boot to wait for at this layer anymore (Platform API starts instantly) — the whole
platform is reachable at http://localhost:4200 (the Gateway) right away: students at /,
instructors at /admin (sign in with the owner account you just bootstrapped, then create
further instructor accounts from the Instructors tab).
Tests
provisioner/test/run.js— fast, isolated Provisioner tests (network/container/firewall mechanics, no Gateway/Platform API involved). Run againstprovisioner/docker-compose.dev.yml.test/e2e-phase2a.js— student-flow regression through real session cookies: roster → login → curriculum → launch → Gateway routing → cross-student rejection → progress relay → reset.test/e2e-phase2b.js— the classroom acceptance workflow: instructor creates a cohort, adds students, assigns curriculum, Prepare Labs (concurrent), students complete different scenarios, instructor resets one, instructor destroys all cohort Labs after class — progress survives every step.test/e2e-phase3a.js— persistent sessions (including a real Platform API restart), instructor auth (no enumeration, rate limiting), RBAC across owner/admin/instructor, cohort-scoped instructor access, CSRF enforcement, password change, and audit logging.test/e2e-phase3b.js— attempt telemetry, progressive hint sequencing/persistence through a Lab reset, curriculum stage/Capstone lock-unlock, the exam engine (random draw, server timer, expiry, restart persistence), the Capstone workflow end to end, two-student assessment isolation, and instructor learning analytics/RBAC.test/e2e-phase5a.js— real student credentials (activation/password reset/login), remote classroom sessions (join/heartbeat/announcements/help requests), and instructor conversion of a pilot student to a credentialed account.test/e2e-phase5b.js— programs/enrollment, entitlement gating (pending/future/expired/ cancelled), server-derived completion, disposable-Lab persistence across destroy/recreate, and the website/commerce integration boundary (idempotency, concurrency, RBAC).test/e2e-phase5c.js— the notification lifecycle (queue/retry/failure/exhaustion), activation/enrollment-confirmation/password-reset email delivery via the deterministic dev provider, minor/no-email contact-email resolution, and admin notification RBAC.test/e2e-phase6a.js— course/module/activity hierarchy and ordering, prerequisite-based locking, all three completion behaviors (manual marker/scenario/assessment-derived), resume, cross-student isolation, RBAC for self-paced (no-cohort) students, and draft-content hiding.test/e2e-phase6b.js— hierarchy for the 13 new modules across ROOT 05-10/12/Capstone, prerequisite chains through the newly-inserted front-door modules, the optional-vs-required (is_required) completion rule, and the capstone server-side unlock fix in both directions (a direct bypass attempt rejected, a legitimately unlocked attempt still accepted).test/e2e-phase6c.js— curriculum completion upgrades, the security assessment bank, and ROOT 11's honest lesson-only scope.test/e2e-phase6d.js— ROOT 01 (Computing & Linux Foundations) as the program's real front door, and the real ROOT 01-04 foundational prerequisite chain.test/e2e-phase6e.js— ROOT 02/03/04 completion, the full ROOT 01->02->03->04->05 chain proven via direct API, and the course/module lock-enforcement fix on the manual-completion route.test/e2e-phase6f.js— the ROOT_ V1 curriculum manifest (inclusions/exclusions), the full V1 prerequisite graph (no cycles, no missing nodes), the strengthened Capstone gate (ROOT 09 AND ROOT 12 both required) in both directions, the new orientation module, and an explicit check that no age/grade gate exists anywhere in enrollment.test/e2e-phase7.js— organizations/locations, guardians and student-guardian relationships, curriculum packages (reference-only, never a content clone), offerings (capacity, server- defined status transitions), the registration lifecycle through agreement acceptance and conversion to a real enrollment, registration/guardian RBAC, explicit age-guidance-is-never- an-authorization-check verification, and confirmation that existing ROOT_ V1/self-paced/ classroom flows are unaffected.test/e2e-phase8.js— offering launch readiness, server-authoritative registration open/ closed enforcement, capacity concurrency through the real registration endpoints, the public offering serializer (no internal/PII leakage), the website registration integration contract (sequential and concurrent idempotency), required-agreement conversion blocking with version snapshotting, guardian notification recipient resolution, and confirmation that ROOT_ V1/self- paced/classroom flows remain unaffected.test/e2e-phase9.js— offering pricing in the public serializer, checkout blocked until agreements are accepted, the server-authoritative amount (never caller-supplied), webhook signature verification and idempotency, payment success driving real conversion/activation, a failed payment leaving the seat intact vs. an abandoned checkout releasing it, the reservation reaper, a last-seat checkout race, manual refund with no cascade into registration/enrollment status, minor payment confirmations routing to the guardian, the admin payment view (never card data), instructor/public RBAC on payment data, audit/metric hygiene (no secrets, no high-cardinality labels), the website status-lookup endpoint's minimal safe shape, and confirmation that ROOT_ V1/free-offering/classroom flows remain unaffected.
infra/scripts/drills/ also holds real-Lab dress rehearsals per phase (Phase 6D/6E foundations,
Phase 6F's multi-student pilot and full end-to-end V1 journey, Phase 7/8's Winter Break Camp
rehearsals, Phase 9's Winter Break COMMERCE rehearsal —
test-phase9-winter-camp-commerce.js, 13/13, real checkout/webhook against the deterministic
test payment provider, no live money) — see each script's own header and
docs/PHASE6F_ROOT_V1_PILOT_READINESS.md/docs/PHASE8_ENROLLMENT_OPERATIONS.md/docs/ PHASE9_COMMERCE_AND_LAUNCH.md for what they prove beyond what the e2e suites above already
cover via direct API.
All e2e suites need OWNER_EMAIL / OWNER_PASSWORD set to a real bootstrapped instructor
account (see "Running the full stack locally" above) — the old ADMIN_PASSWORD mechanism is
gone. They're slow (real k3s boots, e2e-phase3b.js also runs a real API restart) and need the
full stack up. Run each suite against its own freshly-reset stack, not chained one after another
against the same Postgres — several routes (the admin dashboard's running-lab count, a course's
percent-complete) read GLOBAL state on purpose, and chaining suites produces false failures from
shared state, not real regressions (found and documented during Phase 6A's own regression pass).
None of them substitute for the manual real-Lab-image checks (node-pty, terminal
WebSocket, kubectl, scenario validation) in docs/PHASE2A_GATEWAY_AND_STUDENT_FLOW.md §8 —
those need a browser-equivalent client and aren't automated here. Two exceptions get a real-Lab
dress rehearsal instead: Phase 6A's gold-standard module
(infra/scripts/drills/run-phase6a-course-rehearsal.sh) and Phase 6B's curriculum expansion,
including a real kubectl expose and a real instructor/classroom narrative
(infra/scripts/drills/run-phase6b-root-course-rehearsal.sh).
Known limitations
See provisioner/README.md (Phase 1), docs/PHASE2A_GATEWAY_AND_STUDENT_FLOW.md §9 (Phase
2A), docs/PHASE3A_IDENTITY_AND_INSTRUCTOR_ACCESS.md (Phase 3A identity limitations), and
docs/PHASE3B_LEARNING_AND_ASSESSMENT.md §20 (Phase 3B learning/assessment limitations) for the
full, honest lists. Phase 3B's "no automatic abandoned-attempt detection" and "no per-cohort
needs-attention threshold configuration" limitations were both closed in Phase 4.1 (§13/§17 of
docs/PHASE4_1_PREPRODUCTION_HARDENING.md); its raw-JSON pool_spec limitation was replaced with
a structured editor (§18) with an owner-only raw-JSON fallback still available. Phase 4.1's own
known limitation: real remote/self-paced delivery needs student email/password authentication,
not built — see docs/REMOTE_AND_SELF_PACED_ARCHITECTURE.md.
Running this in production
../infra/ has the full production infrastructure package (Ansible, Terraform, firewall,
monitoring, backup, CI/CD) for deploying the Platform API/Gateway/Provisioner/Postgres onto a
dedicated rn-platform-01 VM, with student Labs — and a new lab-router service
(../platform/lab-router/) — on a separate rn-lab-host-01 VM. See
../docs/PHASE4_PRODUCTION_INFRASTRUCTURE.md for the overview and
../docs/PHASE4_1_PREPRODUCTION_HARDENING.md for how Gateway-to-Lab reachability across two
real VMs was closed (a Lab Host Router + mTLS data plane, simulation-tested in
../infra/scripts/simulate-two-host/) and for the rest of Phase 4.1's pre-production hardening.