Phase 8: Gitea CI — runner probe + PR gating jobs (fast-checks + api) #3

Merged
luckberg merged 66 commits from gsd/phase-08-gitea-ci into main 2026-06-11 16:11:42 -04:00
3 changed files with 187 additions and 12 deletions
Showing only changes of commit 212d8c1691 - Show all commits
+3 -3
View File
@@ -105,7 +105,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
**Wave 3** *(blocked on Wave 2 completion)*
- [ ] 08-03-PLAN.md — ci.yml: harness job (dev-stack bring-up + readiness waits + Phase 7 Playwright specs, both profiles)
- [x] 08-03-PLAN.md — ci.yml: harness job (dev-stack bring-up + readiness waits + Phase 7 Playwright specs, both profiles)
**Wave 4** *(blocked on Wave 3 completion)*
@@ -217,7 +217,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
| 5. Web Push Notifications | v1.0 | 8/8 | Complete | 2026-06-10 |
| 6. UX Polish | v1.0 | 6/6 | Complete | 2026-06-10 |
| 7. Mobile Test Harness | v1.1 | 4/4 | Complete | 2026-06-11 |
| 8. Gitea CI | v1.1 | 2/4 | In Progress| |
| 8. Gitea CI | v1.1 | 3/4 | In Progress| |
| 9. Faster Write-Back | v1.1 | 0/? | Not started | - |
| 10. Admin Role & Settings | v1.1 | 0/? | Not started | - |
| 11. Per-Event Reminders | v1.1 | 0/? | Not started | - |
@@ -229,7 +229,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
**Goal:** [Captured for future planning] Abstract the calendar backend behind a provider interface so Fastmail/CalDAV is one implementation among potentially many. Shipping with a single provider is fine, but the broker, sync, and event-expansion layers should be structured so additional providers (e.g. other CalDAV hosts, Google Calendar, generic ICS feeds) can be added without rework. Captures the "provider" seam as an explicit architectural concern.
**Requirements:** TBD
**Plans:** 2/4 plans executed
**Plans:** 3/4 plans executed
Plans:
+9 -9
View File
@@ -3,9 +3,9 @@ gsd_state_version: 1.0
milestone: v1.1
milestone_name: Operability & Polish
status: executing
stopped_at: 08-02 complete — advancing to 08-03 (Wave 3)
last_updated: "2026-06-11T18:37:18.400Z"
last_activity: "2026-06-11 -- 08-02 complete; fast-checks (191 PWA tests) + api (238 API tests, MariaDB 11 services:) both green on cold PR run; migration squash c0f892c unblocked cold migrate; advancing to 08-03"
stopped_at: 08-03 complete — advancing to 08-04 (Wave 4, publish job)
last_updated: "2026-06-11T20:00:00.000Z"
last_activity: "2026-06-11 -- 08-03 complete; harness job green on cold CI run (run #11): 58 specs passed across iphone/WebKit + pixel/Chromium in 1.6 min; 4 infrastructure fixes (API-reap, IPv4-first, dev-user seed, reporter forwarding); no Phase 7 harness file modified; advancing to 08-04"
progress:
total_phases: 16
completed_phases: 1
@@ -26,9 +26,9 @@ See: .planning/PROJECT.md (updated 2026-06-10)
## Current Position
Phase: 08 (gitea-ci) — EXECUTING
Plan: 4 of 4 (08-03 next — Wave 3)
Status: Executing — 08-02 complete, PR-gating CI jobs green on cold run
Last activity: 2026-06-11 -- 08-02 complete; fast-checks (191 PWA tests) + api (238 API tests, MariaDB 11 services:) both green on cold PR run; migration squash c0f892c unblocked cold migrate; advancing to 08-03
Plan: 4 of 4 (08-04 next — Wave 4, publish job)
Status: Executing — 08-03 complete, harness green (58 specs, both profiles, cold CI run)
Last activity: 2026-06-11 -- 08-03 complete; harness job green on cold CI run (run #11): 58 specs passed across iphone/WebKit + pixel/Chromium in 1.6 min; 4 infrastructure fixes (API-reap, IPv4-first, dev-user seed, reporter forwarding); no Phase 7 harness file modified; advancing to 08-04
## Performance Metrics
@@ -199,9 +199,9 @@ Recent decisions affecting current work:
## Session Continuity
Last session: 2026-06-11T17:00:00.000Z
Stopped at: 08-02 complete — advancing to 08-03 (Wave 3)
Resume file: .planning/phases/08-gitea-ci/08-03-PLAN.md
Last session: 2026-06-11T20:00:00.000Z
Stopped at: 08-03 complete — advancing to 08-04 (Wave 4, publish job)
Resume file: .planning/phases/08-gitea-ci/08-04-PLAN.md
## Operator Next Steps
@@ -0,0 +1,175 @@
---
phase: 08-gitea-ci
plan: 03
subsystem: testing
tags: [playwright, ci, gitea, mariadb, webkit, chromium, dev-auth-bypass]
# Dependency graph
requires:
- phase: 07-mobile-test-harness
provides: Phase 7 Playwright specs (both device profiles) run unchanged in CI
- phase: 08-02
provides: ci.yml with fast-checks + api jobs; runner-mode (ubuntu-latest, Docker-executor, services:)
provides:
- harness job in .gitea/workflows/ci.yml bringing up the full dev stack in CI and running the Phase 7 mobile specs across both device profiles
- four infrastructure fixes resolving API-reap, IPv6/IPv4 mismatch, missing dev-user seed, and double-pnpm reporter forwarding
affects: [08-04, phase-09, phase-10, phase-11, phase-12]
# Tech tracking
tech-stack:
added: []
patterns:
- "Combine API-start + readiness + test run in a single CI step so the API is not reaped at a step boundary"
- "NODE_OPTIONS=--dns-result-order=ipv4first when Vite is IPv4-only and the runner resolves localhost to ::1 first"
- "Idempotent seed step (INSERT IGNORE) for the DEV_AUTH_BYPASS user before global-setup runs — FK chain requires it"
- "Call the PWA test:e2e script directly with --filter instead of root test:e2e -- -- to avoid double-pnpm arg forwarding"
key-files:
created: []
modified:
- .gitea/workflows/ci.yml
key-decisions:
- "FIX-1 (53a989c): Start API + run e2e in a single step — bare 'node &' in an early step is reaped when that step exits; the API must remain a child of the test shell through the entire Playwright run"
- "FIX-2 (7389740): Use PLAYWRIGHT_BASE_URL=http://127.0.0.1:5173 and NODE_OPTIONS=--dns-result-order=ipv4first — Vite binds IPv4-only; Node fetch does not fall back from ::1 to 127.0.0.1 unlike curl"
- "FIX-3 (e486c6b): Seed dev user id=1 (INSERT IGNORE) after migrate, before API start — DEV_AUTH_BYPASS injects the user in-memory only; on a fresh CI DB the FK constraint silently aborted the calendars seed"
- "FIX-4 (03e8088): Call 'pnpm --filter @familysync/pwa test:e2e --reporter=list,html' directly — 'pnpm test:e2e -- --reporter=list,html' double-forwards '--' through two pnpm layers; Playwright treats --reporter as a test-file filter and finds no tests"
- "Phase 7 harness files (playwright.config.ts, global-setup.ts, vite.config.ts, all specs) were NOT modified — CI owns stack bring-up only (D-01/D-02 phase boundary held)"
- "Reporter override --reporter=list,html kept: Gitea does not render 'github' annotations; list output is legible in the log"
patterns-established:
- "Harness step pattern: install browsers, then start API + wait for :3000/health, then run Playwright — all in one step"
- "Idempotent user seed step: INSERT IGNORE + upsert pattern for DEV_AUTH_BYPASS user before global-setup's FK-dependent seeds"
requirements-completed: [CI-01]
# Metrics
duration: ~2h (including CI iteration across 4 infrastructure fixes)
completed: 2026-06-11
---
# Phase 08 Plan 03: Harness CI Job Summary
**Gitea Actions harness job brings up MariaDB + API (DEV_AUTH_BYPASS) + Playwright Vite on every PR and runs 58 Phase 7 specs across iPhone/WebKit + Pixel/Chromium in 1.6 min — four infrastructure fixes required, no harness file modified**
## Performance
- **Duration:** ~2h (task authoring + 4 CI fix iterations)
- **Started:** 2026-06-11
- **Completed:** 2026-06-11
- **Tasks:** 2 auto + 1 checkpoint (human-verified)
- **Files modified:** 1 (.gitea/workflows/ci.yml)
## Accomplishments
- Harness job added to ci.yml: MariaDB 11 service → migrate → seed dev user → API background (DEV_AUTH_BYPASS=true, :3000) → Playwright starts Vite (:5173) → both iphone (WebKit) + pixel (Chromium) profiles → traces upload on failure
- 58 Phase 7 specs passed green on Gitea Actions run #11 (PR #3, pull_request) — cold CI stack, 1.6 min
- All four CI-side infrastructure fixes resolved without touching any Phase 7 harness file (phase boundary D-01/D-02 held)
- Artifact upload confirmed working: playwright-traces-10 downloaded from Gitea UI on run #10
## Task Commits
1. **Task 1: Add harness job — DB + migrate + API background + :3000 readiness** - `d55e347` (feat)
2. **Task 2: Add Playwright install + run (both profiles) + artifact upload** - `71c8909` (feat)
3. **Fix 1: Keep API alive during harness — start API + run e2e in one step** - `53a989c` (fix)
4. **Fix 2: Harness uses 127.0.0.1 + ipv4first — Vite is IPv4-only** - `7389740` (fix)
5. **Fix 3: Seed dev user id=1 — global-setup assumes it exists** - `e486c6b` (fix)
6. **Fix 4: Call pwa test:e2e directly so --reporter forwards cleanly** - `03e8088` (fix)
## Files Created/Modified
- `.gitea/workflows/ci.yml` — harness job added; fast-checks + api jobs unchanged
## Decisions Made
**D-08-03-COMBINE-STEP:** API start + readiness wait + `pnpm test:e2e` run combined into a single CI step. When the API was started with `node &` in a standalone step, the backgrounded process was reaped when that step exited — the multi-minute browser install that followed caused the API to die before the test step. Confirmed the API does not self-crash when left as a background child of the test shell.
**D-08-03-IPV4FIRST:** `PLAYWRIGHT_BASE_URL=http://127.0.0.1:5173` and `NODE_OPTIONS=--dns-result-order=ipv4first` set on the harness step. The Gitea runner resolves `localhost` to `::1` (IPv6) first; Vite binds IPv4-only (`127.0.0.1:5173`); Node `fetch` does not fall back to IPv4 unlike `curl`. Proven: `[::1]:5173 ECONNREFUSED` vs `127.0.0.1:5173 200`. The API is dual-stack so its `localhost:3000` references were unaffected.
**D-08-03-SEED-USER:** An idempotent "Seed dev user (id=1)" step runs after `db:migrate` and before the API starts. `DEV_AUTH_BYPASS` in `devBypass.ts` injects the user entirely in-memory — on a fresh CI database there is no `users` row, so the `global-setup.ts` `INSERT IGNORE INTO calendars` silently fails on the FK constraint and calendar id=10 is absent, causing a cascade FK error on `calendar_events`. The seed is `INSERT IGNORE INTO users (id, oidc_iss, oidc_sub, display_name, color) VALUES (1, 'dev', 'dev-user', 'Dev User', '#4A90D9')`.
**D-08-03-REPORTER-FORWARD:** The root `test:e2e` script is `pnpm --filter @familysync/pwa test:e2e`. Calling `pnpm test:e2e -- --reporter=list,html` from the root passes `--` through two pnpm layers, resulting in `playwright test -- --reporter=list,html` where `--reporter=list,html` is treated as a test-file path filter — Playwright finds no tests. Fix: call `pnpm --filter @familysync/pwa test:e2e --reporter=list,html` directly. Validated: 58 specs listed vs 0 with the broken invocation.
## Deviations from Plan
### Auto-fixed Issues (all Rule 3 — blocking)
**1. [Rule 3 - Blocking] API reaped at step boundary**
- **Found during:** CI run after Task 1+2 commits
- **Issue:** Bare `node apps/api/dist/index.js &` in an early step was reaped when that step exited. The browser install (multi-minute) ran next, then the test step found no API.
- **Fix:** Merged API start + curl :3000/health readiness loop + `pnpm test:e2e` into a single step; moved browser install to the step immediately before it.
- **Files modified:** .gitea/workflows/ci.yml
- **Committed in:** 53a989c
**2. [Rule 3 - Blocking] global-setup ECONNREFUSED on Vite :5173**
- **Found during:** CI run post fix 1
- **Issue:** `global-setup.ts` fetched `${PLAYWRIGHT_BASE_URL}/health`; `PLAYWRIGHT_BASE_URL` defaulted to `http://localhost:5173`; runner resolved `localhost``::1`; Vite bound only `127.0.0.1:5173``ECONNREFUSED`.
- **Fix:** Added `PLAYWRIGHT_BASE_URL: http://127.0.0.1:5173` and `NODE_OPTIONS: --dns-result-order=ipv4first` to the harness step env.
- **Files modified:** .gitea/workflows/ci.yml
- **Committed in:** 7389740
**3. [Rule 3 - Blocking] Missing dev user id=1 causes FK error in global-setup seed**
- **Found during:** CI run post fix 2
- **Issue:** `global-setup.ts` seeds `calendars` + `calendar_events` for `user_id=1`. `DEV_AUTH_BYPASS` injects that user in-memory only (no DB row). On a fresh CI DB, the `INSERT IGNORE INTO calendars` silently aborted on the `users` FK; calendar id=10 was absent; the `calendar_events` insert then failed on the calendars FK.
- **Fix:** Added a "Seed dev user" step after `db:migrate`: `INSERT IGNORE INTO users` with `id=1, oidc_iss='dev', oidc_sub='dev-user', display_name='Dev User', color='#4A90D9'`.
- **Files modified:** .gitea/workflows/ci.yml
- **Committed in:** e486c6b
**4. [Rule 3 - Blocking] --reporter flag treated as test-file filter**
- **Found during:** CI run post fix 3
- **Issue:** `pnpm test:e2e -- --reporter=list,html` from the workspace root double-forwarded `--` through two pnpm invocations, delivering `playwright test -- --reporter=list,html`; Playwright interpreted `--reporter=list,html` as a test-file path and found no tests.
- **Fix:** Changed invocation to `pnpm --filter @familysync/pwa test:e2e --reporter=list,html` — bypasses the root script delegation entirely.
- **Files modified:** .gitea/workflows/ci.yml
- **Committed in:** 03e8088
---
**Total deviations:** 4 auto-fixed (all Rule 3 — blocking CI failures). All were infrastructure/orchestration issues. No Phase 7 harness files (playwright.config.ts, global-setup.ts, vite.config.ts, or any spec) were modified.
## Verified CI Result
**Gitea Actions run #11** (PR #3, `pull_request` event) — conclusion **SUCCESS**
- **Harness result:** 58 passed in 1.6 min
- **Profiles:** iphone (WebKit) + pixel (Chromium), both passing
- **Co-running jobs:** fast-checks (191 PWA tests) + api (238 API tests) — all green in the same run
- **Artifact upload:** Confirmed working on run #10`playwright-traces-10` uploaded with a download URL via `ChristopherHX/gitea-upload-artifact@v4`
- **Phase boundary:** Zero Phase 7 files modified — confirmed via `git diff --quiet -- apps/pwa/playwright.config.ts apps/pwa/e2e/global-setup.ts apps/pwa/vite.config.ts`
## CI Stack Bring-Up Order (confirmed working)
1. `services: mariadb:11` container (DB_HOST=mariadb, Docker-executor mode)
2. `actions/checkout@v4` + `setup-node@v4` (Node 22) + `corepack enable pnpm`
3. `pnpm install --frozen-lockfile`
4. mysql2 readiness loop until mariadb port 3306 accepts connections
5. `pnpm --filter @familysync/api db:migrate` (never push — verified no `db:push` in ci.yml)
6. Seed dev user id=1 (INSERT IGNORE — idempotent)
7. `pnpm --filter @familysync/api build` → dist/index.js
8. `npx playwright install --with-deps webkit chromium` (from apps/pwa working-directory)
9. Combined step: `NODE_ENV=development DEV_AUTH_BYPASS=true ... node apps/api/dist/index.js &` → curl :3000/health readiness loop → `pnpm --filter @familysync/pwa test:e2e --reporter=list,html`
10. `if: failure()` — artifact upload via `ChristopherHX/gitea-upload-artifact@v4`
11. `if: always()` — kill API background process
## Issues Encountered
WebKit deps install: clean exit 0 — confirmed on this runner (D-PROBE-05 from plan 01, re-verified here). No issues encountered.
Reporter legibility: `list` reporter produced readable per-test output in the Gitea log; `html` report built but is only accessible via artifact download.
## Known Stubs
None.
## Threat Flags
None — no new network endpoints or auth paths introduced. The `DEV_AUTH_BYPASS=true` flag is scoped to the harness job only; it does not appear in the publish job (Plan 04). Threat mitigations T-08-06, T-08-07, T-08-08 confirmed implemented.
## Next Phase Readiness
- Plan 04 (publish job) is unblocked: harness green, CI-01 harness half complete
- ROADMAP CI-01 criteria 3 (failure gates merge) and 4 (readiness waits) satisfied
- Plan 04 needs `GITEA_REGISTRY_PAT` (deferred D-PROBE-08) — operator must create the PAT before the publish step can push to the Gitea container registry
---
*Phase: 08-gitea-ci*
*Completed: 2026-06-11*