--- phase: 08-gitea-ci plan: 02 subsystem: infra tags: [gitea-actions, ci, mariadb, vitest, drizzle, pnpm, playwright] # Dependency graph requires: - phase: 08-01 provides: runner-mode probe answers (Docker-executor, ubuntu-latest, cache-skip, no mysql CLI) provides: - PR-gating fast-checks job (lint + typecheck + PWA unit tests) - PR-gating api job (MariaDB 11 service container + drizzle-kit migrate + 238 API tests) - Single CI workflow file .gitea/workflows/ci.yml affects: [08-03, 08-04, phase-09, phase-10, phase-11, phase-12] # Tech tracking tech-stack: added: [] patterns: - 'Docker-executor services: mariadb (not docker-run) — confirmed by 08-01 probe' - 'Node mysql2 poll for MariaDB readiness (no mysql CLI in runner image)' - 'drizzle-kit migrate (never db:push) on single squashed baseline migration' - 'pnpm -r --if-present lint as auto-gate (exits 0 until a package lint script lands)' key-files: created: - .gitea/workflows/ci.yml - apps/api/src/db/migrations/0000_baseline.sql modified: - package.json key-decisions: - 'D-PROBE-01/02 honored: runs-on ubuntu-latest (not self-hosted), DB_HOST=mariadb (Docker-executor services:)' - 'Cache DISABLED — actions/cache@v4 timed out in 08-01 probe (D-PROBE-04)' - 'Readiness: Node mysql2 poll (90s deadline) — no mysql CLI available in runner image (D-PROBE-03)' - 'Migration squash: all migrations collapsed to 0000_baseline.sql to fix broken cold drizzle-kit migrate' - 'Lint is a documented no-op placeholder; root script changed to pnpm -r --if-present lint; real ESLint deferred to BACKLOG 999.16' patterns-established: - 'PR-gate pattern: parallel fast-checks (no DB) + api (MariaDB services:) jobs both gated on pull_request' - 'Readiness poll pattern: Node mysql2 script with 90s deadline before drizzle-kit migrate' requirements-completed: [CI-01] # Metrics duration: ~90min (including squash fix, PR push, and cold-run verification) completed: 2026-06-11 --- # Phase 08 Plan 02: PR-Gating CI Jobs Summary **Gitea Actions ci.yml delivers parallel PR-gating fast-checks (191 PWA tests green) and api (MariaDB 11 service container, squashed baseline migration, 238 API tests green) jobs on a cold first run — CI-01 non-harness half complete** ## Performance - **Duration:** ~90 min - **Started:** 2026-06-11T15:00:00Z - **Completed:** 2026-06-11T~17:00:00Z - **Tasks:** 3 (including 1 checkpoint verified by operator) - **Files modified:** 3 ## Accomplishments - Created `.gitea/workflows/ci.yml` with parallel `fast-checks` and `api` jobs triggered on `pull_request → main` - `fast-checks` job: Node 22 + pnpm via corepack, lint (no-op gate), tsc typecheck (both apps including pwa tsconfig.e2e.json), 191/191 PWA unit tests green - `api` job: MariaDB 11 via `services:` (Docker-executor confirmed by 08-01), Node mysql2 readiness poll (90s), drizzle-kit migrate, 238/238 API integration tests green — passed cold on first run - Fixed broken cold `drizzle-kit migrate` by squashing all migrations to a single `0000_baseline.sql` (deviation, see below) - Fixed root lint script from `pnpm -r lint` → `pnpm -r --if-present lint` so it exits 0 today and auto-gates once a package lint script lands - Gitea Actions run #5 (PR #3, head 0b148b9): both jobs SUCCESS on a cold pull_request run ## Task Commits 1. **Task 1: Create ci.yml with the fast-checks job** - `667f017` (feat) 2. **Task 2: Add the API job (MariaDB service + migrate + DB-backed tests)** - `3343f36` (feat) 3. **Task 3 (out-of-plan deviation — migration squash)** - `c0f892c` (fix) 4. **Task 3 (out-of-plan deviation — lint fix)** - `dc31d4e` (fix) 5. **Task 3 (out-of-plan — probe set to manual-only after CI verified)** - `0b148b9` (chore) **Task 3 was a checkpoint:human-verify; operator confirmed both jobs green on cold run.** ## Files Created/Modified - `.gitea/workflows/ci.yml` — PR-gating workflow: fast-checks + api jobs in parallel - `apps/api/src/db/migrations/0000_baseline.sql` — Squashed baseline migration (replaces multiple fragmented migrations) - `package.json` — Root `lint` script changed from `pnpm -r lint` to `pnpm -r --if-present lint` ## Decisions Made - **runs-on: ubuntu-latest** — plan text said `self-hosted` but 08-01 probe confirmed the runner has no self-hosted label; ubuntu-latest is the only working value (D-PROBE-01). - **services: mariadb (Docker-executor path)** — 08-01 confirmed Docker-executor (/.dockerenv present); used `services: mariadb:11` with `DB_HOST: mariadb`, not the host-mode docker-run fallback. - **Cache DISABLED** — actions/cache@v4 timed out in the 08-01 probe run; omitted entirely (D-PROBE-04). - **Node mysql2 readiness poll** — no `mysql` CLI in runner image (D-PROBE-03), and `mysqladmin ping` was removed in MariaDB 11. Used a Node.js script that polls `mysql2.createConnection().query('SELECT 1')` with a 90s deadline. - **Migration squash** — cold `drizzle-kit migrate` failed because 0001_lists_schema recreated tables already created in 0000 (duplicates lists/list_shares/list_items + calendars unique constraint). Squashed to a single `0000_baseline.sql` generated from current schema.ts. See Deviations. - **Lint no-op gate** — `pnpm lint` (`pnpm -r --if-present lint`) exits 0 today (no package defines a lint script). This is intentional: the gate exists structurally and will auto-block once ESLint is wired. Real lint wiring deferred to BACKLOG 999.16 (operator decision). ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] Squashed fragmented drizzle-kit migrations to fix broken cold migrate** - **Found during:** Task 3 (PR cold-run verification) - **Issue:** Cold `drizzle-kit migrate` failed with "table already exists" — migration 0001_lists_schema recreated `lists`, `list_shares`, `list_items`, and the `calendars` unique-constraint that migration 0000 had already created. An orphaned migration `0001_calendars_user_url_unique` also existed. Cold migration was impossible on a fresh DB. - **Fix:** Squashed all migrations into a single `apps/api/src/db/migrations/0000_baseline.sql` regenerated from `schema.ts` via `drizzle-kit generate`. Verified: fresh `db:migrate` succeeds, schema is structurally identical to dev DB, `drizzle-kit generate` reports no drift, 238 API tests pass. Local dev DBs must be rebuilt (drop + `db:migrate`); no production DB exists. - **Files modified:** `apps/api/src/db/migrations/0000_baseline.sql`, removed orphaned 0001 files - **Verification:** CI run #5 cold api job passed; `drizzle-kit generate` reports no drift post-squash - **Committed in:** `c0f892c` **2. [Rule 1 - Bug] Fixed root lint script to exit 0 on no-script workspaces** - **Found during:** Task 1/2 (fast-checks job authoring) - **Issue:** `pnpm -r lint` emits `ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT` and exits non-zero when no package has a `lint` script. This would immediately block the CI gate even though ESLint is not yet wired. - **Fix:** Changed root `package.json` lint script from `pnpm -r lint` to `pnpm -r --if-present lint`. The `--if-present` flag silently skips packages without the script; exits 0. When any package adds a lint script, it is auto-gated. ESLint wiring deferred to BACKLOG 999.16. - **Files modified:** `package.json` - **Verification:** CI fast-checks job passes lint step; no ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT in run #5 log - **Committed in:** `dc31d4e` --- **Total deviations:** 2 auto-fixed (2 Rule 1 bugs) **Impact on plan:** Both fixes were required for the cold-run pass. No scope creep. ## Issues Encountered - MariaDB 11 does not include `mysqladmin ping` (removed upstream) — plan text mentioned it as a readiness option but this is a known pitfall (08-PITFALLS Pitfall 2). Used Node mysql2 poll instead. - `healthcheck.sh --connect --innodb_initialized` is available in the MariaDB 11 container image but not callable from the step shell in Docker-executor mode without a `docker exec` into the sidecar. Node mysql2 poll was simpler and equivalent. - 08-01 probe workflow was left as `push + pull_request`-triggered after CI verification — set to `workflow_dispatch` only (commit `0b148b9`) to stop redundant re-runs on unrelated PRs. ## Threat Surface Scan No new endpoints, auth paths, file access patterns, or schema changes at trust boundaries introduced in this plan. The ci.yml uses throwaway creds (familysync/testpass, root/root) scoped to ephemeral MariaDB service containers only — T-08-03 mitigated. `db:push` absent from workflow — T-08-04 mitigated. Node mysql2 readiness poll with 90s deadline — T-08-05 mitigated. ## Known Stubs None — this plan produces CI workflow config only. ## Next Phase Readiness - **08-03 (PWA harness):** ci.yml is the target file for Plans 03 and 04. The `push: branches: [main]` trigger is already declared in ci.yml for the publish job (Plan 04). Plan 03 adds the harness job; both `fast-checks` and `api` jobs are green and stable. - **Local dev note:** After the migration squash, any local dev DB that was created before `c0f892c` must be rebuilt: `DROP DATABASE familysync; CREATE DATABASE familysync; pnpm --filter @familysync/api db:migrate`. - **BACKLOG 999.16:** ESLint wiring is explicitly deferred. The `--if-present` lint gate in ci.yml will auto-activate once any package adds a `lint` script — no ci.yml change needed. --- _Phase: 08-gitea-ci_ _Completed: 2026-06-11_