diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml index e913e82d..3b76d1f4 100644 --- a/.github/workflows/add-to-project.yml +++ b/.github/workflows/add-to-project.yml @@ -47,11 +47,11 @@ jobs: add-to-project: permissions: contents: read - uses: combsmith/.github/.github/workflows/add-to-project-reusable.yml@add-to-project/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref + uses: combsmith/.github/.github/workflows/add-to-project-reusable.yml@add-to-project/v1-ring1 # NOSONAR(githubactions:S7637) first-party channel ref with: project_id: PVT_kwDOD2inqs4BZq3- project_url: https://github.com/orgs/combsmith/projects/1 - agent_ref: add-to-project/v1-stable + agent_ref: add-to-project/v1-ring1 # Pass only the secrets the reusable declares (least privilege; avoids # `secrets: inherit` handing the reusable every org secret). secrets: diff --git a/.github/workflows/initiative-driver.yml b/.github/workflows/initiative-driver.yml index 8782e6cc..3b7b158d 100644 --- a/.github/workflows/initiative-driver.yml +++ b/.github/workflows/initiative-driver.yml @@ -12,7 +12,7 @@ # is pure-bash (no claude-code-action), so this stub dispatches the central # workflow_dispatch DIRECTLY with `gh workflow run`. No LLM runs here. # • You MUST NOT change: the dispatch target -# (-R combsmith/.github-private -f target_repo=${{ github.repository }}), +# (-R combsmith/.github-private -f target_repo=), # the `initiative:auto` label filter, or the PAT guard. # • If you need different release behaviour, open a PR against the central # driver in combsmith/.github-private — never by editing callers. @@ -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 (combsmith/.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 combsmith/.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 combsmith/.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 +# combsmith/.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/combsmith/.github/blob/main/standards/ci-standards.md name: Initiative Driver — Dispatch Central @@ -76,14 +94,89 @@ 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 + # combsmith/.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 combsmith/.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: combsmith/.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 }} + # Pass github.repository via env (not inline template expansion) to keep + # the workflow expression out of shell text (zizmor template-injection). + # The dispatch target value is unchanged — target_repo is still this repo. + TARGET_REPO: ${{ github.repository }} run: | gh workflow run initiative-driver.yml \ -R combsmith/.github-private \ - -f target_repo=${{ github.repository }} + -f target_repo="${TARGET_REPO}" diff --git a/.github/workflows/pr-review.yml b/.github/workflows/pr-review.yml index ca10be69..c1eeebcb 100644 --- a/.github/workflows/pr-review.yml +++ b/.github/workflows/pr-review.yml @@ -1,29 +1,29 @@ -name: PR Review Agent - -# Thin caller for the org PR Review agent (combsmith/.github-private), -# pinned to the known-good `pr-review/stable` channel. Adopted from the validated -# `.github-private/pr-review-trigger.yml` template (#536 consumer fan-out). +# ───────────────────────────────────────────────────────────────────────────── +# SOURCE OF TRUTH: combsmith/.github/standards/workflows/pr-review.yml +# Standard: combsmith/.github/standards/ci-standards.md +# Reusable: combsmith/.github-private/.github/workflows/pr-review.yml +# (the pr-review ENGINE — grandfathered name, not *-reusable.yml) # -# - Version selection is the MOVING `pr-review/stable` tag — this file never -# changes on promotion; a release is rolled out by moving the tag centrally. -# Do NOT pin the `uses:` ref to a commit SHA here: Dependabot's github-actions -# updater treats a SHA-pinned reusable as a versioned dependency and auto-bumps -# it to the newest tag, which dragged this caller onto the unreleased `next` -# v1.8.0 ahead of stable (#310). A non-semver moving tag is left untouched by -# Dependabot, matching the other consumers (markets/ContentTwin/...). -# - The channel-pinned `uses:` line carries the inline -# `# NOSONAR(githubactions:S7637)` marker — the org-wide canonical exemption -# for first-party reusable-ref caller stubs (#549). It suppresses SonarCloud's -# "pin actions to a full SHA" rule at analysis time, clearing BOTH the quality -# gate AND the code-scanning SARIF alert (supersedes the per-file -# `sonar-project.properties` exemption, which only quieted the gate). -# - `agent_ref: pr-review/stable` pins the agent's own scripts to the same -# channel (#506), so the review logic AND scripts run the known-good version. -# - Secrets are forwarded via `secrets: inherit`, matching every other pr-review -# caller in the org and TalkTerm's own `dev-lead.yml` (#385). The reusable reads -# the inherited org secrets it needs (`DON_PETRY_BOT_GH_PAT_CLASSIC`, falling -# back to `DON_PETRY_BOT_GH_PAT`) — a classic PAT required for approvals since -# fine-grained PATs cannot `addPullRequestReview`. +# AGENTS — READ BEFORE EDITING: +# • This file is a THIN CALLER STUB. All review logic lives in the engine +# above, including concurrency (per PR + head SHA, never cancelling an +# in-flight review), the check_suite-without-a-PR skip, and reading +# `client_payload.pr_url` from repository_dispatch. +# • You MUST NOT change: the `@pr-review/v1-stable` channel in the `uses:` +# line or the matching `agent_ref` — both are re-pinned in lockstep to this +# repo's canary-ring tier by the standards sweep. Nor the workflow `name:`, +# the job id `review` (check names depend on both), the trigger event types, +# the Dependabot skip guard, or the job-level `permissions:` block. Do not +# add a stub-level `concurrency:` block. +# • The standards sweep only REPLACES an existing caller with this file; it +# never adds it to a repo that has none, and never touches +# combsmith/.github-private (whose pr-review.yml is the engine). +# • If you need different behaviour, open a PR against the engine in +# combsmith/.github-private. It reaches every repo when the channel +# tag is promoted. +# ───────────────────────────────────────────────────────────────────────────── + +name: PR Review Agent on: check_suite: @@ -31,7 +31,7 @@ on: pull_request_review: types: [submitted, dismissed] pull_request: - types: [opened, ready_for_review, reopened, synchronize] + types: [ready_for_review, reopened, synchronize] workflow_dispatch: inputs: pr_url: @@ -53,43 +53,16 @@ on: permissions: {} -# Serialize redundant runs for the same PR head commit. The overlapping triggers -# above (check_suite:completed, pull_request:synchronize, pull_request_review) -# all fire for one commit; without a concurrency lane they launch parallel -# duplicate reviews, inflating run duration and producing cancelled runs -# (Fleet Monitor #374). Grouping per head SHA lets a *new* commit start its own -# run while same-SHA events share a lane. cancel-in-progress: false so an -# in-flight review is never interrupted — the reusable's per-head-SHA -# idempotency no-ops the queued duplicate. workflow_dispatch falls back to -# pr_url, and any other event (e.g. repository_dispatch) to run_id (unique, so -# never blocked). Matches the sibling-caller convention (ci-failure-analyst.yml). -concurrency: - group: >- - pr-review-${{ - github.event.pull_request.head.sha || - github.event.check_suite.head_sha || - github.event.inputs.pr_url || - github.run_id }} - cancel-in-progress: false - jobs: review: - # Skip Dependabot-triggered runs. Dependabot events read the separate - # Dependabot secret store, so `secrets: inherit` forwards EMPTY PAT/OAuth - # secrets and the reusable's "Verify auth scopes" step fails outright - # (`gh auth status failed`) — the recurring failed run behind Fleet Monitor - # #465. A guarded job skips (neutral), keeping the failure rate clean, and a - # Dependabot PR gains nothing from this reviewer anyway. A human-initiated - # workflow_dispatch on a Dependabot PR still runs because github.actor is - # then the human, not dependabot[bot]. if: ${{ github.actor != 'dependabot[bot]' }} permissions: contents: read pull-requests: write checks: read - uses: combsmith/.github-private/.github/workflows/pr-review.yml@pr-review/stable # NOSONAR(githubactions:S7637) first-party channel ref + uses: combsmith/.github-private/.github/workflows/pr-review.yml@pr-review/v1-ring1 # NOSONAR(githubactions:S7637) first-party channel ref with: - agent_ref: pr-review/stable + agent_ref: pr-review/v1-ring1 pr_url: ${{ inputs.pr_url || '' }} dry_run: ${{ inputs.dry_run || '' }} force_review: ${{ inputs.force_review || '' }}