# Chicken Babies R Us — Roadmap High-level phased plan. Each phase ends in a mergeable `dev` state and a passing manual test. Claude implements phase-by-phase. This document intentionally avoids application code; the only code here is **data model (dataclasses)** and **SQL schema**, which are authoritative. --- ## Phase 0 — Foundation ✅ **Completed:** 2026-04-21 **Summary:** Scaffolded the FastAPI skeleton — package layout, pinned deps, multi-stage Dockerfile, compose file, typed config loader, structlog init, and a `/healthz` liveness endpoint surfacing app version + git commit SHA. **Key files:** - `app/__init__.py` — package `__version__ = "0.1.0"` - `app/main.py` — `create_app()` factory + module-level `app`; configures logging then mounts routers - `app/config.py` — `Settings(BaseSettings)` with the full env contract (Optional where unused in Phase 0) + model validator refusing the dev-sentinel `SECRET_KEY` in production; `get_settings()` is `lru_cache`-d - `app/logging_config.py` — `configure_logging(app_env)`; `ConsoleRenderer` in dev, `JSONRenderer` otherwise - `app/routes/health.py` — `APIRouter` mounting `GET /healthz`, typed `HealthResponse` pydantic model - `app/models/`, `app/services/`, `app/templates/`, `app/static/` — placeholder dirs with `.gitkeep` - `tests/test_healthz.py` — FastAPI `TestClient` smoke test - `requirements.txt` — 17 pinned packages, `>=X,"}`. **Key details:** - **Healthz shape:** minimal flat JSON (not the full code_guidelines envelope). Future phases that add JSON APIs will use the envelope; the healthcheck stays small on purpose. - **Version source:** `app.__version__` string constant. **Commit SHA source:** `GIT_COMMIT_SHA` env var, baked into the image via `ARG GIT_COMMIT_SHA=unknown` + `ENV GIT_COMMIT_SHA`. Surfaces as `"unknown"` when unset (dev). - **Config loader:** single `Settings` class covers every ROADMAP env-var row. Fields not yet used at Phase 0 (`RESEND_*`, `HCAPTCHA_*`, `ADMIN_CONTACT_EMAIL`) are `Optional[str] = None`. `SECRET_KEY` defaults to the sentinel `"dev-insecure-change-me"`; a `@model_validator(mode="after")` refuses to boot if that sentinel survives into `APP_ENV=production`. - **Admin emails:** stored as raw comma-separated string; `admin_emails_list` property returns stripped+lowercased list for allowlist comparisons (used by Phase 3 auth). - **Logging:** `configure_logging` runs inside `create_app()` before any `structlog.get_logger` call; `app_started` structured event fires once at startup with `app_env`, `version`, `commit_sha` (no secrets). - **Docker CMD:** uvicorn runs with `--proxy-headers --forwarded-allow-ips=127.0.0.1` (Phase 6 will swap the IP for Caddy's LAN address). - **Verification run:** `python -c "from app.main import app"` ✓ · `pytest -q` 1 passed ✓ · `curl /healthz` returned both the default `"unknown"` payload and the real commit SHA when `GIT_COMMIT_SHA=$(git rev-parse HEAD)` was set ✓ · `docker compose config` exit 0 ✓. - **Branch:** built on `chore/phase-0-foundation` off `dev`; merged `--no-ff` into `dev` on completion. Not pushed. ## Phase 1 — Public Site Skeleton ✅ **Completed:** 2026-04-21 **Summary:** Shipped the public brochure site: base Jinja layout with logo + nav + footer, mobile-first single-file CSS using the ROADMAP palette, and four public routes (`/`, `/about`, `/contact`, `/shop`). Blog index renders via a service stub returning `[]`; Phase 2 swaps the body for SQLite without touching the route. **Key files:** - `app/models/posts.py` — `PostSummary` `@dataclass(frozen=True)` with `slug/title/published_at/excerpt` — list-view projection used by the homepage; richer `Post` arrives in Phase 2 alongside. - `app/services/posts.py` — `PostService.list_published(limit=20) -> list[PostSummary]` stub returning `[]`; `get_post_service()` DI helper (Phase 2 keeps the signature, swaps the body). - `app/routes/public.py` — `APIRouter` with `GET /`, `/about`, `/contact`, `/shop`; pulls templates off `app.state.templates` via `get_templates()` DI helper. - `app/templates/public/base.html` — layout: skip link, `
`/`