Skip to content

Commit 788715e

Browse files
ci(dogfood): the three Dogfood Regression Gate shards run on merge_group (#30639)
<!-- claude-projects-attribution: {"github_login":"marchtian"} --> _Requested by **田騏** · [project thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiNjyvCdXYygFqntMNR7S2dW)_ Part of #30461 (the card stays open: the follow-up ruling 6669982756 owes a 24-hour reading of the queue builds after this lands, then the required-set step) Ruling-ref: 6663883327 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:合并队列(`merge_group`)现在也跑三个 `Dogfood Regression Gate` 分片;PR 上照旧不跑,`Dogfood Verify CLI`、Temporal Conformance、UI Published Artifacts 仍只在 `main` 的全量里跑。顺带把说明文字和两个读 `if:` 的仓内工具改到与新行为一致。 - **为什么改**:按裁决 B 执行。10-22 以来有 9 张 dogfood 引起的 `main` 红,都是合进去之后才发现;现在 dogfood 红会出现在该条目的队列构建上,比等 `main` 推送后才发现更早(目前只是提示,不拦合并)。 - **风险与回滚**:每个队列条目多 3 个 runner 作业(push 上实测每片约 11 分钟,与 Test Core 并行,Test Core 在队列里中位约 21 分钟才收尾)。注意:`Dogfood Regression Gate` 自 #22998 起不在必需检查里,所以队列里 dogfood 红**不会**自动把条目踢出,只是让红提前可见;是否加回必需集合已由后续裁定 6669982756(A,分两步)定下:本 PR 先落地,不动规则集;落地后 24 小时读队列构建的 dogfood 红与抖动,读数干净再由你把它加进规则集(车道先合一行 `REQUIRED_CONTEXTS`)。回滚只需 revert 本 PR。 - **先看**:① `.github/workflows/ci.yml` 的 `dogfood` 作业 `if:`(只删了 `merge_group` 一项);② 同文件 THE TWO SPEEDS 注释块;③ `scripts/pm/dispatch-gates.mjs` 的 `jobIfReading`。 - **两分钟验证**:`node scripts/pm/check-expected-skips.mjs --roster | grep Dogfood`,应看到分片一行只排除 `pull_request`,`Dogfood Verify CLI` 一行仍排除 `pull_request|merge_group`。 - **需要你做的**:审批本 PR(`.github/workflows/**` 与 `AGENTS.md` 属 Tier H),席位随后落地。 ## What changes - `.github/workflows/ci.yml`, the `dogfood` job: its `if:` drops `&& github.event_name != 'merge_group'` and nothing else. `!cancelled()`, the `core` filter term and the `pull_request` exclusion stay. `dogfood-verify`, `temporal-conformance` and `ui-published-artifacts` keep both exclusions (the ruling's second line). No affected-only filter: on `merge_group` the `core` output is `'true'` by its existing guard, so the shards run on every queue entry (letter C is not ruled). - What the job `needs:` on the queue: `filter` only. It runs on every event, and its `core` output is `'true'` on `merge_group`, so the shards cannot skip silently there. No other needed job is denied on `merge_group`, so nothing else is widened. - Prose brought in line with the new split: THE TWO SPEEDS block, the `core` output's reader list, the `dogfood` job and `dogfood-gate` comments (ci.yml); `release.yml`'s `full-battery` comment; `upgrade-harness-optin.yml`'s gap paragraph; `scripts/ci/scheduled-full-run.mjs` HALF 3; the `check-required-contexts.mjs` tombstone; `AGENTS.md`'s Two speeds line and its detail in `docs/agents/multi-agent-discipline.md`. - Two repo gates read job `if:` spellings, and the new one (`pull_request` excluded alone) changed what they said: - `scripts/pm/check-expected-skips.mjs`: the shards stay in the derived roster (a PR head still skips them), but their reason no longer says "full battery only". A `pr-event` term that excludes only `pull_request` now reads "skips on every PR head and is answered by the merge-queue build and main's push and scheduled runs". The fixture tree models the live shape, with three new self-test cases. - `scripts/pm/dispatch-gates.mjs`: `jobIfReading` refused a lone `!= 'pull_request'` clause, so the dogfood shards' path population (the dogfood leads `--commands` exists to give) would have dropped. It now strips that clause the same way as the full-battery one (exact spelling, last conjunct only) and returns `notOnPullRequest` beside `fullBatteryOnly`. The row clause says "does NOT run on this PR … its first run on this change is the merge-queue build". `merge_group` excluded alone is still refused. Self-test: the near-miss case is re-pointed, and new cases cover the clause, its position, the doubled spelling, both flags, and a LIVE pin on the shards' row with an in-place ablation. The full-battery LIVE pin stays on `Dogfood Verify CLI`, as the test's own note asks. - Read and left alone, from the code: `check-shard-attestation.mjs` reads no event in any `if:`. Its verdict already judges a full dogfood matrix beside a skipped CLI leg green (self-test case "each leg is judged on its own roster"), so a queue build counts 3 credentials plus 0 expected. `check-required-contexts.mjs` pins only registry rows, and `Dogfood Regression Gate` is not one (prose only, above). `check-ci-filter-parity.mjs` reads filter globs, not events. ## Queue wall time (before) Window: the 24 hours before the read, runs created at or after `2026-11-04T18:41:25Z` (read 2026-11-05T18:41Z, at `origin/main` `6701c08a44`). ``` gh api -X GET "repos/objectstack-ai/objectstack/actions/workflows/ci.yml/runs?event=merge_group&created=%3E%3D2026-11-04T18:41:25Z&per_page=100&page=N" # N = 1..4 duration = updated_at - run_started_at, completed runs, run_attempt 1 throughout ``` | `ci.yml` on `merge_group`, last 24 h | n | median | p90 | max | |---|---|---|---|---| | completed, every conclusion | 303 | 20.5 min | 26.0 min | 32.8 min | | `success` | 285 | 20.4 min | 25.8 min | 32.8 min | | `failure` | 18 | 21.9 min | 26.7 min | 27.8 min | (306 runs listed, 3 still `in_progress`, none cancelled.) What finishes last in a queue run, from the jobs of the 60 newest `success` runs (`repos/objectstack-ai/objectstack/actions/runs/RUN_ID/jobs?per_page=100&filter=latest`, offset = job `completed_at` minus the run's first job `created_at`): `Test Core` closes 38 of 60 runs at a median of 20.8 min (p90 25.9), then `Test UI` (11) and `Lint UI` (10). `Build Core` closes at a median of 8.6 min. Dogfood shards for comparison, from `main`'s push runs in the same window (`repos/objectstack-ai/objectstack/actions/workflows/ci.yml/runs?event=push&branch=main&created=%3E%3D2026-11-04T18:41:25Z`, 135 runs; jobs read for 128 of the 130 completed ones, 2 reads answered 502). `success` jobs only, `completed_at - started_at`: | job (push, `main`) | n | median | p90 | max | |---|---|---|---|---| | `Dogfood Regression Gate (n/3)` shard | 282 | 11.4 min | 12.3 min | 14.2 min | | `Dogfood Verify CLI` (stays off the queue) | 99 | 5.5 min | 8.3 min | 8.6 min | | `Test Core (n/N)` shard, for scale | 790 | 12.6 min | 18.4 min | 26.0 min | A shard (~11.4 min of execution, started after `filter`) fits inside the queue run's ~20.8 min `Test Core` close. So queue wall time should move little unless runner pickup slows under the +3 jobs per entry. That is the reading the seat's 24 h "after" measurement checks. It is not assumed here. The card's "~5 min per shard" figure is today's ~11.4 min on push. ## Tests All readings at head `4bb48774cf` (this branch with `origin/main` `63de248a1c` merged once), after `pnpm install --frozen-lockfile` and `pnpm build` (lock VERDICT command-exit 0). - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives **80** commands for this diff (10 paths). Each was run, and its exit code was recorded before any pipe. **All 80 exit 0.** `--ran` reconciles: "80 derived, 80 run, 0 NOT-MEASURED, 0 UNRUN" ("a DERIVED zero — all 80 recorded an exit code and none of them is 3"). - On the pre-merge head `57fd195a35`, four dist-reading gates first answered exit 3 (PREREQUISITE NOT MET, no build): `check:doc-formula-expressions`, `check:dts-closure`, `check:dual-build-cjs-loads` and `check:lean-entry-closure`. After the build all four exit 0. `check:pm-skill-ratchet` went red on an `AGENTS.md` line of 123 bytes against the 120-byte budget. Commit `c029b25517` rewrapped it (no line added, ceiling 1050 held), and the gate exits 0. The final-head run above includes both. - Edited tools' own suites, run beyond the derived list: - `node scripts/pm/dispatch-gates.mjs --self-test` (both tiers): "2240 cases pass" (fast 1951 + slow 289). - `pnpm check:pm-expected-skips`: 209 cases pass, with the new reason and fixture cases. - `pnpm check:required-contexts`, `pnpm check:shard-attestation` and `node scripts/ci/scheduled-full-run.mjs --self-test` are in the 80 above. - Full dogfood battery: not owed. There is no `!` in the title and no `(narrowing)` arm. The diff changes no test or runtime code, so no ablation either: a workflow, scripts and docs are out of its scope. - CI check-runs on this PR: read at hand-off; the seat cites them. ## Acceptance notes - ⚠ **The ruling's "ejects the PR from the queue" holds only when the context is required.** `Dogfood Regression Gate` has been outside the ruleset's required set since #22998, and AGENTS.md §7 lists the ten contexts the queue enforces. After this PR a dogfood red shows on the queue build and reds that run, but GitHub's merge queue holds an entry only on required checks, so the entry still merges and `main`'s push run goes red as before. Whether the context rejoins the required set (a ruleset change, plus a `REQUIRED_CONTEXTS` row in `check:required-contexts`) was not ruled when this PR opened. The follow-up ruling 6669982756 (maintainer 「同意」, A in two steps) settles it: this PR lands without a ruleset change; after a clean 24-hour reading of the queue builds, a `REQUIRED_CONTEXTS` row lands first and the maintainer adds the context to the ruleset after it. - `AGENTS.md` and `docs/agents/multi-agent-discipline.md` were outside the claim's file surface. They are edited because their Two speeds line restates the split this PR changes ("PR and queue runs carry the fast set"). The seat's delta corrected the detail file's stale "10 minutes of wall per queue entry" to the measured median. - Carrier: none. ## Verification - `node scripts/pm/check-expected-skips.mjs --roster` at this head: `Dogfood Regression Gate (${{ matrix.shard }}/3) … gate: job-gate filter core + pr-event pull_request` and `Dogfood Verify CLI … pr-event pull_request|merge_group`. - `node scripts/pm/dispatch-gates.mjs --commands` at this head renders the shards' row as "⛔ does NOT run on this PR: its `if:` excludes the pull_request event, so its first run on this change is the merge-queue build, after the PR is enqueued", and `Dogfood Verify CLI`'s with the unchanged full-battery sentence. - The live proof is this PR's own queue entry, and every entry after it: three `Dogfood Regression Gate (n/3)` jobs on a `merge_group` run, and `Dogfood Regression Gate` counting 3 credentials beside a skipped `Dogfood Verify CLI`. Session: `session_017ihL2ebbZkr2QAEG1x7UC8` · seat `domain:devx#2`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- signed: domain:devx#2 · marchtian · session_017ihL2ebbZkr2QAEG1x7UC8 --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0c9aa82 commit 788715e

10 files changed

Lines changed: 206 additions & 77 deletions

‎.github/workflows/ci.yml‎

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -114,26 +114,43 @@ concurrency:
114114
cancel-in-progress: true
115115

116116
# ── THE TWO SPEEDS (#22998) ──────────────────────────────────────────────────
117-
# A `pull_request` or `merge_group` run carries the FAST SET: `Build Core`,
118-
# `Test Core` (affected-only) and `Build Docs`. `main`'s 4-hourly `schedule` run,
119-
# its `push` runs and an on-demand `workflow_dispatch` carry the FULL BATTERY:
120-
# the fast set plus the four jobs below that spell
117+
# A `pull_request` run carries the FAST SET: `Build Core`, `Test Core`
118+
# (affected-only) and `Build Docs`. A `merge_group` run carries the fast set
119+
# plus the three `dogfood` shards (#30461, ruling record 6663883327, letter B),
120+
# whose job alone spells
121+
#
122+
# github.event_name != 'pull_request'
123+
#
124+
# in its `if:`, so a dogfood red shows on the queue build of the entry that
125+
# carries it rather than first on `main`'s push run after it. It is advisory
126+
# there: the context is outside the required set (below), so the queue may
127+
# merge the entry before or despite the shards.
128+
# `main`'s 4-hourly `schedule` run, its `push` runs and an on-demand
129+
# `workflow_dispatch` carry the FULL BATTERY: all of that plus the three jobs
130+
# below that spell
121131
#
122132
# github.event_name != 'pull_request' && github.event_name != 'merge_group'
123133
#
124-
# in their `if:` — `temporal-conformance`, `dogfood` (three shards),
125-
# `dogfood-verify` and `ui-published-artifacts`. Nothing is deleted: the jobs
126-
# moved off the per-PR path, the last one never had one (#26717). A red in one of them is a main-red for the owning lane, and the
127-
# publish lane reads the full battery before its approval (release.yml's
128-
# `full-battery` job, `scripts/ci/scheduled-full-run.mjs --release-gate`).
134+
# in their `if:` — `temporal-conformance`, `dogfood-verify` and
135+
# `ui-published-artifacts`. Nothing is deleted: the jobs moved off the per-PR
136+
# path, the last one never had one (#26717). A red in one of them is a main-red
137+
# for the owning lane, and the publish lane reads the full battery before its
138+
# approval (release.yml's `full-battery` job,
139+
# `scripts/ci/scheduled-full-run.mjs --release-gate`). ⛔ No affected-only
140+
# dogfood filter on the queue: the shards run on every entry, through the
141+
# `core` output, which is 'true' on `merge_group` (letter C was not ruled).
142+
# The queue enforces only the ruleset's required set, and `Dogfood Regression
143+
# Gate` is outside it since #22998 (check:required-contexts' tombstone); this
144+
# file does not decide whether it rejoins.
129145
#
130-
# Spelled as a DENY-list of the two per-PR events on purpose — the filter
146+
# Spelled as a DENY-list of the per-PR events on purpose — the filter
131147
# contract's half 1 below, applied to events: a trigger added to `on:` later
132148
# RUNS these jobs, and the failure direction of a wrong guess is a slower PR,
133149
# never a full battery that silently stopped running. `dogfood-gate` keeps
134-
# `if: always()` (check:shard-attestation pins it): with both legs skipped it
135-
# counts zero expected attestations and concludes success, exactly as on a
136-
# filter-skipped diff.
150+
# `if: always()` (check:shard-attestation pins it): with both legs skipped (a
151+
# pull request) it counts zero expected attestations and concludes success,
152+
# exactly as on a filter-skipped diff; on a queue build it counts the three
153+
# shards' credentials and `dogfood-verify`'s skip as zero expected.
137154

138155
jobs:
139156
filter:
@@ -158,8 +175,9 @@ jobs:
158175
# 'true'` did.
159176
docs: ${{ github.event_name != 'merge_group' && steps.changes.outputs.docs || 'true' }}
160177
# Read by `test-plan`, `test` (ORed with `crosspkg`) and `build-core`,
161-
# and on push / schedule by `dogfood`, `dogfood-verify` and
162-
# `temporal-conformance` — see the `core` filter below and each job's `if:`.
178+
# on merge_group / push / schedule by `dogfood`, and on push / schedule
179+
# by `dogfood-verify` and `temporal-conformance` — see the `core` filter
180+
# below and each job's `if:`.
163181
core: ${{ github.event_name != 'merge_group' && steps.changes.outputs.core || 'true' }}
164182
# Gates the `test` job ONLY, ORed with `core` — see the `crosspkg`
165183
# filter below and the `test` job's `if:` (#9829, #10015).
@@ -2390,9 +2408,11 @@ jobs:
23902408
# below — the shard count can change without touching it.
23912409
name: Dogfood Regression Gate (${{ matrix.shard }}/3)
23922410
needs: filter
2393-
# See THE FILTER CONTRACT on the filter job's outputs (#4928). Full battery
2394-
# only: see THE TWO SPEEDS above `jobs:` (#22998).
2395-
if: ${{ !cancelled() && needs.filter.outputs.core != 'false' && github.event_name != 'pull_request' && github.event_name != 'merge_group' }}
2411+
# See THE FILTER CONTRACT on the filter job's outputs (#4928). The queue
2412+
# build and the full battery, never a pull request: see THE TWO SPEEDS
2413+
# above `jobs:` (#22998; `merge_group` left this deny-list by #30461,
2414+
# ruling record 6663883327). `dogfood-verify` below keeps both events.
2415+
if: ${{ !cancelled() && needs.filter.outputs.core != 'false' && github.event_name != 'pull_request' }}
23962416
runs-on: ubuntu-latest
23972417
# Backstop only — the stall guard on the test step is the primary detector
23982418
# for a #4250-style hang (see Test Core). 30 min is ~6× a shard (~5 min;
@@ -2704,11 +2724,13 @@ jobs:
27042724
overwrite: true
27052725

27062726
dogfood-gate:
2707-
# Stable check name for a SHARDED job (#3622 follow-up). Since #22998 its
2708-
# legs run in the full battery only (THE TWO SPEEDS above `jobs:`) and the
2709-
# context leaves the required set; on a PR or queue build both legs skip
2710-
# and this gate counts zero expected attestations. The history below is
2711-
# why the name and the `if: always()` stay.
2727+
# Stable check name for a SHARDED job (#3622 follow-up). Since #22998 the
2728+
# context is outside the required set (THE TWO SPEEDS above `jobs:`). On a
2729+
# PR both legs skip and this gate counts zero expected attestations; on a
2730+
# queue build (since #30461) the three dogfood shards run and are counted,
2731+
# while `dogfood-verify` skips and is judged with zero expected; the full
2732+
# battery runs and counts both. The history below is why the name and the
2733+
# `if: always()` stay.
27122734
#
27132735
# Branch protection required the context "Dogfood Regression Gate". Once
27142736
# the job became a 2-way matrix its checks publish as "Dogfood Regression

‎.github/workflows/release.yml‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,8 +1496,9 @@ jobs:
14961496
# ══════════════════════════════════════════════════════════════════════════
14971497
full-battery:
14981498
name: Full battery green at the version commit
1499-
# A PR or queue build carries ci.yml's FAST SET since #22998; the dogfood
1500-
# and temporal legs run only on main's push, 4-hourly schedule and
1499+
# A PR or queue build carries ci.yml's FAST SET since #22998 (a queue
1500+
# build adds the three dogfood shards since #30461); `dogfood-verify` and
1501+
# the temporal leg run only on main's push, 4-hourly schedule and
15011502
# dispatch runs. So "tested before a release" is read HERE, as a gate: the
15021503
# newest completed `schedule` or `push` runs of `CI` and `Lint & Type
15031504
# Check` whose head is the version commit or a first-parent ancestor of it

‎.github/workflows/upgrade-harness-optin.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
# its data" — a check each of the line's PRs passes BEFORE it merges. The
99
# harness that answers R2 is a dogfood test,
1010
# `packages/qa/dogfood/test/upgrade-18x-harness.dogfood.test.ts`, and the
11-
# dogfood jobs in `ci.yml` run only on `main`'s full battery (THE TWO SPEEDS,
12-
# #22998): they skip `pull_request` and `merge_group`. So without this workflow
13-
# the line's PRs get their first R2 answer after the merge.
11+
# dogfood shards in `ci.yml` skip `pull_request` (THE TWO SPEEDS, #22998; they
12+
# run on `merge_group` since #30461). So without this workflow the line's PRs
13+
# get their first R2 answer in the merge queue, after the PR is enqueued.
1414
#
1515
# This workflow adds the missing TRIGGER for that ONE file, and nothing else.
1616
# The test, its fixture and its assertions stay where they are, and the full

‎AGENTS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,8 +510,8 @@ Even inside your own worktree, operate defensively:
510510
`Build & E2E UI` and `Live E2E UI`; a check outside those ten is advisory
511511
([why that matters](docs/agents/multi-agent-discipline.md#advisory-checks-ride-through)). A required context is
512512
matched by check-run name, so a rename detaches its gate silently — treat those ten names as contract.
513-
**Two speeds** — PR and queue runs carry the fast set; `main`'s scheduled and push runs carry the full
514-
battery ([detail](docs/agents/multi-agent-discipline.md#two-speeds-which-runs-carry-which-battery)).
513+
**Two speeds** — PR runs carry the fast set, queue runs add the dogfood shards; `main`'s scheduled and push runs
514+
carry the full battery ([detail](docs/agents/multi-agent-discipline.md#two-speeds-which-runs-carry-which-battery)).
515515
**Main-red:** a full-battery red is its lane's first work — triage files it under
516516
the main-red convention, the lane's next PR fixes it, and ⛔ no lane PR arms the queue meanwhile.
517517

‎docs/agents/multi-agent-discipline.md‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,13 @@ A check outside those ten is advisory and rides through, and an advisory red
130130

131131
← [`AGENTS.md` › Multi-agent working discipline](../../AGENTS.md#multi-agent-working-discipline)
132132

133-
**Two speeds** (ruling record 6120820653): PR and queue runs carry the fast set, 10 minutes of
134-
wall per queue entry; `main`'s scheduled (4-hourly) and push runs carry the full battery — dogfood, e2e,
135-
`Temporal Conformance (live PG + MySQL)` — read by the publish lane before
136-
any approval.
133+
**Two speeds** (ruling record 6120820653): PR and queue runs carry the fast set, about 20 minutes
134+
of wall per queue entry (median, 2026-11-05); `main`'s scheduled (4-hourly) and push runs
135+
carry the full battery — dogfood, e2e, `Temporal Conformance (live PG + MySQL)` — read by the publish lane before
136+
any approval. Queue runs also carry the three `Dogfood Regression Gate` shards (ruling record
137+
6663883327), so a dogfood red shows on the queue build, advisory only; `Dogfood Verify CLI`,
138+
Temporal Conformance and `UI Published Artifacts` stay full-battery only. The `Dogfood Regression
139+
Gate` context is outside the required set, so the queue does not hold an entry on it.
137140

138141
## Re-arm awareness
139142

‎scripts/check-required-contexts.mjs‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,14 @@ const UNATTRIBUTED_BATTERY = '(no battery open)';
384384
* RETIRED_CONTEXT_NAMES row, for the reason the `Build Docs` / `Console Pin
385385
* Gate` tombstone above gives: neither name is dead — both jobs still publish
386386
* these check-runs on every full-battery run, and the instruction files name
387-
* them correctly as full-battery jobs.
387+
* where each runs.
388+
* Since #30461 (ruling record 6663883327, letter B) the three dogfood shards
389+
* also run on `merge_group`, so a queue build publishes `Dogfood Regression
390+
* Gate` over their credentials; `Temporal Conformance` stays full-battery
391+
* only. The context stays out of this registry and of the ruleset for now: the
392+
* follow-up ruling on #30461 (A, in two steps, 6669982756) puts it back only
393+
* after a clean 24-hour reading of the queue builds, the row here landing
394+
* first and the ruleset edit after it.
388395
*/
389396
export const REQUIRED_CONTEXTS = [
390397
{

‎scripts/ci/scheduled-full-run.mjs‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -798,8 +798,9 @@ export function renderBody({
798798
// ---------------------------------------------------------------------------
799799
//
800800
// A PR or queue build carries the FAST SET since #22998 (ci.yml's THE TWO
801-
// SPEEDS); the dogfood and temporal legs run only on `main`'s
802-
// push, 4-hourly schedule and dispatch runs. "Tested before a release" is
801+
// SPEEDS; a queue build adds the three dogfood shards since #30461);
802+
// `dogfood-verify` and the temporal leg run only on `main`'s push, 4-hourly
803+
// schedule and dispatch runs. "Tested before a release" is
803804
// therefore a statement about those runs, and this half makes it a gate:
804805
// release.yml's `full-battery` job, which `publish` needs, so a refusal here
805806
// means no deployment is queued at the `release` environment at all.

‎scripts/pm/check-expected-skips.mjs‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,14 @@ function gatedReason(workflow, wf, gate, matrix) {
539539
}
540540
if (t.kind === 'pr-event') {
541541
const where = [t.events.includes('pull_request') && 'every PR head', t.events.includes('merge_group') && 'every queue build'].filter(Boolean).join(' and ');
542-
return `full battery only: its \`if:\` excludes ${tick(t.events)}, so it skips on ${where} and is answered by main's push and scheduled runs`;
542+
// "Full battery only" is said only when BOTH per-PR events are excluded:
543+
// since #30461 ci.yml's `dogfood` shards exclude `pull_request` alone and
544+
// run on every queue build, so the queue build answers them first.
545+
if (PR_EVENTS.every((e) => t.events.includes(e))) {
546+
return `full battery only: its \`if:\` excludes ${tick(t.events)}, so it skips on ${where} and is answered by main's push and scheduled runs`;
547+
}
548+
const answered = t.events.includes('pull_request') ? "the merge-queue build and main's push and scheduled runs" : "every PR head and main's push and scheduled runs";
549+
return `its \`if:\` excludes ${tick(t.events)} alone, so it skips on ${where} and is answered by ${answered}`;
543550
}
544551
if (t.kind === 'event-action') {
545552
return `its \`if:\` excludes the ${tick(t.actions)} pull_request event(s); each such event is its own run on the same head, beside the run that concluded`;
@@ -1284,6 +1291,8 @@ function fixtureRun(name, conclusion, suite = 1, extra = {}) {
12841291
const FILTER_WIDENED = (o) => `\${{ github.event_name != 'merge_group' && steps.changes.outputs.${o} || 'true' }}`;
12851292
const CORE_IF = "${{ !cancelled() && needs.filter.outputs.core != 'false' }}";
12861293
const FULL_BATTERY_IF = "${{ !cancelled() && needs.filter.outputs.core != 'false' && github.event_name != 'pull_request' && github.event_name != 'merge_group' }}";
1294+
// ci.yml's `dogfood` shards since #30461: the queue build runs them, a PR head skips them.
1295+
const QUEUE_AND_FULL_IF = "${{ !cancelled() && needs.filter.outputs.core != 'false' && github.event_name != 'pull_request' }}";
12871296
const EVENT_ACTION_IF = "github.event.action != 'labeled' && github.event.action != 'unlabeled' && github.event.action != 'edited'";
12881297
const optInIf = (label) => `contains(github.event.pull_request.labels.*.name, '${label}') && (github.event.action != 'labeled' || github.event.label.name == '${label}')`;
12891298
const FIXTURE_TREE = Object.freeze({
@@ -1307,7 +1316,7 @@ const FIXTURE_TREE = Object.freeze({
13071316
},
13081317
'test-gate': { name: 'Test Core', needs: ['test', 'filter'], if: 'always()' },
13091318
'temporal-conformance': { name: 'Temporal Conformance (live PG + MySQL)', needs: 'filter', if: FULL_BATTERY_IF },
1310-
dogfood: { name: 'Dogfood Regression Gate (${{ matrix.shard }}/3)', needs: 'filter', if: FULL_BATTERY_IF, strategy: { matrix: { shard: [1, 2, 3] } } },
1319+
dogfood: { name: 'Dogfood Regression Gate (${{ matrix.shard }}/3)', needs: 'filter', if: QUEUE_AND_FULL_IF, strategy: { matrix: { shard: [1, 2, 3] } } },
13111320
'dogfood-verify': { name: 'Dogfood Verify CLI', needs: 'filter', if: FULL_BATTERY_IF },
13121321
'dogfood-gate': { name: 'Dogfood Regression Gate', needs: ['dogfood', 'dogfood-verify', 'filter'], if: 'always()' },
13131322
'build-core': { name: 'Build Core', needs: 'filter', if: CORE_IF },
@@ -1531,6 +1540,7 @@ export function selfTest() {
15311540
t('jobGate: …nor always(), an absent if:, a non-string, or two ${{ }} blocks', [jg('always()'), jg(undefined), jg(true), jg("${{ needs.filter.outputs.a != 'false' }} && ${{ needs.filter.outputs.b != 'false' }}")].every((x) => x === null), true);
15321541
t('jobGate: …nor an if: whose parentheses do not balance', jg("!cancelled() && (needs.filter.outputs.core != 'false'"), null);
15331542
t('spelling: a gate renders in one short line', gateTermsSpelling(jg(FULL_BATTERY_IF).terms), 'filter core + pr-event pull_request|merge_group');
1543+
t('jobGate: the queue-and-full shape (#30461) is one pr-event term excluding pull_request alone', JSON.stringify(jg(QUEUE_AND_FULL_IF)?.terms.find((x) => x.kind === 'pr-event')?.events), JSON.stringify(['pull_request']));
15341544

15351545
// ---- the derivation, on the frozen fixture tree ----------------------------
15361546
const fx = fixtureRoster();
@@ -1556,6 +1566,9 @@ export function selfTest() {
15561566
t("reason: a queue-widened output says the merge-queue build runs it", fxRow('Build Core')?.reason.includes("merge-queue build widens it to 'true'"), true);
15571567
t("reason: a ui output says the queue reads its own diff", fxRow('Build UI Deps')?.reason.includes("against the queue's base"), true);
15581568
t('reason: a full-battery job says so', fxRow('Dogfood Verify CLI')?.reason.includes('full battery only'), true);
1569+
const queueRow = fxRow('Dogfood Regression Gate (${{ matrix.shard }}/3)');
1570+
t('reason: a job excluding pull_request alone is NOT called full battery only (#30461)', queueRow?.reason.includes('full battery only'), false);
1571+
t('reason: …it says only PR heads skip it and the merge-queue build answers it', queueRow?.reason.includes('skips on every PR head and is answered by the merge-queue build') && !queueRow?.reason.includes('every queue build'), true);
15591572
t('reason: an opt-out label names its label', fxRow('Check Changeset')?.reason.includes('`skip-changeset` label skips it'), true);
15601573
t('reason: a matrix job names its raw template', fxRow('Test Core (${{ matrix.shard }}/6)')?.reason.includes('raw `name:` template'), true);
15611574
t('reason: unread conjuncts are counted, never hidden', fxRow('Build UI Deps')?.reason.includes('1 further condition(s)'), true);

0 commit comments

Comments
 (0)