Skip to content
Merged
18 changes: 13 additions & 5 deletions components/github-actions-conventions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,19 @@ what it does. The Apps in use are listed under `apps` in `vocabulary.json`.

## Exemptions

The complete list is `exemptions` in `vocabulary.json`. It holds one entry:
github-iac's OIDC negative test, `release-deploy-oidc-negative-test.yml`,
whose `name:` is `release-deployx`: the deploy workflow's name plus one
character, so the identity policy that trusts `release-deploy` must reject it.
Only that exact value passes: neither the stem nor a Title Case form does.
The complete list is `exemptions` in `vocabulary.json`, each entry with its
reason. Two kinds stand:

- **OIDC trust inputs.** github-iac's `release-deploy.yml` keeps the `name:`
`release-deploy`, which the Pulumi issuer policy trusts, and its negative
test `release-deploy-oidc-negative-test.yml` keeps `release-deployx`, one
character more, which that policy must reject. Only those exact values pass:
neither the stem's Title Case form nor anything else does.
- **Pending renames.** A `workflow-filename` exemption lets a repository run
the lint enforcing before a file it cannot rename yet is renamed; the
entry's reason links the issue whose rename removes it, and every other rule
still applies to the file.

GitHub's dynamic workflows (Dependabot, CodeQL default setup, Pages) need no
Comment thread
kyle-sexton marked this conversation as resolved.
Resolved
entry: they have no file in `.github/workflows/`.

Expand Down
2 changes: 1 addition & 1 deletion components/github-actions-conventions/naming-lint.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ test("the vocabulary loader rejects cross-field drift the schema cannot express"
assert.throws(() => validateVocabulary(nounFunction), /accepted verb/u);

const nameWithoutValue = clone();
delete nameWithoutValue.exemptions[0].expectedName;
delete nameWithoutValue.exemptions.find((e) => e.rules.includes("workflow-name")).expectedName;
assert.throws(() => validateVocabulary(nameWithoutValue), /expectedName/u);

const badShape = clone();
Expand Down
54 changes: 54 additions & 0 deletions components/github-actions-conventions/vocabulary.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"intake": [
{ "name": "label", "status": "in-use" },
{ "name": "label-needs-triage", "status": "in-use" },
{ "name": "reenter", "status": "in-use" },
{ "name": "triage", "status": "in-use" }
],
"plan": [],
Expand Down Expand Up @@ -47,6 +48,7 @@
},
{ "name": "lint-dockerfile", "status": "in-use" },
{ "name": "merge", "status": "reserved", "purpose": "Merge the pull request." },
{ "name": "rate-merge-risk", "status": "in-use" },
{
"name": "refine",
"status": "reserved",
Expand Down Expand Up @@ -75,11 +77,13 @@
],
"post-merge": [
{ "name": "detect-silent-revert", "status": "in-use" },
{ "name": "select-mutation-range", "status": "in-use" },
{
"name": "sweep-comments",
"status": "reserved",
"purpose": "File late review comments as issues."
},
{ "name": "test-mutation", "status": "in-use" },
{
"name": "verify",
"status": "reserved",
Expand All @@ -99,6 +103,7 @@
],
"maintenance": [
{ "name": "audit-code-review-rules", "status": "in-use" },
{ "name": "audit-lane-runs", "status": "in-use" },
{ "name": "audit-link-check-excludes", "status": "in-use" },
{ "name": "audit-org-architecture-pointer", "status": "in-use" },
{ "name": "audit-pr-convention-pins", "status": "in-use" },
Expand All @@ -108,12 +113,18 @@
"status": "in-use",
"purpose": "Check every Markdown link with the network on."
},
{ "name": "check-upstream-drift", "status": "in-use" },
{ "name": "delete-stale-branches", "status": "in-use" },
{ "name": "file-dead-links", "status": "in-use" },
{ "name": "file-recurring-issues", "status": "in-use" },
{ "name": "file-upstream-drift-issues", "status": "in-use" },
{
"name": "measure-skill-edit",
"status": "reserved",
"purpose": "Measure one proposed skill edit against the merged skill on a person's dispatch, and open a draft pull request when it wins."
},
{ "name": "monitor-signals", "status": "in-use" },
{ "name": "reconcile-lost-runs", "status": "in-use" },
{ "name": "reconcile-pr-creation-policy", "status": "in-use" },
{ "name": "repin-account-rotation", "status": "in-use" },
{ "name": "repin-ci-workflows", "status": "in-use" },
Expand Down Expand Up @@ -148,6 +159,7 @@
"clean",
"collect",
"compare",
"delete",
"deploy",
"detect",
"download",
Expand All @@ -161,12 +173,17 @@
"install",
"label",
"lint",
"log",
"measure",
"merge",
"monitor",
"notify",
"open",
"prune",
"publish",
"rate",
"reconcile",
"reenter",
"refine",
"regenerate",
"repin",
Expand All @@ -178,6 +195,7 @@
"review",
"run",
"scan",
"seal",
"select",
"setup",
"sign",
Expand Down Expand Up @@ -291,6 +309,42 @@
}
],
"exemptions": [
{
"path": ".github/workflows/dependabot-fragments.yml",
"repository": "melodic-software/claude-code-plugins",
"rules": ["workflow-filename"],
"reason": "The file name has no stage word; its rename is tracked in melodic-software/claude-code-plugins#15394, which removes this entry."
},
{
"path": ".github/workflows/eval-browser-dispatch.yml",
"repository": "melodic-software/claude-code-plugins",
"rules": ["workflow-filename"],
"reason": "The file name has no stage word; its rename is tracked in melodic-software/claude-code-plugins#15394, which removes this entry."
},
{
"path": ".github/workflows/eval-hill-climb-pass.yml",
"repository": "melodic-software/claude-code-plugins",
"rules": ["workflow-filename"],
"reason": "The file name has no stage word; its rename is tracked in melodic-software/claude-code-plugins#15394, which removes this entry."
},
{
"path": ".github/workflows/security-outside-actor.yml",
"repository": "melodic-software/claude-code-plugins",
"rules": ["workflow-filename"],
"reason": "The file name has no stage word; its rename is tracked in melodic-software/claude-code-plugins#15394, which removes this entry."
},
{
"path": ".github/workflows/pr-feedback-dispatch.yml",
"repository": "melodic-software/claude-code-plugins",
"rules": ["workflow-filename"],
"reason": "The file name is not verb-first after its stage; its rename is tracked in melodic-software/claude-code-plugins#15394, which removes this entry."
},
{
"path": ".github/workflows/post-merge-demotion-alert.yml",
"repository": "melodic-software/claude-code-plugins",
"rules": ["workflow-filename"],
"reason": "The file name is not verb-first after its stage; its rename is tracked in melodic-software/claude-code-plugins#15394, which removes this entry."
},
{
"path": ".github/workflows/release-deploy.yml",
"repository": "melodic-software/github-iac",
Expand Down
1 change: 1 addition & 0 deletions components/lychee-online/targets/claude-code-plugins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ permissions: {}

jobs:
check-links:
name: Check Links
permissions:
contents: read
uses: melodic-software/ci-workflows/.github/workflows/maintenance-check-links-online.yml@main
Expand Down
2 changes: 1 addition & 1 deletion components/managed-files-guard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ the workflow `name:` (each is its destination stem in Title Case) and their head
| `managed-files-guard-fleet-caller` | `managed-files-guard-fleet.yml` | `.github/workflows/pr-check-managed-files.yml` | `melodic-ubuntu-24.04-x64` |

Both carry the same action pin and the same job name, so the check context is
`pr-check-managed-files` on every target. The destinations differ because the
`PR Check Managed Files` on every target. The destinations differ because the
manifest requires each destination to have one owning component.
The guard caller is the second recorded exception to the rule that workflow callers stay
consumer-owned (`distribution/README.md`), for the same reason as the Claude
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ name: PR Check Managed Files
# The two files differ only in this paragraph, the source lines above, the
# workflow `name:` (each is its destination stem in Title Case), the `paths` entries
# (each lists its own targets' destinations) and `runs-on`; the job
# name is the same, so the check context is `pr-check-managed-files` on every
# name is the same, so the check context is `PR Check Managed Files` on every
# target.

# The guard fails only on an exact managed destination path, so the workflow
Expand Down Expand Up @@ -115,9 +115,9 @@ concurrency:

jobs:
pr-check-managed-files:
# Equal to the job id so the check context is `pr-check-managed-files`;
# zizmor's pedantic `anonymous-definition` audit flags an unnamed job.
name: pr-check-managed-files
# The check context is this Title Case name (org naming lint, job-name);
# zizmor's pedantic `anonymous-definition` audit also flags an unnamed job.
name: PR Check Managed Files
runs-on: melodic-ubuntu-24.04-x64
timeout-minutes: 10
# No checkout step: the action checks out this repository itself, with
Expand Down
4 changes: 2 additions & 2 deletions components/managed-files-guard/managed-files-guard.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ check_shape() {

assert_eq "$p exactly one job" '1' "$(q '.jobs | length')"
assert_eq "$p the job is named pr-check-managed-files" 'pr-check-managed-files' "$(q '.jobs | keys | .[0]')"
assert_eq "$p the job display name equals its id (check context is pr-check-managed-files)" \
'pr-check-managed-files' "$(q '.jobs["pr-check-managed-files"].name')"
assert_eq "$p the job display name is the Title Case of its id (check context is PR Check Managed Files)" \
'PR Check Managed Files' "$(q '.jobs["pr-check-managed-files"].name')"
assert_eq "$p the job runs on $runs_on directly" "$runs_on" \
"$(q '.jobs["pr-check-managed-files"]["runs-on"]')"
assert_eq "$p the job has a 10-minute timeout" '10' "$(q '.jobs["pr-check-managed-files"]["timeout-minutes"]')"
Expand Down
6 changes: 3 additions & 3 deletions components/managed-files-guard/managed-files-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ concurrency:

jobs:
pr-check-managed-files:
# Equal to the job id so the check context is `pr-check-managed-files`;
# zizmor's pedantic `anonymous-definition` audit flags an unnamed job.
name: pr-check-managed-files
# The check context is this Title Case name (org naming lint, job-name);
# zizmor's pedantic `anonymous-definition` audit also flags an unnamed job.
name: PR Check Managed Files
runs-on: ubuntu-24.04
timeout-minutes: 10
# No checkout step: the action checks out this repository itself, with
Expand Down
6 changes: 3 additions & 3 deletions distribution/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,9 @@ Both call `.github/actions/check-managed-files` at the same pin. The two files
differ only in `runs-on`, comments, their `on.pull_request.paths` entries (each
lists the managed destinations of the targets that receive it) and the workflow
`name:`, which is each destination stem in Title Case (`PR Check Managed Files Hosted` and
`PR Check Managed Files`). They share the job id and job name
`pr-check-managed-files`, so the check context is `pr-check-managed-files`
on every target, hosted or fleet-routed. The hosted caller
`PR Check Managed Files`). They share the job id
`pr-check-managed-files` and job name `PR Check Managed Files`, which is the
check context on every target, hosted or fleet-routed. The hosted caller
runs on `ubuntu-24.04` and is `managed` for the public targets. The
fleet-routed caller runs on `melodic-ubuntu-24.04-x64` and is `managed` for
the fleet-enrolled private targets (`agent-automations`,
Expand Down
2 changes: 1 addition & 1 deletion distribution/sync-manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ components:
# Two components, one per runner-policy inventory. The files are identical
# except for `runs-on`, the workflow `name:`, their headers and their
# `paths` entries (each lists its own targets' destinations): same action
# pin, same job name, so the check context is `pr-check-managed-files` on
# pin, same job name, so the check context is `PR Check Managed Files` on
# every target. Destinations must be unique across components, so each has
# its own path (`pr-check-managed-files-hosted.yml` for the hosted caller).
# A target takes one or the other, never both.
Expand Down
Loading