From 2c35b12545b6a3d1c11101af35655853cf033f58 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 03:56:09 +0000 Subject: [PATCH 1/3] ci(dogfood): carry the shard slice in a task env so the dogfood test hash sees its upstream closure --- .github/workflows/ci.yml | 30 +++++++++++++++------------- packages/qa/dogfood/vitest.config.ts | 25 ++++++++++++++++++++++- turbo.json | 2 +- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e10989d662..1be62ca258f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1704,11 +1704,16 @@ jobs: # Boots real example apps in-process (in-memory SQLite) and exercises them # through the real HTTP + service stack — catches runtime regressions that # build / unit tests / spec-liveness pass over (e.g. the #2018 tz-bucketing - # break, which was green on every static gate). The `--` args reach the - # package's `vitest run` and are hashed into the turbo task, so each - # shard caches its test leg independently -- and under `--only` that is - # the ONLY task the passthrough is hashed into; the build closure it used - # to scatter is built once, shard-independently, by the step above. + # break, which was green on every static gate). The shard slice travels in + # the task-declared env `OS_TEST_SHARD` (#20820, #19278's carrier): + # `turbo.json` lists it in `@objectstack/dogfood#test`'s `env`, so it is + # hashed into THAT task only, and `packages/qa/dogfood/vitest.config.ts` + # hands it to vitest's `shard`. There is no `--` passthrough and no + # `--only`: the run plans the `^build` closure with the same hashes as the + # step above (they replay), and the test task's hash therefore moves when + # an upstream package changes -- the old `--only` + `-- --shard=k/3` shape + # dropped the closure from the test's hash and a main-seeded cache entry + # could replay a shard green across an upstream-only change. # run-with-stall-guard: same wiring as Test Core (see the comment there; # #4250/#4314) — it tees to dogfood.log itself for the completeness guard # below, propagates the suite's real exit status (no `| tee` + pipefail), @@ -1728,17 +1733,14 @@ jobs: # globalPassThroughEnv entry or turbo strips it — see the script header. export VITEST_MAX_WORKERS="$(node scripts/vitest-worker-cap.mjs)" mkdir -p "$RUNNER_TEMP/stall-reports" - # `--only` (#16395's flag, #16886's site): the step above already - # built this package's dependency closure in a passthrough-free run, - # so this run must schedule the ONE task the passthrough is for. - # Without it turbo re-hashes the whole `^build` closure under - # `--shard=k/3` and rebuilds it, once per shard -- the step comment - # above carries the measurement. ⚠ That build step is load-bearing - # for this flag: with the closure unbuilt, this run fails LOUDLY - # here (imports resolve to a missing dist), never as a silent green. + # The slice is the ENV below, not a passthrough: see the step comment. + # The build step above still builds the closure once, so the closure's + # `build` tasks REPLAY here (identical hashes: nothing run-level is + # hashed into them any more) and only `dogfood#test` executes. + export OS_TEST_SHARD="${{ matrix.shard }}/3" node scripts/run-with-stall-guard.mjs --log "$RUNNER_TEMP/dogfood.log" --stall-minutes 10 \ --report-dir "$RUNNER_TEMP/stall-reports" -- \ - pnpm turbo run test --filter=@objectstack/dogfood --only --log-order=stream -- --shard=${{ matrix.shard }}/3 + pnpm turbo run test --filter=@objectstack/dogfood --log-order=stream # Dogfood boots real apps in-process, so a native/OOM abort is likelier # here than in the unit suites — and a shard that dies silently looks like diff --git a/packages/qa/dogfood/vitest.config.ts b/packages/qa/dogfood/vitest.config.ts index 82e8e513515..e6b617754fc 100644 --- a/packages/qa/dogfood/vitest.config.ts +++ b/packages/qa/dogfood/vitest.config.ts @@ -40,7 +40,7 @@ // HERE, in the harness, where the test author can see it. import { defineConfig } from 'vitest/config'; import path from 'path'; -import { parseCLI } from 'vitest/node'; +import { parseCLI, type TestUserConfig } from 'vitest/node'; import { exactAndGlobPopulations, runFilterPreflight, @@ -111,8 +111,31 @@ runProjectCliOverridePreflight({ parse: parseCLI, }); +// #20820 -- THE FILE-LEVEL SLICE ARRIVES AS AN ENV VAR, NOT AS A PASSTHROUGH. +// The `Dogfood Regression Gate (k/3)` leg runs `OS_TEST_SHARD=k/3 turbo run test`, +// the same carrier `packages/cli/vitest.config.ts` documents (#19278). The value +// reaches vitest HERE because vitest reads no shard variable of its own, and it +// reaches this process at all only because `turbo.json` declares `OS_TEST_SHARD` +// in THIS package's `@objectstack/dogfood#test` task `env` (a per-package task +// entry REPLACES the shared `test` entry's env, it does not extend it -- the +// shared declaration does not cover this package) -- which is also what puts the +// slice in the task hash. Unset (every local run) it is `undefined` and the run +// is unsharded; a `--shard` on the command line still wins, because vitest +// merges the CLI options OVER this block. +// +// Why a passthrough (`-- --shard=k/3`) is no longer the carrier: turbo folds a +// run-level passthrough into the hash of every task in the run, so the leg had +// to be `--only`, and `--only` drops the `^build` closure out of the test's hash +// -- a shard could replay a main-seeded cache entry across an upstream change +// that put it in the affected set. An env declared on the task reaches only the +// task. `shard` is typed through `TestUserConfig` because vitest declares it on +// its CLI options and not on `InlineConfig`; it sits on the ROOT `test` block +// (the projects below do not carry it), where vitest resolves it for all of them. export default defineConfig({ test: { + // The file-level slice, when the dogfood gate runs one (#20820) -- see the + // section above `export default` for why it is spread and typed this way. + ...({ shard: process.env.OS_TEST_SHARD } satisfies Pick), projects: [ { test: { diff --git a/turbo.json b/turbo.json index 534a082d647..f00160a390e 100644 --- a/turbo.json +++ b/turbo.json @@ -418,7 +418,7 @@ "@objectstack/dogfood#test": { "dependsOn": ["^build"], "outputs": [], - "env": ["OS_TEST_TIERS"], + "env": ["OS_TEST_TIERS", "OS_TEST_SHARD"], "inputs": [ "$TURBO_DEFAULT$", "!dist/**", From 55bd9e057901d7f3fdc21833307b924d4dec964e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 04:08:12 +0000 Subject: [PATCH 2/3] ci(dogfood): correct the build-step comment that named the retired --only shape --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1be62ca258f..77008e67472 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1653,8 +1653,11 @@ jobs: run: pnpm install --frozen-lockfile # ⛔ THE DOGFOOD PACKAGE'S DEPENDENCY CLOSURE IS BUILT HERE, IN A RUN - # THAT CARRIES NO PASSTHROUGH, so that the sharded run in the next step - # can be `--only` (#16395's shape, applied to this job by #16886). + # THAT CARRIES NO PASSTHROUGH. (#20820: the next step no longer needs this + # for correctness -- it carries its shard in the task env `OS_TEST_SHARD`, + # so it plans and replays this same closure itself, with no `--only`. The + # step stays as its own stall-guard site, and the closure replays there. + # The measurement below is the history of the `-- --shard=k/3` shape.) # # Turbo folds a run-level passthrough into the hash of EVERY task in the # run, not only the task that receives it -- and `-- --shard=k/3` is the From a4295f3f266e5afc153a81bff68e9fff25c46bfa Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 04:45:11 +0000 Subject: [PATCH 3/3] ci(dogfood): restate the Restore Turbo cache comment for the env-carried shard --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77008e67472..111d48cfa35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1632,8 +1632,8 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store-v3- - # Shard-scoped key: the turbo test hash differs per shard (pass-through - # args are part of the task hash). Restore-only on PRs — see the Restore + # Shard-scoped key: the turbo test hash differs per shard (the task-declared + # env `OS_TEST_SHARD` is part of the task hash). Restore-only on PRs — see the Restore # Turbo cache comment in the test job; the save step at the end of this # job seeds from main only. The job-level catch-all (same as Test Core's) # is what keeps a NEW shard number warm before main has ever saved it: @@ -1659,10 +1659,10 @@ jobs: # step stays as its own stall-guard site, and the closure replays there. # The measurement below is the history of the `-- --shard=k/3` shape.) # - # Turbo folds a run-level passthrough into the hash of EVERY task in the - # run, not only the task that receives it -- and `-- --shard=k/3` is the - # whole reason each shard gets its own invocation at all (the next step's - # comment says what those args are for). Measured on turbo 2.10.10, + # (History.) Turbo folds a run-level passthrough into the hash of EVERY + # task in the run, not only the task that receives it -- and the old leg's + # `-- --shard=k/3` was the whole reason each shard got its own invocation + # (the next step's comment says what carries the slice now). Measured on turbo 2.10.10, # `--filter=@objectstack/dogfood`, `turbo run test ... --dry=json` # (67 tasks: 66 `build` + 1 `test`): #