# infra/gitea — platform settings that are NOT GitOps-managed Gitea's own repo settings (branch protection, required status checks, approvals) live in Gitea's database, not in a manifest Flux reconciles. They are therefore **drift-prone and invisible to the gate** — exactly how the state below went unnoticed. ## The gap this closes Verified against the live API on 2026-09-29, against a `DECISIONS-LOG.md` entry (2026-08-31) asserting the opposite: | Claimed | Measured | |---|---| | `main` is a protected branch, gate is a required status check | `branch_protections` → `[]`; `protected:false`, `enable_status_check:false`, `required_approvals:0`, `user_can_push:true` | | Runner `incluster-dind` online | **zero** runners registered at repo, org and site level | So `main` accepted direct pushes, and the gate could not run at all. `CLAUDE.md` is the rule; this directory is how the rule gets enforced by the machine. ## `branch-protection.sh` Blocks direct pushes to `main`. Run it, then run the push probe it prints — **verify, do not trust the API response**, since Gitea may let site admins bypass protection. ./infra/gitea/branch-protection.sh It authenticates with the credential `git` already has stored for this host, and never prints the secret. ## `runner-token-rotate.sh` Mints a fresh instance-level registration token from the Gitea API and writes it into `apps/wave-1/gitea-actions-runner-secret.sops.yaml` via `sops set`. **The token value is never printed.** ./infra/gitea/runner-token-rotate.sh A fresh token is usually **not sufficient on its own**. `act_runner` registers exactly once and persists its identity in `/data/.runner` on the `act-runner-data` PVC. If Gitea no longer holds a matching runner row, the pod starts, sees `.runner`, **skips registration**, and polls with an identity Gitea does not recognise — so it never appears in the runner list. That is precisely the observed symptom: zero runners, pod running, no error anyone looks at. Clearing the PVC is what forces a real re-registration; the script prints the commands. ## Sequence — order matters 1. **Get an Actions runner registered** — `runner-token-rotate.sh`, then clear the PVC so the runner actually re-registers, then confirm it shows `online` in the API. Do not skip the confirmation: the whole reason this went unnoticed is that a runner was *recorded* as online without anyone checking. 2. **Watch the gate actually report on a real PR**, and note the context name it produced. 3. **Only then set `enable_status_check: true`** with that exact context. 4. Add the human reviewer as a repo collaborator, then raise `required_approvals` to 1. > **Do not do step 3 before step 1.** A required status check with no runner leaves every > PR permanently pending — including the PR that would fix it. The same applies to > `gate.yml`'s `conformance` job: it is `if: false` and never runs, yet still publishes a > `gate / conformance (pull_request)` context. Never list that one as required.