Repository navigation
Commit 3083906
Fixes #19278
Clause-②: no
Seat review ACCEPT on the landing head `e00136407`: comment
`5909822071`. No contract-review record is owed, because the diff
touches no review face and `Clause-②: no` (`contract-review.md`).
## What it changes
Test Core's file-level slice leg (`.github/workflows/ci.yml`) ran `pnpm
turbo run test --filter=$PKG --only --force … -- --shard=$SLICE`.
- `--only` drops 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 #19271) keeps such a slice from being replayed from
cache, but the hash stays blind. That is #19278.
This branch carries the slice in a task-declared env instead:
- `turbo.json` declares `OS_TEST_SHARD` on `test` and on
`@objectstack/cli#test`. A package-specific task replaces the generic
one, so both need it.
- `packages/cli/vitest.config.ts` reads it into vitest's `shard`, typed
through vitest's CLI-options type. A literal `test.shard` fails
typecheck with TS2769.
- The slice leg runs `env OS_TEST_SHARD=$SLICE pnpm turbo run test
--filter=$PKG …`, with no passthrough, no `--only` and no `--force`.
- `scripts/partition-test-shards.mjs --self-test` gains 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.mjs` reads an env-carried slice
back from the run summary's `environmentVariables.configured` digest,
and keeps the `cliArguments` path that nightly-tiers still uses. Without
this, a timings refresh would record one slice as the package's whole
cost (#16173's defect).
- Comments the change made false are rewritten: the slice leg's block
and the closure-build step in `ci.yml`, and `report-test-timings.mjs`.
## Measured (round 1, the dev's report `5907562928` on #19278, turbo
2.10.10, vitest 4.1.11)
| | old leg | new leg |
|:--|:--|:--|
| `cli#test` hash after an upstream edit in `spec` / `types` | unchanged
| moves each time |
| an edit inside `cli` itself | moves | moves |
| control edit outside cli's closure | — | unchanged |
| slice after an undeclared upstream edit, no `--force` | `cache hit,
replaying logs` (not run) | `cache miss, executing` (runs) |
| planned tasks, HIT / MISS | 1 task (the `--only` leg) | 62 tasks: 59
HIT / 3 MISS |
## Evidence on the final head `e00136407d` (handover steps 1 to 4)
Final head: `e00136407d04e3b2871b30a9c6463a2f9f108f6c`, a merge of
`origin/main` `4b45afaed5`. The six paths are byte-identical to
`9df0b71851`, except one comment block in
`scripts/measure-test-shard-timings.mjs` (`099e5ff641`, comment only).
### 1. The reader's new self-test cases, ablated
- Every mutation went through `scripts/ablation-replace.mjs`: the anchor
hit 1 then 0, the blob moved, the file was restored to the HEAD blob,
and `git diff HEAD` was empty afterwards. A shell trap also guarded each
run.
- The reader blob was `453a072feedb` on the final head. The same table
was measured first on `cb42313d89e6` (`9df0b71851`) and gave the same
result.
- Each mutation got two readings:
- the real `--self-test`, its exit code and its first red;
- an instrumented copy of the harness that records every red case
instead of stopping at the first one.
- Control: the instrumented harness with no mutation records 0 reds.
| mutation | real `--self-test` | red cases in `env-carried slices
(#19278)` |
|:--|:--|:--|
| A0: the digest path is dropped (`sliceOfLeg` never reads
`OS_TEST_SHARD`) | exit 1, "read as null, not 2/3" | 1, 2, 4, 5, 6, 9,
10 |
| A1: the env slice is computed but not returned | exit 1, same | 1, 2,
10 |
| A2: the default map is unbound from `FILE_SHARDED_PACKAGES` | exit 1,
the refusal on `@objectstack/cli` 2/2 | 2 |
| A3: the `cliArguments` carrier is dropped | exit 1, older battery case
29 first | 3, plus older-battery cases 29, 31, 34 to 37 and 40 to 43 |
| A4: the refusal is removed (an unmatched digest returns null) | exit
1, "not refused naming its candidates" | 4, 5, 6 |
| A5: a package the partitioner does not slice ignores its digest | exit
1 | 5 |
| A6: the empty-value guard is removed | exit 1 | 7 |
| A7: the entry is picked without its `OS_TEST_SHARD=` prefix | exit 1 |
8 |
| A8: the refusal of two disagreeing carriers is removed | exit 1 | 9 |
- Every case goes red under at least one mutation.
- The unmatched-digest case (4) goes red under A0 and A4.
- Cases 6 and 10 have no mutation of their own. They are end-to-end pins
over the code that cases 4 and 1 pin: case 6 checks that the refusal
never reaches a dataset, and case 10 that three env slices sum to one
package.
- After the mutations the self-test reads OK and `git diff HEAD` is
empty.
- One comment was fixed as a result (`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
- This PR's CI summaries could not be read from this container. The
artifact download redirects to blob storage, and the container's egress
proxy refuses that with 403. The summaries were therefore produced
locally with the leg's own shape, after the closure build:
`OS_TEST_TIERS=queue env OS_TEST_SHARD=k/2 pnpm turbo run test
--filter=@objectstack/cli --summarize --log-order=stream`.
- Added for this box: `--concurrency=2`, `VITEST_MAX_WORKERS=2` and a
private `--cache-dir`. None of them enters a task hash.
- Commit: `95b97ef2e`. Its six paths and every `packages/cli` file are
identical to the final head's.
- Slice 1/2: `cache miss, executing 90ddc44dcc24b458`; 60 tasks, 59
cached; 152 files; 2069 tests passed and 1 skipped. In the summary,
`cliArguments` is empty, and `environmentVariables.configured` holds
`OS_TEST_SHARD=d939926f…` (the sha256 of `1/2`) and
`OS_TEST_TIERS=00b109cf…`.
- Slice 2/2: `cache miss, executing 52f80464736e668b`; 151 files; 1865
tests.
- CI shows the same thing. In `Test Core (6/6)` on `9df0b71851` (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.
| reader | slice 1/2 | slice 2/2 | cli in the dataset |
|:--|:--|:--|:--|
| this branch | 644.411 s, read as slice 1/2 | 572.636 s, read as slice
2/2 | **1217.05 s** (the two slices summed) |
| `origin/main` `810d42b69c` | 644.411 s, no slice | 572.636 s, no slice
| 608.52 s (the median of two "whole" samples, which is #16173's defect)
|
- `report-test-timings.mjs`, with `--capture` run twice and then
`--merge`:
- cli reads 1217.05 s, "all 2 slices", complete, with 303 of 303 file
lines.
- With only slice 1/2's capture present, this branch labels the row
"slice 1/2 — PART, not a total". The `origin/main` script prints 644.41
s as cli's total, with no note.
- `partition-test-shards.mjs --check-drift`:
- It predicts **229.1 s** for cli, one slice's share of the 458.15 s
weight, against 644.4 s measured. That is DRIFT at 2.81x, exit 1. The
ratio is this shared container, about 2.7 times slower than CI. The
number that matters is the prediction.
- The `origin/main` script predicts 458.1 s, the whole package, and
reports OK at 1.41x. It compares one slice against the whole package's
weight.
- An unmatched digest on a real summary. One hex digit of the
`OS_TEST_SHARD` digest in slice 1/2's summary was flipped:
- `measure-test-shard-timings.mjs` exits 1, with the refusal naming the
candidates `1/2, 2/2`, and writes no dataset.
- `--check-drift` exits 1 with the same refusal.
- `report-test-timings.mjs` exits 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/main`
- Two merge commits, no rebase: `95b97ef2e` merged main at `810d42b69c`,
and `e00136407` merged main at `4b45afaed5`.
- The second merge was needed because `dispatch-gates` flagged the first
derivation as stale. Main had changed
`scripts/pm/fleet-write/dispatch.mjs`, one of the files the derivation
reads.
- Neither merge conflicted. Main touched none of the six paths. After
each merge the six paths were byte-identical to the branch's own.
- Main has since moved to `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 `e00136407`
**Gates.**
- `dispatch-gates --commands --repo objectstack-ai/objectstack` derived
89 commands, and all 89 were run.
- `check:dual-build-cjs-loads` first exited 3 with PREREQUISITE NOT MET:
nine packages outside cli's closure had no `dist`. Those nine were built
and the gate re-run: exit 0. The four other `dist` readers
(`dts-closure`, `lean-entry-closure`, `published-files`,
`sourcemap-no-sources-content`) were re-run on the fuller tree: exit 0.
- `check:pm-dispatch-gates` ran detached: exit 0, 1976 cases, 1058.8 s.
- Verdict: `✓ 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.
- Nine families that the derivation names as taking a value from the
workflow cannot run locally, because their argv or env has no value
outside CI. They are the `check-shard-attestation` emit and verify
steps, `check-test-completeness` on the two CI logs,
`check-issue-citations --census` and `check-required-contexts
--verify-required-set`. They are not in the 89, and CI runs them.
- Beyond the derived list:
- `pnpm --filter @objectstack/cli typecheck`: exit 0;
`check:test-typecheck` OK, 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.
- The whole cli suite at the queue tier (both slices above, 303 files)
passed on `95b97ef2e`.
- `check-test-completeness.mjs --self-test` passed. That script imports
the partitioner.
- eslint over the four changed lintable files: 4 files, 0 errors, 0
warnings. eslint's own config places all four in its population. The
count comes from `--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=json` hashes of
`cli#test`. Each edit was a one-line marker, planted and then restored.
| edit | new leg (`OS_TEST_SHARD=1/2`) | old leg (`--only --force --
--shard=1/2`) |
|:--|:--|:--|
| clean | `5fe85e3cd60398b1` | `d26a68b9c44aac59` |
| `packages/spec/src/ui/view.zod.ts` | `f57477faa9b85239`, moves |
unchanged |
| `packages/types/src/env.ts` | `b64907aa07f030ad`, moves | unchanged |
| `packages/cli/src/utils/one-shot-exit.ts` | `123e600f94cc13c8`, moves
| `297d412288a9c65b`, moves |
| `packages/client/src/index.ts` (no `$TURBO_ROOT$` input names it) |
`32e74945bc85d7e1`, moves | unchanged |
| connector-slack (control, outside cli's closure) | unchanged |
unchanged |
- The new leg plans 62 tasks: 59 HIT and 3 MISS. All 61 of its build
hashes equal those of `turbo run build --filter=@objectstack/cli`, so
the closure replays.
- The second merge moved the new leg's clean hash from
`6a3fe12522c74bb9` (on `099e5ff641`) to `5fe85e3cd60398b1`. The old leg
stayed at `d26a68b9c44aac59`.
**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=queue` was set
throughout.
- New leg, unchanged tree: `cache miss, executing 8cc2f107d21814cf`,
then `cache hit, replaying logs 8cc2f107d21814cf` in 86 ms. That replay
is legitimate, because nothing changed.
- Old leg (`--only`, no `--force`), unchanged final tree: `cache hit,
replaying logs dc666be1735ca19a`. That cache entry was written on
`099e5ff641`, 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.**
- Next, an edit was planted in `packages/client/src/index.ts`, followed
by CI's closure rebuild (`client#build` and `cli#build` executed; 57 of
59 were cached):
- Old leg, no `--force`: `cache hit, replaying logs dc666be1735ca19a` in
118 ms. The slice was not run.
- New leg, no `--force`: `cache miss, executing 61f0a4c75daa610c`, 19
files passed. The slice ran.
- The same sequence ran on `099e5ff641` with the real partition slice
1/2:
- unchanged tree: the slice replays `90ddc44dcc24b458`;
- after the client edit: `cache miss, executing 2df7b9311ca50a57`; 152
files; 2069 tests passed and 1 skipped; 10m22.8s.
- After each run the edited file matched its HEAD blob and `git diff
HEAD` was empty.
### Changeset disposition: `skip-changeset`, applied by the seat
- `Check Changeset` on `e00136407` fails 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.
- Presence: 0 `.changeset/*.md` files were added against merge base
`4b45afaed5`.
- No published file changes:
- `packages/cli` ships `files: [dist, README.md, CHANGELOG.md]`. Its
`tsconfig.build.json` compiles only `src`, so `vitest.config.ts` is test
configuration and is never shipped.
- In the built cli `dist`, README and CHANGELOG, `OS_TEST_SHARD` appears
in 0 files. The positive control, `exitOneShotCommand`, appears in 6
files. There is no `vitest.config*` in `dist`.
- The other five paths are CI configuration, the root `turbo.json`, and
scripts of the root package, which is private.
## Acceptance notes
- The config-level shard is typed through vitest's CLI-options type. If
a vitest upgrade stops merging the test block with the CLI options, both
slices will run the whole suite: extra cost, not lost coverage. The
partitioner battery checks only spelling, so it cannot catch this.
- Landing moves 52 test-task hashes once. Any `turbo.json` edit already
makes all 80 packages affected on the landing PR.
- The Dogfood gate's shard leg has the same `--only` passthrough shape
and no `--force`. It is filed separately as #20820.
- The unmatched-digest refusal reaches `report-test-timings.mjs` as a
stated problem, not as an exit code. That line names the summary file
twice, because `readSummaries` prefixes the entry and
`samplesFromSummary` also labels the error with it. Every refusal that
function relays already has this shape. It is left alone.
- The local timings in section 2 are shared-container seconds, about 2.7
times CI's. Read the prediction, not the drift ratio.
- `Shard Timings Refresh` went red on `e00136407` (run 36701198568):
"candidates existed and none was eligible".
- That lane reads main's scheduled CI runs through the GitHub
run-listing API, not this diff. Its inputs here
(`select-shard-timings-run.mjs`, the workflow, and the reader) are
byte-identical to the three earlier heads, where it was green.
- Reproduced locally at 10:22Z: the listing served a stale index (70
scheduled runs, the newest from 2026-09-27T14:00Z, every artifact
expired), so every candidate was rejected.
- The same query at 10:23Z returned the 10:01Z and 09:01Z runs, both
ELIGIBLE, and the selector exited 0 in three straight attempts.
- The details are in the report on #19278.
Rounds 1 and 2 were written by session
`session_014EJ1ED8X4MMrT18BhVx4tx`. The evidence above, the second merge
and the comment fix are by session `session_01TdiauJaVCHuj45EzZGUxHh`,
the `domain:devx` seat 2 dev, on 2026-09-30.
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1741c5d commit 3083906
6 files changed
Lines changed: 536 additions & 95 deletions
File tree
- .github/workflows
- packages/cli
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
556 | 556 | | |
557 | 557 | | |
558 | 558 | | |
559 | | - | |
560 | | - | |
561 | | - | |
562 | | - | |
563 | | - | |
564 | | - | |
565 | | - | |
566 | | - | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
567 | 572 | | |
568 | 573 | | |
569 | 574 | | |
| |||
677 | 682 | | |
678 | 683 | | |
679 | 684 | | |
680 | | - | |
681 | | - | |
682 | | - | |
683 | | - | |
684 | | - | |
685 | | - | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
686 | 694 | | |
687 | 695 | | |
688 | 696 | | |
| |||
727 | 735 | | |
728 | 736 | | |
729 | 737 | | |
730 | | - | |
731 | | - | |
732 | | - | |
733 | | - | |
734 | | - | |
735 | | - | |
736 | | - | |
737 | | - | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
738 | 746 | | |
739 | | - | |
740 | | - | |
741 | | - | |
742 | | - | |
743 | | - | |
744 | | - | |
745 | | - | |
746 | | - | |
747 | | - | |
748 | | - | |
749 | | - | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
750 | 765 | | |
751 | | - | |
752 | | - | |
753 | | - | |
754 | | - | |
755 | | - | |
756 | | - | |
757 | | - | |
758 | | - | |
| 766 | + | |
| 767 | + | |
759 | 768 | | |
760 | | - | |
761 | | - | |
762 | | - | |
763 | | - | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
764 | 775 | | |
765 | | - | |
766 | | - | |
767 | | - | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
768 | 782 | | |
769 | | - | |
770 | | - | |
771 | | - | |
772 | | - | |
773 | | - | |
774 | | - | |
775 | | - | |
776 | | - | |
777 | | - | |
778 | | - | |
779 | | - | |
780 | | - | |
781 | | - | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
782 | 786 | | |
783 | | - | |
784 | | - | |
785 | | - | |
786 | | - | |
787 | | - | |
788 | | - | |
789 | | - | |
790 | | - | |
791 | | - | |
792 | | - | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
793 | 790 | | |
794 | | - | |
795 | | - | |
796 | | - | |
797 | | - | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
798 | 805 | | |
799 | 806 | | |
800 | 807 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
643 | 643 | | |
644 | 644 | | |
645 | 645 | | |
646 | | - | |
| 646 | + | |
647 | 647 | | |
648 | 648 | | |
649 | 649 | | |
| |||
710 | 710 | | |
711 | 711 | | |
712 | 712 | | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
713 | 742 | | |
714 | 743 | | |
715 | 744 | | |
| |||
784 | 813 | | |
785 | 814 | | |
786 | 815 | | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
787 | 819 | | |
788 | 820 | | |
789 | 821 | | |
| |||
0 commit comments