Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
85d43ba
fix(compliance): track per-workflow version tags in stub checker
don-petry May 16, 2026
4b82049
fix(compliance): guard against missing version tag in centralized array
don-petry May 16, 2026
3645418
docs(ci-standards): update tier table and pin rationale for per-workf…
don-petry May 16, 2026
680929e
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
c9766e0
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
c5fde9b
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
6f89a0b
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
204a1f1
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
f1da6e6
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
930e1ad
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
eee61d0
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
dd63ce0
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
60bc9df
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
51b6150
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
cc02b14
Merge branch 'main' into fix/pr-review-mention-v2-compliance
don-petry May 17, 2026
fce58c3
chore(compliance): use safe regex escaping for reusable version tags
don-petry May 17, 2026
01eb68c
chore(compliance): include braces in regex escaping
don-petry May 17, 2026
ca4c4f7
docs(ci): clarify internal reusable workflow tag exceptions for v2
don-petry May 17, 2026
6b627fe
chore(compliance): document regex escaping invariant
don-petry May 17, 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
43 changes: 23 additions & 20 deletions scripts/compliance-audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -677,17 +677,20 @@ check_ci_concurrency() {
}

# ---------------------------------------------------------------------------
# Check: Tier 1 centralized workflows must be thin caller stubs pinned to @v1
# Check: Tier 1 centralized workflows must be thin caller stubs pinned to the
# canonical version tag for their reusable.
#
# For each workflow that the org has centralized into a reusable workflow,
# verify the downstream repo's copy is a stub that delegates via:
# uses: petry-projects/.github/.github/workflows/<reusable>.yml@v1
# uses: petry-projects/.github/.github/workflows/<reusable>.yml@<version>
#
# This prevents drift: a repo that copies the inline pre-centralization
# version (or pins to @main, or pins to an older tag) is flagged so it
# can be re-synced from the standard. The central .github repo itself is
# version (or pins to @main, or pins to a non-canonical tag) is flagged so
# it can be re-synced from the standard. The central .github repo itself is
# exempt because it owns the reusables and may legitimately reference
# its own workflows by @main during release prep.
#
# Array format: "workflow-filename:expected-reusable-basename:version-tag"
# ---------------------------------------------------------------------------
check_centralized_workflow_stubs() {
local repo="$1"
Expand All @@ -696,16 +699,16 @@ check_centralized_workflow_stubs() {
# own reusables by @main; skip the stub check for it.
[ "$repo" = ".github" ] && return

# workflow-filename:expected-reusable-basename
# workflow-filename:expected-reusable-basename:version-tag
local centralized=(
"dev-lead.yml:dev-lead-reusable"
"auto-rebase.yml:auto-rebase-reusable"
"dependency-audit.yml:dependency-audit-reusable"
"dependabot-automerge.yml:dependabot-automerge-reusable"
"dependabot-rebase.yml:dependabot-rebase-reusable"
"agent-shield.yml:agent-shield-reusable"
"feature-ideation.yml:feature-ideation-reusable"
"pr-review-mention.yml:pr-review-mention-reusable"
"dev-lead.yml:dev-lead-reusable:v1"
"auto-rebase.yml:auto-rebase-reusable:v1"
"dependency-audit.yml:dependency-audit-reusable:v1"
"dependabot-automerge.yml:dependabot-automerge-reusable:v1"
"dependabot-rebase.yml:dependabot-rebase-reusable:v1"
"agent-shield.yml:agent-shield-reusable:v1"
"feature-ideation.yml:feature-ideation-reusable:v1"
"pr-review-mention.yml:pr-review-mention-reusable:v2"
)

# List the repo's workflow directory once instead of probing each file.
Expand All @@ -714,9 +717,9 @@ check_centralized_workflow_stubs() {
workflow_list=$(gh_api "repos/$ORG/$repo/contents/.github/workflows" --jq '.[].name' 2>/dev/null || echo "")
[ -z "$workflow_list" ] && return

local entry wf reusable
local entry wf reusable version
for entry in "${centralized[@]}"; do
IFS=':' read -r wf reusable <<< "$entry"
IFS=':' read -r wf reusable version <<< "$entry"
Comment thread
don-petry marked this conversation as resolved.

# Skip workflows that don't exist in this repo. Required workflows are
# checked separately by check_required_workflows; conditional ones
Expand All @@ -734,27 +737,27 @@ check_centralized_workflow_stubs() {
[ -z "$decoded" ] && continue

# Required pattern: a non-comment line whose `uses:` value is exactly
# petry-projects/.github/.github/workflows/<reusable>.yml@v1
# petry-projects/.github/.github/workflows/<reusable>.yml@<version>
# Anchor to start-of-line + optional indent so a `# uses: ...` comment
# cannot satisfy the check.
local expected="petry-projects/\\.github/\\.github/workflows/${reusable}\\.yml@v1"
local expected="petry-projects/\\.github/\\.github/workflows/${reusable}\\.yml@${version}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if echo "$decoded" | grep -qE "^[[:space:]]*uses:[[:space:]]*${expected}([[:space:]]|$)"; then
continue # stub is correctly pinned to @v1 — compliant
continue # stub is correctly pinned to the canonical version — compliant
fi

# Determine why it's non-compliant for a more actionable message.
local why
if echo "$decoded" | grep -qE "^[[:space:]]*uses:[[:space:]]*petry-projects/\\.github/\\.github/workflows/${reusable}\\.yml@"; then
why="references the reusable but is not pinned to \`@v1\` (org standard)"
why="references the reusable but is not pinned to \`@${version}\` (org standard)"
elif echo "$decoded" | grep -qF "petry-projects/.github/.github/workflows/${reusable}"; then
why="references the reusable but the \`uses:\` line does not match the canonical stub"
else
why="is an inline copy instead of a thin caller stub — re-sync from \`standards/workflows/${wf}\`"
fi

add_finding "$repo" "ci-workflows" "non-stub-$wf" "error" \
"Centralized workflow \`$wf\` $why. Replace with the canonical stub from \`standards/workflows/${wf}\` which delegates to \`petry-projects/.github/.github/workflows/${reusable}.yml@v1\`." \
"Centralized workflow \`$wf\` $why. Replace with the canonical stub from \`standards/workflows/${wf}\` which delegates to \`petry-projects/.github/.github/workflows/${reusable}.yml@${version}\`." \
"standards/ci-standards.md#centralization-tiers"
done
}
Expand Down
2 changes: 1 addition & 1 deletion standards/ci-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ present as an org-level secret; no per-repo setup needed.

**Compliance:** The compliance audit (`check_centralized_workflow_stubs`) verifies that
repos have `pr-review-mention.yml` as a thin caller stub delegating to
`petry-projects/.github/.github/workflows/pr-review-mention-reusable.yml@v1`.
`petry-projects/.github/.github/workflows/pr-review-mention-reusable.yml@v2`.
Comment thread
don-petry marked this conversation as resolved.

---

Expand Down