phase: 01_runnable_service_scaffold

**Phase 01 final verification pass — all criteria green**

- Verified (nothing left to implement; all 4 task files already in `complete/`): toolchain lock, typed `Settings`+`.env.example`, debugpy bootstrap, app factory, CLI (`--host/--port/--reload/--levels-dir`, `python -m undercooked`), static shell + `/static`, security headers, no-CDN/doc guards, `validate.sh`.
- `uv sync --extra dev` → exit 0; `uv.lock` present (untracked → harness commits it with the phase).
- `uv run undercooked --host 127.0.0.1 --port 8000` → `curl /healthz` = 200 `{"status":"ok","version":"0.1.0","rooms":0}` (superset of the criterion's body — now documented in README); `curl /` = 200 shell HTML; static css/js 200; CSP/nosniff/no-referrer/DENY on 200, 404 and 405 too.
- `DEBUGPY=1`: port 5678 is held on this machine by the user's own podman dev-container debugpy adapter — **no live change made/killed**; verified via `DEBUGPY=1 DEBUGPY_PORT=5679` → listener opens, `/healthz` still 200; the occupied-port case degraded gracefully as designed (warning, server serves). Bad config → one line + exit 2 ✓.
- `bash .agents/validate.sh` (root **and** `.agents/`) → exit 0: `ruff check .` 0 errors, `pyright` 0 errors, levels-CLI notice-skip (matches README prose), **284 passed** full suite incl. 10 Playwright e2e, coverage **100.00%** (floor 90), repo hygiene clean. `uv run pytest -m e2e` → 10 passed.
- Docs fixed: README Status → phase 01 complete + next pending 02 (was pointing at `todo/01…`), documented real `/healthz` body, precedence now "CLI flags > env > .env > default", added the PLAN §13-promised **How to play** section (honest about shell-only today), Layout rows marked what exists vs phases 02/07. AGENTS.md: recorded the `data:`-URL exemption in the no-CDN rule (test allows it) and matched the session-token rule to the browser's WebCrypto/base64url implementation. Also corrected a stale "session-wide chromium" docstring in `tests/e2e/conftest.py` (fixtures are function-scoped).
- Deviations: no container build run (needs an image pull; not a phase criterion — `Containerfile`/`compose.yaml` reviewed and consistent with `docs/DEPLOYMENT.md`); `HEAD /` is 405 (routes are GET-only by design; health checks use GET). No git index/history commands run.
- Next pending phase: `02_level_content_and_validation`.
This commit is contained in:
2026-10-06 01:51:20 -04:00
parent f84c6ebaef
commit 6fd83446ec
43 changed files with 5478 additions and 30 deletions
+48
View File
@@ -0,0 +1,48 @@
# DEPLOYMENT.md — building, running and operating the service
**Status:** stub written by phase 01 · **Filled in by phase 14**, which measures the real
capacity and writes the reverse-proxy/TLS/ops runbook against a production profile.
## Purpose
This document covers everything between "a repository" and "players can reach it": the
multi-stage container build (A19), the single-service `compose.yaml`, the environment
variables an operator actually sets, how TLS and a reverse proxy terminate the WebSocket
connection, and the runbook for the failure modes that an in-memory server has.
`README.md` "Container" stays the copy-paste quickstart; this file is the reasoning and
the operational detail.
## What is deployable today
One artifact, one process, no database (A6/A7): the image builds the package with
`pip install --prefix=/install .` in a builder stage and copies the result into a slim
Python 3.12 runtime that runs as the non-root `player` user (uid 10001), exposes port
8000, and health-checks `GET /healthz` from inside the container. `compose.yaml` runs that
one service with `restart: unless-stopped`. Configuration is environment-only — see the
README table and `.env.example`; the container sets `UNDERCOOKED_HOST=0.0.0.0` and points
`UNDERCOOKED_STATIC_DIR` / `UNDERCOOKED_LEVELS_DIR` at the paths copied into the image,
because the source-checkout defaults do not hold under `site-packages`.
```bash
podman build -t undercooked -f Containerfile .
podman run --rm -p 8000:8000 undercooked
podman compose up -d
```
Rules that hold for every deployment of this project:
- **Never set `DEBUGPY=1` on a host reachable from outside your machine** — the debugger
listener binds `0.0.0.0` (A18, dev-only).
- **Never commit `.env`.** It is gitignored, and `.env.example` is the secret-free
template; the game has no secrets by design (anonymous, A6/A16).
- Everything is in memory: a restart ends every round and erases every scoreboard. That is
a documented product decision, not an incident.
## What this document will contain when it is filled in
Reverse-proxy configuration (WebSocket upgrade, `Connection: upgrade`, idle timeouts
longer than the room's heartbeat), TLS termination and HSTS within the project's
`default-src 'self'` CSP (A12), resource limits derived from the phase-12 capacity
measurement in `BUDGETS.md`, log/metric surfaces that never record a room code with player
contents (§11), a rolling-restart procedure that accepts the round it kills, and the
backup story: none, and why.