docs(08): Phase 8 close-out — SUMMARY + VERIFICATION + tracking #4

Merged
luckberg merged 1 commits from gsd/phase-08-gitea-ci into main 2026-06-11 16:30:10 -04:00
5 changed files with 185 additions and 23 deletions
+4 -4
View File
@@ -39,8 +39,8 @@ Each requirement maps to exactly one roadmap phase (see Traceability).
### CI — Gitea continuous integration
- [ ] **CI-01**: Every pull request targeting `main` runs full regression — lint, typecheck (both apps), unit tests, API integration tests against a MariaDB service container, **and the Phase 7 mobile Playwright harness as a UI-regression step (CI brings up the API + PWA dev servers + MariaDB service container with `DEV_AUTH_BYPASS` in the runner and runs the harness specs headlessly against the authed PWA)** — and the result gates the merge.
- [ ] **CI-02**: On merge to `main`, the API Docker image is built and published to the Gitea container registry.
- [x] **CI-01**: Every pull request targeting `main` runs full regression — lint, typecheck (both apps), unit tests, API integration tests against a MariaDB service container, **and the Phase 7 mobile Playwright harness as a UI-regression step (CI brings up the API + PWA dev servers + MariaDB service container with `DEV_AUTH_BYPASS` in the runner and runs the harness specs headlessly against the authed PWA)** — and the result gates the merge.
- [x] **CI-02**: On merge to `main`, the API Docker image is built and published to the Gitea container registry.
### Test — Mobile-emulated authed browser harness
@@ -73,8 +73,8 @@ Maps each REQ-ID to its phase. v1.1 phases continue v1.0 numbering (v1.0 ended a
|--------|-------|--------|
| TEST-01 | Phase 7 (Mobile Test Harness) | Complete |
| TEST-02 | Phase 7 (Mobile Test Harness) | Complete |
| CI-01 | Phase 8 (Gitea CI) | In progress |
| CI-02 | Phase 8 (Gitea CI) | Pending |
| CI-01 | Phase 8 (Gitea CI) | Complete |
| CI-02 | Phase 8 (Gitea CI) | Complete |
| CAL-15 | Phase 9 (Faster Write-Back) | Pending |
| ADMIN-01 | Phase 10 (Admin Role & Settings) | Pending |
| ADMIN-02 | Phase 10 (Admin Role & Settings) | Pending |
+3 -3
View File
@@ -26,7 +26,7 @@ Full phase detail archived in [`milestones/v1.0-ROADMAP.md`](milestones/v1.0-ROA
Make FamilySync configurable, administrable, and maintainable for real multi-member use — without hand-editing env files or the database. The new critical path runs **mobile test harness → Gitea CI** (CI consumes the harness specs for UI regression), and the **admin role → reminders / setup wizard** chain (a single `/api/admin` + `/api/setup` route surface carrying the v1.1 DB migration). Faster write-back is a fully independent track.
- [x] **Phase 7: Mobile Test Harness** - Mobile-emulated, authenticated PWA browser harness so the assistant (and CI) can catch mobile-only defects (completed 2026-06-11)
- [ ] **Phase 8: Gitea CI** - Full regression on PR to main (lint/typecheck/unit/API-integration vs a MariaDB service container **+ the Phase 7 mobile harness as a UI-regression step against a CI-hosted dev stack**) + Docker image publish on merge
- [x] **Phase 8: Gitea CI** - Full regression on PR to main (lint/typecheck/unit/API-integration vs a MariaDB service container **+ the Phase 7 mobile harness as a UI-regression step against a CI-hosted dev stack**) + Docker image publish on merge (completed 2026-06-11)
- [ ] **Phase 9: Faster Write-Back** - Event-driven outbox drain so edits land in ~1-2s instead of ~15s, preserving every outbox durability guarantee
- [ ] **Phase 10: Admin Role & Settings** - DB foundation (is_admin / reminder_lead / app_config) + role-gated admin UI to rotate app passwords and designate the shared calendar
- [ ] **Phase 11: Per-Event Reminders** - Reminder selector on the event form (incl. "None") serialized as VALARM, with a variable-lead scheduler that honors each event's choice
@@ -109,7 +109,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
**Wave 4** *(blocked on Wave 3 completion)*
- [ ] 08-04-PLAN.md — ci.yml: publish job (build production image + push :latest + :v1.1-<sha> via --password-stdin)
- [x] 08-04-PLAN.md — ci.yml: publish job (build production image + push :latest + :v1.1-<sha> via --password-stdin)
**UI hint**: yes
@@ -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 | 3/4 | In Progress| |
| 8. Gitea CI | v1.1 | 4/4 | Complete | 2026-06-11 |
| 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 | - |
+17 -16
View File
@@ -2,16 +2,16 @@
gsd_state_version: 1.0
milestone: v1.1
milestone_name: Operability & Polish
status: executing
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"
status: phase-complete
stopped_at: Phase 08 complete — all 4 plans executed, CI-01 + CI-02 delivered, publish job verified green (run #14)
last_updated: "2026-06-11T22:00:00.000Z"
last_activity: "2026-06-11 -- 08-04 complete; publish job green (run #14, merge commit 98acff8): both image tags pushed (familysync-api:latest + :v1.1-98acff8), PAT masked, --password-stdin confirmed. REGISTRY_PAT naming fix (73eecf7). Phase 8 (Gitea CI) complete — all 6 ROADMAP criteria met."
progress:
total_phases: 16
completed_phases: 1
completed_phases: 2
total_plans: 8
completed_plans: 6
percent: 6
completed_plans: 8
percent: 12
---
# Project State
@@ -25,10 +25,10 @@ See: .planning/PROJECT.md (updated 2026-06-10)
## Current Position
Phase: 08 (gitea-ci) — EXECUTING
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
Phase: 08 (gitea-ci) — COMPLETE
Plan: 4 of 4 (08-04 complete)
Status: Phase 08 complete — all 4 plans executed, CI-01 + CI-02 delivered
Last activity: 2026-06-11 -- 08-04 complete; publish job green (run #14): both tags pushed, PAT masked, REGISTRY_PAT naming fix applied. Phase 8 fully complete.
## Performance Metrics
@@ -98,6 +98,7 @@ Recent decisions affecting current work:
- D-PROBE-06 (2026-06-11, 08-01): ChristopherHX/gitea-upload-artifact@v4 works — MUST use this fork; actions/upload-artifact@v4 broken on Gitea.
- D-PROBE-07 (2026-06-11, 08-01): ${GITHUB_SHA:0:7} produces 7 chars — D-04 publish tag expression valid.
- D-PROBE-08 (2026-06-11, 08-01): GITEA_REGISTRY_PAT deferred to Plan 04; PAT not exercised in probe.
- D-PAT-NAMING (2026-06-11, 08-04): Gitea runner silently drops secrets with the `GITEA_` prefix (reserved namespace). Secret renamed from GITEA_REGISTRY_PAT → REGISTRY_PAT in both repo secret and ci.yml (commit 73eecf7). Use REGISTRY_PAT in any future registry operations.
- CAL-08 RESOLVED → GO (Phase 1): per-member Fastmail app password reaches all of that account's calendars; no cross-account ACL needed. Unified view stands; no shared-only fallback. See CAL-08-DECISION.md.
- D-14 (2026-06-04): Phase 1 Gate 2 (live Authelia/Pangolin) deferred. SSE-over-Pangolin smoke = hard gate before Phase 4; live AUTH smoke incl. iOS standalone-PWA folded into Phase 3. Phases 23 build behind a dev-auth bypass. Tracked in 01-HUMAN-UAT.md + docs/deployment.md.
- D-15 (2026-06-04): Validate real topology via local Newt connector + test subdomain through Pangolin (Mode A), not an Unraid deploy; Unraid reserved for go-live.
@@ -199,11 +200,11 @@ Recent decisions affecting current work:
## Session Continuity
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
Last session: 2026-06-11T22:00:00.000Z
Stopped at: Phase 08 complete — CI-01 + CI-02 delivered, publish job verified, SUMMARY + VERIFICATION written
Resume file: None — start Phase 09 with /gsd-plan-phase 9
## Operator Next Steps
- Review the reordered v1.1 roadmap in `.planning/ROADMAP.md` (Phases 712).
- Plan the first phase with `/gsd-plan-phase 7` (Mobile Test Harness — independent, goes first; Phase 8 CI runs its specs). Alternatives that can start in parallel: `/gsd-plan-phase 9` (Faster Write-Back — independent, lowest risk) or `/gsd-plan-phase 10` (Admin Role & Settings — carries the DB migration that Phases 11 & 12 depend on).
- **Phase 8 is complete.** CI pipeline is fully operational on the self-hosted Gitea runner.
- Next: `/gsd-plan-phase 9` (Faster Write-Back — fully independent, lowest risk) or `/gsd-plan-phase 10` (Admin Role & Settings — carries the v1.1 DB migration that Phases 11 & 12 depend on). These can run in parallel once planned.
@@ -0,0 +1,111 @@
---
phase: 08-gitea-ci
plan: "04"
subsystem: infra
tags: [gitea, docker, ci, registry, publish, security]
requires:
- phase: 08-03
provides: harness job in ci.yml; dev-stack bring-up confirmed in CI
provides:
- publish job in .gitea/workflows/ci.yml (push to main → build + push API Docker image)
- familysync-api:latest and familysync-api:v1.1-<7charsha> tags in Gitea registry
- --password-stdin docker login pattern (REGISTRY_PAT secret, never plaintext)
affects: [phase-09, phase-10, phase-11, phase-12]
tech-stack:
added: []
patterns:
- "docker login --password-stdin (PAT piped via stdin; -p flag forbidden)"
- "dual-tag publish: :latest + :<milestone>-<shortsha> (D-04 traceability)"
- "docker build from repo root: -f apps/api/Dockerfile . (Dockerfile requires workspace manifest)"
- "docker logout in always() step to drop credential from runner after push"
- "Gitea secret prefix exclusion: REGISTRY_PAT not GITEA_REGISTRY_PAT (Gitea reserves GITEA_ prefix)"
key-files:
created: []
modified:
- .gitea/workflows/ci.yml
key-decisions:
- "D-PAT-NAMING: Gitea runner silently ignores secrets with the GITEA_ prefix; secret renamed from GITEA_REGISTRY_PAT to REGISTRY_PAT in both the Gitea repo secret and ci.yml (commit 73eecf7). This is the load-bearing fix that unblocked the publish job."
- "D-SHELL-FORM: Used shell docker login/build/push (not docker/login-action or docker/build-push-action) — matches 08-RESEARCH §Pattern 6 recommendation; simpler and has no marketplace action dependency."
- "D-DUAL-TAG: Two docker push calls for :latest and :v1.1-<shortsha>; same digest, two tags. Traceability tag links image to the exact merge commit."
- "D-LOGOUT: docker logout in always() step — drops stored credential from runner regardless of push success/failure (security hygiene)."
requirements-completed: [CI-02]
duration: checkpoint-verified
completed: "2026-06-11"
---
# Phase 08 Plan 04: Publish Job Summary
**Publish job wired to push-to-main: API Docker production image pushed to Gitea registry under :latest and :v1.1-<7charsha>, PAT authenticated via --password-stdin (never plaintext), security audit passed — CI-02 delivered.**
## Performance
- **Duration:** multi-step (Task 1 executed, Task 2 was a human-verify checkpoint — verified green on run #14)
- **Started:** 2026-06-11
- **Completed:** 2026-06-11
- **Tasks:** 2 (1 auto + 1 human-verify checkpoint)
- **Files modified:** 1
## Accomplishments
- Added the `publish` job to `.gitea/workflows/ci.yml`: gated on `github.event_name == 'push' && github.ref == 'refs/heads/main'`, never fires on pull_request.
- Run #14 (merge commit 98acff8 pushed to main) confirmed SUCCESS (1m): docker login succeeded, both image tags pushed (same digest sha256:ce724852…) — `familysync-api:latest` and `familysync-api:v1.1-98acff8` present in Gitea Packages API.
- Security audit of the CI log passed: PAT masked as `***` throughout (Gitea secret scrubber); `--password-stdin` used exclusively; no `-p`/`--password` form anywhere; `docker logout` ran in the `always()` cleanup step. No plaintext token anywhere in the log.
- Identified and fixed the GITEA_-prefix secret naming bug: Gitea runner silently drops secrets whose names start with `GITEA_`; renamed secret from `GITEA_REGISTRY_PAT` to `REGISTRY_PAT` in both the repo secret and ci.yml (commit 73eecf7).
## Task Commits
1. **Task 1: Add the publish job** - `ebcc38d` (feat)
2. **Task 1 fix: REGISTRY_PAT naming** - `73eecf7` (fix — GITEA_ prefix exclusion)
3. **Pre-merge cleanup: remove throwaway runner-probe.yml** - `dcf4242` (chore)
4. **Task 2: Human-verify checkpoint** — verified green (run #14); no code commit required
## Files Created/Modified
- `.gitea/workflows/ci.yml` — publish job added (push-to-main guard, --password-stdin docker login, build --target production from repo root, dual push :latest + :v1.1-<sha>, docker logout always())
## Decisions Made
- **REGISTRY_PAT naming (D-PAT-NAMING):** Gitea reserves the `GITEA_` prefix for built-in variables and silently strips any user-defined secret with that prefix from the runner environment. The original plan named the secret `GITEA_REGISTRY_PAT`; the fix renames it to `REGISTRY_PAT` (commit 73eecf7). Downstream plans must use `REGISTRY_PAT` if they add registry operations.
- **Shell form over marketplace actions:** `echo "${{ secrets.REGISTRY_PAT }}" | docker login ... --password-stdin` + `docker build` + `docker push` rather than `docker/login-action` / `docker/build-push-action`. This matches the 08-RESEARCH §Pattern 6 preferred path; no external action marketplace dependency.
- **Dual push:** two separate `docker push` calls (`:latest` + `:<milestone>-<sha>`) — same digest, two references. `:latest` is the moving pointer; `:<milestone>-<sha>` is the immutable traceability tag per D-04.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] GITEA_REGISTRY_PAT secret prefix rejected by Gitea runner**
- **Found during:** Task 1 → human-verify checkpoint (run #14 initially failed login)
- **Issue:** Gitea Actions runner silently ignores secrets with the `GITEA_` prefix (reserved namespace). The `GITEA_REGISTRY_PAT` secret was never injected into the job environment, causing `docker login` to fail with an empty password.
- **Fix:** Renamed the Gitea repo secret from `GITEA_REGISTRY_PAT` to `REGISTRY_PAT` and updated the ci.yml reference accordingly (commit 73eecf7).
- **Files modified:** `.gitea/workflows/ci.yml`
- **Verification:** Run #14 succeeded — "Login Succeeded" in log; both tags pushed; PAT masked.
- **Committed in:** `73eecf7`
---
**Total deviations:** 1 auto-fixed (Rule 1 - Bug)
**Impact on plan:** Required for the publish job to function at all. No scope creep. Security outcome (no plaintext PAT) unchanged.
## Issues Encountered
The GITEA_-prefix restriction was not documented in the plan or research notes; it was discovered empirically when the first publish run failed at docker login. The fix was straightforward once identified. No other issues encountered.
## Next Phase Readiness
- Phase 8 (Gitea CI) is fully complete — all six ROADMAP success criteria met (see 08-VERIFICATION.md).
- CI-01 (PR regression: lint + typecheck + unit + API integration + mobile harness) delivered by plans 02 + 03.
- CI-02 (push-to-main publish) delivered by this plan (04).
- The Gitea registry now has a pullable `familysync-api:latest` image. Future phases can reference it for deployment documentation or smoke tests.
- Phase 9 (Faster Write-Back) is fully independent and can start immediately.
---
*Phase: 08-gitea-ci*
*Completed: 2026-06-11*
@@ -0,0 +1,50 @@
# Phase 8: Gitea CI — Verification
**Status: PASSED**
**Verified:** 2026-06-11
**Evidence basis:** Gitea Actions run log + Gitea Packages API inspection by operator
---
## Phase Goal
Every PR to `main` runs a full regression that gates the merge — lint, typecheck, unit, API-integration against a MariaDB service container, and the Phase 7 mobile Playwright harness as a UI-regression step — and a merge to `main` builds and publishes the API Docker image. All on the existing self-hosted Gitea Actions runner.
## Six-Criteria Evidence Table
| # | Success Criterion | Status | Evidence |
|---|-------------------|--------|----------|
| 1 | PR triggers lint + typecheck + unit + API-integration vs MariaDB service container; failing run blocks merge | **PASSED** | Runs #11 and #12: `fast-checks` (191s, lint no-op + tsc both apps + PWA vitest) and `api` (238s, MariaDB cold start + drizzle-kit migrate + DB-backed integration tests) both green. Run blocking enforced by Gitea branch protection on `main`. |
| 2 | API integration tests connect to service-container MariaDB (DB_HOST=127.0.0.1) and pass reliably on cold first run | **PASSED** | Run #11 cold start: MariaDB readiness wait (mysql2 loop) completed before migrate; squashed migration baseline (commit c0f892c) eliminated false drizzle-kit destructive diff; all API integration tests passed on first attempt. |
| 3 | PR workflow brings up dev stack (API + PWA dev servers + MariaDB, DEV_AUTH_BYPASS=true) and runs Phase 7 mobile Playwright harness headlessly; harness failure blocks merge | **PASSED** | Run #11: `harness` job — 58 specs across iphone/WebKit + pixel/Chromium — all green in 1.6 min. Four CI-specific infrastructure fixes applied (API-reap timeout, ipv4first/127.0.0.1 DNS, dev-user seed, direct test:e2e call). No Phase 7 harness files modified. |
| 4 | Harness step waits for both API and PWA dev servers before launching Playwright (no startup-race flake) | **PASSED** | mysql2 readiness loop (DB) → `:3000/health` poll (API) → global-setup `:5173` poll (PWA) — three sequential readiness gates before Playwright launches. No flake observed on cold or warm runs. |
| 5 | Merge to `main` builds and pushes API Docker image under a sensible tag | **PASSED** | Run #14 (push of merge commit 98acff8): `docker build --target production -f apps/api/Dockerfile .` succeeded; both tags pushed — `familysync-api:latest` and `familysync-api:v1.1-98acff8` — same digest `sha256:ce724852…`; confirmed present in Gitea Packages API. |
| 6 | Registry credentials never appear in plaintext in the CI logs | **PASSED** | Log audit of run #14: PAT masked as `***` throughout (Gitea secret scrubber); `--password-stdin` used exclusively (token piped via stdin); no `-p`/`--password` argument form anywhere; `docker logout` ran in `always()` cleanup. REGISTRY_PAT naming used (GITEA_ prefix excluded to avoid Gitea's silent secret-drop behavior). |
## Plans Delivering the Criteria
| Plan | Contribution |
|------|-------------|
| 08-01 | Runner probe — confirmed ubuntu-latest label, Docker executor (/.dockerenv), GITHUB_SHA availability, Playwright deps install cleanly |
| 08-02 | ci.yml: fast-checks job (lint, tsc, PWA vitest) + api job (MariaDB service, mysql2 readiness, drizzle-kit migrate, integration tests). Delivers criteria 1 + 2. |
| 08-03 | ci.yml: harness job (dev-stack bring-up, readiness waits, pnpm test:e2e). Delivers criteria 3 + 4. Four CI infrastructure fixes (no Phase 7 files modified). |
| 08-04 | ci.yml: publish job (docker login --password-stdin, build --target production from repo root, dual push :latest + :v1.1-<sha>, docker logout). Delivers criteria 5 + 6. |
## Key Fixes Applied During Phase 8 (CI-side only, no harness changes)
1. **squashed migration baseline** (commit c0f892c) — single `0000_baseline.sql` replaces the incremental migration history; eliminates the false drizzle-kit destructive diff that aborted the first cold CI run.
2. **API-reap + ipv4first**`pkill -f "tsx watch"` reap guard + `--dns-result-order=ipv4first` flag ensures the API dev server binds on 127.0.0.1 (not ::1), matching the harness readiness poll.
3. **dev-user seed** — global-setup inserts the DEV_AUTH_BYPASS user (id=1) before Playwright launches, so the authed bypass session is valid on first spec.
4. **direct test:e2e**`pnpm --filter @familysync/pwa test:e2e` called directly (not via Makefile) to avoid shell-expansion issues in the runner.
5. **REGISTRY_PAT naming** (commit 73eecf7) — Gitea runner silently drops secrets whose names begin with `GITEA_`; secret renamed from `GITEA_REGISTRY_PAT` to `REGISTRY_PAT`.
## Requirements Delivered
| REQ-ID | Description | Status |
|--------|-------------|--------|
| CI-01 | PR regression gates merge (lint + typecheck + unit + API-integration + mobile harness) | **Complete** |
| CI-02 | Push to main builds + publishes API Docker image | **Complete** |
## Phase 8 Outcome
Phase 8 is complete. The Gitea CI pipeline is fully operational on the self-hosted runner. Future merges to `main` will automatically run the full regression gate and publish a new image — no manual steps required.