diff --git a/.github/scripts/apply-code-quality-ruleset.sh b/.github/scripts/apply-code-quality-ruleset.sh index 1e1dd419..932f64e6 100755 --- a/.github/scripts/apply-code-quality-ruleset.sh +++ b/.github/scripts/apply-code-quality-ruleset.sh @@ -11,8 +11,13 @@ # - agent-shield / AgentShield agent-shield.yml, caller job: agent-shield, reusable job: AgentShield # - dependency-audit / Detect ecosystems dependency-audit.yml, caller job: dependency-audit, reusable job: Detect ecosystems # +# Bypass actors configured (required on every ruleset targeting main — see #399): +# - OrganizationAdmin (bypass_mode: always) emergency admin override +# - dependabot-automerge-petry (actor_id: 3167543, bypass_mode: always) enables Dependabot auto-merge +# # Standard reference: # https://github.com/petry-projects/.github/blob/main/standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories +# https://github.com/petry-projects/.github/blob/main/standards/github-settings.md#bypass-actors--required-on-every-ruleset-targeting-main # # Usage: # GH_TOKEN= bash .github/scripts/apply-code-quality-ruleset.sh @@ -37,7 +42,30 @@ export GH_TOKEN EXISTING_ID=$(gh api "repos/$REPO/rulesets" \ --jq ".[] | select(.name == \"$RULESET_NAME\") | .id" 2>/dev/null || true) -PAYLOAD=$(jq -n '{ +# Required bypass actors per org standard (see #399) +REQUIRED_BYPASS_ORG_ADMIN='{"actor_type":"OrganizationAdmin","bypass_mode":"always"}' +REQUIRED_BYPASS_DEPENDABOT='{"actor_id":3167543,"actor_type":"Integration","bypass_mode":"always"}' +REQUIRED_BYPASS_ACTORS=$(jq -n \ + --argjson org_admin "$REQUIRED_BYPASS_ORG_ADMIN" \ + --argjson dependabot "$REQUIRED_BYPASS_DEPENDABOT" \ + '[$org_admin, $dependabot]') + +# When updating an existing ruleset, fetch its current bypass actors and merge them +# with the required actors so that other legitimate bypass actors are not silently removed. +if [ -n "$EXISTING_ID" ]; then + EXISTING_BYPASS=$(gh api "repos/$REPO/rulesets/$EXISTING_ID" \ + --jq '.bypass_actors // []' 2>/dev/null || echo "[]") + BYPASS_ACTORS=$(echo "$EXISTING_BYPASS" | \ + jq --argjson req "$REQUIRED_BYPASS_ACTORS" \ + 'map(select( + .actor_type != "OrganizationAdmin" and + (.actor_type != "Integration" or .actor_id != 3167543) + )) + $req') +else + BYPASS_ACTORS=$REQUIRED_BYPASS_ACTORS +fi + +PAYLOAD=$(jq -n --argjson bypass_actors "$BYPASS_ACTORS" '{ name: "code-quality", target: "branch", enforcement: "active", @@ -61,7 +89,7 @@ PAYLOAD=$(jq -n '{ } } ], - bypass_actors: [] + bypass_actors: $bypass_actors }') if [ -n "$EXISTING_ID" ]; then diff --git a/.github/scripts/tests/apply-code-quality-ruleset.bats b/.github/scripts/tests/apply-code-quality-ruleset.bats index b6836b93..1a2b9ff8 100644 --- a/.github/scripts/tests/apply-code-quality-ruleset.bats +++ b/.github/scripts/tests/apply-code-quality-ruleset.bats @@ -44,3 +44,52 @@ assert_required_check() { @test "script requires the dependency-audit / Detect ecosystems status check" { assert_required_check "dependency-audit / Detect ecosystems" } + +@test "payload includes the OrganizationAdmin bypass actor with bypass_mode always" { + export GH_TOKEN="mock-token" + gh() { + if [ "$1" = "api" ]; then + if [ "$2" = "repos/petry-projects/markets/rulesets" ]; then + echo "" + return 0 + elif [ "$2" = "-X" ] && [ "$3" = "POST" ]; then + cat > "$BATS_TEST_TMPDIR/payload.json" + echo '{"id": 12345}' + return 0 + fi + fi + command gh "$@" + } + export -f gh + + run bash "$SCRIPT" + [ "$status" -eq 0 ] + + jq -e '.bypass_actors == [{"actor_type":"OrganizationAdmin","bypass_mode":"always"},{"actor_id":3167543,"actor_type":"Integration","bypass_mode":"always"}]' "$BATS_TEST_TMPDIR/payload.json" +} + +@test "update preserves existing bypass actors while ensuring OrganizationAdmin is present" { + export GH_TOKEN="mock-token" + gh() { + if [ "$1" = "api" ]; then + if [ "$2" = "repos/petry-projects/markets/rulesets" ]; then + echo "999" + return 0 + elif [ "$2" = "repos/petry-projects/markets/rulesets/999" ] && [ "$3" = "--jq" ]; then + echo '[{"actor_type":"RepositoryRole","bypass_mode":"pull_request"}]' + return 0 + elif [ "$2" = "-X" ] && [ "$3" = "PUT" ]; then + cat > "$BATS_TEST_TMPDIR/payload.json" + echo '{}' + return 0 + fi + fi + command gh "$@" + } + export -f gh + + run bash "$SCRIPT" + [ "$status" -eq 0 ] + + jq -e '.bypass_actors == [{"actor_type":"RepositoryRole","bypass_mode":"pull_request"},{"actor_type":"OrganizationAdmin","bypass_mode":"always"},{"actor_id":3167543,"actor_type":"Integration","bypass_mode":"always"}]' "$BATS_TEST_TMPDIR/payload.json" +}