44154a4697
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
15 KiB
15 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-desktop-client-ci-cd | 03 | execute | 1 |
|
false |
|
|
|
Purpose: Minimal-manual-effort version control + CI/CD so milestone pushes automatically lint, test, build, and redeploy the running stack.
Output: .gitea/workflows/ci.yml, an act_runner service definition, and a setup runbook.
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/06-desktop-client-ci-cd/06-CONTEXT.md @.planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md @docker-compose.yml @package.json @turbo.json<artifacts_this_phase_produces>
.gitea/workflows/ci.yml— CI/CD pipeline (quality / test / build-deploy jobs)docker-compose.ci.yml— act_runner service (Docker socket mount, ephemeral mode)docs/ci-cd-setup.md— runbook for Gitea remote + runner registration + secrets </artifacts_this_phase_produces>
The executor presents these steps; the human performs them and supplies values back:
1. Confirm the Gitea base URL (e.g. https://gitea.example). If Gitea is not running, decide whether to add it
to docker-compose or use an existing instance.
2. Create a repository (e.g. `tessera`) and run `git remote add origin <gitea-repo-url>`.
3. In Gitea, enable Actions for the repo (Settings → Actions) and generate an act_runner registration token
(Admin/Repo → Actions → Runners → Create registration token).
4. Provide the runner registration token + Gitea instance URL so Task 2 can configure the runner, and confirm
the deploy host has the project checked out at the docker-compose path for `docker compose` deploys.
1. `git remote -v` shows an `origin` pointing at the Gitea repo
2. The human confirms Actions is enabled for the repo in Gitea settings
3. A runner registration token + Gitea URL are available for Task 2
- `git remote -v` lists a Gitea `origin` remote
- Gitea Actions is enabled for the repository (human-confirmed)
- Runner registration token and Gitea URL captured for runner setup
Type "approved" with the Gitea URL confirmed and runner token available, or describe the blocker
Task 2: Define act_runner service and CI/CD setup runbook
docker-compose.ci.yml, docs/ci-cd-setup.md
- docker-compose.yml (network names frontend-net/backend-net/data-net, service patterns, healthcheck style)
- .planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md (act_runner setup, Pitfall 5 socket security + ephemeral runners, anti-pattern: mount host docker.sock not Docker-in-Docker, anti-pattern: skip registry build locally)
Create `docker-compose.ci.yml` (separate compose file so CI infra is opt-in, not part of the app stack)
defining an `act_runner` service using the official `gitea/act_runner:latest` image. Configure via env:
`GITEA_INSTANCE_URL` (from Task 1), `GITEA_RUNNER_REGISTRATION_TOKEN` (from Task 1, referenced from a
`.env`/secret — never hardcoded), `GITEA_RUNNER_NAME` "tessera-runner", and `GITEA_RUNNER_EPHEMERAL=1`
(Pitfall 5: ephemeral runner revokes credentials after each job). Mount the host Docker socket
`/var/run/docker.sock:/var/run/docker.sock` (RESEARCH anti-pattern: mount the socket, do NOT run
Docker-in-Docker) and a named volume for runner config/data. Add restart policy `unless-stopped`.
Create `docs/ci-cd-setup.md` runbook documenting: (1) the Gitea repo + remote setup from Task 1, (2) how to
start the runner `docker compose -f docker-compose.ci.yml up -d`, (3) required secrets/vars
(GITEA_INSTANCE_URL, GITEA_RUNNER_REGISTRATION_TOKEN, and any deploy secrets used by ci.yml such as the deploy
path), (4) the Docker socket security note + ephemeral runner rationale (Pitfall 5), (5) that registry push is
intentionally skipped — images build locally on the same server (D-13, RESEARCH anti-pattern).
test -f docker-compose.ci.yml && grep -q "act_runner" docker-compose.ci.yml && grep -q "GITEA_RUNNER_EPHEMERAL" docker-compose.ci.yml && grep -q "/var/run/docker.sock" docker-compose.ci.yml && test -f docs/ci-cd-setup.md && docker compose -f docker-compose.ci.yml config >/dev/null
- `docker compose -f docker-compose.ci.yml config` validates (exit 0)
- act_runner mounts the host Docker socket and sets GITEA_RUNNER_EPHEMERAL=1
- Registration token is referenced from env/secret, never hardcoded
- docs/ci-cd-setup.md documents remote setup, runner start, secrets, and socket security note
act_runner compose file validates; runbook documents the full Gitea + runner setup
Task 3: Create .gitea/workflows/ci.yml multi-stage pipeline
.gitea/workflows/ci.yml
- package.json (root turbo scripts: lint, test, type-check — pipeline calls these via pnpm)
- turbo.json (lint/test/type-check tasks exist)
- docker-compose.yml (service names `web` and `api`, the build+deploy targets)
- apps/web/Dockerfile, apps/api/Dockerfile (reused image builds — do not author new ones)
- .planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md (Pattern 4 Multi-Stage Pipeline YAML, Pitfall 4 docker/build-push-action JWT error → use plain docker commands, anti-pattern skip registry, Security Domain CI secrets)
Create `.gitea/workflows/ci.yml` (GitHub Actions-compatible syntax, D-12). Name "Tessera CI/CD", trigger on
`push` to branch `main`. Three sequential jobs with `needs` chaining (D-12 staged quality gates):
1. `quality` (runs-on ubuntu-latest): `actions/checkout@v4`, `pnpm/action-setup@v4` (version 9, matching
packageManager pnpm@9.15.0), `actions/setup-node@v4` (node-version 24, cache pnpm), `pnpm install
--frozen-lockfile`, `pnpm lint` (Biome via turbo), `pnpm type-check`.
2. `test` (needs: quality): same checkout/pnpm/node setup, `pnpm install --frozen-lockfile`, `pnpm test`
(Vitest via turbo — `apps/web/vitest.config.ts` exists).
3. `build-deploy` (needs: test): checkout, then build + deploy with PLAIN docker compose commands (Pitfall 4 —
do NOT use docker/build-push-action which fails on Gitea's non-JWT token; Pattern 4 + RESEARCH anti-pattern):
`docker compose build web api` then `docker compose up -d web api`. Since the deploy target is the same
server as the runner (D-13), this builds images locally and restarts the services with no registry push.
Never echo secrets in steps. If the runbook (Task 2) defines deploy secrets/vars (e.g. a deploy path), read
them via Gitea Actions `${{ secrets.* }}` / `${{ vars.* }}`, not inline literals (Security Domain: CI secrets
leakage). Keep the workflow minimal and readable per CLAUDE.md (Claude builds maintainable infra).
test -f .gitea/workflows/ci.yml && grep -q "on:" .gitea/workflows/ci.yml && grep -q "pnpm lint" .gitea/workflows/ci.yml && grep -q "pnpm test" .gitea/workflows/ci.yml && grep -q "docker compose build" .gitea/workflows/ci.yml && ! grep -q "build-push-action" .gitea/workflows/ci.yml && python3 -c "import yaml,sys; yaml.safe_load(open('.gitea/workflows/ci.yml'))"
- ci.yml is valid YAML and triggers on push to main
- Three chained jobs: quality (lint+type-check) → test (vitest) → build-deploy (docker compose)
- Uses plain `docker compose build`/`up -d` — NOT docker/build-push-action (Pitfall 4)
- No hardcoded secrets; secrets/vars referenced via `${{ }}` expressions
Valid multi-stage pipeline file present; runs lint+type-check → tests → local docker build+deploy on push
Task 4: Trigger the pipeline with a real push and confirm it runs green
- .planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md (Validation Architecture — INFRA-04 integration test "Push to Gitea, verify Actions run")
- docs/ci-cd-setup.md (the runbook produced in Task 2)
The complete CI/CD track: Gitea remote (Task 1), act_runner (Task 2), and the ci.yml pipeline (Task 3).
INFRA-04 can only be proven by an actual push that triggers Gitea Actions — this requires the live Gitea
instance + running runner, so it is a blocking human-verify checkpoint.
Before this checkpoint the executor ensures the runner is up (`docker compose -f docker-compose.ci.yml up -d`)
and the workflow + compose files are committed to the branch that will be pushed.
1. Push the current main to the Gitea remote (`git push origin main`) — Claude does this manually per D-11.
2. Open the Gitea repo → Actions tab and confirm a "Tessera CI/CD" run started for the push.
3. Confirm the three jobs run in order and the `quality` and `test` jobs pass (lint, type-check, vitest).
4. Confirm `build-deploy` builds the web + api images and restarts them (`docker compose ps` shows them up).
5. Confirm no secrets appear in the job logs.
- A Gitea Actions run is triggered by the push (INFRA-04)
- quality + test jobs pass; build-deploy rebuilds and restarts web + api
- No secret values leaked in pipeline logs
Type "approved" once the pipeline run is green, or paste the failing job log
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Gitea push → act_runner execution | Pushed workflow code executes on the runner with Docker socket access |
| act_runner → host Docker daemon | Mounted /var/run/docker.sock grants the runner control over host containers |
| pipeline → secrets store | Deploy/registration secrets pass through Gitea Actions context |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-06-07 | Elevation | act_runner Docker socket mount | accept | Internal-only CI, only Claude pushes (D-11); GITEA_RUNNER_EPHEMERAL=1 revokes creds per job (Pitfall 5) |
| T-06-08 | Information Disclosure | secrets in pipeline logs | mitigate | Reference secrets via ${{ secrets.* }}/${{ vars.* }}; never echo; registration token from env not hardcoded (Security Domain) |
| T-06-09 | Tampering | malicious workflow modification | accept | Single trusted committer (Claude), push to main only; no external contributors (D-10/D-11) |
| T-06-SC | Tampering | act_runner image | mitigate | Use official gitea/act_runner image; CI actions pinned to major (checkout@v4, setup-node@v4) |
| </threat_model> |
<success_criteria>
- Gitea remote configured and main pushed (INFRA-04)
- act_runner registered and executing jobs
- Multi-stage pipeline (lint+type-check → tests → docker build+deploy) runs on push (D-12, D-13)
- No Git/Gitea functionality added to the Tessera app itself (D-10) </success_criteria>