2 Commits
4 changed files with 199 additions and 14 deletions
+4 -4
View File
@@ -33,7 +33,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
- [ ] **Phase 12: Initial Setup Wizard** - First-run validated bootstrap of env/VAPID/DB/OIDC + first app password, reusing the admin route surface
- [x] **Phase 13: Real Lint Gate (ESLint)** - Wire ESLint flat config (typescript-eslint + React) across both apps so the Phase 8 CI lint slot actually fails on violations instead of no-op'ing (completed 2026-06-12)
- [x] **Phase 14: Desktop E2E Coverage** - Add a Desktop Chrome Playwright profile + make the mobile-authored specs desktop-safe so the Phase 8 regression gate validates desktop, not just mobile (completed 2026-06-12)
- [ ] **Phase 15: Doc-Only CI Skip + Markdown Lint** - Aggregate-gate the slow api/harness CI jobs so doc-only PRs to main merge without running them (no branch-protection deadlock), and add markdownlint to `fast-checks` so docs get a fast format+lint gate (promoted from backlog 999.17)
- [x] **Phase 15: Doc-Only CI Skip + Markdown Lint** - Aggregate-gate the slow api/harness CI jobs so doc-only PRs to main merge without running them (no branch-protection deadlock), and add markdownlint to `fast-checks` so docs get a fast format+lint gate (promoted from backlog 999.17) (completed 2026-06-12)
## Phase Details
@@ -292,7 +292,7 @@ Plans:
**Wave 3** *(blocked on Wave 2 completion)*
- [ ] 15-03-PLAN.md — operator branch-protection checkpoint (require `CI / fast-checks` + `CI / gate`, drop api/harness) + publish.yml comment update (SC-3)
- [x] 15-03-PLAN.md — operator branch-protection checkpoint (require `CI / fast-checks` + `CI / gate`, drop api/harness) + publish.yml comment update (SC-3)
**UI hint**: no
@@ -314,7 +314,7 @@ Plans:
| 12. Initial Setup Wizard | v1.1 | 0/? | Not started | - |
| 13. Real Lint Gate (ESLint) | v1.1 | 3/3 | Complete | 2026-06-12 |
| 14. Desktop E2E Coverage | v1.1 | 1/1 | Complete | 2026-06-12 |
| 15. Doc-Only CI Skip + MD Lint | v1.1 | 2/3 | In Progress| |
| 15. Doc-Only CI Skip + MD Lint | v1.1 | 3/3 | Complete | 2026-06-12 |
## Backlog
@@ -322,7 +322,7 @@ Plans:
**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/3 plans executed
**Plans:** 3/3 plans complete
Plans:
+12 -10
View File
@@ -2,16 +2,16 @@
gsd_state_version: 1.0
milestone: v1.1
milestone_name: Operability & Polish
status: executing
status: completed
stopped_at: Phase 14 context gathered
last_updated: "2026-06-12T14:39:49.644Z"
last_activity: 2026-06-12 -- Phase 15 execution started
last_updated: "2026-06-12T15:18:17.057Z"
last_activity: 2026-06-12
progress:
total_phases: 18
completed_phases: 4
completed_phases: 5
total_plans: 15
completed_plans: 12
percent: 22
completed_plans: 15
percent: 28
---
# Project State
@@ -25,14 +25,15 @@ See: .planning/PROJECT.md (updated 2026-06-10)
## Current Position
Phase: 15 (ci-skip-api-harness-jobs-for-doc-only-prs) — PENDING (deferred checkpoint)
Plan: 15-03 (Task 2 of 2 — human-action checkpoint, deferred)
Phase: 999.1
Plan: Not started
Status: Plans 15-01 + 15-02 complete; 15-03 Task 1 (publish.yml comment) committed (da623ac); 15-03 Task 2 awaiting operator after branch merges to main
Last activity: 2026-06-12 -- Phase 15 code complete; PR pushed; 15-03 operator checkpoint deferred to post-merge
Last activity: 2026-06-12
### Deferred Checkpoint — Phase 15 Plan 15-03 Task 2 (human-action)
Operator must update Gitea branch protection on `main` AFTER this branch merges and `CI / gate` has reported at least once:
- Set required status checks to EXACTLY: `CI / fast-checks` + `CI / gate`
- REMOVE: `CI / api` + `CI / harness` (now conditionally skipped on doc-only PRs, gated via the always-running `CI / gate`)
- CLI option (login Bergerhouse): `tea api --method PATCH repos/luckberg/familysync/branches/main/protection --data '{"status_check_contexts":["CI / fast-checks","CI / gate"]}'`
@@ -46,7 +47,7 @@ Resume: after the operator completes the change, re-run `/gsd-execute-phase 15`
**Velocity:**
- Total plans completed: 25
- Total plans completed: 31
- Average duration: -
- Total execution time: 0 hours
@@ -59,6 +60,7 @@ Resume: after the operator completes the change, re-run `/gsd-execute-phase 15`
| 07 | 4 | - | - |
| 13 | 3 | - | - |
| 14 | 1 | - | - |
| 15 | 3 | - | - |
**Recent Trend:**
@@ -0,0 +1,80 @@
---
phase: 15-ci-skip-api-harness-jobs-for-doc-only-prs
plan: "03"
subsystem: ci
tags: [ci, gitea-actions, branch-protection, gate, doc-only-skip]
dependency_graph:
requires: [15-02]
provides: [main branch protection requires fast-checks + gate, api/harness no longer required]
affects: [.gitea/workflows/publish.yml, "Gitea branch protection (main) — admin state"]
tech_stack:
patterns:
- Gitea branch-protection status_check_contexts via tea API
key_files:
modified:
- .gitea/workflows/publish.yml
decisions:
- D-15-03-CTX-SUFFIX: Gitea status-check contexts carry a "(pull_request)" suffix; required checks set to "CI / fast-checks (pull_request)" + "CI / gate (pull_request)" — NOT the bare names the plan text suggested (bare names would deadlock — Gitea never emits them)
- D-15-03-API-ENDPOINT: branch protection lives at repos/{owner}/{repo}/branch_protections/{name}, not branches/{name}/protection (latter 404s on this Gitea)
metrics:
completed_date: "2026-06-12"
tasks_completed: 2
tasks_total: 2
files_modified: 1
---
# Phase 15 Plan 03: Finalize gating surface Summary
**One-liner:** Updated `publish.yml`'s safety-gate comment to name the new required checks, then (post-merge, operator-authorized) changed Gitea branch protection on `main` to require exactly `CI / fast-checks (pull_request)` + `CI / gate (pull_request)` and drop the now-skippable `CI / api`/`CI / harness` — making doc-only PRs mergeable without a missing-required-check deadlock.
## Tasks Completed
| Task | Name | Commit / Artifact | Files |
|------|------|-------------------|-------|
| 1 | Update publish.yml safety-gate comment to name new required checks | da623ac | .gitea/workflows/publish.yml |
| 2 | Operator — update Gitea branch protection on main (drop api/harness, require gate) | Gitea admin state (tea API PATCH) | branch_protections/main |
## What Was Built
**Task 1 — publish.yml comment:**
Edited only the safety-gate comment block (no job/step/env/trigger change). The comment now states that the required checks are `CI / fast-checks` and `CI / gate`, and that `CI / api`/`CI / harness` are conditionally skipped on doc-only PRs and gated via the always-running `CI / gate` aggregate. publish.yml still parses as valid YAML.
**Task 2 — branch protection (operator, executed post-merge):**
After PR #11 merged to `main` (bringing the `gate` job) and `CI / gate (pull_request)` reported `success` on the PR run, the `main` branch-protection rule's `status_check_contexts` was set to:
- `CI / fast-checks (pull_request)`
- `CI / gate (pull_request)`
and `CI / api (pull_request)` + `CI / harness (pull_request)` were removed. Applied via:
`tea api --method PATCH repos/luckberg/familysync/branch_protections/main --data '{"status_check_contexts":["CI / fast-checks (pull_request)","CI / gate (pull_request)"]}'`
## Decisions Made
- **D-15-03-CTX-SUFFIX:** The live Gitea status-check contexts carry a `(pull_request)` suffix (e.g. `CI / gate (pull_request)`), as confirmed by the PR #11 commit-status API. The plan's how-to-verify named bare `CI / fast-checks`/`CI / gate`; using those would have required contexts Gitea never emits, re-creating the very deadlock this phase fixes. Required checks were therefore set with the suffix to match emitted reality.
- **D-15-03-API-ENDPOINT:** Branch protection is read/written at `repos/{owner}/{repo}/branch_protections/{name}` on this Gitea (1.26.2); the older `branches/{name}/protection` path returns "not found".
## Deviations from Plan
- The operator action was performed via the `tea` API by the agent (operator-authorized "proceed") rather than via the Gitea UI by a human, and used the **suffixed** context names (D-15-03-CTX-SUFFIX) instead of the bare names in the plan text.
- The throwaway doc-only / code PR live A/B test described in the plan's how-to-verify was replaced with: (a) live proof from PR #11 that all five jobs (`changes`/`fast-checks`/`api`/`harness`/`gate`) run and report `success` on a code PR (SC-2), and (b) static proof that the `changes` `code` filter matches no `docs/**` or root `*.md` path, so a doc-only PR yields `code=false``api`/`harness` skip → `gate` passes (SC-1/SC-3). The protection change was verified by re-reading the rule (required = fast-checks + gate; api/harness absent).
## Threat Model Coverage
| Threat | Mitigation | Status |
|--------|-----------|--------|
| T-15-08: leaving api/harness required after skip logic ships (deadlock) | both dropped from required checks; always-running `CI / gate` required instead | Implemented |
| T-15-09: dropping heavy-job requirements before `CI / gate` exists (bypass window) | precondition confirmed — `CI / gate (pull_request)` reported `success` on PR #11 before any protection edit | Implemented |
| T-15-10: publish.yml comment still claims api/harness required (false sense of gating) | Task 1 updated the comment to name `CI / fast-checks` + `CI / gate` | Implemented |
## Verification
- publish.yml: `CI / gate` and `CI / fast-checks` present; old "three required checks (… api, … harness)" line replaced; valid YAML (`publish` job intact).
- Branch protection (re-GET after PATCH): `status_check_contexts == ['CI / fast-checks (pull_request)', 'CI / gate (pull_request)']`; api/harness no longer present; `enable_status_check: true`.
- Precondition: PR #11 head commit-status shows `CI / gate (pull_request) => success`.
- SC-1 (doc-only mergeable): static — `changes` `code` filter excludes `docs/**` + root `*.md`.
- SC-2 (code PR gates all): live — PR #11 ran and gated on all jobs.
## Self-Check: PASSED
- `.gitea/workflows/publish.yml` modified (comment): EXISTS
- Commit da623ac (Task 1): FOUND
- Branch protection required checks = fast-checks + gate, api/harness dropped: VERIFIED via tea API
@@ -0,0 +1,103 @@
---
phase: 15-ci-skip-api-harness-jobs-for-doc-only-prs
verified: 2026-06-12T00:00:00Z
status: passed
score: 3/3 must-haves verified
overrides_applied: 0
---
# Phase 15: CI doc-only skip + markdown lint gate — Verification Report
**Phase Goal:** Aggregate-gate the slow api/harness CI jobs so doc-only PRs to main merge without running them (no branch-protection deadlock), and add markdownlint to `fast-checks` so docs get a fast format+lint gate.
**Verified:** 2026-06-12
**Status:** passed
**Re-verification:** No — initial verification
## Requirement Traceability Note
No formal REQ-IDs are assigned to this phase (promoted from backlog 999.17 as CI tooling/operability work). Requirement-traceability is N/A; this is noted here rather than flagged as a gap.
---
## Goal Achievement
### Observable Truths (Success Criteria)
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| SC-1 | A doc-only PR (only docs/\*\*.md or root \*.md) skips `api` and `harness` jobs and is still mergeable — no missing-required-check deadlock | VERIFIED | The `changes` job's `code` filter lists only code paths (`**/*.ts`, `apps/**`, `pnpm-lock.yaml`, `Dockerfile`, `docker-compose*.yml`, etc.) with no `docs/**` or root `*.md` entry. A doc-only PR yields `code=false``api`/`harness` skip. The `gate` job uses `if: always()` and accepts `skipped` as a passing state for api/harness. Branch protection requires `CI / fast-checks (pull_request)` + `CI / gate (pull_request)` only — both always-reporting. Verified live: PR #11 (code PR) ran all five jobs; the protection rule was confirmed via tea API PATCH + re-GET. |
| SC-2 | A code PR runs `fast-checks`, `api`, `harness`, and `gate`, and is mergeable only when all are green | VERIFIED | `api` and `harness` carry `needs: [changes]` and `if: github.event_name == 'pull_request' && needs.changes.outputs.code == 'true'`. Any file matching the positive code filter triggers all heavy jobs. The `gate` job fails (exit 1) if `needs.fast-checks.result != 'success'` or if `needs.api.result` / `needs.harness.result` is neither `success` nor `skipped`. Confirmed live by PR #11 (a code PR) where all five jobs ran and reported success. |
| SC-3 | Branch protection on `main` requires exactly `CI / fast-checks` + `CI / gate` and no longer requires `CI / api`/`CI / harness` | VERIFIED | tea API GET on `repos/luckberg/familysync/branch_protections/main` returns `status_check_contexts: ['CI / fast-checks (pull_request)', 'CI / gate (pull_request)']` with `enable_status_check: true`. `CI / api` and `CI / harness` are absent. Context suffix `(pull_request)` per decision D-15-03-CTX-SUFFIX — Gitea emits suffixed contexts; bare names would deadlock. |
**Score:** 3/3 truths verified
---
### Required Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `.markdownlint-cli2.jsonc` | markdownlint-cli2 config (extends prettier preset; content rules; globs + ignores) | VERIFIED | File exists. `config.extends = "markdownlint/style/prettier"`. MD001/MD024/MD040/MD031/MD051/MD052 enabled; MD041/MD034/MD036 disabled. `globs: ["docs/**/*.md", "*.md", "apps/**/*.md"]`. `ignores: [".planning/**", "node_modules/**", "**/node_modules/**", ".pnpm-store/**"]`. |
| `package.json` | `md:lint` script + `markdownlint-cli2` devDependency | VERIFIED | `scripts["md:lint"] = "markdownlint-cli2"` (no glob args — config-file-driven). `devDependencies["markdownlint-cli2"] = "0.22.1"` (exact pin, no `^`/`~`). |
| `.gitea/workflows/ci.yml` | changes job, md lint step in fast-checks, conditional api/harness, always-running gate | VERIFIED | All four structural elements present (detail in Key Link Verification below). YAML parses without error. |
| `.gitea/workflows/publish.yml` | Updated safety-gate comment naming new required checks | VERIFIED | Comment at lines 1319 names `CI / fast-checks` and `CI / gate`; no longer asserts `CI / api`/`CI / harness` are required. Old "three required checks (…api, …harness)" clause is replaced. YAML parses without error. |
---
### Key Link Verification
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `.gitea/workflows/ci.yml` fast-checks job | `package.json` `md:lint` script | Step "Markdown lint" runs `pnpm md:lint` | WIRED | Step present at position 6 in fast-checks steps, after "Format check" (pos 5) and before "Typecheck" (pos 7). |
| `package.json` `md:lint` script | `.markdownlint-cli2.jsonc` | `markdownlint-cli2` auto-discovers root config | WIRED | Script is bare `markdownlint-cli2` with no CLI glob args; globs/ignores live in the config file. `pnpm md:lint` exits 0 scanning 12 files. |
| `ci.yml` `api`/`harness` jobs | `ci.yml` `changes` job output | `needs: [changes]` + `if: needs.changes.outputs.code == 'true'` | WIRED | Both heavy jobs carry `needs: [changes]` and the combined `if`. Confirmed via YAML parse. |
| `ci.yml` `gate` job | `ci.yml` `fast-checks`/`api`/`harness` results | `needs: [fast-checks, changes, api, harness]` + `if: always()` + individual `needs.X.result` checks | WIRED | `gate.needs` contains all four jobs. `gate.if = "always()"`. Bash script references `needs.fast-checks.result`, `needs.api.result`, `needs.harness.result` individually. No `needs.*.result` wildcard (Gitea bug #31007 avoided). |
| Gitea branch protection (main) | `ci.yml` `gate` job | Required status check `CI / gate (pull_request)` | WIRED | tea API GET confirms `status_check_contexts = ['CI / fast-checks (pull_request)', 'CI / gate (pull_request)']`. `enable_status_check = true`. `CI / api (pull_request)` and `CI / harness (pull_request)` are absent. |
---
### Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|----------|---------|--------|--------|
| `pnpm md:lint` exits 0 on current tree | `pnpm md:lint` | "Summary: 0 error(s)" / exit 0 | PASS |
| 12 in-scope files scanned, `.planning/**` excluded | `pnpm md:lint` output | "Finding: … !.planning/** …; Linting: 12 file(s)" | PASS |
| `ci.yml` parses as valid YAML | `python3 -c "import yaml; yaml.safe_load(...)"` | No error | PASS |
| `publish.yml` parses as valid YAML | `python3 -c "import yaml; yaml.safe_load(...)"` | No error | PASS |
| Branch protection requires exactly fast-checks + gate, no api/harness | `tea api repos/luckberg/familysync/branch_protections/main` | `status_check_contexts: ['CI / fast-checks (pull_request)', 'CI / gate (pull_request)']` | PASS |
| Markdown lint step is between Format check and Typecheck | YAML parse of fast-checks steps | Format check @ idx 5, Markdown lint @ idx 6, Typecheck @ idx 7 | PASS |
| No `needs.*.result` wildcard in gate (Gitea #31007 guard) | `grep 'needs\.\*\.result' ci.yml` | No match | PASS |
| All three individual result refs present in gate | count of `needs.fast-checks.result`, `needs.api.result`, `needs.harness.result` | 3 matches | PASS |
---
### Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| — | — | — | — | None found |
No `TBD`, `FIXME`, or `XXX` markers in any file modified by this phase. No stub implementations. No hardcoded empty data. No orphaned artifacts.
---
### Human Verification Required
None. All success criteria are verifiable statically (YAML structure, config values, branch-protection API state) or behaviorally (`pnpm md:lint` exit code, file counts). The one item that was previously a human-action checkpoint (Task 2 of Plan 03 — branch protection update) was executed by the agent under operator authorization and independently confirmed via the tea API GET in this verification session.
---
### Requirements Coverage
Requirement-traceability is N/A for this phase per the phase brief. No formal REQ-IDs are assigned. This is documented here to distinguish it from a missing-coverage gap.
---
### Gaps Summary
None. All three success criteria are verified against live codebase and live admin state.
---
_Verified: 2026-06-12_
_Verifier: Claude (gsd-verifier)_