- All four pnpm install steps now use --store-dir /pnpm-store --prefer-offline
- harness job env adds PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
- Updated stale D-PROBE-04 comments to reflect persistent store
- Playwright install step gets a comment noting the future runner-image optimization
dorny/paths-filter combines patterns with Array.some, and picomatch compiles
'!.gitea/**' as 'any path not under .gitea'. The '- !.gitea/**' line (from
quick task 260613-dmw) therefore matched every non-.gitea file — including
.planning/** and *.md — so code=true for doc-only PRs and the heavy api/harness
jobs ran anyway. It also never excluded .gitea (the **/*.yml glob already
matched workflow files), regressing the Phase 15 doc-only skip.
Switch to predicate-quantifier 'every' with negation-only globs: a file counts
as code only if it is outside .gitea/, outside .planning/, and not Markdown.
Verified with picomatch against representative file sets.
Add paths-ignore ['.gitea/**', '.planning/**'] to publish.yml's push
trigger. Doc/CI-only pushes to main (planning docs push straight to main;
workflow edits merge via PR) produce a byte-identical image (.dockerignore
already excludes .planning), so the build + :latest re-push was wasted.
paths-ignore skips only when every changed file matches; mixed code+docs
pushes still publish.
Workflow-only edits shouldn't pay for the multi-minute MariaDB/integration/harness
suite. A negation '- !.gitea/**' (ordered after the yml/yaml globs) makes CI-config-
only PRs resolve code=false so api/harness skip like docs. fast-checks (format:check
still validates the workflow YAML) + gate continue to gate them. PRs that also touch
app code or lockfile still resolve code=true and run the full suite.
WR-02: capture docker run exit directly (not the piped head exit) so a
chatty booting image can't SIGPIPE to 141 and false-PASS; require the
FATAL guard marker in output as a positive assertion.
WR-05: strip comment lines and use anchored fixed-string (grep -qF)
matching so a commented-out rule can't satisfy the hygiene check and
patterns aren't treated as regexes.
Bind github.base_ref/base.sha/head.sha through env: and reference quoted
shell vars (no inline ${{ }} in run:) to close the script-injection vector.
Add a HEAD_SHA rev-parse fallback mirroring BASE_SHA and echo the final
${BASE_SHA}..${HEAD_SHA} range before gitleaks.
- Static assertions step: verifies .dockerignore exists, covers all forbidden
patterns (.env, node_modules, apps/api/scripts, .git, .planning, apps/api/tests,
apps/pwa/e2e), and --target production is still pinned in publish.yml (T-16-19)
- Boot-smoke step: runs freshly-built sha_tag image with NODE_ENV=production +
DEV_AUTH_BYPASS=true; fails if image starts (EXIT==0) or times out (EXIT==124),
passes on any other non-zero exit — proves D-08 guard fires in shipped image (T-16-18)
- Both steps ordered after build and before push; a hygiene failure blocks publish (T-16-20)
- Separate 'Build and push' into 'Build production image' (build only) and 'Push image' steps
- Creates insertion point between build and push for hygiene assertions (Task 2)
- Preserves --target production, Dockerfile path, both tags, and root context
- WR-04 immutable-tag-first push ordering preserved in push step
- gate needs: expanded to include security
- individual needs.security.result check added (requires success, not success-or-skipped)
- security not folded into api/harness for loop — gitleaks always runs so it cannot be skipped
- follows Gitea #31007 individual needs.X.result pattern
- Replace "three required checks (CI / fast-checks, CI / api, CI / harness)"
with the new gating surface: CI / fast-checks + CI / gate
- Note that CI / api and CI / harness are conditionally skipped on doc-only PRs
and gated via the always-running CI / gate aggregate
- Comment-only change; no job/step/env/trigger modified
- gate job needs [fast-checks, changes, api, harness] with if: always()
- gate fails (exit 1) when fast-checks != success
- gate accepts success OR skipped for api and harness, fails on any other result
- uses individual needs.X.result checks (not wildcard) — Gitea 1.26.2 bug #31007
- once merged, emits CI / gate commit-status required by Plan 03 branch-protection update
- insert changes job (dorny/paths-filter@v4) before fast-checks
- changes job: permissions pull-requests:read, outputs code, no checkout
- code filter lists positive patterns: **/*.ts, apps/**, pnpm-lock.yaml, Dockerfile, etc.
- api job: needs [changes] + if combined with needs.changes.outputs.code == 'true'
- harness job: same needs/if pattern as api
- services, env, and step bodies unchanged in both heavy jobs
- Fix MD040 (11 bare fences): add language tags (text/bash) across 7 files
- Fix MD031 (2 violations): add blank lines around fence in GETTING-STARTED.md
- Wire 'Markdown lint' step to fast-checks job (after Format check, before Typecheck)
- Reformat .markdownlint-cli2.jsonc per Prettier (trailing commas in JSONC)
- pnpm md:lint exits 0; pnpm format:check exits 0; gate can fail on bare fence (verified)
- Updated calendar.spec.ts header to list all three profiles (iphone/pixel/desktop)
- Updated lists.spec.ts header to list all three profiles (iphone/pixel/desktop)
- Updated e2e/README.md preamble to add 'Desktop Chrome (1280x720)'
- Added --project=desktop example to README run-commands block
- Updated README full-suite command comment to name all three profiles
- Cosmetic: ci.yml step-name and comment updated to mention desktop (no plumbing change)
- Full suite verified: 85 passed, 5 skipped (3 desktop geometry + 2 parity guards), 0 failed
- Insert 'Format check: pnpm format:check' after Lint in fast-checks job
- Remove the 3-line comment that said lint was a no-op (no longer true)
- Step order: Install → Lint → Format check → Typecheck → PWA unit tests
- Branch-protection contexts (name:CI, fast-checks, api, harness) unchanged
- Create .gitea/workflows/publish.yml (push-to-main only, name=Publish)
- Strip publish job, push trigger, and MILESTONE env from ci.yml
- Eliminates orphaned CI / publish (pull_request) status on PRs
- Preserves all three required PR status contexts unchanged
The registry PAT secret had to be named REGISTRY_PAT (Gitea forbids the GITEA_
prefix for secret names). Point the publish docker-login at secrets.REGISTRY_PAT.
- 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)
Run #10 cleared global-setup but playwright errored 'No tests found':
`pnpm test:e2e -- --reporter=list,html` routes through two pnpm script layers
(root→pwa) and the `--` survives into `playwright test -- --reporter=list,html`,
where playwright treats --reporter as a test-file filter. Call the pwa script
directly and append the flag without `--` (validated: 58 specs list vs 0).
global-setup.ts seeds calendars/lists/events for user_id=1 but never creates
the user (DEV_AUTH_BYPASS injects DEV_USER in-memory only). On a fresh CI DB the
calendars INSERT IGNORE is silently skipped on the users FK → calendar 10 missing
→ calendar_events insert fails FK. Add an idempotent users(id=1) seed after
migrate (validated locally: full insert chain passes). No harness files changed.
global-setup polls baseURL/health via Node fetch; the runner resolves localhost
to ::1 first, but Vite binds IPv4-only (127.0.0.1:5173), so localhost→::1:5173
→ ECONNREFUSED → health poll never returns 200. (curl passed for :3000 because
curl falls back IPv4↔IPv6 and the API is dual-stack; Node fetch does not.)
Proven: [::1]:5173 ECONNREFUSED vs 127.0.0.1:5173 200. Point PLAYWRIGHT_BASE_URL
at 127.0.0.1 and add --dns-result-order=ipv4first. No harness files changed.
Run #7 harness failed: global-setup polled :5173/health (Vite proxy → :3000)
and never got 200. The API connected to the DB and :3000/health was green
during the separate 'Wait for API' step, but the bare-backgrounded node
process was reaped at the step boundary and was dead by the time the e2e step
ran (after the multi-minute browser install). Confirmed locally the API does
not self-crash (alive + healthy for 75s in-shell).
Install browsers first, then start the API and run Playwright in a SINGLE step
so the API stays a child of the test shell for the whole run; capture the test
exit code and kill the API after. No harness files touched.
- Add harness job to ci.yml (ubuntu-latest, pull_request, parallel with fast-checks + api)
- MariaDB 11 service container with healthcheck.sh readiness (same pattern as api job)
- mysql2 readiness poll (no mysql CLI in runner image, D-PROBE-03)
- db:migrate via drizzle-kit (never db:push, T-08-07)
- pnpm --filter @familysync/api build before starting (Pitfall 4)
- API background: DEV_AUTH_BYPASS=true inline on node line (Pitfall 8), NODE_ENV=development
- curl retry loop on localhost:3000/health, 60s deadline, kill+exit on timeout (D-02/T-08-08)
- api job: runs-on ubuntu-latest, if pull_request, parallel with fast-checks (no needs:)
- services: mariadb:11 with healthcheck.sh --connect --innodb_initialized options
(--health-start-period=30s for MariaDB 11 InnoDB cold-start, --health-retries=10)
- DB_HOST: mariadb (Docker-executor confirmed by D-PROBE-02)
- Throwaway creds: familysync/testpass scoped to ephemeral service container (T-08-03)
- No actions/cache (D-PROBE-04)
- Node mysql2 readiness poll via --input-type=commonjs inline script, 90s deadline
(no mysql CLI in runner image per D-PROBE-03; Pitfall 11 belt-and-suspenders)
- db:migrate (drizzle-kit migrate); drizzle push never used (T-08-04, MariaDB unsafe)
- pnpm --filter @familysync/api test: full DB-backed API test suite
- on: pull_request + push branches:[main]; workflow env MILESTONE: v1.1
- fast-checks job: runs-on ubuntu-latest, if pull_request
- Node 22 via actions/setup-node@v4 + corepack enable pnpm
- No actions/cache (D-PROBE-04: times out on this runner)
- pnpm install --frozen-lockfile, lint (no-op), typecheck, PWA unit tests
- DB-backed pnpm test intentionally absent from this job
The act_runner advertises ubuntu-latest/ubuntu-24.04/ubuntu-22.04; runs-on:
self-hosted matched no runner and the probe job stayed queued. Switch the
probe (and the plan key-link) to ubuntu-latest. ci.yml (Plans 02-04) must
use the same label.
- Probe-only workflow triggering on gsd/phase-08-gitea-ci branch only
- Answers P-01..P-11 + P-13: Node version, pnpm, runner mode (critical
fork Docker vs host), Docker socket, MariaDB service container spawn
and reachability on both hostnames, actions/cache, Playwright WebKit
deps, gitea-upload-artifact fork, and GITHUB_SHA short-SHA expression
- Uses healthcheck.sh --connect --innodb_initialized for MariaDB (never
the binary removed from mariadb:11 — Pitfall 11)
- Uses ChristopherHX/gitea-upload-artifact@v4 (not the official action
which aborts on Gitea with GHES detection — Pitfall 6 / T-08-SC)
- P-12 (docker login) deferred to Plan 04 — probe never references any
secret (T-08-01 compliant)
- All steps that may fail use continue-on-error: true so probe reports
findings instead of red-failing on expected unknowns