---
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"
---

<objective>
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.
</objective>

<execution_context>
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
</execution_context>

<context>
@.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
</context>

<tasks>

<task type="execute">
  <name>Task 1: Update the publish.yml safety-gate comment to name the new required checks</name>
  <read_first>
    - .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)
  </read_first>
  <action>
    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).
  </action>
  <verify>
    <automated>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</automated>
  </verify>
  <acceptance_criteria>
    - 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
  </acceptance_criteria>
  <done>publish.yml's safety-gate rationale matches the new branch-protection reality.</done>
</task>

<task type="checkpoint:human-action" gate="blocking">
  <name>Task 2: Operator — update Gitea branch protection on main (drop api/harness, require gate)</name>
  <read_first>
    - .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)
  </read_first>
  <what-built>
    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.
  </what-built>
  <how-to-verify>
    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.
  </how-to-verify>
  <resume-signal>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.</resume-signal>
</task>

</tasks>

<artifacts_produced>
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
</artifacts_produced>

<threat_model>
## 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`. |
</threat_model>

<verification>
- 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.
</verification>

<success_criteria>
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.)
</success_criteria>

<output>
Create `.planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-03-SUMMARY.md` when done.
</output>
