ci(test-core): carry the shard slice in a task env so the slice leg's hash sees its upstream closure (#19278) - #20824
Conversation
turbo.json declares OS_TEST_SHARD on the test task (generic and @objectstack/cli#test); packages/cli/vitest.config.ts reads it into vitest's shard option. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…ring The slice leg now runs OS_TEST_SHARD=k/n turbo run test --filter=PKG with no passthrough, so its test hash carries the build closure again and the closure replays from the preceding build step. The cli config reads the variable at its use site, typed against vitest's CLI-options type. partition-test-shards --self-test gains a battery that fails a sliceable package whose turbo test task or vitest config does not carry the variable. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…aries samplesFromSummary resolves the sha256 digest turbo records for OS_TEST_SHARD against the k/n the partitioner can emit for that package, keeps the cliArguments (passthrough) path, and refuses a digest that matches no candidate instead of reading the window as a whole-package sample. Comments that described the passthrough carrier are made true. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check
What this run could not see
Coarse fallback — 25 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
…closure Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
…which are controls Ablating the digest path (sliceOfLeg never consulting OS_TEST_SHARD) reds seven of the env-carried-slice cases; the passthrough, empty-value and unrelated-variable cases stay green by design. The battery comment claimed every case fails that way. Comment only. Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
…closure Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh Co-authored-by: Claude <noreply@anthropic.com>
Seat review:
|
Fixes #19278
Clause-②: no
Seat review ACCEPT on the landing head
e00136407: comment5909822071. No contract-review record is owed, because the diff touches no review face andClause-②: no(contract-review.md).What it changes
Test Core's file-level slice leg (
.github/workflows/ci.yml) ranpnpm turbo run test --filter=$PKG --only --force … -- --shard=$SLICE.--onlydrops the dependency tasks, so the slice's task hash sees only the package's own files and the hand-declared$TURBO_ROOT$inputs. An upstream change does not move it.--force(PR ci(test-core): force the sliced test leg to execute, not replay #19271) keeps such a slice from being replayed from cache, but the hash stays blind. That is [finding] 分片腿的任务哈希仍不携带上游闭包 —— #19271 的--force修好的是「执行」,根因是「哈希」,而恒真的绕过是一条不会再报警的电线 #19278.This branch carries the slice in a task-declared env instead:
turbo.jsondeclaresOS_TEST_SHARDontestand on@objectstack/cli#test. A package-specific task replaces the generic one, so both need it.packages/cli/vitest.config.tsreads it into vitest'sshard, typed through vitest's CLI-options type. A literaltest.shardfails typecheck with TS2769.env OS_TEST_SHARD=$SLICE pnpm turbo run test --filter=$PKG …, with no passthrough, no--onlyand no--force.scripts/partition-test-shards.mjs --self-testgains a battery. It fails any package the partitioner can slice whose turbo task or vitest config does not carry the variable.scripts/measure-test-shard-timings.mjsreads an env-carried slice back from the run summary'senvironmentVariables.configureddigest, and keeps thecliArgumentspath that nightly-tiers still uses. Without this, a timings refresh would record one slice as the package's whole cost (CI: the shard-timings file is stale for the CLI package — 672s predicted vs 28m46s measured against a 30-minute timeout, so Test Core shard 1/6 is one slow run from being killed on any PR touching the CLI #16173's defect).ci.yml, andreport-test-timings.mjs.Measured (round 1, the dev's report
5907562928on #19278, turbo 2.10.10, vitest 4.1.11)cli#testhash after an upstream edit inspec/typescliitself--forcecache hit, replaying logs(not run)cache miss, executing(runs)--onlyleg)Evidence on the final head
e00136407d(handover steps 1 to 4)Final head:
e00136407d04e3b2871b30a9c6463a2f9f108f6c, a merge oforigin/main4b45afaed5. The six paths are byte-identical to9df0b71851, except one comment block inscripts/measure-test-shard-timings.mjs(099e5ff641, comment only).1. The reader's new self-test cases, ablated
scripts/ablation-replace.mjs: the anchor hit 1 then 0, the blob moved, the file was restored to the HEAD blob, andgit diff HEADwas empty afterwards. A shell trap also guarded each run.453a072feedbon the final head. The same table was measured first oncb42313d89e6(9df0b71851) and gave the same result.--self-test, its exit code and its first red;--self-testenv-carried slices (#19278)sliceOfLegnever readsOS_TEST_SHARD)FILE_SHARDED_PACKAGES@objectstack/cli2/2cliArgumentscarrier is droppedOS_TEST_SHARD=prefixgit diff HEADis empty.099e5ff641). The battery comment said every case fails when the digest path is dropped. A0 shows that cases 3, 7 and 8 are controls that pass either way, and the comment now says so.2. A real new-leg summary through the pipeline
OS_TEST_TIERS=queue env OS_TEST_SHARD=k/2 pnpm turbo run test --filter=@objectstack/cli --summarize --log-order=stream.--concurrency=2,VITEST_MAX_WORKERS=2and a private--cache-dir. None of them enters a task hash.95b97ef2e. Its six paths and everypackages/clifile are identical to the final head's.cache miss, executing 90ddc44dcc24b458; 60 tasks, 59 cached; 152 files; 2069 tests passed and 1 skipped. In the summary,cliArgumentsis empty, andenvironmentVariables.configuredholdsOS_TEST_SHARD=d939926f…(the sha256 of1/2) andOS_TEST_TIERS=00b109cf….cache miss, executing 52f80464736e668b; 151 files; 1865 tests.Test Core (6/6)on9df0b71851(job 109819361192, whose log was read through the MCP job-log tool):@objectstack/cli:test: cache miss, executing 874a69822b22ac22, 60 tasks, 59 cached, 151 files, 1865 tests. That is the same slice with the same counts.origin/main810d42b69creport-test-timings.mjs, with--capturerun twice and then--merge:origin/mainscript prints 644.41 s as cli's total, with no note.partition-test-shards.mjs --check-drift:origin/mainscript predicts 458.1 s, the whole package, and reports OK at 1.41x. It compares one slice against the whole package's weight.OS_TEST_SHARDdigest in slice 1/2's summary was flipped:measure-test-shard-timings.mjsexits 1, with the refusal naming the candidates1/2, 2/2, and writes no dataset.--check-driftexits 1 with the same refusal.report-test-timings.mjsexits 0, as designed, because it may not fail the job it reports on. It lists the refusal under "Problems while reading this run" and shows cli as "slice 2/2 — PART, not a total". It never shows cli as a whole sample.3. The merge of
origin/main95b97ef2emerged main at810d42b69c, ande00136407merged main at4b45afaed5.dispatch-gatesflagged the first derivation as stale. Main had changedscripts/pm/fleet-write/dispatch.mjs, one of the files the derivation reads.22e584c9d. Neither of the two new commits touches the six paths or anything the derivation reads, and the derivation says so.4. Gates and the card's acceptance, on
e00136407Gates.
dispatch-gates --commands --repo objectstack-ai/objectstackderived 89 commands, and all 89 were run.check:dual-build-cjs-loadsfirst exited 3 with PREREQUISITE NOT MET: nine packages outside cli's closure had nodist. Those nine were built and the gate re-run: exit 0. The four otherdistreaders (dts-closure,lean-entry-closure,published-files,sourcemap-no-sources-content) were re-run on the fuller tree: exit 0.check:pm-dispatch-gatesran detached: exit 0, 1976 cases, 1058.8 s.✓ dispatch-gates --ran: 89 derived famil(ies) accounted for — 89 run, 0 NOT-MEASURED (a DERIVED zero — all 89 recorded an exit code and none of them is 3), with 0 UNRUN.check-shard-attestationemit and verify steps,check-test-completenesson the two CI logs,check-issue-citations --censusandcheck-required-contexts --verify-required-set. They are not in the 89, and CI runs them.pnpm --filter @objectstack/cli typecheck: exit 0;check:test-typecheckOK, holding the same 3 files, 28 errors and 6 pinned signatures.pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: 237 files and 3370 tests passed.95b97ef2e.check-test-completeness.mjs --self-testpassed. That script imports the partitioner.--format json. The config enables no type-aware linting, so this diff cannot change eslint's verdict on any untouched file.Acceptance 2: the hash moves. These are
--dry=jsonhashes ofcli#test. Each edit was a one-line marker, planted and then restored.OS_TEST_SHARD=1/2)--only --force -- --shard=1/2)5fe85e3cd60398b1d26a68b9c44aac59packages/spec/src/ui/view.zod.tsf57477faa9b85239, movespackages/types/src/env.tsb64907aa07f030ad, movespackages/cli/src/utils/one-shot-exit.ts123e600f94cc13c8, moves297d412288a9c65b, movespackages/client/src/index.ts(no$TURBO_ROOT$input names it)32e74945bc85d7e1, movesturbo run build --filter=@objectstack/cli, so the closure replays.6a3fe12522c74bb9(on099e5ff641) to5fe85e3cd60398b1. The old leg stayed atd26a68b9c44aac59.Acceptances 1 and 3: an affected slice executes without
--force. The runs used cli slice 1/16 for speed; turbo's replay decision is a hash lookup and does not depend on k/n.OS_TEST_TIERS=queuewas set throughout.cache miss, executing 8cc2f107d21814cf, thencache hit, replaying logs 8cc2f107d21814cfin 86 ms. That replay is legitimate, because nothing changed.--only, no--force), unchanged final tree:cache hit, replaying logs dc666be1735ca19a. That cache entry was written on099e5ff641, before the second merge brought in upstream changes; the closure rebuild after that merge executed 58 of 59 builds. This is the defect, reproduced by a real merge.packages/client/src/index.ts, followed by CI's closure rebuild (client#buildandcli#buildexecuted; 57 of 59 were cached):--force:cache hit, replaying logs dc666be1735ca19ain 118 ms. The slice was not run.--force:cache miss, executing 61f0a4c75daa610c, 19 files passed. The slice ran.099e5ff641with the real partition slice 1/2:90ddc44dcc24b458;cache miss, executing 2df7b9311ca50a57; 152 files; 2069 tests passed and 1 skipped; 10m22.8s.git diff HEADwas empty.Changeset disposition:
skip-changeset, applied by the seatCheck Changesetone00136407fails at "Require a changeset (or the skip-changeset label)". The PR adds no changeset and has no label. It lacks a disposition; the gate did not misread anything.check-empty-changeset.mjs --base origin/main: exit 0. No declaring changeset was added, and no existing changeset was modified or deleted.check-changeset-no-major.mjs --base origin/main: exit 0, no major bump..changeset/*.mdfiles were added against merge base4b45afaed5.packages/clishipsfiles: [dist, README.md, CHANGELOG.md]. Itstsconfig.build.jsoncompiles onlysrc, sovitest.config.tsis test configuration and is never shipped.dist, README and CHANGELOG,OS_TEST_SHARDappears in 0 files. The positive control,exitOneShotCommand, appears in 6 files. There is novitest.config*indist.turbo.json, and scripts of the root package, which is private.Acceptance notes
turbo.jsonedit already makes all 80 packages affected on the landing PR.--onlypassthrough shape and no--force. It is filed separately as [finding] The requiredDogfood Regression Gateshard step runs--onlywith a--shardpassthrough and no--force, so itsdogfood#testhash ignores upstream changes and a main-seeded cache can replay it green #20820.report-test-timings.mjsas a stated problem, not as an exit code. That line names the summary file twice, becausereadSummariesprefixes the entry andsamplesFromSummaryalso labels the error with it. Every refusal that function relays already has this shape. It is left alone.Shard Timings Refreshwent red one00136407(run 36701198568): "candidates existed and none was eligible".select-shard-timings-run.mjs, the workflow, and the reader) are byte-identical to the three earlier heads, where it was green.--force修好的是「执行」,根因是「哈希」,而恒真的绕过是一条不会再报警的电线 #19278.Rounds 1 and 2 were written by session
session_014EJ1ED8X4MMrT18BhVx4tx. The evidence above, the second merge and the comment fix are by sessionsession_01TdiauJaVCHuj45EzZGUxHh, thedomain:devxseat 2 dev, on 2026-09-30.