---
phase: 15-ci-skip-api-harness-jobs-for-doc-only-prs
plan: 03
type: execute
wave: 3
depends_on:
- 15-02
files_modified:
- .gitea/workflows/publish.yml
autonomous: false
requirements: []
must_haves:
truths:
- "Branch protection on main requires CI / fast-checks + CI / gate"
- "Branch protection on main no longer requires CI / api or CI / harness directly"
- "A doc-only PR (which skips api/harness) is mergeable — no missing-required-check deadlock"
- "publish.yml's safety-gate comment names the new required checks (fast-checks + gate)"
artifacts:
- path: ".gitea/workflows/publish.yml"
provides: "Updated safety-gate comment naming the new required checks"
contains: "CI / gate"
key_links:
- from: "Gitea branch-protection (main)"
to: "ci.yml gate job"
via: "required status check 'CI / gate' (always-running aggregate)"
pattern: "CI / gate"
---
Finalize the gating surface: update Gitea branch protection on `main` to require `CI / fast-checks` + `CI / gate` and DROP the now-skippable `CI / api` and `CI / harness` direct requirements, and update the `publish.yml` safety-gate comment to name the new required checks.
This delivers Success Criterion 3 — the only step that makes doc-only PRs actually mergeable without a missing-required-check deadlock.
Purpose: With the heavy jobs conditionally skipped (Plan 02), a doc-only PR no longer emits `CI / api` / `CI / harness` statuses; leaving them required would deadlock the merge. The always-running `CI / gate` is the correct gating surface.
Output: A Gitea branch-protection change (operator checkpoint — not automatable in YAML) + a one-comment edit to publish.yml.
ORDERING HAZARD (read before executing): This plan MUST run only AFTER Plan 02's ci.yml change has merged to `main` and Gitea has emitted at least one `CI / gate` commit-status. Dropping `CI / api` + `CI / harness` before `CI / gate` exists would leave `main` with no valid heavy-job gate, and adding `CI / gate` as required before it has ever reported can itself block PRs. Confirm `CI / gate` has appeared on a recent run before changing branch protection.
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-RESEARCH.md
@.planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-PATTERNS.md
Task 1: Update the publish.yml safety-gate comment to name the new required checks
- .gitea/workflows/publish.yml (lines 13-17 — the "Safety gate" comment block currently naming "the three required checks (CI / fast-checks, CI / api, CI / harness)")
- .planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-PATTERNS.md ("publish.yml Comment Update (advisory)" section — exact current text and the required-check change)
Edit ONLY the safety-gate comment in publish.yml (lines ~14-17). Change the clause that names "the three required checks (CI / fast-checks, CI / api, CI / harness)" to name the new required checks: `CI / fast-checks` and `CI / gate`. Reflect that `api`/`harness` are now conditionally skipped and gated via the always-running `CI / gate` aggregate rather than being required directly. Do not change any publish.yml job, step, env, or trigger — comment text only. This is the documentation half; the actual protection change is Task 2 (operator).
grep -q 'CI / gate' .gitea/workflows/publish.yml && grep -q 'CI / fast-checks' .gitea/workflows/publish.yml && ! grep -qE 'three required checks \(CI / fast-checks, CI / api, CI / harness\)' .gitea/workflows/publish.yml && command -v yq >/dev/null 2>&1 && yq -e '.jobs.publish' .gitea/workflows/publish.yml >/dev/null && echo OK
- publish.yml safety-gate comment names `CI / fast-checks` and `CI / gate` as the required checks
- publish.yml safety-gate comment no longer asserts `CI / api` and `CI / harness` are required (the old "three required checks" line is replaced)
- no publish.yml job/step/env/trigger changed (only comment text); publish.yml still parses as valid YAML
publish.yml's safety-gate rationale matches the new branch-protection reality.
Task 2: Operator — update Gitea branch protection on main (drop api/harness, require gate)
- .planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-RESEARCH.md (Gitea-Specific Notes #4 — branch-protection required-check update is a MANUAL Gitea admin step; Pitfall 1 — required-check deadlock from skipped jobs)
Plan 02 added an always-running `CI / gate` aggregate job to ci.yml and made `CI / api` / `CI / harness` conditionally skipped on doc-only PRs. Once Plan 02 merged, Gitea emits a `CI / gate` commit-status on every PR run. Branch protection still requires the old `CI / api` + `CI / harness` contexts, which a doc-only PR will never emit — so until you make this change, doc-only PRs deadlock on "missing required checks". This change is a Gitea admin action that cannot be done in a workflow file.
PRECONDITION — confirm first: open the most recent PR run in Gitea Actions for this repo and verify a `CI / gate` job ran and reported a status. Do NOT proceed until `CI / gate` has appeared at least once (Plan 02 must already be merged to main). If it has not appeared, stop — the protection change is premature.
Then update branch protection:
1. Gitea → this repo → Settings → Branches → edit the protection rule for `main`.
2. Under "Status Check Patterns" / required status checks, set the required contexts to EXACTLY these two:
- `CI / fast-checks`
- `CI / gate`
3. REMOVE these two from the required list (they are now conditionally skipped and gated via `CI / gate`):
- `CI / api`
- `CI / harness`
4. Save the protection rule.
(Alternative: the `tea` CLI / Gitea API can set branch-protection `status_check_contexts` to `["CI / fast-checks", "CI / gate"]` — login Bergerhouse — if you prefer not to use the UI.)
VERIFY the change end-to-end:
- Open a throwaway DOC-ONLY PR (edit only a `*.md` under docs/ or the repo root). Expect: `fast-checks` runs, `api`/`harness` show skipped, `CI / gate` passes, and the PR is MERGEABLE (no "missing required checks"). This proves SC-1 + SC-3.
- Open a throwaway CODE PR (touch a `*.ts` file). Expect: `fast-checks`, `api`, `harness`, and `CI / gate` all run; the PR is mergeable only when all are green. This proves SC-2.
- Close both throwaway PRs without merging.
Type "approved" once branch protection requires exactly `CI / fast-checks` + `CI / gate`, `CI / api` and `CI / harness` are no longer required, and the doc-only + code throwaway PRs behaved as described. Or describe what differed.
This phase introduces the following new configuration state (Plan 03 portion). The plan-review source-grounding pass must treat these as newly-created, not drift:
- Branch-protection required checks on `main` changed to `CI / fast-checks` + `CI / gate` (Gitea admin state, not a repo file)
- publish.yml safety-gate comment updated to name the new required checks
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| operator → Gitea branch-protection config | the required-check set decides whether unreviewed/failing code can merge to main |
| skipped heavy job → branch-protection check list | a skipped job may emit no commit-status; a still-required skipped context would deadlock |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-15-08 | Denial of Service (deadlock) | leaving `CI / api`/`CI / harness` required after the skip logic ships | mitigate | This plan explicitly drops both from required checks and requires the always-running `CI / gate` instead (RESEARCH Pitfall 1 / Gitea-Specific Note #4). |
| T-15-09 | Elevation of Privilege (bypass window) | dropping the heavy-job requirements BEFORE `CI / gate` exists leaves main ungated for code | mitigate | Ordering hazard called out: this plan is wave 3, `depends_on: [15-02]`; the checkpoint's PRECONDITION requires confirming `CI / gate` has reported at least once before any required-check edit. |
| T-15-10 | Tampering (false sense of gating) | publish.yml comment still claims api/harness are required, misleading a future maintainer | mitigate | Task 1 updates the comment to name `CI / fast-checks` + `CI / gate`. |
- Gitea branch protection for `main` requires exactly `CI / fast-checks` + `CI / gate`; `CI / api` and `CI / harness` are not required.
- Throwaway doc-only PR is mergeable with api/harness skipped (SC-1/SC-3).
- Throwaway code PR runs and gates on all jobs (SC-2).
- publish.yml safety-gate comment names the new required checks.
Maps to Phase 15 Success Criterion 3: branch protection requires `CI / fast-checks` + an always-running `CI / gate` aggregate; the direct `api`/`harness` requirements are dropped so a skipped heavy job never deadlocks the merge. (SC-1 doc-only mergeability and SC-2 code-PR gating are confirmed end-to-end here against the live protection rule.)