From 725f38bd46b5ae063fb91df216408932b695a777 Mon Sep 17 00:00:00 2001 From: don-petry <{}+don-petry@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:16:40 +0000 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20implement=20issue=20#2018=20?= =?UTF-8?q?=E2=80=94=20Wire=20PR=5FLIMITS=5FORG=5FCAP=20org=20variable=20i?= =?UTF-8?q?nto=20the=20PR-limit=20admission=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev-lead-reusable.yml | 3 + .github/workflows/gh-aw-cross-org.yml | 3 + .../unit/test_fix_issue_pr_limit_gate.bats | 63 +++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/.github/workflows/dev-lead-reusable.yml b/.github/workflows/dev-lead-reusable.yml index 9d1fc9546..4d1f8ec18 100644 --- a/.github/workflows/dev-lead-reusable.yml +++ b/.github/workflows/dev-lead-reusable.yml @@ -549,6 +549,9 @@ jobs: ISSUE_NUMBER: ${{ env.INTENT_ISSUE_NUMBER }} REPO: ${{ github.repository }} REVIEW_ENGINE: ${{ env.DEV_LEAD_ENGINE }} + # Runtime override for the org-wide automation open-PR cap read by the + # PR-limit admission gate (#2018); unset → cap from pr-limits.json. + PR_LIMITS_ORG_CAP: ${{ vars.PR_LIMITS_ORG_CAP }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} GEMINI_API_KEY: ${{ secrets.GOOGLE_API_KEY }} diff --git a/.github/workflows/gh-aw-cross-org.yml b/.github/workflows/gh-aw-cross-org.yml index fe6c158c7..151703537 100644 --- a/.github/workflows/gh-aw-cross-org.yml +++ b/.github/workflows/gh-aw-cross-org.yml @@ -157,6 +157,9 @@ jobs: PROMPTS_DIR: ${{ github.workspace }}/dotgithub-private/prompts/dev-lead REVIEW_ENGINE: ${{ env.DEV_LEAD_ENGINE }} GH_TOKEN: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }} + # Runtime override for the org-wide automation open-PR cap read by the + # PR-limit admission gate (#2018); unset → cap from pr-limits.json. + PR_LIMITS_ORG_CAP: ${{ vars.PR_LIMITS_ORG_CAP }} working-directory: target-repo run: bash ${{ github.workspace }}/dotgithub-private/scripts/dev-lead-fix-issue.sh diff --git a/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats b/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats index 313a45a20..01ea88106 100644 --- a/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats +++ b/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats @@ -224,3 +224,66 @@ teardown() { [[ "$output" == *"::warning::"* ]] [[ "$output" == *"failing open"* ]] } + +# ── PR_LIMITS_ORG_CAP wiring (#2018) ───────────────────────────────────────── +# The shared guard's plg_effective_org_cap (petry-projects/.github#1221) reads +# PR_LIMITS_ORG_CAP as a runtime override for the org-wide cap. The org variable +# only takes effect if every workflow step that runs the gate maps it into env. + +# Overwrite the fake guard with one that records the PR_LIMITS_ORG_CAP it sees +# ("" when absent) so tests can assert the value reaches the gate. +_install_cap_recording_guard() { + CAP_SEEN_FILE="$(mktemp -u)"; export CAP_SEEN_FILE + cat > "$PLG_STANDARDS_DIR/scripts/lib/pr-limit-gate.sh" <<'GUARDEOF' +#!/usr/bin/env bash +plg_admission_gate() { + printf '%s' "${PR_LIMITS_ORG_CAP-}" > "$CAP_SEEN_FILE" + return 0 +} +GUARDEOF +} + +@test "pr-limit gate: PR_LIMITS_ORG_CAP from step env reaches plg_admission_gate" { + export DEV_LEAD_DRY_RUN="false" + export PR_LIMITS_ORG_CAP="42" + _install_cap_recording_guard + + run bash "$FIX_ISSUE_SCRIPT" + + [ "$status" -eq 0 ] + [ "$(cat "$CAP_SEEN_FILE")" = "42" ] + rm -f "$CAP_SEEN_FILE" +} + +@test "pr-limit gate: PR_LIMITS_ORG_CAP unset → script does not inject a cap" { + export DEV_LEAD_DRY_RUN="false" + unset PR_LIMITS_ORG_CAP + _install_cap_recording_guard + + run bash "$FIX_ISSUE_SCRIPT" + + [ "$status" -eq 0 ] + [ "$(cat "$CAP_SEEN_FILE")" = "" ] + rm -f "$CAP_SEEN_FILE" +} + +@test "pr-limit gate: every workflow step running dev-lead-fix-issue.sh maps vars.PR_LIMITS_ORG_CAP" { + command -v yq >/dev/null || skip "yq not installed" + local wf steps found=0 + for wf in "$SCRIPT_DIR"/.github/workflows/*.yml; do + # Step names (one per line) whose run: invokes the gate-running script. + steps=$(yq -r '.jobs[].steps[]? | select((.run // "") | test("dev-lead-fix-issue\\.sh")) | .name' "$wf") + [ -n "$steps" ] || continue + while IFS= read -r name; do + found=$((found + 1)) + local val + val=$(NAME="$name" yq -r '.jobs[].steps[]? | select(.name == strenv(NAME)) | .env.PR_LIMITS_ORG_CAP // ""' "$wf") + [ "$val" = '${{ vars.PR_LIMITS_ORG_CAP }}' ] || { + echo "$(basename "$wf"): step '$name' PR_LIMITS_ORG_CAP='$val' (want \${{ vars.PR_LIMITS_ORG_CAP }})" >&2 + return 1 + } + done <<< "$steps" + done + # Guard against the selector silently matching nothing. + [ "$found" -ge 2 ] +} From 70bc598ae95691e76d302a9e328f1aa3036a5d18 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:22:10 +0000 Subject: [PATCH 2/4] chore: dev-lead update (review-changes) [skip ci-relay] --- .github/workflows/dev-lead-reusable.yml | 2 +- .github/workflows/gh-aw-cross-org.yml | 2 +- .../unit/test_fix_issue_pr_limit_gate.bats | 21 ++++++++++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev-lead-reusable.yml b/.github/workflows/dev-lead-reusable.yml index 4d1f8ec18..7189d70dd 100644 --- a/.github/workflows/dev-lead-reusable.yml +++ b/.github/workflows/dev-lead-reusable.yml @@ -550,7 +550,7 @@ jobs: REPO: ${{ github.repository }} REVIEW_ENGINE: ${{ env.DEV_LEAD_ENGINE }} # Runtime override for the org-wide automation open-PR cap read by the - # PR-limit admission gate (#2018); unset → cap from pr-limits.json. + # PR-limit admission gate (#2018); empty/undefined → cap from pr-limits.json. PR_LIMITS_ORG_CAP: ${{ vars.PR_LIMITS_ORG_CAP }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} diff --git a/.github/workflows/gh-aw-cross-org.yml b/.github/workflows/gh-aw-cross-org.yml index 151703537..408161f97 100644 --- a/.github/workflows/gh-aw-cross-org.yml +++ b/.github/workflows/gh-aw-cross-org.yml @@ -158,7 +158,7 @@ jobs: REVIEW_ENGINE: ${{ env.DEV_LEAD_ENGINE }} GH_TOKEN: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }} # Runtime override for the org-wide automation open-PR cap read by the - # PR-limit admission gate (#2018); unset → cap from pr-limits.json. + # PR-limit admission gate (#2018); empty/undefined → cap from pr-limits.json. PR_LIMITS_ORG_CAP: ${{ vars.PR_LIMITS_ORG_CAP }} working-directory: target-repo run: bash ${{ github.workspace }}/dotgithub-private/scripts/dev-lead-fix-issue.sh diff --git a/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats b/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats index 01ea88106..f5bfd23ff 100644 --- a/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats +++ b/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats @@ -267,8 +267,27 @@ GUARDEOF rm -f "$CAP_SEEN_FILE" } +# In workflows, ${{ vars.PR_LIMITS_ORG_CAP }} renders as "" (set-but-empty) when the +# org variable is undefined, so that is the production "no override" path. +@test "pr-limit gate: PR_LIMITS_ORG_CAP empty (undefined org var) → passed through as empty" { + export DEV_LEAD_DRY_RUN="false" + export PR_LIMITS_ORG_CAP="" + _install_cap_recording_guard + + run bash "$FIX_ISSUE_SCRIPT" + + [ "$status" -eq 0 ] + [ "$(cat "$CAP_SEEN_FILE")" = "" ] + rm -f "$CAP_SEEN_FILE" +} + @test "pr-limit gate: every workflow step running dev-lead-fix-issue.sh maps vars.PR_LIMITS_ORG_CAP" { - command -v yq >/dev/null || skip "yq not installed" + # yq is preinstalled on GitHub-hosted runners; never let this regression check + # silently vanish from CI (skip is for local runs only). + if ! command -v yq >/dev/null; then + [ -z "${CI:-}" ] || { echo "yq is required in CI" >&2; return 1; } + skip "yq not installed" + fi local wf steps found=0 for wf in "$SCRIPT_DIR"/.github/workflows/*.yml; do # Step names (one per line) whose run: invokes the gate-running script. From 39aaa6f0f0fc5d10f5b82016d4ce68d90ebec7c0 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:38:34 +0000 Subject: [PATCH 3/4] fix(reviews): address review comments [skip ci-relay] --- .../unit/test_fix_issue_pr_limit_gate.bats | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats b/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats index f5bfd23ff..c2f578f84 100644 --- a/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats +++ b/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats @@ -233,7 +233,8 @@ teardown() { # Overwrite the fake guard with one that records the PR_LIMITS_ORG_CAP it sees # ("" when absent) so tests can assert the value reaches the gate. _install_cap_recording_guard() { - CAP_SEEN_FILE="$(mktemp -u)"; export CAP_SEEN_FILE + CAP_SEEN_FILE="$(mktemp)" || { echo "Failed to create temp file" >&2; return 1; } + export CAP_SEEN_FILE cat > "$PLG_STANDARDS_DIR/scripts/lib/pr-limit-gate.sh" <<'GUARDEOF' #!/usr/bin/env bash plg_admission_gate() { @@ -288,20 +289,17 @@ GUARDEOF [ -z "${CI:-}" ] || { echo "yq is required in CI" >&2; return 1; } skip "yq not installed" fi - local wf steps found=0 + local wf count bad_steps found=0 for wf in "$SCRIPT_DIR"/.github/workflows/*.yml; do - # Step names (one per line) whose run: invokes the gate-running script. - steps=$(yq -r '.jobs[].steps[]? | select((.run // "") | test("dev-lead-fix-issue\\.sh")) | .name' "$wf") - [ -n "$steps" ] || continue - while IFS= read -r name; do - found=$((found + 1)) - local val - val=$(NAME="$name" yq -r '.jobs[].steps[]? | select(.name == strenv(NAME)) | .env.PR_LIMITS_ORG_CAP // ""' "$wf") - [ "$val" = '${{ vars.PR_LIMITS_ORG_CAP }}' ] || { - echo "$(basename "$wf"): step '$name' PR_LIMITS_ORG_CAP='$val' (want \${{ vars.PR_LIMITS_ORG_CAP }})" >&2 - return 1 - } - done <<< "$steps" + count=$(yq -r '[.jobs[].steps[]? | select((.run // "") | test("dev-lead-fix-issue\\.sh"))] | length' "$wf") + [ "$count" -gt 0 ] || continue + found=$((found + count)) + # Steps running the script that do not map the variable (unnamed-safe). + bad_steps=$(yq -r '.jobs[].steps[]? | select((.run // "") | test("dev-lead-fix-issue\\.sh")) | select(.env.PR_LIMITS_ORG_CAP != "${{ vars.PR_LIMITS_ORG_CAP }}") | (.name // "unnamed step")' "$wf") + [ -z "$bad_steps" ] || { + echo "$(basename "$wf"): steps lacking PR_LIMITS_ORG_CAP: $bad_steps" >&2 + return 1 + } done # Guard against the selector silently matching nothing. [ "$found" -ge 2 ] From 2f399298d1000009b5b15be335a2f8f423eb5ead Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Fri, 2 Oct 2026 17:33:02 +0000 Subject: [PATCH 4/4] fix(reviews): address review comments [skip ci-relay] --- tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats b/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats index c2f578f84..58599f3b1 100644 --- a/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats +++ b/tests/dev-lead/unit/test_fix_issue_pr_limit_gate.bats @@ -143,6 +143,7 @@ CFGEOF teardown() { rm -f "$GITHUB_ENV" "$GITHUB_OUTPUT" "$PR_CREATED_FLAG" "$COMMENT_FILE" + [ -z "${CAP_SEEN_FILE:-}" ] || rm -f "$CAP_SEEN_FILE" rm -rf "$STUB_BIN_DIR" "$PLG_STANDARDS_DIR" }