Repository navigation
ci(test-core): retire the CLI's file-level slicing by the partitioner's own slice-count derivation - #21487
Merged
Conversation
…'s own derivation, and re-point the slicing pins at fixtures The refreshed shard-timings dataset (run 36380128221) moves the mean, not only the CLI: @objectstack/spec at 1391.38s is the heaviest item, and with the CLI sliced at 2, whole as measured (733.33s), or whole at its worst reading (1231.52s), the split is 1.124x / 1.124x / 1.053x of the mean, all inside 1.3x. The smallest slice count that meets the bound is 1, so FILE_SHARDED_PACKAGES is emptied; the mechanism stays. - Pin 3c no longer substitutes the 1231.52s bridge reading for the CLI. It asks the committed dataset, for every map entry, whether n - 1 slices would also meet the bound (sliceCountProblems), with five fixtures that hold each refusal in both directions. - Pin 3b grades the real split and a cut of the dataset's heaviest package, so it is not vacuous while the map slices nothing. - The mechanism's pins (vitest floor, slice-share prediction, drift on a sliced package, weighItems slicing, the OS_TEST_SHARD wiring reader) read a fixture map through new optional `sliced` parameters. - Pin 6's inversion pair flipped in the refresh (example-todo 35.30s vs core 57.09s); the new pair is plugin-pinyin-search over sdui-parser, and a guard now reads the dataset's order so a flipped pair asks for a new pair instead of reporting a weighing defect. - measure-test-shard-timings.mjs: its live-map case holds for an empty map. Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w Co-authored-by: Claude <noreply@anthropic.com>
…ange replaced, so the refresh lane survives the retirement
The refresh lane's pull_request rehearsal on this branch (run 37074888579)
refused all ten eligible hourly runs on main and regenerated nothing. Every
retained run summary carries the CLI's slices as an OS_TEST_SHARD digest
of 1/2 or 2/2, and with FILE_SHARDED_PACKAGES emptied the generator's
digest matcher had no candidate for either, so it refused each summary
whole.
- partition-test-shards.mjs declares PREVIOUS_FILE_SHARDED_PACKAGES, the
outgoing map ({ '@objectstack/cli': 2 }), with when it decodes nothing:
run summaries are kept for one day.
- sliceOfEnvironment matches a digest against both closed maps, exactly by
hash. A decoded slice is still summed within its run (an incomplete set
contributes nothing), and a digest neither map names is still refused.
- measure self-test: the live-maps case covers both defaults, and a new
case reads a pre-change run's two halves into its 733.33s whole while
refusing a count neither map names. Battery floor 10 to 11.
Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Oct 3, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 7, 2026
…red/predicted, warning past 1.3x (objectstack-ai#21998) Fixes objectstack-ai#16465 Clause-②: no Wires the Test Core shard timing-drift check the tree has carried unwired since objectstack-ai#16173, adds the card's 1.3x as a warning tier under its red, and restates the `ci.yml` comments that the wiring and the objectstack-ai#21487 / objectstack-ai#21826 changes left stale. Root `scripts/` and one workflow, nothing published: `skip-changeset`. ## The ruling this follows Triage `5925054826`, as the unlock `6013064225` restates it, verbatim: - "one red rule, the in-tree `partition-test-shards.mjs --check-drift` at `MAX_MEASURED_OVER_PREDICTED = 1.5`;" - "the card's 1.3× is a `::warning::` only;" - "⛔ no second red rule, so no 70%-of-`timeout-minutes` red." The maintainer's authority on the card: 「同意你的建议,你负责执行派发所有可行的优化」. ## What changed **`.github/workflows/ci.yml`, the Test Core shard job only** - New step `Check this shard's timing drift`: `--check-drift` over `.turbo/runs/*.json` with `--label "Test Core (N/6)"` (the matrix shard), with no `if:` and no `continue-on-error`. It sits between the run-summary upload and the completeness guard, above the attestation pair, so a drift red also withholds that shard's attestation. A shard with no packages writes no summary; the step reports that as NOT MEASURED and exits 0, because the script itself treats zero inputs as a usage error. - The "⛔ THE DRIFT STEP IS DELIBERATELY NOT WIRED HERE YET" block is replaced by a description of what the step does. The stale CLI figures ("458.15s recorded, 1231.52s measured") and "the CLI is halved across two runners" are gone. The comment now cites the dataset as objectstack-ai#21826 refreshed it (provenance run 37262126122, CLI 1702.69s) and the second sample below. - **WHY SIX** now names `@objectstack/cli` (1702.69s) as the heaviest indivisible suite, ahead of `@objectstack/spec` (1134.86s). The 6/7/8/10-shard table is re-derived on the current dataset with the partitioner's own `partition()` / `balanceOf()`: 1.04x / 1.22x / 1.39x / 1.74x, with the max at 1703s at every count. The old claim that the self-test "pins this arithmetic" is corrected: what it pins is pin 3, the heaviest package within 1.3x of the mean at `SHARD_COUNT`. - **The 45-minute timeout comment.** ⛔ The value stays 45. The revert condition is now stated against measured wall time instead of "once objectstack-ai#16173 lands": back to 30 only when the slowest shard's job wall time stays at or under 24 minutes (80% of 30) on every scheduled run for a week. On the 16 main runs after the objectstack-ai#21826 refresh, shard 1/6 (the CLI alone) read 6m14s to 35m43s of job wall time, and was over 30 minutes on 9 of them (for example 34m39s in run 37415122516 and 35m43s in run 37453598388). Two present-tense "30-minute wall" phrases in the same job now say "`timeout-minutes` wall". - **Slice-leg prose.** The build-closure step and the test step now say that `FILE_SHARDED_PACKAGES` has been empty since objectstack-ai#21487, so no shard runs a slice today. **`scripts/partition-test-shards.mjs`** - `WARN_MEASURED_OVER_PREDICTED = 1.3` sits beside `MAX_MEASURED_OVER_PREDICTED = 1.5`, which is unchanged. It is the same ratio over the same executed windows; it is not `MAX_SHARD_OVER_MEAN`, and its docblock says why the two are kept separate. - `driftReport()` gains `warned`, the band strictly between the two bounds and exclusive of the red, so one shard gets one verdict. - `renderDriftVerdict()` returns the four verdicts (OK, WARN, DRIFT, NOT MEASURED) without printing them. WARN emits exactly one `::warning title=Test Core shard timing drift::…` line on stdout and exits 0; DRIFT exits 1, with the same remedy text as before. `escapeWorkflowCommandMessage()` keeps the message on one line. - New self-test battery `drift warning tier under the red (objectstack-ai#16465)`, with 12 cases, registered in `SELF_TEST_BATTERIES` (floor 12). The roster floor goes from 10 to 11. The cases cover: - both edges of the band, the red's exclusivity and a healthy 1.18x reading; - replay-only input; - band non-empty (`WARN` below the red) and not below the balance tolerance; - the rendered annotation line, the red's exit code, and command escaping. Nothing in the self-test prints a workflow command (`check:self-test-workflow-commands` green). Not touched: the attestation schema, `scripts/test-shard-timings.json`, `measure-test-shard-timings.mjs`, and the required-check names. `Test Core` and `Test Core (N/6)` are unchanged, and `check:required-contexts` is green. ## The second sample (carried item 2): executed windows only The `test-core-run-summary-*` artifacts could not be read from this container: the egress policy denies `productionresultssa17.blob.core.windows.net`. Job logs reach only their last 5000 lines through `get_job_logs`. So the sample comes from each run's `Test Core` timing table, which the aggregator echoes into its log. The table carries turbo's own execution windows through `samplesFromSummary()`, which is the same reader and the same exclusions as the gate. Replayed packages are listed there and excluded here. | shard | reading | source runs | |---|---|---| | 1/6 (CLI alone) | **exact**, 0.61x, 1.05x, 0.65x, 0.78x, 1.02x, 1.03x, 1.01x, 0.79x | the 8 scheduled runs 37416453417, 37427862594, 37433381795, 37440129185, 37446949708, 37453598388, 37460325624, 37467795959 | | 2/6 to 6/6 | **NOT MEASURED whole.** The table prints only the 10 slowest packages, so a shard's lighter members have no window here | same runs | | (small-set push runs) | exact on every shard that ran: 0.90x (spec alone), 0.48x (rest alone), 0.67x (create-objectstack alone); 0.18x (client alone) | 37422473456; 37413386379 | On shards 2 to 6, the measured heavy members and the turbo `--concurrency=4` capacity bound (the sum of a shard's task windows is at most 4x its test step) give these results: - **Proven under 1.5x:** 8 shard-run pairs (for example shard 3 in 37427862594, at most 0.82x). - **Undecided** everywhere else. No measured package read 1.5x or more in any of the 9 near-full runs. - **Highest package reading:** `@objectstack/spec`, 1.45x / 1.39x / 1.43x on the three runs that executed it (37433381795, 37453598388, 37467882762). - **Nearest the bound: shard 2/6.** spec carries 70% of its prediction, so its 11 unmeasured members would have to average 1.61x to 1.77x before the shard reached 1.5x. **Stop condition:** not met. No executed-window reading put any shard at or over 1.5x, so the red is wired as ruled. The per-shard numbers, the bounds and this PR's own run are on objectstack-ai#16465 in the `os-dev-report`. ## Verification (at `430859c2f`) - Derived gates: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran …` printed "59 derived famil(ies) accounted for — 59 run, 0 NOT-MEASURED (a DERIVED zero — all 59 recorded an exit code and none of them is 3)". Every gate exited 0. - Four of them (`check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:sourcemap-no-sources-content`) first answered exit 3 (PREREQUISITE NOT MET). They exited 0 once a workspace build had run through the verify lock: 73 tasks, `VERDICT command-exit 0`. - `node scripts/partition-test-shards.mjs --self-test` → `self-test OK (72 measured packages -> 72 shard items, 6 shards, max/mean 1.04x …)`. - `--check-drift` on synthetic summaries: 1.06x gives OK, exit 0; 1.34x gives WARN plus one `::warning` line, exit 0; 1.54x gives DRIFT, exit 1. A replay sits beside each, and they are excluded. - The step's bash, run against an empty directory, gave NOT MEASURED and exit 0. Against one summary it gave the verdict. - `check-governed-merges --test` on the final two paths: "0 of 2 path(s) hit the register … NOT governed", with 494 changed lines. ## Acceptance notes - **Shards 2 to 6 still lack a whole-shard reading from main.** This step is what produces one. The first scheduled runs after landing print it per shard, and the WARN annotations show where. - **This PR's own Test Core run is a thin sample.** - Its affected set, simulated with `turbo ls --affected` and the cross-package union at the merge base, is `@objectstack/spec`, `@objectstack/client` and `@objectstack/driver-sql`. - spec's `test` leg is expected to replay, which makes it NOT MEASURED. client and driver-sql run alone. - So it exercises the wiring rather than the dataset. - **Expect the warning on shard 2/6 of cold full runs.** spec read 1.39x to 1.45x against its single-run 1134.86s entry. That is the warning doing its job, and the remedy is the refresh lane, not this step. - **Small-sample hazard, observed and not filed.** `driftReport()` has no floor on predicted seconds. A PR whose shard executes only a few-second package gets a ratio of that package alone. Every lone-package reading found here was under 1.0x, so this is noted, not filed. - **The 24-minute (80%) revert criterion is this PR's choice**, stated with its reason in the comment. The current 45 is already at 79% on shard 1/6's slowest reading (35m43s). --- _Generated by [Claude Code](https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 9, 2026
…od and Temporal build steps (objectstack-ai#22086) Fixes objectstack-ai#22077 Clause-②: no The turbo remote cache that objectstack-ai#21186 wired into Build Core is now read by every `turbo run build` step of the three Lint `Type Check` build lanes, the Test Core shards, the dogfood shards and Temporal Conformance. Each carrier gets Build Core's four env lines verbatim: the same write rule (writes only on `merge_group` and on `push` / `workflow_dispatch` against `main`, everything else reads), the same signing, and no new secret. Two files: `.github/workflows/ci.yml` and `.github/workflows/lint.yml`. ## Route that landed: the remote cache, not the artifact fallback Nothing in the remote route failed, so the `dist/` upload-artifact alternative was not needed: - **Hashes.** Each carrier's plan is a subset of Build Core's `turbo run build --filter=!@objectstack/docs` plan, with identical task hashes and identical `globalCacheInputs`. Measured with `--dry=json` on `3d9188502e`, turbo 2.11.5, `CI=true`: | carrier | build nodes (with a command) | same hash as Build Core | |:--|--:|--:| | Type Check `Build workspace packages` (3 lanes) | 70 (67) | 70/70 | | `Build the ledgered packages' dependencies` / `Build the nested packages ...` | 77 (71) | 77/77 | | dogfood `Build the dogfood package's dependency closure` | 66 (63) | 66/66 | | Temporal: driver-sql / non-SQL backends / metadata-protocol / runtime | 8 / 18 / 14 / 31 | all | | Test Core `Build this shard's dependency closure`, shards 1-6 | 60 / 62 / 63 / 62 / 32 / 62 | all | Every plan is 100% `#build` tasks. Filters and `--concurrency` are not part of a task hash. - **Signing and permissions.** These are the same secrets and the same expressions as Build Core. A same-repo PR receives them; a fork PR receives none, so `TURBO_TOKEN` evaluates to `''` and turbo reports `Remote caching disabled` (objectstack-ai#21186 measured this). `TURBO_CACHE` never evaluates to `''`. - **The cache already serves.** On the `main` push run at this PR's base (CI `37627942232`), Build Core's build step took 6 s, because the queue entry for the same tree wrote it. In the same run, the jobs that did not read the remote rebuilt: dogfood took 200 / 201 / 336 s, and in Lint `37627942309` the Type Check lanes took 297 / 330 / 335 s. ## Test Core: a new guarded build step Test Core's existing build step, `Build the sliced package's dependency closure`, runs zero iterations, because no package has been sliced since objectstack-ai#21487. The closure was built inside `Run this shard's tests`. That run cannot read the remote, because `test` / `test:repo` stay off it. Merge-queue run `37623284168`, `Test Core (3/6)`, shows the cost: one turbo run of the tests and their closure printed `Cached: 1 cached, 72 total` / `Time: 11m43.373s`. Each shard now runs a new step, `Build this shard's dependency closure`, before the slice step. It is one guarded `turbo run build`, with `--filter=PKG^...` for every package on the shard (PKG's dependencies without PKG), and it carries the env block. The test step then replays that closure from the local cache. Measured on all six shards of the local partition: this plan equals the test plan's build tasks, with identical hashes, 0 extra and 0 missing. One exception is `cli#build` on shard 1/6: `cli#test` has `dependsOn: ["build"]`, so the test step still builds that one, as before. Other properties of the new step: - An empty `FILTERS` exits before turbo runs, because a bare `turbo run build` would build the whole workspace. - It has no `--summarize`, so `.turbo/runs/` stays the test step's alone, for the drift check and the timings capture. - `check:stall-guard-budget` judges it at window 10m, cap 20m, budget 45m, slack 25m. ## The two pins Both pins are written into Build Core's `Turbo remote cache (objectstack-ai#21186)` comment as a PINS paragraph, and every carrier points back to it. They are not a new gate. **objectstack-ai#19086: a step that writes artifacts from a cache-served dist must not be among those served.** - The env goes on build-only steps. Checked mechanically on the final tree: all 12 steps carrying `TURBO_TOKEN` hold Build Core's four lines byte-identical and run only `turbo run build` / `pnpm build`, and no job sets `TURBO_*` at job level. The dry-run plans above contain no `test`, `test:repo`, `typecheck` or `gen:*` task. - `gen:schema` and `gen:skill-refs` are cacheable turbo tasks, but no workflow runs them through turbo (`turbo run gen`: 0 hits). - Grepping the wired jobs for `gen:`, `--fix` and `--write` gives 0 hits, so no step there writes tracked artifacts. - The typecheck step in `Type Check · workspace` stays off the remote. Every build task its `^build` closure schedules is already in the wired build step's plan (66 of 66, 0 extra), so it replays them locally. **objectstack-ai#21193: the build hash covers every root input the builds read.** I re-ran objectstack-ai#21193's probe on `3d9188502e`: append one comment line to a file, re-derive the 72-task Build Core plan, restore from `HEAD`, and prove the restore by blob hash. | file | build hashes moved | |:--|--:| | `tsup-drop-sources-content.mjs`, `check-dts-emitted.mjs`, `invoked-as.mjs` | 72/72 each | | `check-dts-references`, `ts-parse`, `check-regen-pending`, `regen-artifacts`, `git-env`, `import-prerequisite`, `cli-build-prerequisite`, `check-dev-prereqs`, `build-input-hash`, `workspace-enumerator`, `js-comment-mask` | 71/72 each | | `sync-scaffold-emission-policy.mjs`, `sync-template-versions.mjs`, `packages/cli/src/commands/init.ts` | 6/72 each | | control: root `tsup.config.ts` | 72/72 | | controls: `scripts/check-turbo-task-graph.mjs`, `ci.yml`, `lint.yml` | 0/72 each | These are objectstack-ai#21193's own numbers. The last row also shows that this PR's own diff moves no build hash. A static check agrees: the import closure of every root script that a build command or a tsup config names lies inside the declared `$TURBO_ROOT$` inputs. The spec generators' closure (450 files) reaches 10 root scripts, all declared. ## Measurement (before / after) **Before.** Build-step seconds, from the jobs API: | step | `main` push at base `3d9188502e` (CI `37627942232` / Lint `37627942309`) | merge-queue entry for the same tree (CI `37623284168` / Lint `37623284080`) | |:--|--:|--:| | Build Core `Build packages (excluding docs)` (reads the remote already) | 6 s | 193 s | | Type Check · workspace `Build workspace packages` | 297 s | 323 s | | Type Check · debt ledger: build, then ledgered build | 335 s, then 31 s | 340 s, then 32 s | | Type Check · consumer gates: build, then nested build | 330 s, then 30 s | 342 s, then 31 s | | dogfood 1/3, 2/3, 3/3 closure build | 200, 336, 201 s | 322, 340, 287 s | | Temporal: driver-sql, non-SQL, metadata-protocol, runtime | 1, 35, 0, 79 s | 95, 47, 1, 91 s | | Test Core `Run this shard's tests` (closure plus tests), shards 1-6 | 1181, 835, 616, 752, 663, 767 s | not split out; see the 3/6 reading above | **After** (seat-appended from the dev's report `6040381091` on objectstack-ai#22077; this PR's `pull_request` run, CI `37635720375` / Lint `37635720510`, head `cbb948b926`). Step seconds from the jobs API: | step | before (`main` push, base `3d9188502e`) | after (this PR) | |:--|--:|--:| | Type Check · workspace `Build workspace packages` | 297 s | 1 s | | Type Check · debt ledger: build, then ledgered build | 335 s, then 31 s | 1 s, then 1 s | | Type Check · consumer gates: build, then nested build | 330 s, then 30 s | 1 s, then 1 s | | dogfood 1/3, 2/3, 3/3 closure build | 200, 336, 201 s | 1, 2, 1 s | | Temporal: driver-sql, non-SQL, metadata-protocol, runtime | 1, 35, 0, 79 s | 3, 3, 1, 3 s | | Build Core (wiring unchanged) | 6 s | 7 s | | Test Core new `Build this shard's dependency closure`, shards 1-6 | — | 1, 0, 0, 0, 0, 0 s | Most of that main-to-PR delta is the local `actions/cache` seed, which this run restored from the base push. The **attributable** reading is the same-base control, Lint `37632788013` (PR objectstack-ai#22084: no remote, same seed), against Lint `37635720510`: - debt `Build the ledgered packages' dependencies`: control `Remote caching disabled` / `Cached: 66 cached, 71 total` / `Time: 29.447s`; this PR `Remote caching enabled` / `Cached: 71 cached, 71 total` / `Time: 527ms >>> FULL TURBO`; - consumers nested build: control `Cached: 66 cached, 71 total` / `Time: 29.819s`; this PR `Cached: 71 cached, 71 total` / `Time: 558ms >>> FULL TURBO`. The 5 tasks the control executed lie outside the plan the typecheck seed is saved from, so the remote served them here. Turbo's lines for the dogfood, workspace, driver-sql and non-SQL steps are NOT MEASURED: they fall outside the last 5000 log lines the read path returns. The **merge-queue reading is pending**: the seat takes it from this PR's own queue entry at landing and records it. How to read the after numbers: a PR's restored `actions/cache` seed comes from the latest `main` push. A cache hit does not say whether the local seed or the remote served it. So the clean signal is the next `main` push after landing: today Build Core takes 6 s there while the Type Check lanes take about 300 s. ## Kept as ruled - ⛔ No job removed, no required context changed, no new gate. `check:required-contexts` is green. - ⛔ The local `actions/cache` restore and save steps are untouched. They stay until the remote hit rate is measured. - ⛔ No new secret, and nothing written from a PR context. - `turbo.json` and `scripts/**` are not touched. ## Local gates The gates come from `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on this diff: 52 commands. Each exit code was written to disk before it was read. The full table, with exit codes and the `--ran` reconciliation, is in the `os-dev-report` on objectstack-ai#22077. The workflow readers were green before this PR opened: `check-ci-filter-parity`, `check-self-test-workflow-commands`, `check-step-collectors`, `check-self-test-wired`, `check-aggregator-roster`, `check:stall-guard-budget`, `check:workflow-step-name-quoting`, `check:required-contexts`, `check:pnpm-filter-targets` and `check:pm-expected-skips`. ## Acceptance notes - `Dogfood Verify CLI` and `Console Pin Gate` also run turbo builds. The card does not name them, so they are not wired here. - The `Type Check · source gates` lane runs no turbo build, so it has nothing to wire. - This PR changes only `.github/workflows/**`, which publishes nothing. `skip-changeset` applies. --- _Generated by [Claude Code](https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw)_ Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Unblocks #20388 (the Test Core shard-timings refresh), which #16465 and #16468 wait on.
This PR binds no card: #20388, #16465 and #16468 all remain open. #20388 lands by itself after this merges, on its own green, through the auto-merge the maintainer armed.
What this changes
scripts/partition-test-shards.mjs --self-testnow passes on BOTH datasets: thescripts/test-shard-timings.jsononmain, and #20388's (blob84342b45, byte-identical to its head8d60ae86, overlaid unstaged in a second worktree and never committed).FILE_SHARDED_PACKAGESis emptied (the CLI's2is retired). The mechanism stays: the item grammar,expandSlices, the vitest file-count floor, theOS_TEST_SHARDwiring judge, the generator's slice reassembly, and the dormant CLI wiring inturbo.jsonandpackages/cli/vitest.config.ts.CLI_MEASURED = 1231.52bridge. It asks the committed dataset, for every map entry, whethern - 1slices would also meet the bound (sliceCountProblems), and refuses annthatn - 1could replace. Five fixtures hold each refusal in both directions: needed, retire, lower, unmeasured entry, and an entry below 2.slicedparameters (default: the live map) onsliceCountFor,weighItemsanddriftReport. The re-pointed cases are the vitest floor, the slice-share prediction, drift on a sliced package, observed-run-wins,weighItemsslicing, and the wiring reader pointed at the CLI's real tree.example-todoovercoreflipped in the refresh (35.30s vs 57.09s). The new pair isplugin-pinyin-searchoversdui-parser: 2 vs 13 test files, 14.40s vs 1.62s onmain's dataset, 38.77s vs 4.54s on chore(ci): refresh the Test Core shard-timings dataset #20388's. A new guard reads the dataset's own order, so a flipped pair asks for a new pair instead of reporting a weighing defect.PREVIOUS_FILE_SHARDED_PACKAGES(new, beside the map) holds the outgoing map,{ '@objectstack/cli': 2 }. The generator's slice-digest matcher decodes a digest against both declared maps, exactly by hash, so a run summary written before this change still reads. Without it, the refresh lane refused every run onmain; see the next section. A decoded slice is still summed within its run (an incomplete set contributes nothing), and a digest neither map names is still refused. The map has no reader once a day has passed, because run summaries are kept for one day (retention-days: 1).scripts/measure-test-shard-timings.mjs(outside the claimed surface, and why). This is the matcher change above. Its live-map case also readFILE_SHARDED_PACKAGES['@objectstack/cli'], which reds under an empty map (measured: the unedited file exits 1). That case now covers both live maps, and a new case reads a pre-change run's two halves into their 733.33s whole while still refusing a count neither map names. The env-carried battery floor goes from 10 to 11. A stale "(2 for @objectstack/cli today)" parenthetical is dropped.Battery floors are raised to the measured counts: balancing pins 21 to 25, OS_TEST_SHARD wiring 9 to 11. File-level slice items stays at 20. The counts are the same on both datasets.
The three bin readings, and the keep-or-retire choice
Predicted bins at 6 shards against the 1.3x bound, using the partitioner's own
partition/balanceOf/expandSlices:main(70 pkgs, 3997.47s)mainmainOn #20388's dataset, both R and R-worst meet the bound, and slicing changes no bin's maximum (spec is the heaviest item in all three cases). Solving C ≤ (1.3/6)(6696.67 + C), the CLI fits whole until about 1852s, which is 1.5x its worst reading. The file's own rule makes n the smallest count that meets the bound. That count is 1, so the entry is retired.
Four axes:
Test Core (5/6)and 6m43s on(6/6), and 5/6 was the longest job at 19m23s.2would leave a count no pin can justify. The old counterfactual cannot be rewritten on any measured basis, because R-worst meets. Retirement keeps the mechanism, and pin 3 (the floor) is the live trigger to slice again.The refresh lane across the change: red, then green
c614a094. This PR'spull_requestrehearsal ofshard-timings-refresh.yml(run 37074888579, job 111062437870) refused all ten eligible hourly runs onmain, 37010481060 through 37070188866. It then ended with "The 0 eligible run(s) ... NOTHING was regenerated".main's dataset in the exact turbo 2.10.10 shape: six whole-leg summaries, plus two slice legs whoseOS_TEST_SHARDdigest is the sha256 of1/2or2/2(d939926f…, the measured format). It was fed to the lane's own two commands: the generator with--run/--merge-into, thenselect-shard-timings-run.mjs --check-coverage.aa463223(the map{cli: 2}): exit 0, coverage OK.c614a094(the empty map): exit 1, "matches no slice the partitioner can emit for it (none: FILE_SHARDED_PACKAGES does not slice it)".d049d353: exit 0, coverage OK, and apackagesmap identical toaa463223's.d049d353does: run 37075730446 (job 111065090886) is green. It accepted its first candidate, with no "dropping that run" warning, somain's real env-carried summaries decode once the matcher knows the outgoing map.d049d353: a post-change run alone gives exit 0 (cli whole, 740s). A post-change run accumulated with a pre-change sliced run gives exit 0 (cli 599.08s, the median of 740 and 458.15). So a refresh that straddles the merge also reads. It is still a prediction: it depends on an eligible full-battery run existing, as every refresh does.Self-test on both datasets (head
d049d353)The BEFORE red reproduces #20388's AFTER block on today's
main(line 1075 now, 967 in that body). It also mislabels spec's 1391s as the CLI's, because the old counterfactual read the overall heaviest item. A second red was masked behind it. With the 3c throw muted onaa463223, pin 6 reds on #20388's dataset with "The run is weighing test-file count again". That is a misdiagnosis: the pair had flipped.Mutation proof, one per rewritten or added pin
Every leg was taken from committed
c614a094throughscripts/ablation-replace.mjs. The whole table plus N1 to N4 was then re-run from committedd049d353on both datasets: 48 legs, every one landed and restored. In each leg the anchor hit 1 and the replacement went 0 to 1. Each leg was restored to the HEAD blob withgit diff HEADempty, under a shelltrapthat re-proved the hashes in both worktrees at the end. The red line below is the self-test's firstError:, the same on both datasets unless shown.'@objectstack/cli': 2to the live mapslice derivation, committed dataset (1 of 1 ...), "sliced 2 ways at 458.15s (main) / 733.33s (#20388), but at 1 the split already meets 1.3x ... Retire the entry"meetsBoundalways meetsa count of 2 that 1 cannot replace was refuseda package that fits whole kept its slicing with no refusala count of 3 where 2 meets the bound was acceptedan entry the dataset never measured was acceptedan entry of fewer than 2 slices was acceptedexpandSlicesstops slicingslice spread: cutting @objectstack/cli (main) / @objectstack/spec (#20388) in two produced no ... pair to grademain(the pair still holds there); on #20388:the dataset no longer measures @objectstack/example-todo slower than @objectstack/core (35.3s vs 57.09s) ... Pick a new inversion pairweighItemsweighs test-file countweight: @objectstack/plugin-pinyin-search weighed 2 and @objectstack/sdui-parser weighed 13 ...slice count: a package outside the slice map was slicedsliceCountForignoresslicedslice count: the configured package did not read its configured countslice floor: slicing below the test-file count was not refused (no throw)prediction: a sliced package was charged its WHOLE dataset entrydriftReportignoressliceddrift: a sliced overshoot read 0.83x and was not reported as driftdrift: an observed WHOLE run was charged a slice-sized prediction (1.67x)weighItemsignoresslicedweighItems: 1 package(s) produced 1 item(s), expected 2slice wiring: judged 1 of 0 sliced package(s) ...packages/cli/vitest.config.tsreadsOS_TEST_SHARD_UNREADslice wiring, the tree read through a fixture map ... never reads OS_TEST_SHARD into vitest's shardsamplesFromSummary's defaultslicedis not the live mapenv slice: a @objectstack/cli digest of 1/3, a count neither live map names, was not refused listing (1/2, 2/2) (got "no throw")@objectstack/cli ran with OS_TEST_SHARD set ... matches no slice the partitioner can emit for it, or emitted under the map it replaced (none: ...)samplesFromSummary's defaultpreviousis not the live onesamplesFromSummarydrops itspreviousargumenta: cli ran with OS_TEST_SHARD set (digest d939926f…) ... matches no slice ...env slice: a @objectstack/cli digest of 1/3, a count neither live map names, was not refused listing (1/2, 2/2)Premise checks (the dispatch's A1 to A4)
aa463223: the map was{ '@objectstack/cli': 2 }, pin 3b carried the 1231.52s/800.7s prose, and pin 3c carriedCLI_MEASURED = 1231.52.main; see BEFORE above.main: 70 packages, 3997.47s, cli 458.15s, spec 403.65s. On chore(ci): refresh the Test Core shard-timings dataset #20388: 72 packages, 7430.00s,measuredAt2026-09-28, runs[36380128221], spec 1391.38s (heaviest), cli 733.33s. The two new packages areorganizationsandvitest-filter-preflight. The package set equals today's workspace minus the CI-excludeddogfood.provenancekeys, and byte-identicalnote,mergeRuleandrefreshstrings. It accepts the file as a--merge-intotarget (carriedspecon a HIT witness).skippedAsCached,skippedIncompleteSlicesandcarriedOverare all empty, and the CLI's 733.33s is a two-slice sum from one run. The 09-28 run carried its slices as a--shardpassthrough, which today's generator still reads (sliceOfCliArguments). Its run-summary artifacts are no longer retained (the run lists onlytest-core-timing-tableandbuild-output), so a byte-level re-generation is NOT MEASURED.Consequences outside this diff (known, deliberate)
c614a094, green atd049d353.test-nightly-tiers.ymlpartitions the one tier-owning package at 2 shards. Measured locally: shard 1/2 now carries@objectstack/cliwhole, and 2/2 carries nothing ("No packages on this shard", exit 0). The run stays inside its 45-minute timeout by its own header's estimate. Its header prose and its 2-shard matrix are now stale; no carrier.mainthen splits on the stale weights with the CLI whole. Modeled with chore(ci): refresh the Test Core shard-timings dataset #20388's weights standing in for real cost: the heaviest actual bin stays spec's (1775s sliced, 1773s whole). The CLI's bin is 1116s at 733.33s, or 1614s at 1231.52s. Mergingmaininto chore(ci): refresh the Test Core shard-timings dataset #20388 right after this lands keeps the window short.Gates (head
d049d353)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(change set from git, 2 paths) derived 32 families. The list is the same onc614a094andd049d353. On both heads, all 32 ran with exit 0, recorded and reconciled by--ran: "32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN".node scripts/report-test-timings.mjs --self-testalso ran, exit 0. The list includes the three self-tests (partition / measure / select-shard-timings-run),check:cross-package-test-inputs,check:nul-bytes,check:pm-dispatch-gates(1976 cases, 977s),check-scripts-symbol-anchorsandcheck-self-test-wired.Lint, narrowed and proven.
eslint --no-inline-config --format jsonon the 2 changed files reports 2 files, 0 errors and 0 warnings on both heads, and neither file is ignored.--print-configshows 2 per-file rules (no-restricted-imports,comment-swallow/no-code-inside-block-comment) and noparserOptions.projectorprojectService. Type-aware linting is off, so this diff cannot move any untouched file's verdict. The fullpnpm lintis CI's.skip-changeset: rootscripts/ships in no package'sfiles[](the root package is private).Acceptance notes
testandtest:repowindows, and the windows overlapped. Spec is now the floor, at about 83% of its own breach point (about 1670s). ci: a per-package suite-duration ratchet — a PR that makes a suite exceed its measured ceiling is red; ceilings rise only by ruling (maintainer-directed, growth constraint) #16468's ceilings will read this number.::error::blames a coverage shortfall ("a suite failed, a package was renamed or removed, or its slices could not be assembled") rather than the refusals, as run 37074888579 showed. No public door; no carrier.ci.ymlslice-leg comments and the 1231.52s note at its drift step are for ci: Test Core compares each shard's measured duration with its predicted weight — warning at 1.3x, red at 70% of the timeout (maintainer-directed, drift alarm) #16465, which edits that file.packages/cli/vitest.config.ts's "partition-test-shards.mjs slices this package" has no carrier. Thetest-nightly-tiers.ymlheader and matrix have no carrier.shard-timings-refresh.yml's "pin 3c ... the day the CLI comes back under the bound" is still true in mechanism; no carrier.Generated by Claude Code