Skip to content

ci(test-shards): place Test Core bins by predicted wall, and cut the CLI into 4 slices on the refreshed dataset - #25968

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-lvcw23
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-lvcw23

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Part of #25870. This PR carries two steps: (1) wall-based bin placement, and (2) the refreshed dataset with the CLI cut into 4 slices. #25870 stays open, because its acceptance is measured on 3 consecutive spec-touching PRs after landing. The prediction below is not that measurement.

Supersedes #25960. It carries the shard-timings-refresh lane's commit 92343f5 (chore(ci): refresh the Test Core shard-timings dataset) unchanged. That refresh cannot land alone, because pin 3c refuses it at 3 slices (step 2).

Diagnosis — which of the three, with run ids

The long shards are always the three that carry a @objectstack/cli file-level slice. Read from gh api …/runs/N/jobs step timings on 56 pull_request runs (2026-10-25 18:54Z – 2026-10-26 05:44Z), and from the slice leg's own Duration / turbo Time: lines on the 18 slice-shard jobs of six packages/spec-touching runs.

The slowest shard on a spec-touching PR breaks down like this (minutes):

run (spec PR) slowest setup closure whole-package leg cli slice leg (turbo Time:)
40274911131 (#25903) 5/8 28.8 ~1.5 6.0 5.7 15.4
40271593540 (#25944) 4/8 28.1 ~1.5 5.5 3.4 17.3
40269339195 (#25813) 4/8 28.5 ~1.5 5.6 3.4 17.5
40266487909 (#25903) 5/8 28.5 ~1.5 5.9 5.6 15.4
40263851511 (#25813) 3/8 26.8 ~1.5 6.3 4.5 14.3
40261969569 (#25830) 3/8 25.7 ~1.5 6.0 4.3 13.6

(whole-package leg = Run this shard's tests minus the slice leg's turbo Time:.)

Three causes, all real, in this order of size:

  1. The cli slice leg itself, 13.6–17.5 min. vitest cuts the CLI by file COUNT (BaseSequencer.shard: sha1 of the path, then equal-count chunks), so the three slices are not equal: across the six runs they measured 518–1047 s, the heaviest ~1.27× the mean (~820 s). Total cli 2406–2542 s against a pinned 2899 s (0.83–0.88×).
  2. The dependency closure, 5.5–6.3 min, against 0.4–1.0 min on shards that skip declarations. @objectstack/cli is in ci.yml's DTS_READERS, so every slice shard builds the whole cli closure WITH .d.ts, and a PR run reads no remote cache, so that build is cold (ci.yml: local cold closure 5m59s with declarations, 1m24s without).
  3. A whole-package leg of 2.2–5.7 min run before the slice — the partitioner's doing. partition() balanced bins by their SUM of predicted seconds. A slice runs serially after the four-wide whole leg, so the step is ~ wholeSum / 2.9 + slice (the ci(test-shards): widen Test Core from 6 to 8 shards once @objectstack/spec is file-sharded — the "6 is enough" arithmetic predates the cli slices (full-battery slowest shard ≈ 1937 s → ≈ 1452 s) #25376 wall model). Balancing sums filled every slice shard up to the mean with ~900 s of whole packages: predicted step 1277 s on the three slice shards against 643 s on the other five (committed dataset, full list).

Stale weights are not the cause: on the refreshed dataset (#25960, the shard-timings-refresh lane's 2026-10-26 output) the sum-placed split still predicts 1107 s on the slice shards against 518 s elsewhere — the same 2.1× gap.

Step 1: wall-based placement (cause 3)

In scripts/partition-test-shards.mjs, with no workflow or package edit:

Predicted test steps, full list (turbo ls, dogfood excluded), via planRun():

dataset before (slice shards / others) after (slice shards / others)
committed (2026-10-23) 1277 s / 643 s 966 s / 830 s
#25960 refresh (2026-10-26) 1107 s / 518 s 899 s / 642 s

Measured-based expectation for the six spec runs above: removing the whole-package leg takes the slowest shard from 25.7–28.8 min to ~22.3–25.1 min. The other five shards absorb the moved packages (their slowest was 17.5 min; +~2.5 min of four-wide work each).

Step 2: the refreshed dataset, and the CLI at 4 slices

The refresh (92343f5, 14 runs, newest 40272142430) measures @objectstack/cli at 2697.45s. Pin 3c (sliceCountProblems, the serial-floor derivation of #22075) then refuses 3 slices:

n one slice vs the heaviest other serial task (@objectstack/metadata-protocol 670.12s; spec and runtime run as two tasks) limit 1.3x = 871.16s
3 899.15s 1.34x refused: "Raise it to 4" (this is #25960's red, job 121437007857)
4 674.36s 1.01x meets, and 3 does not, so 4 is the smallest

So the change is FILE_SHARDED_PACKAGES cli 3 → 4, with PREVIOUS_FILE_SHARDED_PACKAGES set to the outgoing {cli: 3} per the map's own rule, so the refresh lane still decodes the 3-slice summaries retained from before the change. The docblocks carry the new derivation table. The bin-sum floor and the wall model agree on this dataset, and no ratio or bound moved. Predicted test-step walls on the full list are 738s on all 8 shards (max/mean 1.00x), and every bin is within the density cap.

Predicted from measurement, not from the model. vitest's BaseSequencer.shard (sha1 of the path relative to the package root, sorted, equal-count chunks) reproduced locally over vitest list --filesOnly (506 files) matches run 40274911131's three slice jobs file for file: 169/169/168 files on jobs 121415631670/…679/…708. From those jobs' per-file times, with the wall taken as 0.39 × the per-file sum (vitest's Duration over its tests sum on the same jobs: 0.403/0.381/0.390):

split predicted slice walls measured (vitest Duration) heaviest / mean
3 ways 789 / 691 / 926 s 812.65 / 673.01 / 921.52 s 1.15
4 ways 487 / 616 / 673 / 630 s — 1.12

The heaviest slice falls 27%. Applied to the six spec runs (slowest = setup ~1.5 + that run's cold DTS closure + ~1.1 min of whole leg, the wall model's 738 − 674 s, + 0.727 × that run's heaviest slice):

run (spec PR) slowest before predicted after both steps
40274911131 (#25903) 28.8 19.8
40271593540 (#25944) 28.1 20.7
40269339195 (#25813) 28.5 20.9
40266487909 (#25903) 28.5 19.7
40263851511 (#25813) 26.8 19.3
40261969569 (#25830) 25.7 18.5

That gives 18.5–20.9 min, with three of six at or under 20. The cost: a fourth shard now builds the CLI's DTS closure (~5.8 min cold). The 738s walls above count test time only, not that build. The per-file read of 5/8 lacks 11 files that print before the 5,000-line log tail; their 172.2s (vitest tests minus the captured sum) is spread evenly over them.

If the readings stay above ~20 min

What is left on a slice shard is the cold DTS closure (5.5–6.3 min) plus the heaviest slice. The next steps, neither taken here:

  • B. Declarations only where a slice reads them. Three CLI test files read built .d.ts. With the split now reproducible, a slice holding none of them could build DTS-off (~1 min instead of ~5.8). This edits ci.yml, so it is Tier H.
  • C. Weight-balanced CLI slices (a sequencer splitting by measured file time): heaviest/mean 1.12 → ~1.0. It needs a per-file weight source that the dataset does not carry today.

Acceptance notes

  • Deliberate leftovers in .github/workflows/ci.yml (Tier H, untouched): the Test Core header still says each slice is "placed at its PLAIN weight so every bin keeps the whole-package split's density", and its "3 slices" wording (the drift-step comment, "a shard carrying one of its 3 slices is predicted a third of that"; the timeout history) is now stale. Correcting either makes a PR Tier H, so both are left for whichever step edits ci.yml. The partitioner's own headers carry the corrected statements.
  • Slice shards now come out as shards 1–4 on the full list (LPT places the heaviest loads first). Shard numbers carry no meaning beyond the per-shard Turbo seed key.

Tests

  • node scripts/partition-test-shards.mjs --self-test at b323410 → self-test OK (75 measured packages -> 78 shard items, 8 shards, predicted test-step walls 738/738/738/738/738/738/738/739s, max/mean 1.00x <= 1.3x, …, file-level slices: @objectstack/cli x4).
  • Step 1 revert proof (scripts/ablation-replace.mjs, bin choice back to total, on the committed dataset): the self-test reds with balance: at 8 shards the slowest predicted test step is 1.45x the mean (1277s vs 881s). Restored blob == HEAD, and git diff HEAD is empty.
  • Step 2 revert proof: the refreshed dataset with cli at 3 reds pin 3c with @objectstack/cli: sliced 3 ways at 2697.45s, one slice is 899s -- past 1.3x the heaviest other serial task (670s, so 871s) … Raise it to 4. (the same red as chore(ci): refresh the Test Core shard-timings dataset #25960's job 121437007857).
  • scripts/test-shard-timings.json is byte-identical to 92343f5 (git diff 92343f5b b3234103 -- scripts/test-shard-timings.json is empty).
  • Sibling self-tests are green at b323410: measure-test-shard-timings (decodes through both maps), check-test-completeness, ci/select-shard-timings-run, nightly-tiers, check-test-suite-ceilings, ci/select-shard-packages.selftest.sh.

Gates

  • Local derived set at b323410 (node scripts/pm/dispatch-gates.mjs --commands, merge-base origin/main cdd4eea): all 35 families exit 0. The --ran reconciliation reads 35 derived famil(ies) accounted for — 35 run, 0 NOT-MEASURED. Step 1 alone (8ca31e0) ran its 33 families the same way.
  • Everything else (Lint & Repo Gates, Test Core, …) belongs to CI and is read from this PR's check-runs.

Clause-②: no
Metadata-protocol: none

🤖 Generated with Claude Code

https://claude.ai/code/session_01NPH84ZP1jWcst3tpRwfrJL


Generated by Claude Code

…ce shard carries no whole-package leg ahead of its serial slice

A shard runs its whole packages four at a time and then each file-level slice
alone, so its tests step reads ~wholeSum / 2.9 + slice (the #25376 wall model).
partition() balanced plain sums, which filled every slice shard up to the mean
with whole packages that ran before the slice. It now places by load (a slice
counts 2.9x its weight); a split with no slice is placed byte-identically.

Claude-Session: https://claude.ai/code/session_01NPH84ZP1jWcst3tpRwfrJL
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 26, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

Verdict: ACCEPT at head ec177ae84c75d8991ce06b0b4008cb97344902ae. This lands the cheapest step only. #25870 stays open for the remaining gap.

Checklist

Gate readings at head ec177ae

  • 34 success, 16 skipped, 0 failing.
  • Lint & Repo Gates, TypeScript Type Check, Test Core (all 8 shards) and Governed Surface Queue Guard are success.
  • The skipped checks are UI and opt-in jobs filtered out by path.

Seat's own readings, in a detached worktree at ec177ae

  • node scripts/partition-test-shards.mjs --self-test → self-test OK (… predicted test-step walls 966/966/966/830/830/830/829/829s, max/mean 1.10x …).
  • Revert proof: I set the bin choice back to total, and the self-test fails with balance: … slowest predicted test step is 1.45x the mean (1277s vs 881s). The worktree was restored afterwards, with an empty diff.
  • The PR says a list with no slice gets a byte-identical split. I tested that with 900 random slice-free lists (5–80 packages each, at 3, 6 and 8 shards), comparing partition() at the head against origin/main. 0 lists differed.
  • Diagnosis: the PR body has the per-run table (6 spec-touching runs with ids). Stale weights are ruled out by numbers from the chore(ci): refresh the Test Core shard-timings dataset #25960 refresh dataset, which the card asked for.

Deviations / follow-ups (named, not fixed here)


Generated by Claude Code

claude and others added 3 commits October 26, 2026 07:24
Regenerated by .github/workflows/shard-timings-refresh.yml from the test-core-run-summary artifacts of 14 accumulated run(s) (40272142430 40266979145 40262173089 40257470166 40242835329 40238189261 40233481335 40228747755 40223991179 40219252911 40214410982 40209651657 40204927516 40200279306), newest 40272142430 at 2a728ba. Generated, never hand-edited.
…he 2026-10-26 refresh

The refreshed dataset measures @objectstack/cli at 2697.45s. At 3 slices one
slice is 899s, past 1.3x the heaviest other serial task
(@objectstack/metadata-protocol, 670.12s, so 871s), and pin 3c refuses the
refresh. At 4 a slice is 674s and meets; 3 does not, so 4 is the smallest.

PREVIOUS_FILE_SHARDED_PACKAGES carries the outgoing {cli: 3}, so the refresh
lane still decodes the 3-slice summaries retained from before this change.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPH84ZP1jWcst3tpRwfrJL
@github-actions github-actions Bot added size/l and removed size/m labels Oct 26, 2026
@objectstack-fleet objectstack-fleet Bot changed the title ci(test-shards): place Test Core bins by predicted wall, so a CLI slice shard runs no whole-package leg before its slice ci(test-shards): place Test Core bins by predicted wall, and cut the CLI into 4 slices on the refreshed dataset Oct 26, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 (delta re-review)

Verdict: ACCEPT at head b32341039f093b50bbe98b7f38ae7e568921cb07. This replaces the ACCEPT given at ec177ae8. Step 1 (wall placement) is unchanged since that review. This review covers step 2 only.

The delta

Gate readings at head b323410

Seat's own readings at b323410, in a detached worktree

  • --self-test → self-test OK (… 78 shard items, 8 shards, predicted test-step walls 738/738/738/738/738/738/738/739s, max/mean 1.00x …, file-level slices: @objectstack/cli x4).
  • Revert proof: with cli set back to 3, pin 3c turns red with the same text chore(ci): refresh the Test Core shard-timings dataset #25960 showed in CI (sliced 3 ways at 2697.45s, one slice is 899s -- past … 871s … Raise it to 4.). The worktree was restored afterwards, with an empty status.
  • This also clears chore(ci): refresh the Test Core shard-timings dataset #25960's red "Shard partitioner self-test" (Lint · CI, release & engine gates), because the refreshed weights cannot land at cli ×3.

Still open on #25870 (named, not done here)

  • The PR predicts the slowest shard at 18.5–20.9 min on the six spec runs it lists. The prediction comes from a reproduction of vitest's sha1 split, file for file, against run 40274911131. The card's acceptance is a measurement, though: 3 consecutive spec-touching pull_request runs after landing.
  • The ci.yml Test Core header ("PLAIN weight", "3 slices") is stale. It is a deliberate leftover, because correcting it makes a PR Tier H.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 26, 2026 08:07
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 26, 2026 08:07
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit f36f619 Oct 26, 2026
54 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-lvcw23 branch October 26, 2026 08:35
os-zhuang pushed a commit that referenced this pull request Oct 26, 2026
…6-10-23 dataset (undo #25968) (#26077)

<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D5ZEhycrEz6EHUjW1whANXQ)_

Fixes #26074

Clause-②: no
Metadata-protocol: none

## What

This is a clean revert of f36f619 (#25968). It changes two files and
nothing else: `scripts/partition-test-shards.mjs` goes back to plain-sum
bin placement, and `scripts/test-shard-timings.json` goes back to the
2026-10-23 dataset with cli in 3 slices. Neither file has a later commit
on `main`. No other file imports the exports this removes
(`WHOLE_LEG_PARALLELISM`, `loadOf`, `wallOf`).

## Why

Since the first queue build on the #25968 layout (08:16Z), full Test
Core `merge_group` builds have gone red 5 times in 27. On the old
layout, 05:00–08:10Z, they went red 0 times in 19. Every red ejects its
PR and rebuilds every entry queued behind it, so throughput fell from
12–13 merges an hour to 6. The failing tests are all load-sensitive:

- objectql `src/hook-run-as.test.ts` (cold `import('./index.js')`): Test
timed out in 5000ms
- plugin-security
`src/objects/shipped-sets-identity-default-row-scope.test.ts`: Test
timed out in 5000ms
- cli `test/hook-timeout-override-refusal.test.ts` CONTROL: the `vitest
list` child exits 1

The heads that failed passed when they went back in unchanged.

The #25870 thread replayed `planRun()` on both trees. Before, five
whole-package shards carried about 1866 s of four-wide work each, and
plugin-security sat on a cli-slice shard with 899 s. After, four
whole-package shards carry about 2141 s each, and objectql and
plugin-security share one shard. The suite-duration ceiling annotations
show the effect: plugin-security reads 605–611 s against a ceiling of
474, plugin-auth 617–928 s against 600, driver-sql 603–612 s against
451, and objectql 1102 s against 952.

## Cost and rollback

The slowest spec-PR shard goes back to about 25–29 min, from about 20.
#25870 stays open for a placement that takes contention into account.
Rollback means re-landing f36f619 as it is.

## Verification

- `node scripts/partition-test-shards.mjs --self-test`: OK. 75 measured
packages, 77 items, 8 shards, max/mean 1.00x, bins about 1866 s, all
within the 2899.02 s density cap, cli x3.
- `node scripts/check-commit-card-trailers.mjs --range
origin/main..HEAD`: pass.
- The other gates are left to this PR's own CI run.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01SrpCjPfYv8N9P9EJvoDyN9

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants