Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
e9c710a
feat: implement issue #1115 — [bug] dismiss-stale-bot-reviews is miss…
don-petry Sep 12, 2026
1628c70
chore: dev-lead update (review-changes) [skip ci-relay]
don-petry Sep 12, 2026
db1af86
fix(reviews): address review comments [skip ci-relay]
don-petry Sep 12, 2026
effd39d
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 13, 2026
abda840
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 13, 2026
10492b4
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 13, 2026
cb91a6d
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 13, 2026
87297c4
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 16, 2026
a93eb82
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 16, 2026
68cc8c3
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 20, 2026
77e22f5
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 20, 2026
23db64f
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 20, 2026
d238628
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 20, 2026
4646d9e
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 21, 2026
b066763
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 23, 2026
bf344fd
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 23, 2026
f33a9fe
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 23, 2026
0edfd81
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 23, 2026
60b66b7
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 24, 2026
973a5ec
fix(reviews): address review comments [skip ci-relay]
don-petry Sep 24, 2026
522a345
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 27, 2026
bad8d40
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 30, 2026
a75c5ee
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Sep 30, 2026
6c9693d
fix: address Cubic review findings on dismiss-stale-bot-reviews workf…
don-petry Sep 30, 2026
862f740
fix: address CI failures in dismiss-stale-bot-reviews workflow
don-petry Sep 30, 2026
1cfc5a2
fix: correct pagination loop logic in dismiss-stale-bot-reviews.sh
don-petry Sep 30, 2026
97215dc
fix: add --apply flag logic to dismiss-stale-bot-reviews workflows
don-petry Sep 30, 2026
74f0541
fix(bot): address bot feedback [skip ci-relay]
don-petry Sep 30, 2026
acd9c9c
fix: address Lint and bats test failures
don-petry Sep 30, 2026
9430b1d
fix: resolve test failures in dismiss-stale-bot-reviews and deploy-st…
don-petry Sep 30, 2026
3226728
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 2, 2026
6cde664
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 2, 2026
16310da
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 2, 2026
a011198
fix(reviews): address review comments [skip ci-relay]
don-petry Oct 2, 2026
6f5c527
fix(reviews): address review comments [skip ci-relay]
don-petry Oct 2, 2026
827c781
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 3, 2026
2943068
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 3, 2026
fdb1fce
fix: use bash [[ ]] instead of [ ] for conditional tests
don-petry Oct 3, 2026
9af00bd
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 3, 2026
7547c1c
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 3, 2026
0e42039
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 3, 2026
a95d647
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 3, 2026
8cedb84
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 4, 2026
6f32f0c
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 4, 2026
fc5b3f5
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 4, 2026
60828c1
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 5, 2026
6913e37
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 7, 2026
d713392
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 8, 2026
fad23c6
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 9, 2026
21b03fc
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 10, 2026
1d4be55
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 10, 2026
c6eb356
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 10, 2026
873545c
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 10, 2026
4112475
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 10, 2026
9fc41e5
Merge branch 'main' into dev-lead/issue-1115-20260912-1657
don-petry Oct 10, 2026
dd0b14f
chore: rename petry-projects references to combsmith
don-petry Oct 14, 2026
d610496
chore: limit the org-rename rewrite to the files this PR changes
don-petry Oct 14, 2026
76bfe4b
chore: limit the org-rename rewrite to the files this PR changes
don-petry Oct 14, 2026
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
71 changes: 71 additions & 0 deletions .github/workflows/dismiss-stale-bot-reviews-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Quality gate for the dismiss-stale-bot-reviews decision core (#1115).
#
# Triggered on any PR that touches:
# - scripts/dismiss-stale-bot-reviews.sh (orchestrator glue)
# - scripts/lib/dismiss-stale-bot-reviews.sh (pure decision core)
# - test/workflows/dismiss-stale-bot-reviews/** (the tests themselves)
# - .github/workflows/dismiss-stale-bot-reviews*.yml
# - standards/workflows/dismiss-stale-bot-reviews.yml
#
# Gate: bats — the decision matrix (dismiss an allow-listed bot's
# CHANGES_REQUESTED on a superseded commit; keep it on the current head;
# never touch human or non-allow-listed reviews).

name: Dismiss Stale Bot Reviews Tests

on:
pull_request:
paths:
- 'scripts/dismiss-stale-bot-reviews.sh'
- 'scripts/lib/dismiss-stale-bot-reviews.sh'
- 'test/workflows/dismiss-stale-bot-reviews/**'
- '.github/workflows/dismiss-stale-bot-reviews*.yml'
- 'standards/workflows/dismiss-stale-bot-reviews.yml'
push:
branches:
- main
paths:
- 'scripts/dismiss-stale-bot-reviews.sh'
- 'scripts/lib/dismiss-stale-bot-reviews.sh'
- 'test/workflows/dismiss-stale-bot-reviews/**'
- '.github/workflows/dismiss-stale-bot-reviews*.yml'
- 'standards/workflows/dismiss-stale-bot-reviews.yml'

permissions: {}

concurrency:
group: dismiss-stale-bot-reviews-tests-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: bats
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
persist-credentials: false

- name: Install bats and jq
run: |
set -euo pipefail
max_retries=3
for attempt in $(seq 1 "$max_retries"); do
if sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends bats jq; then
exit 0
fi
if [ "$attempt" -lt "$max_retries" ]; then
backoff=$((2 ** (attempt - 1)))
echo "::warning::apt failed (attempt $attempt/$max_retries); retrying in ${backoff}s…" >&2
sleep "$backoff"
fi
done
exit 1

- name: Run bats suite
run: bats --print-output-on-failure test/workflows/dismiss-stale-bot-reviews/
100 changes: 100 additions & 0 deletions .github/workflows/dismiss-stale-bot-reviews.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# ─────────────────────────────────────────────────────────────────────────────
# SOURCE OF TRUTH: combsmith/.github/standards/workflows/dismiss-stale-bot-reviews.yml
# Standard: combsmith/.github/standards/ci-standards.md
# Decision core: combsmith/.github/scripts/lib/dismiss-stale-bot-reviews.sh
# combsmith/.github/scripts/dismiss-stale-bot-reviews.sh
#
# AGENTS — READ BEFORE EDITING:
# • This file is a SELF-CONTAINED verbatim stub (the initiative-driver class),
# NOT a thin caller for a reusable. A bot's CHANGES_REQUESTED review is NOT
# cleared by GitHub's dismiss_stale_reviews_on_push (that only dismisses stale
# *approvals*), so it persists on a superseded commit and blocks reviewDecision
# forever (#617 blocker 3). This workflow dismisses such reviews once a NEWER
# commit has been pushed.
# • All dismiss/keep decision logic lives in the decision core above, which is
# unit-tested by bats in combsmith/.github. This stub fetches that core
# from combsmith/.github@v1 at runtime and runs it — the same runtime
# tooling-checkout pattern initiative-driver uses for the rate-limit gate.
# • You MUST NOT change: the trigger event types (synchronize + submitted), the
# fork guard, or the job-level `permissions:` block (pull-requests: write is
# required to dismiss a review; removing it breaks the dismissal).
# • BOT-ONLY by design: human reviews are never dismissed. To change the bot
# allow-list, set the DSBR_BOT_ALLOWLIST repo/org variable (comma-separated
# logins) — it REPLACES the default list. Do not fork the decision logic here.
# • If you need different behaviour, open a PR against the decision core in
# combsmith/.github — the change propagates everywhere on next run.
# ─────────────────────────────────────────────────────────────────────────────
#
# Dismiss stale bot reviews — self-contained org-standard workflow.
# To adopt: copy this file verbatim to .github/workflows/dismiss-stale-bot-reviews.yml.
# No secrets required — uses GITHUB_TOKEN only (pull-requests: write).
name: Dismiss stale bot reviews

on:
# A new push advances the head; a bot's CHANGES_REQUESTED now sits on a
# superseded commit and must be cleared.
pull_request:
types: [synchronize]
Comment on lines +36 to +37

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 Badge Backfill reviews that were stale before installation

This workflow only runs for a future synchronize event (or a newly submitted review); merging it does not replay either event for already-open PRs. Consequently the stale bot review already blocking #1094—and any other review made stale before this workflow is deployed—remains blocking until another commit is pushed or another review is submitted. Add a targeted dispatch/backfill path (or explicitly dismiss the known review) so the stated existing blocker is actually cleared.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Skipping this one — a code change here would conflict with the workflow's own hard constraints. The stub carries an explicit AGENTS — READ BEFORE EDITING block: "You MUST NOT change: the trigger event types (synchronize + submitted), the fork guard, or the job-level permissions block." A workflow_dispatch/backfill path can't function without touching exactly those: the fork guard (if: github.event.pull_request.head.repo.full_name == github.repository) and the concurrency group both key off github.event.pull_request, which is null for a dispatch event, so the job would be skipped on dispatch unless the guard is rewritten. This file is also a verbatim copy of standards/workflows/dismiss-stale-bot-reviews.yml and must stay byte-identical, so the change couldn't be confined here.

The stated blocker (the stale review on #1094) clears through the designed mechanism — the next synchronize (any push) or pull_request_review submitted event on that PR triggers this workflow against the settled head. A one-time backfill/manual dismissal is an operational action rather than a change to this self-contained stub; leaving this thread open for maintainer follow-up if a dedicated backfill path is desired.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Skipping — re-confirmed this pass. A backfill/workflow_dispatch path cannot be added without violating this stub's hard constraints: the AGENTS — READ BEFORE EDITING block (line 18) forbids changing the trigger event types, and both the fork guard (if: github.event.pull_request.head.repo.full_name == github.repository, line 55) and the concurrency group (line 48) key off github.event.pull_request, which is null for a workflow_dispatch event — so the job would be skipped on dispatch unless those forbidden lines were rewritten. This file is also byte-identical to standards/workflows/dismiss-stale-bot-reviews.yml (verified this pass) and must stay so, so no fix could be confined here.

The stated blocker (the stale review on #1094) clears through the designed mechanism: the next synchronize (any push) or pull_request_review submitted event on that PR triggers this workflow against the settled head. A one-time manual dismissal is an operational action, not a code change to this self-contained stub. Leaving this thread open for maintainer follow-up if a dedicated backfill path is later desired.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Skipping — re-verified this pass against the current file. A backfill/workflow_dispatch path cannot be added without violating this stub's hard constraints:

  • The AGENTS — READ BEFORE EDITING block (line 18) forbids changing the trigger event types (synchronize + submitted), the fork guard, and the job-level permissions: block.
  • Both the fork guard (if: github.event.pull_request.head.repo.full_name == github.repository, line 55) and the concurrency group (line 48) key off github.event.pull_request, which is null for a workflow_dispatch event — so the job would be skipped on dispatch unless those forbidden lines were rewritten.
  • This file is byte-identical to standards/workflows/dismiss-stale-bot-reviews.yml (verified via diff this pass) and must stay so, so no fix could be confined here.

The stated blocker (the stale review on #1094) clears through the designed mechanism: the next synchronize (any push) or pull_request_review submitted event on that PR triggers this workflow against the settled head. A one-time manual dismissal is an operational action, not a code change to this self-contained stub. Leaving this thread open for maintainer follow-up if a dedicated backfill path is later desired.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Skipping — re-confirmed this pass (unchanged from prior passes). A backfill/workflow_dispatch path cannot be added without violating this stub's hard constraints: the AGENTS — READ BEFORE EDITING block forbids changing the trigger event types (synchronize + submitted), and both the fork guard (github.event.pull_request.head.repo.full_name == github.repository) and the concurrency group key off github.event.pull_request, which is null for a workflow_dispatch event — so the job would be skipped on dispatch unless those forbidden lines were rewritten. The file is also byte-identical to standards/workflows/dismiss-stale-bot-reviews.yml (verified via diff this pass) and must stay so. The stated blocker (the stale review on #1094) clears through the designed mechanism: the next synchronize (any push) or pull_request_review submitted event triggers this workflow against the settled head. A one-time manual dismissal is an operational action, not a code change to this self-contained stub. Leaving open for maintainer follow-up if a dedicated backfill path is later desired.

# A bot may also submit a review on an already-superseded commit (race); the
# decision core keeps it only when it is on the current head.
pull_request_review:
types: [submitted]

permissions: {}

concurrency:
# One lane per PR so a push burst does not fan out duplicate dismissals; the
# latest head wins.
group: dismiss-stale-bot-reviews-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
dismiss:
# Same-repo PRs only: a fork PR's GITHUB_TOKEN is read-only, so the dismissal
# mutation would fail. Forked-PR stale reviews are left for human handling.
if: github.event.pull_request.head.repo.full_name == github.repository
Comment on lines +53 to +55

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 Badge Account for read-only tokens on Dependabot pull requests

Dependabot branches live in the same repository, so they pass this fork guard, but GitHub runs workflows for Dependabot-authored pull requests with a read-only GITHUB_TOKEN even when the job requests pull-requests: write. On a Dependabot PR with a stale CodeRabbit or Copilot change request, the job therefore reaches dismissPullRequestReview but cannot perform the mutation, leaving the PR blocked. Handle these PRs through a trusted write-capable event or token, or explicitly exclude and surface them as unsupported rather than treating all same-repository PRs as writable.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Skipping — the concern (Dependabot PRs run with a read-only GITHUB_TOKEN even when the job requests pull-requests: write, so a same-repo Dependabot PR passes the fork guard but the dismissal mutation cannot mutate) is real, but it cannot be fixed within this stub's hard constraints. Both suggested remedies require touching lines the AGENTS — READ BEFORE EDITING block explicitly locks: (a) a trusted write-capable event would change the trigger event types (locked to synchronize + submitted); (b) explicitly excluding Dependabot PRs would change the fork guard if: expression (also locked). The file must additionally stay byte-identical to standards/workflows/dismiss-stale-bot-reviews.yml, so no fix could be confined here. Importantly the current behaviour already fails CLOSED and safely: the orchestrator verifies the mutation result (errors-envelope check + confirmed DISMISSED state) and, when it cannot dismiss, emits a ::warning:: and leaves the review in place — the same 'left for human handling' outcome the fork guard already documents for forked PRs. Nothing is silently mis-reported. Leaving open for maintainer follow-up if the guard/event model is ever revisited at the standard-template level.

runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
pull-requests: write # dismissPullRequestReview via GITHUB_TOKEN
contents: read # checkout
steps:
- name: Checkout decision-core tooling from combsmith/.github
# The decision core lives in combsmith/.github; a consumer repo does
# not carry it, so fetch it read-only at the pinned v1 tag into a scratch
# path. combsmith/.github is public, so GITHUB_TOKEN suffices.
# continue-on-error: a tooling-checkout outage must never fail the PR — the
# run step then finds no script and no-ops (fail-closed: nothing dismissed).
continue-on-error: true
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: combsmith/.github
ref: v1
path: .dsbr-tooling
fetch-depth: 1
persist-credentials: false

- name: Dismiss stale allow-listed bot reviews on superseded commits
env:
GH_TOKEN: ${{ github.token }}
# Optional wholesale override of the bot allow-list (comma-separated
# logins). Unset → the decision core's built-in CodeRabbit/Copilot list.
DSBR_BOT_ALLOWLIST: ${{ vars.DSBR_BOT_ALLOWLIST }}
# Optional dry-run mode (set to "true" to preview without dismissing).
DSBR_DRY_RUN: ${{ vars.DSBR_DRY_RUN }}
run: |
set -euo pipefail
core=".dsbr-tooling/scripts/dismiss-stale-bot-reviews.sh"
if [ ! -f "$core" ]; then
echo "::warning::decision-core tooling unavailable (checkout failed); skipping — nothing dismissed"
exit 0
fi
args=(
--owner "${{ github.repository_owner }}"
--name "${{ github.event.repository.name }}"
--pr "${{ github.event.pull_request.number }}"
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
)
if [ "$DSBR_DRY_RUN" != "true" ]; then
args+=(--apply)
fi
Comment on lines +97 to +99

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Logic error: Default behavior is reversed - will dismiss reviews instead of dry-run

When DSBR_DRY_RUN is unset/empty (the normal case), the condition [ "$DSBR_DRY_RUN" != "true" ] evaluates to true (empty string != "true"), causing --apply to be added. This makes the workflow dismiss reviews by default.

However, the orchestrator script (line 51) explicitly states "dry-run is the default", and APPLY defaults to false (line 25). The workflow contradicts this by adding --apply when the variable is unset.

# Fix: Only add --apply when explicitly requested
if [ "$DSBR_DRY_RUN" = "false" ]; then
  args+=(--apply)
fi

Or alternatively:

# Default to dry-run, only apply when explicitly enabled
if [ "${DSBR_DRY_RUN:-true}" != "true" ]; then
  args+=(--apply)
fi

Without this fix, the workflow will immediately start dismissing bot reviews in production when deployed, rather than running safely in preview mode by default.

Suggested change
if [ "$DSBR_DRY_RUN" != "true" ]; then
args+=(--apply)
fi
if [ "$DSBR_DRY_RUN" = "false" ]; then
args+=(--apply)
fi

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

bash "$core" "${args[@]}"
26 changes: 22 additions & 4 deletions scripts/deploy-standard-workflows.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# deploy-standard-workflows.sh — Deploy org-standard workflow stubs to all repos.
#
# Reads canonical stubs from standards/workflows/ and deploys them into every
# repo in the petry-projects org by opening a pull request per repo (via the
# repo in the combsmith org by opening a pull request per repo (via the
# shared scripts/lib/standards-deploy.sh primitive). Only workflows that have a
# template in standards/workflows/ AND appear in the DEPLOYABLE_WORKFLOWS list
# below are eligible — tech-stack-specific workflows (ci.yml, sonarcloud.yml)
Expand All @@ -14,7 +14,7 @@
# succeed. Opening a PR works uniformly on protected and unprotected repos and
# leaves an auditable, CI-gated record. The PRs are labeled `standards-sync` and
# left for the normal review/auto-merge pipeline — this script never merges and
# never uses --admin. See petry-projects/.github#478.
# never uses --admin. See combsmith/.github#478.
#
# Usage:
# deploy-standard-workflows.sh [options]
Expand Down Expand Up @@ -54,7 +54,7 @@ trap 'rm -f "${_TMPFILES[@]+"${_TMPFILES[@]}"}"' EXIT
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
ORG="petry-projects"
ORG="combsmith"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
STANDARDS_DIR="$REPO_ROOT/standards/workflows"

Expand Down Expand Up @@ -134,6 +134,7 @@ DEPLOYABLE_WORKFLOWS=(
initiative-driver.yml
pr-auto-review.yml
feature-ideation.yml
dismiss-stale-bot-reviews.yml
Comment thread
don-petry marked this conversation as resolved.
Comment thread
don-petry marked this conversation as resolved.
)

# Deployable workflows whose stub BODY carries a documented per-repo edit the
Expand Down Expand Up @@ -379,6 +380,23 @@ is_pin_compliant() {
fi

local prefix="${expected_uses%@*}" ref_after="${expected_uses##*@}" base
# Distinguish a reusable-WORKFLOW call from a third-party ACTION step. A thin
# caller stub's first `uses:` targets a reusable workflow — `owner/repo/.github/
# workflows/<name>.yml@<ref>` (prefix ends in `.yml`) — and that pin IS the
# compliance signal. A SELF-CONTAINED verbatim stub (initiative-driver class,
# e.g. dismiss-stale-bot-reviews.yml, #1115) has NO reusable call; its first
# `uses:` is an action step like `actions/checkout@<sha>` (prefix does NOT end
# in `.yml`). Matching only on that checkout SHA would declare the stub
# compliant even after its triggers, permissions, or run step drift from the
# template, so later non-checkout template fixes would never propagate (#1116).
# Treat such a stub exactly like the no-uses verbatim case above: full-content
# compare (CRLF-normalized).
if [[ "$prefix" != *.yml ]]; then
local template_content normalized_existing
template_content=$(tr -d '\r' < "$template")
normalized_existing=$(printf '%s' "$existing_content" | tr -d '\r')
[[ "$normalized_existing" == "$template_content" ]] && return 0 || return 1
fi
if [[ "$prefix" =~ /([a-z0-9-]+)-reusable\.yml$ ]]; then
base="${BASH_REMATCH[1]}"
# Only treat it as ring-managed if the reusable is on the ring model AND the
Expand Down Expand Up @@ -432,7 +450,7 @@ reusable_base_of() {
}

# reusable_host_of <template> -> the org/repo that HOSTS the reusable (and thus
# carries its release tags), e.g. `petry-projects/.github-private` for dev-lead.
# carries its release tags), e.g. `combsmith/.github-private` for dev-lead.
reusable_host_of() {
local prefix; prefix="$(reusable_uses_of "$1")"; prefix="${prefix%@*}"
cut -d/ -f1-2 <<< "$prefix"
Expand Down
Loading