Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions .github/workflows/pr-limits-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ jobs:
env:
# `gh search prs` needs a token; github.token has the read scope it needs.
GH_TOKEN: ${{ github.token }}
# Org-level Actions variable; overrides the cap in pr-limits.json when set.
PR_LIMITS_ORG_CAP: ${{ vars.PR_LIMITS_ORG_CAP }}
Comment thread
don-petry marked this conversation as resolved.
Comment thread
don-petry marked this conversation as resolved.
run: bash scripts/pr-limits-report.sh
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,10 @@ auto-rebase fan-out. Full standard, rationale, and operator runbook:
fails or closes) when the queue is at the cap. Live-path wiring is Story 3 (#508).
- **The configured value lives only in [`standards/pr-limits.json`](https://github.com/petry-projects/.github/blob/main/standards/pr-limits.json)** —
the single source of truth. Read it with `jq`; never hardcode or restate it.
- **Runtime override:** the org-level Actions variable `PR_LIMITS_ORG_CAP`
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
(positive integer) overrides the file value without a code change; invalid or
unset falls back to the file. Pass it via `vars.PR_LIMITS_ORG_CAP` (see
[`standards/pr-limits.md` §2.1](https://github.com/petry-projects/.github/blob/main/standards/pr-limits.md)).

> **Exempt actors are sanctioned policy, not drift.** `dependabot[bot]`,
> `OrganizationAdmin`, `@petry-projects/org-leads`, the `dependabot-automerge-petry`
Expand Down
29 changes: 27 additions & 2 deletions scripts/lib/pr-limit-gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
# - $PR_LIMITS_CONFIG — path to the pr-limits.json single source of truth
# (default: <repo>/standards/pr-limits.json, resolved
# relative to this file)
# - $PR_LIMITS_ORG_CAP — runtime override for org_wide.automation_open_pr_cap.
# Wire it from the org-level Actions variable
# (`vars.PR_LIMITS_ORG_CAP`) so the ceiling can change on
# the fly with no code change. Must be a positive
# integer; unset/empty/invalid falls back to the config.
# - $DRY_RUN / $DEV_LEAD_DRY_RUN — "true" forces an allow result with no
# side effects, after printing the computed decision
# - `gh` CLI on PATH, `jq` on PATH
Expand All @@ -53,6 +58,26 @@
printf '%s' "${PR_LIMITS_CONFIG:-$PLG_DEFAULT_CONFIG}"
}

# ---------------------------------------------------------------------------
# plg_effective_org_cap <config> — echo the org-wide cap in force.
#
# Precedence: $PR_LIMITS_ORG_CAP (org-level variable) when set to a positive
# integer, else org_wide.automation_open_pr_cap from the config. An invalid
# override is ignored with a warning (never silently disables or zeroes the cap).
# Echoes nothing if neither source yields an integer.
# ---------------------------------------------------------------------------
plg_effective_org_cap() {
local config="$1" override="${PR_LIMITS_ORG_CAP:-}"
if [ -n "$override" ]; then

Check failure on line 71 in scripts/lib/pr-limit-gate.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=petry-projects_.github&issues=AaD5J7q5a-7KIkAgkiPz&open=AaD5J7q5a-7KIkAgkiPz&pullRequest=1221
if [[ "$override" =~ ^[1-9][0-9]*$ ]]; then
Comment thread
codeant-ai[bot] marked this conversation as resolved.
Outdated
printf '%s' "$override"
return 0
fi
plg_log "warning: ignoring invalid PR_LIMITS_ORG_CAP='$override' (need a positive integer); using config value"
fi
Comment thread
don-petry marked this conversation as resolved.
jq -er '.org_wide.automation_open_pr_cap' "$config" 2>/dev/null || printf ''
Comment thread
don-petry marked this conversation as resolved.
Outdated
}

# ---------------------------------------------------------------------------
# plg_is_dry_run — 0 (true) when DRY_RUN or DEV_LEAD_DRY_RUN is "true".
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -193,9 +218,9 @@
fi

local org_cap org_count
org_cap="$(jq -er '.org_wide.automation_open_pr_cap' "$config" 2>/dev/null || printf '')"
org_cap="$(plg_effective_org_cap "$config")"
if ! [[ "$org_cap" =~ ^[0-9]+$ ]]; then
plg_log "error: org_wide.automation_open_pr_cap is missing or not an integer in $config"
plg_log "error: org cap is missing or not an integer (PR_LIMITS_ORG_CAP / org_wide.automation_open_pr_cap in $config)"
return 2
fi
org_count="$(plg_count_open_automation_prs "$config")"
Expand Down
10 changes: 8 additions & 2 deletions scripts/pr-limits-report.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#
# Environment (all optional):
# ORG — GitHub org slug to scope the search (default: petry-projects)
# PR_LIMITS_ORG_CAP — org-variable override of the cap (positive integer); wins
# over the config value, same as the admission gate
# PR_LIMITS_CONFIG — path to pr-limits.json (default: <repo>/standards/pr-limits.json,
# resolved relative to this script so it works from any CWD)
# GH_TOKEN / GITHUB_TOKEN — token for `gh search prs` (github.token in CI)
Expand All @@ -37,15 +39,19 @@ DEFAULT_CONFIG="${SCRIPT_DIR}/../standards/pr-limits.json"
CONFIG="${PR_LIMITS_CONFIG:-$DEFAULT_CONFIG}"
ORG="${ORG:-petry-projects}"

# Reuse the gate's cap resolution so report and gate always agree.
# shellcheck source=scripts/lib/pr-limit-gate.sh
source "${SCRIPT_DIR}/lib/pr-limit-gate.sh"

if [ ! -f "$CONFIG" ]; then
echo "pr-limits-report: error: config not found at $CONFIG" >&2
exit 1
fi

# ── Read the cap + exempt lists from the single source of truth ───────────────
CAP="$(jq -er '.org_wide.automation_open_pr_cap' "$CONFIG" 2>/dev/null || printf '')"
CAP="$(plg_effective_org_cap "$CONFIG")"
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
if ! [[ "$CAP" =~ ^[0-9]+$ ]]; then
echo "pr-limits-report: error: org_wide.automation_open_pr_cap missing or not an integer in $CONFIG" >&2
echo "pr-limits-report: error: org cap missing or not an integer (PR_LIMITS_ORG_CAP / org_wide.automation_open_pr_cap in $CONFIG)" >&2
exit 1
fi
EXEMPT_ACTORS="$(jq -c '.exempt_actors // []' "$CONFIG" 2>/dev/null || echo '[]')"
Expand Down
27 changes: 27 additions & 0 deletions standards/pr-limits.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,28 @@ The config carries its own inline `_note` fields recording the human sign-off
rationale for the number. Consumers (the apply path in §3, and any future
PR-creating workflow) **must** read the value from this file — never hardcode it.

### 2.1 Runtime override — the org-level variable

The ceiling can be changed **on the fly, with no code change or PR**, via the
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
org-level Actions variable **`PR_LIMITS_ORG_CAP`** (Org → Settings → Secrets and
variables → Actions → Variables). Consumers pass it to the gate as the
`PR_LIMITS_ORG_CAP` environment variable (`env: PR_LIMITS_ORG_CAP: ${{ vars.PR_LIMITS_ORG_CAP }}`).

- **Precedence:** a positive-integer `PR_LIMITS_ORG_CAP` wins; if it is unset,
empty, or invalid (zero, negative, non-integer — logged as a warning), the gate
falls back to `org_wide.automation_open_pr_cap` in the config. A bad value can
never silently disable or zero the cap.
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
- The file value is the **default/fallback**; the variable is the **operational
lever**. Exempt actors/labels and sub-caps still come only from the config.
- Resolution lives in one place (`plg_effective_org_cap` in the gate library) and
is reused by [`scripts/pr-limits-report.sh`](../scripts/pr-limits-report.sh), so
the gate and the daily report always agree.
- Set or change it with `gh variable set PR_LIMITS_ORG_CAP --org petry-projects --visibility all --body <N>`.
Take effect on the next run of each consumer; remove it to revert to the file.
- **Wiring:** this repo's report workflow already passes it. The PR-creation
engine in `petry-projects/.github-private` must add the same `env:` line to the
step that sources the gate for the variable to take effect there.

Its contract (parseable JSON, positive-integer cap, required keys,
`dependabot[bot]` and the `security` label present on the exempt lists) is
guarded by [`test/scripts/pr-limits/pr-limits-config.bats`](../test/scripts/pr-limits/pr-limits-config.bats),
Expand Down Expand Up @@ -138,6 +160,11 @@ All changes here are edits to the single source of truth
them touches a GitHub setting or ruleset — there is no such surface to apply
(§3).

### 6.0 Quick change (no PR)

Set the org variable (§2.1): `gh variable set PR_LIMITS_ORG_CAP --org petry-projects --visibility all --body <N>`.
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Use §6.1 only to change the signed-off default in the file.
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

### 6.1 Change the limit and re-apply it

1. Edit `org_wide.automation_open_pr_cap` in
Expand Down
38 changes: 38 additions & 0 deletions test/scripts/pr-limits/pr-limit-gate.bats
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,44 @@ run_gate() {
[[ "$output" == *"decision=allow"* ]]
}

# --------------------------------------------------------------------------
# PR_LIMITS_ORG_CAP — org-variable override of the config cap
# --------------------------------------------------------------------------
@test "PR_LIMITS_ORG_CAP raises the cap above the config value" {
write_config 5 9
stub_open_prs 7
PR_LIMITS_ORG_CAP=20 run_gate "claude"
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
[ "$status" -eq 0 ]
[[ "$output" == *"decision=allow"* ]]
}

@test "PR_LIMITS_ORG_CAP can lower the cap below the config value" {
write_config 50 9
stub_open_prs 7
PR_LIMITS_ORG_CAP=7 run_gate "claude"
[ "$status" -eq 1 ]
[[ "$output" == *"decision=defer"* ]]
}

@test "invalid PR_LIMITS_ORG_CAP is ignored and the config cap applies" {
write_config 5 9
stub_open_prs 7
for bad in 0 -3 abc 1.5; do
PR_LIMITS_ORG_CAP="$bad" run_gate "claude"
[ "$status" -eq 1 ]
[[ "$output" == *"decision=defer"* ]]
[[ "$output" == *"ignoring invalid PR_LIMITS_ORG_CAP"* ]]
done
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@test "empty PR_LIMITS_ORG_CAP (unset org variable) falls back to config" {
write_config 5 9
stub_open_prs 7
PR_LIMITS_ORG_CAP="" run_gate "claude"
[ "$status" -eq 1 ]
[[ "$output" == *"decision=defer"* ]]
}

# --------------------------------------------------------------------------
# AC #1 — at / over the org-wide cap: defer
# --------------------------------------------------------------------------
Expand Down
Loading