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
15 changes: 1 addition & 14 deletions .github/runner-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,5 @@
"repositoryOwner": "melodic-software",
"visibility": "private",
"selfHostedCi": true,
"exceptions": {
".github/workflows/ci.yml#ci-status": {
"reason": "hosted-control-plane",
"justification": "The required CI gateway remains independent of selector and fleet health so route failures cannot become successful skipped checks."
},
".github/workflows/link-check.yml#link-check": {
"reason": "privileged-control-plane",
"justification": "The scheduled link checker can create or update repository issues and therefore remains on GitHub-hosted compute."
},
".github/workflows/pr-title.yml#pr-title": {
"reason": "hosted-control-plane",
"justification": "The required PR-title gateway remains independent of selector and fleet health so route failures cannot become successful skipped checks."
}
}
"exceptions": {}
}
15 changes: 10 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ jobs:
files: >-
.github/workflows/ci.yml
.github/workflows/claude-review.yml
.github/workflows/do-not-merge.yml
.github/workflows/link-check.yml
.github/workflows/pr-issue-linkage.yml
.github/workflows/pr-title.yml

- name: Verify shebang files are executable
Expand Down Expand Up @@ -352,19 +354,22 @@ jobs:
CI_REPOSITORY_VISIBILITY: ${{ github.event.repository.visibility }}

ci-status:
if: always()
needs:
- select-runner
- hygiene
- hook-utils-sync
- cross-plugin-source-drift
- plugin-gate
- miro-plugin
- runner-policy
- zizmor
# The required gateway remains independent of selector/fleet health so a
# failed route materializes as a failing required check, never a skipped
# check that GitHub could treat as successful.
runs-on: ubuntu-24.04
# 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).
if: ${{ !cancelled() }}
runs-on: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
timeout-minutes: 15
steps:
- name: Aggregate lane results
Expand Down
54 changes: 54 additions & 0 deletions .github/workflows/do-not-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: do-not-merge

# Blocks merging while the PR carries the `do-not-merge` label, via the shared
# do-not-merge-gate reusable from ci-workflows. Runs on pull_request_target so
# the base-branch definition evaluates (a head edit cannot neuter the gate);
# safe because the reusable reads PR label metadata via the API and runs no head
# code. `labeled`/`unlabeled` are required so applying or removing the label
# re-evaluates the gate — a status check binds to a SHA, so without them a check
# 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:

permissions:
pull-requests: read

# pull_request_target runs the base-branch definition, so github.ref is the base
# branch: the PR number scopes cancellation and github.ref covers merge_group
# (which carries no pull_request object); the fallback is inert without a queue.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
select-runner:
name: Select runner
permissions: {}
uses: melodic-software/ci-workflows/.github/workflows/select-runner.yml@cdc5917c15aade1995bd810b60d818cadc635b52 # cdc5917 2026-07-16
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 }}

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@885302176345486ca6c2c392d83131f9b5389251 # 8853021 2026-07-16
Comment thread
kyle-sexton marked this conversation as resolved.
with:
runner: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
prerequisite-result: ${{ needs.select-runner.result }}
25 changes: 21 additions & 4 deletions .github/workflows/link-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,34 @@ permissions:
contents: read

jobs:
select-runner:
name: Select runner
permissions: {}
uses: melodic-software/ci-workflows/.github/workflows/select-runner.yml@cdc5917c15aade1995bd810b60d818cadc635b52 # cdc5917 2026-07-16
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 }}

link-check:
# This issue-writing advisory lane remains hosted by policy and is paused
# while strict local-only emergency routing is active.
if: ${{ vars.CI_RUNNER_POLICY != 'self-hosted-only' }}
# 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@99ac2f8c5b09dbb785d4eaf18465cbd96c30290c # 99ac2f8 2026-07-11
uses: melodic-software/ci-workflows/.github/workflows/link-check.yml@3dfb18452a8c6059a22e62456390d84feb10b42f # 3dfb184 2026-07-16
with:
runner: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
args: >-
--cache
--max-cache-age 7d
Expand Down
53 changes: 53 additions & 0 deletions .github/workflows/pr-issue-linkage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: pr-issue-linkage

# Validates the PR body carries a native closing keyword (Closes/Fixes/Resolves
# #N, including owner/repo#N, or the literal "No linked issue" when the PR closes
# nothing) and a non-empty `## Related` section, via the shared
# pr-issue-linkage reusable from ci-workflows. `pull_request_target` runs the
# base-branch definition, so a head-branch edit cannot bypass the gate — safe
# here because the reusable reads PR body metadata from the event payload only
# and runs no head code. `edited` re-validates on a body edit. `merge_group`
# 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:

permissions: {}

# pull_request_target runs the base-branch definition, so github.ref is the base
# branch: the PR number scopes cancellation and github.ref covers merge_group
# (which carries no pull_request object); the fallback is inert without a queue.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
select-runner:
name: Select runner
permissions: {}
uses: melodic-software/ci-workflows/.github/workflows/select-runner.yml@cdc5917c15aade1995bd810b60d818cadc635b52 # cdc5917 2026-07-16
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 }}

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@f7e94a80254fdca0aa85a600a07d784753b090e3 # f7e94a8 2026-07-16
Comment thread
kyle-sexton marked this conversation as resolved.
with:
runner: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
prerequisite-result: ${{ needs.select-runner.result }}
23 changes: 14 additions & 9 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,29 @@ jobs:
observer-private-key: ${{ secrets.CI_RUNNER_OBSERVER_PRIVATE_KEY }}

validate-pr-title:
# Run semantic validation only after a successful routing decision. A
# strict selector/configuration failure never dispatches this workload to
# paid hosted Linux.
if: ${{ !cancelled() && needs.select-runner.result == 'success' }}
# 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@99ac2f8c5b09dbb785d4eaf18465cbd96c30290c # 99ac2f8 2026-07-11
uses: melodic-software/ci-workflows/.github/workflows/semantic-pr.yml@51012e2c7b8bf74bc26e08c6446b488254a8770f # 51012e2 2026-07-12
with:
runner: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
prerequisite-result: ${{ needs.select-runner.result }}

pr-title:
name: pr-title / pr-title
needs: [select-runner, validate-pr-title]
if: ${{ always() && !cancelled() }}
# This fail-closed required gateway must report selector failure even when
# the local fleet cannot accept the validation workload.
runs-on: ubuntu-24.04
# 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).
if: ${{ !cancelled() }}
runs-on: ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }}
permissions: {}
steps:
- name: Enforce routing and semantic validation results
Expand Down
Loading