Every check in this repo reads the repository, the rendered manifests, or the cluster. None
could see Gitea's own database — and that is where branch protection and runner registration
live. Three failures came out of that blind spot today, all the same shape: a setting that read as enforced while not being, with nothing able to notice.
main had no branch protection for ~a month while DECISIONS-LOG recorded a required status check
Zero runners registered — PR #1 sat pending for 30.6 minutes with nothing to execute it
After enable_status_check + required_approvals went on they still didn't bite, because block_admin_merge_override was false: PRs #5–#9 merged with zero approvals against required_approvals: 1
infra/gitea/*.shassert desired state. Nothing detected drift from it. This is the detector.
How it works
ci/gitea-settings.sh runs as a merge-gate stage: diffs the live rule against infra/gitea/expected-branch-protection.json, and asserts at least one runner is online. It reads /branch_protections/main, not/branches/main — the branch view doesn't expose block_admin_merge_override and showed all-green while the rule was overridable.
Verified by test, not assumed:
Case
Result
no token
exit 3 — skips loudly, does not block
live == declaration
exit 0
injected drift
exit 1 — blocks, naming the drifted key and the runner count
full ci/gate.sh with token
PASS, rc 0
The skip is deliberately not a block
It is not in gate.sh's strict-skips loop. Making an absent credential block every PR is
precisely the lock-yourself-out failure this repo keeps walking into. A skip denies a PASS
(exit 3) and says loudly what is unverified.
gate.yml's || [ "$?" -eq 3 ] now covers a second stage. That widening is declared in the
file rather than left to be discovered, and narrows back the moment REQUIRE_GITEA_SETTINGS=true
is set.
⚠️ Not done when this merges
infra/gitea/set-ci-token.sh mints the token and stores it as the repo secret without printing it. Until that token exists this stage skips — and a skipped detector detects nothing. Run it,
confirm the stage goes green on a real PR, then uncomment REQUIRE_GITEA_SETTINGS.
Every check in this repo reads the repository, the rendered manifests, or the cluster. **None
could see Gitea's own database** — and that is where branch protection and runner registration
live. Three failures came out of that blind spot today, all the same shape: a setting that
*read* as enforced while not being, with nothing able to notice.
- `main` had **no branch protection** for ~a month while DECISIONS-LOG recorded a required status check
- **Zero runners** registered — PR #1 sat `pending` for 30.6 minutes with nothing to execute it
- After `enable_status_check` + `required_approvals` went on they **still didn't bite**, because
`block_admin_merge_override` was false: **PRs #5–#9 merged with zero approvals** against
`required_approvals: 1`
`infra/gitea/*.sh` **assert** desired state. Nothing **detected** drift from it. This is the detector.
### How it works
`ci/gitea-settings.sh` runs as a merge-gate stage: diffs the live rule against
`infra/gitea/expected-branch-protection.json`, and asserts at least one runner is online. It reads
`/branch_protections/main`, **not** `/branches/main` — the branch view doesn't expose
`block_admin_merge_override` and showed all-green while the rule was overridable.
**Verified by test, not assumed:**
| Case | Result |
|---|---|
| no token | `exit 3` — skips loudly, does **not** block |
| live == declaration | `exit 0` |
| injected drift | `exit 1` — blocks, naming the drifted key *and* the runner count |
| full `ci/gate.sh` with token | **PASS, rc 0** |
### The skip is deliberately not a block
It is **not** in `gate.sh`'s strict-skips loop. Making an absent credential block every PR is
precisely the lock-yourself-out failure this repo keeps walking into. A skip denies a PASS
(exit 3) and says loudly what is unverified.
`gate.yml`'s `|| [ "$?" -eq 3 ]` now covers a second stage. That widening is **declared in the
file** rather than left to be discovered, and narrows back the moment `REQUIRE_GITEA_SETTINGS=true`
is set.
### ⚠️ Not done when this merges
`infra/gitea/set-ci-token.sh` mints the token and stores it as the repo secret without printing it.
**Until that token exists this stage skips — and a skipped detector detects nothing.** Run it,
confirm the stage goes green on a real PR, *then* uncomment `REQUIRE_GITEA_SETTINGS`.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Every check in this repo reads the repository, the rendered manifests, or the cluster.
NONE could see Gitea's own database, and that is where branch protection and runner
registration live. Three failures came out of that blind spot on 2026-09-29, all the same
shape -- a setting that READ as enforced while not being, with nothing able to notice:
* main had NO branch protection for ~a month while DECISIONS-LOG recorded it as a
required status check;
* ZERO Actions runners were registered, so PR #1 -- the first PR this repo ever had --
sat pending for 30.6 minutes with nothing to execute it;
* after enable_status_check and required_approvals were turned on they STILL did not
bite, because block_admin_merge_override was false: PRs #5-#9 merged with zero
approvals recorded against required_approvals: 1.
infra/gitea/*.sh ASSERT the desired state. Nothing DETECTED drift from it. This is the
detector.
ci/gitea-settings.sh runs as a merge-gate stage: it diffs the live rule at
/branch_protections/main against infra/gitea/expected-branch-protection.json and asserts at
least one runner is online. It reads the RULE object, not /branches/main -- the branch view
does not expose block_admin_merge_override and showed all-green while the rule was
overridable by the only account that merges.
Verified by test, not assumed:
no token -> exit 3, skips loudly, does NOT block
live == declaration -> exit 0
injected drift -> exit 1, blocks, naming both the drifted key and the runner count
The skip is DELIBERATELY not in gate.sh's strict-skips loop. Making an absent credential
block every PR is precisely the lock-yourself-out failure this repo keeps walking into, so
a skip denies a PASS (exit 3) and says loudly what is unverified. REQUIRE_GITEA_SETTINGS=true
upgrades it to a block once the token is wired.
gate.yml's `|| [ "$?" -eq 3 ]` tolerance now covers a second stage. That widening is
declared in the file rather than left to be discovered, and it narrows back the moment
REQUIRE_GITEA_SETTINGS is set.
infra/gitea/set-ci-token.sh mints the token and stores it as the repo Actions secret without
ever printing the value.
UNTIL THAT TOKEN EXISTS THIS STAGE SKIPS, AND A SKIPPED DETECTOR DETECTS NOTHING.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Every check in this repo reads the repository, the rendered manifests, or the cluster. None
could see Gitea's own database — and that is where branch protection and runner registration
live. Three failures came out of that blind spot today, all the same shape: a setting that
read as enforced while not being, with nothing able to notice.
mainhad no branch protection for ~a month while DECISIONS-LOG recorded a required status checkpendingfor 30.6 minutes with nothing to execute itenable_status_check+required_approvalswent on they still didn't bite, becauseblock_admin_merge_overridewas false: PRs #5–#9 merged with zero approvals againstrequired_approvals: 1infra/gitea/*.shassert desired state. Nothing detected drift from it. This is the detector.How it works
ci/gitea-settings.shruns as a merge-gate stage: diffs the live rule againstinfra/gitea/expected-branch-protection.json, and asserts at least one runner is online. It reads/branch_protections/main, not/branches/main— the branch view doesn't exposeblock_admin_merge_overrideand showed all-green while the rule was overridable.Verified by test, not assumed:
exit 3— skips loudly, does not blockexit 0exit 1— blocks, naming the drifted key and the runner countci/gate.shwith tokenThe skip is deliberately not a block
It is not in
gate.sh's strict-skips loop. Making an absent credential block every PR isprecisely the lock-yourself-out failure this repo keeps walking into. A skip denies a PASS
(exit 3) and says loudly what is unverified.
gate.yml's|| [ "$?" -eq 3 ]now covers a second stage. That widening is declared in thefile rather than left to be discovered, and narrows back the moment
REQUIRE_GITEA_SETTINGS=trueis set.
⚠️ Not done when this merges
infra/gitea/set-ci-token.shmints the token and stores it as the repo secret without printing it.Until that token exists this stage skips — and a skipped detector detects nothing. Run it,
confirm the stage goes green on a real PR, then uncomment
REQUIRE_GITEA_SETTINGS.Co-Authored-By: Claude Opus 5 (1M context) [email protected]
🤖 Generated with Claude Code
Every check in this repo reads the repository, the rendered manifests, or the cluster. NONE could see Gitea's own database, and that is where branch protection and runner registration live. Three failures came out of that blind spot on 2026-09-29, all the same shape -- a setting that READ as enforced while not being, with nothing able to notice: * main had NO branch protection for ~a month while DECISIONS-LOG recorded it as a required status check; * ZERO Actions runners were registered, so PR #1 -- the first PR this repo ever had -- sat pending for 30.6 minutes with nothing to execute it; * after enable_status_check and required_approvals were turned on they STILL did not bite, because block_admin_merge_override was false: PRs #5-#9 merged with zero approvals recorded against required_approvals: 1. infra/gitea/*.sh ASSERT the desired state. Nothing DETECTED drift from it. This is the detector. ci/gitea-settings.sh runs as a merge-gate stage: it diffs the live rule at /branch_protections/main against infra/gitea/expected-branch-protection.json and asserts at least one runner is online. It reads the RULE object, not /branches/main -- the branch view does not expose block_admin_merge_override and showed all-green while the rule was overridable by the only account that merges. Verified by test, not assumed: no token -> exit 3, skips loudly, does NOT block live == declaration -> exit 0 injected drift -> exit 1, blocks, naming both the drifted key and the runner count The skip is DELIBERATELY not in gate.sh's strict-skips loop. Making an absent credential block every PR is precisely the lock-yourself-out failure this repo keeps walking into, so a skip denies a PASS (exit 3) and says loudly what is unverified. REQUIRE_GITEA_SETTINGS=true upgrades it to a block once the token is wired. gate.yml's `|| [ "$?" -eq 3 ]` tolerance now covers a second stage. That widening is declared in the file rather than left to be discovered, and it narrows back the moment REQUIRE_GITEA_SETTINGS is set. infra/gitea/set-ci-token.sh mints the token and stores it as the repo Actions secret without ever printing the value. UNTIL THAT TOKEN EXISTS THIS STAGE SKIPS, AND A SKIPPED DETECTOR DETECTS NOTHING. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>