New: ROOTNODE_ACADEMY_BUSINESS_AND_PRODUCT_ARCHITECTURE.md (canonical business/platform bridge — course families, delivery modes, BUILT NOW/PHASE 7 BUILT/FUTURE), OFFERING_AND_ REGISTRATION_MODEL.md (lifecycle diagrams, capacity mechanics), STUDENT_GUARDIAN_DATA_ MODEL.md (data minimization, RBAC, legal-review items), SCHOOL_AND_COMMUNITY_DELIVERY_ ARCHITECTURE.md (organization/location mapping, explicitly not multi-tenancy), PHASE7_PRODUCT_ARCHITECTURE.md (phase summary). Updated: WEBSITE_ENROLLMENT_INTEGRATION.md (documented future offering/registration flow, current live contract unchanged), platform/README.md (test list). Co-Authored-By: Claude Sonnet 5 <[email protected]>
9.5 KiB
Phase 7 — Business & Product Architecture Foundation
Phase 7's mission: formalize RootNode Academy as a business/product platform capable of a future catalog across INIT_/COMPILE_/ROOT_/BOOTCAMP_, without rebuilding any proven system and without authoring new curriculum.
Baseline
Confirmed at start: branch main, clean tree, HEAD = origin/main = Phase 6F's
d0664104515e4bd389c8e7bc88293327e7eda100, tag v0.6.0-root-v1 present. Reviewed existing
programs/enrollments/cohorts/classroom_sessions/students schema before designing
anything new — found programs.family/delivery_mode and cohorts.seat_target already
existed, so Phase 7 widened rather than duplicated them where it could.
What was built
- Organizations, locations (
033_phase7_organizations_locations_guardians.sql) — a business/ delivery relationship and a physical/virtual venue, deliberately not SaaS multi-tenancy. - Guardians, student_guardians (same migration) — the real foundation replacing the Phase
5C
contact_emailworkaround, left functioning unchanged for the live website integration. - Offerings, curriculum packages, registrations, agreements
(
034_phase7_offerings_registrations_agreements.sql) — the full purchasable-delivery model, pre-enrollment registration lifecycle, versioned agreement acceptance records, and reference-only curriculum subsets. platform/api/src/offerings.js— the business logic: server-defined status transitions (offerings and registrations both), race-safe atomic seat reservation, andconvertRegistrationToEnrollment()reusingenrollment.js's existing student-matching and enrollment-creation logic rather than duplicating it.platform/api/src/admin-offerings-routes.js— the full admin/owner API surface for all of the above, registered inserver.jsalongside the existing route modules.programs.familywidened to acceptbootcamp(a pure CHECK-constraint widening; no existing row changes).
What was NOT built this phase
Admin UI (Steps 16/17 — Offerings/Registrations screens in /admin). This was a deliberate
scope call: given the phase's own emphasis ("BUSINESS DOMAIN MODEL, PRODUCT ARCHITECTURE,
ENROLLMENT/OPERATIONS ARCHITECTURE"), the schema, business logic, RBAC, tests, and a full
working rehearsal were prioritized as the load-bearing deliverable over a polished React
surface. Every capability Steps 16-17 describe is fully implemented and tested at the API layer
— an admin UI is now a bounded, well-specified follow-up (the routes and their exact shapes
already exist), not a design question.
Also not built, per the explicit DO-NOT-BUILD list: payment processing, subscription billing, public checkout, INIT_/COMPILE_ curriculum, ROOT 08/11 expansion, a Parent Portal UI, school SIS integrations, certificates.
A real bug found and fixed during this phase's own testing
The registration-creation route originally reserved an offering's seat before validating the
registration's own fields. Under a full offering, this let a plain validation failure (e.g. a
minor registration missing guardianId) get masked behind a false 409 OFFERING_FULL, and even
when capacity wasn't the issue, a request that would fail validation anyway had already consumed
a real seat. Found via this phase's own smoke testing before any commit; fixed by validating
(creating) the registration first, then reserving the seat, rolling back the registration row if
reservation then genuinely fails. See docs/OFFERING_AND_REGISTRATION_MODEL.md.
Testing
platform/test/e2e-phase7.js — 17/17 passing (fresh stack): course-family/age-guidance
independence from authorization, offerings across two distinct locations, curriculum-package
content-reference integrity (including rejecting a fabricated course id), atomic capacity
enforcement with correct validate-before-reserve ordering, instructor assignment, guardian
creation/linking, minor-vs-adult registration paths, registration lifecycle transitions
(including a rejected invalid jump), agreement-version-snapshotted acceptance, registration→
enrollment conversion with correct cohort/no-cohort behavior, curriculum-package version-bump
stability, registration/guardian RBAC (admin/owner only) vs. offering/location visibility (any
instructor, with access_notes stripped), explicit age-independence, and confirmation that an
existing ROOT_ V1 self-paced enrollment and an existing classroom/cohort flow are both
completely unaffected.
Winter Break Camp rehearsal
infra/scripts/drills/test-phase7-winter-camp.js — 18/18 passing against a real, freshly-built
stack, with no payment code invoked anywhere: admin creates a real organization/location, a
curriculum package referencing the existing ROOT 01-02 courses (matching docs/ WINTER_BREAK_CAMP_CURRICULUM_OPTIONS.md's Option A exactly), a real cohort, and a DRAFT offering
with dates/price/exact seat count left unset as authorized; a guardian registers a minor and an
adult registers themselves; both move through submission, agreement acceptance, and a simulated
admin-side commerce confirmation; both convert to real enrollments and real cohort membership
with real one-time activation tokens; both activate real accounts; the instructor starts a real
classroom session and Prepares Labs; the minor student joins, completes real ROOT 01 orientation
and lesson activities, and passes a REAL Lab task (linux-inspect-hardware) inside a real
disposable Lab container — with the pass then confirmed to survive a real logout/login; the
instructor sees real roster state; the offering moves through in_progress to completed.
ROOT_ V1 regression
e2e-phase6f.js rerun fresh: 17/17 unaffected. e2e-phase5b.js (the enrollment-related suite
most exposed by this phase's one shared-code change — enrollment.js's resolveOrCreateStudent
gained an optional, default-off parameter) rerun fresh: 32/32, including its own embedded
"Phase 5A student authentication still works unmodified" and "Phase 5A remote classroom still
works unmodified" checks. e2e-phase5c.js was not independently re-run: no code in
notifications.js/students-auth.js itself changed, only an additive, default-off parameter on
a function notifications.js doesn't call.
Frontend, migrations, content validation
Frontend: npm run build clean, no frontend code touched this phase. Content validation:
0 errors, unchanged from Phase 6F's own numbers (this phase touched no course/module/activity
content at all). Migrations: additive-only, CREATE TABLE IF NOT EXISTS/DROP CONSTRAINT IF EXISTS throughout, applied once via the existing schema_migrations tracking mechanism — no
destructive statement anywhere in 033/034.
Documentation
New: ROOTNODE_ACADEMY_BUSINESS_AND_PRODUCT_ARCHITECTURE.md, OFFERING_AND_REGISTRATION_ MODEL.md, STUDENT_GUARDIAN_DATA_MODEL.md, SCHOOL_AND_COMMUNITY_DELIVERY_ARCHITECTURE.md,
this document. Updated: WEBSITE_ENROLLMENT_INTEGRATION.md (a documented future flow, current
contract unchanged). Reviewed, no change needed: REMOTE_AND_SELF_PACED_ARCHITECTURE.md,
ENGINEERING_SOP.md, LAB_ARCHITECTURE.md, platform/README.md (no new e2e-suite convention
beyond what its existing list format already documents — see commit history for the actual
platform/README.md addition covering e2e-phase7.js).
Known limitations
- No admin UI for offerings/registrations — the API is complete and tested; the screens are a well-specified follow-up, not a design question.
- Guardian communications are not yet wired into
notifications.js—student_guardians.can_receive_communications/is_primary_contactare recorded but nothing currently reads them to route an actual email. - The website integration endpoint does not yet accept an
offering_slug— the future flow is documented, not implemented; that decision (extend the existing endpoint vs. a parallel one) was deliberately left open pending real website/checkout design. agreement_acceptances.accepted_by_type = 'student'is only meaningful post-conversion, once a realstudent_idexists — an adult accepting at registration time (before their account exists) currently must accept as'guardian'-style even when self-registering; a future phase could add a'self'accepted-by-type distinct from'guardian'if that distinction becomes operationally important.
Items requiring legal/business review before production use with real minors
See docs/STUDENT_GUARDIAN_DATA_MODEL.md's own list in full: the actual legal text of any
participation/media/liability agreement, jurisdiction-specific minor-consent requirements,
data-retention/deletion policy for guardian and registration records, and whether emergency-
contact fields need additional encryption at rest. None of these are resolved by this phase's
work — they are named so they are never silently assumed away.
Bugs found/fixed
One (the seat-reservation-before-validation ordering bug above), found and fixed before any commit via this phase's own smoke testing — not discovered by a later test run.
Recommended Phase 8 scope
The admin UI for offerings/registrations (the one deliberately deferred piece of this phase's
own mission); wiring guardian communications into notifications.js; and, informed by real
business decisions, either extending /api/integrations/enrollments with an offering_slug
or designing the parallel endpoint the future commerce flow needs — plus whatever the business
decides about actually opening Winter Break Camp registration (which requires payment
processing, explicitly out of every phase's scope so far).