Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
14 KiB
14 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 08-gitea-ci | 02 | execute | 2 |
|
|
false |
|
|
Purpose: Fast PR feedback (D-03 — a lint failure does not wait behind the heavier jobs) plus a reliable cold-start API-integration gate. Uses the runner mode answer from 08-01-SUMMARY to choose service-container vs docker-run DB bring-up.
Output: .gitea/workflows/ci.yml containing fast-checks and api jobs gated on pull_request → main.
CRITICAL CONTEXT — read 08-01-SUMMARY first for the runner-mode fork:
- If 08-01 found DOCKER-executor mode: use
services: mariadb:withDB_HOST: mariadb(08-RESEARCH Pattern 1). - If 08-01 found HOST-executor mode: use a
docker run -d mariadb:11 -p 3306:3306step + explicit readiness loop withDB_HOST: 127.0.0.1(08-RESEARCH Pattern 2). Service containers do NOT work in host mode (nektos/act#2711).
<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/08-gitea-ci/08-RESEARCH.md @.planning/research/PITFALLS.md @.planning/phases/08-gitea-ci/08-01-SUMMARY.md<artifacts_this_phase_produces>
.gitea/workflows/ci.yml(NEW — this plan creates it; Plans 03/04 extend it) </artifacts_this_phase_produces>
<interface_context> Confirmed repo facts the executor MUST honor (do not re-derive):
- Root scripts:
lint=pnpm -r lint,typecheck=pnpm -r typecheck,test=pnpm --filter @familysync/api test(=vitest run), PWA unit =pnpm --filter @familysync/pwa test. - IMPORTANT — lint is currently a NO-OP: no package defines a
lintscript and ESLint is not installed, sopnpm lint(pnpm -r lint) printsERR_PNPM_RECURSIVE_RUN_NO_SCRIPTbut EXITS 0 and passes. Runpnpm lintas the documented command (satisfies CI-01's "lint" gate literally); do NOT add ESLint config — wiring lint is out of this phase's scope (CI-plumbing-only boundary). Note this in the SUMMARY so it is not mistaken for a bug. - ALL
apps/apitests live inapps/api/tests/andapps/api/test/setup.tstruncates DB tables in anafterEach(it swallows errors if no DB). Sopnpm --filter @familysync/api testREQUIRES a real MariaDB — the API "unit" and "integration" tests are one DB-backed command. The fast-checks job therefore runs only the PWA unit tests (no DB); the API job owns all API tests (with DB). apps/pwaunit tests (pnpm --filter @familysync/pwa test) need NO DB.- DB env var names (from apps/api/src/db/client.ts + drizzle.config.ts): DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME. Migrations:
pnpm --filter @familysync/api db:migrate(= drizzle-kit migrate). NEVER db:push (unsafe on MariaDB — project memory). - packageManager is
pnpm@11.5.1; no .nvmrc/engines pin → pin Node 22 viaactions/setup-node@v4+corepack enable pnpm. - Workspace is
apps/*only (no packages/shared despite CLAUDE.md mention) —pnpm -rspans 2 packages. </interface_context>
Add a workflow-level `env: { MILESTONE: v1.1 }` (per D-04; Plan 04 uses it).
Add the `fast-checks` job: `runs-on: self-hosted`, guarded `if: github.event_name == 'pull_request'`. Steps:
1. `uses: actions/checkout@v4`
2. `uses: actions/setup-node@v4` with `node-version: '22'`
3. `run: corepack enable pnpm`
4. Optional pnpm-store cache via `actions/cache@v4` ONLY if 08-01-SUMMARY reported cache works; otherwise OMIT the cache step entirely (do not add a hanging step). If included, wrap with `continue-on-error: true` (Pitfall 7).
5. `run: pnpm install --frozen-lockfile`
6. `run: pnpm lint` (no-op per interface_context, but the documented lint gate)
7. `run: pnpm typecheck` (= `pnpm -r typecheck` → tsc --noEmit in both apps incl. pwa tsconfig.e2e.json)
8. `run: pnpm --filter @familysync/pwa test` (PWA unit tests — no DB needed)
Do NOT run `pnpm test` here (that is the DB-backed API suite — it belongs in the api job).
DB bring-up — branch on 08-01-SUMMARY runner mode:
- DOCKER mode: declare `services: mariadb:` with `image: mariadb:11`, env `{ MARIADB_ROOT_PASSWORD: root, MARIADB_DATABASE: familysync, MARIADB_USER: familysync, MARIADB_PASSWORD: testpass }`, and `options: >- --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=10 --health-start-period=30s`. Set job `env.DB_HOST: mariadb`. (08-RESEARCH Pattern 1.) `--health-start-period=30s` because MariaDB 11 InnoDB init is slow (A9).
- HOST mode: instead, a first step `docker run -d --name mariadb -e MARIADB_ROOT_PASSWORD=root -e MARIADB_DATABASE=familysync -e MARIADB_USER=familysync -e MARIADB_PASSWORD=testpass -p 3306:3306 mariadb:11`, then an explicit readiness-loop step using `docker exec mariadb healthcheck.sh --connect --innodb_initialized` with a ~90s deadline (08-RESEARCH Pattern 2). Set `env.DB_HOST: 127.0.0.1`.
Regardless of mode, set job-level `env`: DB_PORT: 3306, DB_USER: familysync, DB_PASSWORD: testpass, DB_NAME: familysync (throwaway creds — NEVER reuse production secrets; T-08-03).
Even in Docker mode (where options: auto-waits), add an explicit readiness step BEFORE migrate: a loop that polls `healthcheck.sh --connect --innodb_initialized` (in Docker mode, via a one-shot `mariadb:11` client container or `mysql -h $DB_HOST ... -e "SELECT 1"`) with a deadline — Pitfall 11: healthy-in-Docker ≠ accepting-connections, and the cold-first-run reliability is ROADMAP criterion 2. Never use `mysqladmin ping` (removed in MariaDB 11).
Then steps:
- `uses: actions/checkout@v4`; `uses: actions/setup-node@v4` (node 22); `corepack enable pnpm`; `pnpm install --frozen-lockfile`.
- `run: pnpm --filter @familysync/api db:migrate` (drizzle-kit migrate — applies repo SQL; NEVER db:push). Pass DB_* env.
- `run: pnpm --filter @familysync/api test` (the full DB-backed API suite). Pass DB_* env.
Reuse the same cache decision as Task 1 (include only if 08-01 confirmed cache works).
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| PR head → CI runner | PR-triggered job runs untrusted branch content on operator infra |
| Test DB creds → job env | Throwaway creds in CI env; must not be production secrets |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-08-03 | Information Disclosure | MariaDB creds in job env | mitigate | Use throwaway creds (familysync/testpass, root/root) scoped to the ephemeral service container only; NEVER reference production DB_PASSWORD or any repo secret in these jobs (08-RESEARCH Security Domain). |
| T-08-04 | Tampering | drizzle-kit against CI DB | mitigate | Use db:migrate (applies committed SQL) exclusively; db:push is forbidden (emits destructive TRUNCATE diff on MariaDB — project memory drizzle-mariadb-push-unsafe). Verified by grep gate (! grep db:push). |
| T-08-05 | Denial of Service | cold-start readiness race | mitigate | Explicit healthcheck.sh readiness loop before migrate (Pitfall 11) so the gate is reliable on first run, not flaky. |
</threat_model>
- ci.yml passes both Task grep gates (service container + readiness + migrate-not-push; fast-checks node-pin + typecheck + pwa test). - PR run shows fast-checks ∥ api in parallel; api green on cold first run. - No production secret referenced in either job.<success_criteria>
- CI-01 (non-harness half): PR to main runs lint + typecheck (both apps) + unit tests + API integration vs MariaDB service container; failures gate merge (ROADMAP criteria 1 + 2).
- Pitfall 11 honored: healthcheck.sh --connect --innodb_initialized readiness, never mysqladmin; reliable cold-start.
- One workflow file (D-03), parallel event-gated jobs. </success_criteria>