Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1cc72cd
feat(rulesets): base protection floor applier, branch + tag (#787)
hyperpolymath Sep 23, 2026
3d66ada
fix(rulesets): org rulesets must enter the floor cover set (#787)
hyperpolymath Sep 23, 2026
4c8b2f8
Update scripts/apply-protection-floor.sh
hyperpolymath Sep 23, 2026
dec21a3
docs(scripts): document protection floor and test helper functions
coderabbitai[bot] Sep 23, 2026
8de0ea0
docs(rulesets): README said the applier was missing (#787)
hyperpolymath Sep 23, 2026
c815fb3
fix(rulesets): a throttled 403 is UNKNOWN, not PLAN-EXCLUDED (#787)
hyperpolymath Sep 23, 2026
34a5404
fix(rulesets): stop the sweep at the rate wall, don't grind (#787)
hyperpolymath Sep 23, 2026
209987e
Update scripts/apply-protection-floor.sh
hyperpolymath Sep 23, 2026
2a70faf
Update scripts/tests/protection-floor-test.sh
hyperpolymath Sep 23, 2026
ce05ccc
Merge branch 'main' into feat/protection-floor
hyperpolymath Sep 23, 2026
6400446
fix(rulesets): count the throttle streak in repos, not calls (#787)
hyperpolymath Sep 23, 2026
09d0e5a
docs(protection-floor): clarify throttling helper behavior
coderabbitai[bot] Sep 23, 2026
3552411
Merge branch 'main' into feat/protection-floor
hyperpolymath Sep 23, 2026
5004f28
Merge branch 'main' into feat/protection-floor
hyperpolymath Sep 23, 2026
21c33ef
Merge branch 'main' into feat/protection-floor
hyperpolymath Sep 23, 2026
444fcba
Merge branch 'main' into feat/protection-floor
hyperpolymath Sep 23, 2026
945cc38
fix(protection): Validate inherited org rulesets
coderabbitai[bot] Sep 23, 2026
a889610
Update scripts/apply-protection-floor.sh
hyperpolymath Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions config/rulesets/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,65 @@ baseline; `Optimus-Extras.json` carries the extras for repos on the Optimus prof
`gates-only.json` / `gates.json` describe which workflow files become required contexts;
`immutable-tags.json` is the tag canon.

== Nothing here is applied automatically
`branch-floor.json` and `tag-floor.json` are the *base protection floor* — deliberately
**not** named `base*.json`, because `base.json` is the richer tier and the collision would
mislead every future reader. The floor is `deletion` + `non_fast_forward` with
`bypass_actors: []`; branch target scopes to `~DEFAULT_BRANCH`, tag target to `~ALL`.
`gcrypt-vault-class.txt` is the D50 exclusion class for that floor (see below).

== The floor IS applied by a committed script; the richer tiers still are not

`scripts/apply-tag-ruleset-canon.sh` converges *only* the tag ruleset, and its own header
records the consequence: after the 2026-09-11 deployment wave the branch-side remediation was
"manual per-repo `gh api` PUTs, because no committed script in this repository globs
`config/rulesets/`". That gap is why `Optimus-Extras.json` could keep four constraints §7.3
retired (`code_coverage`, `code_quality`, `required_deployments`, `copilot_code_review`) and
re-add them on the next manual convergence, on 120-162 active rulesets estate-wide. The file is
now trimmed; the propagation mechanism is still missing by design, and this note is the
now trimmed; that propagation gap is still open for the richer tiers, and this note is the
reminder that a template fix without an applier is a half fix.

`scripts/apply-branch-gates.sh` (required-status-check gates) and
`scripts/apply-protection-floor.sh` (the base floor) close the gap for their own scopes.
Both default to **report-only**; writes need `--apply`, never the inverse.

=== Reading a floor requires `bypass_actors`, not just the rule list

A ruleset's rules say what it forbids; `bypass_actors` says whom it forbids it to. A floor
read without the second column is not a floor. Measured 2026-09-23: `metadatastician`'s
`EstateTagging` carries every floor rule and **three actors that bypass it `always`**, and
`EstateBranching` carries **nine** bypass actors — so "add the missing field to
`EstateBranching`" would have shipped a floor nine actors could walk through. The cure is a
**standalone** zero-bypass ruleset alongside (`Branch-Floor` 23868655, `Tag-Floor` 23868851),
never an edit to a richer ruleset: a floor that lives inside a richer ruleset dies the day
that ruleset is disabled, as 375 rulesets were on 2026-09-22.

=== Org cover is a distinct state from repo cover

`GET /repos/{o}/{r}/rulesets` returns org-inherited rulesets too, discriminated by
`.source_type`. They put rules **in force** but cannot be written per repo, so the applier
keeps them in a separate cover set and reports `ORG-INHERITED` rather than
`COVERED-BY-RICHER`. Conflating the two in either direction is a real defect: counting org
rulesets without unioning their rule types made `ORG-INHERITED` unreachable and reported 67
already-covered repos as needing a write. An **absent** `.source_type` is a REFUSAL, never a
default to the writable arm.

=== A half cover is not a cover

`EstateBranching` carries `deletion` and not `non_fast_forward`. A repo covered only by it is
`WOULD-CREATE`, not covered. The regression suite pins this with a fixture of exactly that
shape.

=== 🚨 The gcrypt vault class must be excluded, by explicit list

`hyperpolymath/dev-notes-vault` and `hyperpolymath/memory-vault` carry a deliberately
`deletion`-only `Gcrypt-Vault-Guard` (**D50**): git-remote-gcrypt **force-pushes on every
sync**, so `non_fast_forward` would silently stop the hourly backup at the next timer fire.
Any logic reading *"has `deletion`, lacks `non_fast_forward` ⇒ complete the floor"* writes
exactly that rule. Membership is the explicit list in `gcrypt-vault-class.txt` and **never a
name regex** — `reasonably-good-token-vault` and `befunge93-vault-cracker` match `/vault/`
and genuinely need the floor. A missing class file is a hard refusal, because an absent
exclusion list is indistinguishable from an empty one.

`scripts/plan-ruleset-constraint-repair.rb` is the sanctioned planner: it emits a reviewable PUT
body for exactly those retired constraints, optionally dropping one integration's bypass once
GitHub has rejected that app as no longer installed. It never calls GitHub.
Expand Down
16 changes: 16 additions & 0 deletions config/rulesets/branch-floor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "Branch-Floor",
"target": "branch",
"enforcement": "active",
"bypass_actors": [],
"conditions": {
"ref_name": {
"include": ["~DEFAULT_BRANCH"],
"exclude": []
}
},
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" }
]
}
17 changes: 17 additions & 0 deletions config/rulesets/gcrypt-vault-class.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# The gcrypt-vault exclusion class -- owner ruling D50, 2026-09-14T20:58Z.
#
# A git-remote-gcrypt vault FORCE-PUSHES ON EVERY SYNC: the remote ref is a rolling
# pointer at the newest encrypted pack and is never a fast-forward of the previous one.
# Applying `non_fast_forward` to a member does not harden it -- it silently stops the
# hourly backup at the next timer fire.
#
# Members carry `Gcrypt-Vault-Guard` instead: `deletion` only, on `~ALL` (a gcrypt vault
# has exactly ONE remote ref and it is `master`, so a `main`-scoped rule is a fake gate).
#
# ⚠ MEMBERSHIP IS THIS LIST, NEVER A NAME MATCH. `reasonably-good-token-vault` and
# `befunge93-vault-cracker` both match /vault/ and are NOT members -- they are ordinary
# source repos that genuinely need the floor.
#
# Class record: dev-notes/estate-management/gcrypt-vault-protection-class-2026-09-14/
hyperpolymath/dev-notes-vault
hyperpolymath/memory-vault
16 changes: 16 additions & 0 deletions config/rulesets/tag-floor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "Tag-Floor",
"target": "tag",
"enforcement": "active",
"bypass_actors": [],
"conditions": {
"ref_name": {
"include": ["~ALL"],
"exclude": []
}
},
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" }
]
}
Loading
Loading