feat(ci): the pipeline is the approver #17

Merged
jakman85 merged 1 commits from feat/ci-is-the-approver into main 2026-09-30 04:42:44 +00:00
Owner

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

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)
gitea-admin added 1 commit 2026-09-29 20:35:08 +00:00
feat(ci): the pipeline is the approver; humans approve changes to the pipeline
gate / gate (pull_request) Successful in 1m35s
gate / conformance (pull_request) Has been skipped
0c6949ed98
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]>
jakman85 approved these changes 2026-09-30 04:39:57 +00:00
jakman85 merged commit 296d6d3275 into main 2026-09-30 04:42:44 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Home-Network/home-network-k3s-impl#17