Files
home-network-k3s-impl/infra/gitea
jakman85andClaude Opus 5 234cc6a71a
gate / gate (push) Successful in 1m33s
gate / gate (pull_request) Successful in 1m32s
gate / conformance (push) Has been skipped
gate / conformance (pull_request) Has been skipped
docs(gitea): a skipped job reports SUCCESS, not pending -- correct the deadlock claim
The runner came up on 2026-09-29 and the first CI jobs this repo has ever run went
green, which falsified a claim merged in #2.

MEASURED:
  gate / gate (pull_request)          success   1m58s (PR 1), 1m34s (PR 2)
  gate / gate (push)                  success   1m32s (PR 2 only)
  gate / conformance (pull_request)   success   "Has been skipped"

#2 said listing the conformance context as a required check would deadlock every PR,
because the job is `if: false` and never runs. That is wrong. Gitea reports a skipped
job as SUCCESS. Requiring it would not deadlock anything -- it would pass VACUOUSLY,
forever, while nothing ran.

The conclusion is unchanged (never require it) but the reason inverts, and the corrected
reason matters more: under HARD RULE 4 a permanently-green check that verifies nothing is
worse than a deadlock. A deadlock is loud. This is the exact fail-open shape the
2026-09-18 audit and this one both keep finding.

Also records that the (push) contexts only appear for branches matching gate.yml's push
trigger -- feature/**, fix/**, docs/**, chore/** -- and NOT feat/**, which is why PR 1's
feat/ branch produced only pull_request contexts. Require the pull_request context alone,
or PRs from non-matching prefixes never satisfy the rule.

CLAUDE.md's enforcement table is updated: the runner half is genuinely fixed and observed
green, the branch-protection half is not. Adds the warning that a green tick is not by
itself evidence that anything ran.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-29 01:07:13 -05:00
..

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. That half is measured: PR #1 sat pending for 30.6 minutes across 122 polls with no runner registered, and never reported.

Measured, once the runner came up (2026-09-29)

The first CI jobs this repo ever ran, on PR #1 and PR #2:

Context Result Time
gate / gate (pull_request) success 1m58s (PR #1), 1m34s (PR #2)
gate / gate (push) success 1m32s (PR #2 only)
gate / conformance (pull_request) success — "Has been skipped" —

Require gate / gate (pull_request). Do NOT require either conformance context — but not for the reason an earlier draft of this file gave. A skipped Gitea Actions job does not hang a status check: Gitea reports it as success with the description "Has been skipped". So requiring it would not deadlock anything — it would pass vacuously, every time, forever, while nothing ran. Under HARD RULE 4 that is worse than a deadlock: a deadlock is loud, a permanently-green check that verifies nothing is exactly the fail-open shape this repo keeps finding.

The (push) contexts appear only for branches matching gate.yml's push trigger (feature/**, fix/**, docs/**, chore/**). feat/** is not in that list, which is why PR #1's feat/ branch produced only pull_request contexts. Require only the pull_request context, or PRs from non-matching prefixes will never satisfy the rule.