docs(phase10): canonical state audit, secrets inventory, production/security/launch readiness

Gate 1 — canonical state audit: this workstation's HEAD/origin/main and every claimed milestone
SHA (Phase 2A through 9.1) verified consistent; no stranded branches/tags on Gitea; full-history
secret scan clean (no rotation required); Phase 9.5 confirmed absent from canonical history
(pricing was given directly by Chase and used without it); second-PC reconciliation commands
provided since this session cannot see that machine.

Gate 3 — secrets: real inventory derived from process.env greps across every service (not
guessed); Infisical auth fixed (wrong domain was the actual root cause of Phase 9.1's failures —
the identity belongs to infisical.cedshomelab.com, not app.infisical.com) but no RootNode
Academy project exists there yet, contradicting this phase's own authorization — flagged for
Chase to create.

Gates 4-9/15-16 — production: verified, not assumed, that no dedicated server/Proxmox VM has
been provisioned (empty terraform.tfvars, no state file, placeholder group_vars/production.yml)
— IaC validated (tofu validate, ansible --syntax-check both clean) and left unexecuted per this
phase's own 'do not fake production deployment' instruction. rootnodeacademy.com confirmed live
(Cloudflare, real HTTPS) — the separate, not-RootNode-owned website; no Platform API subdomain
exists yet.

Gate 18 — security audit: full review against every regression suite rerun this phase; no
launch-critical defect found.

Gates 19/20 — full regression (131 checks: e2e-phase5b/5c/6f/7/8/9, content validation,
migrations, frontend build) and the Winter Break commerce rehearsal, both rerun clean against
current main.

Gates 10-14/21 — website integration, Winter Break pricing (99/49, real, Stripe-validated),
agreements/legal, tax, and refund policy all reviewed; legal text/tax/refund policy/dates/venue/
capacity correctly left as open owner decisions, not invented.

No application code changed this phase — audit/launch-prep only.
This commit is contained in:
2026-08-28 12:07:25 -05:00
parent a4c0058f8c
commit 0b2303a3e2
6 changed files with 634 additions and 80 deletions
+169
View File
@@ -0,0 +1,169 @@
# Phase 10 — Canonical State Audit
Gate 1. Performed before any Phase 10 production/launch work, per Phase 10's own explicit
requirement — Chase has been developing from two workstations, so nothing downstream was
started until canonical state was actually verified, not assumed.
## Baseline
- Starting SHA (as reported at the end of Phase 9.1): `a4c00580...` (full:
`a4c0058f8c3d9612f1d30508626fa5be00431fa7`).
- This workstation's `HEAD` and `origin/main`, confirmed via `git fetch --all --prune`, are
**both exactly `a4c0058f8c3d9612f1d30508626fa5be00431fa7`** — this workstation is fully
up to date with canonical Gitea, and canonical Gitea has not moved since Phase 9.1 ended.
## 1.1 Current workstation state
- Branch: `main`. Working tree: clean (`git status --short` — no output).
- `git remote -v`: `origin` → `gitea:ced4568/rootnode-academy.git` (fetch + push) — canonical,
no GitHub `kubekosh` remote configured in this clone at all.
- `git branch -vv`: `* main a4c0058 [origin/main]` — tracking correctly, no divergence.
- No stashes, no dangling work, no uncommitted changes.
## 1.2 Remote audit
`git ls-remote --heads --tags origin` shows exactly one branch (`main`, at `a4c0058`) and four
tags (`v0.4.1-preprod`, `v0.5.0-student-auth`, `v0.5.3-enrollment-comms`, `v0.6.0-root-v1`) — no
other branches, no unexpected experimental branches, no stale branches to reconcile.
`git log --all --oneline` and `git log --oneline` both return exactly **184** commits — every
commit reachable in this repository is reachable from `main`. **There is no orphaned or
unmerged work sitting in Gitea outside `main`.**
## 1.3 Second-PC reconciliation
This execution environment has no access to Chase's other workstation. Gitea's own state (§1.2)
proves there is no SECOND branch or unpushed-but-fetchable ref waiting anywhere on the remote —
if work exists only on the other PC, it is **local and unpushed there**, not merely unmerged
here. This is not automatically resolvable from this side.
**Exact commands for Chase to run on PC #2** to prove no local-only RootNode work is stranded:
```bash
cd ~/Dev/Gitea/ced4568/rootnode-academy # or wherever the clone lives on that machine
git status # must show "nothing to commit, working tree clean"
git branch -vv # every branch should show [origin/main] with no
# "ahead" count, or should not exist at all
git fetch origin
git log origin/main..HEAD --oneline # MUST be empty — any output here is unpushed work
git stash list # should be empty, or each entry reviewed manually
```
If `git log origin/main..HEAD --oneline` on PC #2 produces ANY output, that is real, unpushed
work — it must be reviewed and pushed (or explicitly discarded by Chase's own decision) before
being considered part of canonical state. **This audit cannot rule that out from here — it can
only confirm Gitea itself has no evidence of it.**
## 1.4 Expected milestone audit
Every phase SHA Phase 10's own authorization listed (Phase 2A through Phase 9.1 final) was
verified present in canonical history, in full agreement with the commit message labels:
| Phase | SHA (verified) | Commit subject |
|---|---|---|
| 2A | `42dfb1c` | docs(phase2a): document gateway and student launch architecture |
| 2B | `08ff531` | docs(phase2b): document admin and platform-data architecture |
| 3A | `4069292` | docs(phase3a): document production identity architecture |
| 3B | `45a147a` | docs(phase3b): document the learning and assessment engine |
| 4 | `bf1e1b5` | docs(phase4): production infrastructure documentation + runbooks |
| 4.1 | `5bc0743` | docs(phase4.1): pre-production hardening documentation |
| 5A | `896c230` | docs(phase5a): student identity and remote classroom documentation |
| 5B | `1c08a30` | docs(phase5b): self-paced enrollment documentation + website integration contract |
| 5C | `777efd1` | docs(phase5c): enrollment communications documentation + Google Workspace runbook |
| 6A | `fd38d06` | docs(phase6a): document RootNode course foundation |
| 6B | `2db1368` | docs(phase6b): document curriculum expansion |
| 6C | `bd0a10f` | docs(phase6c): pilot readiness standard, pilot curriculum manifest, ROOT 11 scope |
| 6D | `b6516a7` | docs(phase6d): document the ROOT Foundations milestone |
| 6E | `87ad125` | docs(phase6e): document the ROOT Foundations Completion milestone |
| 6F | `d066410` | docs(phase6f): document ROOT_ V1 pilot readiness (tag `v0.6.0-root-v1`) |
| 7 | `df5e39f` | docs(phase7): document the business/product architecture foundation |
| 8 | `788fb19` | docs(phase8): document enrollment operations |
| 9 | `80a5f6e` | docs(phase9): document commerce integration and Winter Break launch readiness |
| 9.1 (prep) | `77fd186` | docs(stripe): document Phase 9.1 blocked status and live-mode readiness checklist |
| 9.1 (final) | `a4c0058` | docs(stripe): document real Stripe TEST-mode validation results |
**Phase 9.5 — NOT FOUND.** No commit, branch, or tag matching "Phase 9.5" or a founder-pricing
document exists anywhere in this repository's reachable history (`git log --all` contains no
such commit), and no `docs/ROOTNODE_PRICING_AND_OFFERS_V1.md` file exists at any point in
history. This exact discrepancy was already surfaced during Phase 9.1 (Chase explained it was
done on the desktop PC, separate from this laptop) — §1.3's commands above are how to confirm
whether that work is sitting unpushed on PC #2, or whether it was a decision made directly rather
than a planned commit. **The founder-approved pricing itself ($399 launch / $449 standard,
39900/44900 cents, "Founding Cohort," paid in full, no deposit) was given directly by Chase in
conversation and used for Phase 9.1's real Stripe validation and this phase's documentation — it
did not require a committed document to act on, and is not invented by this phase.**
## 1.5 File/artifact audit
Searched all tracked files for secret-shaped filenames (`.env*`, `.local`, `credential`,
`secret`, `.key`, `.pem`, `.p12`, service-account JSON). Every match is a legitimate
template/placeholder or educational-content reference, never a real runtime secret:
- Course content (`content/root/root-07-.../m02-configuration-and-secrets/`) — teaching material
about Kubernetes Secrets, no real secret data.
- `infra/ansible/roles/*/templates/*.env.j2` — Ansible Jinja2 templates referencing
`{{ vault_* }}` variables (values live in an Ansible Vault-encrypted file, never plaintext in
git).
- `infra/environments/{production,staging}/*.env.template` — checked-in templates with explicit
`__REPLACE_WITH_...__`/`__REPLACE_openssl_rand_hex_32__` placeholders, never real values.
- `platform/provisioner/.env.example`, migration `017_student_credentials.sql` (schema, not
data), `scenarios/data/*secret*.json` (fake Kubernetes lab-scenario fixtures).
`.gitignore` correctly excludes `.env` (blanket pattern) — confirmed via `git status
--porcelain --ignored`: `platform/.env` and `platform/provisioner/.env` (both containing real
local secrets) are ignored, never tracked.
## 1.6 Secret history audit
Searched the **entire** git history (`git log --all -p`, pickaxe search `-S`) for:
`sk_test_`, `sk_live_`, `whsec_`, `BEGIN PRIVATE KEY`, `BEGIN RSA PRIVATE KEY`, `"private_key"`,
and a broad regex for any `sk_test_`/`sk_live_`/`whsec_`-prefixed string of realistic secret
length (20+ trailing characters).
**Result: clean. No real secret value has ever been committed.** Every `sk_test_`/`sk_live_`/
`whsec_` hit is a code guard-clause literal (e.g. `.startsWith('sk_test_')`) or documentation
prose referencing the prefix pattern, from this session's own Phase 9.1 commits — never a full
key. The one `"private_key"` hit (`b38c46a`, Phase 5C) is a code comment showing an EXAMPLE
env-var invocation with a `"..."` placeholder, not a real value. No `BEGIN PRIVATE KEY`/`BEGIN
RSA PRIVATE KEY` header appears anywhere in history. **No credential rotation is required.**
## 1.7 Current runtime secret state (this workstation)
`platform/.env` (gitignored, never committed) holds real local-dev values for
`PROVISIONER_SHARED_SECRET`, `INTEGRATION_SHARED_SECRET`, `GOOGLE_SERVICE_ACCOUNT_JSON`,
`EMAIL_FROM_ADDRESS`, `GOOGLE_DELEGATED_USER` — all pre-existing from earlier phases. Its
`PAYMENT_PROVIDER`/`STRIPE_SECRET_KEY`/`STRIPE_WEBHOOK_SECRET` lines are present but **blank**,
per this repo's own "set only for a deliberate session, unset again after" convention (matching
`EMAIL_PROVIDER`'s established pattern) — confirmed after Phase 9.1's real-Stripe validation
session ended. See `docs/SECRETS_AND_INFISICAL.md` for the full inventory and centralization
status.
## Discrepancies found
1. **"Phase 9.5" has no canonical commit** — see §1.4. Not a blocker (the pricing decision was
usable directly), but flagged so it isn't assumed to exist in code review later.
2. **Chase's Phase 10 authorization states "Chase has ALREADY created RootNode Academy" as an
Infisical project** on `infisical.cedshomelab.com` — verified NOT present. The organization
("Ceds HomeLab") has exactly six existing projects (Core Infrastructure, Hermes Fleet,
Kubernetes, Media Platform, Monitoring, Network Service), none named RootNode Academy or
similar. See `docs/SECRETS_AND_INFISICAL.md` §"Project status" for the full finding and what
Chase needs to do (create the project via the Infisical web dashboard — the CLI has no
non-interactive project-creation command).
3. **No dedicated production server/Proxmox VM has been provisioned** — `infra/terraform/` has
no `terraform.tfvars` (only `.example`) and no state file; `infra/ansible/group_vars/
production.yml` still holds literal placeholders (`__PLATFORM_VM_IP__`,
`__LAB_HOST_VM_IP__`, `__PROXMOX_HOST_MGMT_CIDR__`). See `docs/
PHASE10_PRODUCTION_DEPLOYMENT.md`.
## Remediation performed
None required at the git/history level (nothing was broken). Documentation created/updated to
record all of the above (this document, plus `docs/SECRETS_AND_INFISICAL.md` and `docs/
PHASE10_PRODUCTION_DEPLOYMENT.md`).
## Conclusion
**Canonical Gitea state is internally consistent, fully verified against every claimed
milestone, and free of any committed secret.** The only open item from this gate is Chase
running the PC #2 commands in §1.3 to positively rule out stranded local work there — Gitea's
own history gives no reason to believe any exists, but this audit cannot see that machine
directly.
+152
View File
@@ -0,0 +1,152 @@
# Phase 10 — Launch Readiness
Gates 2, 10-14, 19-20, and the final launch matrix. See `docs/PHASE10_CANONICAL_STATE_AUDIT.md`
(Gate 1), `docs/SECRETS_AND_INFISICAL.md` (Gate 3), `docs/PHASE10_PRODUCTION_DEPLOYMENT.md`
(Gates 4-9/15-16), and `docs/PHASE10_SECURITY_AUDIT.md` (Gate 18) for the rest.
## Gate 2 — Full application audit
Reviewed for architectural coherence across every phase; nothing was found inconsistent or in
need of a fix. Identity/auth, learning, Lab platform, business (offerings/registrations/
guardians/agreements), and commerce (pricing/payments/webhooks) all remain exactly as documented
in their own phase docs — re-verified by actually rerunning every suite (Gate 19 below), not by
re-reading code and assuming it still works.
## Gate 10 — Website integration
`docs/WEBSITE_REGISTRATION_AND_CHECKOUT.md` and `docs/WEBSITE_ENROLLMENT_INTEGRATION.md` remain
the full, accurate, already-tested contract: public offerings API, registration submission,
agreement acceptance, checkout creation, and the status-lookup endpoint. Nothing new was built —
the contract was fully proven end to end (including against real Stripe) in Phase 9/9.1. The
website itself (`rootnodeacademy.com`) is confirmed live and real (Gate 6, `docs/
PHASE10_PRODUCTION_DEPLOYMENT.md`) but remains a separate, not-RootNode-owned repository — this
phase did not access or modify it, per the standing boundary.
## Gate 11 — Winter Break offering
Pricing is locked and real: **$399.00 launch ("Founding Cohort") / $449.00 standard reference,
39900/44900 cents, paid in full, no deposit, no payment plan** — given directly by Chase,
validated against real Stripe TEST mode at the exact $399 amount in Phase 9.1. **Dates, venue,
and capacity remain undecided** — not invented by this phase, exactly as every prior phase's own
documentation already states. The offering therefore cannot be transitioned to
`enrollment_open` for real, public registration yet — the platform mechanism is ready
(`PATCH /api/admin/offerings/:id` accepts all four fields the moment they're decided).
## Gate 12 — Agreements / legal
`agreement_definitions`/`agreement_acceptances` architecture is real, tested, and versioned
(Phase 7-9). **No attorney-approved legal text exists for any required Winter Break agreement**
— `agreement_definitions.body_summary` remains a plain-language placeholder, exactly as every
prior phase's docs have stated since Phase 7. This phase did not write legal language and did
not treat any placeholder as attorney-approved, per Phase 10's own explicit instruction.
**LAUNCH BLOCKED — APPROVED AGREEMENT TEXT REQUIRED.** Likely required categories (not
authored, just the taxonomy Phase 10 asked to identify): program participation terms, guardian
consent (minor registrants), a participation/liability waiver, acceptable-use / lab/technology
rules, a cancellation/refund acknowledgment tied to whatever policy Gate 14 resolves, and a
privacy/data-handling notice if RootNode's counsel determines one is needed beyond what's already
in `docs/STUDENT_GUARDIAN_DATA_MODEL.md`. A media release is optional — RootNode's own choice,
not decided here.
## Gate 13 — Tax
**TAX DECISION REQUIRED.** No deliberate Mississippi (or any jurisdiction's) tax treatment has
been configured anywhere in this codebase — `payment_transactions.amount` is charged exactly as
the offering's own price, with no tax line item, no tax calculation, and no tax remittance logic
anywhere (Phase 9's own explicit "no complex tax automation" scope boundary, unchanged). This is
not this phase's decision to make, and the current absence of tax handling should not be read as
an implicit "no tax owed" determination — that requires Chase (or a professional) to actually
decide, then tell the platform what to do (charge tax as a line item, treat the $399 as
tax-inclusive, or confirm no sales tax applies to this specific service in this jurisdiction).
## Gate 14 — Refund / cancellation policy
**LAUNCH BLOCKED — REFUND/CANCELLATION POLICY REQUIRED.** The platform's MECHANICAL behavior is
real, tested, and proven against actual Stripe money-movement in Phase 9.1: a refund is recorded
as its own tracked state and **never automatically** cancels the registration or revokes academic
enrollment — confirmed directly against the database after a real Stripe TEST refund. What is
still missing is the actual customer-facing POLICY text (how many days before the camp is a
refund allowed, is it full or partial, etc.) — a founder decision this phase does not invent.
## Gate 19 — Complete regression
Fresh stack, this phase, after confirming canonical baseline (Gate 1):
| Suite | Result |
|---|---|
| `e2e-phase5b.js` | 32/32 |
| `e2e-phase5c.js` | 29/29 |
| `e2e-phase6f.js` | 17/17 |
| `e2e-phase7.js` | 17/17 |
| `e2e-phase8.js` | 16/16 |
| `e2e-phase9.js` | 20/20 |
| Course-content validator | PASS, 0 errors |
| Migration idempotency | Confirmed both ways — a full `down -v`/`up` cycle applies all 37 migrations cleanly from empty, and a same-database container restart re-applies with zero errors |
| Frontend production build | Clean (only the pre-existing chunk-size advisory) |
| `tofu validate` (Terraform/OpenTofu) | Clean |
| `ansible-playbook --syntax-check` | Clean |
**131 automated checks, 0 failures.** No code changed this phase (Phase 10 is audit/launch-prep,
not feature work), so this is confirmation, not new coverage.
`platform/test/stripe-integration-test.js` (the Phase 9.1 opt-in real-Stripe check) was **not**
rerun this phase — it requires real Stripe TEST credentials, which currently have no durable home
(Gate 3's Infisical project doesn't exist yet) and were correctly unset from the local `.env`
after Phase 9.1 ended. Phase 9.1's own evidence (`docs/PHASE9_1_STRIPE_TEST_VALIDATION.md`)
stands as the real-Stripe proof; nothing in this phase's own code changes touches that path.
## Gate 20 — Pre-launch dress rehearsal
`infra/scripts/drills/test-phase9-winter-camp-commerce.js` rerun fresh this phase against
current `main`: **13/13** — website registration → guardian → agreement → checkout → webhook →
enrollment → activation → classroom → a real disposable Lab task → instructor visibility, plus
failed-payment/duplicate-webhook/abandoned-checkout/last-seat-race, all using the deterministic
test provider (no real money, as instructed). This is the closest possible complete rehearsal
without real production infrastructure or real dates/venue/capacity — it cannot be run against
the ACTUAL Winter Break offering because that offering cannot legitimately open registration yet
(Gate 11). No fake evidence was created for the steps genuinely blocked by unresolved business
decisions.
## Final launch matrix
| Gate | Status | Evidence | Owner action | Blocks registration? |
|---|---|---|---|---|
| 1. Canonical repo reconciled | ✅ COMPLETE | `docs/PHASE10_CANONICAL_STATE_AUDIT.md` | None | No |
| 2. Second-PC state reconciled | ⚠️ DECISION REQUIRED | Gitea shows no stranded work; PC #2 itself unverified from here | Run §1.3's commands on PC #2 | No (informational) |
| 3. Secrets centralized (Infisical) | 🔧 MANUAL OWNER ACTION | `docs/SECRETS_AND_INFISICAL.md` | Create the RootNode Academy Infisical project | No (dev secrets already safe locally) |
| 4. Production server | ⛔ BLOCKED | `docs/PHASE10_PRODUCTION_DEPLOYMENT.md` | Purchase/provision hardware | **Yes** |
| 5. Proxmox / VMs | ⛔ BLOCKED | Same — no host to provision on | Same | **Yes** |
| 6. Firewall/isolation | ✅ COMPLETE (code), ⏸ unverified live | `docs/PHASE10_SECURITY_AUDIT.md` | None until real host exists | No (code ready) |
| 7. Production database | ⛔ BLOCKED | Same hardware blocker | Same | **Yes** |
| 8. DNS | ✅ PARTIAL | `rootnodeacademy.com` live; no Platform API subdomain yet | Decide/create the API subdomain once a server exists | **Yes**, for the API portion |
| 9. HTTPS | ✅ PARTIAL | Website real HTTPS; Platform API HTTPS pending a real host | Same as above | **Yes**, for the API portion |
| 10. Monitoring | ✅ COMPLETE (code) | `/healthz`/`/metrics` live in every test run | None until deployed | No |
| 11. Backups | ✅ COMPLETE (code), ⛔ unexecuted | Ansible `backup` role exists, never run | Run once a real host exists | **Yes** (restore-test) |
| 12. Restore test | ⛔ BLOCKED | Never executed — no target | Same | **Yes** |
| 13. Stripe TEST validation | ✅ COMPLETE | `docs/PHASE9_1_STRIPE_TEST_VALIDATION.md` | None | No |
| 14. Stripe LIVE configuration | ⛔ BLOCKED | Not configured, not attempted | Chase completes Stripe business/identity verification, provides LIVE credentials | **Yes** |
| 15. Live webhook configuration | ⛔ BLOCKED | Depends on #14 and a real HTTPS endpoint (#9) | Same | **Yes** |
| 16. Google Workspace | ✅ COMPLETE (validated in Phase 5C) | `docs/RUNBOOK_GOOGLE_WORKSPACE_EMAIL.md` | Deploy alongside the real server | No (already proven) |
| 17. Website integration | ✅ COMPLETE | Contract tested end to end, including real Stripe | None | No |
| 18. Winter Break price | ✅ COMPLETE | $399/$449, real, Stripe-validated | None | No |
| 19. Winter Break dates | ⚠️ DECISION REQUIRED | Not decided | Chase decides | **Yes** |
| 20. Venue | ⚠️ DECISION REQUIRED | Not decided | Chase decides | **Yes** |
| 21. Capacity | ⚠️ DECISION REQUIRED | Not decided | Chase decides | **Yes** |
| 22. Agreements (legal text) | ⛔ BLOCKED | Placeholder only | Attorney-approved text required | **Yes** |
| 23. Refund/cancellation policy | ⚠️ DECISION REQUIRED | Mechanism proven, policy undecided | Chase decides | **Yes** |
| 24. Tax decision | ⚠️ DECISION REQUIRED | Not configured | Chase/professional decides | **Yes** |
| 25. Production offering | ⛔ BLOCKED | Cannot legitimately open — depends on #19-24 | Depends on all of the above | **Yes** |
| 26. Instructor assignment | 🔧 MANUAL OWNER ACTION | Mechanism ready (`instructor_offerings`) | Chase assigns once the real offering exists | **Yes** |
| 27. Roster process | ✅ COMPLETE (mechanism) | Rehearsed in Gate 20 | None | No |
| 28. Student activation | ✅ COMPLETE | Proven real, twice (Phase 9.1 + Gate 20) | None | No |
| 29. Lab capacity | ✅ COMPLETE (code), ⛔ real capacity unverified | Capacity/race logic proven; real server capacity planning needs #4 | Plan once real hardware specs are known | No (mechanism), **yes** (real capacity) |
| 30. Complete dress rehearsal | ✅ COMPLETE (deterministic) | Gate 20, 13/13 | Repeat against the real offering once it can legitimately open | No |
## Bottom line
## PHASE 10 PARTIAL / WINTER BREAK LAUNCH BLOCKED
Every gate that is genuinely software/audit work is complete and verified. Every gate that is a
real business, legal, tax, or hardware decision remains exactly that — a decision, not
something this phase invented on Chase's behalf. See `docs/WINTER_BREAK_LAUNCH_CHECKLIST.md` for
the single consolidated, categorized checklist Chase can work down.
+90
View File
@@ -0,0 +1,90 @@
# Phase 10 — Production Deployment
Gates 4-9, 15-16. Covers production infrastructure, network security, DNS/HTTPS, database
productionization, Stripe LIVE configuration, Google Workspace production email, observability,
and backups/restore.
## Hardware status — PRODUCTION DEPLOYMENT BLOCKED — SERVER REQUIRED
Verified, not assumed: `infra/terraform/` has no real `terraform.tfvars` (only
`terraform.tfvars.example`) and no `.tfstate` file anywhere in the repo or working tree —
`tofu apply` has never been run against real infrastructure. `infra/ansible/group_vars/
production.yml` still holds literal, unfilled placeholders: `platform_ip:
__PLATFORM_VM_IP__`, `lab_host_ip: __LAB_HOST_VM_IP__`, `proxmox_mgmt_cidr:
__PROXMOX_HOST_MGMT_CIDR__`. No SSH config, known-hosts entry, or inventory file on this
workstation references a real `rn-platform-01`/`rn-lab-host-01`/Proxmox host.
**The dedicated RootNode Academy production server has not been purchased or provisioned.**
Per Phase 10's own explicit instruction, this phase does not fake a production deployment
against placeholder infrastructure. Everything below is preparation, validation, and rehearsal —
genuinely useful the moment real hardware exists, but none of it is a live deployment.
## What WAS done this phase (all safe, all pre-hardware)
- **Gate 4 — IaC validated**: `tofu validate` (OpenTofu, the Terraform-compatible tool actually
installed) passes cleanly against `infra/terraform/`'s existing Proxmox provider config — no
redesign, the existing config is syntactically sound and ready for real `terraform.tfvars`.
`ansible-playbook site.yml --syntax-check -i inventory` passes cleanly against `infra/
ansible/`'s existing `site.yml` and its `firewall`/`docker-tls`/`platform`/`lab-host`/
`monitoring-agent`/`backup`/`common` roles (all present from Phase 4/4.1, unmodified).
- **Gate 5 — network security architecture reviewed, unchanged**: RFC1918 egress
denial/allowlisting present in `platform/provisioner/src/egress.js`/`lifecycle.js` (Provisioner
owns lab-container network policy); mTLS wiring present in `infra/ansible/roles/docker-tls/`
and referenced by `gateway.env.j2`; the `firewall` Ansible role exists for both host tiers. All
of this is existing Phase 4/4.1 work, re-confirmed present and un-redesigned — none of it can
be verified AGAINST a real host without one existing.
- **Gate 6 — DNS/HTTPS audited (read-only)**: `rootnodeacademy.com` **is already live** —
resolves via Cloudflare (`172.67.141.39`, `104.21.38.247`), serves a real HTTPS site (`HTTP/2
200`, Next.js application headers), and its mail is already routed to Google
(`smtp.google.com`), consistent with the existing `[email protected]` Google
Workspace setup. This is the separate, not-RootNode-owned marketing website (`docs/
WEBSITE_REGISTRATION_AND_CHECKOUT.md`'s explicit boundary) — nothing here was touched, and no
DNS record was added or changed. **No subdomain or DNS record exists yet for the Platform API
itself** (e.g. `api.rootnodeacademy.com` or similar) — that's the piece blocked on real
server hardware.
- **Gate 7 — database productionization reviewed**: migrations (37, through Phase 9) verified
idempotent again this phase (a fresh `down -v`/`up` cycle and a same-database container
restart both apply/re-apply cleanly with zero errors — see `docs/
PHASE10_LAUNCH_READINESS.md`'s regression section). The Ansible `platform` role's
`postgres.env.j2`/`.template` already model least-privilege connection strings
(`rootnode_platform` app user, not a superuser). No student records of any kind exist to
accidentally copy into a production database — this dev/test Postgres only ever holds
disposable test fixtures, all cleaned up after each rehearsal.
- **Gate 8 — Stripe LIVE**: explicitly **not configured, not attempted**. See "Stripe LIVE
status" below.
- **Gate 9 — Google Workspace**: `GOOGLE_SERVICE_ACCOUNT_JSON`/`GOOGLE_DELEGATED_USER` already
exist as real, previously-validated local values (Phase 5C's own `docs/
RUNBOOK_GOOGLE_WORKSPACE_EMAIL.md` documents that validation) — `[email protected]`,
`gmail.send` scope only, domain-wide delegation. Not re-sent this phase (no new test email was
needed to confirm what Phase 5C already proved, and Phase 10 explicitly says not to make GWS a
blocker for Stripe/launch work). Production DEPLOYMENT of this config is blocked on the same
missing server as everything else.
- **Gates 15/16 — observability and backups reviewed, unchanged**: `/healthz` (Platform API,
Gateway, Provisioner) and `/metrics` (Prometheus text exposition, low-cardinality labels only —
reconfirmed by this phase's own audit in `docs/PHASE10_SECURITY_AUDIT.md`) already exist and
were exercised repeatedly by every regression suite rerun this phase. The `monitoring-agent`
and `backup` Ansible roles exist (`infra/ansible/roles/monitoring-agent/`, `.../backup/`,
including a templated `run-backup.sh.j2`) — built in Phase 4, never yet run against a real
host. **A restore has never been tested against real infrastructure, because none exists** —
this phase cannot manufacture that proof; it is not sufficient for launch per Phase 10's own
"a backup that has never been restore-tested is not sufficient" rule, and is listed as a real
blocker in the launch checklist, not glossed over.
## Stripe LIVE status
**Not configured.** No live Stripe credential was provided to this session, and this phase did
not request one — activating Stripe live mode requires Chase's own Stripe account
business/identity verification (a real KYC-style process only Chase can complete), which is an
explicit Phase 10 STOP condition ("identity verification requires Chase"). Phase 9.1's real
TEST-mode validation (`docs/PHASE9_1_STRIPE_TEST_VALIDATION.md`) remains the full proof of the
application-layer correctness; going live only needs Chase's real credentials plugged into the
same, already-proven code path — see `docs/STRIPE_INTEGRATION_RUNBOOK.md`'s "Going live"
section, unchanged by this phase.
## Bottom line
**PRODUCTION DEPLOYMENT BLOCKED — SERVER REQUIRED.** Every gate that depends on real hardware
(4, 5's live verification, 6's platform subdomain, 7's live database, 8, 9's live deployment, 15,
16) is prepared, validated, and ready to execute the moment `rn-platform-01`/`rn-lab-host-01`
exist on real Proxmox hardware and `terraform.tfvars`/`group_vars/production.yml` are filled
with real values — none of it requires new code or a redesign.
+44
View File
@@ -0,0 +1,44 @@
# Phase 10 — Security Audit
Gate 18. A production-oriented review of RootNode Academy's security posture, drawing on every
regression suite rerun this phase (`docs/PHASE10_LAUNCH_READINESS.md`) plus direct code review.
No genuine launch-critical defect was found this phase — the one real defect Phase 10's
predecessor phases exposed (Phase 9.1's webhook-correlation bug) was already fixed and
regression-tested before this phase began.
| Area | Status | Evidence |
|---|---|---|
| Student authentication | ✅ Verified | `e2e-phase5b.js`/`e2e-phase5c.js` rerun clean this phase — activation, single-use tokens, expiration, password reset with anti-enumeration, session revocation on reset. |
| Instructor authentication | ✅ Verified | Same suites — instructor login, RBAC roles (owner/admin/instructor). |
| Sessions | ✅ Verified | Persistent, server-controlled sessions (Phase 3A); a real Platform API restart preserves sessions/enrollment state (rerun clean this phase). |
| Password reset / activation | ✅ Verified | Single-use, server-side expiration, no plaintext token ever returned by any admin API (`toAdminView` never includes it). |
| RBAC | ✅ Verified | Admin/owner-only routes (guardian detail, payment data) vs. any-instructor routes (offering/location lists) — reconfirmed across Phase 7/8/9 suites this phase. |
| Cohort scoping | ✅ Verified | An instructor sharing a cohort can manage; an unrelated instructor cannot (Phase 5B/7 suites). |
| CSRF | ✅ Present, unchanged | `platform/api/src/security.js` + per-route session/cookie checks (student/instructor auth routes) — not modified this phase, no regression. |
| Rate limiting / lockout | ✅ Present, unchanged | Login-attempt tracking in `security.js` (matches the existing sweeper pattern also used for session expiry). |
| Audit logging | ✅ Verified | Every admin/integration action recorded; audit-hygiene test (no secrets/PII in audit metadata) rerun clean in `e2e-phase9.js`. |
| Server-authoritative pricing | ✅ Verified, real Stripe | Phase 9.1 proved a tampered caller amount is ignored against **Stripe's own recorded object**, not just RootNode's database. |
| Agreement enforcement | ✅ Verified | Checkout blocked (`409`) until acceptance, in both the deterministic suite and real Stripe TEST mode. |
| Capacity enforcement | ✅ Verified | Race-safe seat reservation (`reserved_seats < capacity` in one UPDATE) — last-seat race test rerun clean this phase. |
| No card-data storage | ✅ Verified | `payment_transactions` schema has no card fields; explicit admin-view test asserts none appear; Stripe's hosted Checkout is the only thing that ever sees a real card. |
| Webhook signature verification | ✅ Verified, real Stripe | Invalid/forged signatures rejected (both deterministic and real-Stripe-signed tests); idempotent via a real DB primary key on the provider's event id, reconfirmed with a genuinely re-signed real Stripe event in Phase 9.1. |
| Logging/audit redaction | ✅ Verified | This session's own Stripe secret/webhook-secret values were grepped across every log/scratch file produced — none found; `e2e-phase9.js`'s hygiene test asserts no `sk_`/`whsec_`/card-number pattern in audit metadata or metrics text. |
| Secrets never committed | ✅ Verified | Full-history pickaxe search this phase — clean (`docs/PHASE10_CANONICAL_STATE_AUDIT.md` §1.6). |
| mTLS (Provisioner ↔ Docker, Gateway ↔ Lab Host Router) | ✅ Present, unchanged, not re-verified live | Ansible `docker-tls` role and TLS env vars exist; cannot be exercised against a real mTLS handshake without real hardware (Gate 4/5's hardware blocker). |
| Privileged lab boundary / RFC1918 egress denial | ✅ Present, unchanged | `provisioner/src/egress.js`; this phase reconfirmed the module exists and is wired, did not re-run Phase 1/2's own dedicated isolation drills (out of this phase's changed-code scope — nothing touched networking code). |
| Production cookie settings (`COOKIE_SECURE`) | ✅ Present, verified pattern | `e2e-phase5c.js`'s own test explicitly proves `COOKIE_SECURE=1` production gating via a real container recreate — reconfirmed present, not re-executed as a fresh drill this phase since nothing in that code path changed. |
| HTTPS end-to-end | ⏸ Cannot verify — no production host | `COOKIE_SECURE=1`/`TRUST_PROXY=1` require a real HTTPS-terminating reverse proxy in front of a real Gateway, which doesn't exist yet (Gate 4-6's hardware blocker). The marketing site at `rootnodeacademy.com` IS real HTTPS (Cloudflare) — the Platform API itself has no production endpoint yet. |
## Findings requiring action
**None launch-critical within existing architecture.** Everything marked ⏸ above is blocked on
the same missing production server documented in `docs/PHASE10_PRODUCTION_DEPLOYMENT.md` — not
a code defect, and building around it (e.g. faking an HTTPS check) would violate this phase's
own "do not fake production readiness" instruction.
## Scope discipline
This audit reviewed and reconfirmed existing security properties; it did not expand into new
security tooling, new scanning infrastructure, or unrelated hardening work not already part of
Phase 1-9's architecture, per Phase 10's own "do not expand into unrelated feature work"
instruction.
+87
View File
@@ -0,0 +1,87 @@
# Secrets & Infisical
Gate 3. The full, real secret inventory — derived by grepping every `process.env.*` reference
across `platform/api/src`, `platform/gateway/src`, `platform/provisioner/src`, and
`platform/lab-router/src` (not guessed, not copied from a template list) — and the current
status of centralizing it in Infisical. **This document never contains a secret value.**
## Infisical project status — action required
Chase's self-hosted instance is `infisical.cedshomelab.com` (confirmed reachable, confirmed NOT
`app.infisical.com` — the earlier Phase 9.1 attempt failed for exactly this reason: the existing
machine identity is valid for the self-hosted instance only). Authenticated successfully this
phase using the machine identity at `~/.config/infisical/identity.env`
(`universal-auth`, org "Ceds HomeLab").
**No "RootNode Academy" project exists yet** in that organization — verified via `infisical
init`'s own project picker, which lists exactly six existing projects: Core Infrastructure,
Hermes Fleet, Kubernetes, Media Platform, Monitoring, Network Service. This contradicts Phase
10's own authorization text ("Chase has ALREADY created RootNode Academy"). The Infisical CLI
has no non-interactive project-creation command — creating one requires the web dashboard
(`https://infisical.cedshomelab.com`), which this session cannot reach (no browser).
**Owner action required:** create a "RootNode Academy" project in the Infisical dashboard, with
three environments (**Development**, **Staging**, **Production** — matching this document's own
categorization below), and tell this session (or a future one) its project id so the secret
inventory below can actually be pushed. Until then, every secret continues to live only in local,
gitignored `.env` files (development) or has no home at all (staging/production don't exist yet
— see `docs/PHASE10_PRODUCTION_DEPLOYMENT.md`).
## Secret inventory (real, from code — not from this prompt's guessed category list)
Every environment variable the application code actually reads, categorized. "Secret" = must
never appear in git, logs, or a browser; "Config" = fine to template/document in plain text
(already handled by `infra/environments/*/*.env.template`, unchanged by this phase).
| Variable | Category | Purpose | Consuming service | Environments | Rotation |
|---|---|---|---|---|---|
| `PLATFORM_DB_URL` | **Secret** (contains DB password) | Postgres connection string | Platform API | Dev/Staging/Prod (per-env DB, never shared) | On suspected compromise; routine rotation not yet scheduled |
| `PROVISIONER_SHARED_SECRET` | **Secret** | Platform API ↔ Provisioner trust boundary (`X-Provisioner-Key`) | Platform API, Provisioner | Dev/Staging/Prod (distinct per env) | On suspected compromise |
| `INTEGRATION_SHARED_SECRET` | **Secret** | Website backend ↔ Platform API trust boundary (`X-RootNode-Integration-Key`) | Platform API, website's backend (external) | Dev/Staging/Prod | On suspected compromise; the website team needs the current value out-of-band |
| `GOOGLE_SERVICE_ACCOUNT_JSON` | **Secret** | Full GWS service-account credential (Gmail API, domain-wide delegation) | Platform API (`email/gws-provider.js`) | Prod only (dev/staging use the deterministic dev provider by default) | Per Google Cloud's own service-account key rotation practice |
| `GOOGLE_DELEGATED_USER` | Config (an email address, not a secret) | Which real mailbox the service account impersonates via domain-wide delegation | Platform API | Prod | N/A |
| `EMAIL_FROM_ADDRESS` / `EMAIL_FROM_NAME` / `EMAIL_REPLY_TO` | Config | Sender identity — `[email protected]` | Platform API | All | N/A |
| `STRIPE_SECRET_KEY` | **Secret** | Stripe API authentication (checkout creation, refunds, retrieval) | Platform API (`payment-provider.js`) | Dev/Staging = TEST key; Production = LIVE key — **never mixed**, see Gate 8 status below | Per Stripe's own key-rotation guidance; rotate immediately if ever exposed |
| `STRIPE_WEBHOOK_SECRET` | **Secret** | Verifies a webhook delivery actually came from Stripe | Platform API (`webhook-routes.js`) | One per registered webhook endpoint (TEST vs. LIVE endpoints each have their own) | Regenerates automatically if the endpoint is recreated |
| `TEST_PAYMENT_WEBHOOK_SECRET` | Low-sensitivity (dev-only, never real money) | Shared secret for the deterministic `TestProvider`'s fake webhook signature | Platform API | Dev/test only | N/A |
| `LAB_ROUTER_TLS_KEY` | **Secret** | Lab Host Router's mTLS private key | Lab Host Router | Staging/Prod (dev uses a self-signed pair generated locally) | Per the mTLS cert lifecycle already documented in Phase 4.1 |
| `LAB_ROUTER_TLS_CERT` / `LAB_ROUTER_TLS_CA` | Sensitive but not strictly secret (public cert material) | mTLS certificate/CA chain | Lab Host Router, Gateway | Staging/Prod | Same lifecycle as the key above |
| `PLATFORM_DB_URL`'s host component, ports, timeouts, session TTLs, `PAYMENT_PROVIDER`, `COOKIE_SECURE`, `TRUST_PROXY`, `PUBLIC_APP_ORIGIN`, `DNS_RESOLVER`, `NO_PROXY_CIDRS`, etc. | Config | Plain operational configuration | Various | All, per-environment values | N/A |
Every "Config" row is already handled the existing way — plain values in `infra/environments/
*/*.env.template` (checked into git, safe) or `docker-compose.dev.yml`'s `${VAR:-default}`
substitutions. Only the "Secret" rows above need an Infisical home.
## Environment separation (Stripe — the one category with real launch consequences)
- **Development/Staging**: `STRIPE_SECRET_KEY`/`STRIPE_WEBHOOK_SECRET` must be **TEST** values
only (`sk_test_...`/a `stripe listen`-issued or TEST-mode dashboard `whsec_...`). This is what
Phase 9.1 validated end to end.
- **Production**: `STRIPE_SECRET_KEY`/`STRIPE_WEBHOOK_SECRET` must be **LIVE** values
(`sk_live_...`, and a permanent LIVE-mode dashboard webhook endpoint's own signing secret).
**Not configured — Chase has not provided live Stripe credentials, and this phase did not ask
for or receive them** (per Phase 10's own stop condition: "real money could move" /
"identity verification requires Chase" — live Stripe activation requires Chase's own Stripe
business/identity verification, not something this session can do on his behalf). See `docs/
PHASE10_LAUNCH_READINESS.md` Gate 8 status.
## Secret hygiene performed this phase
- Confirmed (again, freshly) that no real secret value has ever entered git history (`docs/
PHASE10_CANONICAL_STATE_AUDIT.md` §1.6).
- Confirmed `platform/.env`'s Stripe fields are correctly blank (not left set from Phase 9.1's
validation session) — matches this repo's own "unset after a deliberate session" convention.
- **Not yet performed** (blocked on the missing Infisical project): migrating any real secret
value out of local `.env` files and into Infisical. Per this phase's own instruction, a local
credential is never deleted until its Infisical replacement is verified — nothing was deleted.
## What Chase needs to do
1. Create the "RootNode Academy" project in the Infisical dashboard
(`https://infisical.cedshomelab.com`) with Development/Staging/Production environments.
2. Share the project id (or grant the existing machine identity access to it) so secrets can
actually be pushed.
3. Once that exists, this session (or the next one) can populate every "Secret" row above from
the current local `platform/.env`/`platform/provisioner/.env` values for Development, and
Chase provides the real Staging/Production values (Stripe LIVE keys especially) directly when
ready for those environments — never invented or guessed by this phase.
+92 -80
View File
@@ -1,89 +1,101 @@
# Winter Break Camp — Launch Checklist # Winter Break Camp — Launch Checklist
The single go/no-go list for opening the Winter Break Linux & Systems Foundations Camp The single go/no-go list for opening the Winter Break Linux & Systems Foundations Camp
(`docs/WINTER_BREAK_CAMP_CURRICULUM_OPTIONS.md` Option A) to real public registration. Combines (`docs/WINTER_BREAK_CAMP_CURRICULUM_OPTIONS.md` Option A) to real public registration. Every
`docs/WINTER_BREAK_CAMP_OPERATIONS.md`'s operational sequence with Phase 9's commerce item is tagged **COMPLETE**, **BLOCKED**, **DECISION REQUIRED**, or **MANUAL OWNER ACTION** —
requirements into one checklist Chase can walk top to bottom. Nothing on this list is invented by see `docs/PHASE10_LAUNCH_READINESS.md` for the full evidence matrix behind each tag. Nothing on
the platform — every unchecked item is a real decision or asset that has to come from Chase, the this list is invented by the platform — every non-COMPLETE item is a real decision, asset, or
legal reviewer, or Stripe, not from more code. piece of hardware that has to come from Chase, a legal reviewer, or Stripe, not from more code.
## Platform readiness — done, verified, ready whenever the business inputs below land - [x] **Canonical repo reconciled** — COMPLETE. `docs/PHASE10_CANONICAL_STATE_AUDIT.md`: this
workstation's HEAD, Gitea's `origin/main`, and every claimed milestone SHA all verified
- [x] Offering, capacity, registration lifecycle, agreements — built and tested (Phase 7/8). consistent. No stranded branches/tags on the remote.
- [x] Offering pricing field, payment-provider abstraction, hosted checkout, verified webhook, - [ ] **Second-PC state reconciled** — DECISION REQUIRED (verification only, not a code
refund recording, abandoned-reservation reaper — built and tested against the deterministic blocker). Gitea shows no evidence of stranded work, but this session cannot see the other
test provider (Phase 9). workstation directly — run the commands in `docs/PHASE10_CANONICAL_STATE_AUDIT.md` §1.3
- [x] Website registration + checkout + agreement + status-lookup contract — documented and there to positively confirm.
tested (`docs/WEBSITE_REGISTRATION_AND_CHECKOUT.md`). - [ ] **Secrets centralized (Infisical)** — MANUAL OWNER ACTION. No "RootNode Academy" project
- [x] Admin UI for offering pricing and per-registration payment/refund status. exists yet in `infisical.cedshomelab.com` (verified, contradicts this being already
- [x] `GET /api/admin/offerings/:id/readiness` reflects real configuration state, including the done) — create it with Development/Staging/Production environments. See `docs/
new `price` and `payment_provider` checks. SECRETS_AND_INFISICAL.md` for the full secret inventory ready to migrate in.
- [x] Full Winter Break commerce rehearsal green end to end against a real, freshly-built stack - [ ] **Production server** — BLOCKED. Not purchased/provisioned — verified via empty
(`infra/scripts/drills/test-phase9-winter-camp-commerce.js`, 13/13): admin configuration, `terraform.tfvars`/no state file and placeholder `group_vars/production.yml`. See `docs/
minor and adult registration, checkout, payment success, webhook, enrollment activation, a PHASE10_PRODUCTION_DEPLOYMENT.md`.
real classroom session, a real Lab task, failed payment, duplicate webhook, abandoned - [ ] **Production VMs (rn-platform-01 / rn-lab-host-01)** — BLOCKED. Depends on the server above.
checkout, and a last-seat race. - [x] **Firewall/isolation (code)** — COMPLETE. `docs/PHASE10_SECURITY_AUDIT.md` — RFC1918
egress denial, mTLS wiring, firewall Ansible role all present and unchanged from Phase 4/
## Business decisions — Chase's call, not invented by this phase 4.1; cannot be verified against a live host until one exists.
- [ ] **Production database** — BLOCKED. Depends on the server above. Migrations themselves
- [ ] Real camp dates and duration. reconfirmed idempotent this phase (37/37, both a fresh apply and a same-DB restart).
- [ ] Real venue/location (or confirm fully online/remote). - [x] **DNS (marketing site)** / [ ] **DNS (Platform API)** — PARTIAL. `rootnodeacademy.com` is
- [ ] Real capacity (seat count). live and real (Cloudflare-fronted, confirmed this phase); no subdomain exists yet for the
- [x] Price and currency — **$399.00 USD launch tuition ("Founding Cohort"), $449.00 standard Platform API itself — needs a real server first.
retail, paid in full, no deposit/payment plan** — founder-approved per Chase directly - [x] **HTTPS (marketing site)** / [ ] **HTTPS (Platform API)** — PARTIAL, same split as DNS.
(Phase 9.1). Validated against real Stripe TEST mode at this exact amount. Not yet - [x] **Monitoring (code)** — COMPLETE. `/healthz`/`/metrics` live and exercised in every
reflected in a committed pricing document in this canonical repo (referenced but not found regression run this phase; deployment itself needs the production server.
— see `docs/PHASE9_1_STRIPE_TEST_VALIDATION.md` §1/§33); set directly on the real Winter - [x] **Backups (code)** / [ ] **Backups (executed)** — PARTIAL. Ansible `backup` role exists,
Break offering via `PATCH /api/admin/offerings/:id` when ready to open registration. never run against a real host.
- [ ] Whether registration opens ahead of, or alongside, a specific marketing push. - [ ] **Restore test** — BLOCKED. Never executed — no target exists. A backup that has never
- [ ] Refund policy in plain terms (the platform can RECORD any refund decision; it does not been restore-tested does not count as launch-ready.
decide policy — see `docs/OFFERING_AND_REGISTRATION_MODEL.md`'s "Payment and the commerce - [x] **Stripe TEST validation** — COMPLETE. A real Stripe TEST Checkout Session, a real TEST
boundary" section for what "record, never auto-cascade" means operationally). payment at the actual $399.00 price, a genuine signed webhook, the existing
## Legal review — required before any real family sees the agreement
- [ ] The actual participation/media/liability agreement text, reviewed by a real attorney
(`agreement_definitions.body_summary` is currently a plain-language placeholder only — see
`docs/STUDENT_GUARDIAN_DATA_MODEL.md`'s legal-review list).
- [ ] Jurisdiction-specific minor-consent requirements confirmed.
- [ ] Data-retention/deletion policy decided and, if needed, reflected in the agreement text.
- [ ] A decision on whether emergency-contact fields need additional encryption at rest.
## Payments
- [x] **Stripe TEST-mode validation** — a real Stripe TEST Checkout Session, a real TEST payment
at the actual $399.00 founding-cohort price, a genuine signed webhook, the existing
registration→enrollment conversion, a real duplicate-delivery proof, a real automated registration→enrollment conversion, a real duplicate-delivery proof, a real automated
abandoned-checkout proof, and a real refund with the non-cascade rule reconfirmed — all abandoned-checkout proof, and a real refund with the non-cascade rule reconfirmed — all
proven end to end in Phase 9.1. One real defect (webhook-event correlation) was found and proven end to end in Phase 9.1 (`docs/PHASE9_1_STRIPE_TEST_VALIDATION.md`). One real
fixed as a direct result. See `docs/PHASE9_1_STRIPE_TEST_VALIDATION.md` for the full defect (webhook-event correlation) was found and fixed as a direct result.
evidence and reconciliation IDs. **This is TEST mode only — it does not authorize or imply - [ ] **Stripe LIVE configuration** — BLOCKED. Not configured, not attempted — requires Chase's
LIVE-mode readiness**, see the unchecked items below. own Stripe account business/identity verification (only Chase can do this) plus real LIVE
credentials placed in Infisical (blocked on the missing project above), never invented or
- [ ] Real Stripe account created/available (live mode). substituted by this phase.
- [ ] `STRIPE_SECRET_KEY`/`STRIPE_WEBHOOK_SECRET` (live values) provisioned into production - [ ] **Live webhook configuration** — BLOCKED. Depends on LIVE credentials and a real HTTPS
configuration only — see `docs/STRIPE_INTEGRATION_RUNBOOK.md`'s "Going live" section. Platform API endpoint, neither of which exist yet.
- [ ] At least one real, small, refundable test transaction run end to end in production before - [x] **Google Workspace** — COMPLETE. `[email protected]`, `gmail.send` scope,
opening registration publicly, then refunded. domain-wide delegation — validated in Phase 5C (`docs/
- [ ] Recommended (not required — the test provider already validates the application logic): RUNBOOK_GOOGLE_WORKSPACE_EMAIL.md`); deployment to production needs the real server.
one full rehearsal against Stripe's own TEST mode (fake cards, no real money) before - [x] **Website integration** — COMPLETE. Full contract tested end to end, including against
flipping to live credentials, per `docs/STRIPE_INTEGRATION_RUNBOOK.md`'s test-mode section. real Stripe. The website itself (separate repo, not RootNode-owned) is live and real;
this phase did not access or modify it.
## Website (separate repo, not RootNode-owned) - [x] **Winter Break price** — COMPLETE. **$399.00 launch ("Founding Cohort") / $449.00
standard reference, paid in full, no deposit, no payment plan** — founder-approved by
- [ ] rootnodeacademy.com's own registration/checkout form built against Chase directly, validated against real Stripe TEST mode at this exact amount.
`docs/WEBSITE_REGISTRATION_AND_CHECKOUT.md`. Not tracked or built in this repository — if - [ ] **Winter Break dates** — DECISION REQUIRED. Chase's call, not invented.
that repo becomes available to RootNode's own engineering effort, say so explicitly before - [ ] **Venue** — DECISION REQUIRED. Chase's call (or confirm fully online/remote), not invented.
any code changes are made against it. - [ ] **Capacity** — DECISION REQUIRED. Chase's call, not invented.
- [ ] The website's `INTEGRATION_SHARED_SECRET` provisioned into its OWN backend configuration - [ ] **Agreements (legal text)** — BLOCKED. `agreement_definitions.body_summary` remains a
(never browser-reachable) — see `docs/WEBSITE_ENROLLMENT_INTEGRATION.md`'s "Production plain-language placeholder; no attorney-approved text exists for any required category
secret handling." (program terms, guardian consent, participation waiver, acceptable-use/lab rules,
- [ ] The website's success page built against the status-lookup polling pattern in cancellation acknowledgment, privacy notice). See `docs/PHASE10_LAUNCH_READINESS.md` Gate
`docs/WEBSITE_REGISTRATION_AND_CHECKOUT.md` §6 — never derived from the redirect URL alone. 12 for the full taxonomy — none of it was authored or treated as approved by this phase.
- [ ] **Refund/cancellation policy** — DECISION REQUIRED. The platform's mechanical behavior
(record, never auto-cascade into registration/enrollment status) is real and proven
against actual Stripe money movement; the customer-facing POLICY text is Chase's decision.
- [ ] **Tax decision** — DECISION REQUIRED. No tax treatment is configured anywhere in this
codebase (Phase 9's own explicit "no complex tax automation" boundary) — this is not an
implicit "no tax owed" determination, it requires Chase or a professional to actually
decide.
- [ ] **Production offering** — BLOCKED. Cannot legitimately open registration until dates/
venue/capacity/agreements/refund policy/tax are all resolved — the mechanism itself
(`PATCH /api/admin/offerings/:id`, transition to `enrollment_open`) is ready the moment
they are.
- [ ] **Instructor assignment** — MANUAL OWNER ACTION. Mechanism ready
(`instructor_offerings`); Chase assigns the real instructor once the real offering exists.
- [x] **Roster process** — COMPLETE. Rehearsed end to end (classroom session, real Lab task,
instructor visibility) in this phase's dress rehearsal, 13/13.
- [x] **Student activation** — COMPLETE. Proven real twice — Phase 9.1's real-Stripe run and
this phase's deterministic rehearsal.
- [x] **Lab capacity (mechanism)** / [ ] **Lab capacity (real sizing)** — PARTIAL. Race-safe
capacity logic is proven; real-world capacity planning for actual concurrent students
needs the real server's actual specs, unknown until Gate "production server" resolves.
- [x] **Complete dress rehearsal** — COMPLETE (deterministic). 13/13 this phase, against
current `main`. Cannot yet be repeated against the REAL Winter Break offering because that
offering cannot legitimately open (dates/venue/capacity/agreements/policy/tax all pending).
## Final go/no-go ## Final go/no-go
Registration should not open to the public until every box above is checked. The platform side ## PHASE 10 PARTIAL / WINTER BREAK LAUNCH BLOCKED
(first section) has been true since this phase shipped; everything else is a business, legal, or
external-team dependency this phase deliberately did not — and should not — invent on Chase's Registration must not open to the public until every BLOCKED and DECISION REQUIRED item above
behalf. is resolved. Every COMPLETE item has real evidence behind it, not a self-assessment — see
`docs/PHASE10_LAUNCH_READINESS.md`'s full matrix. **Even once every gate above passes, this
platform will not open registration on its own — that action requires Chase's explicit,
separate authorization, given directly, not inferred from a checklist being green.**