From 383ba154ee151b5536721ef52595c50829e2dd85 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 23 Sep 2026 00:52:58 +0100 Subject: [PATCH] Drop the GNU-only grep -P, and name the credential the cure needs Two hardening follow-ups on the org-inherited fix: 1. `grep -P` is a GNU extension. The classification of active branch rulesets into repo-level vs org-inherited is the load-bearing step of this script; it must not depend on which grep the runner ships. awk with an explicit `-F'\t'` does the same field test and is portable. Mutant F is retargeted at the awk line so the control still dies. 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 ruleset in full and cannot write it, which is precisely the asymmetry that produced the original 404. Saying so in the report line saves the operator a second 403 chase. Suite: 46/46, mutant F still killed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Ji1bq3TypfycfUPAR7hSxR --- scripts/apply-branch-gates.sh | 10 ++++++---- scripts/tests/branch-gates-apply-test.sh | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/apply-branch-gates.sh b/scripts/apply-branch-gates.sh index a8470148..74bc2cbb 100755 --- a/scripts/apply-branch-gates.sh +++ b/scripts/apply-branch-gates.sh @@ -349,16 +349,18 @@ while IFS= read -r R; do || { emit "$R" "UNKNOWN" "rulesets GET failed"; continue; } jq -r '.[]|select(.target=="branch" and .enforcement=="active") |[(.source_type // "MISSING"), (.id|tostring)]|@tsv' "$WORK/rs.json" > "$WORK/active" - command grep -P '^Repository\t' "$WORK/active" | cut -f2 > "$WORK/ids" - command grep -vP '^(Repository|MISSING)\t' "$WORK/active" | cut -f2 > "$WORK/inherited" - NMISS=$(command grep -cP '^MISSING\t' "$WORK/active" || true) + # awk, not grep -P: -P is a GNU extension and this script must not depend on + # which grep the runner ships. + awk -F'\t' '$1=="Repository"{print $2}' "$WORK/active" > "$WORK/ids" + awk -F'\t' '$1!="Repository" && $1!="MISSING"{print $2}' "$WORK/active" > "$WORK/inherited" + NMISS=$(awk -F'\t' '$1=="MISSING"{c++} END{print c+0}' "$WORK/active") NIDS=$(wc -l < "$WORK/ids") NINH=$(wc -l < "$WORK/inherited") # An absent discriminator REFUSES; it never defaults to the writable arm. [ "${NMISS:-0}" -gt 0 ] && { emit "$R" "UNKNOWN" "$DETAIL — $NMISS active branch ruleset(s) carry no .source_type; cannot tell repo-level from org-inherited, refusing to guess"; continue; } if [ "$NIDS" -eq 0 ] && [ "$NINH" -gt 0 ]; then - emit "$R" "ORG-INHERITED" "$DETAIL — the only active branch ruleset(s) here are org-level ($(paste -sd, "$WORK/inherited")); writable ONLY at /orgs/{org}/rulesets/{id}, cured once at the org, never per repo" + emit "$R" "ORG-INHERITED" "$DETAIL — the only active branch ruleset(s) here are org-level ($(paste -sd, "$WORK/inherited")); writable ONLY at /orgs/{org}/rulesets/{id} with an admin:org credential (a repo token reads it and cannot write it), cured once at the org, never per repo" continue fi [ "$NIDS" -eq 0 ] && { emit "$R" "NORULESET" "$DETAIL — no active branch ruleset; this script never creates one"; continue; } diff --git a/scripts/tests/branch-gates-apply-test.sh b/scripts/tests/branch-gates-apply-test.sh index 685bc72f..0d086a47 100755 --- a/scripts/tests/branch-gates-apply-test.sh +++ b/scripts/tests/branch-gates-apply-test.sh @@ -515,7 +515,7 @@ S=$(state_of "$OUT") # ---- MUTANT F: delete the source_type filter, restoring the original defect. -- # The pre-fix line selected every active branch ruleset regardless of ownership. MUTF="$WORK/mutant-f.sh" -sed 's|^ command grep -P .\^Repository.*> "\$WORK/ids"$| cut -f2 "$WORK/active" > "$WORK/ids"|' "$APPLIER" > "$MUTF" +sed 's|^ awk -F.\\t. ..1=="Repository".*> "\$WORK/ids"$| cut -f2 "$WORK/active" > "$WORK/ids"|' "$APPLIER" > "$MUTF" chmod +x "$MUTF" if ! cmp -s "$MUTF" "$APPLIER" && bash -n "$MUTF" 2>/dev/null; then reset_fix