Skip to content
Closed
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
4 changes: 2 additions & 2 deletions .github/runner-policy.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"repositoryOwner": "melodic-software",
"visibility": "private",
"selfHostedCi": true,
"visibility": "public",
"selfHostedCi": false,
Comment thread
kyle-sexton marked this conversation as resolved.
"exceptions": {}
}
63 changes: 14 additions & 49 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,10 @@ concurrency:
# local ci-status gateway aggregates every lane into the
# single required check the org ci-gate ruleset keys on.
jobs:
# One governed routing gate feeds every lane in this workflow: the liveness
# selector makes one routing decision per workflow instead of fanning out
# identical preflights. The short hygiene checks still share one checkout and
# one ephemeral worker to avoid runner-registration churn. A
# selector/configuration failure blocks the complete workload; it cannot
# silently redirect to paid hosted Linux.
select-runner:
name: Select runner
uses: melodic-software/ci-workflows/.github/workflows/select-runner.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5 # ec91c34 2026-07-17
with:
policy: ${{ vars.CI_RUNNER_POLICY }}
self-hosted-label: ${{ vars.CI_SELF_HOSTED_LABEL }}
hosted-runner: ${{ vars.CI_HOSTED_RUNNER }}
scope: ${{ vars.CI_RUNNER_SCOPE }}
managed-runner-prefix: ${{ vars.CI_MANAGED_RUNNER_PREFIX }}
observer-client-id: ${{ vars.CI_RUNNER_OBSERVER_CLIENT_ID }}
secrets:
observer-private-key: ${{ secrets.CI_RUNNER_OBSERVER_PRIVATE_KEY }}

# This repository is public: the runner policy forbids the local-runner
# selector, so every lane pins GitHub-hosted Linux (free on public repos).
hygiene:
needs: select-runner
if: ${{ !cancelled() && needs.select-runner.result == 'success' }}
runs-on: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out
Expand Down Expand Up @@ -187,20 +168,16 @@ jobs:
run: scripts/aggregate-hygiene-results.sh

zizmor:
needs: select-runner
if: ${{ !cancelled() && needs.select-runner.result == 'success' }}
permissions:
contents: read
# Advisory Actions security lint; findings annotate without blocking.
uses: melodic-software/ci-workflows/.github/workflows/zizmor.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5 # ec91c34 2026-07-17
with:
runner: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
runner: ubuntu-24.04
paths: .

hook-utils-sync:
needs: select-runner
if: ${{ !cancelled() && needs.select-runner.result == 'success' }}
runs-on: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out
Expand All @@ -220,9 +197,7 @@ jobs:
run: scripts/sync-hook-utils.sh --check-bump "origin/$BASE_REF"

standards-contract-sync:
needs: select-runner
if: ${{ !cancelled() && needs.select-runner.result == 'success' }}
runs-on: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out
Expand All @@ -242,9 +217,7 @@ jobs:
run: scripts/sync-standards-contract.sh --check-bump "origin/$BASE_REF"

cross-plugin-source-drift:
needs: select-runner
if: ${{ !cancelled() && needs.select-runner.result == 'success' }}
runs-on: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out
Expand All @@ -257,9 +230,7 @@ jobs:
run: bash scripts/check-cross-plugin-source-drift.test.sh

plugin-gate:
needs: select-runner
if: ${{ !cancelled() && needs.select-runner.result == 'success' }}
runs-on: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out
Expand Down Expand Up @@ -309,9 +280,7 @@ jobs:
# and fails on any drift, then runs the bundle over stdio so a build that
# compiles but cannot serve MCP is caught here, not on a consumer's machine.
miro-plugin:
needs: select-runner
if: ${{ !cancelled() && needs.select-runner.result == 'success' }}
runs-on: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out
Expand Down Expand Up @@ -351,9 +320,7 @@ jobs:

runner-policy:
name: Runner policy
needs: select-runner
if: ${{ !cancelled() && needs.select-runner.result == 'success' }}
runs-on: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
Expand All @@ -377,7 +344,6 @@ jobs:

ci-status:
needs:
- select-runner
- hygiene
- hook-utils-sync
- standards-contract-sync
Expand All @@ -387,12 +353,11 @@ jobs:
- runner-policy
- zizmor
# Fail-closed through execution: !cancelled() (never a success-guard) so a
# selector/fleet failure still runs this required aggregate on the hosted
# fallback and the result join below turns it red. A success-guard would skip
# the job, and a skipped required check reports success to branch protection.
# Happy path routes to the fleet (github-iac#78).
# lane failure still runs this required aggregate and the result join below
# turns it red. A success-guard would skip the job, and a skipped required
# check reports success to branch protection.
if: ${{ !cancelled() }}
runs-on: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Aggregate lane results
Expand Down
27 changes: 3 additions & 24 deletions .github/workflows/claude-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,16 @@ permissions:
contents: read

jobs:
# Resolves the capped review tier's label (not the default fleet label) so
# reviews queue on the dedicated review scale set instead of competing with
# build capacity. Routing through the fleet also retires the old
# strict-policy skip guard: review no longer depends on the hosted lane.
select-review:
name: Select runner
permissions: {}
uses: melodic-software/ci-workflows/.github/workflows/select-runner.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5 # ec91c34 2026-07-17
secrets:
observer-private-key: ${{ secrets.CI_RUNNER_OBSERVER_PRIVATE_KEY }}
with:
policy: ${{ vars.CI_RUNNER_POLICY }}
self-hosted-label: ${{ vars.CI_REVIEW_SELF_HOSTED_LABEL }}
hosted-runner: ${{ vars.CI_HOSTED_RUNNER }}
scope: ${{ vars.CI_RUNNER_SCOPE }}
managed-runner-prefix: ${{ vars.CI_MANAGED_RUNNER_PREFIX }}
observer-client-id: ${{ vars.CI_RUNNER_OBSERVER_CLIENT_ID }}

# This repository is public: the runner policy forbids the local-runner
# selector, so review runs on GitHub-hosted Linux (free on public repos).
review:
needs: select-review
# !cancelled() overrides GitHub's skip propagation, so selector success must
# be required explicitly: a hard-failed selector emits no runner output and
# the hosted fallback below would otherwise run this privileged job.
if: ${{ !cancelled() && needs.select-review.result == 'success' }}
permissions:
contents: read # checkout + read the diff
pull-requests: write # post review + track_progress tracking comment
id-token: write # OIDC — mints the Claude GitHub App token
uses: melodic-software/ci-workflows/.github/workflows/claude-review.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5 # ec91c34 2026-07-17
with:
runner: ${{ needs.select-review.outputs.runner || 'ubuntu-24.04' }}
runner: ubuntu-24.04
# Passing skip-actors replaces the reusable's default (dependabot[bot]),
# so the default member is restated alongside the sync bot.
skip-actors: "dependabot[bot],melodic-standards-sync[bot]"
Expand Down
25 changes: 3 additions & 22 deletions .github/workflows/do-not-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# that passed before the label was applied stays green and the merge is never
# blocked. `merge_group` re-checks the label in the queue (inert without a
# queue). The emitted required-check context is `do-not-merge / do-not-merge`.
on:

Check failure on line 12 in .github/workflows/do-not-merge.yml

View workflow job for this annotation

GitHub Actions / zizmor / zizmor

dangerous-triggers

do-not-merge.yml:12: use of fundamentally insecure workflow trigger: pull_request_target is almost always used insecurely
pull_request_target:
types: [opened, reopened, synchronize, labeled, unlabeled]
merge_group:
Expand All @@ -25,30 +25,11 @@
cancel-in-progress: true

jobs:
select-runner:
name: Select runner
permissions: {}
uses: melodic-software/ci-workflows/.github/workflows/select-runner.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5 # ec91c34 2026-07-17
secrets:
observer-private-key: ${{ secrets.CI_RUNNER_OBSERVER_PRIVATE_KEY }}
with:
policy: ${{ vars.CI_RUNNER_POLICY }}
self-hosted-label: ${{ vars.CI_SELF_HOSTED_LABEL }}
hosted-runner: ${{ vars.CI_HOSTED_RUNNER }}
scope: ${{ vars.CI_RUNNER_SCOPE }}
managed-runner-prefix: ${{ vars.CI_MANAGED_RUNNER_PREFIX }}
observer-client-id: ${{ vars.CI_RUNNER_OBSERVER_CLIENT_ID }}

# This repository is public: the runner policy forbids the local-runner
# selector, so the gate runs on GitHub-hosted Linux (free on public repos).
do-not-merge:
needs: select-runner
# Fail-closed selector-result reporter: always() so every routing outcome
# materializes the required check. The reusable receives the selector result
# and fail-closes on any non-success before evaluating the label, so a
# routing failure never dispatches this gate to paid hosted Linux.
if: ${{ always() }}
permissions:
pull-requests: read
uses: melodic-software/ci-workflows/.github/workflows/do-not-merge-gate.yml@90f1c54935203fa31b5b3d1f41531228be2c2b7f # 90f1c54 2026-07-18 gh-CLI-free label refetch
with:
runner: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
prerequisite-result: ${{ needs.select-runner.result }}
runner: ubuntu-24.04
23 changes: 3 additions & 20 deletions .github/workflows/link-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,17 @@ permissions:
contents: read

jobs:
select-runner:
name: Select runner
permissions: {}
uses: melodic-software/ci-workflows/.github/workflows/select-runner.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5 # ec91c34 2026-07-17
secrets:
observer-private-key: ${{ secrets.CI_RUNNER_OBSERVER_PRIVATE_KEY }}
with:
policy: ${{ vars.CI_RUNNER_POLICY }}
self-hosted-label: ${{ vars.CI_SELF_HOSTED_LABEL }}
hosted-runner: ${{ vars.CI_HOSTED_RUNNER }}
scope: ${{ vars.CI_RUNNER_SCOPE }}
managed-runner-prefix: ${{ vars.CI_MANAGED_RUNNER_PREFIX }}
observer-client-id: ${{ vars.CI_RUNNER_OBSERVER_CLIENT_ID }}

# This repository is public: the runner policy forbids the local-runner
# selector, so this lane runs on GitHub-hosted Linux (free on public repos).
link-check:
# Routes through the governed selector to the fleet; the old strict-policy
# pause is retired now that this issue-writing lane no longer depends on
# hosted compute.
needs: select-runner
if: ${{ !cancelled() && needs.select-runner.result == 'success' }}
# issues: write is scoped to this job (least privilege) — only the called
# workflow files the rolling tracking issue.
permissions:
contents: read
issues: write
uses: melodic-software/ci-workflows/.github/workflows/link-check.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5 # ec91c34 2026-07-17
with:
runner: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
runner: ubuntu-24.04
args: >-
--cache
--max-cache-age 7d
Expand Down
25 changes: 3 additions & 22 deletions .github/workflows/pr-issue-linkage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# reports the check green in the queue (the body was validated at PR time; inert
# without a queue). The emitted required-check context is
# `pr-issue-linkage / pr-issue-linkage`.
on:

Check failure on line 13 in .github/workflows/pr-issue-linkage.yml

View workflow job for this annotation

GitHub Actions / zizmor / zizmor

dangerous-triggers

pr-issue-linkage.yml:13: use of fundamentally insecure workflow trigger: pull_request_target is almost always used insecurely
pull_request_target:
types: [opened, edited, reopened, synchronize]
merge_group:
Expand All @@ -25,29 +25,10 @@
cancel-in-progress: true

jobs:
select-runner:
name: Select runner
permissions: {}
uses: melodic-software/ci-workflows/.github/workflows/select-runner.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5 # ec91c34 2026-07-17
secrets:
observer-private-key: ${{ secrets.CI_RUNNER_OBSERVER_PRIVATE_KEY }}
with:
policy: ${{ vars.CI_RUNNER_POLICY }}
self-hosted-label: ${{ vars.CI_SELF_HOSTED_LABEL }}
hosted-runner: ${{ vars.CI_HOSTED_RUNNER }}
scope: ${{ vars.CI_RUNNER_SCOPE }}
managed-runner-prefix: ${{ vars.CI_MANAGED_RUNNER_PREFIX }}
observer-client-id: ${{ vars.CI_RUNNER_OBSERVER_CLIENT_ID }}

# This repository is public: the runner policy forbids the local-runner
# selector, so the gate runs on GitHub-hosted Linux (free on public repos).
pr-issue-linkage:
needs: select-runner
# Fail-closed selector-result reporter: always() so every routing outcome
# materializes the required check. The reusable receives the selector result
# and fail-closes on any non-success before validating the body, so a
# routing failure never dispatches this gate to paid hosted Linux.
if: ${{ always() }}
permissions: {}
uses: melodic-software/ci-workflows/.github/workflows/pr-issue-linkage.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5 # ec91c34 2026-07-17
with:
runner: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
prerequisite-result: ${{ needs.select-runner.result }}
runner: ubuntu-24.04
44 changes: 12 additions & 32 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,52 +20,32 @@ concurrency:
cancel-in-progress: true

jobs:
select-runner:
name: Select runner for PR title
uses: melodic-software/ci-workflows/.github/workflows/select-runner.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5 # ec91c34 2026-07-17
with:
policy: ${{ vars.CI_RUNNER_POLICY }}
self-hosted-label: ${{ vars.CI_SELF_HOSTED_LABEL }}
hosted-runner: ${{ vars.CI_HOSTED_RUNNER }}
scope: ${{ vars.CI_RUNNER_SCOPE }}
managed-runner-prefix: ${{ vars.CI_MANAGED_RUNNER_PREFIX }}
observer-client-id: ${{ vars.CI_RUNNER_OBSERVER_CLIENT_ID }}
secrets:
observer-private-key: ${{ secrets.CI_RUNNER_OBSERVER_PRIVATE_KEY }}

# This repository is public: the runner policy forbids the local-runner
# selector, so validation runs on GitHub-hosted Linux (free on public repos).
validate-pr-title:
# Fail-closed selector-result reporter: always() so every routing outcome
# materializes the required check. The reusable receives the selector result
# and fail-closes on any non-success, reporting the check as failed rather
# than dispatching validation to paid hosted Linux.
if: ${{ always() }}
needs: select-runner
permissions:
pull-requests: read # Reads the pull-request title for validation.
uses: melodic-software/ci-workflows/.github/workflows/semantic-pr.yml@ec91c3433a8c3c0a7ebbdd239286e5a6a25eeec5 # ec91c34 2026-07-17
with:
runner: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
prerequisite-result: ${{ needs.select-runner.result }}
runner: ubuntu-24.04

pr-title:
name: pr-title / pr-title
needs: [select-runner, validate-pr-title]
needs: validate-pr-title
# Fail-closed through execution: !cancelled() (never a success-guard) so a
# selector failure still runs this required wrapper on the hosted fallback
# and the step below turns it red. A success-guard would skip the job, and a
# skipped required check reports success to branch protection. Happy path
# routes to the fleet (github-iac#78).
# validation failure still runs this required wrapper and the step below
# turns it red. A success-guard would skip the job, and a skipped required
# check reports success to branch protection.
if: ${{ !cancelled() }}
runs-on: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
permissions: {}
steps:
- name: Enforce routing and semantic validation results
- name: Enforce semantic validation result
env:
SELECTOR_RESULT: ${{ needs.select-runner.result }}
VALIDATION_RESULT: ${{ needs.validate-pr-title.result }}
run: |
if [[ "$SELECTOR_RESULT" != success || "$VALIDATION_RESULT" != success ]]; then
echo "Required PR-title gate failed: selector=$SELECTOR_RESULT validation=$VALIDATION_RESULT"
if [[ "$VALIDATION_RESULT" != success ]]; then
echo "Required PR-title gate failed: validation=$VALIDATION_RESULT"
exit 1
fi
echo "PR-title routing and semantic validation passed."
echo "PR-title semantic validation passed."
Loading