feat(rulesets): add apply-branch-gates.sh, the branch-gates applier gates.json specified but nothing performed - #1011
Conversation
…cified but nothing performed
config/rulesets/README.adoc has said so since it was written: "the propagation
mechanism is still missing by design, and this note is the reminder that a
template fix without an applier is a half fix." That was literally true —
nothing in this repository read config/rulesets/gates.json as DATA. Every
reference was prose, or tests/test_governance_reusable_shape.sh asserting the
file's contents. gates.json specified a context derivation that no code
performed, so required_status_checks was never propagated to any repository.
apply-tag-ruleset-canon.sh is the TAG applier; this is its branch sibling.
CONTEXTS ARE DERIVED, NEVER TYPED
Per gates.json, contexts come from the check names the latest default-branch
run of each gate workflow actually emitted, via
/actions/workflows/{file}/runs?branch=<default>&per_page=1
/actions/runs/{id}/jobs
A hand-typed context that nothing emits is a PHANTOM: it can never turn
green, so it blocks the branch permanently.
THE REFUSAL THAT MATTERS
Zero derivable contexts => report UNGATED and write NOTHING. A
required_status_checks rule carrying an empty list is a VACUOUS GATE: it
reports "protected" in every summary view while requiring nothing, which is
strictly worse than having no rule, because it is indistinguishable from a
working one.
EXACTNESS GUARD
A ruleset PUT replaces the whole object. The planned body, normalised with
the required_status_checks rule removed from both sides, must be
byte-identical to the source; anything else moved is a refusal. Without it,
one jq slip strips required_signatures from every repo it touches, silently.
DELIBERATE NON-ACTIONS
* never creates a ruleset (NORULESET is reported, not repaired) — creating
branch protection where none exists is a policy act, not a gate-fill;
* never removes the four retired rule types unless --strip-retired is passed
— the estate census was ruled report-only;
* two active branch rulesets => AMBIGUOUS, fail closed. Rulesets are
ADDITIVE: filling one of a pair leaves the other enforcing and the repo
stays blocked by a rule nothing announced.
--require-green N implements the standing ruling on #956, "require the
reliably-green set": a context that is currently red becomes a merge deadlock
the instant it is required. `skipped` and `neutral` count as green, because
GitHub treats both as satisfying a required status check.
TESTS — a passing suite would not have been evidence
scripts/tests/branch-gates-apply-test.sh runs 18 controls, two of which are
MUTANTS that must go red:
A. delete the zero-context refusal -> the applier writes the vacuous
empty-list rule (verified by inspecting the captured PUT body);
B. make the body-builder also drop required_signatures -> the exactness
guard refuses.
Both die. The suite exercises the applier through a `gh` shim, so it needs
no network and no credentials.
Two defects were found by these controls rather than in review: the
--strip-retired jq filter used `index(.type)`, where jq rebinds `.` to the
array so `.type` was always null (the same class of bug apply-baseline-test.sh
was written to pin), and the exactness guard rejected its own honest control
because ADDING the required_status_checks rule legitimately changes the rules
array length.
Both files are committed 100755 — a suite committed 0644 passes every local
run and dies in CI at exit 126 before a single control executes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR
|
Warning Review limit reachedNext included review available in 23 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
@coderabbitai review The first attempt hit the review-capacity limit and posted the rate-limit banner, so this PR currently has a green CodeRabbit check with zero review comments — i.e. "did not look", not "found nothing". Requesting the real pass before merge. Worth a close look: |
|
I will inspect
|



The applier
gates.jsonspecified and nothing performedconfig/rulesets/README.adochas said this outright since it was written:That was literally true. Nothing in this repository read
config/rulesets/gates.jsonas data — every reference is prose, ortests/test_governance_reusable_shape.shasserting the file's contents.gates.jsonspecified a context derivation that no code performed, sorequired_status_checkswas never propagated anywhere.apply-tag-ruleset-canon.shis the tag applier; this is its branch sibling.Closes the D76 / #956 workstream's missing component.
Contexts are derived, never typed
Per
gates.json, contexts come from the check names the latest default-branchrun of each gate workflow actually emitted:
A hand-typed context that nothing emits is a phantom: it can never turn
green, so it blocks the branch permanently.
The refusal that matters
Zero derivable contexts → report
UNGATED, write nothing. Arequired_status_checksrule carrying an empty list is a vacuous gate: itreports "protected" in every summary view while requiring nothing. That is
strictly worse than having no rule at all, because it is indistinguishable from
a working one.
Exactness guard
A ruleset
PUTreplaces the whole object. The planned body — normalisedwith the
required_status_checksrule removed from both sides — must bebyte-identical to the source. Anything else moved is a refusal. Without this,
one
jqslip stripsrequired_signaturesfrom every repo it touches, silently.Deliberate non-actions
NORULESET, reported--strip-retiredopts inAMBIGUOUS, fail closedno_run=[…]--require-green NImplements the standing ruling on #956, "require the reliably-green set". A
context that is currently red becomes a merge deadlock the instant it is
required.
skippedandneutralcount as green — GitHub treats both assatisfying a required status check.
Measured on this repository, over the last 10 default-branch runs:
All three exclusions matched after the
" / "— the reusable-job-name formof
never_required_contexts— proving that branch against the live API and notonly against a fixture. The one dropped context is the known-red Hypatia
baseline gate.
Tests — a passing suite would not have been evidence
scripts/tests/branch-gates-apply-test.shruns 18 controls, two of whichare mutants that must go red:
PUTbody. Killed.required_signaturesThe suite drives the applier through a
ghshim, so it needs no network andno credentials.
Two defects were found by these controls rather than in review:
--strip-retiredjqfilter usedindex(.type), where jq rebinds.to the array, so
.typewas alwaysnulland the filter errored. This isthe same class of bug
scripts/tests/apply-baseline-test.shwas written topin — its header documents
.file_patterninsidetest(...)failingidentically.
required_status_checksrule legitimately changes the rules array's length.Both files are committed 100755 — a suite committed
0644passes everylocal run and dies in CI at exit 126 before a single control executes.
Not done here
Applying it to
standards/mainis a separate change: it convertsmainfrom zero required checks to 18, which is a merge-policy decision for theowner, not a side effect of landing a script. The ruleset that would be written
is
23787415, the repo's one active branch ruleset.Verification
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR