Files
familysync/.planning/phases/08-gitea-ci/08-03-PLAN.md
T
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
2026-06-11 20:35:18 -04:00

183 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 08-gitea-ci
plan: 03
type: execute
wave: 3
depends_on: ['08-02']
files_modified:
- .gitea/workflows/ci.yml
autonomous: false
requirements: [CI-01]
must_haves:
truths:
- "On a PR to main, a harness job brings up the full dev stack inside the runner: MariaDB + API dev server (DEV_AUTH_BYPASS=true, :3000) + PWA Vite dev server (:5173, started by Playwright's own webServer)"
- 'The harness step waits for BOTH the API (:3000/health) and the PWA Vite server (:5173) to accept connections before Playwright launches, so it does not flake on startup races'
- 'The Phase 7 Playwright specs run UNCHANGED across both device profiles (iPhone 14/WebKit + Pixel 7/Chromium) and a failure blocks merge'
- 'On harness failure, test-results/ (traces/screenshots/videos) upload as a CI artifact via the gitea-upload-artifact fork'
artifacts:
- path: '.gitea/workflows/ci.yml'
provides: 'PR-triggered harness job running the Phase 7 mobile harness'
contains: 'test:e2e'
key_links:
- from: '.gitea/workflows/ci.yml (harness job)'
to: 'apps/pwa/e2e/global-setup.ts'
via: 'DEV_AUTH_BYPASS + PLAYWRIGHT_BASE_URL + DB_* env → pnpm test:e2e'
pattern: 'DEV_AUTH_BYPASS'
- from: 'harness job'
to: 'API :3000'
via: 'background node dist/index.js + curl /health readiness loop'
pattern: 'localhost:3000/health'
---
<objective>
Add the harness job to `.gitea/workflows/ci.yml`: bring up the dev stack inside the runner (MariaDB → migrate → API background process with DEV_AUTH_BYPASS=true on :3000 → Playwright starts Vite on :5173 itself) and run the Phase 7 mobile Playwright harness UNCHANGED across both device profiles, uploading traces on failure. This is the v1.1 extension of CI-01 (ROADMAP criteria 3 + 4) and Pitfall "dev-stack readiness races".
Purpose: Catch mobile-only regressions on every PR with no developer's host stack required (Phase 7 success criterion 4). CI owns ONLY stack bring-up + readiness waits — never spec content (D-01/D-02; 08-CONTEXT phase boundary).
Output: a `harness` job in ci.yml gated on `pull_request → main`.
The orchestration order in 08-RESEARCH §Dev-Stack Bring-Up is mandatory and SEQUENTIAL within the job. Read 08-01-SUMMARY for the runner-mode DB path and the WebKit-deps / upload-artifact answers.
</objective>
<execution_context>
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/08-gitea-ci/08-RESEARCH.md
@.planning/research/PITFALLS.md
@.planning/phases/08-gitea-ci/08-01-SUMMARY.md
@apps/pwa/playwright.config.ts
@apps/pwa/e2e/global-setup.ts
@apps/pwa/vite.config.ts
</context>
<artifacts_this_phase_produces>
- `.gitea/workflows/ci.yml` (EXTENDED — adds the harness job; created in Plan 02)
</artifacts_this_phase_produces>
<interface_context>
Confirmed harness contract (from playwright.config.ts + global-setup.ts + vite.config.ts — do NOT modify these files):
- `playwright.config.ts`: `reuseExistingServer: !process.env.CI` → with CI=true, Playwright STARTS Vite itself (`pnpm --filter @familysync/pwa dev`, :5173). `retries: 2`, `workers: 1`, `reporter: 'github'` are all gated on `process.env.CI`. Two projects: `iphone` (WebKit) + `pixel` (Chromium), both `serviceWorkers: 'block'`.
- `reporter: 'github'` likely emits invisible output in Gitea (08-RESEARCH Pitfall 5 / D-06). Override the reporter at the CI invocation: pass `--reporter=list,html` (e.g. `pnpm test:e2e -- --reporter=list,html`) OR confirm from 08-01-SUMMARY whether Gitea rendered annotations; if it did, the override is harmless. Do NOT edit playwright.config.ts.
- `global-setup.ts`: FAILS CLOSED — throws if `NODE_ENV=production` OR if `DEV_AUTH_BYPASS !== 'true'`. It polls `${PLAYWRIGHT_BASE_URL}/health` (via the Vite proxy → :3000), then gates `/api/me` (must be 200 → proves DEV_AUTH_BYPASS reached the API), then mysql2-seeds calendar id=10 + lists for user 1. It reads DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME (DB_HOST default 127.0.0.1).
- `vite.config.ts`: dev proxy `/health`, `/api`, `/callback` → http://localhost:3000. So PLAYWRIGHT_BASE_URL=http://localhost:5173 reaches the API health endpoint through the proxy.
- Root `test:e2e` = `pnpm --filter @familysync/pwa test:e2e` = `playwright test`.
- API start: `dev` script is `node --watch dist/index.js` and needs a prior build. In CI run `pnpm --filter @familysync/api build` (tsc → dist/index.js) then `node apps/api/dist/index.js &` (no --watch; the watcher is irrelevant in CI — Claude's Discretion in D + 08-RESEARCH Pattern 3). Pass DEV_AUTH_BYPASS=true INLINE on the node line (Pitfall 8 — env inheritance across `&` steps is not guaranteed).
- `npx playwright install --with-deps webkit chromium` must run from `apps/pwa` (where @playwright/test lives). Playwright explicitly says do NOT cache browser binaries (08-RESEARCH).
- DEV_AUTH_BYPASS user 1 has no CalDAV credential → harness verifies layout/flows, not live event-create (project memory). Specs already account for this; no change.
</interface_context>
<tasks>
<task type="auto">
<name>Task 1: Add the harness job — DB + migrate + API background process + readiness</name>
<files>.gitea/workflows/ci.yml</files>
<read_first>
- .planning/phases/08-gitea-ci/08-RESEARCH.md (§Dev-Stack Bring-Up — the numbered 1..8 sequence is canonical; §Pattern 3 API background process; Pitfall 8 DEV_AUTH_BYPASS inline)
- .planning/phases/08-gitea-ci/08-01-SUMMARY.md (runner-mode DB path; WebKit deps y/n)
- apps/pwa/e2e/global-setup.ts (fail-closed guards; readiness order)
</read_first>
<action>
Add a `harness` job to ci.yml: `runs-on: self-hosted`, `if: github.event_name == 'pull_request'` (parallel with fast-checks + api — D-03; no `needs:`).
DB bring-up: SAME runner-mode branch as Plan 02's api job (services: mariadb: for Docker mode with DB_HOST=mariadb, or `docker run -d` + readiness loop for host mode with DB_HOST=127.0.0.1). Set job env: DB_PORT 3306, DB_USER familysync, DB_PASSWORD testpass, DB_NAME familysync (throwaway creds). Include the explicit healthcheck.sh readiness loop before migrate (Pitfall 11; never mysqladmin).
Steps, in this exact order (08-RESEARCH §Dev-Stack Bring-Up 1..6):
1. `uses: actions/checkout@v4`; `uses: actions/setup-node@v4` (node 22); `corepack enable pnpm`.
2. `run: pnpm install --frozen-lockfile`.
3. (after DB ready) `run: pnpm --filter @familysync/api db:migrate` with DB_* env (drizzle-kit migrate; never push).
4. `run: pnpm --filter @familysync/api build` (produces dist/index.js — Pitfall 4).
5. Start API as a background process with DEV_AUTH_BYPASS INLINE:
`NODE_ENV=development DEV_AUTH_BYPASS=true DB_HOST=$DB_HOST DB_PORT=3306 DB_USER=familysync DB_PASSWORD=testpass DB_NAME=familysync node apps/api/dist/index.js & echo $! > /tmp/api.pid` (Pitfall 8). NODE_ENV must be `development` (not production — global-setup refuses; not test — dev-bypass activation checks development per 08-RESEARCH note).
6. Wait for API :3000: a curl retry loop `until curl -sf http://localhost:3000/health` with a ~60s deadline; on timeout, `kill $(cat /tmp/api.pid)` and `exit 1`. This step-level wait (D-02) ensures the API is up BEFORE Playwright starts Vite — separate from and earlier than global-setup's own poll.
</action>
<verify>
<automated>grep -q "harness" .gitea/workflows/ci.yml && grep -q "node apps/api/dist/index.js" .gitea/workflows/ci.yml && grep -q "DEV_AUTH_BYPASS=true node" .gitea/workflows/ci.yml && grep -q "localhost:3000/health" .gitea/workflows/ci.yml && grep -q "db:migrate" .gitea/workflows/ci.yml && ! grep -q "db:push" .gitea/workflows/ci.yml && echo HARNESS_STACK_OK</automated>
</verify>
<done>The harness job brings up MariaDB (per runner mode), migrates, builds the API, starts it as a background process with DEV_AUTH_BYPASS=true passed inline on the node line, and waits for :3000/health before continuing.</done>
</task>
<task type="auto">
<name>Task 2: Add Playwright install + run (both profiles) + artifact upload on failure</name>
<files>.gitea/workflows/ci.yml</files>
<read_first>
- apps/pwa/playwright.config.ts (CI gating: reuseExistingServer, reporter:'github', both projects)
- .planning/phases/08-gitea-ci/08-RESEARCH.md (§Pattern 5 Playwright harness; Pitfall 5 reporter override; Pitfall 6 upload-artifact fork)
- .planning/phases/08-gitea-ci/08-01-SUMMARY.md (WebKit deps y/n; upload-artifact fork y/n; did Gitea render 'github' reporter annotations?)
</read_first>
<action>
Continue the `harness` job (08-RESEARCH §Dev-Stack Bring-Up 7..8):
7. Install browsers: `run: npx playwright install --with-deps webkit chromium` with `working-directory: apps/pwa`. (If 08-01-SUMMARY showed WebKit deps cannot install on this runner, record that as a phase blocker in the SUMMARY — do NOT silently drop the iphone profile; D-05 requires BOTH profiles. WebKit feasibility is a hard CI-01 input.)
8. Run the harness:
`run: pnpm test:e2e -- --reporter=list,html` (the `--reporter=list,html` overrides the config's CI `'github'` reporter which renders invisibly in Gitea — Pitfall 5; skip the override only if 08-01-SUMMARY confirmed Gitea renders 'github' annotations, in which case it is harmless to keep).
env on this step: `CI: 'true'`, `PLAYWRIGHT_BASE_URL: http://localhost:5173`, `DEV_AUTH_BYPASS: 'true'`, `NODE_ENV: development`, plus DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME (global-setup seeds the DB directly via mysql2). CI=true makes Playwright start Vite itself (:5173) and use retries:2/workers:1; the run covers both `iphone` and `pixel` projects by default (no --project filter).
9. Upload artifacts on failure: a final step `if: failure()` `uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4` (NEVER actions/upload-artifact@v4 — GHES-blocked on Gitea, Pitfall 6) with `name: playwright-traces-${{ github.run_id }}`, `path: apps/pwa/test-results/`, `retention-days: 14` (D-06). Add a final `if: always()` step to `kill $(cat /tmp/api.pid) 2>/dev/null || true` to clean up the API background process.
Do NOT modify playwright.config.ts, global-setup.ts, vite.config.ts, or any spec — CI owns bring-up only (D-01/D-02; phase boundary).
</action>
<verify>
<automated>grep -q "playwright install --with-deps webkit chromium" .gitea/workflows/ci.yml && grep -q "test:e2e" .gitea/workflows/ci.yml && grep -q "PLAYWRIGHT_BASE_URL: http://localhost:5173" .gitea/workflows/ci.yml && grep -q "ChristopherHX/gitea-upload-artifact@v4" .gitea/workflows/ci.yml && ! grep -q "actions/upload-artifact@v4" .gitea/workflows/ci.yml && git diff --quiet -- apps/pwa/playwright.config.ts apps/pwa/e2e/global-setup.ts apps/pwa/vite.config.ts && echo HARNESS_RUN_OK</automated>
</verify>
<done>The harness job installs webkit+chromium with deps, runs pnpm test:e2e (CI=true, DEV_AUTH_BYPASS=true, base URL :5173, DB env) across both profiles with a list,html reporter override, and uploads test-results/ on failure via the gitea fork. No Phase 7 harness file is modified.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 3: Verify the harness job on the PR</name>
<what-built>The harness job (Tasks 12) added to ci.yml, exercised on the open PR to main.</what-built>
<how-to-verify>
1. Push the branch; on the PR, confirm the `harness` job runs alongside fast-checks + api.
2. Confirm it brings up MariaDB → migrate → API (:3000) → Playwright starts Vite (:5173) → both `iphone` and `pixel` projects execute and pass (ROADMAP criteria 3 + 4).
3. Confirm the readiness waits prevented a startup race (no "/api/me did not return 200" or ECONNREFUSED from global-setup on a cold run). If global-setup throws the DEV_AUTH_BYPASS error, the API was started without the inline flag (Pitfall 8) — fix the node invocation, do not re-run.
4. Deliberately break a spec or seed once (or inspect a prior failure) to confirm test-results/ uploads as a downloadable artifact in the Gitea UI (D-06). Revert the break.
5. Confirm test output is readable in the Gitea log (list reporter), not invisible 'github' annotations.
</how-to-verify>
<resume-signal>Type "harness green" once both device profiles pass against the CI-brought-up stack and artifact upload is confirmed, or paste the failing log.</resume-signal>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
| --------------------- | --------------------------------------------------------------------------------- |
| DEV_AUTH_BYPASS in CI | Bypass auth flag active in the harness job only; must never reach the publish job |
| CI test DB → seed | global-setup TRUNCATEs tables; fail-closed guards protect against prod DB |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
| --------- | ----------------- | ----------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| T-08-06 | Spoofing | DEV_AUTH_BYPASS=true in harness job | mitigate | Bypass is set ONLY in the harness job env, against throwaway DB creds; it never appears in the publish job (Plan 04). global-setup.ts fails closed on NODE_ENV=production and on missing DEV_AUTH_BYPASS, so it cannot wipe/seed an unconfirmed DB (08-RESEARCH Security Domain). |
| T-08-07 | Tampering | drizzle migrate against CI DB | mitigate | db:migrate only; db:push forbidden (grep gate). Throwaway creds, ephemeral container. |
| T-08-08 | Denial of Service | dev-server startup race | mitigate | Explicit :3000/health curl loop before Playwright (D-02) on top of global-setup's :5173/health + /api/me gates; MariaDB healthcheck.sh readiness loop before migrate (Pitfall 11). |
</threat_model>
<verification>
- ci.yml passes both Task grep gates (background API + inline bypass + :3000 readiness; both browsers + base URL + gitea upload fork; no harness-file edits).
- PR run shows the harness job green across iphone + pixel on a cold run.
- Artifact upload confirmed on a forced failure; output legible via list reporter.
</verification>
<success_criteria>
- CI-01 (harness half): PR to main brings up API + PWA dev servers + MariaDB with DEV_AUTH_BYPASS and runs the Phase 7 specs headlessly; failure gates merge (ROADMAP criterion 3).
- Readiness: waits for both :3000 and :5173 before Playwright (ROADMAP criterion 4; Pitfall dev-stack races).
- Phase 7 specs reused UNCHANGED (phase boundary); both device profiles run (D-05); traces upload on failure (D-06).
</success_criteria>
<output>
Create `.planning/phases/08-gitea-ci/08-03-SUMMARY.md` when done. Record: final API readiness timeout, whether the reporter override was needed, WebKit-deps install outcome on the runner, and confirmation no Phase 7 harness file was modified.
</output>