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
51 changes: 51 additions & 0 deletions .github/workflows/standards-templates-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Quality gate for the caller-stub templates under standards/workflows/.
#
# Triggered on any PR that touches:
# - standards/workflows/** (a template pin changed)
# - test/scripts/standards-templates/** (the guard itself changed)
#
# Gate: bats — every deployable template pins its major-scoped
# `<agent>/v<M>-stable` channel and never the bare `<agent>/stable` form.

name: Standards Templates Tests

on:
pull_request:
paths:
- 'standards/workflows/**'
- 'test/scripts/standards-templates/**'
- '.github/workflows/standards-templates-tests.yml'
push:
branches:
- main
paths:
- 'standards/workflows/**'
- 'test/scripts/standards-templates/**'
- '.github/workflows/standards-templates-tests.yml'

permissions:
contents: read

concurrency:
group: standards-templates-tests-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: bats
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
Comment thread
don-petry marked this conversation as resolved.
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 1

- name: Install bats
run: |
set -euo pipefail
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends bats

- name: Run bats suite
run: bats --print-output-on-failure test/scripts/standards-templates/
6 changes: 3 additions & 3 deletions standards/workflows/add-to-project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# • This file is a THIN CALLER STUB. All noise-gate / reconcile logic lives
# in the reusable workflow above.
# • You MAY change: nothing in normal use. The `uses:` ref and `agent_ref`
# are pinned to the `add-to-project/stable` channel and move with it — do
# are pinned to the `add-to-project/v1-stable` channel and move with it — do
# not change them to `@main` or a SHA (see ci-standards.md → Reusable
# workflow versioning).
# • You MUST NOT change: trigger events, the concurrency group, or the
Expand Down Expand Up @@ -47,11 +47,11 @@ jobs:
add-to-project:
permissions:
contents: read
uses: petry-projects/.github/.github/workflows/add-to-project-reusable.yml@add-to-project/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/add-to-project-reusable.yml@add-to-project/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref
with:
project_id: PVT_kwDOD2inqs4BZq3-
project_url: https://github.com/orgs/petry-projects/projects/1
agent_ref: add-to-project/stable
agent_ref: add-to-project/v1-stable
# Pass only the secrets the reusable declares (least privilege; avoids
# `secrets: inherit` handing the reusable every org secret).
secrets:
Expand Down
2 changes: 1 addition & 1 deletion standards/workflows/agent-shield.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ permissions:

jobs:
agent-shield:
uses: petry-projects/.github/.github/workflows/agent-shield-reusable.yml@agent-shield/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/agent-shield-reusable.yml@agent-shield/v2-stable # NOSONAR(githubactions:S7637) first-party channel ref
4 changes: 2 additions & 2 deletions standards/workflows/auto-rebase.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# • This file is a THIN CALLER STUB. All branch-update logic lives in the
# reusable workflow above.
# • You MUST NOT change: the `uses:` ref — it is pinned to the
# `auto-rebase/stable` channel, a moving tag advanced centrally. Never
# `auto-rebase/v2-stable` channel, a moving tag advanced centrally. Never
# repoint it to `@main`, a SHA, or a frozen `@vX` (see ci-standards.md →
# Reusable workflow versioning). Also do not change the trigger event,
# the concurrency group name,
Expand Down Expand Up @@ -50,5 +50,5 @@ jobs:
permissions:
contents: write # update-branch via GITHUB_TOKEN (may touch .github/workflows/)
pull-requests: write # post comments on PRs
uses: petry-projects/.github/.github/workflows/auto-rebase-reusable.yml@auto-rebase/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/auto-rebase-reusable.yml@auto-rebase/v2-stable # NOSONAR(githubactions:S7637) first-party channel ref
secrets: inherit
2 changes: 1 addition & 1 deletion standards/workflows/dependabot-automerge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ jobs:
permissions:
contents: read
pull-requests: read
uses: petry-projects/.github/.github/workflows/dependabot-automerge-reusable.yml@dependabot-automerge/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/dependabot-automerge-reusable.yml@dependabot-automerge/v2-stable # NOSONAR(githubactions:S7637) first-party channel ref
secrets: inherit
4 changes: 2 additions & 2 deletions standards/workflows/dependabot-rebase.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# • This file is a THIN CALLER STUB. All rebase/merge serialization logic
# lives in the reusable workflow above.
# • You MUST NOT change: the `uses:` ref — it is pinned to the
# `dependabot-rebase/stable` channel, a moving tag advanced centrally.
# `dependabot-rebase/v2-stable` channel, a moving tag advanced centrally.
# Never repoint it to `@main`, a SHA, or a frozen `@vX` (see
# ci-standards.md → Reusable workflow versioning). Also do not change the
# concurrency group name, the explicit secrets
Expand Down Expand Up @@ -50,7 +50,7 @@ jobs:
permissions:
contents: write # update-branch via GITHUB_TOKEN (may touch .github/workflows/)
pull-requests: write # re-approve PRs after branch update
uses: petry-projects/.github/.github/workflows/dependabot-rebase-reusable.yml@dependabot-rebase/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/dependabot-rebase-reusable.yml@dependabot-rebase/v2-stable # NOSONAR(githubactions:S7637) first-party channel ref
secrets:
APP_ID: ${{ secrets.APP_ID }}
APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}
2 changes: 1 addition & 1 deletion standards/workflows/dependency-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ permissions:

jobs:
dependency-audit:
uses: petry-projects/.github/.github/workflows/dependency-audit-reusable.yml@dependency-audit/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/dependency-audit-reusable.yml@dependency-audit/v2-stable # NOSONAR(githubactions:S7637) first-party channel ref
8 changes: 4 additions & 4 deletions standards/workflows/dev-lead.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ permissions: {}

jobs:
dev-lead:
# Pinned to the moving dev-lead/stable channel tag, not @main, so a broken
# Pinned to the moving dev-lead/v1-stable channel tag, not @main, so a broken
# change to dev-lead can no longer gate its own fix (the self-host circular
# dependency). Promotion is done by moving the dev-lead/stable tag centrally; this
# dependency). Promotion is done by moving the dev-lead/v1-stable tag centrally; this
# caller is never edited on release. agent_ref threads the same channel into
# dev-lead's own scripts/prompts checkout. See https://github.com/petry-projects/.github/blob/main/standards/ci-standards.md#dev-lead-agent.
uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref
with:
agent_ref: dev-lead/stable
agent_ref: dev-lead/v1-stable
secrets: inherit
permissions:
contents: write
Expand Down
2 changes: 1 addition & 1 deletion standards/workflows/feature-ideation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ jobs:
discussions: write
id-token: write
actions: read
uses: petry-projects/.github/.github/workflows/feature-ideation-reusable.yml@feature-ideation/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/feature-ideation-reusable.yml@feature-ideation/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref
with:
# All values below come from `needs.prep.outputs.*` (resolved in the `prep`
# job) — NOT the `inputs` context — so this reusable `with:` compiles on the
Expand Down
4 changes: 2 additions & 2 deletions standards/workflows/idea-enhancer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# • This file is a THIN CALLER STUB. All enhancement-dispatch logic (including
# the Ideas-category / non-bot guard) lives in the reusable above.
# • You MUST NOT change: the `uses:` ref — it is pinned to the
# `idea-enhancer/stable` channel, a moving tag advanced centrally. Never
# `idea-enhancer/v1-stable` channel, a moving tag advanced centrally. Never
# repoint it to `@main`, a SHA, or a frozen `@vX`. Do not change the trigger
# events or the `secrets:` block.
# • You MAY change: the cron schedule if the weekly cadence doesn't suit.
Expand Down Expand Up @@ -45,5 +45,5 @@ permissions: {}

jobs:
idea-enhancer:
uses: petry-projects/.github/.github/workflows/idea-enhancer-reusable.yml@idea-enhancer/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/idea-enhancer-reusable.yml@idea-enhancer/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref
secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable
4 changes: 2 additions & 2 deletions standards/workflows/idea-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# • This file is a THIN CALLER STUB. All triage-dispatch logic lives in the
# reusable above.
# • You MUST NOT change: the `uses:` ref — it is pinned to the
# `idea-triage/stable` channel, a moving tag advanced centrally. Never
# `idea-triage/v1-stable` channel, a moving tag advanced centrally. Never
# repoint it to `@main`, a SHA, or a frozen `@vX`. Do not change the
# `secrets:` block.
# • You MAY change: the cron schedule if the weekly cadence doesn't suit.
Expand Down Expand Up @@ -42,5 +42,5 @@ permissions: {}

jobs:
idea-triage:
uses: petry-projects/.github/.github/workflows/idea-triage-reusable.yml@idea-triage/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/idea-triage-reusable.yml@idea-triage/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref
secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable
4 changes: 2 additions & 2 deletions standards/workflows/initiative-planner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# `idea:approved` gate, the trusted-actor check, and the cross-repo dispatch
# to the central BMAD Scrum Master planner) lives in the reusable above.
# • You MUST NOT change: the `uses:` ref — it is pinned to the
# `initiative-planner/stable` channel, a moving tag advanced centrally.
# `initiative-planner/v1-stable` channel, a moving tag advanced centrally.
# Never repoint it to `@main`, a SHA, or a frozen `@vX` (see
# ci-standards.md → Reusable workflow versioning). Also do not change the
# trigger events or the `secrets:` block.
Expand Down Expand Up @@ -44,5 +44,5 @@ permissions: {}

jobs:
initiative-planner:
uses: petry-projects/.github/.github/workflows/initiative-planner-reusable.yml@initiative-planner/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/initiative-planner-reusable.yml@initiative-planner/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref
secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable
4 changes: 2 additions & 2 deletions standards/workflows/pr-auto-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# reusable workflow above.
# • You MAY change: the workflow name(s) in `workflow_run.workflows` to match
# your repository's CI workflow name(s).
# • You MUST NOT change: the `@pr-auto-review/stable` channel in the `uses:`
# • You MUST NOT change: the `@pr-auto-review/v1-stable` channel in the `uses:`
# line — this reusable is on the org-wide moving-channel model (rollout and
# rollback are a single central tag move; a frozen `@vN`/`@<sha>` pin is
# rejected by the compliance audit). Nor the trigger event types or the
Expand Down Expand Up @@ -50,7 +50,7 @@ jobs:
pull-requests: read
checks: read
actions: read
uses: petry-projects/.github/.github/workflows/pr-auto-review-reusable.yml@pr-auto-review/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/pr-auto-review-reusable.yml@pr-auto-review/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
secrets:
# Canonical-first fallback (.github-private#1326). The reusable resolves this
# secret BY NAME, so only the VALUE changes — the passed key stays named
Expand Down
4 changes: 2 additions & 2 deletions standards/workflows/pr-review-mention.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# • This file is a THIN CALLER STUB. All review-dispatch logic lives in the
# reusable workflow above.
# • You MUST NOT change: the `uses:` ref — it is pinned to the
# `pr-review-mention/stable` channel, a moving tag advanced centrally.
# `pr-review-mention/v2-stable` channel, a moving tag advanced centrally.
# Never repoint it to `@main`, a SHA, or a frozen `@vX` (see
# ci-standards.md → Reusable workflow versioning). Also do not change the
# trigger events or the job-level `permissions:` block — reusable workflows
Expand Down Expand Up @@ -41,7 +41,7 @@ jobs:
pr-review-mention:
permissions:
pull-requests: write
uses: petry-projects/.github/.github/workflows/pr-review-mention-reusable.yml@pr-review-mention/stable # NOSONAR(githubactions:S7637) first-party channel ref
uses: petry-projects/.github/.github/workflows/pr-review-mention-reusable.yml@pr-review-mention/v2-stable # NOSONAR(githubactions:S7637) first-party channel ref
secrets:
GH_PAT_WORKFLOWS: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }}
DON_PETRY_BOT_GH_PAT: ${{ secrets.DON_PETRY_BOT_GH_PAT }}
89 changes: 89 additions & 0 deletions test/scripts/standards-templates/vform-pins.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env bats
# Template-guard for the caller-stub templates under standards/workflows/ (#860).
#
# A "deployable" template is one that pins a first-party reusable through a
# channel ref — the `uses:` line carrying the inline
# # NOSONAR(githubactions:S7637) first-party channel ref
# marker. Every such pin must use the major-scoped `<agent>/v<M>-stable` channel
# and never the bare `<agent>/stable` form (a bare pin cannot be migrated by the
# major-scope tooling and silently rides whatever the tip of the channel is).
#
# The 4 verbatim templates (ci, copilot-setup-steps, initiative-driver,
# sonarcloud) have no first-party reusable `uses:` line and are exempt.

REPO_ROOT="$(cd -- "${BATS_TEST_DIRNAME}/../../.." && pwd)"
Comment thread
don-petry marked this conversation as resolved.
WF_DIR="${REPO_ROOT}/standards/workflows"

# The templates that carry no first-party channel-ref `uses:` line.
VERBATIM="ci.yml copilot-setup-steps.yml initiative-driver.yml sonarcloud.yml"

# Emit each first-party channel-ref line in a file (marker-tagged uses lines).
channel_ref_lines() {
grep -nE 'S7637\) first-party channel ref' "$1" || true
}

# Extract the `@<ref>` channel from a `uses: …-reusable.yml@<ref> # …` line.
ref_of() {
sed -E 's/.*-reusable\.yml@([^[:space:]]+).*/\1/' <<<"$1"
Comment on lines +20 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Inspect active reusable uses: entries, not arbitrary marker text.

channel_ref_lines accepts commented/unrelated marker text. A stale commented v1-stable line can make every test pass while the active uses: entry is changed to bare stable. Match anchored, active first-party reusable uses: lines; validate their refs, then require the marker on each matched line.

Proposed direction
-channel_ref_lines() {
-  grep -nE 'S7637\) first-party channel ref' "$1" || true
+reusable_uses_lines() {
+  grep -nE '^[[:space:]]*uses:[[:space:]]+petry-projects/\.github(-private)?/\.github/workflows/[^[:space:]@]+-reusable\.yml@[^[:space:]]+' "$1" || true
 }

Use reusable_uses_lines for ref extraction and separately fail any matched line that lacks the exact NOSONAR marker.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/scripts/standards-templates/vform-pins.bats` around lines 20 - 27,
Update channel_ref_lines and the surrounding validation to inspect only
anchored, active first-party reusable uses: entries, using reusable_uses_lines
for reference extraction. Validate each extracted ref, then independently
require the exact S7637 marker on every matched uses line so unrelated or
commented marker text cannot satisfy the tests.

}

@test "every deployable template pins <agent>/v<M>-stable, never bare <agent>/stable" {
local violations=()
local f name lines line ref
for f in "${WF_DIR}"/*.yml; do
name="$(basename "$f")"
lines="$(channel_ref_lines "$f")"
[ -n "$lines" ] || continue
Comment thread
don-petry marked this conversation as resolved.
while IFS= read -r line; do
ref="$(ref_of "$line")"
# Must be the major-scoped v-form: <agent>/v<M>-stable.
if [[ ! "$ref" =~ ^[a-z0-9-]+/v[0-9]+-stable$ ]]; then
violations+=("${name}: '${ref}' is not <agent>/v<M>-stable")
fi
done <<<"$lines"
done
if [ "${#violations[@]}" -ne 0 ]; then
printf 'bad pin -> %s\n' "${violations[@]}"
return 1
fi
}

@test "agent_ref is byte-equal to the uses: channel where present" {
Comment thread
don-petry marked this conversation as resolved.
local f name uses_ref agent_ref
for f in "${WF_DIR}"/*.yml; do
name="$(basename "$f")"
grep -qE '^[[:space:]]*agent_ref:' "$f" || continue
uses_ref="$(ref_of "$(channel_ref_lines "$f")")"
agent_ref="$(grep -oE 'agent_ref:[[:space:]]*[^[:space:]]+' "$f" | sed -E 's/agent_ref:[[:space:]]*//')"
[ "$agent_ref" = "$uses_ref" ] || {
echo "${name}: agent_ref='${agent_ref}' != uses channel='${uses_ref}'"
return 1
}
done
}

@test "the 4 verbatim templates carry no first-party channel-ref line" {
local name lines
for name in $VERBATIM; do
[ -f "${WF_DIR}/${name}" ] || { echo "missing ${name}"; return 1; }
lines="$(channel_ref_lines "${WF_DIR}/${name}")"
[ -z "$lines" ] || { echo "${name} unexpectedly has a channel-ref line"; return 1; }
done
}

@test "every non-verbatim template HAS a marker-tagged channel-ref line" {
# Guards against a deployable template silently losing its channel pin (or the
# NOSONAR marker): without this, the first test's `[ -n "$lines" ] || continue`
# would skip an unmarked file and pass. Any non-verbatim template must carry a
# first-party channel-ref line so it stays under the v-form pin check.
local f name missing=()
for f in "${WF_DIR}"/*.yml; do
name="$(basename "$f")"
case " $VERBATIM " in *" $name "*) continue ;; esac
[ -n "$(channel_ref_lines "$f")" ] || missing+=("$name")
done
if [ "${#missing[@]}" -ne 0 ]; then
printf 'non-verbatim template missing a first-party channel-ref line -> %s\n' "${missing[@]}"
return 1
fi
}
4 changes: 2 additions & 2 deletions test/workflows/pr-auto-review/stub-secrets.bats
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ CHAIN='${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }}'
[ "$output" = 'GH_PAT_WORKFLOWS' ]
}

@test "stub: keeps the @pr-auto-review/stable channel pin in the uses: line" {
@test "stub: keeps the @pr-auto-review/v1-stable channel pin in the uses: line" {
run yq -r '.jobs.pr-auto-review.uses' "$STUB"
[ "$status" -eq 0 ]
[[ "$output" == *'@pr-auto-review/stable'* ]]
[[ "$output" == *'@pr-auto-review/v1-stable'* ]]
Comment thread
don-petry marked this conversation as resolved.
Comment on lines +28 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the complete channel contract, including rejection of the bare pin.

This assertion only checks for a substring, so malformed refs could pass and @pr-auto-review/stable is not explicitly rejected. Add an exact/anchored v-form assertion plus a negative bare-pin check.

Suggested test change
 `@test` "stub: keeps the `@pr-auto-review/v1-stable` channel pin in the uses: line" {
   run yq -r '.jobs.pr-auto-review.uses' "$STUB"
   [ "$status" -eq 0 ]
-  [[ "$output" == *'`@pr-auto-review/v1-stable`'* ]]
+  [[ "$output" =~ `@pr-auto-review/v`[0-9]+-stable$ ]]
+  [[ "$output" != *'`@pr-auto-review/stable`'* ]]
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@test "stub: keeps the @pr-auto-review/v1-stable channel pin in the uses: line" {
run yq -r '.jobs.pr-auto-review.uses' "$STUB"
[ "$status" -eq 0 ]
[[ "$output" == *'@pr-auto-review/stable'* ]]
[[ "$output" == *'@pr-auto-review/v1-stable'* ]]
`@test` "stub: keeps the `@pr-auto-review/v1-stable` channel pin in the uses: line" {
run yq -r '.jobs.pr-auto-review.uses' "$STUB"
[ "$status" -eq 0 ]
[[ "$output" =~ `@pr-auto-review/v`[0-9]+-stable$ ]]
[[ "$output" != *'`@pr-auto-review/stable`'* ]]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/workflows/pr-auto-review/stub-secrets.bats` around lines 28 - 31, Update
the test for the pr-auto-review uses value to require an exact anchored
`@pr-auto-review/v1-stable` channel reference, rather than accepting any substring
match. Add an explicit negative assertion ensuring the bare
`@pr-auto-review/stable` pin is rejected, while preserving the existing
command-status validation.

}
Loading