WIN · LAUNCH WORKBOOK pre-UAT → production · mined from Codex session 019f25cc · snapshot 2026-07-06 · origin/main 734eff41

Lane 5 · Infrastructure

Production deployment — staged rollout, starting simple

Today WIN deploys to two Mac-Mini environments via launchd deploy-controllers + docker-compose, fronted by a Cloudflare Tunnel. The path to AWS is real but decision-blocked twice over: Deploy Design A (written, reviewed, zero code, awaiting SIGNOFF) and an unreconciled EC2-vs-ECS split between specs 019 and 048.

45
commits of runtime drift (main vs served)
0
lines of Terraform / IaC in repo
0
secrets in a secrets manager (all plaintext env)
7/11
INS instrumentation helpers implemented

Current architecture (verified)

Runtime
8 compose services (postgres, redis, minio, server, kernel, daemon, proxy, dashboard) from docker/services.Dockerfile; staging is a 43-line compose override
Ingress
Remotely-managed Cloudflare Tunnel (docker-compose.yml:435–443); the checked-in cloudflared compose file is explicitly dead
Deploy control
launchd-run Bun controllers on :9475 (beta) / :9476 (staging), per-env WIN_DEPLOY_CONTROLLER_HOME since PR #597; win deploy CLI is a thin HTTP client that hardcodes http://mac-mini:<port> (controller-client.ts:73–77)
Images
CI builds+pushes 5 images to GHCR (arm64) — but scripts/deploy-{beta,staging}.sh rebuild from source locally instead of pulling them
Health
/api/health (ok, db, jobs, build.commit) + rich /api/health/diagnostics fan-out
Telemetry
Pino w/ redaction, opt-in Sentry, opt-in OTel (traces+metrics) — no metrics backend/aggregation platform wired

Latent bugs found during audit

Deploy Design A — signed off or explicitly gapped, but decide

Plan at docs/plans/pre-uat+prod-prep-3/deploy-design-a-plan-20260706.md (status: prepare-only). Proposes: all callers (CLI, slack-cc-ops, humans) route via the server admin-deploy relay; performDeploy becomes async (202 {deployId} + /api/admin/deploy/runs/:id[/logs|/cancel]); controller-client.ts deleted last; slack-cc-ops loses its own controller token (WIN-OPS-CHAIN-006). This is also the prerequisite for any cloud deploy trigger — a blocking synchronous deploy HTTP call can’t drive ECS/EC2 rollouts. Recommend: SIGNOFF now, implement the server-contract slice first.

AWS staged rollout — recommendation

Phase A — lift-and-shift EC2 (recommended start)

~days · closest to today’s shape · spec 019 aligned

One EC2 instance per env running today’s compose stack. Swap MinIO→S3 (code already S3-wire-compatible via Bun.S3Client), Postgres→RDS, Redis→ElastiCache — all config-level. Replace Cloudflare Tunnel with ALB+Route53 (or keep Tunnel fronting the instance). Deploy trigger via SSM Run Command replacing the mac-mini controller.

Phase B — staged rollout mechanics

after A stabilizes

Second instance + ALB weighted target groups for blue/green; staging→prod promotion = same GHCR (or ECR-mirrored) image digest, never a rebuild. Deploy receipts (INS-1) attach digest + health before/after.

Phase C — ECS/Fargate (spec 048’s sketch)

later · cleaner target

Drop Docker-socket + launchd entirely; compose services → ECS services, CodeDeploy blue/green. Requires rewriting performDeploy around ECS APIs — do it after Design A lands, since Design A defines the deploy-runs contract ECS would implement. Kernel’s per-job container spawning needs its own decision (Fargate can’t nest Docker; EC2-backed ECS or keep kernel on EC2).

Decision needed first Specs 019 (EC2+systemd+Terraform) and 048 (ECS forward-compat note) are unreconciled, and spec 032 scopes cloud prod out entirely “absent a decision.” Phase A honors 019 while keeping 048’s ECS door open via image-digest promotion. Ratify this sequencing in the launch decision register (D-row) so Terraform work can start.

Internal tooling — have vs need

INSToolState
0/1/2/7/8/10proof-stamp · deploy-receipt · browser-evidence-bundle · output-quality-score · readiness-dashboard · proof-ledger-lint (scripts/launch-*.mjs, bun run launch:packet)IMPLEMENTED (lint not CI-wired)
3Slack inbound trace (correlated ids, signing result)PARTIAL — pairs with SLK-7
4Suggester decision telemetry (decision/quietReason/confidence missing from metrics)PARTIAL
5Run lifecycle trace as one named-state viewPARTIAL
6UI runtime-assertion runner (hosted computed-style/viewport checks)PLANNED-ONLY
9Production smoke/watch packet (only a bare uptime pinger exists)PLANNED-ONLY
Terraform/Packer modules, secrets-manager layer, ECR promotion, traffic-shift toolingNET NEW

Gate chain (extracted from the runbooks)

UAT-0/1/2/6 partial; UAT-3 (Slack DM proof), UAT-4 (hosted rows), UAT-5 (/warm), UAT-7 (responsive), UAT-8 (quality), UAT-9 (dashboard green), UAT-10 (operator acceptance) all blocked → PROD-0..7 transitively blocked → cutover C0–C7 not started. Go-live board: P0 ready, P1–P9 blocked on STAGING-AUTH · MINT · DM-PROOF · HOSTED-ROWS · SIGNOFF · AUTHOR/RATIFY. Every one of those six blockers is an operator decision or a single bug — none is a project.

Plan of attack

Implementation worksheet0/9
Closes the “what you tested is what you run” gap and is the foundation of staged promotion.

Agent handoff prompt

prompt · DEP-2 staging runner auth
Fix WIN's staging runner auth (blocker STAGING-AUTH). Symptom: jobs spawn from the staging
web UI (POST /api/me/jobs → 201) but execution fails with "Not logged in · Please run
/login" — the spawned claude -p subprocess has no valid Claude OAuth credential in the
staging environment. Beta works. This is the same env-isolation bug class PR #597 fixed
for the deploy controller (shared ~/.win-deploy-controller → per-env
WIN_DEPLOY_CONTROLLER_HOME): the daemon/kernel Claude credential home (~/.win/claude/,
canonical auth.json + pool/) appears to be shared or absent for staging.

Do: (1) trace how the kernel sources CLAUDE_CODE_OAUTH_TOKEN per environment
(packages/claude-auth, kernel spawn env, launchd/compose env files — see
docker-compose.staging.yml overrides); (2) propose and implement per-env credential
isolation (e.g. WIN_HOME or claude-auth home override per env, or a dedicated pool
account for staging), matching the #597 pattern; (3) add a doctor check (win doctor
runtime) that reports which credential home/account each env resolves; (4) verification =
a staging job reaching a terminal state with output, captured as a ledger row. Respect the
protected-prod rails: no destructive Docker/db commands; coordinate any Mac Mini restarts
with the operator.