- PHASE5C_ENROLLMENT_COMMUNICATIONS.md: the full design doc. - RUNBOOK_GOOGLE_WORKSPACE_EMAIL.md: every REQUIRES GOOGLE WORKSPACE ADMIN ACTION step (mailbox, service account, domain-wide delegation scoped to gmail.send only, SPF/DKIM/DMARC), environment variables, testing procedure, production secret handling, rotation, revocation, troubleshooting, quotas. - RUNBOOK_STUDENT_ACTIVATION.md: day-to-day operation of the pipeline from /admin. - WEBSITE_ENROLLMENT_INTEGRATION.md: explicit website-vs-RootNode responsibility boundary, the new contact_email field, and closes the 'RootNode doesn't send activation email yet' known limitation this doc previously documented. - RUNBOOK_SELF_PACED_ENROLLMENT.md, RUNBOOK_STUDENT_ACCOUNT_LIFECYCLE.md: point at the new automatic-email behavior and the Resend activation control. - REMOTE_AND_SELF_PACED_ARCHITECTURE.md, ENGINEERING_SOP.md: close the enrollment- communication gap; new ENGINEERING_SOP §16. - PRODUCTION_VALIDATION_CHECKLIST.md, README.md, platform/README.md: cross-references and a Phase 5C production-validation section.
8.1 KiB
8.1 KiB
Production Validation Checklist
Walk this once, top to bottom, before calling a production deployment live. Every item here needs the real hardware — this is the "DEPLOY LATER" half of Phase 4, not something this repo can prove in advance.
Hardware / Proxmox
- Disk inspection done, layout chosen and matches what's actually in the server (
docs/RUNBOOK_DISK_FAILURE.mdinspection checklist,docs/PHASE4_PRODUCTION_INFRASTRUCTURE.md§4) - Proxmox VE installed, reachable only from
management_cidr/proxmox_mgmt_cidr - VM template built from real cloud-init (
infra/proxmox/README.md) tofu applycreated both VMs; both boot and are SSH-reachable
Platform VM
ansible-playbook site.ymlcompleted clean, all rolesdocker compose psshows every service healthy (postgres, provisioner, api, gateway, reverse-proxy, prometheus, alertmanager, grafana, loki, promtail, exporters)curl https://<origin>/healthz(via Gateway) returns 200curl http://localhost:4300/healthz(Platform API, from the VM) returns{"ok":true,"db":"ok"}- TLS certificate issued and valid for the real domain
Lab Host VM
- Docker daemon TLS verified:
docker --tlsverify --tlscacert=... -H tcp://<lab_host_ip>:2376 infosucceeds from the Platform VM, fails from anywhere else - Lab Host Router mTLS verified: a request to
https://<lab_host_ip>:8443with no client cert is rejected; with the Gateway's real client cert it isn't (mirrorsinfra/scripts/simulate-two-host/test-remote-routing.js's "direct public access: NOT AVAILABLE" case, now against the real host) - Provisioner successfully creates a real Lab against the real Lab Host
- Gateway can actually reach that Lab's terminal (HTTP UI load + WebSocket
kubectlcommand) — application mechanism built and simulation-tested in Phase 4.1 (docs/PHASE4_1_PREPRODUCTION_HARDENING.md§1/§6/§7); this line is the real-hardware confirmation that the same result holds across the actual two VMs, not two Docker networks on one daemon - Egress proxy correctly restricts a Lab's outbound traffic (Provisioner's per-Lab
DOCKER-USERchain)
Security
infra/firewall/validate-firewall.shrun against the real hosts with realiptables-restore --test, not just the structural lint- Confirm from an unauthorized IP: Postgres, Provisioner, Docker TLS port, Proxmox UI are all unreachable
- Confirm Student Lab → another Lab is blocked; confirm Student Lab → Proxmox is blocked
COOKIE_SECURE/TRUST_PROXYactive (check a real Set-Cookie header over HTTPS)
Academy (application-level smoke test)
- Owner bootstrap login works
- Instructor can create a cohort, roster a student
- Student can log in, boot a Lab, complete a scenario, see progress
- An assessment can be assigned, taken, and graded end-to-end
- Instructor analytics dashboards populate with real data
Backup
- Manual
backup-postgres.shrun succeeds against real Postgres, real off-host target reachable - A real restore (
restore-postgres.sh+verify-restore.sh) has been rehearsed at least once against a throwaway target, not just the local test harness — Phase 4.1'sinfra/backup/test-destructive-restore-drill.shproved the full mechanism (realistic data, real storage destruction, restore, real Platform API boot) locally; this line is the real-hardware confirmation - Cron job confirmed scheduled (
crontab -l)
Observability
- Grafana dashboard shows real data for both VMs
- Trigger one alert deliberately (e.g. stop a service) and confirm it fires and reaches the webhook
- Loki has logs flowing from both VMs
Pre-production hardening (Phase 4.1 — see docs/PHASE4_1_PREPRODUCTION_HARDENING.md)
resource-policy.yaml'sadmission_control.max_concurrent_labsset from a real measuredinfra/scripts/capacity-model.jsrun against the real Lab Host, not left disabled- A deliberate over-capacity attempt returns the clean 503 "Lab capacity is temporarily full" message, not a 500 — confirms the real host's admission control matches the local test
docs/RUNBOOK_LAB_HOST_RESOURCE_PRESSURE.md's steps still make sense once capacity numbers are real
Student identity & remote classroom (Phase 5A — see docs/PHASE5A_STUDENT_IDENTITY_REMOTE_CLASSROOM.md)
COOKIE_SECURE=1in the real production environment — confirms/api/student/ request-password-resetnever returnsdev_reset_tokenoutside of dev/CI- A real activation/reset link (
?activate=/?reset=) round-trips against the production domain, not justlocalhost— query-param parsing, no router library in play - Student login rate limiting still 429s after a restart against the real production
Postgres, same as the local
e2e-phase5a.jscheck GET /metricsis reachable by the real Prometheus instance (infra/monitoring/ prometheus/prometheus.yml.j2'srootnode-platform-apijob) and no student/instructor ID appears in any label valuedocs/RUNBOOK_STUDENT_ACCOUNT_LIFECYCLE.mdanddocs/RUNBOOK_REMOTE_CLASSROOM.md's steps still make sense walked against the real admin portal
Self-paced enrollment & website integration (Phase 5B — see docs/PHASE5B_SELF_PACED_ENROLLMENT.md)
INTEGRATION_SHARED_SECRETis set to a real, unique-to-production secret (not the dev value) and is stored only in the Platform API's own server-side environment — never committed, never in any browser-reachable config- The website team's production integration key was delivered out of band (not plaintext email/chat) and is confirmed working with one real, low-stakes test enrollment before go-live
- A deliberate
POST /api/integrations/enrollmentscall missing/with a wrong key returns a clean401against the real production endpoint, not a 500 or an information leak GET /metrics'srootnode_enrollments_total/rootnode_program_completions_totallabels contain only status/delivery_mode/program_id values — confirm no student_id, email, or external_reference ever appears in a real production scrapedocs/RUNBOOK_SELF_PACED_ENROLLMENT.md's steps still make sense walked against the real admin portal, anddocs/WEBSITE_ENROLLMENT_INTEGRATION.mdis what the website team actually integrated against (update it if reality drifted during that integration)
Enrollment communications / Google Workspace email (Phase 5C — see docs/PHASE5C_ENROLLMENT_COMMUNICATIONS.md)
- Every step in
docs/RUNBOOK_GOOGLE_WORKSPACE_EMAIL.mdmarked REQUIRES GOOGLE WORKSPACE ADMIN ACTION has actually been completed (sending mailbox, service account, domain-wide delegation scoped togmail.sendonly, SPF/DKIM/DMARC) EMAIL_PROVIDER=gwsset in the real production environment — confirm it is NOT left at the dev-provider default, which would silently mean no real student ever receives emailGOOGLE_SERVICE_ACCOUNT_JSON,EMAIL_FROM_ADDRESS,EMAIL_FROM_NAME,EMAIL_REPLY_TO, andPUBLIC_APP_ORIGINare all set from the real production secret store, not left at any dev/example valueinfra/scripts/gws-email-test.js(opt-in,RUN_GWS_EMAIL_TEST=true) run once against a realTEST_EMAIL_RECIPIENTand confirmed delivered — the one and only "does this actually work" proof this checklist can't fakeCOOKIE_SECURE=1in the real production environment — confirms the dev-preview notification route (GET /api/admin/notifications/:id/dev-preview) is unreachable and no activation/reset token is ever returned by any API responseGET /metrics's notification gauges/counters contain no student_id/email/token/ notification_id/external_event_id in any label value on a real production scrapedocs/RUNBOOK_STUDENT_ACTIVATION.md's steps still make sense walked against the real admin portal, including a real (not dev-provider) activation email actually arriving
Only once every box above is checked against the real server does the deployment graduate from "infrastructure package built" to "actually in production."