3 Commits
Author SHA1 Message Date
Lucas Berger dcf42423a5 chore(08): remove throwaway runner-probe workflow before merge to main
CI / fast-checks (pull_request) Successful in 50s
CI / api (pull_request) Successful in 56s
CI / harness (pull_request) Successful in 3m27s
CI / publish (pull_request) Has been skipped
Probe answers are recorded in 08-01-SUMMARY; the probe is no longer needed and
should not live on main (it was workflow_dispatch-only/inert anyway).
2026-06-11 15:57:49 -04:00
Lucas Berger ebcc38d810 feat(08-04): publish job — build + push API image on merge to main
- Add publish job gated on push to refs/heads/main (never pull_request)
- docker login via --password-stdin with secrets.GITEA_REGISTRY_PAT (Pitfall 13)
- docker build --target production -f apps/api/Dockerfile . (repo-root context, T-08-10)
- Push :latest and :${MILESTONE}-${SHORT_SHA} tags per D-04
- docker logout in always() step to drop credential after push
- No dev-bypass flag in publish job (T-08-09 boundary)
2026-06-11 15:56:37 -04:00
Lucas Berger 212d8c1691 docs(08-03): SUMMARY — harness green, 58 specs both profiles on cold CI stack
- Run #11 (PR #3): 58 passed in 1.6 min (iphone/WebKit + pixel/Chromium)
- 4 infrastructure fixes: API-reap at step boundary, IPv4-first for Vite, dev-user FK seed, reporter double-forward via pnpm
- No Phase 7 harness file modified (phase boundary D-01/D-02 held)
- Advance position to 08-04 (publish job)
2026-06-11 15:52:15 -04:00
5 changed files with 235 additions and 240 deletions
+48
View File
@@ -310,3 +310,51 @@ jobs:
name: playwright-traces-${{ github.run_id }}
path: apps/pwa/test-results/
retention-days: 14
publish:
runs-on: ubuntu-latest
# Push to main only — never on pull_request (D-03). No dev-bypass flag in this job (T-08-09).
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
# Compute both image tags per D-04:
# :latest — moving pointer for easy pulls
# :<milestone>-<shortsha> — immutable, rollback-traceable (e.g. v1.1-4303a1b)
# GITHUB_SHA is confirmed available in Gitea Actions (probe P-13).
# MILESTONE is read from the workflow-level env var (set to v1.1 above) — update at milestone boundaries.
- name: Compute image tags
id: tags
run: |
SHORT_SHA=${GITHUB_SHA:0:7}
MILESTONE="${{ env.MILESTONE }}"
echo "latest=git.bergerhouse.net/luckberg/familysync-api:latest" >> $GITHUB_OUTPUT
echo "sha_tag=git.bergerhouse.net/luckberg/familysync-api:${MILESTONE}-${SHORT_SHA}" >> $GITHUB_OUTPUT
# Pitfall 13 (load-bearing security step): PAT piped via stdin — never via -p/--password.
# GITEA_TOKEN/GITHUB_TOKEN cannot push packages; a PAT with write:package scope is required
# (confirmed: Gitea forum + registry docs). Token is masked by Gitea's secret-log scrubber
# and never echoed elsewhere or set as a plain env var.
- name: Docker login
run: |
echo "${{ secrets.GITEA_REGISTRY_PAT }}" | \
docker login git.bergerhouse.net \
--username luckberg \
--password-stdin
# Build from REPO ROOT (T-08-10): the Dockerfile copies the pnpm workspace manifest +
# lockfile from the root context; building from apps/api/ would fail to find them.
- name: Build and push
run: |
docker build --target production \
-f apps/api/Dockerfile \
-t ${{ steps.tags.outputs.latest }} \
-t ${{ steps.tags.outputs.sha_tag }} \
.
docker push ${{ steps.tags.outputs.latest }}
docker push ${{ steps.tags.outputs.sha_tag }}
# Always drop the stored credential from the runner after push (defence in depth).
- name: Docker logout
if: always()
run: docker logout git.bergerhouse.net || true
-228
View File
@@ -1,228 +0,0 @@
name: runner-probe
# Probe-only workflow — answers all runner unknowns before real CI is designed.
# Runs ONLY on the feature branch; never on main.
# All steps that may fail on this runner use continue-on-error: true or || true
# so the probe REPORTS findings instead of red-failing on an expected unknown.
# Do NOT reference any secret in this workflow (T-08-01).
#
# Checks performed: P-01 through P-11 + P-13
# P-12 (docker login) is deferred to Plan 04 (publish) — DO NOT add it here.
# Probe served its purpose (run #2 recorded the fork answers in 08-01-SUMMARY).
# Switched to manual-only so it no longer re-runs on every push and competes
# with ci.yml on the single runner. Throwaway — remove before merge to main.
on:
workflow_dispatch:
jobs:
probe:
name: runner-probe
# Runner advertises ubuntu-latest/ubuntu-24.04/ubuntu-22.04 (no 'self-hosted' label).
# Probe confirmed `runs-on: self-hosted` matched no runner and the job stayed queued.
runs-on: ubuntu-latest
# P-05: Service container — probe whether it spawns and on which hostname.
# Uses healthcheck.sh --connect --innodb_initialized.
# Note: healthcheck.sh is used because the binary from older clients was
# removed from mariadb:11 (Pitfall 11).
services:
mariadb:
image: mariadb:11
env:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: familysync
MARIADB_USER: familysync
MARIADB_PASSWORD: testpass
options: >-
--health-cmd="healthcheck.sh --connect --innodb_initialized"
--health-interval=10s
--health-timeout=5s
--health-retries=10
--health-start-period=30s
steps:
# ─── P-07: actions/checkout ──────────────────────────────────────────────
# Must be first real step. Reaching subsequent steps proves it resolves.
- name: P-07 checkout (actions/checkout@v4)
uses: actions/checkout@v4
# ─── P-08: actions/setup-node ────────────────────────────────────────────
- name: P-08 setup-node (actions/setup-node@v4 — pin Node 22)
uses: actions/setup-node@v4
with:
node-version: '22'
# ─── P-01: Node.js version ───────────────────────────────────────────────
- name: P-01 Node.js version
run: |
echo "=== P-01: Node.js version ==="
node --version
echo "Expected: v22.x"
# ─── P-02: pnpm availability ─────────────────────────────────────────────
- name: P-02 pnpm availability
run: |
echo "=== P-02: pnpm availability ==="
pnpm --version 2>/dev/null || (
echo "pnpm not found — attempting corepack activation"
corepack enable pnpm
pnpm --version
)
echo "Expected: pnpm 11.x (from packageManager field in package.json)"
# ─── P-03: Runner mode — THE critical fork ───────────────────────────────
# Docker-executor mode: job runs inside a Docker container
# → services: works; DB_HOST=mariadb
# Host-executor mode: job runs directly on the Unraid host
# → services: not supported; must use docker run -d fallback
- name: P-03 runner mode detection (Docker vs host — critical fork)
run: |
echo "=== P-03: Runner mode (critical fork) ==="
echo "--- /proc/1/cgroup (first 5 lines) ---"
cat /proc/1/cgroup 2>/dev/null | head -5 || echo "(not readable)"
echo "--- hostname ---"
hostname
echo "--- /.dockerenv presence ---"
ls -la /.dockerenv 2>&1
echo "--- Conclusion ---"
if [ -f /.dockerenv ]; then
echo "RUNNER MODE: Docker-executor (job is running inside a Docker container)"
echo " → services: will work; MariaDB hostname = service label name (mariadb)"
echo " → DB_HOST=mariadb in downstream jobs"
else
echo "RUNNER MODE: host-executor (job is running directly on the host)"
echo " → services: NOT supported (nektos/act#2711)"
echo " → Downstream jobs must use: docker run -d mariadb:11 + explicit readiness loop"
echo " → DB_HOST=127.0.0.1 with -p 3306:3306 in docker run"
fi
# ─── P-04: Docker socket access ──────────────────────────────────────────
- name: P-04 Docker socket access
continue-on-error: true
run: |
echo "=== P-04: Docker socket access ==="
docker info 2>&1 | head -20
echo "--- docker ps (first few lines) ---"
docker ps 2>&1 | head
# ─── P-05: Service container spawn check ─────────────────────────────────
- name: P-05 service container spawn (mariadb:11 visible in docker ps?)
continue-on-error: true
run: |
echo "=== P-05: Service container spawn ==="
docker ps 2>&1 | grep -i maria \
&& echo "RESULT: MariaDB service container IS visible in docker ps (Docker-executor mode confirmed)" \
|| echo "RESULT: no mariadb container visible in docker ps (likely host-executor mode — services: not supported)"
# ─── P-06: MariaDB reachability — try BOTH hostnames ─────────────────────
# Does NOT fail the job: records which hostname resolves.
- name: P-06 MariaDB reachability (hostname=mariadb — Docker mode)
continue-on-error: true
run: |
echo "=== P-06a: MariaDB via hostname 'mariadb' (Docker-executor mode) ==="
mysql -h mariadb -P 3306 -u familysync -ptestpass -e "SELECT 1" 2>&1 | head \
&& echo "P-06a RESULT: mariadb hostname RESOLVES — Docker mode" \
|| echo "P-06a RESULT: mariadb hostname DOES NOT resolve (expected if host-executor mode)"
- name: P-06 MariaDB reachability (hostname=127.0.0.1 — host mode)
continue-on-error: true
run: |
echo "=== P-06b: MariaDB via 127.0.0.1 (host-executor mode fallback) ==="
mysql -h 127.0.0.1 -P 3306 -u familysync -ptestpass -e "SELECT 1" 2>&1 | head \
&& echo "P-06b RESULT: 127.0.0.1 RESOLVES — host mode" \
|| echo "P-06b RESULT: 127.0.0.1 does not resolve (expected if Docker-executor mode uses 'mariadb' hostname)"
# ─── P-09: actions/cache ─────────────────────────────────────────────────
# Known issue: cache server runs in runner container; job container on different
# network may cause socket hang-up. continue-on-error so probe reports finding.
- name: P-09 cache action (actions/cache@v4 — may time out in Docker mode)
uses: actions/cache@v4
continue-on-error: true
with:
path: /tmp/probe-cache-test
key: runner-probe-cache-test-${{ github.sha }}
- name: P-09 cache result
run: |
echo "=== P-09: Cache action result ==="
echo "If the previous 'cache' step completed without hanging, cache IS usable."
echo "If it timed out or errored, fall back to no-cache in downstream jobs."
# ─── P-10: Playwright WebKit system deps ────────────────────────────────
# Confirms WebKit system deps install without sudo/apt failure (Assumption A10).
# Runs from apps/pwa where @playwright/test is installed.
- name: P-10 Playwright WebKit + Chromium system deps
continue-on-error: true
working-directory: apps/pwa
run: |
echo "=== P-10: Playwright browser system deps (webkit + chromium) ==="
npx playwright install --with-deps webkit chromium 2>&1 | tail -30
echo "--- Exit code: $? ---"
echo "If the above shows installed without 'sudo' or 'apt' errors, WebKit deps are OK."
# ─── P-11: gitea-upload-artifact fork ───────────────────────────────────
# The official upload-artifact action is broken on Gitea (detected as GHES, aborts).
# Use ChristopherHX/gitea-upload-artifact@v4 — the confirmed Gitea fix (RESEARCH T-08-SC).
- name: P-11 write dummy artifact for upload test
run: |
echo "=== P-11: Upload artifact test ==="
mkdir -p /tmp/probe-artifact
echo "runner-probe artifact: sha=${GITHUB_SHA}" > /tmp/probe-artifact/probe.txt
cat /tmp/probe-artifact/probe.txt
- name: P-11 artifact upload (ChristopherHX/gitea-upload-artifact@v4)
uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4
continue-on-error: true
with:
name: runner-probe-artifact
path: /tmp/probe-artifact/
retention-days: 3
# ─── P-13: GITHUB_SHA short SHA expression ───────────────────────────────
- name: P-13 short SHA expression (D-04 tag validation)
run: |
echo "=== P-13: GITHUB_SHA short SHA ==="
echo "GITHUB_SHA full = ${GITHUB_SHA}"
echo "short sha = ${GITHUB_SHA:0:7}"
echo "Expected: a 7-character hex string — confirms D-04 tag expression works"
if [ "${#GITHUB_SHA}" -ge 7 ]; then
echo "P-13 RESULT: OK — GITHUB_SHA is available and bash substring works"
else
echo "P-13 RESULT: WARN — GITHUB_SHA is shorter than expected or empty"
fi
# ─── SUMMARY: one-line verdict per fork ──────────────────────────────────
# Plans 0204 consume these answers to pick the correct implementation path.
- name: SUMMARY — fork verdicts (record answers for SUMMARY.md)
run: |
echo "========================================================"
echo " RUNNER PROBE SUMMARY — FORK ANSWERS"
echo "========================================================"
echo ""
echo "P-03 Runner mode:"
if [ -f /.dockerenv ]; then
echo " DOCKER-EXECUTOR — job runs in a container"
echo " → services: works; DB_HOST=mariadb"
else
echo " HOST-EXECUTOR — job runs directly on host"
echo " → services: NOT supported; use docker run -d + DB_HOST=127.0.0.1"
fi
echo ""
echo "P-05/P-06 MariaDB service container:"
MARIA_CONTAINER=$(docker ps 2>/dev/null | grep -i maria | head -1 || true)
if [ -n "$MARIA_CONTAINER" ]; then
echo " Service container IS visible — hostname 'mariadb' should resolve"
else
echo " Service container NOT visible — use docker run -d fallback"
fi
echo ""
echo "P-09 Cache: see 'cache' step result above (continue-on-error — pass = usable)"
echo "P-10 WebKit deps: see 'playwright install' step above (continue-on-error)"
echo "P-11 Upload artifact: see 'gitea-upload-artifact' step above (continue-on-error)"
echo ""
echo "P-13 Short SHA: ${GITHUB_SHA:0:7}"
echo ""
echo " SECURITY CHECK: this probe references NO secrets (T-08-01 compliant)"
echo " P-12 (docker login/push) is deferred to Plan 04 — NOT in this probe."
echo "========================================================"
+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*