---
phase: 01-foundation
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - backend/go.mod
  - backend/go.sum
  - backend/.env.example
  - backend/.gitignore
  - backend/.air.toml
  - backend/compose.yaml
  - backend/justfile
  - backend/sqlc.yaml
  - backend/tailwind.input.css
  - backend/migrations/0001_init.sql
  - backend/internal/db/doc.go
  - backend/internal/session/doc.go
  - backend/internal/tablos/doc.go
  - backend/internal/tasks/doc.go
  - backend/internal/files/doc.go
autonomous: false
requirements:
  - FOUND-01
  - FOUND-02
  - FOUND-05
tags:
  - go
  - scaffold
  - tooling
  - postgres
  - tailwind
  - htmx

must_haves:
  truths:
    - "Running `just bootstrap` from a fresh clone installs goose, templ, sqlc, air CLIs and bootstrap-downloads the Tailwind standalone binary + htmx.min.js into backend/bin and backend/static (these assets are NOT committed; they are gitignored and reproduced by `just bootstrap`)"
    - "Running `just db-up` (or `podman compose up -d postgres`) starts a healthy Postgres 16 container reachable at localhost:5432"
    - "Running `just migrate up` against the running Postgres applies migration 0001_init.sql cleanly and `just migrate status` shows it applied"
    - "Running `just --list` shows recipes for at least: bootstrap, dev, db-up, db-down, migrate, generate, styles-watch, test, lint, build, clean"
    - "Phase-1 caveat: the `generate`, `test`, `build`, and `dev` recipes are scaffolded in Plan 01-01 but only become runnable after Plans 01-02 and 01-03 land their consumers (templ files, ui/*.css, cmd/web/main.go, cmd/worker/main.go). The justfile parses and `just --list` enumerates them in Plan 01-01; their end-to-end execution is covered by Plan 01-03's checkpoint."
    - "CDN policy (clarified): no runtime CDN references in served HTML/CSS/JS or in any committed source file consumed at request time; bootstrap-time download URLs (Tailwind GitHub release, HTMX from unpkg) appear ONLY inside `backend/justfile` and are the single authoritative source for the pinned asset versions."
    - "Tailwind input CSS declares @source globs that include backend/templates/**/*.templ and backend/internal/web/**/*.{templ,go} so JIT does not purge classes used only in templ files"
    - ".env.example documents DATABASE_URL, PORT, ENV and .env is gitignored"
    - "D-01: Two-binary cmd/web + cmd/worker layout with shared internal/ packages"
    - "D-02: Phase 1 creates directory skeleton for all internal/<domain> packages with placeholder doc.go files"
    - "D-04: goose (pressly/goose) chosen as migration tool with .sql files using +goose Up/Down annotations"
    - "D-05: just migrate up/down/status recipes wired via goose CLI for local development"
    - "D-06: Phase 1 includes one trivial bootstrap migration 0001_init.sql exercising the migration pipeline end-to-end"
    - "D-12: Standalone Tailwind CLI binary downloaded by just bootstrap into ./bin/tailwindcss — no Node or pnpm toolchain"
    - "D-13: air (cosmtrek/air) used for Go live-reload via just dev watching .go and .templ files"
    - "D-14: Tailwind watch mode runs as separate just styles-watch process — not via air pre_cmd"
    - "D-15: Env-driven configuration via .env files with required keys DATABASE_URL PORT ENV documented in .env.example"
    - "D-16: Postgres driver pgx/v5 with pgxpool for connection pool; sqlc configured to emit pgx-compatible code"
  artifacts:
    - path: backend/go.mod
      provides: Go module declaration with required deps (chi, templ, pgx, goose, uuid)
      contains: "module backend"
    - path: backend/compose.yaml
      provides: podman/docker compose service for postgres:16-alpine with pg_isready healthcheck
      contains: "postgres:16-alpine"
    - path: backend/justfile
      provides: task runner recipes (bootstrap, dev, db-up, db-down, migrate, generate, styles-watch, test, lint, build)
      contains: "bootstrap"
    - path: backend/migrations/0001_init.sql
      provides: goose-formatted no-op bootstrap migration exercising the migration pipeline
      contains: "+goose Up"
    - path: backend/sqlc.yaml
      provides: sqlc configuration pointing at migrations/ as schema source, internal/db/queries as queries source, emitting pgx/v5 code
      contains: "sql_package"
    - path: backend/tailwind.input.css
      provides: Tailwind v4 entry CSS with @source globs for templ files and @import lines for ui/*.css
      contains: "@source"
    - path: backend/.air.toml
      provides: air live-reload config for Go + templ
      contains: "templ generate"
    - path: backend/.env.example
      provides: Required env keys (DATABASE_URL, PORT, ENV)
      contains: "DATABASE_URL"
    - path: backend/.gitignore
      provides: Excludes bin/, tmp/, .env, static/tailwind.css, static/htmx.min.js, *_templ.go
      contains: ".env"
    - path: backend/internal/db/doc.go
      provides: db package marker (Phase 1 placeholder; pgxpool wiring lands in Plan 03)
      contains: "package db"
    - path: backend/internal/session/doc.go
      provides: session package placeholder (populated Phase 2)
      contains: "package session"
    - path: backend/internal/tablos/doc.go
      provides: tablos package placeholder (populated Phase 3)
      contains: "package tablos"
    - path: backend/internal/tasks/doc.go
      provides: tasks package placeholder (populated Phase 4)
      contains: "package tasks"
    - path: backend/internal/files/doc.go
      provides: files package placeholder (populated Phase 5)
      contains: "package files"
  key_links:
    - from: backend/justfile (bootstrap recipe)
      to: backend/bin/tailwindcss + backend/static/htmx.min.js
      via: curl download to pinned versions
      pattern: "curl.*tailwindcss"
    - from: backend/justfile (migrate recipe)
      to: backend/migrations/0001_init.sql
      via: goose CLI with GOOSE_MIGRATION_DIR=migrations
      pattern: "GOOSE_MIGRATION_DIR=migrations"
    - from: backend/sqlc.yaml
      to: backend/migrations/
      via: schema path so sqlc reads the same SQL files goose runs
      pattern: "schema:.*migrations"
    - from: backend/tailwind.input.css
      to: backend/templates/**/*.templ and backend/internal/web/**/*.{templ,go}
      via: "@source" directives
      pattern: "@source.*templ"
---

<objective>
Lay down the entire `backend/` project skeleton: Go module, directory layout, local Postgres compose file, justfile, env example, gitignore, air config, sqlc config, Tailwind input CSS, the no-op goose bootstrap migration, and `doc.go` placeholders for every `internal/<domain>` package locked in CONTEXT D-01/D-02.

Purpose: Every subsequent plan in this phase (and the rest of v1) depends on a working `cd backend && just bootstrap && just db-up && just migrate up` loop. This plan delivers that loop without yet writing any Go application code or handlers — pure infra.

Output: `backend/` exists with a valid Go module, all directory placeholders, working `just` recipes, a healthy compose-managed Postgres, and one applied bootstrap migration.
</objective>

<execution_context>
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/workflows/execute-plan.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/STATE.md
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/phases/01-foundation/01-CONTEXT.md
@.planning/phases/01-foundation/01-RESEARCH.md
@.planning/phases/01-foundation/01-UI-SPEC.md
@.planning/phases/01-foundation/01-VALIDATION.md
@.planning/phases/01-foundation/SKELETON.md

# Reference scaffolds — read for shape, do NOT copy wholesale (they use pnpm Tailwind which we are dropping)
@go-backend/justfile
@go-backend/compose.yaml
@go-backend/sqlc.yaml

<interfaces>
# Phase 1 has no upstream Go interfaces to honor. The shape this plan establishes:
# - Module path: `backend`
# - Tailwind input file path (relative to backend/): `tailwind.input.css`
# - Tailwind output path: `static/tailwind.css`
# - Goose migration dir: `migrations/`
# - sqlc schema source: `migrations/`
# - sqlc queries source: `internal/db/queries/` (empty in Phase 1)
# - sqlc output: `internal/db/sqlc/` (empty in Phase 1)
# - Air tmp dir: `tmp/`
# - Local DB DSN (dev only, in justfile): `postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable`
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Initialize Go module and pin runtime dependencies</name>
  <files>backend/go.mod, backend/go.sum</files>
  <read_first>
    - .planning/phases/01-foundation/01-RESEARCH.md (Standard Stack section — pinned versions are authoritative)
    - .planning/phases/01-foundation/SKELETON.md (locked versions table)
  </read_first>
  <action>From repo root, create `backend/`, then `cd backend && go mod init backend`. Pin the following runtime deps with `go get` at the exact versions from RESEARCH.md Standard Stack: `github.com/go-chi/chi/v5@v5.2.5`, `github.com/a-h/templ@v0.3.1020`, `github.com/jackc/pgx/v5@v5.9.2`, `github.com/pressly/goose/v3@v3.27.1`, `github.com/google/uuid@v1.6.0`. Do NOT use any version other than those listed.

**Do NOT run `go mod tidy` in this task.** Because no Go source file imports these packages yet in Plan 01-01, `go mod tidy` would aggressively strip the `require` lines we just pinned (Codex review concern #1). `go get` writes the pins into `go.mod` and `go.sum` directly; that is sufficient for Plan 01-01's purposes. `go mod tidy` becomes safe to run only after Plan 01-03 lands the consumer code in `internal/db/pool.go`, `internal/web/{router,handlers,middleware,slog}.go`, and `cmd/{web,worker}/main.go` — Plan 01-03's `just generate` / `just build` workflow will execute it implicitly, and at that point all five deps have real importers so `tidy` is a no-op on the require list.

The Go directive should match the developer's installed Go (1.22 minimum; existing `go-backend/go.mod` uses 1.26 — use the same `go` line if available, otherwise `1.22`). Do not import or fetch `air`, `sqlc`, `templ` CLI, `goose` CLI as Go module deps in `go.mod` — those are installed via `go install` from the `just bootstrap` recipe (Task 5).</action>
  <verify>
    <automated>cd backend && test -f go.mod && test -f go.sum && grep -q '^module backend$' go.mod && grep -q 'github.com/go-chi/chi/v5 v5.2.5' go.mod && grep -q 'github.com/a-h/templ v0.3.1020' go.mod && grep -q 'github.com/jackc/pgx/v5 v5.9.2' go.mod && grep -q 'github.com/pressly/goose/v3 v3.27.1' go.mod && grep -q 'github.com/google/uuid v1.6.0' go.mod</automated>
  </verify>
  <acceptance_criteria>
    - `backend/go.mod` declares `module backend`
    - All five runtime deps pinned at the exact versions above (verified by grep on the literal `name version` line)
    - `go.sum` exists and is non-empty (populated by `go get`)
    - `go mod tidy` is NOT run in this task (Codex concern #1: would strip the require lines because no Go source imports them yet)
    - `go mod verify` is NOT asserted here because it can interact badly with un-imported requires on some Go versions; Plan 01-03 reasserts it after real importers land
  </acceptance_criteria>
  <done>`backend/go.mod` and `backend/go.sum` exist, module is `backend`, all five deps pinned at locked versions; tidying deferred to Plan 01-03 where real imports exist.</done>
</task>

<task type="auto">
  <name>Task 2: Create directory skeleton and per-package doc.go placeholders</name>
  <files>backend/internal/db/doc.go, backend/internal/session/doc.go, backend/internal/tablos/doc.go, backend/internal/tasks/doc.go, backend/internal/files/doc.go, backend/internal/db/queries/.gitkeep, backend/internal/db/sqlc/.gitkeep, backend/templates/.gitkeep, backend/migrations/.gitkeep, backend/bin/.gitkeep, backend/static/.gitkeep</files>
  <read_first>
    - .planning/phases/01-foundation/01-CONTEXT.md (D-01, D-02 — directory layout is non-negotiable)
    - .planning/phases/01-foundation/SKELETON.md (Directory layout section)
  </read_first>
  <action>Create the directory tree exactly as locked in D-01/D-02 and mirrored in SKELETON.md. For each placeholder Go package directory, write a minimal `doc.go` containing only a package clause and a one-line comment naming the phase that will populate it. Example shape (do NOT add anything else): a `// Package files is a placeholder; the upload/storage implementation lands in Phase 5.` line above `package files`. The five required doc.go files cover packages: `db`, `session`, `tablos`, `tasks`, `files` — per D-02 ("creates the directory skeleton for all internal/&lt;domain&gt; packages (empty doc.go is fine) so later phases drop files in without restructuring"). Use `.gitkeep` empty files for directories that have no Go files yet (`migrations/`, `templates/`, `bin/`, `static/`, `internal/db/queries/`, `internal/db/sqlc/`). Do NOT create `internal/web/` here — Plan 02 owns that package (handler tests + ui design-system). Do NOT create `cmd/web/` or `cmd/worker/` here — Plan 03 owns those entrypoints. This task strictly delivers the placeholder skeleton.</action>
  <verify>
    <automated>cd backend && for p in db session tablos tasks files; do test -f "internal/$p/doc.go" && grep -q "^package $p$" "internal/$p/doc.go" || { echo "missing or wrong package: $p"; exit 1; }; done && test -d migrations && test -d templates && test -d bin && test -d static && go build ./internal/...</automated>
  </verify>
  <acceptance_criteria>
    - Each of `internal/{db,session,tablos,tasks,files}/doc.go` exists and declares the correct package
    - `internal/db/queries/`, `internal/db/sqlc/`, `migrations/`, `templates/`, `bin/`, `static/` directories exist (via `.gitkeep`)
    - `go build ./internal/...` succeeds (empty packages compile)
    - `internal/web/`, `cmd/web/`, `cmd/worker/` are NOT created by this task
  </acceptance_criteria>
  <done>Directory skeleton matches D-01/D-02 verbatim; placeholder Go packages compile.</done>
</task>

<task type="auto">
  <name>Task 3: Compose file, .env.example, .gitignore, bootstrap goose migration</name>
  <files>backend/compose.yaml, backend/.env.example, backend/.gitignore, backend/migrations/0001_init.sql</files>
  <read_first>
    - .planning/phases/01-foundation/01-RESEARCH.md (Code Examples section — compose.yaml and .env.example are verbatim references; D-15, D-20)
    - go-backend/compose.yaml (existing healthy reference; strip dev seed mounts as the research note instructs)
  </read_first>
  <action>Write `backend/compose.yaml` based on the verbatim block in RESEARCH.md Code Examples. Required: service name `postgres`, image `postgres:16-alpine`, container name `xtablo-backend-postgres`, env `POSTGRES_DB=xtablo`, `POSTGRES_USER=xtablo`, `POSTGRES_PASSWORD=xtablo`, port mapping `5432:5432`, named volume `postgres_data`, `pg_isready -U xtablo -d xtablo` healthcheck (interval 5s, timeout 5s, retries 10), `restart: unless-stopped`. Do NOT include the seed-mount volume from `go-backend/compose.yaml` — Phase 1 has nothing to seed. Write `backend/.env.example` with exactly the three keys locked in D-15: `DATABASE_URL=postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable`, `PORT=8080`, `ENV=development`. Add a brief comment line above each key. Write `backend/.gitignore` covering: `bin/` (Tailwind binary + go-installed CLIs may live here), `tmp/` (air rebuild output), `.env`, `.env.local`, `static/tailwind.css` (generated), `static/htmx.min.js` (bootstrap-downloaded by `just bootstrap`, never committed), `*_templ.go` (templ-generated; per RESEARCH Pitfall 1 these are never committed), `internal/db/sqlc/*.go` (generated, except keep `.gitkeep`). Write `backend/migrations/0001_init.sql` exactly per RESEARCH.md Code Examples — goose annotations `-- +goose Up` and `-- +goose Down`, each section containing `SELECT 1;`. The file MUST start with `-- +goose Up` on the first non-comment line (goose parser requirement).</action>
  <verify>
    <automated>cd backend && grep -q 'postgres:16-alpine' compose.yaml && grep -q 'pg_isready' compose.yaml && grep -q '^DATABASE_URL=' .env.example && grep -q '^PORT=' .env.example && grep -q '^ENV=' .env.example && grep -q '^\.env$' .gitignore && grep -q 'tailwind.css' .gitignore && grep -q 'htmx.min.js' .gitignore && grep -q '_templ\.go' .gitignore && grep -q '\-\- +goose Up' migrations/0001_init.sql && grep -q '\-\- +goose Down' migrations/0001_init.sql</automated>
  </verify>
  <acceptance_criteria>
    - `compose.yaml` declares postgres:16-alpine with a pg_isready healthcheck
    - `.env.example` contains exactly DATABASE_URL, PORT, ENV (per D-15)
    - `.gitignore` excludes bin/, tmp/, .env, generated CSS, vendored htmx, *_templ.go
    - `0001_init.sql` is a valid goose file with `-- +goose Up` / `-- +goose Down` markers and a no-op body
    - No seed-mount carryover from go-backend/compose.yaml
  </acceptance_criteria>
  <done>Local Postgres can be brought up via `podman compose up -d postgres` (verified in Task 7); the bootstrap migration file is goose-parseable.</done>
</task>

<task type="auto">
  <name>Task 4: sqlc config, Tailwind input CSS, air config</name>
  <files>backend/sqlc.yaml, backend/tailwind.input.css, backend/.air.toml</files>
  <read_first>
    - .planning/phases/01-foundation/01-RESEARCH.md (Pitfall 3, Pitfall 5, Pitfall 7, Code Examples for .air.toml)
    - .planning/phases/01-foundation/01-UI-SPEC.md (Tailwind Configuration Contract — the @source globs and @import lines are exact)
    - go-backend/sqlc.yaml (reference shape only)
  </read_first>
  <action>Write `backend/sqlc.yaml` with `version: "2"`, one `sql` entry: `engine: postgresql`, `schema: "migrations"` (Pitfall 7 — point sqlc at the goose migrations dir so the schema sources match), `queries: "internal/db/queries"`, output `gen.go` with `package: "sqlc"`, `out: "internal/db/sqlc"`, `sql_package: "pgx/v5"`, `emit_json_tags: false`, `emit_interface: false`. Write `backend/tailwind.input.css` verbatim per UI-SPEC §Tailwind Configuration Contract: line 1 `@import "tailwindcss";`, then `@source "../templates/**/*.templ";`, `@source "../internal/web/**/*.templ";`, `@source "../internal/web/**/*.go";`, then `@import "../internal/web/ui/base.css";`, `@import "../internal/web/ui/button.css";`, `@import "../internal/web/ui/card.css";`, `@import "../internal/web/ui/badge.css";`. Note: the four `ui/*.css` files are created in Plan 02. They do not exist yet when this plan runs — that's fine, Tailwind v4 will error only when invoked. The CSS file pins the contract; the styles compile step is exercised by Plan 03's verify (after Plan 02 lands the ui CSS files). Write `backend/.air.toml` per RESEARCH.md Code Examples: `root = "."`, `tmp_dir = "tmp"`, `[build]` with `cmd = "templ generate && go build -o ./tmp/web ./cmd/web"`, `bin = "./tmp/web"`, `include_ext = ["go", "templ"]`, `exclude_dir = ["tmp", "bin", "static", ".git", "internal/db/sqlc"]`, `exclude_regex = [".*_templ\\.go$"]`, `delay = 200`, `stop_on_error = true`. Per RESEARCH Open Question 2 the recommendation is two-terminal workflow (Tailwind watch separate from air); do NOT add Tailwind to air's pre_cmd.</action>
  <verify>
    <automated>cd backend && grep -q '^version: "2"' sqlc.yaml && grep -q 'engine: postgresql' sqlc.yaml && grep -q 'schema: "migrations"' sqlc.yaml && grep -q 'sql_package: "pgx/v5"' sqlc.yaml && grep -q '^@import "tailwindcss";' tailwind.input.css && grep -c '@source' tailwind.input.css | awk '$1>=3{exit 0} {exit 1}' && grep -q 'internal/web/ui/button.css' tailwind.input.css && grep -q 'include_ext = \["go", "templ"\]' .air.toml && grep -q 'exclude_regex = \[".*_templ' .air.toml && grep -q 'templ generate' .air.toml</automated>
  </verify>
  <acceptance_criteria>
    - sqlc.yaml schema source points at `migrations/` (Pitfall 7)
    - tailwind.input.css declares ≥3 @source globs covering templ files and internal/web Go files (Pitfall 3)
    - tailwind.input.css @imports the four `internal/web/ui/*.css` files exactly as UI-SPEC requires
    - .air.toml watches `.go` + `.templ`, excludes generated `*_templ.go` (Pitfall 5), runs `templ generate` before build
    - Tailwind is NOT added to air's pre_cmd (RESEARCH Open Question 2 recommendation)
  </acceptance_criteria>
  <done>sqlc, Tailwind, and air configs match UI-SPEC + RESEARCH-mandated shapes; later plans can rely on these contracts.</done>
</task>

<task type="auto">
  <name>Task 5: justfile with bootstrap, db, migrate, generate, dev, test, lint, build recipes</name>
  <files>backend/justfile</files>
  <read_first>
    - .planning/phases/01-foundation/01-RESEARCH.md (Code Examples section — justfile recipes are the canonical reference; Environment Availability table for tool install commands and pinned versions)
    - .planning/phases/01-foundation/01-CONTEXT.md (D-05, D-11, D-12, D-13)
    - go-backend/justfile (shape only — strip pnpm Tailwind, retain podman + templ + sqlc + air pattern)
  </read_first>
  <action>Write `backend/justfile` modeled on the RESEARCH Code Examples block. Required at minimum, these recipes (names exact):

  - `default` → `@just --list`
  - `bootstrap` → mkdir bin, then `go install` the four CLI tools at the exact RESEARCH-pinned versions: `github.com/pressly/goose/v3/cmd/goose@v3.27.1`, `github.com/a-h/templ/cmd/templ@v0.3.1020`, `github.com/sqlc-dev/sqlc/cmd/sqlc@v1.31.1`, `github.com/air-verse/air@v1.65.1`. Then bootstrap-download the Tailwind v4 standalone binary into `./bin/tailwindcss` using an **explicit OS/arch mapping** (Codex concern #2 — the `uname -s`/`uname -m` outputs do NOT match GitHub release asset naming; `darwin` vs `macos`, `x86_64` vs `x64`, `aarch64`/`arm64` vs `arm64`). The recipe must compute the asset name with a shell case on `uname -s` and `uname -m` resolving to one of: `tailwindcss-macos-x64`, `tailwindcss-macos-arm64`, `tailwindcss-linux-x64`, `tailwindcss-linux-arm64` (verify these against `https://github.com/tailwindlabs/tailwindcss/releases/latest` at implementation time — the four-name set above is current as of Tailwind v4.0). Use a recipe-local `tailwind_version` variable (e.g., `tailwind_version := "v4.0.0"` — use the latest 4.x stable available at bootstrap time and document in a comment). After download, `chmod +x bin/tailwindcss`. Then bootstrap-download the HTMX v2.x `htmx.min.js` from `unpkg.com/htmx.org@2/dist/htmx.min.js` into `./static/htmx.min.js` — this asset is bootstrap-downloaded (not committed; `.gitignore` excludes it), and the justfile is the single authoritative source for the HTMX version. Per CONTEXT D-10 the runtime page MUST NOT reference any CDN; the bootstrap-time `unpkg.com` URL inside the justfile is the explicit allowed exception.
  - `db-up` → `podman compose up -d postgres`
  - `db-down` → `podman compose down`
  - `migrate cmd="status"` → invokes `goose` with `GOOSE_DRIVER=postgres`, `GOOSE_DBSTRING` set to the dev DSN, `GOOSE_MIGRATION_DIR=migrations`, passing `{{cmd}}` (so `just migrate up`, `just migrate down`, `just migrate status` all work)
  - `generate` → runs `templ generate`, then `sqlc generate`, then `./bin/tailwindcss -i tailwind.input.css -o static/tailwind.css`
  - `styles-watch` → `./bin/tailwindcss -i tailwind.input.css -o static/tailwind.css --watch`
  - `dev` → depends on `db-up`, runs `just generate` once, then exports `DATABASE_URL` for the dev DSN and runs `air -c .air.toml`. Document in a top-of-file comment that the developer is expected to run `just styles-watch` in a second terminal (RESEARCH Open Question 2 — two-terminal workflow).
  - `test` → runs `just generate` then `go test ./...`
  - `lint` → runs `go vet ./...` and `gofmt -l .` (failing if any file would be reformatted)
  - `build` → runs `just generate`, then `go build -o bin/web ./cmd/web`, then `go build -o bin/worker ./cmd/worker`
  - `clean` → removes bootstrap-downloaded and generated artifacts: `rm -rf bin/ tmp/ static/htmx.min.js static/tailwind.css` and `find . -name '*_templ.go' -delete`. Does NOT remove the Postgres volume — call `just db-down` first if a full reset is needed. (Codex concern #10 — README Method B references `just clean`; provide a real recipe.)

  Document at top of justfile in a comment block: project name, podman/docker portability note (CONTEXT D-11 — `compose.yaml` works under either; README will spell out the alternative `docker compose` invocation), and the two-terminal `dev` + `styles-watch` workflow. Do NOT add a recipe to download `goose`/`templ`/`sqlc`/`air` outside of `bootstrap` — keep one install path.

  Note: `build` will fail until Plan 03 ships `cmd/web/main.go` and `cmd/worker/main.go`. That is expected and acceptable for Plan 01's verification — we only assert the justfile parses and `just --list` enumerates the required recipes.</action>
  <verify>
    <automated>cd backend && just --list 2>/dev/null | grep -E '^\s+(bootstrap|db-up|db-down|migrate|generate|styles-watch|dev|test|lint|build|clean)\b' | wc -l | awk '$1>=10{exit 0} {exit 1}'</automated>
  </verify>
  <acceptance_criteria>
    - `just --list` enumerates at least: bootstrap, db-up, db-down, migrate, generate, styles-watch, dev, test, lint, build, clean
    - `bootstrap` recipe installs goose v3.27.1, templ v0.3.1020, sqlc v1.31.1, air v1.65.1 at exact pinned versions (verifiable by grep on the justfile)
    - `bootstrap` recipe resolves the Tailwind asset name via an explicit OS/arch case — `darwin→macos`, `linux→linux`, `x86_64→x64`, `arm64`/`aarch64→arm64` — and downloads one of the four documented asset names (Codex concern #2). Grep test: `grep -E 'tailwindcss-(macos|linux)-(x64|arm64)' backend/justfile` returns ≥ 1 match.
    - `bootstrap` recipe bootstrap-downloads htmx.min.js into static/ from unpkg.com (this is a bootstrap-time exception to D-10's no-CDN rule; D-10 forbids runtime CDN references — the served HTML/CSS/JS must reference only `/static/htmx.min.js`)
    - `clean` recipe exists and removes `bin/`, `tmp/`, `static/htmx.min.js`, `static/tailwind.css`, and `*_templ.go` files
    - `migrate` recipe sets GOOSE_DRIVER, GOOSE_DBSTRING, GOOSE_MIGRATION_DIR
    - `dev` recipe runs `db-up`, `generate`, then `air`
    - No pnpm / npm / node references anywhere in the justfile (D-12)
  </acceptance_criteria>
  <done>justfile parses, exposes required recipes at the right names, and only references tools approved by CONTEXT/RESEARCH.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 6 (checkpoint): Bootstrap + bring up Postgres + apply migration end-to-end</name>
  <what-built>The full scaffold from Tasks 1–5: Go module, directory skeleton, compose.yaml, .env.example, .gitignore, sqlc.yaml, tailwind.input.css, .air.toml, justfile, bootstrap migration. After this checkpoint a developer can run the four-command bootstrap loop and see a healthy DB with one migration applied.</what-built>
  <files>backend/bin/tailwindcss, backend/static/htmx.min.js, postgres container, goose_db_version table</files>
  <action>Human verifies that the scaffold from Tasks 1–5 actually bootstraps end-to-end. The CLI work is automated in `just bootstrap`; the human gate confirms the tool downloads + compose health + first migration are real, not aspirational. Steps in how-to-verify are executed by the user; the agent does not run them autonomously because `just bootstrap` performs network downloads and `podman compose up -d postgres` mutates host state — both require explicit user authorization.</action>
  <how-to-verify>
    Run, from the repo root, in order:
    1. `cd backend && just bootstrap` — installs goose/templ/sqlc/air, downloads `bin/tailwindcss`, downloads `static/htmx.min.js`. Confirm all four `go install` commands succeed and both files exist (`test -x bin/tailwindcss && test -s static/htmx.min.js`).
    2. `just db-up` — starts the postgres container. Wait for `podman compose ps` to show the service as healthy (5–15s).
    3. `just migrate up` — exit 0; output mentions `0001_init`.
    4. `just migrate status` — shows `0001_init.sql` as Applied with a timestamp.
    5. `psql "postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable" -c "SELECT version FROM goose_db_version ORDER BY id DESC LIMIT 1;"` — returns `1`.

    Expected outcome: postgres container healthy, goose_db_version table contains version 1, no errors in any step.

    If any step fails: investigate before approving. Common failure modes per RESEARCH Pitfalls 1, 2, 7.
  </how-to-verify>
  <verify>Human walks the five-step bootstrap loop and confirms each step exits successfully; `just migrate status` shows `0001_init.sql` Applied.</verify>
  <done>User responds "approved" after observing all five steps succeed on their machine.</done>
  <resume-signal>Type "approved" or describe issues (e.g., "Tailwind binary URL 404", "goose migration parse error").</resume-signal>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Internet → `just bootstrap` downloads | Tailwind binary + htmx.min.js are pulled from public URLs during bootstrap; integrity not yet pinned by hash in Phase 1 |
| Local filesystem → goose / sqlc CLIs | Installed via `go install` from public proxy.golang.org |
| Local Postgres container ↔ host | Bound to `localhost:5432` only via compose port mapping |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-01-01 | T (Tampering) | `static/htmx.min.js` bootstrap-downloaded via curl from unpkg | accept | Phase 1 scope: vendoring from a public CDN is an explicit decision (D-10 forbids runtime CDN, not bootstrap-time fetch). Subresource integrity hashes deferred to Phase 7 along with deploy hardening. Rationale: low-value local-dev attack surface; CSS/HTMX assets do not handle secrets in Phase 1. |
| T-01-02 | T (Tampering) | `bin/tailwindcss` binary downloaded via curl | accept | Same rationale as T-01-01. Tailwind is local-dev tooling only; not shipped in any production image in Phase 1. Hash pinning revisited at Phase 7 (deploy) where reproducible CI bootstrap matters. |
| T-01-03 | I (Information disclosure) | `.env` containing dev DSN | mitigate | `.gitignore` excludes `.env`; only `.env.example` (with placeholder/dev-only credentials) is committed. Dev DSN uses the literal `xtablo:xtablo` credential set, which is harmless for local-only bound containers. |
| T-01-04 | I (Information disclosure) | `goose_db_version` table | accept | Standard goose metadata table; contains only migration version numbers and timestamps. No user data. |
| T-01-05 | D (Denial of service) | postgres container port 5432 exposed to host | accept | Container binds to all interfaces by default in compose; mitigation deferred — developer machine is the trust zone. If the developer is on a hostile network, they can bind `127.0.0.1:5432:5432` in a follow-up; not in Phase 1 scope. |
</threat_model>

<verification>
- `cd backend && go mod verify` exits 0
- `cd backend && go build ./internal/...` succeeds (placeholder packages compile)
- `cd backend && just --list` enumerates all required recipes
- `cd backend && just bootstrap && just db-up && just migrate up && just migrate status` runs cleanly end-to-end (checkpoint Task 6)
- Manual: open `backend/compose.yaml`, `backend/.env.example`, `backend/.gitignore`, `backend/sqlc.yaml`, `backend/.air.toml`, `backend/tailwind.input.css`, `backend/justfile`, `backend/migrations/0001_init.sql` and verify each matches its acceptance criteria above
</verification>

<success_criteria>
1. `backend/` directory tree exactly matches the layout locked in CONTEXT D-01/D-02 and reproduced in SKELETON.md
2. Go module declares `module backend` with all five runtime deps pinned at RESEARCH-Standard-Stack versions
3. `just bootstrap` installs all four CLI tools at pinned versions and vendors `bin/tailwindcss` + `static/htmx.min.js`
4. `just db-up` starts a healthy postgres:16-alpine container reachable at localhost:5432
5. `just migrate up` applies the no-op bootstrap migration; `just migrate status` shows it as Applied
6. No Node/npm/pnpm artifacts anywhere in `backend/` (D-12)
7. No **runtime** CDN references in any served HTML/CSS/JS (D-10 clarified): bootstrap-time download URLs inside `backend/justfile` (Tailwind GitHub release URL, HTMX unpkg URL) are the explicit allowed exception; templates and committed CSS reference only `/static/*` paths
8. Tailwind asset name is resolved via explicit OS/arch mapping (`darwin→macos`, `x86_64→x64`, `arm64`/`aarch64→arm64`) — never via raw `uname` interpolation (Codex concern #2)
9. `go mod tidy` is NOT executed in Plan 01-01 (Codex concern #1) — pinning is via `go get` only; tidy lands implicitly in Plan 01-03 when consumers import the deps
</success_criteria>

<output>
After completion, create `.planning/phases/01-foundation/01-01-SUMMARY.md` documenting:
- Pinned versions actually installed (Tailwind 4.x patch, HTMX 2.x patch chosen at bootstrap time)
- Any deviations from the RESEARCH-locked stack (there should be none)
- Path forward for Plan 02 (Wave 2) — handler tests + ui design-system package
</output>
