Repository navigation
Commit df91a35
ci: let a covered push run borrow the merge queue's verdict (#31611)
Fixes #31519
## 维护者速读
- 改了什么:`main` 的 push 运行先读合并队列的结论。本次 push
的每个提交,如果都在自己的第一父提交上有一次全绿的队列运行,push 运行就只跑 main
专属的作业,其余的测试从队列借用结论;只要缺一个、红一个或基不对,就照旧跑全套。
- 为什么改:897 次 push 运行里有 896 次在重测队列刚刚测过的同一棵树,每次大约 260 分钟的 runner 时间。
- 风险与回滚:默认关闭——读不到、读错或结论不全都算"未覆盖",push 照跑全套;每 4
小时的定时运行仍是完整测试;发布门禁通过队列运行重新读取借用的结论。回滚就是 revert 这一个 PR。
- 先看:`.github/workflows/ci.yml` 顶部新增的两个作业(`queue-verdict`、`Borrowed
queue verdict`),以及 `scripts/ci/scheduled-full-run.mjs` 里的 HALF 4。
- 两分钟验证:`node scripts/ci/scheduled-full-run.mjs --self-test` 与 `node
scripts/check-shard-attestation.mjs --self-test` 都应 exit 0;"queue
verdict: the push probe" 一组里包含下文三个故意制造的红例。
- 需要你做的:审阅后手动合并(Tier H)。
## What changes
- **`queue-verdict` job** (push only, sparse checkout of `scripts/`):
`scheduled-full-run.mjs --queue-verdict --before --after` lists the
pushed commits (cap 20) and reads, for each one, the newest
`merge_group` run of `ci.yml` on that commit. The push is **covered**
only when every commit's deciding queue run concluded `success` and was
built on the commit's first parent. It writes `covered`, `runs` and
`ui_full_green` to `GITHUB_OUTPUT` and a verdict paragraph to the step
summary. A read failure or an empty push is `covered=false`.
- **`Borrowed queue verdict` job** (`if: ${{ success() &&
needs.queue-verdict.outputs.covered == 'true' }}`): the per-merge
verdict the release gate reads by name. It runs the affected-test
selector against `HEAD^1` and answers `test_ui=skip` only when the
head's queue run selected the whole Test UI suite and all 16 shards
passed (design note Q2 = B).
- **Covered pushes skip** the jobs whose verdict is borrowed. Every skip
reads `needs.queue-verdict.outputs.covered != 'true'`, so a missing or
garbled output runs the job (fail closed). The three aggregates
(`test-gate`, `dogfood-gate`, `test-ui-gate`) carry `if: always() &&
needs.queue-verdict.outputs.covered != 'true'`; `build-docs`,
`build-e2e-ui` and `live-e2e-ui` carry the covered conjunct; `test-ui`
also needs `test_ui != 'skip'`.
- **Seed-only legs** (design note Q1 = A): on a covered push the Test
Core and dogfood shards still run to save their Turbo seeds, but exit
early on `OS_SEED_ONLY` before their slice build and test step. Their
attestation pairs are guarded off.
- **Release gate** (`release.yml` comment, `scheduled-full-run.mjs` HALF
3): a borrowed push run is re-read through the head's queue run. An
unread borrow record is `pending` and refused, never a pass.
- **`ui/scripts/test-file-timings.mjs`**: `selectRuns` reads schedule
runs only, since a covered push no longer runs every Test UI shard.
- **Docs**: the AGENTS.md "Two speeds" sentence and a new paragraph in
`docs/agents/multi-agent-discipline.md`.
No required context is removed: every required check still reports on
every push, as a pass or a skip that its aggregate reads.
## Why
The push run of `main` re-tests the tree the merge queue just passed
(896 of 897 measured runs), at about 260 runner-minutes each. The queue
already ran that exact tree green on that exact base, so the push run
can borrow the verdict. It keeps a per-merge verdict for the release
gate, and it keeps the cache seeds. The 4-hourly schedule stays the full
battery, and no test is skipped on a SHA the queue did not run green.
## Where the build differs from the design note (6722126387)
- **(a) Lint UI runs on a covered push.** Its Turbo seed saves on `push`
only, not on `schedule`, so skipping it would starve the seed.
- **(b) A seed-only Test Core leg seeds the closure and holds no test
results.** `prune-turbo-cache.mjs` is unchanged; a seed-only leg passes
the closure's summary directory as `--run-summaries`.
- **(c) Dogfood Regression Gate skips on a covered push**, while Dogfood
Verify CLI's own conclusion stands.
- **(d) `check-shard-attestation.mjs` admits exactly two spellings**
(the borrowed gate `if:` and the seed-only credential guard), with an
8-case battery.
- **The seed-only exits read `OS_SEED_ONLY`** instead of a step `if:`,
because dispatch-gates' LIVE case "names the invocation itself" reads
the dogfood test step and a step `if:` broke it.
- **AGENTS.md re-wrap:** the paragraph from "**Green means**" through
Main-red is re-wrapped, with every word outside the "Two speeds"
sentence byte-identical, to stay inside the file's 1050-line and
120-byte line budget. The lines are in the claim surface.
- **Files admitted by claim amendment:**
`scripts/check-aggregator-roster.mjs` (6 self-test anchors only),
`.github/workflows/ui-test-timings-refresh.yml` (2 prose lines only),
and `ui/scripts/__tests__/turbo-cache-save-bound.test.ts` (one assertion
and its comment).
## Tests
- `scheduled-full-run.mjs --self-test`: new battery "queue verdict: the
push probe" (17 cases) and "release gate: a borrowed push verdict" (15
cases). The battery floor goes from 9 to 11.
- `check-shard-attestation.mjs --self-test`: new battery "#31519 a
borrowed push: the gate skip and the seed-only credential guard" (8
cases). The floor goes from 24 to 25.
- `ui/scripts/__tests__/test-file-timings.test.ts`: a `selectRuns` case
(17 pass). `turbo-cache-save-bound.test.ts`: 46 pass.
- **Three deliberate reds** through `runQueueVerdict` on a 3-commit
push, each turning a covered push into a full run:
- **missing queue run**: "no merge_group run of the workflow is on
cccccccccc", so `covered=false`.
- **red queue run**: "queue run 601 on bbbbbbbbbb concluded failure", so
`covered=false`.
- **wrong base**: "queue run 500 on aaaaaaaaaa was built on dddddddddd,
not on the commit's first parent bbbbbbbbbb", so `covered=false`.
- The control (all three green on their parents) reads `covered=true`.
- **Ablations**: removing each guard (missing, red, wrong base, and the
release gate reading an unread record as a full verdict) makes
`--self-test` exit 1; restored, it exits 0.
## Verification
All at head 80eca7d (origin/main 8c75f83 merged), after a full `pnpm
build` (exit 0):
- `node scripts/pm/dispatch-gates.mjs --commands`: 83 derived gate
commands, each run with its exit code recorded before any pipe. **83 of
83 exit 0.**
- `node scripts/pm/dispatch-gates.mjs --ran ran.list`: "83 derived
famil(ies) accounted for, 83 run, 0 NOT-MEASURED (a DERIVED zero)", exit
0.
- That sweep caught one red the earlier sweep could not: the AGENTS.md
re-wrap had split `Dogfood Regression Gate` across two lines, and
`check:required-contexts` matches the literal by substring. Fixed in
80eca7d; the gate is green in the 83.
- ESLint (`--no-inline-config`) on the changed root scripts and the
changed `ui/scripts` files: exit 0.
- `git merge-tree` against PR #31429's head 65cf47a and against current
`origin/main`: clean.
Serial: PR #31429 (devx#3) also edits ci.yml; merge-tree clean at
65cf47a; whichever lands second merges main
Size: 11 files, +1206 / -87 (under 3000 lines). Tier H (`ci.yml`,
`release.yml`, `AGENTS.md`): the maintainer merges it by hand.
Clause-②: no
Metadata-protocol: none
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; ESLint passes on every changed script (the
full `pnpm lint` tree was not run).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset. (nothing
publishes: `skip-changeset`)
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (not touched)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QsR2jQztfi9hnsaBeBVcCU
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 5d1382a commit df91a35
11 files changed
Lines changed: 1213 additions & 87 deletions
File tree
- .github/workflows
- docs/agents
- scripts
- ci
- ui/scripts
- __tests__
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1609 | 1609 | | |
1610 | 1610 | | |
1611 | 1611 | | |
1612 | | - | |
| 1612 | + | |
| 1613 | + | |
| 1614 | + | |
1613 | 1615 | | |
1614 | 1616 | | |
1615 | 1617 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| |||
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
142 | | - | |
| 142 | + | |
143 | 143 | | |
144 | 144 | | |
145 | 145 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
501 | 501 | | |
502 | 502 | | |
503 | 503 | | |
504 | | - | |
505 | | - | |
506 | | - | |
507 | | - | |
508 | | - | |
509 | | - | |
510 | | - | |
511 | | - | |
512 | | - | |
513 | | - | |
514 | | - | |
515 | | - | |
516 | | - | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
517 | 517 | | |
518 | 518 | | |
519 | 519 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
138 | 138 | | |
139 | 139 | | |
140 | 140 | | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
141 | 150 | | |
142 | 151 | | |
143 | 152 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
502 | 502 | | |
503 | 503 | | |
504 | 504 | | |
505 | | - | |
| 505 | + | |
506 | 506 | | |
507 | 507 | | |
508 | 508 | | |
509 | 509 | | |
510 | | - | |
511 | | - | |
| 510 | + | |
| 511 | + | |
512 | 512 | | |
513 | | - | |
| 513 | + | |
514 | 514 | | |
515 | 515 | | |
516 | 516 | | |
| |||
532 | 532 | | |
533 | 533 | | |
534 | 534 | | |
535 | | - | |
| 535 | + | |
536 | 536 | | |
537 | 537 | | |
538 | 538 | | |
539 | | - | |
540 | | - | |
| 539 | + | |
| 540 | + | |
541 | 541 | | |
542 | 542 | | |
543 | | - | |
| 543 | + | |
544 | 544 | | |
545 | 545 | | |
546 | 546 | | |
| |||
0 commit comments