Files
rootnode-academy/docs/PHASE7_PRODUCT_ARCHITECTURE.md
T
ced4568andClaude Sonnet 5 df5e39f767 docs(phase7): document the business/product architecture foundation
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]>
2026-08-26 22:44:13 -05:00

152 lines
9.5 KiB
Markdown

# 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_email` workaround, 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, and
`convertRegistrationToEnrollment()` reusing `enrollment.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 in `server.js` alongside the existing route modules.
- **`programs.family` widened** to accept `bootcamp` (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_contact` are 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 real `student_id` exists — 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).