You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 788715e
Browse filesBrowse the repository at this point in the historyBrowse 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>
Copy file name to clipboardExpand all lines: scripts/pm/check-expected-skips.mjs
+15-2Lines changed: 15 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -539,7 +539,14 @@ function gatedReason(workflow, wf, gate, matrix) {
539
539
}
540
540
if(t.kind==='pr-event'){
541
541
constwhere=[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
+
constanswered=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}`;
543
550
}
544
551
if(t.kind==='event-action'){
545
552
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 = {}) {
@@ -1531,6 +1540,7 @@ export function selfTest() {
1531
1540
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);
1532
1541
t('jobGate: …nor an if: whose parentheses do not balance',jg("!cancelled() && (needs.filter.outputs.core != 'false'"),null);
1533
1542
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']));
1534
1544
1535
1545
// ---- the derivation, on the frozen fixture tree ----------------------------
1536
1546
constfx=fixtureRoster();
@@ -1556,6 +1566,9 @@ export function selfTest() {
1556
1566
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);
1557
1567
t("reason: a ui output says the queue reads its own diff",fxRow('Build UI Deps')?.reason.includes("against the queue's base"),true);
1558
1568
t('reason: a full-battery job says so',fxRow('Dogfood Verify CLI')?.reason.includes('full battery only'),true);
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);
1559
1572
t('reason: an opt-out label names its label',fxRow('Check Changeset')?.reason.includes('`skip-changeset` label skips it'),true);
1560
1573
t('reason: a matrix job names its raw template',fxRow('Test Core (${{ matrix.shard }}/6)')?.reason.includes('raw `name:` template'),true);
1561
1574
t('reason: unread conjuncts are counted, never hidden',fxRow('Build UI Deps')?.reason.includes('1 further condition(s)'),true);
0 commit comments