fix(applier): never PUT an org-inherited ruleset at the repo endpoint - #1027
Merged
Merged
Conversation
repos/{r}/rulesets RETURNS the organisation's rulesets alongside the
repository's own. An inherited one reads back IN FULL at
repos/{r}/rulesets/{id}, so every GET succeeds and nothing warns you --
and the PUT to that same path 404s.
apply-branch-gates.sh selected `.target=="branch" and .enforcement=="active"`
with no ownership filter, so on every metadatastician repo it picked up the
org-level EstateBranching (18225024) and then PUT to the repo path. That is
the failure already measured 67 times on this estate, once per reached repo.
The discriminator is .source_type, which the LIST endpoint does return --
verified against the live API, every entry carries it. The population is now
fetched and classified LOCALLY; a server-side select whose empty result is
also its success result cannot fail closed.
Three outcomes, none of them a guess:
* only inherited rulesets active -> ORG-INHERITED, naming the ids and the
/orgs/{org}/rulesets/{id} endpoint. The cure is at the org, applied ONCE;
67 doomed per-repo writes are not a smaller version of it.
* inherited BESIDE a repo-level one -> the repo-level one is selected and
the inherited one is REPORTED, because rulesets are additive and it goes
on enforcing. Counting both as candidates would have turned an ordinary
repo into AMBIGUOUS and left it permanently ungated.
* .source_type absent -> UNKNOWN. Writability is exactly what that field
decides; defaulting a missing one to the writable arm is a silent 404.
Verified report-only against the live estate: metadatastician/paint-type,
whose three active rulesets are ALL source_type=Organization, now reports
ORG-INHERITED instead of attempting the write.
Tests: CASE 12/13/14 plus MUTANT F, which restores the original selection and
dies by PUTting to repos/{r}/rulesets/18225024 -- the 404 reproduced. Cases 12
and 13 deliberately ship NO fixture for the inherited ruleset's own path, so
the shim's exit-1-on-missing-fixture asserts the fixed applier never reads it.
The 14 existing rulesets-list fixtures omitted source_type, which the live API
always sends; they are now faithful to it. 46/46 green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR
Contributor
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
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 |
|
Contributor
|
❌ Failed to create Coding Agent finishing-touch task. Please try again. |
hyperpolymath
added a commit
that referenced
this pull request
Sep 22, 2026
…cure needs (#1029) Two hardening follow-ups on #1027, which landed the org-inherited fix. **1. `grep -P` is a GNU extension.** Classifying active branch rulesets into repo-level vs org-inherited is the load-bearing step of this script — it is what stops the PUT that 404'd 67 times. It must not depend on which `grep` the runner ships. `awk -F'\t'` does the same field test portably. Mutant F is retargeted at the awk line, so the control still dies: without the ownership filter the applier becomes `DRIFT` and PUTs to `repos/acme/org-inherited/rulesets/18225024` — the measured 404, reproduced. **2. The `ORG-INHERITED` detail named the endpoint but not the credential.** An org-level ruleset write needs `admin:org`. A repo-scoped token reads the inherited ruleset *in full* and cannot write it — that asymmetry is the whole bug. Saying so in the report line saves the operator a second 403 chase. Suite: **46/46**, mutant F killed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



The defect
repos/{r}/rulesetsreturns the organisation's rulesets alongside the repository's own. An inherited one reads back in full atrepos/{r}/rulesets/{id}— so every GET succeeds and nothing warns you — while thePUTto that same path 404s.scripts/apply-branch-gates.sh:333selected:with no ownership filter. On every
metadatasticianrepo that picks up the org-levelEstateBranching(18225024) and then PUTs it to the repo path. That is the failure already measured 67 times on this estate, once per reached repo.The discriminator
.source_type. Verified against the live API that the list endpoint does return it on every entry:The population is now fetched and classified locally — a server-side
selectwhose empty result is also its success result cannot fail closed.Three outcomes, none a guess
ORG-INHERITED/orgs/{org}/rulesets/{id}endpoint. The cure is at the org, applied once; 67 doomed per-repo writes are not a smaller version of it.AMBIGUOUSand leave it permanently ungated..source_typeabsentUNKNOWNVerified against the live estate, report-only
metadatastician/paint-type— all three active rulesets aresource_type: Organization, zero repo-level:Before this change it would have selected
18225024and issued the 404.Tests — 46/46, and a mutant that died
ORG-INHERITED, no PUT even with--apply.WOULD-GATEonruleset=9, inherited still reported..source_typeabsent →UNKNOWN, no PUT.repos/{r}/rulesets/18225024— the 404 reproduced, not merely asserted.Cases 12 and 13 deliberately ship no fixture for the inherited ruleset's own path: the shim exits 1 on a missing fixture, so that absence asserts the fixed applier never reads it.
The 14 existing rulesets-list fixtures omitted
source_type, which the live API always sends; they are now faithful to it.Scope
Read-only except for the two files above. No ruleset was written — every live run in this PR was report-only.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR