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
12 changes: 12 additions & 0 deletions scripts/compliance-audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,18 @@ check_reusable_workflows_disabled() {
continue # hybrid (workflow_call + real trigger) — exempt, must stay active
fi

# Naming lint — a pure reusable must carry the `-reusable.yml` suffix so its
# "library, safe to disable, never runnable" nature is legible from the
# filename. Detection above is by triggers, never by name (a name can lie);
# this is a separate readability finding. Grandfathered: the legacy
# `pr-review.yml` engine is tracked for rename in .github-private#1127 —
# suppress its naming finding until the rename lands.
if [[ "$wf" != *-reusable.yml && "$repo/$wf" != ".github-private/pr-review.yml" ]]; then
add_finding "$repo" "reusable-workflows" "reusable-naming-$wf" "warning" \
"Pure reusable workflow \`$wf\` (workflow_call-only) does not use the required \`-reusable.yml\` name suffix; rename it to \`<purpose>-reusable.yml\` so its reusable nature is legible from the filename" \
"standards/ci-standards.md#pure-reusable-workflows-must-be-disabled"
fi

# Pure reusable — check its enabled/disabled state.
local state
state=$(gh_api "repos/$ORG/$repo/actions/workflows/$wf" --jq '.state' 2>/dev/null || echo "")
Expand Down
22 changes: 19 additions & 3 deletions standards/ci-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,21 @@ release-strategy initiative

**Standard.** A **pure reusable workflow** — one whose `on:` block declares
**`workflow_call` and nothing else** — MUST be set to `disabled_manually` in the
Actions UI. This applies to the single-source-of-truth `*-reusable.yml`
definition files hosted in `petry-projects/.github` and
`petry-projects/.github-private`.
Actions UI, and MUST be named with a **`-reusable.yml`** suffix. Both apply to
the single-source-of-truth reusable definition files hosted in
`petry-projects/.github` and `petry-projects/.github-private`.

**Naming.** The `-reusable.yml` suffix makes the "this is a library, safe to
disable, never runnable on its own" property legible from the filename alone — a
reader (or an auditor) should not have to open the file and parse its `on:` block
to know it is a reusable. The suffix is a **readability aid, not the detection
mechanism**: compliance tooling classifies by triggers, never by name (a name can
lie — see the `pr-review.yml` engine, a pure reusable that predates this rule).
When you extract logic into a new reusable, name it `<purpose>-reusable.yml` from
the start. *Grandfathered exception:* `.github-private/.github/workflows/pr-review.yml`
is a pure reusable that keeps its legacy name until it can be renamed in lockstep
with its caller path and `pr-review/*` release-channel tags — tracked in
`petry-projects/.github-private#1127`.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

**Why.** A pure reusable has no independent event triggers, so it can never run
on its own — it executes **only** when another workflow invokes it via `uses:`.
Expand Down Expand Up @@ -214,6 +226,10 @@ finding. Note that disabled state is **not** captured in git — a newly added
reusable is born `active` and must be disabled explicitly, which is exactly what
this check catches on the next audit.

It also flags, as a separate `warning` finding, any pure reusable whose
filename doesn't carry the `-reusable.yml` suffix (grandfathered exception:
`pr-review.yml`, tracked in `petry-projects/.github-private#1127`).

### Available templates

| Template | Tier | Purpose |
Expand Down
38 changes: 38 additions & 0 deletions test/scripts/compliance-audit/reusable-workflows-disabled.bats
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,41 @@ _should_flag() {
run _should_flag "unknown"
[ "$status" -eq 0 ]
}

# ---------------------------------------------------------------------------
# Mirror the naming-suffix check from check_reusable_workflows_disabled.
# Returns 0 when a reusable-naming finding should be emitted.
# ---------------------------------------------------------------------------
_should_flag_naming() {
local repo="$1" wf="$2"
[[ "$wf" != *-reusable.yml && "$repo/$wf" != ".github-private/pr-review.yml" ]]
}

# ===========================================================================
# Naming suffix check — pure reusable must end in -reusable.yml
# ===========================================================================

@test "naming: compliant suffix (-reusable.yml) is NOT flagged" {
run _should_flag_naming "my-repo" "dev-lead-reusable.yml"
[ "$status" -eq 1 ]
}

@test "naming: missing suffix is flagged" {
run _should_flag_naming ".github-private" "some-workflow.yml"
[ "$status" -eq 0 ]
}

@test "naming: grandfathered pr-review.yml in .github-private is NOT flagged" {
run _should_flag_naming ".github-private" "pr-review.yml"
[ "$status" -eq 1 ]
}

@test "naming: pr-review.yml in a different repo IS flagged (grandfathering is repo-scoped)" {
run _should_flag_naming "some-other-repo" "pr-review.yml"
[ "$status" -eq 0 ]
}

@test "naming: -reusable.yaml extension (not .yml) is flagged" {
run _should_flag_naming "my-repo" "foo-reusable.yaml"
[ "$status" -eq 0 ]
}
Loading