99 # workflow (cache-poisoning).
1010 branches : [main]
1111 pull_request :
12+ # The merge queue tests each queued PR merged onto the tip of main (plus
13+ # the PRs ahead of it) before it lands. `ci-ok` below is the required
14+ # check, so this event has to run the same pull_request-tier job set.
15+ merge_group :
16+ types : [checks_requested]
1217 schedule :
1318 # Nightly on main: the `full` tier (e2e-full, cargo-vex-matrix-full,
1419 # yarn-berry-full) and e2e-docker, which pull_request runs skip.
@@ -27,13 +32,14 @@ permissions:
2732 contents : read
2833
2934# A newer push to the same PR supersedes its older run; nothing else is
30- # cancelled. Push, dispatch and schedule runs always finish: main runs are
31- # the ONLY rust-cache writers (save-if) and must not die mid-save, and a
32- # dispatched base-branch run is the base's only CI verdict. The nightly
33- # gets its own group: a group holds one pending run, so sharing main's would
34- # let a queued push and the nightly cancel each other.
35+ # cancelled. Push runs are grouped per commit: a concurrency group holds only
36+ # ONE pending run, so a shared `refs/heads/main` group silently cancelled every
37+ # queued push but the newest during a merge burst, and most main commits never
38+ # got a verdict. Merge-group refs (gh-readonly-queue/...) are unique per queue
39+ # entry already. The nightly keeps its own group so it never queues behind (or
40+ # cancels) a push.
3541concurrency :
36- group : ci-${{ github.event.pull_request.number || github.ref }}${{ github.event_name == 'schedule' && '-nightly' || '' }}
42+ group : ci-${{ github.event.pull_request.number || (github.event_name == 'push' && github.sha) || github.ref }}${{ github.event_name == 'schedule' && '-nightly' || '' }}
3743 cancel-in-progress : ${{ github.event_name == 'pull_request' }}
3844
3945jobs :
@@ -1706,7 +1712,8 @@ jobs:
17061712 # v5 landings, the nightly schedule and dispatch run them with the `e2e`
17071713 # steps.
17081714 e2e-full :
1709- if : github.event_name != 'pull_request' || github.head_ref == 'release/v5-prerelease'
1715+ # merge_group runs the pull_request tier: the queue gates on ci-ok.
1716+ if : (github.event_name != 'pull_request' && github.event_name != 'merge_group') || github.head_ref == 'release/v5-prerelease'
17101717 needs : [test, coverage, e2e-build]
17111718 strategy :
17121719 fail-fast : false
@@ -1927,7 +1934,8 @@ jobs:
19271934 # Skipped on pull_request (except v5 landings), like e2e-full.
19281935 yarn-berry-full :
19291936 name : yarn-berry ${{ matrix.yarn }} (${{ matrix.os }})
1930- if : github.event_name != 'pull_request' || github.head_ref == 'release/v5-prerelease'
1937+ # merge_group runs the pull_request tier: the queue gates on ci-ok.
1938+ if : (github.event_name != 'pull_request' && github.event_name != 'merge_group') || github.head_ref == 'release/v5-prerelease'
19311939 needs : [test, coverage]
19321940 strategy :
19331941 fail-fast : false
@@ -2019,7 +2027,8 @@ jobs:
20192027
20202028 cargo-vex-matrix-full :
20212029 name : cargo ${{ matrix.toolchain }} lock-v${{ matrix.lock || 'own' }} (${{ matrix.os }})
2022- if : github.event_name != 'pull_request' || github.head_ref == 'release/v5-prerelease'
2030+ # merge_group runs the pull_request tier: the queue gates on ci-ok.
2031+ if : (github.event_name != 'pull_request' && github.event_name != 'merge_group') || github.head_ref == 'release/v5-prerelease'
20232032 needs : [test, coverage, e2e-build]
20242033 runs-on : ${{ matrix.os }}
20252034 timeout-minutes : 40
@@ -2081,7 +2090,8 @@ jobs:
20812090 cargo test -p socket-patch-cli --test e2e_vendor_cargo_build -- old_toolchain --nocapture
20822091
20832092 # ----------------------------------------------------------------------
2084- # Hosted-mode production e2e — REQUIRED status check, with a kill switch.
2093+ # Hosted-mode production e2e — merge-blocking through `ci-ok`, with a kill
2094+ # switch.
20852095 #
20862096 # Drives `scan --mode hosted` against the REAL production endpoints
20872097 # (patches-api.socket.dev + patch.socket.dev) and the REAL upstream
@@ -2093,8 +2103,8 @@ jobs:
20932103 # The suite itself is `#[ignore]`-gated, so it stays OUT of the `test` and
20942104 # `e2e` jobs and only runs where it is explicitly asked for — here.
20952105 #
2096- # INVARIANTS (this job is registered in branch protection as a required
2097- # check named exactly `hosted-e2e`):
2106+ # INVARIANTS (`ci-ok` needs this job, and older rulesets may still name the
2107+ # check `hosted-e2e` directly ):
20982108 # * NO job-level `if:` — a *skipped* required check is ambiguous to branch
20992109 # protection and can wedge a PR at "Expected — waiting for status".
21002110 # The kill switch gates the STEPS, never the job.
@@ -2113,15 +2123,17 @@ jobs:
21132123 # hosted_e2e = force (ignore the variable) | skip (bypass this run).
21142124 # ----------------------------------------------------------------------
21152125 hosted-e2e :
2116- name : hosted-e2e # registered in branch protection ; do not rename
2126+ name : hosted-e2e # may be a required check ; do not rename
21172127 runs-on : ubuntu-latest
21182128 permissions :
21192129 contents : read
21202130 timeout-minutes : 30
21212131 concurrency :
21222132 # These are real requests against a real production service — keep it to
2123- # one run per ref rather than one per push.
2124- group : hosted-e2e-${{ github.ref }}
2133+ # one run per PR ref rather than one per push. Main pushes group per
2134+ # commit like the workflow: a group holds ONE pending job, so a shared
2135+ # main group cancelled queued pushes and ci-ok failed them.
2136+ group : hosted-e2e-${{ (github.event_name == 'push' && github.sha) || github.ref }}
21252137 cancel-in-progress : ${{ github.event_name == 'pull_request' }}
21262138 env :
21272139 HOSTED_E2E_DISABLED : ${{ vars.HOSTED_E2E_DISABLED }}
@@ -2309,3 +2321,25 @@ jobs:
23092321 done
23102322 echo "::error title=hosted-e2e::the vendored vlt production proof failed on all 3 attempts"
23112323 exit 1
2324+
2325+ # The single required status check for the merge queue (and PRs). It needs
2326+ # every job above, so adding a job here is how it becomes merge-blocking.
2327+ # Skipped jobs (the nightly `full` tier) pass; failed or cancelled ones fail.
2328+ ci-ok :
2329+ name : ci-ok # registered as a required check; do not rename
2330+ if : always()
2331+ needs : [clippy, node-addon, lint-ecosystems, release-readiness, test, test-release, coverage, docker-base, coverage-docker, coverage-merge, dispatch-tests, e2e-build, e2e, e2e-full, e2e-docker, yarn-classic-matrix, yarn-berry-e2e, yarn-berry-full, cargo-vex-matrix, cargo-vex-matrix-full, cargo-old-toolchains, hosted-e2e]
2332+ runs-on : ubuntu-latest
2333+ timeout-minutes : 5
2334+ steps :
2335+ - name : Check every needed job
2336+ env :
2337+ RESULTS : ${{ toJSON(needs) }}
2338+ run : |
2339+ echo "$RESULTS" | python3 -c '
2340+ import json, sys
2341+ bad = {k: v["result"] for k, v in json.load(sys.stdin).items()
2342+ if v["result"] not in ("success", "skipped")}
2343+ for k, v in sorted(bad.items()):
2344+ print(f"::error::{k}: {v}")
2345+ sys.exit(1 if bad else 0)'
0 commit comments