Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions config/rulesets/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions config/rulesets/gcrypt-vault-class.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
30 changes: 23 additions & 7 deletions scripts/apply-protection-floor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand All @@ -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
Expand All @@ -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 <<EOF
$org_ids
Expand Down Expand Up @@ -326,8 +338,12 @@ EOF
fi

# 7. The write.
byp_note=""
if [ $((bypassable_n + org_bypassable_n)) -gt 0 ]; then
byp_note=" (D106: $bypassable_n repo + $org_bypassable_n org floor-scoped ruleset(s) carry bypass actors and are not cover)"
fi
if [ "$APPLY" -eq 0 ]; then
report "$repo" "WOULD-CREATE" "no floor in force; --apply would POST $(basename "$CANON")"
report "$repo" "WOULD-CREATE" "no floor in force$byp_note; --apply would POST $(basename "$CANON")"
continue
fi

Expand Down
48 changes: 43 additions & 5 deletions scripts/tests/apply-protection-floor-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ metadatastician/org-throttled
EOF
cat > "$REPOS" <<'EOF'
hyperpolymath/memory-vault
hyperpolymath/claude-config-vault
hyperpolymath/plain-repo
hyperpolymath/converged-repo
hyperpolymath/richer-repo
Expand All @@ -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.
Expand Down Expand Up @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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")"
Expand Down Expand Up @@ -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.
Expand Down
Loading