Files
rootnode-academy/docs/PHASE10_PRODUCTION_DEPLOYMENT.md
T
ced4568 0b2303a3e2 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.
2026-08-28 12:07:25 -05:00

91 lines
6.5 KiB
Markdown

# 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.