From 6377c8a20b2124b83583c1c37a71648d4ea618c9 Mon Sep 17 00:00:00 2001 From: DJ Date: Tue, 7 Apr 2026 20:25:02 -0700 Subject: [PATCH 1/3] feat(compliance-audit): detect non-stub centralized workflow copies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new check to compliance-audit.sh that flags downstream repos whose Tier 1 workflows are not the canonical thin caller stubs pinned to @v1. For each centralizable workflow (claude, dependency-audit, dependabot-{automerge,rebase}, agent-shield, feature-ideation), the check distinguishes three failure modes for actionable findings: 1. Inline copy of pre-centralization logic → "is an inline copy instead of a thin caller stub" 2. References the reusable but not pinned to @v1 (e.g. @main, @v0) → "references the reusable but is not pinned to @v1" 3. Some other malformed uses: line → "the uses: line does not match the canonical stub" The central .github repo is exempt because it owns the reusables and may legitimately reference them by @main during release preparation. Verified locally with hand-crafted fixtures: stub@v1 → no finding, stub@main → flagged with the @v1 message, inline copy → flagged with the inline message, missing file → no finding (handled by check_required_workflows). Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/compliance-audit.sh | 66 +++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/scripts/compliance-audit.sh b/scripts/compliance-audit.sh index 93ac806..8815a9c 100755 --- a/scripts/compliance-audit.sh +++ b/scripts/compliance-audit.sh @@ -468,6 +468,71 @@ check_claude_workflow_checkout() { done } +# --------------------------------------------------------------------------- +# Check: Tier 1 centralized workflows must be thin caller stubs pinned to @v1 +# +# 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/.yml@v1 +# +# 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 +# exempt because it owns the reusables and may legitimately reference +# its own workflows by @main during release prep. +# --------------------------------------------------------------------------- +check_centralized_workflow_stubs() { + local repo="$1" + + # The .github repo is the source of truth and is allowed to reference its + # own reusables by @main; skip the stub check for it. + [ "$repo" = ".github" ] && return + + # workflow-filename:expected-reusable-basename + local centralized=( + "claude.yml:claude-code-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" + ) + + local entry wf reusable + for entry in "${centralized[@]}"; do + IFS=':' read -r wf reusable <<< "$entry" + + local content + content=$(gh_api "repos/$ORG/$repo/contents/.github/workflows/$wf" --jq '.content' 2>/dev/null || echo "") + [ -z "$content" ] && continue # missing workflow caught by check_required_workflows + + local decoded + decoded=$(echo "$content" | base64 -d 2>/dev/null || echo "") + [ -z "$decoded" ] && continue + + # Required pattern: uses: petry-projects/.github/.github/workflows/.yml@v1 + local expected="petry-projects/\\.github/\\.github/workflows/${reusable}\\.yml@v1" + + if echo "$decoded" | grep -qE "uses:[[:space:]]*${expected}([[:space:]]|$)"; then + continue # stub is correctly pinned to @v1 — compliant + fi + + # Determine why it's non-compliant for a more actionable message + local why + if echo "$decoded" | grep -qE "uses:[[:space:]]*petry-projects/\\.github/\\.github/workflows/${reusable}\\.yml@"; then + why="references the reusable but is not pinned to \`@v1\` (org standard)" + elif echo "$decoded" | grep -q "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\`." \ + "standards/ci-standards.md#centralization-tiers" + done +} + # --------------------------------------------------------------------------- # Check: CLAUDE.md exists and references AGENTS.md # --------------------------------------------------------------------------- @@ -944,6 +1009,7 @@ main() { check_sonarcloud "$repo" check_workflow_permissions "$repo" check_claude_workflow_checkout "$repo" + check_centralized_workflow_stubs "$repo" check_claude_md "$repo" check_agents_md "$repo" From f598427d6620850ccfdc9b477efb277fd29cb40a Mon Sep 17 00:00:00 2001 From: DJ Date: Tue, 7 Apr 2026 20:30:32 -0700 Subject: [PATCH 2/3] fix(compliance-audit): use fixed-string grep for reusable path match CodeRabbit on #89: the second-branch grep used an unescaped "petry-projects/.github/.github/workflows/${reusable}" pattern, where BRE dots could in principle match any character. Switch to \`grep -F\` (fixed-string) to match the path literally. No real-world false positive observed (workflow paths contain literal dots), but the hygiene is right. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/compliance-audit.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/compliance-audit.sh b/scripts/compliance-audit.sh index 8815a9c..2f9931b 100755 --- a/scripts/compliance-audit.sh +++ b/scripts/compliance-audit.sh @@ -521,7 +521,7 @@ check_centralized_workflow_stubs() { local why if echo "$decoded" | grep -qE "uses:[[:space:]]*petry-projects/\\.github/\\.github/workflows/${reusable}\\.yml@"; then why="references the reusable but is not pinned to \`@v1\` (org standard)" - elif echo "$decoded" | grep -q "petry-projects/.github/.github/workflows/${reusable}"; then + 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}\`" From da30f529da14ae68c1e96eee1043f05e26a81c5e Mon Sep 17 00:00:00 2001 From: DJ Date: Tue, 7 Apr 2026 20:35:39 -0700 Subject: [PATCH 3/3] fix(compliance-audit): anchor uses: regex + reduce per-repo API calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address remaining Copilot review feedback on #89: 1. Anchor the \`uses:\` regex to start-of-line + optional indent (\`^[[:space:]]*uses:\`) so a commented \`# uses: ...@v1\` line cannot fool the check into marking an inline workflow as compliant. Verified with a fixture: a workflow whose only mention of @v1 is in a YAML comment is now correctly flagged. 2. List \`.github/workflows/\` once per repo and short-circuit the per-file check when the workflow isn't present, instead of probing each of the six centralized files individually. Cuts up to 5 wasted gh api calls per repo (worst case ~2500 fewer requests across the org per audit run). 3. Drop the misleading "missing workflow caught by check_required_workflows" comment — only some of the six are required (claude, dependency-audit, dependabot-automerge, agent-shield); dependabot-rebase and feature-ideation are intentionally optional/conditional. The new directory-listing short-circuit handles all of these uniformly. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/compliance-audit.sh | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/scripts/compliance-audit.sh b/scripts/compliance-audit.sh index 2f9931b..82466e5 100755 --- a/scripts/compliance-audit.sh +++ b/scripts/compliance-audit.sh @@ -498,28 +498,44 @@ check_centralized_workflow_stubs() { "feature-ideation.yml:feature-ideation-reusable" ) + # List the repo's workflow directory once instead of probing each file. + # If the listing fails (no workflows dir), there's nothing to check. + local workflow_list + workflow_list=$(gh_api "repos/$ORG/$repo/contents/.github/workflows" --jq '.[].name' 2>/dev/null || echo "") + [ -z "$workflow_list" ] && return + local entry wf reusable for entry in "${centralized[@]}"; do IFS=':' read -r wf reusable <<< "$entry" + # Skip workflows that don't exist in this repo. Required workflows are + # checked separately by check_required_workflows; conditional ones + # (dependabot-rebase, feature-ideation) are intentionally optional. + if ! echo "$workflow_list" | grep -qxF "$wf"; then + continue + fi + local content content=$(gh_api "repos/$ORG/$repo/contents/.github/workflows/$wf" --jq '.content' 2>/dev/null || echo "") - [ -z "$content" ] && continue # missing workflow caught by check_required_workflows + [ -z "$content" ] && continue local decoded decoded=$(echo "$content" | base64 -d 2>/dev/null || echo "") [ -z "$decoded" ] && continue - # Required pattern: uses: petry-projects/.github/.github/workflows/.yml@v1 + # Required pattern: a non-comment line whose `uses:` value is exactly + # petry-projects/.github/.github/workflows/.yml@v1 + # 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" - if echo "$decoded" | grep -qE "uses:[[:space:]]*${expected}([[:space:]]|$)"; then + if echo "$decoded" | grep -qE "^[[:space:]]*uses:[[:space:]]*${expected}([[:space:]]|$)"; then continue # stub is correctly pinned to @v1 — compliant fi - # Determine why it's non-compliant for a more actionable message + # Determine why it's non-compliant for a more actionable message. local why - if echo "$decoded" | grep -qE "uses:[[:space:]]*petry-projects/\\.github/\\.github/workflows/${reusable}\\.yml@"; then + 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)" 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"