ci: validate structured runner pool labels
This is a narrow fix-forward for the base-red policy regression exposed by #30081. The nightly hosted-Mac guard reads RUNNER_POOLS from runs-on expressions, but the runner-label policy treated that JSON object as an unvalidated non-label variable.
RUNNER_POOLS is now parsed as pool metadata: _dead timestamps are ignored, while every pool member is checked for hosted macOS labels and self-hosted fleet labels. Empty configuration remains allowed, trusted Blacksmith Linux/macOS pool members remain allowed, malformed non-empty JSON fails closed, and no blanket exemption was added.
Leo’s routing rule is that paid runners are bounded overflow and hosted Macs are not part of the route. This keeps the policy test aligned with that rule without changing workflow permissions or runner trust.
Validation:
python3 tests/test_ci_nightly_no_hosted_mac.py— 5 passed- focused runner policy classes — 20 passed
git diff --check— passed- Full
tests/test_runner_label_policy.pystill reports the pre-existingCI_CONTROL_RUNNERreport-list drift on this base; the new structured-pool tests pass.
Agent session: agent session
🤖 Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes the base-red policy regression from #30081 by validating RUNNER_POOLS as structured JSON pool metadata in the nightly runner-label policy. Previously the JSON object was treated as an unvalidated non-label variable, letting hosted Macs slip through pool definitions.
_dead metadata timestamps are ignored; every other pool label is checked for hosted macOS and self-hosted fleet labels. Empty configuration and trusted Blacksmith Linux/macOS pools remain allowed. Malformed JSON, non-object values, and non-string labels all fail closed. No blanket exemption was added; workflow permissions and runner trust are unchanged.