Operator, 2026-09-23: *"tighten the gate so :dev can't publish on red tests"*,
then *"I don't want failing builds to publish anywhere going forward."*
Run 7348 is the worked example. The backend unit lane went red on `2f8f0bc`
and `build-web` pushed `:dev` in the same minute, because the lanes and the
build were SEPARATE WORKFLOWS on the same push trigger. Neither could see the
other's verdict. `:dev` was a "it built" signal, never a "it passed" one, and
nothing about that was visible from either run.
Two workflows cannot express the gate. A `needs:` edge only exists inside one
graph. So `ci.yml`'s five lanes move into `build.yml` and `ci.yml` is deleted;
`sign-extension`, `build-web` and `build-agent` now need all five.
Nothing here is a new mechanism — it is the same edge that has gated `promote`
since milestone 362 step 4, and it keeps that step's hardest-won property:
**not running is not the same as passing.** `needs` treats a SKIPPED
dependency as unsatisfied, so a lane that silently skips itself blocks the
publish exactly as a failing one does. Run 5290 is why that is worth stating.
Scope, said plainly rather than implied:
- Gated: every image tag (`:dev`, `:latest`, `:c-<sha>`), the weekly base
refresh, and the `ext-<version>` signed-XPI release asset — `sign-extension`
publishes too, so it is gated with the rest.
- Not gated, deliberately: `extension.yml` publishes nothing, and
`release.yml` runs on a `v*` tag, generates notes rather than an artifact,
and its commit already went through main's gated build.
- `pull_request` (Renovate bumps into `dev`) comes across with the lanes. Its
runs are the lanes and nothing else, via an `if:` on each publishing job
rather than an inference from the `needs` chain.
The cost, accepted knowingly: this workflow queues per branch and never
cancels, so on two pushes in quick succession the second's lint feedback waits
out the first's build. A slower red beats a fast red that ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR