Skip to content

ci: route control-plane lanes through the governed selector#237

Merged
kyle-sexton merged 1 commit into
mainfrom
ci/wave3-fleet-routing
Jul 17, 2026
Merged

ci: route control-plane lanes through the governed selector#237
kyle-sexton merged 1 commit into
mainfrom
ci/wave3-fleet-routing

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Wave 3 per-repo floor conversion (melodic-software/github-iac#78, 2026-07-16 owner override): converts every remaining hosted control-plane lane to governed selector routing — claude-code-plugins ends with ZERO runner-policy exceptions.

No linked issue.

What

Verification

  • Local runner-policy.mjs run (CI env parity): clean; the two new callers' contracts landed via the standards sync (chore: sync standards components #236) already merged to main.
  • actionlint clean; every converted runs-on keeps the || 'ubuntu-24.04' fallback (epic decision 4).

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01K3QehVwmWzkBLpKokNCkkt

Route ccp's remaining GitHub-hosted control-plane lanes through the
governed runner selector to the self-hosted fleet, ending hosted-minute
spend on this private repo:

- ci.yml ci-status and pr-title.yml pr-title: the fail-closed required
  gateways route via the selector instead of a fixed ubuntu-24.04 runner
  (fail-closed on selector/fleet outage is the accepted tradeoff).
- pr-title validate-pr-title: bump semantic-pr to its runner-input
  variant, passing runner + prerequisite-result.
- link-check: migrate to the runner-input link-check reusable behind a
  select-runner job; retire the strict-policy pause.
- Add do-not-merge and pr-issue-linkage callers (ci-workflows#120/#121),
  both routed through the selector.
- Drop all .github/runner-policy.json hosted exceptions (now zero).

Refs melodic-software/github-iac#78.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01K3QehVwmWzkBLpKokNCkkt

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4621267c29

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/do-not-merge.yml
Comment thread .github/workflows/pr-issue-linkage.yml
@claude

claude Bot commented Jul 17, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 57s —— View job


PR Review — ci: route control-plane lanes through the governed selector

  • Gather context and understand request
  • Read changed files (runner-policy.json, ci.yml, do-not-merge.yml, link-check.yml, pr-issue-linkage.yml, pr-title.yml)
  • Validate runner-policy contract coverage for new workflows
  • Review governed selector routing correctness
  • Check fail-closed semantics and fallback logic
  • Assess security posture and pin hygiene
  • Post final review

Summary

The fleet-routing migration itself is structurally sound — fail-closed semantics are applied consistently and the design decisions are well-documented. However, there is one blocking gap that will cause the runner-policy CI lane to fail: the two new reusable workflow SHAs called from do-not-merge.yml and pr-issue-linkage.yml are absent from policy.json.


🔴 Blocking — Missing runner-policy contracts

Codex's P1 findings are confirmed.

do-not-merge.yml:51 calls:

melodic-software/ci-workflows/.github/workflows/do-not-merge-gate.yml@885302176345486ca6c2c392d83131f9b5389251

pr-issue-linkage.yml:50 calls:

melodic-software/ci-workflows/.github/workflows/pr-issue-linkage.yml@f7e94a80254fdca0aa85a600a07d784753b090e3

Neither SHA appears in .github/standards/runner-policy/policy.json's approvedReusableWorkflowContracts. The runner-policy.mjs validator (reusableWorkflowStatus, line ~857 of the script) will return approved: false with "the reusable workflow path@SHA has no reviewed runner-input contract" for both, failing the runner-policy CI lane.

The PR description states these contracts "landed via the standards sync (#236) already merged to main," but #236 does not appear in the branch's git ancestry — the most recent standards sync visible is b26444f (#232). This suggests #236 has not been merged yet, or the contracts weren't included in it.

Both callers follow the selectorResultInput pattern (passing prerequisite-result), which means auto-approval is explicitly declined by the validator even if matching basis SHAs existed (the code at line ~1620 of runner-policy.mjs declines any selectorResultInput contract from auto-approval because the fail-closed behavior cannot be proven via surface diff). These entries must be added explicitly to policy.json.

Required entries follow the same shape as the existing semantic-pr.yml@51012e2 contract:

"melodic-software/ci-workflows/.github/workflows/do-not-merge-gate.yml@885302176345486ca6c2c392d83131f9b5389251": {
  "routing": "runner-input",
  "runnerInput": "runner",
  "selectorResultInput": "prerequisite-result",
  "allowedInputs": ["runner", "prerequisite-result"],
  "allowedSecrets": {},
  "allowedCallerPermissions": {
    "pull-requests": "read"
  }
},
"melodic-software/ci-workflows/.github/workflows/pr-issue-linkage.yml@f7e94a80254fdca0aa85a600a07d784753b090e3": {
  "routing": "runner-input",
  "runnerInput": "runner",
  "selectorResultInput": "prerequisite-result",
  "allowedInputs": ["runner", "prerequisite-result"],
  "allowedSecrets": {}
}

(exact allowedCallerPermissions depend on what do-not-merge-gate.yml declares — pull-requests: read is required by the do-not-merge job at line 49, and the validator enforces that caller grants match the contract's allowedCallerPermissions)

Resolution: Either merge standards sync #236 first (if it truly contains these contracts), or add them to policy.json in this PR.


✅ What looks correct

ci-status fail-closed routing (ci.yml:356–388)

Adding select-runner to ci-status's needs and switching from runs-on: ubuntu-24.04 to ${{ needs.select-runner.outputs.runner || 'ubuntu-24.04' }} with if: ${{ !cancelled() }} is the correct pattern. The !cancelled() guard (not always()) means a cancelled workflow won't run the aggregate, but it still runs on selector failure (falling through to the hosted fallback) and the case-based loop correctly rejects any non-success result — including skipped. The old always() was removed correctly; always() would include cancelled state, but !cancelled() is sufficient here since a cancelled required check is treated as pending, not a skip-as-success.

pr-title.yml pattern upgrade (pr-title.yml:36–71)

The shift from if: ${{ !cancelled() && needs.select-runner.result == 'success' }} to if: ${{ always() }} on validate-pr-title (coupled with the prerequisite-result input to semantic-pr@51012e2) correctly delegates the fail-closed decision into the reusable. The pr-title wrapper's change from if: ${{ always() && !cancelled() }} to if: ${{ !cancelled() }} is also correct — always() && !cancelled() was redundant (always() implies !cancelled() is possible but doesn't need the AND).

link-check.yml retirement of the pause guard (link-check.yml:24–46)

The CI_RUNNER_POLICY != 'self-hosted-only' pause guard was a workaround for issue-writing on self-hosted runners. The new link-check.yml@3dfb184 contract in policy.json accepts a runner input with an allowedCallerPermissions entry including issues: write, which is the correct upgrade path. The condition if: ${{ !cancelled() && needs.select-runner.result == 'success' }} (not always()) is appropriate here since link-check is advisory and doesn't need to materialize a required check on selector failure.

pull_request_target usage in new callers

The security justification in the comments is sound: do-not-merge reads PR label metadata via API; pr-issue-linkage reads PR body from the event payload. Neither executes head-branch code. Using pull_request_target is the correct trigger choice for base-branch–evaluated gates.

runner-policy.json — clean exceptions removal

Dropping all 3 exceptions (runner-policy.json) correctly reflects that ci-status, link-check, and pr-title no longer need hosted-only carveouts.


🟡 Minor — select-runner SHA version skew

New workflows (do-not-merge.yml, pr-issue-linkage.yml, link-check.yml) pin select-runner at cdc5917 (2026-07-16), while ci.yml and pr-title.yml pin at 3415de3 (2026-07-13). Both are in approvedSelectorReferencesByRepositoryOwner, so this won't fail policy. But aligning all callers to the same SHA would make future Dependabot bumps simpler to review.


Bottom line

Merge is blocked on the missing policy.json contracts. Everything else — the routing semantics, fail-closed patterns, security model, and permission scope — is well-executed. Once the contract gap is resolved (either by landing #236 first or adding entries here), this is clean to merge.

@kyle-sexton
kyle-sexton merged commit 3cebd45 into main Jul 17, 2026
15 checks passed
@kyle-sexton
kyle-sexton deleted the ci/wave3-fleet-routing branch July 17, 2026 12:16
kyle-sexton added a commit that referenced this pull request Jul 22, 2026
…rine

PLUGIN-PHILOSOPHY's design boundary is the plugin specialization of the
org-wide convention now owned by melodic-software/standards
conventions/engineering/shareable-artifact-design.md (standards #237,
PR #238). Design boundary states the specialization relationship;
authoritative references gain the explicit entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 22, 2026
…rine (#941)

## Summary

PLUGIN-PHILOSOPHY's design boundary is the plugin specialization of the
org-wide convention now owned by `melodic-software/standards`
`conventions/engineering/shareable-artifact-design.md` (standards #237,
merged in standards PR #238). The design boundary states the
specialization relationship; the authoritative references gain the
explicit entry. Completes the follow-up promised in #933's companion
work.

No linked issue: the tracked items (#933, standards#237) are already
closed by their own PRs; this is their promised follow-up citation.

## Related

- melodic-software/standards#237 — the convention doc this cites (closed
by standards PR #238; not closed by this PR).
- #933 — the advisor-escalation work this citation follow-up was
sequenced after (already closed by #934).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant