fix(ci): gate the Windows lane's checkout verification per trigger

The first thing the revived triggers hit was not a test failure but the
lane's own plumbing. `test_windows` verifies its checkout with
`verify-checkout-head`, and the input was written when this lane ran in
the merge queue alone: `expected_sha: github.event.merge_group.head_sha`,
with no event gate. On a pull request that expression is empty, the
action refuses an empty SHA, and the lane went red in 63 seconds without
running a test — the first Windows run in six weeks, failing on the
trigger rather than on the code.

Give it the event-aware shape the Ubuntu gate already uses, and skip it
where there is nothing to verify: the scheduled and dispatch runs check
out a branch by name, not a head commit.

Pinned generally rather than by name: for both lanes, any step whose
inputs read a `github.event.<event>` context must be gated to that
event, in the step's own `if` or in the expression itself. Restoring the
old spelling turns that test red.
This commit is contained in:
wenshao 2026-08-18 08:14:42 +08:00
parent 7c9c073502
commit 49ae2b2a4b
2 changed files with 37 additions and 2 deletions

View file

@ -1012,11 +1012,17 @@ jobs:
# Same stale-checkout guard as the Ubuntu gate: this job now runs on ECS,
# so fail loud if the checkout lacks the merge-queue head rather than
# silently testing the wrong tree into a merge.
# Written when this lane ran in the merge queue alone, so its expected
# SHA named only the queue's event: on any other trigger the input is
# empty and the step fails the whole lane before a single test runs.
# That is what the revived triggers hit first. Same event-aware shape as
# the Ubuntu gate now, and skipped where there is no head to verify —
# the scheduled and dispatch runs check out a branch by name.
- name: 'Verify checkout includes expected head commit'
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && (github.event_name == 'pull_request' || github.event_name == 'merge_group') }}"
uses: './.github/actions/verify-checkout-head'
with:
expected_sha: '${{ github.event.merge_group.head_sha }}'
expected_sha: "${{ github.event_name == 'merge_group' && github.event.merge_group.head_sha || github.event.pull_request.head.sha }}"
# Avoid setup-node downloads on ECS, where nodejs.org may be unreachable
# through the egress proxy; reuse the machine's Node instead.

View file

@ -64,6 +64,35 @@ describe('platform lanes — triggers', () => {
});
}
for (const lane of LANES) {
it(`${lane}'s steps are gated for every trigger it now has`, () => {
// The first thing the revived triggers hit was not a test failure but
// the lane's own plumbing: a `verify-checkout-head` step written when
// this lane ran in the merge queue alone, with `expected_sha` naming
// only `github.event.merge_group.head_sha`. On a pull request that
// input is empty and the step fails the lane before a single test
// runs. A step whose inputs name one event must be gated to that
// event — for every step in a job that now runs on four.
for (const step of ci.jobs[lane].steps ?? []) {
const inputs = JSON.stringify(step.with ?? {});
const gate = String(step.if ?? '');
for (const [context, event] of [
['github.event.merge_group', "'merge_group'"],
['github.event.pull_request', "'pull_request'"],
]) {
if (!inputs.includes(context)) continue;
const guarded =
inputs.includes(`github.event_name == ${event}`) ||
gate.includes(`github.event_name == ${event}`);
expect(
guarded,
`${lane} step "${step.name}" reads ${context} on every trigger`,
).toBe(true);
}
}
});
}
it('keeps a nightly run to exactly the two lanes', () => {
// A `schedule:` trigger fires the whole workflow. Every other job must
// therefore either exclude `schedule` outright or gate on an event