ci: extract traffic-controller into a testable Python module (#342) #345

Merged
JMR-dev merged 4 commits from ci-342-traffic-control-python into main 2026-07-05 03:33:38 +00:00
JMR-dev commented 2026-07-05 03:08:44 +00:00 (Migrated from github.com)

Closes #342

What

Extracts CI's priority-based runner orchestration ("traffic-control") out of the large inline-bash step in ci.yml into a checked-in, unit-tested Python module: .github/scripts/traffic_control.py (+ test_traffic_control.py).

Design: pure decision CORE + thin gh-I/O SHELL

The testability win is a network-free decision core:

  • effective_priority(pr) — lowest-numbered P0–P9 label; default P5; broken OR draft ⇒ 10 (bottom, below P9).
  • runs_to_cancel(this_pr, all_prs, self_run_id=…) — PASS 1: run ids to cancel. A strictly-lower OTHER PR's active/queued run is cancelled iff (a) THIS PR is P0 (an emergency reclaims all strictly-lower runners), or (b) the target is broken/draft (effective priority 10) and THIS PR is strictly-higher (a wasted run any ready PR may reclaim). P1–P9 never bump a normal lower run; a broken/draft PR (P10) preempts nothing. Never self (by number or run-id), never equal-or-higher.
  • wait_blockers(this_pr, all_prs) — PASS 2: yield to any strictly-higher-priority PR with an active/queued run, and to same-level peers ordered ahead (running-first, then oldest createdAt). Empty list ⇒ proceed.

The shell (run_live) gathers the snapshot via gh, applies the cancellations, and runs the bounded hold-back poll loop; it always exits 0. --dry-run feeds the core a snapshot JSON and prints the decisions with zero network. No jq/bash dependency (cross-platform, per the repo's Python-stdlib convention).

ci.yml wiring

The traffic-control job now checks out the repo and runs python3 .github/scripts/traffic_control.py. Permissions gain contents: read (for the checkout) alongside the existing actions: write / pull-requests: read; env and the downstream needs: traffic-control wiring are unchanged; the step stays continue-on-error.

Safety invariants (preserved)

Never cancel a run on main/push (the gh run list query filters --event pull_request and drops headBranch == main), never cancel THIS PR's own run, never cancel an equal-or-higher-priority PR (so a P10 never preempts another P10).

Behaviour vs the old bash

Behaviour-preserving, plus the ticket's two refinements: (1) drafts now count as P10 (bottom); (2) an explicit same-level running-first-then-oldest tiebreaker. The old bash's broken-reclaim — any strictly-higher PR (not just P0) may cancel a broken target's run to reclaim its runner — is preserved and extended to drafts: any strictly-higher PR may reclaim a broken or draft run, while P1–P9 still never bump a normal lower run (only P0 does that).

Tests

37 stdlib unittest cases: priority resolution (P-label / broken / draft / default P5); PASS 1 (P0 reclaims all strictly-lower incl. P10; a P3 reclaims a broken P10 and a draft P10; a P3 does not bump a normal P5; a P10 self preempts nothing; self / equal-or-higher / own-run-id never cancelled); PASS 2 same-level running-first/oldest ordering; end-to-end scenarios incl. a P5 that reclaims a draft and waits behind a higher PR.

Validation (no emulator/Gradle needed)

  • python -m unittest discover -s .github/scripts → 37 pass.
  • python -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))" → parses clean.
  • python -m py_compile .github/scripts/traffic_control.py → OK.
  • --dry-run on sample PR-sets, incl. a P3 self that cancels a draft (4401) + broken (4402) run, spares a normal P5, and yields to a higher P1.

🤖 Generated with Claude Code

Closes #342 ## What Extracts CI's priority-based runner orchestration ("traffic-control") out of the large inline-bash step in `ci.yml` into a checked-in, unit-tested Python module: **`.github/scripts/traffic_control.py`** (+ `test_traffic_control.py`). ## Design: pure decision CORE + thin gh-I/O SHELL The testability win is a network-free decision core: - `effective_priority(pr)` — lowest-numbered `P0`–`P9` label; default `P5`; **`broken` OR `draft` ⇒ 10** (bottom, below P9). - `runs_to_cancel(this_pr, all_prs, self_run_id=…)` — **PASS 1**: run ids to cancel. A strictly-lower OTHER PR's active/queued run is cancelled iff **(a)** THIS PR is P0 (an emergency reclaims *all* strictly-lower runners), **or (b)** the target is broken/draft (effective priority 10) and THIS PR is strictly-higher (a wasted run any ready PR may reclaim). P1–P9 never bump a *normal* lower run; a broken/draft PR (P10) preempts nothing. Never self (by number or run-id), never equal-or-higher. - `wait_blockers(this_pr, all_prs)` — **PASS 2**: yield to any strictly-higher-priority PR with an active/queued run, and to same-level peers ordered ahead (**running-first, then oldest `createdAt`**). Empty list ⇒ proceed. The **shell** (`run_live`) gathers the snapshot via `gh`, applies the cancellations, and runs the bounded hold-back poll loop; it always exits 0. `--dry-run` feeds the core a snapshot JSON and prints the decisions with zero network. No `jq`/bash dependency (cross-platform, per the repo's Python-stdlib convention). ## ci.yml wiring The `traffic-control` job now checks out the repo and runs `python3 .github/scripts/traffic_control.py`. Permissions gain `contents: read` (for the checkout) alongside the existing `actions: write` / `pull-requests: read`; `env` and the downstream `needs: traffic-control` wiring are unchanged; the step stays `continue-on-error`. ## Safety invariants (preserved) Never cancel a run on `main`/push (the `gh run list` query filters `--event pull_request` and drops `headBranch == main`), never cancel THIS PR's own run, never cancel an equal-or-higher-priority PR (so a P10 never preempts another P10). ## Behaviour vs the old bash Behaviour-preserving, plus the ticket's two refinements: **(1)** drafts now count as **P10** (bottom); **(2)** an explicit same-level **running-first-then-oldest** tiebreaker. The old bash's broken-reclaim — any strictly-higher PR (not just P0) may cancel a `broken` target's run to reclaim its runner — is **preserved and extended to drafts**: any strictly-higher PR may reclaim a `broken` **or** `draft` run, while P1–P9 still never bump a *normal* lower run (only P0 does that). ## Tests 37 stdlib `unittest` cases: priority resolution (P-label / broken / draft / default P5); PASS 1 (P0 reclaims all strictly-lower incl. P10; a P3 reclaims a broken P10 *and* a draft P10; a P3 does **not** bump a normal P5; a P10 self preempts nothing; self / equal-or-higher / own-run-id never cancelled); PASS 2 same-level running-first/oldest ordering; end-to-end scenarios incl. a P5 that reclaims a draft *and* waits behind a higher PR. ## Validation (no emulator/Gradle needed) - `python -m unittest discover -s .github/scripts` → **37 pass**. - `python -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))"` → **parses clean**. - `python -m py_compile .github/scripts/traffic_control.py` → OK. - `--dry-run` on sample PR-sets, incl. a **P3 self** that cancels a draft (`4401`) + broken (`4402`) run, spares a normal P5, and yields to a higher P1. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.