Skip to content
Merged
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
56 changes: 42 additions & 14 deletions .github/workflows/initiative-driver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,27 @@
# 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), reads every threshold from standards/agent-rate-limits.json, and
# serializes a close-event + schedule burst AHEAD of the cancel-in-progress
# concurrency group so two dispatches cannot race into cancellation (#443/#402).
# 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 @@ -97,13 +105,18 @@ jobs:
# 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, emits no decision, and the dispatch
# step's `!= 'defer'` guard falls through to dispatching (fail-open).
# 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: v1
ref: cd0b16751454d2eb3486ec04477d7ee9cc96c425 # main @ 2026-10-02 (#1232)
path: .arl-gate-tooling
fetch-depth: 1
persist-credentials: false
Expand All @@ -114,7 +127,9 @@ jobs:
# 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-safe: the step can never fail the job.
# 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:
Expand All @@ -129,12 +144,25 @@ jobs:
ARL_TRACKING_ISSUE: ${{ github.event.issue.number || vars.INITIATIVE_DRIVER_TRACKING_ISSUE || '' }}
run: |
set -euo pipefail
bash .arl-gate-tooling/scripts/agent-rate-limit-gate.sh initiative-driver \
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" || true
--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
Expand Down
17 changes: 17 additions & 0 deletions standards/agent-rate-limits.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,23 @@ spend) and the direct target of the dispatch-race defect
calls the orchestrator with `--mode enforce`; a `defer` decision simply skips the
dispatch step (a clean no-op — never a cancel, never a job failure).

**Gate tooling pin ([#1232](https://github.com/petry-projects/.github/issues/1232)).**
An enrolled stub does not carry the gate, so it checks out `petry-projects/.github`
into `.arl-gate-tooling` with the org PAT. That checkout is pinned to a **full
commit SHA**, not a moving tag: the original `ref: v1` predated
`scripts/agent-rate-limit-gate.sh`, so the gate was silently inert fleet-wide,
and a moving ref hands the PAT to whatever the tag next points at. To pick up
gate or threshold changes (`agent-rate-limits.json` is read from the same
checkout), bump the SHA in `standards/workflows/initiative-driver.yml` in its
own reviewed PR, after confirming the three gate files
(`scripts/agent-rate-limit-gate.sh`, `scripts/lib/agent-rate-limit.sh`,
`standards/agent-rate-limits.json`) resolve at that SHA, then fan out via
standards-sync. If the script is missing at run time, the gate step **fails
loudly** (`::error::` annotation, step summary, step marked failed) while
`continue-on-error` keeps the dispatch fail-open. The gate throttles dispatches
that start; it does not pre-empt the stub's `cancel-in-progress` concurrency
group, which may cancel a superseded run after its steps have started.

**`feature-ideation` runs the gate in `--mode log-only`** (`feature-ideation-reusable.yml`):
the decision is computed from run history and logged, but the emitted decision is
always `allow`, so nothing is acted on. `dev-lead` and `compliance-audit` are not
Expand Down
56 changes: 42 additions & 14 deletions standards/workflows/initiative-driver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,27 @@
# 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), reads every threshold from standards/agent-rate-limits.json, and
# serializes a close-event + schedule burst AHEAD of the cancel-in-progress
# concurrency group so two dispatches cannot race into cancellation (#443/#402).
# 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 @@ -97,13 +105,18 @@ jobs:
# 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, emits no decision, and the dispatch
# step's `!= 'defer'` guard falls through to dispatching (fail-open).
# 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: v1
ref: cd0b16751454d2eb3486ec04477d7ee9cc96c425 # main @ 2026-10-02 (#1232)
path: .arl-gate-tooling
fetch-depth: 1
persist-credentials: false
Expand All @@ -114,7 +127,9 @@ jobs:
# 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-safe: the step can never fail the job.
# 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:
Expand All @@ -129,12 +144,25 @@ jobs:
ARL_TRACKING_ISSUE: ${{ github.event.issue.number || vars.INITIATIVE_DRIVER_TRACKING_ISSUE || '' }}
run: |
set -euo pipefail
bash .arl-gate-tooling/scripts/agent-rate-limit-gate.sh initiative-driver \
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 \
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
--repo "$ARL_TRACKING_REPO" \
--mode enforce \
--workflow initiative-driver.yml \
--actor "$ARL_ACTOR" \
--tracking-repo "$ARL_TRACKING_REPO" \
--tracking-issue "$ARL_TRACKING_ISSUE" || true
--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
Expand Down
91 changes: 91 additions & 0 deletions test/workflows/initiative-driver/gate-tooling.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env bats
# Tests for the agent-rate-limit gate wiring of the canonical caller stub
# standards/workflows/initiative-driver.yml (issue #1232).
#
# The Phase-4 stub (#640) fetched the gate tooling at the moving tag `v1`, which
# predates scripts/agent-rate-limit-gate.sh — the gate was silently inert. The
# tooling checkout must be pinned to an immutable commit SHA, a missing gate
# script must be reported loudly (not swallowed), and the header must describe
# what the stub actually does.

TT_REPO_ROOT="$(cd -- "$(dirname -- "${BATS_TEST_DIRNAME}")/../.." && pwd)"
STUB="${TT_REPO_ROOT}/standards/workflows/initiative-driver.yml"
LIVE="${TT_REPO_ROOT}/.github/workflows/initiative-driver.yml"

CHECKOUT_STEP='Checkout agent-rate-limit gate tooling'
GATE_STEP='Agent rate-limit admission gate (enforcing)'

@test "stub: gate tooling checkout is pinned to a full commit SHA" {
run yq -r ".jobs.dispatch.steps[] | select(.name == \"${CHECKOUT_STEP}\") | .with.ref" "$STUB"
[ "$status" -eq 0 ]
[ "$output" = 'cd0b16751454d2eb3486ec04477d7ee9cc96c425' ]
}

@test "stub: gate tooling checkout still targets petry-projects/.github" {
run yq -r ".jobs.dispatch.steps[] | select(.name == \"${CHECKOUT_STEP}\") | .with.repository" "$STUB"
[ "$status" -eq 0 ]
[ "$output" = 'petry-projects/.github' ]
}

@test "stub: gate step detects a missing gate script and reports an ::error::" {
run yq -r ".jobs.dispatch.steps[] | select(.name == \"${GATE_STEP}\") | .run" "$STUB"
[ "$status" -eq 0 ]
echo "$output" | grep -qE '\[ -f "?\$\{?ARL_GATE_SCRIPT\}?"? \]'
echo "$output" | grep -q '::error::'
echo "$output" | grep -q 'GITHUB_STEP_SUMMARY'
}

@test "stub: gate invocation is not masked by '|| true'" {
run yq -r ".jobs.dispatch.steps[] | select(.name == \"${GATE_STEP}\") | .run" "$STUB"
[ "$status" -eq 0 ]
run grep -qF '|| true' <<<"$output"
[ "$status" -eq 1 ]
}

@test "stub: missing gate script exits non-zero (step marked failed, job continues)" {
run yq -r ".jobs.dispatch.steps[] | select(.name == \"${GATE_STEP}\") | .run" "$STUB"
[ "$status" -eq 0 ]
local script="$output"
local tmp
tmp="$(mktemp -d)"
run env -C "$tmp" GITHUB_STEP_SUMMARY="$tmp/summary" GITHUB_OUTPUT="$tmp/out" \
ARL_ACTOR=a ARL_TRACKING_REPO=o/r ARL_TRACKING_ISSUE= bash -c "$script"
[ "$status" -eq 1 ]
[[ "$output" == *"::error::"* ]]
grep -q 'agent-rate-limit-gate.sh' "$tmp/summary"
# No defer is emitted — the dispatch step's fail-open guard still dispatches.
touch "$tmp/out"
run grep -q 'decision=defer' "$tmp/out"
[ "$status" -eq 1 ]
rm -rf "$tmp"

run yq -r ".jobs.dispatch.steps[] | select(.name == \"${GATE_STEP}\") | .\"continue-on-error\"" "$STUB"
[ "$output" = 'true' ]
}

@test "stub: header no longer claims the gate runs AHEAD of the concurrency group" {
run grep -qiE 'AHEAD of the cancel-in-progress' "$STUB"
[ "$status" -eq 1 ]
}

@test "stub: adoption step names the canonical GH_PAT_DON_PETRY secret" {
run grep -nE '^# 3\..*GH_PAT_DON_PETRY' "$STUB"
[ "$status" -eq 0 ]
}

@test "stub: dispatch step guard dispatches on an empty decision (fail-open)" {
run yq -r ".jobs.dispatch.steps[] | select(.name == \"Dispatch central initiative-driver\") | .if" "$STUB"
[ "$status" -eq 0 ]
[ "$output" = "steps.arl_gate.outputs.decision != 'defer'" ]
}

@test "stub: gate invocation passes --repo so run history is the caller's" {
run yq -r ".jobs.dispatch.steps[] | select(.name == \"${GATE_STEP}\") | .run" "$STUB"
[ "$status" -eq 0 ]
[[ "$output" == *'--repo "$ARL_TRACKING_REPO"'* ]]
}

@test "live copy matches the standard verbatim" {
run diff "$STUB" "$LIVE"
[ "$status" -eq 0 ]
}
Loading