From 401fa7f6e461516393d616318cdcdcc3e4cc2661 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Tue, 21 Jul 2026 19:29:55 +0000 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20implement=20issue=20#324=20?= =?UTF-8?q?=E2=80=94=20Compliance:=20ruleset-drift-pr-quality-dismiss=5Fst?= =?UTF-8?q?ale=5Freviews=5Fon=5Fpush?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/scripts/apply-pr-quality-ruleset.sh | 88 +++++++++++++++++++ .../tests/apply-pr-quality-ruleset.bats | 49 +++++++++++ 2 files changed, 137 insertions(+) create mode 100755 .github/scripts/apply-pr-quality-ruleset.sh create mode 100644 .github/scripts/tests/apply-pr-quality-ruleset.bats diff --git a/.github/scripts/apply-pr-quality-ruleset.sh b/.github/scripts/apply-pr-quality-ruleset.sh new file mode 100755 index 00000000..c6fdc3fa --- /dev/null +++ b/.github/scripts/apply-pr-quality-ruleset.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# apply-pr-quality-ruleset.sh — Idempotently create/update the pr-quality ruleset +# +# This script creates (or updates) the `pr-quality` repository ruleset that +# enforces pull-request quality gates on the default branch of +# petry-projects/markets. +# +# Pull-request rule parameters configured (codified standard, source of truth: +# petry-projects/.github/standards/rulesets/pr-quality.json — see #324): +# - required_approving_review_count: 1 +# - require_code_owner_review: true +# - required_review_thread_resolution: true +# - dismiss_stale_reviews_on_push: true (the drifted parameter — issue #324) +# - require_last_push_approval: true +# - allowed_merge_methods: ["squash"] +# +# Standard reference: +# https://github.com/petry-projects/.github/blob/main/standards/github-settings.md#pr-quality--standard-ruleset-all-repositories +# +# Usage: +# GH_TOKEN= bash .github/scripts/apply-pr-quality-ruleset.sh +# +# The org-level script (petry-projects/.github/scripts/apply-rulesets.sh) is the +# canonical tool for managing rulesets across all repos. This script exists as a +# repo-local reference and fallback for the markets repository specifically. + +set -euo pipefail + +REPO="petry-projects/markets" +RULESET_NAME="pr-quality" + +if [ -z "${GH_TOKEN:-}" ]; then + echo "ERROR: GH_TOKEN is required with administration:write scope" >&2 + exit 1 +fi + +export GH_TOKEN + +# Fetch existing rulesets +EXISTING_ID=$(gh api "repos/$REPO/rulesets" \ + --jq ".[] | select(.name == \"$RULESET_NAME\") | .id" 2>/dev/null || true) + +PAYLOAD=$(jq -n '{ + name: "pr-quality", + target: "branch", + enforcement: "active", + conditions: { + ref_name: { + include: ["~DEFAULT_BRANCH"], + exclude: [] + } + }, + rules: [ + { + type: "pull_request", + parameters: { + required_approving_review_count: 1, + require_code_owner_review: true, + required_review_thread_resolution: true, + dismiss_stale_reviews_on_push: true, + require_last_push_approval: true, + allowed_merge_methods: ["squash"] + } + } + ], + bypass_actors: [ + { + actor_type: "OrganizationAdmin", + bypass_mode: "always" + }, + { + actor_id: 3167543, + actor_type: "Integration", + bypass_mode: "always" + } + ] +}') + +if [ -n "$EXISTING_ID" ]; then + echo "Updating existing $RULESET_NAME ruleset (id=$EXISTING_ID) ..." + echo "$PAYLOAD" | gh api -X PUT "repos/$REPO/rulesets/$EXISTING_ID" --input - > /dev/null + echo "Done — ruleset updated: https://github.com/$REPO/rules/$EXISTING_ID" +else + echo "Creating $RULESET_NAME ruleset ..." + RESULT=$(echo "$PAYLOAD" | gh api -X POST "repos/$REPO/rulesets" --input -) + NEW_ID=$(echo "$RESULT" | jq -r '.id') + echo "Done — ruleset created: https://github.com/$REPO/rules/$NEW_ID" +fi diff --git a/.github/scripts/tests/apply-pr-quality-ruleset.bats b/.github/scripts/tests/apply-pr-quality-ruleset.bats new file mode 100644 index 00000000..2fadd92d --- /dev/null +++ b/.github/scripts/tests/apply-pr-quality-ruleset.bats @@ -0,0 +1,49 @@ +#!/usr/bin/env bats +# Tests for apply-pr-quality-ruleset.sh — static content assertions, no live API calls. +# +# The pull_request rule parameters asserted below are the codified standard +# (petry-projects/.github/standards/rulesets/pr-quality.json), the source of +# truth referenced by issue #324. + +SCRIPT="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/apply-pr-quality-ruleset.sh" + +@test "script exists and is executable" { + [ -f "$SCRIPT" ] + [ -x "$SCRIPT" ] +} + +@test "script uses set -euo pipefail" { + grep -q 'set -euo pipefail' "$SCRIPT" +} + +@test "script targets petry-projects/markets repo" { + grep -q 'petry-projects/markets' "$SCRIPT" +} + +@test "script declares the pr-quality ruleset" { + grep -q 'name: "pr-quality"' "$SCRIPT" +} + +@test "script sets dismiss_stale_reviews_on_push to true (issue #324)" { + grep -qE 'dismiss_stale_reviews_on_push:\s*true' "$SCRIPT" +} + +@test "script requires one approving review" { + grep -qE 'required_approving_review_count:\s*1' "$SCRIPT" +} + +@test "script requires code owner review" { + grep -qE 'require_code_owner_review:\s*true' "$SCRIPT" +} + +@test "script requires review thread resolution" { + grep -qE 'required_review_thread_resolution:\s*true' "$SCRIPT" +} + +@test "script requires last push approval" { + grep -qE 'require_last_push_approval:\s*true' "$SCRIPT" +} + +@test "script allows only the squash merge method" { + grep -qE 'allowed_merge_methods:\s*\["squash"\]' "$SCRIPT" +} From 2e7c71ed14b320e2406e325d834f3e671feb406f Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Tue, 21 Jul 2026 19:44:20 +0000 Subject: [PATCH 2/4] chore: dev-lead update (review-changes) [skip ci-relay] --- .github/scripts/apply-pr-quality-ruleset.sh | 2 +- .../scripts/tests/apply-pr-quality-ruleset.bats | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/scripts/apply-pr-quality-ruleset.sh b/.github/scripts/apply-pr-quality-ruleset.sh index c6fdc3fa..4f587454 100755 --- a/.github/scripts/apply-pr-quality-ruleset.sh +++ b/.github/scripts/apply-pr-quality-ruleset.sh @@ -38,7 +38,7 @@ export GH_TOKEN # Fetch existing rulesets EXISTING_ID=$(gh api "repos/$REPO/rulesets" \ - --jq ".[] | select(.name == \"$RULESET_NAME\") | .id" 2>/dev/null || true) + --jq ".[] | select(.name == \"$RULESET_NAME\") | .id") PAYLOAD=$(jq -n '{ name: "pr-quality", diff --git a/.github/scripts/tests/apply-pr-quality-ruleset.bats b/.github/scripts/tests/apply-pr-quality-ruleset.bats index 2fadd92d..be6ca31b 100644 --- a/.github/scripts/tests/apply-pr-quality-ruleset.bats +++ b/.github/scripts/tests/apply-pr-quality-ruleset.bats @@ -21,29 +21,29 @@ SCRIPT="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/apply-pr-quality-rule } @test "script declares the pr-quality ruleset" { - grep -q 'name: "pr-quality"' "$SCRIPT" + grep -qE 'name[[:space:]]*:[[:space:]]*"pr-quality"' "$SCRIPT" } @test "script sets dismiss_stale_reviews_on_push to true (issue #324)" { - grep -qE 'dismiss_stale_reviews_on_push:\s*true' "$SCRIPT" + grep -qE 'dismiss_stale_reviews_on_push[[:space:]]*:[[:space:]]*true' "$SCRIPT" } @test "script requires one approving review" { - grep -qE 'required_approving_review_count:\s*1' "$SCRIPT" + grep -qE 'required_approving_review_count[[:space:]]*:[[:space:]]*1' "$SCRIPT" } @test "script requires code owner review" { - grep -qE 'require_code_owner_review:\s*true' "$SCRIPT" + grep -qE 'require_code_owner_review[[:space:]]*:[[:space:]]*true' "$SCRIPT" } @test "script requires review thread resolution" { - grep -qE 'required_review_thread_resolution:\s*true' "$SCRIPT" + grep -qE 'required_review_thread_resolution[[:space:]]*:[[:space:]]*true' "$SCRIPT" } @test "script requires last push approval" { - grep -qE 'require_last_push_approval:\s*true' "$SCRIPT" + grep -qE 'require_last_push_approval[[:space:]]*:[[:space:]]*true' "$SCRIPT" } @test "script allows only the squash merge method" { - grep -qE 'allowed_merge_methods:\s*\["squash"\]' "$SCRIPT" + grep -qE 'allowed_merge_methods[[:space:]]*:[[:space:]]*\[[[:space:]]*"squash"[[:space:]]*\]' "$SCRIPT" } From 0eddcda9cb60f3d49900b2deea19e27b9d92ff99 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Tue, 21 Jul 2026 19:57:26 +0000 Subject: [PATCH 3/4] chore: dev-lead update (review-changes) [skip ci-relay] --- .../scripts/tests/apply-pr-quality-ruleset.bats | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/scripts/tests/apply-pr-quality-ruleset.bats b/.github/scripts/tests/apply-pr-quality-ruleset.bats index be6ca31b..ca67c5fe 100644 --- a/.github/scripts/tests/apply-pr-quality-ruleset.bats +++ b/.github/scripts/tests/apply-pr-quality-ruleset.bats @@ -17,33 +17,33 @@ SCRIPT="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/apply-pr-quality-rule } @test "script targets petry-projects/markets repo" { - grep -q 'petry-projects/markets' "$SCRIPT" + grep -qE '^REPO="petry-projects/markets"$' "$SCRIPT" } @test "script declares the pr-quality ruleset" { - grep -qE 'name[[:space:]]*:[[:space:]]*"pr-quality"' "$SCRIPT" + grep -qE '^[[:space:]]+name[[:space:]]*:[[:space:]]*"pr-quality"' "$SCRIPT" } @test "script sets dismiss_stale_reviews_on_push to true (issue #324)" { - grep -qE 'dismiss_stale_reviews_on_push[[:space:]]*:[[:space:]]*true' "$SCRIPT" + grep -qE '^[[:space:]]+dismiss_stale_reviews_on_push[[:space:]]*:[[:space:]]*true[[:space:]]*,?[[:space:]]*$' "$SCRIPT" } @test "script requires one approving review" { - grep -qE 'required_approving_review_count[[:space:]]*:[[:space:]]*1' "$SCRIPT" + grep -qE '^[[:space:]]+required_approving_review_count[[:space:]]*:[[:space:]]*1[[:space:]]*,?[[:space:]]*$' "$SCRIPT" } @test "script requires code owner review" { - grep -qE 'require_code_owner_review[[:space:]]*:[[:space:]]*true' "$SCRIPT" + grep -qE '^[[:space:]]+require_code_owner_review[[:space:]]*:[[:space:]]*true[[:space:]]*,?[[:space:]]*$' "$SCRIPT" } @test "script requires review thread resolution" { - grep -qE 'required_review_thread_resolution[[:space:]]*:[[:space:]]*true' "$SCRIPT" + grep -qE '^[[:space:]]+required_review_thread_resolution[[:space:]]*:[[:space:]]*true[[:space:]]*,?[[:space:]]*$' "$SCRIPT" } @test "script requires last push approval" { - grep -qE 'require_last_push_approval[[:space:]]*:[[:space:]]*true' "$SCRIPT" + grep -qE '^[[:space:]]+require_last_push_approval[[:space:]]*:[[:space:]]*true[[:space:]]*,?[[:space:]]*$' "$SCRIPT" } @test "script allows only the squash merge method" { - grep -qE 'allowed_merge_methods[[:space:]]*:[[:space:]]*\[[[:space:]]*"squash"[[:space:]]*\]' "$SCRIPT" + grep -qE '^[[:space:]]+allowed_merge_methods[[:space:]]*:[[:space:]]*\[[[:space:]]*"squash"[[:space:]]*\][[:space:]]*$' "$SCRIPT" } From 618baaf41f2a17236ccf604ff39c5ff818763f05 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:28:00 +0000 Subject: [PATCH 4/4] chore: dev-lead update (review-changes) [skip ci-relay] --- .github/scripts/apply-pr-quality-ruleset.sh | 20 ++++++++++++++++--- .github/scripts/apply-repo-settings.sh | 4 ++++ .../scripts/tests/apply-repo-settings.bats | 4 ++++ .github/workflows/apply-repo-settings.yml | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/scripts/apply-pr-quality-ruleset.sh b/.github/scripts/apply-pr-quality-ruleset.sh index 4f587454..6d038966 100755 --- a/.github/scripts/apply-pr-quality-ruleset.sh +++ b/.github/scripts/apply-pr-quality-ruleset.sh @@ -82,7 +82,21 @@ if [ -n "$EXISTING_ID" ]; then echo "Done — ruleset updated: https://github.com/$REPO/rules/$EXISTING_ID" else echo "Creating $RULESET_NAME ruleset ..." - RESULT=$(echo "$PAYLOAD" | gh api -X POST "repos/$REPO/rulesets" --input -) - NEW_ID=$(echo "$RESULT" | jq -r '.id') - echo "Done — ruleset created: https://github.com/$REPO/rules/$NEW_ID" + # Capture stderr so a 422 conflict from concurrent creation doesn't terminate the script. + # On conflict, re-fetch the ID created by the concurrent run and update instead. + if RESULT=$(echo "$PAYLOAD" | gh api -X POST "repos/$REPO/rulesets" --input - 2>&1); then + NEW_ID=$(echo "$RESULT" | jq -r '.id') + echo "Done — ruleset created: https://github.com/$REPO/rules/$NEW_ID" + else + EXISTING_ID=$(gh api "repos/$REPO/rulesets" \ + --jq ".[] | select(.name == \"$RULESET_NAME\") | .id") + if [ -z "$EXISTING_ID" ]; then + echo "ERROR: POST failed and no existing $RULESET_NAME ruleset found" >&2 + echo "$RESULT" >&2 + exit 1 + fi + echo "Concurrent creation detected — updating $RULESET_NAME ruleset (id=$EXISTING_ID) ..." + echo "$PAYLOAD" | gh api -X PUT "repos/$REPO/rulesets/$EXISTING_ID" --input - > /dev/null + echo "Done — ruleset updated: https://github.com/$REPO/rules/$EXISTING_ID" + fi fi diff --git a/.github/scripts/apply-repo-settings.sh b/.github/scripts/apply-repo-settings.sh index c15cfbdf..773bac90 100755 --- a/.github/scripts/apply-repo-settings.sh +++ b/.github/scripts/apply-repo-settings.sh @@ -61,4 +61,8 @@ echo "Applying code-quality required-status-checks ruleset ..." "$SCRIPT_DIR/apply-code-quality-ruleset.sh" +echo "Applying pr-quality required-review ruleset ..." + +"$SCRIPT_DIR/apply-pr-quality-ruleset.sh" + echo "Done — repository settings applied: https://github.com/$REPO/settings" diff --git a/.github/scripts/tests/apply-repo-settings.bats b/.github/scripts/tests/apply-repo-settings.bats index c9aa4156..6ab7c21c 100755 --- a/.github/scripts/tests/apply-repo-settings.bats +++ b/.github/scripts/tests/apply-repo-settings.bats @@ -33,3 +33,7 @@ SCRIPT="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/apply-repo-settings.s @test "script wires in code-quality ruleset application (issue #375)" { grep -q 'apply-code-quality-ruleset.sh' "$SCRIPT" } + +@test "script wires in pr-quality ruleset application (issue #324)" { + grep -q 'apply-pr-quality-ruleset.sh' "$SCRIPT" +} diff --git a/.github/workflows/apply-repo-settings.yml b/.github/workflows/apply-repo-settings.yml index 51d95e01..3f462fa4 100644 --- a/.github/workflows/apply-repo-settings.yml +++ b/.github/workflows/apply-repo-settings.yml @@ -17,6 +17,7 @@ on: paths: - .github/scripts/apply-repo-settings.sh - .github/scripts/apply-code-quality-ruleset.sh + - .github/scripts/apply-pr-quality-ruleset.sh - .github/workflows/apply-repo-settings.yml permissions: {}