Operations¶
Qubit16 is designed to be operated by a small team. The operating principle is that every integration is optional, every degradation is visible, and every environment is validated before it serves a request.
Zero required configuration¶
The whole stack builds and runs with an empty environment. .env.example documents about 66 variables and not one is required. What each unlocks:
| Variable group | Unlocks | Without it |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL, …_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY |
Accounts, saved circuits in the cloud, orgs, exams, quotas | Everything persists in localStorage; the account page says accounts are not configured |
STRIPE_SECRET_KEY, price ids, webhook secret |
Checkout, portal, subscriptions | Pricing renders; checkout says it is not configured |
GEMINI_API_KEY, GROQ_API_KEY, ANTHROPIC_API_KEY, AZURE_API_* |
The live tutor tiers | The offline FAQ tier |
IBM_QUANTUM_TOKEN, AWS keys, AZURE_QUANTUM_* |
Real hardware | Devices listed as unavailable with the reason |
UPSTASH_REDIS_REST_* |
Shared rate-limit store | In-process limiter, with a startup warning |
RESEND_API_KEY, EMAIL_DISPATCH_SECRET |
Transactional email, the 7-day course | Email is a no-op that reports sent: false |
NEXT_PUBLIC_SENTRY_DSN |
Error reporting | The SDK is never imported |
NEXT_PUBLIC_ANALYTICS_ENDPOINT, ANALYTICS_SUPABASE_TABLE |
Aggregate analytics | Events stay on the device |
Three validators enforce this at boot: lib/env.ts in the web app (run from instrumentation.ts), app/core/env_check.py in the API and agent/env_check.py in the agent. They error only on half-configuration (a Stripe key with no price ids, an AWS access key with no secret, a wildcard CORS origin) and warn on everything else. scripts/preflight.mjs runs the same logic before a deploy, and CI asserts that an empty environment passes.
Health¶
Each service has a health endpoint that is safe to poll:
| Service | Path | What it probes |
|---|---|---|
| Web | GET /api/health |
Configuration of Supabase, Stripe, the two services and the model providers; ?deep=1 (optionally gated by HEALTH_DEEP_TOKEN) adds live reachability |
| API | GET /api/health |
Runs a real one-qubit circuit end to end, pings the job store, reports provider readiness from credential and SDK presence with checked: false |
| Agent | GET /api/agent/health |
API reachability, whether the course index loaded, model provider configuration; makes no model call |
Probe states are ok, degraded (still 200), failing (503) and skipped (never red). A service with nothing configured is healthy, because that is a valid state. Each container declares a HEALTHCHECK against its endpoint and the deployment's smoke test curls the public one.
Errors¶
API errors share one shape from lib/apiError.ts: a stable code, an HTTP status and a human message, with 400 for bad input, 401 for no identity, 402 for a locked feature or exhausted quota, 409 for a conflict, 429 for rate limiting, 503 for an unavailable dependency. Pages have route-level error boundaries and a global one. Nothing returns 500 on a bad request body.
Runbooks in the repository¶
| Document | Covers |
|---|---|
docs/OPERATIONS.md |
Environment variables with build/run annotations, health, error model, privacy decisions |
docs/DEPLOYMENT.md |
Vercel + Railway and one-box Docker Compose paths, first-deploy checklist, rollback |
docs/DEPLOYMENT-AZURE.md |
The production path (next page) |
docs/DEPLOYMENT-AWS.md, docs/DEPLOYMENT-GCP.md |
App Runner and Cloud Run alternatives |
docs/RUNBOOK.md |
A from-nothing local walkthrough including the Windows path |
docs/STRIPE-SETUP.md |
Test-mode billing in fifteen minutes, with the smoke script |
docs/LAUNCH-CHECKLIST.md |
Domain, DNS, email routing, store listings |
Rollback is documented as a platform operation (promote the previous image or deployment) with the explicit note that it does not undo processed Stripe events, browser service-worker caches or schema migrations; migrations are idempotent and additive for that reason.