Skip to content

SCRUM-240 — Deploy: the admin plane in compose

Plan ref: OPS-1 (docs/11-admin-plane-plan.md). Stacked on SCRUM-241.

What exists

Service Notes
admin-auth-migrate, admin-auth admin_auth_rw DSN; key/TOTP/root-password paths under ./secrets/admin_auth (mounted writable so genkey can write; the files come from SCRUM-242); issuer https://admin-auth.otomo.internal, audience otomo:staff; public URL http://localhost:8090 (the tunnel)
config-migrate, config config_rw; blobs on the named volume blobs read-write
patch patch_ro on the config DB; the same blobs volume read-only; starts after config (see below)
dashboard Prometheus/Loki URLs (services arrive in SCRUM-230; panels degrade until then), DASHBOARD_TARGETS = every service's internal :9090
admin-ui no host port; gateway_dev fronts it at /admin/
Staff trust (doc 10 gap 4) every staff verifier uses http://admin-auth:8080/.well-known/jwks.json and the pinned issuer; the STAFF_JWKS_URL/stand-in override is gone; gateway_dev's admin-auth upstream is http://admin-auth:8080
up.sh postgres → migrations → services; readiness waits for admin-auth, config, patch, dashboard, admin-ui; stops with a pointer to generate-secrets.sh if the admin-auth key files are missing
scripts/check-compose.sh renders the file with compose and asserts the pinned JWKS/issuer/audience, blobs rw/ro, unchanged published ports, one-shot migrations, DB user ↔ database, and patch-after-config

No new host ports: registry 127.0.0.1:5000, gateway :8080, gateway_dev 127.0.0.1:8090.

How to verify

cd deploy
sed 's/^\([A-Z_]*PASSWORD\)=$/\1=x/' .env.example > /tmp/t.env
docker compose --env-file /tmp/t.env -f compose.yaml config --quiet
sh scripts/check-compose.sh /tmp/t.env
# on a fresh VM, after SCRUM-242: scripts/generate-secrets.sh && scripts/up.sh

Results at time of writing

  • compose config (standalone compose v2.39.4, no daemon on the dev box) and check-compose.sh: pass; sh -n on every script: pass.
  • Not yet run on a VM: that needs SCRUM-242's key generation, and the shared host's cutover (SCRUM-244) needs the team's sign-off.

How it was built

DeepSeek run scoped (Landlock) to deploy (266 s, ~46k output tokens, reasoning effort low). Claude review: config and patch could start concurrently, and whichever first mounts the empty blobs volume sets its ownership; if patch won (its image has no blob directory) the volume would be root-owned and config could not write blobs. Patch now depends on config, and check-compose asserts it.