Skip to content

Commit 898a5bd

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-20312-save-door-sdui-manifest
2 parents b3d92e5 + 3083906 commit 898a5bd

6 files changed

Lines changed: 536 additions & 95 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 82 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -556,14 +556,19 @@ jobs:
556556
echo 'Items on this shard (a package name, or a package plus a k/n file-level slice):'
557557
cat "$RUNNER_TEMP/shard-packages.txt"
558558
559-
# ⛔ A FILE-LEVEL SLICE BUILDS ITS DEPENDENCY CLOSURE HERE, IN A RUN THAT
560-
# CARRIES NO PASSTHROUGH, so that the sharded run in the next step can be
561-
# `--only` (#16395).
562-
#
563-
# Turbo folds a run-level passthrough into the hash of EVERY task in the
564-
# run, not only the task that receives it -- and `-- "--shard=k/n"` is the
565-
# whole reason a slice gets its own invocation at all (the next step's
566-
# comment says why it cannot ride the shared run). Measured on turbo
559+
# ⛔ A FILE-LEVEL SLICE BUILDS ITS DEPENDENCY CLOSURE HERE, IN ITS OWN
560+
# GUARDED STEP, so the slice leg in the next step REPLAYS it. Since #19278
561+
# that leg carries its slice in `OS_TEST_SHARD`, which only the `test`
562+
# task declares, so its build tasks hash exactly as they do here (61 of 61
563+
# identical, measured) and it schedules its own closure: this step is no
564+
# longer what makes the leg correct, only what keeps the closure build off
565+
# the test step's stall-guard site (see the last paragraph below).
566+
#
567+
# Why the step first existed (#16395, when the slice was still a
568+
# passthrough and the leg was `--only`): turbo folds a run-level
569+
# passthrough into the hash of EVERY task in the run, not only the task
570+
# that receives it -- and `-- "--shard=k/n"` was then the whole reason a
571+
# slice got its own invocation at all. Measured on turbo
567572
# 2.10.10, `--filter=@objectstack/cli`, `turbo run test ... --dry=json`
568573
# (60 tasks: 59 `build` + 1 `test`):
569574
#
@@ -677,12 +682,15 @@ jobs:
677682
678683
# Split the shard's ITEMS into the whole packages, which share one
679684
# turbo run as they always have, and the file-level slices, which
680-
# cannot: `--shard=k/n` is passed through to vitest by turbo as a
681-
# RUN-level argument, so it would reach every package in the run —
682-
# and on any package with fewer test files than n that is a hard
683-
# vitest failure (or, with --passWithNoTests, silently no tests at
684-
# all). A slice therefore gets its own invocation, filtered to the one
685-
# package the partitioner sliced.
685+
# cannot: a slice's `k/n` travels as `OS_TEST_SHARD` in the turbo
686+
# run's environment, and a run has ONE environment — set on the
687+
# shared run it would reach every package's `test` task, which
688+
# turbo.json declares it on. (Before #19278 it travelled as a
689+
# `--shard=k/n` passthrough, which reached every package's vitest: a
690+
# hard failure on any package with fewer test files than n, or with
691+
# --passWithNoTests silently no tests at all.) A slice therefore gets
692+
# its own invocation, filtered to the one package the partitioner
693+
# sliced.
686694
FILTERS=""
687695
SLICES=""
688696
while read -r PKG SLICE; do
@@ -727,74 +735,73 @@ jobs:
727735
PKG="${LEG%%=*}"
728736
SLICE="${LEG#*=}"
729737
LOG="$RUNNER_TEMP/test-core-slice-$(printf '%s' "$PKG" | tr -c 'A-Za-z0-9' '-').log"
730-
# `--only` (#16395): the step above already built this slice's
731-
# dependency closure in a passthrough-free run, so this run must
732-
# schedule the ONE task the passthrough is for. Without it turbo
733-
# re-hashes the whole `^build` closure under `--shard=k/n` and
734-
# rebuilds it -- that comment carries the measurement. ⚠ The build
735-
# step is load-bearing for this flag: a sliced package whose build
736-
# never ran fails LOUDLY here (its imports resolve to a missing
737-
# dist), never as a silent green.
738+
# THE SLICE RIDES IN `OS_TEST_SHARD` (#19278), a variable turbo.json
739+
# declares on this package's `test` task and its vitest.config.ts
740+
# reads into vitest's `shard` (vitest 4.1.11 reads no shard
741+
# variable of its own). Not a `-- --shard=k/n` passthrough, and so
742+
# neither `--only` nor `--force`. The partitioner's `--self-test`
743+
# fails a package it can slice whose config or task does not.
744+
#
745+
# Why the two flags existed, and why they are gone:
738746
#
739-
# ⛔ `--force` (#18671) IS NOT REDUNDANT BESIDE `--only` -- do not
740-
# delete it as a no-op. Read the paragraph above backwards: if
741-
# `--only` is what stops turbo re-hashing the `^build` closure,
742-
# then under `--only` this task's hash NO LONGER CARRIES that
743-
# closure, and the one path by which a change in a dependency
744-
# reaches this task is gone with it. What is left is the package's
745-
# own files plus the hand-declared `$TURBO_ROOT$` inputs in
746-
# turbo.json. So a slice that IS in the affected set -- and this
747-
# shard's package set is exactly the affected set, computed two
748-
# steps up -- can match a main-seeded cache entry across the very
749-
# change that put it in that set, and be replayed instead of run.
747+
# `--only` (#16395). Turbo folds a run-level passthrough into the
748+
# hash of EVERY task in the run (turbo 2.10.10, cli's plan, plain
749+
# vs `-- --shard=1/2`: 0 of 62 hashes identical), so the leg
750+
# re-hashed and rebuilt the `^build` closure the step above had
751+
# just built. `--only` scheduled the test alone -- and dropped the
752+
# closure out of the test's HASH with it (#18671), leaving the
753+
# package's own files plus the hand-declared `$TURBO_ROOT$`
754+
# inputs. A slice in the affected set (on a PR, queue or push run
755+
# this shard's set IS the affected set) could then match a
756+
# main-seeded entry across the very change that put it there.
757+
# Run 34746808828 did: it replayed `@objectstack/cli:test` on
758+
# this leg -- `Cached: 1 cached, 1 total`, `73ms >>> FULL
759+
# TURBO` -- out of a log a main push run had produced ~15
760+
# minutes earlier, on a commit that did not contain the PR under
761+
# test. All six shards reported success,
762+
# `check-test-completeness` graded the replay OK, the shard
763+
# attestation said "ran to completion", and the red reached
764+
# `main`. Neither of those two can tell a run from a replay.
750765
#
751-
# That is not a hypothetical. Run 34746808828 replayed
752-
# `@objectstack/cli:test` on this leg -- `Cached: 1 cached, 1
753-
# total`, `73ms >>> FULL TURBO` -- out of a log a main push run
754-
# had produced ~15 minutes earlier, on a commit that did not
755-
# contain the PR under test. All six shards reported success,
756-
# `check-test-completeness` graded the replay OK, and the shard
757-
# attestation said "ran to completion". Neither of those two can
758-
# tell a run from a replay; the red reached `main`.
766+
# `--force` (#18671, PR #19271). Made the leg execute; a constant
767+
# bypass cannot alarm, and the hash stayed blind to the closure.
759768
#
760-
# Measured on this tree (turbo 2.10.10) with a package whose
761-
# `test` task has the same shape (`dependsOn: ["^build"]`), after
762-
# a source change in an upstream package PLUS the closure rebuild
763-
# the step above performs -- i.e. exactly this job's sequence:
769+
# A declared env reaches ONLY the task that declares it: same plan,
770+
# plain vs `OS_TEST_SHARD=1/2` is 61 of 62 identical, the one that
771+
# moves is `cli#test`, and `1/2` vs `2/2` moves that one alone. The
772+
# closure keeps the hashes the step above gave it (61 of 61 equal
773+
# to `turbo run build --filter=$PKG`) and replays, while the test's
774+
# hash carries the closure again. `cli#test`, `--dry=json`:
764775
#
765-
# --only (before) 1 task `1 cached, 1 total` 81ms >>> FULL TURBO <- NOT RUN
766-
# --only --force (here) 1 task `0 cached, 1 total` 1.15s <- runs
767-
# no --only (option) 6 tasks `0 cached, 6 total` 1m54.963s <- runs, and rebuilds the closure
776+
# this leg, OS_TEST_SHARD=1/2 clean 1ee03ac2f26389a6
777+
# + packages/spec/src/ui/view.zod.ts 04eabd0b5a9db364 moves
778+
# + packages/types/src/env.ts 4b15652b8c493471 moves
779+
# + connector-slack/src/* (not in closure) 1ee03ac2f26389a6 still (control)
780+
# old leg, --only --force -- --shard=1/2
781+
# clean / + view.zod.ts d5e7ec263710fe59 still (the defect)
768782
#
769-
# The third row is why the repair is `--force` rather than
770-
# "drop `--only`": dropping it re-executes the closure the step
771-
# above just built, which is #16395's bill paid twice per job.
772-
# That bill re-measured TODAY at cli's real scale, same tree,
773-
# `--dry=json` against a cache the passthrough-free build had
774-
# just filled: the `--only` leg plans 1 task; dropping `--only`
775-
# plans 60 and every one of the 60 is a cache MISS, against 57
776-
# HIT / 3 MISS for the same plan without the passthrough (2 of
777-
# those 3 are `#build` tasks for packages that declare no `build`
778-
# script, so they never execute; the real miss is the test). Cost
779-
# of re-executing that closure here, `--concurrency=2` on a
780-
# SHARED container: 5m11.262s, against 60ms `>>> FULL TURBO` for
781-
# the same command when its hashes are left alone.
783+
# Executed (cli, slice 1/16 for time; `packages/client/src/index.ts`
784+
# edited -- a cli dependency no `$TURBO_ROOT$` input names -- then
785+
# the step above rebuilt the closure, 57 of 59 cached):
782786
#
783-
# `--force` re-executes THIS ONE TASK and nothing else, which is
784-
# why `--only` stays: the two are a pair. Precedent, for the same
785-
# reason in one sentence -- a gate must not be satisfiable by a
786-
# replayed artifact -- is the docs-build gate's `TURBO_FORCE`
787-
# below. Two things fall out of it that are worth keeping:
788-
# `--summarize` now records a REAL duration for the sliced
789-
# package (measure-test-shard-timings.mjs refuses to read one
790-
# from a replay), and the log says `cache bypass, force
791-
# executing <hash>` where it used to say `cache hit, replaying
792-
# logs <hash>`.
787+
# old leg, no --force `cache hit, replaying logs` 69ms FULL TURBO <- NOT RUN
788+
# old leg, --force `cache bypass, force executing` (same hash) <- runs
789+
# this leg `cache miss, executing`, 59 of 60 cached <- runs
793790
#
794-
# ⚠ What `--force` does NOT do is make the hash honest. It stays
795-
# blind to the closure, so nothing in this repo yet fails when a
796-
# slice leg's hash stops moving for an upstream source change.
797-
set -- pnpm turbo run test "--filter=$PKG" --only --force --concurrency=4 --summarize --log-order=stream -- "--shard=$SLICE"
791+
# and on an unchanged tree this leg replays (`60 cached, 60 total`,
792+
# 104ms): with the closure in the hash a HIT means these inputs were
793+
# already tested, which is why no `--force`. Cost: 62 tasks planned,
794+
# 59 HIT / 3 MISS against the cache the step above fills (the test,
795+
# plus two `#build` tasks of packages with no `build` script, which
796+
# never execute), where the passthrough without `--only` was 62 MISS.
797+
# The step above is no longer load-bearing for correctness -- this
798+
# run schedules its own closure -- but it keeps that build on its
799+
# own stall-guard site, and here it replays. `--summarize` records
800+
# the slice only as the sha256 of `OS_TEST_SHARD` (`cliArguments`
801+
# is empty); measure-test-shard-timings.mjs resolves that digest
802+
# against the k/n the partitioner can emit and refuses one it
803+
# cannot, so a slice is never timed as the whole package.
804+
set -- env "OS_TEST_SHARD=$SLICE" pnpm turbo run test "--filter=$PKG" --concurrency=4 --summarize --log-order=stream
798805
fi
799806
LOGS="$LOGS $LOG"
800807
node scripts/run-with-stall-guard.mjs --log "$LOG" --stall-minutes 10 \

‎packages/cli/vitest.config.ts‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@
643643
// `node_modules` exclusion: an exact-path list matches nothing it does not name.
644644
import { defineConfig } from 'vitest/config';
645645
import path from 'path';
646-
import { parseCLI } from 'vitest/node';
646+
import { parseCLI, type TestUserConfig } from 'vitest/node';
647647
import {
648648
runFilterPreflight,
649649
runProjectCliOverridePreflight,
@@ -710,6 +710,35 @@ runProjectCliOverridePreflight({
710710
parse: parseCLI,
711711
});
712712

713+
// #19278 — THE FILE-LEVEL SLICE ARRIVES AS AN ENV VAR, NOT AS A PASSTHROUGH.
714+
// `scripts/partition-test-shards.mjs` slices this package (`FILE_SHARDED_PACKAGES`),
715+
// and Test Core runs each slice as `OS_TEST_SHARD=k/n turbo run test`. The
716+
// value reaches vitest HERE because vitest 4.1.11 reads no shard variable of its
717+
// own (no `VITEST_SHARD`: the variables it reads are enumerable in its dist),
718+
// and it reaches this process at all only because `turbo.json` declares
719+
// `OS_TEST_SHARD` in this package's `test` task `env` — which is also what
720+
// puts the slice in the task hash. Unset (every local run, the whole-package
721+
// leg, the nightly) it is `undefined`, and the run is unsharded as before; a
722+
// `--shard` on the command line still wins, because vitest merges the CLI
723+
// options OVER this block.
724+
//
725+
// Why a passthrough (`-- --shard=k/n`) is no longer the carrier: turbo folds a
726+
// run-level passthrough into the hash of every task in the run, so the slice
727+
// leg had to be `--only`, and `--only` drops the `build` closure out of the
728+
// test's hash — a slice could replay across the very upstream change that put
729+
// it in the affected set. An env declared on the task reaches only the task.
730+
//
731+
// ⚠️ Typed against vitest's CLI-options type (`TestUserConfig`), spread rather
732+
// than written as a literal key: vitest declares `shard` on its CLI options and
733+
// NOT on `InlineConfig`, the type of this `test` block (a literal `shard:` is
734+
// TS2769 here: "'shard' does not exist in type 'InlineConfig'"), yet it
735+
// resolves the two as one object (`deepMerge(configDefaults, test, cliOptions)`)
736+
// — measured on 4.1.11, it honours this key, projects included. The
737+
// partitioner's `--self-test` fails when a sliced package's config stops
738+
// reading the variable — it looks for the read in code position, so the read
739+
// lives at its use site in the `test` block below, not in a helper binding
740+
// that could outlive the spread.
741+
713742
export default defineConfig({
714743
resolve: {
715744
// Array form with an ANCHORED pattern, per the trap the gate documents:
@@ -784,6 +813,9 @@ export default defineConfig({
784813
],
785814
},
786815
test: {
816+
// The file-level slice, when Test Core runs one (#19278) — see the section
817+
// above `export default` for why it is spread and typed this way.
818+
...({ shard: process.env.OS_TEST_SHARD } satisfies Pick<TestUserConfig, 'shard'>),
787819
// A late console.* must not redden a green suite (#10374): vitest's worker
788820
// forwards console output over RPC and discards the promise, and a write
789821
// landing after teardown's rpcDone() snapshot is rejected into an unhandled

0 commit comments

Comments
 (0)