From 2a3c7ec9dd2ed6190ad7c2e418d59882c97d466f Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 16:29:07 -0400 Subject: [PATCH] ci: adopt the public-repository runner posture The repository went public, and the runner policy component forbids the local-runner selector on public repositories (fork-PR exposure: workflow code from forks must never route to self-hosted workers). Declare visibility public with selfHostedCi false in .github/runner-policy.json, remove every select-runner call, and pin all lanes to GitHub-hosted ubuntu-24.04 (free on public repositories). Co-authored-by: Claude Fable 5 (1M context) --- .github/runner-policy.json | 4 +- .github/workflows/ci.yml | 63 ++++++-------------------- .github/workflows/claude-review.yml | 27 ++--------- .github/workflows/do-not-merge.yml | 25 ++-------- .github/workflows/link-check.yml | 23 ++-------- .github/workflows/pr-issue-linkage.yml | 25 ++-------- .github/workflows/pr-title.yml | 44 +++++------------- 7 files changed, 40 insertions(+), 171 deletions(-) diff --git a/.github/runner-policy.json b/.github/runner-policy.json index 106f7e22c..df1029fc4 100644 --- a/.github/runner-policy.json +++ b/.github/runner-policy.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "repositoryOwner": "melodic-software", - "visibility": "private", - "selfHostedCi": true, + "visibility": "public", + "selfHostedCi": false, "exceptions": {} } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60da700e0..dd62d78a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -377,7 +344,6 @@ jobs: ci-status: needs: - - select-runner - hygiene - hook-utils-sync - standards-contract-sync @@ -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 diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml index 08e8cd2d9..8dc32fe0c 100644 --- a/.github/workflows/claude-review.yml +++ b/.github/workflows/claude-review.yml @@ -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]" diff --git a/.github/workflows/do-not-merge.yml b/.github/workflows/do-not-merge.yml index d95bcab83..18d8119dd 100644 --- a/.github/workflows/do-not-merge.yml +++ b/.github/workflows/do-not-merge.yml @@ -25,30 +25,11 @@ concurrency: 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 diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml index 393fafb4f..e1b812cc7 100644 --- a/.github/workflows/link-check.yml +++ b/.github/workflows/link-check.yml @@ -13,26 +13,9 @@ 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: @@ -40,7 +23,7 @@ jobs: 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 diff --git a/.github/workflows/pr-issue-linkage.yml b/.github/workflows/pr-issue-linkage.yml index a07dbe818..69b54e0b4 100644 --- a/.github/workflows/pr-issue-linkage.yml +++ b/.github/workflows/pr-issue-linkage.yml @@ -25,29 +25,10 @@ concurrency: 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 diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 0a6bcc1db..d37a0b513 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -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."