From e5645b0d59dbc3a8c1cdb652476bc3591c883626 Mon Sep 17 00:00:00 2001 From: Don Petry <36422719+don-petry@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:54:03 +0000 Subject: [PATCH 1/7] chore: sync 4 org-standard workflow stub(s) from petry-projects/.github --- .github/workflows/persona-mention.yml | 66 +++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/persona-mention.yml diff --git a/.github/workflows/persona-mention.yml b/.github/workflows/persona-mention.yml new file mode 100644 index 00000000..ee2f94c4 --- /dev/null +++ b/.github/workflows/persona-mention.yml @@ -0,0 +1,66 @@ +# ───────────────────────────────────────────────────────────────────────────── +# SOURCE OF TRUTH: petry-projects/.github/standards/workflows/persona-mention.yml +# Standard: petry-projects/.github/standards/persona-standards.md (§4.1) +# Reusable: petry-projects/.github/.github/workflows/persona-mention-reusable.yml +# +# AGENTS — READ BEFORE EDITING: +# • This file is a THIN CALLER STUB. All routing logic lives in the reusable +# workflow above, and every per-persona decision lives in that persona's +# manifest — not here and not in the reusable. +# • You MUST NOT change: the `uses:` ref — it is pinned to the +# `persona-mention/v1-stable` channel, a moving tag advanced centrally. +# Never repoint it to `@main`, a SHA, or a frozen `@vX.Y.Z` (see +# ci-standards.md → Reusable workflow versioning). Also do not change the +# trigger events or the job-level `permissions:` block — reusable workflows +# can be granted no more permissions than the calling job, so removing the +# stanza breaks the reusable's gh API calls. +# • Do NOT add a per-persona stub. ONE router serves every persona (§4.1); +# a stub per agent is the drift the manifest exists to prevent. +# • If you need different behaviour, open a PR against the reusable in the +# central repo. A new release is rolled out by moving the channel tag +# centrally — never by editing callers, so no fanout PR is needed. +# ───────────────────────────────────────────────────────────────────────────── +# +# Persona Mention Router — thin caller for the org-level reusable. +# +# Adopting this file makes every persona that enables the `mention` surface +# addressable in this repo as `@petry-projects/` — e.g. +# +# @petry-projects/qa-lead please assess test risk on this PR +# +# The handle is an org TEAM, never a user account: '@qa-lead' is a real GitHub +# account owned by an unrelated person (§4.1). +# +# To adopt: copy this file to .github/workflows/persona-mention.yml in your repo. +# Requires: GH_PAT_WORKFLOWS org secret (already present in petry-projects org). +name: Persona — Mention Router + +on: + # issue_comment covers BOTH issues and PR conversation comments. + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + # Discussions cannot run an agent inline (#571) — but this router only parses + # and dispatches, and the agent itself runs on the resulting + # repository_dispatch, which is exactly the bridge the manifest declares. + discussion_comment: + types: [created] +# NOTE: reviewer assignment (pull_request/review_requested) is NOT subscribed +# here yet, even though §4 says "Reviewer-assignment counts here too". The +# router has no path for it, and a trigger that fires into a router which +# ignores it is a dead trigger that only burns runs. Tracked as finding 10 in +# petry-projects/.github#755; the trigger lands with the routing, not before. + +permissions: {} + +jobs: + persona-mention: + permissions: + contents: read # the reusable checks out its tooling repo — required, + # or the call fails at startup (a reusable is granted + # no more than the caller; persona-standards.md §5) + issues: read + pull-requests: read + uses: petry-projects/.github/.github/workflows/persona-mention-reusable.yml@persona-mention/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref + secrets: inherit # NOSONAR(githubactions:S7635) org secrets are scoped to first-party reusables From a6d5b40cd07654df6723d2e7c661f42dc282390c Mon Sep 17 00:00:00 2001 From: Don Petry <36422719+don-petry@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:54:05 +0000 Subject: [PATCH 2/7] chore: sync 4 org-standard workflow stub(s) from petry-projects/.github --- .github/workflows/agent-shield.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/agent-shield.yml b/.github/workflows/agent-shield.yml index 5887d518..c2fa6ec5 100644 --- a/.github/workflows/agent-shield.yml +++ b/.github/workflows/agent-shield.yml @@ -10,7 +10,11 @@ # required-files, org-standards-ref) — only if your repo genuinely needs # a different policy. # • You MUST NOT change: trigger events, the `uses:` line, or the job name -# (used as a required status check). +# (used as a required status check). NOTE: `merge_group` is part of the +# standard trigger set — it is required so this stub's `agent-shield / +# AgentShield` check reports on a merge queue's `gh-readonly-queue/*` ref +# (see standards/ci-standards.md). Do not drop it on sync and do not flag it +# as drift. # • If you need different behaviour beyond the inputs, open a PR against # the reusable in the central repo. # ───────────────────────────────────────────────────────────────────────────── @@ -24,6 +28,7 @@ on: branches: [main] pull_request: branches: [main] + merge_group: permissions: contents: read From 59169c30f783bf36effab2975d482ec8b74ddd79 Mon Sep 17 00:00:00 2001 From: Don Petry <36422719+don-petry@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:54:06 +0000 Subject: [PATCH 3/7] chore: sync 4 org-standard workflow stub(s) from petry-projects/.github --- .github/workflows/initiative-driver.yml | 101 ++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 6 deletions(-) diff --git a/.github/workflows/initiative-driver.yml b/.github/workflows/initiative-driver.yml index b25ee1e5..1c7c765b 100644 --- a/.github/workflows/initiative-driver.yml +++ b/.github/workflows/initiative-driver.yml @@ -27,14 +27,32 @@ # sub-issues to dev-lead — so the central driver does not have to watch every # enrolled repo's events. # +# Rate-limit gate (#640): before dispatching, this stub asks the source-side +# agent-rate-limit gate (petry-projects/.github/scripts/agent-rate-limit-gate.sh) +# whether `initiative-driver` may dispatch right now. initiative-driver is the +# canary that ENFORCES: the gate derives concurrency / cooldown / daily-budget / +# consecutive-failure counters from this stub's own run history (no state +# backend) and reads every threshold from standards/agent-rate-limits.json. +# It throttles dispatches that DO start (cooldown / daily budget / breaker); it +# does NOT pre-empt the cancel-in-progress concurrency group below — that group +# cancels a superseded run before any step (gate included) executes (#443/#402). +# A `defer` simply skips the dispatch step — never a cancel, never a job failure. +# +# Gate tooling is fetched at a pinned commit SHA of petry-projects/.github (not +# a moving tag — the PAT is handed to that code). If the tooling is unavailable +# or the gate errors, the gate step FAILS LOUDLY (::error:: annotation + step +# summary, step marked failed) but the dispatch still runs (fail-open — an +# outage of the gate must never stop the fleet; standards/agent-rate-limits.md). +# # To adopt: # 1. Copy this file verbatim to .github/workflows/initiative-driver.yml in your repo. # 2. Ensure the `initiative:auto` label exists on the repo. -# 3. Confirm the org-level secret GH_PAT_WORKFLOWS is accessible **and its -# owner has write access to petry-projects/.github-private** (to dispatch -# the central workflow) **and to this repo** (the central driver applies the -# `dev-lead` label cross-repo with that PAT; a label applied with -# GITHUB_TOKEN would not trigger dev-lead). +# 3. Confirm the org-level secret GH_PAT_DON_PETRY (or the legacy fallback +# GH_PAT_WORKFLOWS) is accessible **and its owner has write access to +# petry-projects/.github-private** (to dispatch the central workflow) **and +# to this repo** (the central driver applies the `dev-lead` label +# cross-repo with that PAT; a label applied with GITHUB_TOKEN would not +# trigger dev-lead). # # Standard: https://github.com/petry-projects/.github/blob/main/standards/ci-standards.md name: Initiative Driver — Dispatch Central @@ -76,11 +94,82 @@ jobs: GH_TOKEN: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }} run: | if [ -z "${GH_TOKEN}" ]; then - echo "::error::GH_PAT_WORKFLOWS is required — a workflow_dispatch fired with GITHUB_TOKEN never starts a run." + echo "::error::GH_PAT_DON_PETRY (or legacy GH_PAT_WORKFLOWS) is required — a workflow_dispatch fired with GITHUB_TOKEN never starts a run." + exit 1 + fi + + - name: Checkout agent-rate-limit gate tooling + # Phase-4 wiring (#640): the gate library + orchestrator live in + # petry-projects/.github; an enrolled caller stub does not carry them, so + # fetch them read-only into a scratch path. Uses the same PAT as the + # dispatch — the workflow's GITHUB_TOKEN cannot read another repo — so the + # stub's `permissions:` block is unchanged. + # continue-on-error: a tooling-checkout outage must not stop the fleet — + # the gate step then finds no script, reports an ::error:: (fail loudly), + # emits no decision, and the dispatch step's `!= 'defer'` guard falls + # through to dispatching (fail-open). + # ref: pinned to an immutable commit SHA of petry-projects/.github (#1232) + # — a moving tag silently lagged the gate script (v1 predates it) and + # would hand the PAT to whatever the tag next points at. Bump it in a + # reviewed PR (see standards/agent-rate-limits.md §3.1). + continue-on-error: true + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: petry-projects/.github + ref: cd0b16751454d2eb3486ec04477d7ee9cc96c425 # main @ 2026-10-02 (#1232) + path: .arl-gate-tooling + fetch-depth: 1 + persist-credentials: false + token: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }} + + - name: Agent rate-limit admission gate (enforcing) + # initiative-driver is the ONE canary agent type that ENFORCES (pure bash, + # no model spend, the direct #443 target — AC #5). Reads run history with + # the PAT (independent of the `permissions:` block) and emits + # decision=allow|defer to $GITHUB_OUTPUT; a defer is honoured by the `if:` + # on the dispatch step below. Fail loud, fail open: a missing gate script + # or a gate error marks THIS step failed with an ::error:: (visible in the + # run), but continue-on-error keeps the job — and the dispatch — going. + continue-on-error: true + id: arl_gate + env: + GH_TOKEN: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }} + ARL_ACTOR: ${{ github.actor }} + ARL_TRACKING_REPO: ${{ github.repository }} + # For issue events use the event's issue number; for schedule/workflow_dispatch + # fall back to a configured repo variable so argate_escalate can post the + # breaker marker and apply the needs-human-review label (both are skipped when + # tracking_issue is empty). Set INITIATIVE_DRIVER_TRACKING_ISSUE to the epic + # or operations issue number for this repo to enable escalation on non-issue runs. + ARL_TRACKING_ISSUE: ${{ github.event.issue.number || vars.INITIATIVE_DRIVER_TRACKING_ISSUE || '' }} + run: | + set -euo pipefail + ARL_GATE_SCRIPT=.arl-gate-tooling/scripts/agent-rate-limit-gate.sh + if ! [ -f "$ARL_GATE_SCRIPT" ]; then + msg="Agent rate-limit gate NOT ENFORCED: ${ARL_GATE_SCRIPT} is missing (tooling checkout failed or the pinned ref lacks the script). Dispatching ungated (fail-open)." + echo "::error::${msg}" + echo "### :warning: ${msg}" >> "$GITHUB_STEP_SUMMARY" exit 1 fi + bash "$ARL_GATE_SCRIPT" initiative-driver \ + --repo "$ARL_TRACKING_REPO" \ + --mode enforce \ + --workflow initiative-driver.yml \ + --actor "$ARL_ACTOR" \ + --tracking-repo "$ARL_TRACKING_REPO" \ + --tracking-issue "$ARL_TRACKING_ISSUE" || { + rc=$? + echo "::error::Agent rate-limit gate errored (exit ${rc}) — dispatching ungated (fail-open)." + echo "### :warning: Agent rate-limit gate errored (exit ${rc}) — dispatching ungated (fail-open)." >> "$GITHUB_STEP_SUMMARY" + exit "$rc" + } - name: Dispatch central initiative-driver + # Skipped (clean no-op) ONLY on an explicit `defer` — never a cancel, never + # a job failure (#640, AC #1/#2). Fail-OPEN: an empty/missing decision (the + # gate step or its tooling checkout failed) still dispatches, because an + # outage of the gate must never stop the fleet (ADR §7 fail-safe direction). + if: steps.arl_gate.outputs.decision != 'defer' env: GH_TOKEN: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }} run: | From 069893b2dfe4bb195068da76e4890aa7b1358881 Mon Sep 17 00:00:00 2001 From: Don Petry <36422719+don-petry@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:54:07 +0000 Subject: [PATCH 4/7] chore: sync 4 org-standard workflow stub(s) from petry-projects/.github --- .github/workflows/pr-auto-review.yml | 32 ++++++++++++++++------------ 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pr-auto-review.yml b/.github/workflows/pr-auto-review.yml index 7dc5a780..73df2cea 100644 --- a/.github/workflows/pr-auto-review.yml +++ b/.github/workflows/pr-auto-review.yml @@ -28,7 +28,7 @@ on: # workflow_run fires when a named GitHub Actions workflow completes. # check_suite does NOT trigger for GitHub Actions runs, so this is required # to catch CI turning green on a PR. - # This repo's CI workflow is named "CI" (.github/workflows/ci.yml). + # TODO: replace "CI" with your repository's CI workflow name(s). workflow_run: workflows: ["CI"] types: [completed] @@ -44,24 +44,28 @@ on: permissions: {} -# Deduplicate ONLY pull_request events which have a direct PR context (issue #1126). -# On GitHub Free (20 concurrent jobs, org-wide), avoid race conditions when multiple -# PRs share a commit: -# • pull_request / pull_request_review — have github.event.pull_request.number; -# safe to collapse per PR and cancel in progress. -# • check_suite / workflow_run — only have pull_requests[] array; if multiple PRs -# share the commit, pull_requests[0] is indeterminate and causes the first PR's -# events to cancel the second PR's events. Use unique per-run groups instead. -# A trigger with no associated PR (fork / no PR) also falls back to unique-per-run -# so unrelated workflow runs never interfere. +# Deduplicate ONLY the default-branch-context triggers (issue #1126). On GitHub +# Free (20 concurrent jobs, org-wide) this stub fanned out per trigger. Its runs +# split by context: +# • check_suite / workflow_run — head_branch=main, the check does NOT attach to +# the PR head, so superseding an in-flight run is SAFE. Both events collapse +# onto one group per PR (`…-pr-`) with cancel-in-progress: true. +# • pull_request / pull_request_review — run on the PR head; a cancelled run +# would leave a cancelled `pr-auto-review / check-and-dispatch` check on the +# head. They get a unique-per-run group and never cancel, so they behave +# exactly as before. +# A default-branch trigger with no associated PR (fork / no PR) also falls back to +# the unique-per-run group so unrelated PRs never collapse into one group. concurrency: group: >- ${{ - (github.event_name == 'pull_request' && format('pr-auto-review-ready-check-pr-{0}', github.event.pull_request.number)) - || (github.event_name == 'pull_request_review' && format('pr-auto-review-ready-check-pr-{0}', github.event.pull_request.number)) + (github.event_name == 'check_suite' && github.event.check_suite.pull_requests[0].number) + && format('pr-auto-review-ready-check-pr-{0}', github.event.check_suite.pull_requests[0].number) + || (github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number) + && format('pr-auto-review-ready-check-pr-{0}', github.event.workflow_run.pull_requests[0].number) || format('pr-auto-review-ready-check-unique-{0}', github.run_id) }} - cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_review' }} + cancel-in-progress: ${{ github.event_name == 'check_suite' || github.event_name == 'workflow_run' }} jobs: pr-auto-review: From 5b691207fa1df0ce4a71e87fe07acf01b126e984 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:57:32 +0000 Subject: [PATCH 5/7] fix(bot): correct NOSONAR suppression format in persona-mention.yml Fixed SonarQube rule suppression syntax on lines 65-66. The format should be NOSONAR(S7637) and NOSONAR(S7635), not NOSONAR(githubactions:S7637) and NOSONAR(githubactions:S7635), to properly suppress the GitHub Actions SonarQube rules for first-party channel refs and secrets inheritance. Co-Authored-By: Claude Haiku 4.5 --- .github/workflows/persona-mention.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/persona-mention.yml b/.github/workflows/persona-mention.yml index ee2f94c4..4e7609fd 100644 --- a/.github/workflows/persona-mention.yml +++ b/.github/workflows/persona-mention.yml @@ -62,5 +62,5 @@ jobs: # no more than the caller; persona-standards.md §5) issues: read pull-requests: read - uses: petry-projects/.github/.github/workflows/persona-mention-reusable.yml@persona-mention/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref - secrets: inherit # NOSONAR(githubactions:S7635) org secrets are scoped to first-party reusables + uses: petry-projects/.github/.github/workflows/persona-mention-reusable.yml@persona-mention/v1-stable # NOSONAR(S7637) first-party channel ref + secrets: inherit # NOSONAR(S7635) org secrets are scoped to first-party reusables From ced5da38368a52bf563e2fe04138736390ef556a Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:01:29 +0000 Subject: [PATCH 6/7] fix(bot): prevent concurrency collisions and validate gate decision output - pr-auto-review.yml: Check that pull_requests array has exactly 1 entry before using [0], preventing different PRs with shared commits from colliding in the same concurrency group - initiative-driver.yml: Add validation step to verify agent rate-limit gate actually published decision=allow|defer, emit error if missing while still dispatching (fail-open) Co-Authored-By: Claude Haiku 4.5 --- .github/workflows/initiative-driver.yml | 11 +++++++++++ .github/workflows/pr-auto-review.yml | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/initiative-driver.yml b/.github/workflows/initiative-driver.yml index 1c7c765b..fa971d5a 100644 --- a/.github/workflows/initiative-driver.yml +++ b/.github/workflows/initiative-driver.yml @@ -164,6 +164,17 @@ jobs: exit "$rc" } + - name: Validate agent rate-limit gate decision + # Verify that the gate step actually published a decision (allow|defer). + # If the script exited 0 but emitted no decision (interface drift, script bug), + # emit a loud error but still proceed with dispatch (fail-open). + run: | + decision="${{ steps.arl_gate.outputs.decision }}" + if [ -z "$decision" ] || ([ "$decision" != "allow" ] && [ "$decision" != "defer" ]); then + echo "::error::Agent rate-limit gate did not emit a valid decision (expected 'allow' or 'defer', got '$decision'). Dispatching ungated (fail-open)." + echo "### :warning: Agent rate-limit gate decision validation failed (got '$decision'). Dispatching ungated (fail-open)." >> "$GITHUB_STEP_SUMMARY" + fi + - name: Dispatch central initiative-driver # Skipped (clean no-op) ONLY on an explicit `defer` — never a cancel, never # a job failure (#640, AC #1/#2). Fail-OPEN: an empty/missing decision (the diff --git a/.github/workflows/pr-auto-review.yml b/.github/workflows/pr-auto-review.yml index 73df2cea..dbe04547 100644 --- a/.github/workflows/pr-auto-review.yml +++ b/.github/workflows/pr-auto-review.yml @@ -59,9 +59,9 @@ permissions: {} concurrency: group: >- ${{ - (github.event_name == 'check_suite' && github.event.check_suite.pull_requests[0].number) + (github.event_name == 'check_suite' && github.event.check_suite.pull_requests | length == 1 && github.event.check_suite.pull_requests[0].number) && format('pr-auto-review-ready-check-pr-{0}', github.event.check_suite.pull_requests[0].number) - || (github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number) + || (github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests | length == 1 && github.event.workflow_run.pull_requests[0].number) && format('pr-auto-review-ready-check-pr-{0}', github.event.workflow_run.pull_requests[0].number) || format('pr-auto-review-ready-check-unique-{0}', github.run_id) }} From 888fb54cff5ab410cde9d7807558eed5f5e75381 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:05:10 +0000 Subject: [PATCH 7/7] fix(bot): correct NOSONAR suppression format with githubactions prefix Restored the githubactions: prefix to S7637 and S7635 rule identifiers in persona-mention.yml to match the format used consistently across all other workflow files. These are GitHub Actions-specific SonarQube rules that require the githubactions: namespace. Co-Authored-By: Claude Haiku 4.5 --- .github/workflows/persona-mention.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/persona-mention.yml b/.github/workflows/persona-mention.yml index 4e7609fd..ee2f94c4 100644 --- a/.github/workflows/persona-mention.yml +++ b/.github/workflows/persona-mention.yml @@ -62,5 +62,5 @@ jobs: # no more than the caller; persona-standards.md §5) issues: read pull-requests: read - uses: petry-projects/.github/.github/workflows/persona-mention-reusable.yml@persona-mention/v1-stable # NOSONAR(S7637) first-party channel ref - secrets: inherit # NOSONAR(S7635) org secrets are scoped to first-party reusables + uses: petry-projects/.github/.github/workflows/persona-mention-reusable.yml@persona-mention/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref + secrets: inherit # NOSONAR(githubactions:S7635) org secrets are scoped to first-party reusables