Implements your intent: the pipeline is the approver. A green gate becomes sufficient for an
ordinary change; the branch stops requiring a human for one.
The one exception, and why it isn't a matter of taste
A change to the gate cannot be gated by the gate.
A PR that weakens a check would be approved by the weakened check. Same for the admission
policies, the Flux wiring that decides what reconciles, the SOPS recipients, and the RBAC. Those
paths — and only those — still need an APPROVED review from someone other than the author.
The list is data with a reason per entry (infra/gitea/approval-policy.json), and is itself a
sensitive path, so the policy can't be widened or narrowed without a human.
Fail-closed, deliberately opposite to gitea-settings. There, a missing credential must not
block every PR — that's a lock-out. Here, a missing credential would let an unreviewed change to
the gate merge, so "cannot evaluate" is a block.
Verified against real PRs, not fixtures
Case
Result
PR #16 — sensitive paths, approved by jakman85, author gitea-admin
PASS
PR #6 — touched .gitea/workflows/gate.yml, zero reviews
BLOCK, naming the file
push event (not a PR)
N/A, exit 0
bogus token
BLOCK, not skip
Also caught before shipping: the first draft passed the file list via a second stdin redirect
after a heredoc, which silently wins and leaves the script body unread. Running it found that;
reading it wouldn't have.
required_approvals is deliberately NOT changed here
Flipping it live and changing expected-branch-protection.json must happen close together or gitea-settings correctly fails on the drift — and naively ordered, either order deadlocks
(the PR declaring 0 fails while live is 1; flipping live first fails every other PR). The
working sequence is in my message, and it's a separate deliberate change.
Implements your intent: **the pipeline is the approver.** A green gate becomes sufficient for an
ordinary change; the branch stops requiring a human for one.
### The one exception, and why it isn't a matter of taste
> **A change to the gate cannot be gated by the gate.**
A PR that weakens a check would be approved by the weakened check. Same for the admission
policies, the Flux wiring that decides what reconciles, the SOPS recipients, and the RBAC. Those
paths — and only those — still need an APPROVED review from someone other than the author.
The list is data with a reason per entry (`infra/gitea/approval-policy.json`), and is itself a
sensitive path, so the policy can't be widened *or* narrowed without a human.
**Fail-closed, deliberately opposite to `gitea-settings`.** There, a missing credential must not
block every PR — that's a lock-out. Here, a missing credential would let an unreviewed change to
the gate merge, so "cannot evaluate" is a **block**.
### Verified against real PRs, not fixtures
| Case | Result |
|---|---|
| PR #16 — sensitive paths, approved by `jakman85`, author `gitea-admin` | **PASS** |
| PR #6 — touched `.gitea/workflows/gate.yml`, **zero** reviews | **BLOCK**, naming the file |
| push event (not a PR) | N/A, exit 0 |
| bogus token | **BLOCK**, not skip |
Also caught before shipping: the first draft passed the file list via a second stdin redirect
after a heredoc, which silently wins and leaves the script body unread. Running it found that;
reading it wouldn't have.
### `required_approvals` is deliberately NOT changed here
Flipping it live and changing `expected-branch-protection.json` must happen close together or
`gitea-settings` correctly fails on the drift — and naively ordered, **either order deadlocks**
(the PR declaring `0` fails while live is `1`; flipping live first fails every other PR). The
working sequence is in my message, and it's a separate deliberate change.
Gate: **PASS** — `approval-policy: green`, `gitea-settings: green`.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Implements the operator intent: "CI/CD pipeline is the end-all-be-all for approval. If
the CI pipeline is not good enough to be trusted for this, then it is not good enough to
do its intended job and needs to be improved. Manual approvers should only be for
architecturally or security relevant changes."
Adds ci/approval-policy.sh as a gate stage. A green gate becomes sufficient for an
ordinary change; the one exception is structural rather than a matter of taste:
A CHANGE TO THE GATE CANNOT BE GATED BY THE GATE.
A PR that weakens a check would be approved by the weakened check. The same holds for the
admission policies, the Flux wiring that decides what reconciles, the SOPS recipients and
the RBAC. Those paths -- and only those -- still require an APPROVED review from someone
other than the author.
The list is data with a reason per entry (infra/gitea/approval-policy.json) and is itself
a sensitive path, so the policy cannot be widened or narrowed without a human.
FAIL-CLOSED, and deliberately the opposite of gitea-settings. There, a missing credential
must not block every PR -- that is a lock-out. Here, a missing credential would let an
unreviewed change to the gate merge, so "cannot evaluate" is a BLOCK.
Verified against real PRs rather than fixtures:
PR #16 (sensitive paths, approved by jakman85, author gitea-admin) -> PASS
PR #6 (.gitea/workflows/gate.yml, ZERO reviews) -> BLOCK, naming the file
push event (not a PR) -> N/A, exit 0
bogus token -> BLOCK, not skip
Also caught before shipping: the first draft passed the PR file list with a second stdin
redirect after a heredoc, which silently wins and leaves the script body unread. Running
it found that; reading it would not have.
required_approvals is NOT changed here. Flipping it live must happen together with the
declaration in expected-branch-protection.json, or gitea-settings correctly fails on the
drift -- that is a separate, deliberate change.
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.
Implements your intent: the pipeline is the approver. A green gate becomes sufficient for an
ordinary change; the branch stops requiring a human for one.
The one exception, and why it isn't a matter of taste
A PR that weakens a check would be approved by the weakened check. Same for the admission
policies, the Flux wiring that decides what reconciles, the SOPS recipients, and the RBAC. Those
paths — and only those — still need an APPROVED review from someone other than the author.
The list is data with a reason per entry (
infra/gitea/approval-policy.json), and is itself asensitive path, so the policy can't be widened or narrowed without a human.
Fail-closed, deliberately opposite to
gitea-settings. There, a missing credential must notblock every PR — that's a lock-out. Here, a missing credential would let an unreviewed change to
the gate merge, so "cannot evaluate" is a block.
Verified against real PRs, not fixtures
jakman85, authorgitea-admin.gitea/workflows/gate.yml, zero reviewsAlso caught before shipping: the first draft passed the file list via a second stdin redirect
after a heredoc, which silently wins and leaves the script body unread. Running it found that;
reading it wouldn't have.
required_approvalsis deliberately NOT changed hereFlipping it live and changing
expected-branch-protection.jsonmust happen close together orgitea-settingscorrectly fails on the drift — and naively ordered, either order deadlocks(the PR declaring
0fails while live is1; flipping live first fails every other PR). Theworking sequence is in my message, and it's a separate deliberate change.
Gate: PASS —
approval-policy: green,gitea-settings: green.Co-Authored-By: Claude Opus 5 (1M context) [email protected]
🤖 Generated with Claude Code