From 143978cd6dab5e6681ef7287e3f78f6071cb144f Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:02:27 +0100 Subject: [PATCH] fix(rulesets): bypassable rulesets are not floor cover (D106, D235) D106: a ruleset carrying any bypass actor no longer counts toward the repo-level or org-level cover union. A repo whose only cover is bypassable is WOULD-CREATE and --apply writes the standalone zero-bypass Branch-Floor alongside; nothing existing is modified. The report detail names how many bypassable rulesets were discounted. D235: hyperpolymath/claude-config-vault joins the gcrypt-vault class (already on Gcrypt-Vault-Guard 24256082), so the applier never writes non_fast_forward to it. Tests: 57 passed, 0 failed. New fixtures bypassed-twin (repo) and org-bypassed-repo (org) with one killed mutant each; richer-repo stays the zero-bypass negative control. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR --- config/rulesets/README.adoc | 10 ++++ config/rulesets/gcrypt-vault-class.txt | 2 + scripts/apply-protection-floor.sh | 30 +++++++++--- scripts/tests/apply-protection-floor-test.sh | 48 ++++++++++++++++++-- 4 files changed, 78 insertions(+), 12 deletions(-) diff --git a/config/rulesets/README.adoc b/config/rulesets/README.adoc index e3c351dd6..39ae46787 100644 --- a/config/rulesets/README.adoc +++ b/config/rulesets/README.adoc @@ -58,6 +58,16 @@ default to the writable arm. `WOULD-CREATE`, not covered. The regression suite pins this with a fixture of exactly that shape. +=== A bypassable cover is not a cover (D106) + +A ruleset with **any** `bypass_actors` entry contributes nothing to either cover set, repo-level +or org-level: the actors it names can walk through the floor, so it is not a floor. A repo whose +only cover is bypassable is `WOULD-CREATE`, and `--apply` writes the standalone zero-bypass +floor **alongside** it — the bypassable ruleset itself is never edited. The report detail counts +the bypassable rulesets it discounted. The suite pins both arms (`bypassed-twin`, +`org-bypassed-repo`) with a killed mutant each, and `richer-repo` (zero bypass) is the negative +control proving a genuine richer cover is still honoured. + === 🚨 The gcrypt vault class must be excluded, by explicit list `hyperpolymath/dev-notes-vault` and `hyperpolymath/memory-vault` carry a deliberately diff --git a/config/rulesets/gcrypt-vault-class.txt b/config/rulesets/gcrypt-vault-class.txt index ceb37657d..61d0bf795 100644 --- a/config/rulesets/gcrypt-vault-class.txt +++ b/config/rulesets/gcrypt-vault-class.txt @@ -13,5 +13,7 @@ # source repos that genuinely need the floor. # # Class record: dev-notes/estate-management/gcrypt-vault-protection-class-2026-09-14/ +# Third member (claude-config-vault, already on Gcrypt-Vault-Guard 24256082) added by D235. hyperpolymath/dev-notes-vault hyperpolymath/memory-vault +hyperpolymath/claude-config-vault diff --git a/scripts/apply-protection-floor.sh b/scripts/apply-protection-floor.sh index df0ba774e..ce164519d 100755 --- a/scripts/apply-protection-floor.sh +++ b/scripts/apply-protection-floor.sh @@ -125,7 +125,7 @@ FLOOR_TYPES="$(jq -r '[.rules[].type] | sort | join(",")' "$CANON")" CANON_BODY="$(jq -c . "$CANON")" VAULTS="$(command grep -vE '^[[:space:]]*(#|$)' "$VAULT_CLASS" | tr -d ' \t')" -[ -n "$VAULTS" ] || die "gcrypt-vault class file lists no members -- refusing; D50 names two" +[ -n "$VAULTS" ] || die "gcrypt-vault class file lists no members -- refusing; D50+D235 name three" TARGETS="$(command grep -vE '^[[:space:]]*(#|$)' "$REPOS_FILE" | tr -d ' \t' | sort -u)" [ -n "$TARGETS" ] || die "refusing to report a clean sweep over nothing: $REPOS_FILE yielded no repos" @@ -226,7 +226,7 @@ printf '%s\n' "$TARGETS" | while IFS= read -r repo; do org_ids="$(printf '%s' "$listing" | jq -r ".[]? | select(.source_type==\"Organization\" and .target==\"$TARGET\" and .enforcement==\"active\") | .id")" # 5. Walk the active repo-level rulesets of this target and classify. - exact_n=0; exact_ids=""; union="" + exact_n=0; exact_ids=""; union=""; bypassable_n=0 if [ -n "$repo_ids" ]; then while IFS= read -r rid; do [ -n "$rid" ] || continue @@ -239,9 +239,16 @@ printf '%s\n' "$TARGETS" | while IFS= read -r repo; do inc="$(printf '%s' "$body" | jq -c '.conditions.ref_name.include')" exc="$(printf '%s' "$body" | jq -c '[.conditions.ref_name.exclude[]?] | length')" byp="$(printf '%s' "$body" | jq -c '[.bypass_actors[]?] | length')" + # D106: a ruleset that named actors can walk through is NOT cover. Only a + # zero-bypass ruleset contributes to the union; a bypassable one is counted + # and reported, and the standalone floor is written alongside it. if [ "$exc" = "0" ] && printf '%s' "$body" | jq -e --argjson w "$WANT_INCLUDE" \ 'any(.conditions.ref_name.include[]?; . == $w[0])' >/dev/null 2>&1; then - union="$union,$types" + if [ "$byp" = "0" ]; then + union="$union,$types" + else + bypassable_n=$((bypassable_n + 1)) + fi fi if [ "$types" = "$FLOOR_TYPES" ] && [ "$inc" = "$WANT_INCLUDE" ] && [ "$exc" = "0" ] && [ "$byp" = "0" ]; then exact_n=$((exact_n + 1)); exact_ids="$exact_ids $rid" @@ -268,7 +275,7 @@ EOF # writable per repo, so they are kept in their OWN union: a repo-level cover can be # cured here, an org-level cover must be cured once at the org. Omitting this union # is what made ORG-INHERITED unreachable and reported 67 covered repos as WOULD-CREATE. - union_org=""; org_byp_max=0; org_read_ok=1 + union_org=""; org_byp_max=0; org_read_ok=1; org_bypassable_n=0 if [ -n "$org_ids" ]; then while IFS= read -r rid; do [ -n "$rid" ] || continue @@ -282,12 +289,17 @@ EOF fi fi if [ ! -s "$cache" ]; then org_read_ok=0; break; fi + b="$(jq -r '[.bypass_actors[]?] | length' "$cache")" if jq -e --argjson w "$WANT_INCLUDE" \ '([.conditions.ref_name.exclude[]?] | length) == 0 and any(.conditions.ref_name.include[]?; . == $w[0])' "$cache" >/dev/null 2>&1; then - union_org="$union_org,$(jq -r '[.rules[].type] | sort | join(",")' "$cache")" + # D106 applies to org rulesets too: bypassable org cover is not cover. + if [ "$b" = "0" ]; then + union_org="$union_org,$(jq -r '[.rules[].type] | sort | join(",")' "$cache")" + else + org_bypassable_n=$((org_bypassable_n + 1)) + fi fi - b="$(jq -r '[.bypass_actors[]?] | length' "$cache")" [ "$b" -gt "$org_byp_max" ] && org_byp_max="$b" done < "$REPOS" <<'EOF' hyperpolymath/memory-vault +hyperpolymath/claude-config-vault hyperpolymath/plain-repo hyperpolymath/converged-repo hyperpolymath/richer-repo @@ -116,6 +117,7 @@ hyperpolymath/secondary-throttled-repo hyperpolymath/nosourcetype-repo metadatastician/org-covered-repo metadatastician/org-halffloor-repo +metadatastician/org-bypassed-repo EOF # Rebuild fixtures, optionally making excluded repositories fully writable. @@ -156,7 +158,8 @@ J "conditions":{"ref_name":{"include":["~DEFAULT_BRANCH"],"exclude":[]}},"bypass_actors":[]} J - # same rule set and same include as the floor, but WITH a bypass actor. + # D106: same rule set and same include as the floor, but WITH a bypass actor. A ruleset + # named actors can walk through is NOT cover, so this repo gets the standalone floor. mkrepo hyperpolymath/bypassed-twin main false echo '[{"id":30,"source_type":"Repository","target":"branch","enforcement":"active"}]' \ > "$FIX/repos_hyperpolymath_bypassed-twin_rulesets" @@ -165,6 +168,9 @@ J "conditions":{"ref_name":{"include":["~DEFAULT_BRANCH"],"exclude":[]}}, "bypass_actors":[{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"always"}]} J + echo '{"id":9030}' > "$FIX/POST_repos_hyperpolymath_bypassed-twin_rulesets" + echo '[{"type":"deletion"},{"type":"non_fast_forward"}]' \ + > "$FIX/repos_hyperpolymath_bypassed-twin_rules_branches_main" mkrepo hyperpolymath/archived-repo main true # By default NO rulesets fixture: the archived guard must return before any such read. @@ -252,6 +258,20 @@ J echo '[{"type":"deletion"},{"type":"non_fast_forward"}]' \ > "$FIX/repos_metadatastician_org-halffloor-repo_rules_branches_main" + # D106, org side: an org ruleset carrying the WHOLE floor but with an always-bypass + # actor. Bypassable org cover is not cover, so this repo is WOULD-CREATE, not ORG-INHERITED. + mkrepo metadatastician/org-bypassed-repo main false + echo '[{"id":62,"source_type":"Organization","target":"branch","enforcement":"active"}]' \ + > "$FIX/repos_metadatastician_org-bypassed-repo_rulesets" + cat > "$FIX/repos_metadatastician_org-bypassed-repo_rulesets_62" <<'J' +{"id":62,"rules":[{"type":"deletion"},{"type":"non_fast_forward"}], + "conditions":{"ref_name":{"include":["~DEFAULT_BRANCH"],"exclude":[]}}, + "bypass_actors":[{"actor_id":2,"actor_type":"RepositoryRole","bypass_mode":"always"}]} +J + echo '{"id":9062}' > "$FIX/POST_repos_metadatastician_org-bypassed-repo_rulesets" + echo '[{"type":"deletion"},{"type":"non_fast_forward"}]' \ + > "$FIX/repos_metadatastician_org-bypassed-repo_rules_branches_main" + # A successful response without a rules array must not poison the org cache. The next # repo inherits the same org ruleset id and must retry the read rather than reuse it. mkrepo metadatastician/org-cache-invalid-a main false @@ -292,7 +312,11 @@ check "vault is EXCLUDED-D50" "EXCLUDED-D50" "$(state "$OUT" hyp check "bare repo is WOULD-CREATE" "WOULD-CREATE" "$(state "$OUT" hyperpolymath/plain-repo)" check "exact floor is CONVERGED" "CONVERGED" "$(state "$OUT" hyperpolymath/converged-repo)" check "richer cover is COVERED-BY-RICHER" "COVERED-BY-RICHER" "$(state "$OUT" hyperpolymath/richer-repo)" -check "bypassed twin is NOT converged" "COVERED-BY-RICHER" "$(state "$OUT" hyperpolymath/bypassed-twin)" +check "third vault is EXCLUDED-D50 (D235)" "EXCLUDED-D50" "$(state "$OUT" hyperpolymath/claude-config-vault)" +# D106: a bypassable ruleset is not cover. richer-repo (zero bypass) above is the negative +# control proving the cover arm still fires when nobody can walk through it. +check "bypassed twin is not cover (D106)" "WOULD-CREATE" "$(state "$OUT" hyperpolymath/bypassed-twin)" +check "bypassed org is not cover (D106)" "WOULD-CREATE" "$(state "$OUT" metadatastician/org-bypassed-repo)" check "archived is ARCHIVED" "ARCHIVED" "$(state "$OUT" hyperpolymath/archived-repo)" check "403 is PLAN-EXCLUDED" "PLAN-EXCLUDED" "$(state "$OUT" hyperpolymath/private-repo)" # The plan arm above is the NEGATIVE CONTROL: it proves the throttle arm below did not @@ -318,9 +342,13 @@ build_fixtures OUT="$(run "$SUT" --repos "$REPOS" --apply)" check "bare repo is CREATED" "CREATED" "$(state "$OUT" hyperpolymath/plain-repo)" check "half org cover still CREATED" "CREATED" "$(state "$OUT" metadatastician/org-halffloor-repo)" -# Exactly two repos lack a floor in force: the bare one and the HALF-org-covered one. -# Every other fixture must be left alone, so the count is an assertion in both directions. -check "exactly two writes" "2" "$(wc -l < "$FIX/PUTS.log" | tr -d ' ')" +# Exactly four repos lack a floor in force: the bare one, the HALF-org-covered one, and +# the two whose only cover is bypassable (D106). Every other fixture must be left alone, +# so the count is an assertion in both directions. +check "exactly four writes" "4" "$(wc -l < "$FIX/PUTS.log" | tr -d ' ')" +check "one write targets bypassed twin" "1" "$(command grep -c 'repos/hyperpolymath/bypassed-twin/rulesets' "$FIX/PUTS.log")" +check "one write targets bypassed org" "1" "$(command grep -c 'repos/metadatastician/org-bypassed-repo/rulesets' "$FIX/PUTS.log")" +check "no write touched the third vault" "0" "$(command grep -c 'claude-config-vault' "$FIX/PUTS.log")" check "every write is a POST" "POST" "$(cut -f1 "$FIX/PUTS.log" | sort -u)" check "one write targets plain-repo" "1" "$(command grep -c 'repos/hyperpolymath/plain-repo/rulesets' "$FIX/PUTS.log")" check "one write targets half-org repo" "1" "$(command grep -c 'repos/metadatastician/org-halffloor-repo/rulesets' "$FIX/PUTS.log")" @@ -428,6 +456,16 @@ mutant "bypass dropped from the shape test" \ 's/ \&\& \[ "\$byp" = "0" \]//' \ state "hyperpolymath/bypassed-twin=CONVERGED" +# D106, verbatim: letting a bypassable ruleset into the union reinstates "cover" that +# named actors can walk through, and the standalone floor is silently never written. +mutant "bypassable repo ruleset counted as cover" \ + 's/^ if \[ "\$byp" = "0" \]; then$/ if true; then/' \ + state "hyperpolymath/bypassed-twin=COVERED-BY-RICHER" + +mutant "bypassable org ruleset counted as cover" \ + 's/^ if \[ "\$b" = "0" \]; then$/ if true; then/' \ + state "metadatastician/org-bypassed-repo=ORG-INHERITED" + # Removing the converged early-return does NOT reach a write: the covered-by-richer check # catches it next. That second line of defence is the point, so this mutant is asserted on # the STATE it corrupts, not on a POST that correctly never happens.