feat(qa): one shared vitest filter preflight for all eight project-declaring packages - #18121
Conversation
…claring packages Claude-Session: https://claude.ai/code/session_01TbSMtGzMrtPwh925wDEZd5 Co-authored-by: Claude <noreply@anthropic.com>
…ared-vitest-filter-preflight
…its turbo task Claude-Session: https://claude.ai/code/session_01TbSMtGzMrtPwh925wDEZd5 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 9 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 152 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 147716212017b158f9e0f776e60542c3d91bc2cc && git checkout 147716212017b158f9e0f776e60542c3d91bc2cc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ca7886047b27667122f9c2c44de3b6e67eb361fe cd75179f3519c2c1e7d96f1ac3b564697c4974e9 && git checkout -B drift-repro ca7886047b27667122f9c2c44de3b6e67eb361fe && git merge --no-ff cd75179f3519c2c1e7d96f1ac3b564697c4974e9
node scripts/docs-audit/affected-docs.mjs --json ca7886047b27667122f9c2c44de3b6e67eb361fe
|
Fixes #17978
One shared vitest filter preflight, serving all eight packages that declare vitest
projects. Direction A was ruled by thedomain:cliexecution seat under 验证策略 (5656963807) and confirmed by triage (5657101795), which also ruled the ownership question:What changed
packages/qa/vitest-filter-preflight— adomain:cli-owned home, ⛔ not rootscripts/and ⛔ not insidepackages/spec. It holds the ONE transcription of vitest'sTestProject.filterFiles;packages/qa/refd-timer-testkitis the shape precedent (private, no build,exportsstraight atsrc).packages/climigrates onto it and its local copy is deleted. Triage calls the move "a migration inside the owning lane". Measured behaviour-equivalent (below).packages/spec,packages/objectqlandpackages/coreare call sites only — one file each,vitest.config.ts, 32 insertions and 0 deletions, no new export and nothing undersrc/.@objectstack/cli), and the no---projecttypo case is covered and pinned.Three measurements that shaped the design
1. The precedent's shape does not transfer, re-confirmed at
a26a114d7.Populationstakes concrete paths, so a glob cannot be a member. 82 package roots underpackages/, 8 declaringprojects— the same eight, no drift.packages/cliis 1 of 8 with two exact-path projects, and only as a by-product of a tier walk it already had (#13504 / #14554). The other 7 each pair one explicit listLwith a glob complement ofL, so the missing component is identical across all seven.exactAndGlobPopulationsis that component.2. The glob project's population is a deliberate SUPERSET (the ruled shape, and it holds): a plain recursive walk of the
*.{test,spec}.?(c|m)[jt]s?(x)family minusnode_modules/dist, minusL. Matching nothing in a superset implies matching nothing in the real set, so a false accusation is structurally impossible and drift can only under-report. ⛔ Not vitest's own glob engine. Cost of the walk, measured: 0.3 ms (core) to 5.3 ms (spec, 507 test files) per config load.3. ⛔ Consumers import the module by RELATIVE PATH, never by its bare package name — and that is a measurement, not a preference. Vite bundles a config's relative imports through esbuild, which transpiles TypeScript; it externalises bare specifiers and leaves Node to load the resolved path, which here is a
.tsfile. The bare form works on this box and prints no warning — because Node 22.22 strips types by default. Re-run withNODE_OPTIONS=--no-experimental-strip-types:This repo declares
engines.node: ">=22.0.0", so on a supported Node the bare form turns a silent-drop defect into a total harness outage for that package. The two escapes are worse: building todistwould make eight harnesses' config load depend on build state, andpnpm --filter PKG exec vitest run FILE— the invocation this card is about — runs no build; authoring it as.mjswould drop the types, andCliParseResultOptionsis typed structurally precisely so a vitest upgrade renaming an option is a type error here instead of a silent decline.Per-package before/after — the preflight now speaks where it was silent
before=origin/main's config, run verbatim via--config vitest.before.config.tsbeside the real one so__dirnamestill resolves to the package root. Every run exits 0 in both legs; the defect is silence, not failure.--projecttypo namedpackages/typespackages/restpackages/runtimepackages/specpackages/objectqlpackages/corepackages/qa/dogfood¹ the only diff line is the
Durationdecimal."
FILTER SELECTED NOTHINGpresent" reads 0 before, 2 after in all three shapes in all seven (twice: once at config load, once from theexitlistener). ⭐ This extends the card's reproduction from the 4 packages the first dispatch probed to 7 of 7, includingspec,objectqlandcore, which had never been probed — still zero negative probes, vitest 4.1.11.packages/qa/dogfoodalso confirms the shape outside theREPO_TESTSidiom: its exact list is the inlineSHARED_SHOWCASEand its glob project isisolated.packages/cli— migration equivalence, not before/after. Itsbeforealready carried a preflight, so the reading asked whether the move changed anything a reader sees. Same notice count (2), same dropped-path count (4), samepnpm --filter @objectstack/cliin the notice (4), healthy control byte-identical. The lost-run output differs in exactly two words, twice:tier→project. Deliberate — "tier" is this package's private vocabulary while "project" is vitest's own and is correct for all eight.A healthy narrowed run still contributes zero bytes:
renderLostFilterNoticereturns the empty string and no writer is called and noexitlistener is registered.Tests, and the ablation that proves they can fail
pnpm --filter @objectstack/vitest-filter-preflight test— 2 files, 73 tests, pass.typecheckgreen, andtsc --listFilesputs 4 of 4 of the package's files in the program (src/index.ts, both tests,vitest.config.ts) — nothing hidden.test/config-wiring-sweep.test.tsis the anti-phantom sweep, and its population is derived, not listed: it walkspackages/for package-root vitest configs, masks their comments, and requires every one that declaresprojectsto invoke the preflight — so a ninth package is caught on the PR that adds it. The count is asserted as a floor, so a newcomer fails on its own wiring rather than on a number.Ablation (fix committed first; mutation proven on disk by occurrence count;
trap-restored with absolute paths; restore proven by blob hash, not by an exit code):runFilterPreflightcall frompackages/types/vitest.config.tsrunFilterPreflight({count 1 → 0; bloba5d708a1→ef829c5c1 failed | 40 passed, fails on that package's rowpackageNameat another packageaba7fc56a5d708a1(=HEAD),git diff HEADemptyPredicted direction was "turns red" and that is what both legs did. The second leg is the pin for the carried finding: a shared notice bound to one package sends every other package's reader to a command that runs the wrong suite.
Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackagainst the actual change set, every command run, exit codes recorded to disk, then reconciled:Plus this lane's standing full union, which the derivation does not name:
pnpm lintexit 0 atcd75179f35(eslint . --no-inline-config, the whole repo — not a narrowing).Two gates needed work rather than just passing:
check:cross-package-test-inputswent red and is satisfied, not routed around. The sweep reads outside its package by construction. It now declares its radius inscripts/cross-package-test-inputs.mjs— three globs underpackages/plus threescripts/files, withheldBywitnesses for the two globs built from a loop variable that the detector can see but cannot name — andturbo.jsoncarries the matching@objectstack/vitest-filter-preflight#testinputs with!**/node_modules/**. ⭐ Side effect worth having: the suite's turbo hash now really covers every package's config, so a config change re-runs the sweep.check:type-check-debtfirst exited 3 (OOM at--max-old-space-size=4096on a shared box) — recorded as NOT MEASURED, not as a pass, then re-run at 8192 and green: 77/81 packages type-checked, 55 raw errors, none above its recorded number,surplus: none.check:type-check-coveragegreen with the new package counted (81 packages, +1 vs the record).check-engine-split-ratio --days 90also exited 2 first — the gate refusing to measure on a shallow clone — and is green aftergit fetch --shallow-since, at 97.8%. Neither of those two numbers is about this diff; both are recorded so the zero above is real.No repo-level sweeper was added, so
check:pm-dispatch-gatesdoes not fire: the anti-phantom sweep is a vitest test inside the cli-owned home, which is also what keeps a gate-class file out of rootscripts/.skip-changeset— measured, not assumedNothing published moves. The new package is
private: true. For each of the seven touched published packages, everyfiles[]path was grepped for the subject symbols (runFilterPreflight,exactAndGlobPopulations,matchesVitestFilter,testFilesUnder): zero hits in all seven, with a positive control proving the grep works (ObjectLoggerinpackages/core/dist,defineStackinpackages/spec/dist).packages/cli'sfiles[]is["dist","README.md","CHANGELOG.md"]and the moved module lived at the package root, outside all three.packages/spec'sfiles[]includessrc/**/*.zod.ts; this diff touches no.zod.ts.pnpm-lock.yamlmoved by 12 insertions and 0 deletions — exactly one newimporters:block for the new package, no version change to anything installed — andpnpm install --frozen-lockfileexits 0, which is the proof it is tool-written rather than hand-edited.Acceptance notes
packages/clitype-checks its ownvitest.config.ts(viatsconfig.test.json); the other seven declareinclude: ["src/**/*"]and theirtsconfig.test.jsonsiblings declare["src*"], so no package-root config there is compiled by anything. Pre-existing —packages/cli/tsconfig.test.json's header records the same state for cli before Queue-flake anchor: test/vitest-tiers-partition.test.ts #14554 graduated it — and not widened by this change, but it does mean a@ts-expect-errorin any of those seven files would be a phantom check. Covered here by measurement instead: the wiring sweep asserts the call shape textually, and every one of the seven configs was loaded and run in both legs above. Noted, not filed — no declared contract requires a config to be type-checked, and the carrier is thecheck:type-check-coverageratchet's own graduation work.packages/. A package-root config outside that tree growingprojectswould not be swept — an under-report, the direction this card resolves uncertainty in. Scoping is what keeps the declared radius to one already-open root instead of openingexamples/andapps/roots inci.yml'scrosspkgfilter.Fences
Module home is
domain:cli-owned, so the stop-clause did not trigger: nothing landed in rootscripts/or insidepackages/spec. No gate weakened.packages/rest'sOS_REST_LOG: 'silent'froma26a114d7(#18090) is untouched in all three blocks. No file on #17630's surface was touched — this diff edits no test file inpackages/qa/dogfood. No governed surface is in the PR diff.Generated by Claude Code