#!/usr/bin/env python3 """cmux-next's macOS cascade: idle minis, Blacksmith 12 vCPU, 6 vCPU, then the minis' queue. side_lane_placement.py gives an idle owned side runner to each job it can and leaves the rest queued on the owned label. Until this cascade, a busy fleet meant every remaining job waited there for the rescue's ten-minute budget, and the bot's attempt 3 then sent the compile jobs to vars.MACOS_RUNNER_26 (Blacksmith 12 vCPU) and the session jobs to Blacksmith 6 vCPU, however full either was. From 14:00Z to 21:15Z on 2026-10-19, 533 cmux-next and ci.yml jobs asked for blacksmith-12vcpu-macos-26; the ones that got a runner waited a median 40 minutes (p90 68). The label never ran more than six at once (five in 11 of the 20 five-minute samples it had a queue), the "about 5" Blacksmith gives us. Meanwhile the minis' side label queued at p90 29 minutes. The cascade places the jobs that idle minis did not take, in the placement's priority order, while each Blacksmith label has a free slot now: blacksmith-12vcpu-macos-26 (CAPACITY 5) first, then blacksmith-6vcpu-macos-26 (CAPACITY 10), at most MAX_PER_RUN on each label. When the owned runners were read and none was idle for them (`saturated`), the rest take 6 vCPU's queue while it moves and is short (overflow(): not stalled, fewer than OVERFLOW_BACKLOG queued ahead, MAX_PER_RUN a run), and with the counts unread up to MAX_PER_RUN of them take 6 vCPU, as a feed's compile does. Only what is left then stays queued on the owned label, where the rescue still watches it: the placement is the only lever, since GitHub never moves a queued job and a refusals-only run is never re-run for its queue (cmux#26723's fast tier waited on the side label for hours on 2026-10-29). Only CASCADE_JOBS take part, and only those the workflow says run on this route (CASCADE_RUNS: not fast-tier while an idle vars.CI_FAST_LANE_RUNNER runner takes it, not a job path routing or labels skip), so no slot is spent on a job that never asks for it. The jobs that need a logged-in session (swift-test, daemon-test, generated-files) keep the gui route. release-compile cascades too (2026-10-21 ~20:10Z: 76 Release compiles queued on the minis); off a mini it skips the CEF shim compile, which is advisory and already skipped by a Blacksmith re-run. On attempt 1 of a feat-cmux-next push it compiles on the reserved promotion mini (PROMOTE_LABEL, vars.CI_NEXT_PROMOTE_POOL): while a runner there is idle it stays there and takes no slot, and with none idle it takes a Blacksmith slot ahead of the other jobs, since it gates the nightly-next request. It never counts against an idle side mini then: it does not run there. The labels are not independent: every Blacksmith macOS label the organization runs (these two and blacksmith-6vcpu-macos-15) draws on one pool of POOL_VCPU vCPUs, so a job takes a label only while that pool has room for it (pool_room()). From 13:34Z to 21:30Z on 2026-10-26, 53 of 55 placements sampled saw 12 vCPU with no runner in flight, while 6 vCPU ran up to ten at once (60 vCPU; the janitor's snapshots never saw this repository's Blacksmith macOS jobs run more than 60 vCPU at once). The old per-label count saw "0 in flight + 0 queued of 5" on 12 vCPU and sent jobs there that waited behind the pool: two DEV Release compiles, runs 40386035282 and 40386506758, queued at 19:25Z and started at 20:42Z and 20:52Z. A 12 vCPU job needs twelve vCPUs freed at once, so while any Blacksmith macOS job queues the 6 vCPU jobs take each freed slot first: LARGE takes a job only while nothing queues on any Blacksmith macOS label (pool_queued()), and otherwise SMALL does. A slot counted free can still be taken by another run placing in the same snapshot window, and nothing rescues a job queued on Blacksmith. MAX_PER_RUN bounds how far a burst of runs can overfill a label. A free slot is CAPACITY less the jobs in flight and the jobs already queued: - In flight comes live from the organization's runner groups. Blacksmith registers each label's just-in-time runners in its own scale-set group ("Blacksmith scale sets -