Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 6 additions & 1 deletion .github/workflows/agent-shield.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
# ─────────────────────────────────────────────────────────────────────────────
Expand All @@ -24,6 +28,7 @@ on:
branches: [main]
pull_request:
branches: [main]
merge_group:

permissions:
contents: read
Expand Down
112 changes: 106 additions & 6 deletions .github/workflows/initiative-driver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -76,11 +94,93 @@ 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: 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 }}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The gate output is interpolated into shell source, so a malformed value containing quotes or command substitutions can execute or break this step instead of reaching the validation; the failed step then blocks the fail-open dispatch. Pass the output through the step's env: and read it from $DECISION.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .github/workflows/initiative-driver.yml, line 172:

<comment>The gate output is interpolated into shell source, so a malformed value containing quotes or command substitutions can execute or break this step instead of reaching the validation; the failed step then blocks the fail-open dispatch. Pass the output through the step's `env:` and read it from `$DECISION`.</comment>

<file context>
@@ -164,6 +164,17 @@ jobs:
+        # 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)."
</file context>

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
# 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'
Comment thread
don-petry marked this conversation as resolved.
env:
GH_TOKEN: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }}
run: |
Expand Down
66 changes: 66 additions & 0 deletions .github/workflows/persona-mention.yml
Original file line number Diff line number Diff line change
@@ -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/<role>` — 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
32 changes: 18 additions & 14 deletions .github/workflows/pr-auto-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
# 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).

Check warning on line 31 in .github/workflows/pr-auto-review.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=petry-projects_markets&issues=AaED-vrsb7mETkQZkOtD&open=AaED-vrsb7mETkQZkOtD&pullRequest=512
workflow_run:
workflows: ["CI"]
types: [completed]
Expand All @@ -44,24 +44,28 @@

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-<number>`) 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 | length == 1 && github.event.check_suite.pull_requests[0].number)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: GitHub Actions expressions do not support | length, so this concurrency group cannot be parsed and the workflow will not run. The workflow_run condition repeats the same invalid syntax; guard the first and second array entries using supported expressions instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .github/workflows/pr-auto-review.yml, line 62:

<comment>GitHub Actions expressions do not support `| length`, so this concurrency group cannot be parsed and the workflow will not run. The `workflow_run` condition repeats the same invalid syntax; guard the first and second array entries using supported expressions instead.</comment>

<file context>
@@ -59,9 +59,9 @@ permissions: {}
   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)
</file context>

&& 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 | 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)
}}
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:
Expand Down
Loading