Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

Commit 140916c

Browse files
objectstack-fleet[bot]claudeos-zhuang
authored
ci(test): widen the test matrix from 12 to 16 legs, and move every pin of the width (objectui#12384) (#12399)
Fixes #12384 ## What this does It widens `ci.yml`'s `test` matrix from 12 to 16 legs and moves every pin of the width in the same diff. This is route A of the reading `6181333653`, which the seat accepted in claim `6181366176`. What stays: ⛔ `timeout-minutes: 20` is not raised, ⛔ `testTimeout` is not raised, ⛔ no test is skipped or quarantined, ⛔ the relevance gate is not widened, ⛔ the coverage matrix (8 legs, 40-minute cap) is not touched, and ⛔ no runner is pinned. It applies ruling `6121816480`'s own width rule: "the width derived from the total measured duration at a target of about 10 minutes per shard". Triage `6177117080` says: "A lighter shard plan, or a runner pin, changes no limit and needs no ruling." ## Pins moved Derived with `git grep` for `/12`, `twelve`, `--shards 12`, `shard N/12`, `of 12` and `12-runner`, then read hit by hit. - **`.github/workflows/ci.yml`**: - the `shard:` list (1 to 16); - the job name `Test (shard N/16)`; - the step name `Run tests (shard N/16)` and its echo line; - both `--shard=N/16` invocations; - `test-aggregate`'s `check-test-shard-results.mjs --shards 16`; - `shard-headroom.mjs --job 'Test (shard {n}/16)' --shards 16`; - the full-battery echo line; - the comments: a new "12 -> 16" paragraph beside the "4 -> 8" and "8 -> 12" ones, and the stale "8 runners" in the job's first paragraph, now width-neutral. - **`.github/workflows/test-shard-timings-refresh.yml`**: `test-file-timings.mjs select --shards 16`, its NOT MEASURED warning text, and the header. The header also gains one paragraph on what a width move does to the selection (A3 below). - **`.github/workflows/lint.yml`**: the one comment that names the legs. - **`scripts/dependabot-merge-gate.mjs`**: the `NOT_A_GATE` rows, `Test (shard 1/16)` through `Test (shard 16/16)`, and the comments and strings that name the PR lane. - **Usage headers**: `check-test-shard-results.mjs`, `shard-headroom.mjs`, `test-file-timings.mjs` and `select-affected-tests.mjs`. - **Tests**: - cases in `dependabot-merge-gate.test.ts`, `select-affected-tests.test.ts` and `shard-headroom.test.ts`; - comments in `check-eager-closure-budget.test.ts`, `check-skill-examples.test.ts` and `check-test-path-roots.test.ts`. - **`content/docs/guide/ci-cd-pipeline.md`**: - the job-table row, whose "Appears as" `ci-cd-pipeline-doc.test.ts` matches against ci.yml's `name:`; - a 12 -> 16 history sentence; - the sentences that state the shard count in the aggregator, refresh and Dependabot sections. - ⛔ The `OBJECTUI_ALLOW_MAJOR` sentences that objectui#12334 edits are not touched. - **`AGENTS.md`**: the `--shard=1/16` example, and the sentence two lines below it that names the example's number. Nothing else in that paragraph changes. **One new pin, in the same family.** `test-file-timings.test.ts` now holds the weekly refresh's `select --shards` to the `test` matrix width, the way `check-test-shard-results.test.ts` already holds `test-aggregate`'s `--shards`. - Without it, a refresh left on an old width selects no run. It prints NOT MEASURED and stays green, and the timings dataset quietly stops refreshing. - No test held this width pin before. The ablation is under Verification. **Left on purpose:** - history: "objectui#12208 to 12" and "Balanced, the twelve legs ..."; - `test-file-timings.test.ts`'s writer case, which turns `shard: { index: 3, count: 12 }` into `'3/12'`: an arbitrary reporter input and output, not a pin of the matrix; - `flowLaunch.translation-12075.test.tsx`'s "red in CI's shard 4/12": a dated measurement. ## A2: the required-check set names the aggregate, not the legs - **Command:** `node scripts/check-required-check-set.mjs --fixture FILE`. FILE is a `gh api repos/objectstack-ai/objectui/rules/branches/main` response read on 2026-10-15. The script's own live fetch answered HTTP 401 in this container (exit 2, "could not take a reading"). - **Result:** exit 0, "✅ INTACT — 1 pinned and 6 watched context(s) all present (7 required context(s) read)". - **The 7 contexts:** `Lint`, `Type Check`, `Build & E2E`, `Build Docs`, `Changeset Declaration`, `Test`, `Governed Surface Queue Guard`. No `Test (shard N/W)` leg is required, so the width is a workflow edit, not a ruleset change. ## A3: the timings refresh across the width change `scripts/test-shard-timings.json` is keyed per file and records no width, and the sequencer reads it per file, so it needs no change. The refresh's selector judges a run by the leg names of one width: `judgeRun` looks up `Test (shard n/M)` for each n. **Measured on 2026-10-15** by running `selectRuns()` unchanged against main's live runs, with its reads routed through `gh api`: - `--shards 16`: it examined 30 push and schedule runs, its cap (limit × 5). It skipped every one ("Test (shard 1/16) is not in the run") and took 0. That is the CLI's NOT MEASURED, exit 3. - Control, `--shards 12`: it took 6 of the first 6 runs examined. **After landing:** - The old 12-leg runs are skipped by name and are never mixed in. - The first green 16-leg full battery is the first run taken: the merge's own `push` run, then the hourly `schedule`. - The refresh runs on Mondays at 06:47 UTC. If it fires before such a run exists, it prints the NOT MEASURED warning, exits green and leaves `test-shard-timings.json` untouched. It does not fail, and the next week's run takes the 16-leg runs. Only a landing in the hour or so before that slot hits this. - This PR's own `pull_request` leg of the refresh workflow runs the same selection at 16 and should read NOT MEASURED. ## A4: the per-leg estimate These figures come from the reading's numbers. Actions history was not re-measured. Each leg carries 12/16 = 0.75 of today's load. The reading's 12-leg figures, over 50 hours of finished PR and queue legs: - median 11.3 min and p99 12.6 min; - the full-battery maximum is 13.6 min; - normal setup takes about 35 s; - the reading's upper bound for a fixed per-leg vitest start cost is 1.2 min. | | 12 legs (measured) | 16 legs, all cost scales | 16 legs, 1.8 min fixed per leg | |:--|--:|--:|--:| | median leg | 11.3 min | 8.5 min | 8.9 min | | p99 leg | 12.6 min | 9.45 min | 9.9 min | | full-battery longest leg | 13.6 min | 10.2 min | 10.65 min | | break-even slow factor, p99 leg (20 min / p99) | 1.59x | 2.12x | 2.02x | The last column is 1.8 + 0.75 × (leg − 1.8). - **Headroom.** Slow VMs measured 1.6x to 2.0x against normal controls. The p99 margin stays thin, at about 2.0x to 2.1x, as the reading said. The median leg gets about 2.25x to 2.35x. - **The nine legs named on the card.** Their projected `Run tests` of 1,168 to 1,383 s at 12 legs becomes 876 to 1,037 s at 16. With their own slow setup (47 to 68 s) that is 15.6 to 18.3 min, all under 20. - **Cost.** Four more legs at their fixed cost of 0.6 to 1.2 min each come to about +2.5 to +5 runner-min per run. That is +2% to 4% of about 136 runner-min, or about +330 to +650 runner-min a day at about 134 runs a day. Standard hosted runners on this public repository are not billed. - **The ruling's acceptance.** It reads "ten consecutive full-suite runs with the longest shard under 60 % of the cap", which is 12 min. - The projected longest leg on a normal VM is 10.2 to 10.65 min, under 12. - A VM slower than about 1.15x still crosses 12. - NOT MEASURED until it runs: `test-aggregate`'s headroom step reads it on every run. ## Verification (head `0f5a949e4`) **Tests.** I ran them from the repository root, under the shared verify lock. - Command: `pnpm exec vitest run --maxWorkers=2 scripts/__tests__/`. - Result: `Test Files 187 passed | 2 skipped (189)`, `Tests 5633 passed | 2 skipped (5635)`, lock `VERDICT command-exit 0`. - This covers every pin touched here: `check-test-shard-results`, `shard-headroom`, `select-affected-tests`, `dependabot-merge-gate`, `test-file-timings` and `ci-cd-pipeline-doc`. It also covers the other workflow-reading suites under `scripts/__tests__`. - None of the touched files carries a skip. **Ablation of the new refresh pin.** I used `ablation-replace` in wrap mode, under the lock, against the commit above. - Mutation: `select --shards 16 --limit 6` became `select --shards 12 --limit 6` in the refresh workflow. The anchor count went 1 → 0, the replacement count 0 → 1, and the blob `7198fa4f893c` became `9bdb34cea68c`. - Result: 1 failed and 14 passed, and the failure was the new case: "the refresh's --shards and ci.yml's `test` matrix have drifted apart: expected 12 to be 16". - Restore: the blob after restore equals HEAD's (`7198fa4f893c`) and `git diff HEAD` is empty. The green leg is the full run above. **`check-test-shard-results.mjs --fixture`:** | run | exit | reads | |:--|--:|:--| | `--shards 8` on the recorded 8-leg fixture `run-36042402511-attempt-1.json` | 0 | "all 8 shards reported success" | | `--shards 16` on that 8-leg fixture | 3 | every `Test (shard n/16)` ABSENT | | `--shards 16` on a 16-leg job list synthesized from that fixture (not committed) | 0 | "all 16 shards reported success" | | `--shards 12` on the synthesized list | 3 | every `Test (shard n/12)` ABSENT | **Gates that read these paths**, derived by hand from `lint.yml`, `repo-gates.yml`, `line-citation-gate.yml` and `changeset-presence.yml`. The objectstack `dispatch-gates` tool does not serve this repository. Each exit code was captured before any pipe. | gate | exit | |:--|--:| | `node scripts/check-control-bytes.mjs` | 0 | | `node scripts/check-action-ref-convention.mjs` | 0 | | `node scripts/check-doc-links.mjs` | 0 | | `node scripts/check-shell-escape-residue.mjs` | 0 | | `node scripts/check-doc-fence-languages.mjs --self-test` | 0 | | `node scripts/check-doc-fence-languages.mjs` | 0 | | `pnpm check:new-line-citations` ("0 new citation(s)") | 0 | | `node scripts/check-changeset-presence.mjs` ("no changeset is owed") | 0 | | `node scripts/check-test-path-roots.mjs` | 0 | | `node scripts/check-lint-coverage.mjs` | 0 | | `pnpm check:governed-queue-guard` | 0 | | `pnpm check:required-check-set` | 0 | The repository has no actionlint or other workflow linter, so the workflow-shape readings are the `scripts/__tests__` suites above. Repo-wide `pnpm lint` and the full test farm are CI's; this PR's own CI is the first 16-leg run. **Governed surface.** `node scripts/check-governed-queue-guard.mjs --test` on the 17 changed paths exits 3: ```text ⛔ GOVERNED — 4 of 17 path(s) are on a governed surface: AGENTS.md x1 — the repo-root agent instruction file .github/workflows/** x3 — CI, deploy and release workflows: what runs on every push, what deploys, what gates the queue - .github/workflows/ci.yml - .github/workflows/lint.yml - .github/workflows/test-shard-timings-refresh.yml ``` So this PR stays **draft** until an APPROVED review from a `GOVERNED_APPROVERS` account. The claim also owes a `CONTRACT_REVIEW_TIER` review. ## Acceptance notes - **Changeset.** None is owed. `check-changeset-presence` reads 0 published source and 0 published contract changed, and CI config and repository scripts publish nothing. - **A stale comment, left alone.** `ci-cd-pipeline-doc.test.ts` has a comment that still gives the matrix job's name with `/4`, stale since 4 -> 8. It is a comment that nothing reads, outside this card's surface. Carrier: none, so it is noted here only. - **A stale phrase, made width-neutral in place.** The header of `select-affected-tests.mjs` said "the four-shard coverage lane", stale since objectui#12256. It sat in the same sentence this diff edits, so it now reads "the sharded coverage lane". It is a comment only, and the coverage matrix itself is unchanged. - **What holds each width pin now:** - `--shards` in `test-aggregate`: `check-test-shard-results.test.ts`; - `--shards` in `shard-headroom`: `shard-headroom.test.ts`; - `--shards` in the refresh: `test-file-timings.test.ts`, new; - the `NOT_A_GATE` rows: the partition test in `dependabot-merge-gate.test.ts`; - the job and step names: `select-affected-tests.test.ts` and `test-file-timings.test.ts`; - the doc row: `ci-cd-pipeline-doc.test.ts`. - **The reading's out-of-scope finding stays on this card, its family carrier.** Two `export-to-react-compiles` tests ran 17.3 s and 17.6 s against `testTimeout: 15000` on a slow VM. This PR does nothing about them, and `testTimeout` is unchanged. ## 维护者速读(草稿) **改了什么** - CI 的测试分片从 12 条腿加到 16 条,每条腿的负载降到原来的四分之三。 - 20 分钟超时上限不动,不跳过任何测试,不动覆盖率分片,也不锁定 runner。 - 所有写死 12 的地方一并改成 16:工作流、Dependabot 合并门、每周计时刷新、文档,以及 AGENTS.md 里的示例。 - 另加一条测试:以后再改宽度时,如果漏改了计时刷新,本地测试会直接变红。 **为什么改** - 2026-10-14 有 16 条测试腿在 20 分钟上限被取消,全都没有挂死。 - 原因是部分虚拟机整体变慢,同样的文件要多花 1.6 到 2.0 倍的时间。 - 后果:测试全过的 PR 显示红色,要手动重跑;合并队列里的 PR 被踢出,后面排队的都要多等一轮。 - 12 条腿时,p99 腿只有约 1.59 倍余量,低于慢机器的系数;16 条腿约 2.0 到 2.1 倍。 - 这是执行既有裁决的宽度规则(每条腿约 10 分钟),不是新决定。 **风险与代价(含回滚)** - 每次运行约多 2.5 到 5 个 runner 分钟(+2% 到 4%)。本仓是公开仓,标准 runner 不计费。 - p99 余量仍然偏薄:约 2.0 到 2.1 倍,而实测最慢约 2.0 倍。 - 测试套件继续增长会再吃掉余量。现有的只读 headroom 报告步骤每次运行都会显示,不新增门禁。 - 落地后第一次每周计时刷新,如果早于第一次 16 腿全绿运行触发,会报 NOT MEASURED 并保留原文件,不会变红。 - 回滚:revert 本 PR 即回到 12 条腿。没有数据迁移,也没有仓库设置改动,因为必需检查只认 `Test` 聚合。 **席位意见** (留空) **你要做的** - 审阅后,在本 PR 上留下一条 APPROVED review。本 PR 改动了 `.github/workflows/**` 与 `AGENTS.md`,属于受管面。 - 之后由认领席翻 ready、挂入合并队列。 This change was implemented by the dispatched dev of the `domain:ui` seat 1 PM, session `https://claude.ai/code/session_01JZnppgHRp4sREcouhkQYDB`. --- _Generated by [Claude Code](https://claude.ai/code/session_01JZnppgHRp4sREcouhkQYDB)_ Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: os-zhuang <jack@objectstack.ai>
1 parent 4b496cd commit 140916c

17 files changed

Lines changed: 107 additions & 62 deletions

‎.github/workflows/ci.yml‎

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ on:
5353
# A `pull_request` or `merge_group` run carries the FAST SET: the `test`
5454
# matrix below runs the test files the change affects, selected by
5555
# `scripts/select-affected-tests.mjs`. `push` to `main` and this hourly
56-
# `schedule` carry the FULL BATTERY: every test file in twelve shards, plus
56+
# `schedule` carry the FULL BATTERY: every test file in sixteen shards, plus
5757
# the eight-shard coverage lane. Nothing is deleted or weakened; the whole
5858
# suite moved to `main`, where it runs on every merge and at least hourly.
5959
# GitHub runs `schedule` on the default branch only. The minute is off the
@@ -921,8 +921,9 @@ jobs:
921921
path: .turbo/cache
922922
key: turbo-${{ runner.os }}-type-check-${{ github.sha }}
923923

924-
# PRs run the suite split across 8 runners. The suite is dominated by fixed
925-
# per-file cost rather than by the assertions: a PR run reported 495s wall
924+
# PRs run the suite split across the runners of the `shard:` matrix below.
925+
# The suite is dominated by fixed per-file cost rather than by the
926+
# assertions: a PR run reported 495s wall
926927
# clock of which only 178s was `tests` — the rest was `setup` (831s
927928
# cumulative), `import` (199s) and `environment` (156s), each paid once per
928929
# test file because `isolate: true` re-executes the module graph per file
@@ -960,7 +961,7 @@ jobs:
960961
# over half again as many runners. Duration-balanced sharding is that
961962
# card's second pull request, not this edit. The width is one number on
962963
# both speeds (objectui#12214): the affected run on a pull request or queue
963-
# build and the full battery on `push` and `schedule` both use these twelve
964+
# build and the full battery on `push` and `schedule` both use these same
964965
# legs, so `test-aggregate`'s `--shards` has one value to agree with.
965966
#
966967
# ⭐ Duration-balanced (objectui#12208, the same ruling's second pull
@@ -969,8 +970,25 @@ jobs:
969970
# lucky-heavy leg is the one the cap cancels. The note on the `Run tests`
970971
# step below says what replaced it; `test-aggregate` prints the longest leg's
971972
# headroom on every run.
973+
#
974+
# ⭐ 12 -> 16 (objectui#12384; the same ruling's width rule, "the width
975+
# derived from the total measured duration at a target of about 10 minutes
976+
# per shard"). Balanced, the twelve legs were no longer heavy -- measured on
977+
# that card over the 50 hours after the balanced plan landed, 11.3 min
978+
# median and 12.6 min p99 per finished leg -- but the runners under them
979+
# were not all equally fast: the cap cancelled legs in 12 of the 279 runs
980+
# that ran tests, with no hang, every file on those VMs running 1.6x to 2.0x
981+
# slower than the same files on a normal one, episodically (13 of the 16
982+
# cancelled legs fell on one afternoon, 2026-10-14) and with no runner label
983+
# that tells such a VM apart. Twelve legs left a p99 leg 1.59x (20 / 12.6)
984+
# of slack, below that factor; sixteen legs carry 0.75 of the load each,
985+
# which moves the break-even slow factor to about 2.1x. The same three
986+
# refusals hold: ⛔ the ceiling is NOT raised, ⛔ no test is skipped, and ⛔
987+
# the relevance gate is not widened. A runner pin was not taken: there was
988+
# no measured runner class to pin. Whether the width still fits is
989+
# `test-aggregate`'s headroom reading on every run, not this comment.
972990
test:
973-
name: Test (shard ${{ matrix.shard }}/12)
991+
name: Test (shard ${{ matrix.shard }}/16)
974992
# Coverage instrumentation (v8 adds 40-100% overhead) is skipped on PRs;
975993
# the `test-coverage` job below runs the coverage lane on push.
976994
#
@@ -979,8 +997,9 @@ jobs:
979997
# that runs no tests is the hole this repository just paid for. `push` is
980998
# still excluded because the coverage lane below is the push lane: it is
981999
# sharded of its own since objectui#5403 (4 ways, 8 since objectui#12256) --
982-
# ⛔ INDEPENDENTLY of the matrix above, which objectui#9499 widened to 8 and
983-
# objectui#12208 to 12 without touching it: the two lanes declare their own
1000+
# ⛔ INDEPENDENTLY of the matrix above, which objectui#9499 widened to 8,
1001+
# objectui#12208 to 12 and objectui#12384 to 16 without touching it: the
1002+
# two lanes declare their own
9841003
# `shard:` matrix and their own `--shard=N/M`, and the coverage blobs are
9851004
# named `blob-N-M.json` by the coverage job's own flag -- and its
9861005
# `coverage-report` job merges every shard's blob report into ONE complete
@@ -1005,7 +1024,7 @@ jobs:
10051024
# Report every shard's failures, not just the first one to break.
10061025
fail-fast: false
10071026
matrix:
1008-
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
1027+
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
10091028

10101029
steps:
10111030
- name: Checkout code
@@ -1188,7 +1207,7 @@ jobs:
11881207
merge_group) base="$QUEUE_BASE" ;;
11891208
*)
11901209
echo 'mode=full' >> "$GITHUB_OUTPUT"
1191-
echo "A $EVENT run is the full battery: every test file, twelve shards."
1210+
echo "A $EVENT run is the full battery: every test file, sixteen shards."
11921211
exit 0
11931212
;;
11941213
esac
@@ -1235,18 +1254,18 @@ jobs:
12351254
# `scripts/__tests__/test-file-timings.test.ts` pins all three. The
12361255
# writer never throws into the run: no report is one stderr line, never
12371256
# a red leg.
1238-
- name: Run tests (shard ${{ matrix.shard }}/12)
1257+
- name: Run tests (shard ${{ matrix.shard }}/16)
12391258
if: steps.relevant.outputs.should_run == 'true'
12401259
env:
12411260
MODE: ${{ steps.select.outputs.mode }}
12421261
OBJECTUI_TEST_TIMINGS_OUT: ${{ runner.temp }}/test-file-timings/shard-${{ matrix.shard }}.json
12431262
run: |
12441263
if [ "$MODE" = 'mode=affected' ]; then
12451264
mapfile -t filters < "$RUNNER_TEMP/affected-tests.txt"
1246-
echo "Affected speed: ${#filters[@]} vitest filter(s), shard ${{ matrix.shard }} of 12."
1247-
pnpm exec vitest run --shard=${{ matrix.shard }}/12 "${filters[@]}"
1265+
echo "Affected speed: ${#filters[@]} vitest filter(s), shard ${{ matrix.shard }} of 16."
1266+
pnpm exec vitest run --shard=${{ matrix.shard }}/16 "${filters[@]}"
12481267
else
1249-
pnpm test --shard=${{ matrix.shard }}/12 --reporter=default --reporter=github-actions --reporter=./scripts/test-file-timings.mjs
1268+
pnpm test --shard=${{ matrix.shard }}/16 --reporter=default --reporter=github-actions --reporter=./scripts/test-file-timings.mjs
12501269
fi
12511270
12521271
# The full battery's per-file timings, for the weekly refresh of
@@ -1269,7 +1288,7 @@ jobs:
12691288
# ── The built-artifact pin lane, lifted out of the shard matrix ──────────
12701289
# (objectui#7183 for the lane itself; objectui#9499 for this job.)
12711290
#
1272-
# `pnpm test --shard=N/12` above resolves every workspace package to its `src`
1291+
# `pnpm test --shard=N/16` above resolves every workspace package to its `src`
12731292
# -- that is what the root alias map is for -- so no test in it can observe
12741293
# its own package's BUILT output. This is the only place a `dist`-importing
12751294
# pin runs, and `turbo run test:dist` carries `dependsOn: ["build"]` for the
@@ -1398,7 +1417,7 @@ jobs:
13981417
# ⚠️ `needs.test.result` ALONE WOULD BE A PHANTOM GATE, and that is why this
13991418
# job runs a script rather than reading an expression. A matrix job's
14001419
# `needs.<job>.result` is a single ROLLUP over every leg, and a rollup cannot
1401-
# distinguish "all twelve ran green" from "nine ran green and three were
1420+
# distinguish "all sixteen ran green" from "thirteen ran green and three were
14021421
# SKIPPED". A shard dropped from the matrix, or given a job-level `if:` that
14031422
# is false, would leave this context GREEN while a fraction of the suite never
14041423
# ran -- the objectui#8857 / objectui#3523 shape, a check reporting success
@@ -1443,7 +1462,7 @@ jobs:
14431462
TEST_MATRIX_ROLLUP: ${{ needs.test.result }}
14441463
run: |
14451464
echo "needs.test.result (the matrix rollup this gate deliberately does NOT trust) = $TEST_MATRIX_ROLLUP"
1446-
node scripts/check-test-shard-results.mjs --shards 12 --job-name 'Test'
1465+
node scripts/check-test-shard-results.mjs --shards 16 --job-name 'Test'
14471466
14481467
# The dist-pin lane is NOT a matrix, so its `needs` result is that one
14491468
# job's own conclusion and `skipped` is visible in it -- no API read is
@@ -1473,7 +1492,7 @@ jobs:
14731492
continue-on-error: true
14741493
env:
14751494
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1476-
run: node scripts/shard-headroom.mjs --job 'Test (shard {n}/12)' --shards 12 --cap-minutes 20 --target-minutes 10
1495+
run: node scripts/shard-headroom.mjs --job 'Test (shard {n}/16)' --shards 16 --cap-minutes 20 --target-minutes 10
14771496

14781497
# Push to main/develop: the coverage lane, sharded 4 ways with a blob-report
14791498
# merge (objectui#5403). The comment that used to sit here declined to shard

‎.github/workflows/lint.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ jobs:
398398
#
399399
# This step is a SECOND signal, not the gate. The gate is
400400
# `scripts/__tests__/check-test-path-roots.test.ts`, which runs inside
401-
# `Test (shard N/12)`. Those shards are not required contexts themselves
401+
# `Test (shard N/16)`. Those shards are not required contexts themselves
402402
# since objectui#9499 — the `Test` aggregator reports for all of them, and
403403
# it is required and subscribes `merge_group` — so the verdict still
404404
# blocks a queue build, through one name instead of N. This step reaches the same verdict sooner

‎.github/workflows/test-shard-timings-refresh.yml‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# fetched "the newest timings artifact" at their own start could straddle a
2222
# refresh, and two legs on different weights run some files twice and others
2323
# never, with every leg green. The committed file also makes a partition move a
24-
# reviewed, reproducible event -- `pnpm test --shard=3/12` on a laptop runs the
24+
# reviewed, reproducible event -- `pnpm test --shard=3/16` on a laptop runs the
2525
# same files CI's leg 3 ran -- and the dataset is in the root config's import
2626
# closure, so the pull request that moves it runs the full suite on the new
2727
# split before it lands (`scripts/select-affected-tests.mjs`).
@@ -51,13 +51,22 @@
5151
#
5252
# `scripts/test-file-timings.mjs select`: the newest completed `CI` runs on
5353
# `main` from the full battery's events (`push` and the hourly `schedule`,
54-
# objectui#12214) whose twelve `Test (shard N/12)` legs all reported success
55-
# and whose twelve `test-file-timings-N` artifacts are all present; at most
54+
# objectui#12214) whose sixteen `Test (shard N/16)` legs all reported success
55+
# and whose sixteen `test-file-timings-N` artifacts are all present; at most
5656
# six, and a file keeps the median of the runs that ran it. When no run
5757
# qualifies -- before the first full battery has uploaded, or after a week of
5858
# red -- it prints NOT MEASURED, writes nothing and exits green, because
5959
# nothing failed; a NOT MEASURED that persists is the finding.
6060
#
61+
# The `--shards` it selects by is the `test` matrix's width, and it moves with
62+
# it: a run of another width names other legs (`Test (shard 1/12)` is not
63+
# `Test (shard 1/16)`), so it is never eligible, and a stale value here selects
64+
# nothing forever -- the silent NOT MEASURED above, not a red run. Right after
65+
# a widening, the old-width runs are skipped by name and the first full
66+
# battery at the new width that goes green is the first one taken; a refresh
67+
# that fires before it exists keeps the committed file, which the sequencer
68+
# reads per file and never by width.
69+
#
6170
# ## The pull-request leg
6271
#
6372
# A pull request that edits this workflow or the scripts it runs executes it
@@ -119,10 +128,10 @@ jobs:
119128
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
120129
run: |
121130
status=0
122-
node scripts/test-file-timings.mjs select --shards 12 --limit 6 > "$RUNNER_TEMP/runs.json" || status=$?
131+
node scripts/test-file-timings.mjs select --shards 16 --limit 6 > "$RUNNER_TEMP/runs.json" || status=$?
123132
echo "status=$status" >> "$GITHUB_OUTPUT"
124133
if [ "$status" -eq 3 ]; then
125-
echo "::warning title=Test shard timings::NOT MEASURED: no completed full-battery run on main has all twelve test shards green and all twelve timing artifacts. scripts/test-shard-timings.json is untouched."
134+
echo "::warning title=Test shard timings::NOT MEASURED: no completed full-battery run on main has all sixteen test shards green and all sixteen timing artifacts. scripts/test-shard-timings.json is untouched."
126135
exit 0
127136
fi
128137
if [ "$status" -ne 0 ]; then

‎AGENTS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,10 @@ export const SchemaRenderer = ({ schema }: { schema: BaseSchema }) => {
187187
```bash
188188
pnpm exec vitest run packages/<pkg>/src/<file>.test.ts # 只跑一个文件
189189
pnpm exec vitest run packages/<pkg>/ # 只跑一个包
190-
pnpm test # 全量(CI 就是它,可加 --shard=1/12)
190+
pnpm test # 全量(CI 就是它,可加 --shard=1/16)
191191
```
192192

193-
`--shard=K/N` 在本地也经根 config 的 `sequence.sequencer`(`scripts/test-shard-sequencer.mjs`)分腿:按 `scripts/test-shard-timings.json` 里实测的每文件耗时,不按路径哈希,开跑前打印一行 `test-shard-sequencer:` 读数。分配只由文件集、那份 timings 与 N 决定(`scripts/test-shard-partition.mjs` 文件头),所以 N 取 CI 某个 matrix 的宽度时,同一 checkout 上不带路径的 `pnpm test --shard=K/N` 跑的就是那个 matrix 全量运行时第 K 腿的文件;带了路径,文件集变了,分配也跟着变。N 以 `ci.yml` 为准 —— `test` 与 `test-coverage` 两个 matrix 各自声明宽度;示例里的 `12` 是写下时 `test` matrix 的读数,本文件不复核它(诫条 #9)。
193+
`--shard=K/N` 在本地也经根 config 的 `sequence.sequencer`(`scripts/test-shard-sequencer.mjs`)分腿:按 `scripts/test-shard-timings.json` 里实测的每文件耗时,不按路径哈希,开跑前打印一行 `test-shard-sequencer:` 读数。分配只由文件集、那份 timings 与 N 决定(`scripts/test-shard-partition.mjs` 文件头),所以 N 取 CI 某个 matrix 的宽度时,同一 checkout 上不带路径的 `pnpm test --shard=K/N` 跑的就是那个 matrix 全量运行时第 K 腿的文件;带了路径,文件集变了,分配也跟着变。N 以 `ci.yml` 为准 —— `test` 与 `test-coverage` 两个 matrix 各自声明宽度;示例里的 `16` 是写下时 `test` matrix 的读数,本文件不复核它(诫条 #9)。
194194

195195
AGENTS.md 的「只跑受影响的包」指的是**用上面的路径过滤缩小范围**,不是 `cd` 进包里。
196196
`pnpm --filter <pkg> test` 与 `turbo run test` **不再静默假绿**(objectui#3240):每个包的

‎content/docs/guide/ci-cd-pipeline.md‎

Lines changed: 6 additions & 6 deletions
Large diffs are not rendered by default.

‎scripts/__tests__/check-eager-closure-budget.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1898,7 +1898,7 @@ describe('main', () => {
18981898
* purpose. `docs-route-eager-closure.yml` and `performance-budget.yml` are
18991899
* not among this repo's required merge-queue contexts, so a regression in
19001900
* the fold would not block a merge through the gate's own job. This file
1901-
* runs inside `Test (shard N/12)`, whose verdict is required through the
1901+
* runs inside `Test (shard N/16)`, whose verdict is required through the
19021902
* `Test` aggregator since objectui#9499 (objectui#9098 landed
19031903
* the same reasoning one card earlier).
19041904
*/

‎scripts/__tests__/check-skill-examples.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import {
5151
*
5252
* Everything here runs on an UNBUILT tree: the fence scanner, the marker
5353
* convention, the JSON dialects, and the wiring. That boundary is not tidiness —
54-
* this suite runs inside `ci.yml`'s `Test (shard N/12)` jobs, which do not build
54+
* this suite runs inside `ci.yml`'s `Test (shard N/16)` jobs, which do not build
5555
* the workspace, and a test that needed `dist/*.d.ts` would either be flaky or
5656
* would quietly assert nothing there.
5757
*

‎scripts/__tests__/check-test-path-roots.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*
55
* ## Why the enforcement lives HERE and not only in a workflow step
66
*
7-
* Every test file under `scripts` runs inside `Test (shard N/12)`, reported by
7+
* Every test file under `scripts` runs inside `Test (shard N/16)`, reported by
88
* the `Test` aggregator (objectui#9499), which is a
99
* REQUIRED context and subscribes `merge_group` — so the scan below is what
1010
* actually blocks a queue build. The `Lint` step added alongside it is a second, faster

‎scripts/__tests__/dependabot-merge-gate.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ describe('the declared buckets partition what a pull request actually produces',
582582
it('found the workflows and the shard matrix (the parser still parses)', () => {
583583
expect(workflows.map((w) => w.file)).toContain('ci.yml');
584584
expect([...produced.keys()]).toEqual(
585-
expect.arrayContaining(['Test (shard 1/12)', 'Test (shard 12/12)', 'Test', 'Lint', 'dependabot', 'label']),
585+
expect.arrayContaining(['Test (shard 1/16)', 'Test (shard 16/16)', 'Test', 'Lint', 'dependabot', 'label']),
586586
);
587587
// No unexpanded template survived the matrix expansion.
588588
for (const name of produced.keys()) expect(name).not.toContain('${{');

‎scripts/__tests__/select-affected-tests.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,8 @@ describe('ci.yml wires the selector fail-open (objectui#12214)', () => {
344344
});
345345

346346
it('keeps the matrix width and the full-suite command for the full battery', () => {
347-
const run = code.slice(code.indexOf('- name: Run tests (shard ${{ matrix.shard }}/12)'));
348-
expect(run).toContain('pnpm test --shard=${{ matrix.shard }}/12');
349-
expect(run).toContain('pnpm exec vitest run --shard=${{ matrix.shard }}/12 "${filters[@]}"');
347+
const run = code.slice(code.indexOf('- name: Run tests (shard ${{ matrix.shard }}/16)'));
348+
expect(run).toContain('pnpm test --shard=${{ matrix.shard }}/16');
349+
expect(run).toContain('pnpm exec vitest run --shard=${{ matrix.shard }}/16 "${filters[@]}"');
350350
});
351351
});

0 commit comments

Comments
 (0)