Skip to content

Commit 3e3882c

Browse files
fix(qa): refuse a vitest CLI timeout override that cannot reach a projects package (#18853)
Fixes #18788 Clause-②: no ⚠️ **This is a RESUMED delivery.** A previous run's container was killed after it pushed one commit and before it opened a PR, ran a single test, or measured anything. That commit was treated here as an untrusted proposal: every claim in it was re-measured, one false statement in it was corrected, and its incomplete half was rewritten. What was verified and what was rewritten is itemised at the foot of this body. --- ## 1. The premise, reproduced FIRST — with four lit controls ⛔ The card's whole subject is that a reading which cannot fail is indistinguishable from one that passed, so the premise was reproduced before anything was built on it, against the BASE tree (`ad1f94e8ec`, this branch's merge base) rather than against the branch's own fix. The pre-fix `packages/cli/vitest.config.ts` was read out of that commit verbatim (`git hash-object` of the copy `837c2d1319` equals the BASE blob) and run under a temporary name, so no tracked file was mutated; the probe files were untracked and `git status` was empty afterwards. Probe: a `beforeAll` sleeping 500ms. vitest `4.1.11`. | # | run | exit | evidence | |---|---|---|---| | R1 | `@objectstack/cli`, `--project unit`, `--hookTimeout=1` | **0** | `Test Files 1 passed` | | R2 | `@objectstack/cli`, no `--project`, `--hookTimeout=1` | **0** | `Test Files 1 passed` | | R3 | ⭐ **LIT** `@objectstack/plugin-dev` (no `projects`), `--hookTimeout=1` | **1** | `Error: Hook timed out in 1ms.` | | R4 | ⭐ **LIT** `@objectstack/cli`, `--testTimeout=1`, 500ms in the test BODY | **1** | `Error: Test timed out in 1ms.` | | R5 | ⭐ **LIT** `@objectstack/cli`, `beforeAll(fn, 1)`, no flags | **1** | `Error: Hook timed out in 1ms.` | | R6 | ⭐ **LIT** `@objectstack/cli`, root `test.hookTimeout: 1` in the config | **1** | `Error: Hook timed out in 1ms.` | ⇒ The two zeros are READINGS. R3 proves the flag works where no `projects` narrowing exists; R4 proves the CLI-to-project path is ALIVE in this very package under the same config and invocation shape, so the defect is ALLOWLIST MEMBERSHIP and nothing wider; R5 and R6 prove the VALUE reaches the hook by both other spellings, so `hookTimeout` is not unenforceable here — only the CLI spelling of it is inert. ### The mechanism, read off the installed runner `resolveProjects` in `vitest/dist/chunks/cli-api.CnMVyzaz.js` builds each project's test config as `test: { ...options.test, ...cliOverrides }`, where `cliOverrides` is a CLOSED allowlist of **twenty** CLI option names. `testTimeout` is on it. `hookTimeout` and `teardownTimeout` are not. Everything else reaches the ROOT config and stops there. --- ## 2. The three routes, and why route 3 ⛔ The card deliberately does not rule on which side moves, and neither did the dispatch. All three were costed against the measurements above. **Route 1 — stop relying on the flag, rewrite the witness.** Available and measured to work: R5 and R6 are exactly the two spellings that DO bite in a `projects` package. What it cannot do is stop the NEXT reader reaching for the flag — this repo's own prior art reaches for it, and a dispatching seat handed it over as a suggestion on the round that filed this card. A remedy that is a habit is not a remedy. ⭐ Route 1 is therefore not discarded: it is what the refusal text NAMES, which is the one way a convention gets enforced rather than recommended. **Route 2 — forward the value into the projects.** The cheapest spelling is not even per-project: every one of these configs declares `extends: true`, so writing the parsed value into the ROOT `test` block reaches all of them — R6 measures exactly that, by hand. It works. Rejected on three counts: - **it invents a precedence rule vitest does not have, and then hides the day vitest acquires one.** For an allowlisted name vitest spreads the CLI value last, so the CLI wins over the project's own block; for a name vitest never carries there is no rule at all, so a forward has to pick one, for eight packages, on this repo's authority. And the pick goes stale silently: the day a vitest bump adds `hookTimeout` to `cliOverrides`, vitest's own override and the forward are BOTH live and nothing reddens. The refusal has the opposite failure mode — its transcription pin equals-checks the installed array, so that same bump turns it RED. **This is measured, not argued:** ablation B below puts `hookTimeout` on the transcribed allowlist and the pin goes red on cue. - **it fixes exactly the names it enumerates.** The allowlist is twenty names and vitest's CLI surface is not. A forward built for three names leaves the rest handing out the same false greens — the identical defect, narrower, now behind something called a fix. Widening the forward to every CLI option is a second transcription of vitest's option table, which is the multiplication `packages/qa/vitest-filter-preflight` exists to stop. - **it is a lenient consumer-side accommodation for an upstream contract**, which Prime Directive #12 refuses in the form it usually takes. **Route 3 — refuse the run, and name the spellings that do bite. CHOSEN.** Refusing costs nothing that was working: the flag measures nothing today, so no run loses a capability, and a run naming no such flag is byte-identical to the run it is today (pinned in both directions). What it buys is that the false clearance becomes impossible to READ — and because the notice carries route 1's two measured spellings, the reader leaves with the instrument they came for rather than a bare refusal. --- ## 3. ⚠️ Why the fix lives in `packages/qa/vitest-filter-preflight`, not in `packages/cli` The dispatch order neither scoped this package in nor out, so here is the measurement that justifies it rather than a preference. The fix needs a transcription of a private vitest code path (the twenty-name allowlist). This repo has already ruled once, in #17978, on where such a transcription goes and why there is exactly one: `matchesVitestFilter` — a transcription of `TestProject.filterFiles` — was pulled out of `packages/cli` into this package precisely because eight configs needed it and eight copies would drift from vitest and from each other, with every drift failing SILENTLY GREEN. That is the same failure direction as this card's defect. The concrete measurements that follow from putting it there: - **Cost to the consumer: ZERO new import lines.** All eight configs already import this module by relative path; the new function is re-exported from `src/index.ts`, so each config's wiring is one name in an existing brace list plus the call. - **The anti-phantom sweep already exists and already DERIVES its population.** `test/config-wiring-sweep.test.ts` walks `packages/` for configs declaring `projects` and requires the wiring. Putting the refusal anywhere else means either no sweep for it, or a second sweep. - **The alternative was costed.** Inlining the transcription in `packages/cli/vitest.config.ts` fixes one package and makes the other seven a copy-paste job — which is #17978's finding, reintroduced. ⇒ The package is justified by the prior ruling it is the product of, not by convenience. ⛔ If the seat still wants it narrowed, the narrowing is mechanical (move the module into `packages/cli`, drop seven wirings) and this section is the argument to overrule. --- ## 4. ⭐ The population is EIGHT, and all eight are wired The card names `packages/cli`; triage measured the population at eight and told the fixer so in its own words (「修的时候**总体是 8**,⛔ 不是 1」). The defect is a property of declaring `projects`, not of `packages/cli`, and that was measured in a SECOND package rather than assumed: | run | exit | evidence | |---|---|---| | `@objectstack/types`, 500ms `beforeAll`, `--hookTimeout=1` | **0** | `Test Files 1 passed` | | ⭐ **LIT** `@objectstack/types`, `beforeAll(fn, 1)`, no flag | **1** | `Error: Hook timed out in 1ms.` | The interrupted commit wired `packages/cli` only. Wiring one and leaving seven would have shipped the shared module's own sentence — "eight packages in this repo declare vitest `projects`, every one of them has this defect" — as a documented, unfixed defect in seven places, with nothing to catch them, since a sweep that covered the new call would then have needed a maintained exemption list for the other seven: the exact artefact #17978 removed. ⛔ The wiring is NOT the interesting half. A config can contain the call in code position and still refuse nowhere — a swallowed throw, a wrong argv source, a re-export resolving to a stub. So `config-wiring-sweep.test.ts` grew a BEHAVIOURAL leg and asks a real `vitest` child, per package, in both directions: ``` PACKAGE clean(exit/refusal) --hookTimeout=1(exit) names-own-pkg packages/cli exit=0 refusal=0 exit=1 refusal=1 ownname=2 packages/core exit=0 refusal=0 exit=1 refusal=1 ownname=2 packages/objectql exit=0 refusal=0 exit=1 refusal=1 ownname=2 packages/qa/dogfood exit=0 refusal=0 exit=1 refusal=1 ownname=2 packages/rest exit=0 refusal=0 exit=1 refusal=1 ownname=2 packages/runtime exit=0 refusal=0 exit=1 refusal=1 ownname=2 packages/spec exit=0 refusal=0 exit=1 refusal=1 ownname=2 packages/types exit=0 refusal=0 exit=1 refusal=1 ownname=2 ``` The refusal must name THAT subject's own manifest name — that is what makes the non-zero a reading, since a spawn failure, an unresolvable config or a missing build all exit non-zero too and none of them prints this text. ⛔ The clean-run leg is not optional: without it every other assertion in the file is satisfied by a config that refuses EVERYTHING, which would be a far worse defect than the one being fixed. `--filesOnly` globs test paths and never imports them, so neither leg depends on build state. 16 children, ~9s wall. --- ## 5. Ablations — these pins can fail, and it was watched Each leg: mutate → prove the mutation reached disk by an anchored count → run → restore → prove the restore by `git hash-object` equality against the HEAD blob. ⛔ No `dist` leg applies and that is a measurement, not an omission: `@objectstack/vitest-filter-preflight` is `private: true`, has **no** `build` script, and its `exports` points straight at `src`, which every consumer imports by relative path — there is no built artefact for a mutation to fail to reach. **Ablation A — delete the wiring from `packages/types/vitest.config.ts`.** On-disk proof: call sites before=1 after=0. Result: `Test Files 1 failed`, `Tests 2 failed | 63 passed` — exactly the two new legs for that subject, the source assertion and the behavioural refusal. Restored to blob `c2f03814c52b71f88fa1f8c90938a7db102e4e7e` (== HEAD). **Ablation B — put `hookTimeout` ON the transcribed allowlist.** On-disk proof: occurrences before=0 after=1. Result: `Test Files 2 failed | 1 passed` — the transcription pin red (`equals the cliOverrides array in the installed vitest`), the pure-function legs red, and **all eight** behavioural refusal legs red. ⭐ The discriminator legs (`--testTimeout` is not refused; the notice is the empty string when nothing is dropped) stayed GREEN, which is the right direction: the ablation removed a refusal, it did not make the module refuse everything. Restored to blob `6b078d5e61ee51113ec35d950caffe75ed09f55d` (== HEAD). **Restore leg:** `git diff HEAD --stat` empty, suite green again — `Test Files 3 passed (3)`, `Tests 111 passed (111)`. --- ## 6. Changeset: `skip-changeset`, and it rests on a measurement The dispatch asked this be decided by measuring, ⛔ never inferred from `files[]` or from a file's look. Two independent instruments, both with lit controls: **(a) The manifests.** `@objectstack/vitest-filter-preflight` is `private: true`, carries no `files[]` and has **no `build` script** — nothing in it is ever shipped. `@objectstack/dogfood` likewise. **(b) npm's OWN packing** (`npm pack --dry-run --json`), per published package, asked whether any path this PR changes is in the list npm would actually ship: | package | shipped files | `vitest.config*` | `test/*` | the card's test file | ⭐ POS CTRL (`package.json` + `dist/*`) | |---|---|---|---|---|---| | `cli` | 533 | 0 | 0 | 0 | **529** | | `core` | 18 | 0 | 0 | 0 | **15** | | `objectql` | 18 | 0 | 0 | 0 | **15** | | `rest` | 10 | 0 | 0 | 0 | **7** | | `runtime` | 10 | 0 | 0 | 0 | **7** | | `spec` | 2021 | 0 | 0 | 0 | **217** | | `types` | 16 | 0 | 0 | 0 | **13** | ⇒ Every one of this PR's thirteen paths is a vitest config, a test file, or a file in a private package. Zero of them ship, and the positive control is lit in every row, so the zeros are readings. **`skip-changeset` is the measured verdict**, and the label is applied on this PR. --- ## 7. ⚠️ `#18520` is NOT absorbed, and the TIER split did not move `packages/cli/vitest.config.ts` is touched, and the dispatch required this be stated explicitly. **The `unit` / `integration` TIER split is untouched** — no change to `vitest-tiers.ts`, to `integrationTestFiles` / `unitTestFiles`, to `OS_TEST_TIERS`, or to either project's `include`. The only edit to that file is the preflight call above the `defineConfig`. #18520's other half — "nightly-only tiers can never be reddened by a PR" — is reported, not done. ⛔ Not widened into. The new `packages/cli` pin lands in the `unit` tier by the derivation, not by a name: it imports `node:child_process` to ask vitest a question, which is the same classification `vitest-tiers-partition.test.ts` already carries. --- ## 8. ⛔ Files the dispatch fenced off — untouched `packages/cli/src/commands/compile.ts` (#18780 in flight), `packages/cli/src/utils/artifact-packages.ts` and `packages/cli/src/commands/lint.ts` (#18813) appear in no hunk of this branch's own commits. They appear in `git diff BASE...HEAD` only because `origin/main` was MERGED in — never rebased, never force-pushed, never amended, per the dispatch's hard constraint. --- ## 9. Verification - `pnpm --filter @objectstack/vitest-filter-preflight test` → **`Test Files 3 passed (3)`, `Tests 111 passed (111)`** - `pnpm --filter @objectstack/cli exec vitest run --project unit test/hook-timeout-override-refusal.test.ts` → **`Test Files 1 passed (1)`, `Tests 4 passed (4)`** - `pnpm --filter @objectstack/vitest-filter-preflight typecheck` → clean - The derived gate family (`node scripts/pm/dispatch-gates.mjs --commands`, 62 commands over the measured 13-path change set) — results in the round report. `check:cross-package-test-inputs`, `check:test-source-alias`, `check:tier-file-adoption`, `check:cli-test-child-env`, `check:published-files`, `check:nul-bytes` and `check:comment-mask-corpus` are the ones this diff most directly implicates; all green. - `check:dual-build-cjs-loads` reports **`PREREQUISITE NOT MET`** in this worktree — it reads built output and names `studio`, `client-react` and the connectors, packages this diff never touches. ⛔ Recorded as NOT MEASURED, not as a pass and not as a failure. CI checks out and builds fresh. --- ## 10. What was VERIFIED from the pre-existing commit, and what was REWRITTEN **Verified and kept** — `src/project-cli-override-preflight.ts`'s mechanism (the twenty-name allowlist, transcribed verbatim and confirmed against the installed `cli-api` chunk, order included); its refusal-over-forwarding conclusion; `test/project-cli-override-preflight.test.ts` (the transcription pin, the discriminator and the silence contract — all now actually run, 111 green, and ablated); the `src/index.ts` re-export; the `packages/cli` wiring and its behavioural pin. **Corrected** — the header's account of vitest's merge order. The commit claimed a forwarded value would override a project's own timeout "in the opposite direction from the one vitest documents". Read off `resolveProjects`, the CLI value is spread LAST for an allowlisted name, so the CLI already wins; the true objection is that vitest has NO rule for a name it never carries, so a forward invents one and then goes silently stale. The route-2 rejection is rebuilt on the three counts that survive the correction. **Rewritten / added** — the population. The commit fixed one of eight packages and measured none of it. Seven configs wired, the sweep grown a per-subject behavioural leg in both directions, the defect measured in a second package, both ablations, the npm-pack changeset measurement, and the declared division of labour between the card-local `packages/cli` pin and the population sweep. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3 --- _Generated by [Claude Code](https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 96fdac2 commit 3e3882c

13 files changed

Lines changed: 1072 additions & 2 deletions
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* `--hookTimeout` must never return a GREEN from this package again (#18788).
5+
*
6+
* ## What was measured, and why the green was the defect
7+
*
8+
* `packages/cli` declares vitest `test.projects`. vitest 4.1.11's
9+
* `resolveProjects` carries a CLOSED twenty-name allowlist of CLI options into a
10+
* project's test config and applies everything else to the ROOT config only.
11+
* `testTimeout` is on that list; `hookTimeout` and `teardownTimeout` are not. So
12+
* a probe `beforeAll` sleeping 500ms passed under `--hookTimeout=1` here — exit
13+
* 0, `1 passed`, `tests 505ms` — with and without `--project`, while the same
14+
* probe body under `@objectstack/plugin-dev`, which declares no `projects`,
15+
* exited 1 with `Hook timed out in 1ms.`
16+
*
17+
* ⭐ That flag is the instrument this repo's own prior art reaches for to
18+
* witness that a cold load has left every clocked window: the header of
19+
* `packages/plugins/plugin-dev/src/dev-plugin-security-enforcement-warning.test.ts`
20+
* reads a green under `--hookTimeout=1` AS THE PASS. In this package that green
21+
* could not fail, and a reading that cannot fail is indistinguishable from one
22+
* that passed. `packages/cli/src/commands/datasource/envelope-unwrap.test.ts`
23+
* already records the same measurement and had to fall back to a source
24+
* assertion because of it.
25+
*
26+
* `vitest.config.ts` now REFUSES such a run at config load, through the shared
27+
* `runProjectCliOverridePreflight` in `packages/qa/vitest-filter-preflight`
28+
* (whose header carries the mechanism, the full measured table and why the value
29+
* is NOT forwarded into the projects instead).
30+
*
31+
* ## ⛔ Why this pin spawns, and what each leg controls for
32+
*
33+
* A source assertion that the config still contains the call would pass against
34+
* a preflight that refuses nothing. The subject here is a BEHAVIOUR of this
35+
* package's own config load, so it is asked of a real `vitest` child — the same
36+
* instrument, and the same `execFileSync` + `childEnv()` shape, that
37+
* `vitest-tiers-partition.test.ts` already uses to ask vitest for its file lists.
38+
*
39+
* ⭐ Every leg below owes a control that can go the other way, because that is
40+
* the exact error this card is about:
41+
*
42+
* - `--hookTimeout` and `--teardownTimeout` must REFUSE — if the wiring is
43+
* deleted or the preflight stops refusing, these two go green and fail here;
44+
* - a run naming NO override must PASS — so the refusal is not unconditional,
45+
* which a pin asserting only the red half could never tell apart;
46+
* - `--testTimeout` must PASS — it is on vitest's allowlist and does reach the
47+
* projects (measured in this package: 500ms in a test BODY exits 1 with
48+
* `Test timed out in 1ms.`). This is what proves the detector discriminates
49+
* by ALLOWLIST MEMBERSHIP rather than by "a flag whose name ends in
50+
* Timeout", and a detector that refused it would be breaking a working
51+
* instrument.
52+
*
53+
* Runs in the `unit` tier and needs no built `dist/`: `vitest list --filesOnly`
54+
* only globs, and the two refusing legs abort at config load before that.
55+
* Importing `node:child_process` to ask vitest a question is not spawning the
56+
* CLI — the same classification `vitest-tiers-partition.test.ts` carries.
57+
*
58+
* ## ⚠️ The DECLARED overlap with the population sweep
59+
*
60+
* `packages/qa/vitest-filter-preflight/test/config-wiring-sweep.test.ts` asks
61+
* two of these four questions of EVERY package that declares `projects`, this
62+
* one included: that `--hookTimeout` is really refused, and that a run naming no
63+
* override really is not. That sweep owns the POPULATION — it derives it, so a
64+
* ninth package is caught on the PR that adds it — and it is where those two
65+
* legs are maintained.
66+
*
67+
* ⭐ What is NOT duplicated, and why this file is the one that owns it: the
68+
* DISCRIMINATOR. `--testTimeout` is on vitest's allowlist and does reach the
69+
* projects here — measured in this package, 500ms in a test body exits 1 with
70+
* `Test timed out in 1ms.` — so a detector that refused it would be breaking a
71+
* working instrument, and `--teardownTimeout` is the second knob the same
72+
* allowlist drops. Both readings are about THIS package's measured table, which
73+
* is the card's subject, so they are asked here rather than eight times.
74+
* ⛔ The two overlapping legs are kept rather than deleted because this file
75+
* must stand on its own as `packages/cli`'s record of the defect: a reader who
76+
* finds a refusal here needs the control that says it is not unconditional, in
77+
* the same file, or the record is exactly the kind of one-sided reading this
78+
* card is about.
79+
*/
80+
81+
import { execFileSync } from 'node:child_process';
82+
import { createRequire } from 'node:module';
83+
import { dirname, resolve } from 'node:path';
84+
import { fileURLToPath } from 'node:url';
85+
import { describe, expect, it } from 'vitest';
86+
import { childEnv } from './helpers/serve-process.js';
87+
88+
const HERE = dirname(fileURLToPath(import.meta.url));
89+
const PKG = resolve(HERE, '..');
90+
const require = createRequire(import.meta.url);
91+
const VITEST_ENTRY = resolve(dirname(require.resolve('vitest/package.json')), 'vitest.mjs');
92+
93+
/** The refusal's own headline, as `renderInertOverrideNotice` prints it. */
94+
const REFUSAL = 'TIMEOUT OVERRIDE CANNOT REACH THIS PACKAGE';
95+
96+
interface ChildRun {
97+
readonly status: number;
98+
readonly output: string;
99+
}
100+
101+
/**
102+
* `vitest list --filesOnly [flags]` against THIS package, never throwing.
103+
*
104+
* ⛔ `execFileSync` throws on a non-zero exit, and the non-zero exit is half of
105+
* what this file measures — so the status is read off the thrown error rather
106+
* than allowed to abort the case. `stdout` and `stderr` are joined because the
107+
* refusal is written to stderr at config load while vitest's own report of it
108+
* lands separately, and this file asserts on the text, not on the stream.
109+
*/
110+
function runVitestList(...flags: string[]): ChildRun {
111+
const args = [VITEST_ENTRY, 'list', '--filesOnly', ...flags];
112+
try {
113+
const stdout = execFileSync(process.execPath, args, {
114+
cwd: PKG,
115+
env: childEnv(),
116+
encoding: 'utf8',
117+
stdio: ['ignore', 'pipe', 'pipe'],
118+
});
119+
return { status: 0, output: stdout };
120+
} catch (error) {
121+
const e = error as { status?: number | null; stdout?: string; stderr?: string };
122+
// ⛔ A child that never ran (spawn failure) has a null status. Reporting
123+
// that as a refusal would be this card's own defect in a new place.
124+
expect(typeof e.status).toBe('number');
125+
return { status: e.status ?? -1, output: `${e.stdout ?? ''}${e.stderr ?? ''}` };
126+
}
127+
}
128+
129+
describe('#18788 a CLI timeout override that cannot reach a project is refused, not passed', () => {
130+
it('refuses `--hookTimeout`, naming the package and a spelling that does bite', () => {
131+
const run = runVitestList('--hookTimeout=1');
132+
133+
expect(run.status).not.toBe(0);
134+
expect(run.output).toContain(REFUSAL);
135+
expect(run.output).toContain('--hookTimeout');
136+
// The #17978 finding against the sibling preflight, held here too: a shared
137+
// notice must name the package it is refusing FOR, or it sends the reader to
138+
// a command that runs the wrong suite.
139+
expect(run.output).toContain('@objectstack/cli');
140+
// ⛔ Refusing without a remedy is a bare 404 (AGENTS.md, Route & surface
141+
// ownership §3). Both spellings named here are measured in this package.
142+
expect(run.output).toContain('extends: true');
143+
expect(run.output).toContain('beforeAll(fn, 1)');
144+
});
145+
146+
it('refuses `--teardownTimeout` — the other knob the same allowlist drops', () => {
147+
const run = runVitestList('--teardownTimeout=1');
148+
149+
expect(run.status).not.toBe(0);
150+
expect(run.output).toContain(REFUSAL);
151+
expect(run.output).toContain('--teardownTimeout');
152+
});
153+
154+
it('⭐ CONTROL — a run naming no override passes and says nothing', () => {
155+
// Without this leg the three above are satisfied by a config that refuses
156+
// everything, which would be a worse defect than the one being fixed.
157+
const run = runVitestList();
158+
159+
expect(run.status).toBe(0);
160+
expect(run.output).not.toContain(REFUSAL);
161+
// It really listed something, so the zero is a reading and not an empty run.
162+
expect(run.output).toMatch(/\.test\.ts/);
163+
});
164+
165+
it('⭐ CONTROL — `--testTimeout` is on vitest’s allowlist, reaches the projects, and is NOT refused', () => {
166+
// The discriminator. `--testTimeout=1` bounds test bodies here for real
167+
// (measured: 500ms in a test body exits 1 with `Test timed out in 1ms.`),
168+
// so refusing it would break a working instrument. `list` collects without
169+
// running, so the budget is never applied and this stays a config-load
170+
// question.
171+
const run = runVitestList('--testTimeout=1');
172+
173+
expect(run.status).toBe(0);
174+
expect(run.output).not.toContain(REFUSAL);
175+
});
176+
});

‎packages/cli/vitest.config.ts‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,10 @@
644644
import { defineConfig } from 'vitest/config';
645645
import path from 'path';
646646
import { parseCLI } from 'vitest/node';
647-
import { runFilterPreflight } from '../qa/vitest-filter-preflight/src/index.js';
647+
import {
648+
runFilterPreflight,
649+
runProjectCliOverridePreflight,
650+
} from '../qa/vitest-filter-preflight/src/index.js';
648651
import { integrationTestFiles, unitTestFiles } from './vitest-tiers.js';
649652

650653
// The two tiers, DERIVED from what the files DO — never written down — over
@@ -683,6 +686,30 @@ runFilterPreflight({
683686
parse: parseCLI,
684687
});
685688

689+
// #18788 — the same narrowing, the same failure direction, a different input.
690+
// `projects` also swallows a CLI TIMEOUT OVERRIDE: vitest 4.1.11 carries only a
691+
// closed twenty-name allowlist into a project config, `hookTimeout` and
692+
// `teardownTimeout` are not on it, and a run that names one uses the DEFAULT
693+
// budget and reports a pass that measured nothing. Measured in THIS package and
694+
// not recalled: a probe `beforeAll` sleeping 500ms passes under
695+
// `--hookTimeout=1` (with and without `--project`), while the same probe body
696+
// under `@objectstack/plugin-dev`, which declares no `projects`, exits 1 with
697+
// `Hook timed out in 1ms.`
698+
//
699+
// ⭐ That flag is the instrument this repo's own prior art reaches for to
700+
// witness that a cold load has left every clocked window — the header of
701+
// `packages/plugins/plugin-dev/src/dev-plugin-security-enforcement-warning.test.ts`
702+
// reads a GREEN under `--hookTimeout=1` as the pass. Here that green cannot
703+
// fail, so it is indistinguishable from one that passed, and this call is what
704+
// stops it being read: the run is REFUSED, loudly, naming the spellings that do
705+
// bite here. ⛔ Not forwarded into the projects below — the shared module's
706+
// header carries the three measured reasons that was rejected.
707+
runProjectCliOverridePreflight({
708+
argv: process.argv,
709+
packageName: '@objectstack/cli',
710+
parse: parseCLI,
711+
});
712+
686713
export default defineConfig({
687714
resolve: {
688715
// Array form with an ANCHORED pattern, per the trap the gate documents:

‎packages/core/vitest.config.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { parseCLI } from 'vitest/node';
77
import {
88
exactAndGlobPopulations,
99
runFilterPreflight,
10+
runProjectCliOverridePreflight,
1011
} from '../qa/vitest-filter-preflight/src/index.js';
1112

1213
// #16466 -- two vitest projects, two turbo tasks. `repo` owns the tests that read
@@ -44,6 +45,25 @@ runFilterPreflight({
4445
parse: parseCLI,
4546
});
4647

48+
// #18788 — the same narrowing, the same failure direction, a different input.
49+
// `projects` also swallows a CLI TIMEOUT OVERRIDE: vitest 4.1.11 carries only a
50+
// closed twenty-name allowlist into a project config, `hookTimeout` and
51+
// `teardownTimeout` are not on it, and a run naming one silently uses the
52+
// DEFAULT budget and reports a pass that MEASURED NOTHING. That flag is the
53+
// instrument this repo's own prior art reaches for to witness a cold load
54+
// leaving a clocked window, so the green it returns in a `projects` package is
55+
// indistinguishable from one that passed. The run is REFUSED instead, loudly,
56+
// naming the spellings that DO bite here — ⛔ never forwarded into the projects
57+
// below; the shared module's header carries the measured table, the three
58+
// routes and why this is the one. Every package that declares `projects` calls
59+
// this, and `test/config-wiring-sweep.test.ts` spawns a real vitest child per
60+
// package to prove the refusal is a behaviour rather than a spelling.
61+
runProjectCliOverridePreflight({
62+
argv: process.argv,
63+
packageName: '@objectstack/core',
64+
parse: parseCLI,
65+
});
66+
4767
export default defineConfig({
4868
test: {
4969
// Each project re-declares the root block's test options: a ROOT-level

‎packages/objectql/vitest.config.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { parseCLI } from 'vitest/node';
4747
import {
4848
exactAndGlobPopulations,
4949
runFilterPreflight,
50+
runProjectCliOverridePreflight,
5051
} from '../qa/vitest-filter-preflight/src/index.js';
5152

5253
// #16466 -- two vitest projects, two turbo tasks. `repo` owns the tests that read
@@ -84,6 +85,25 @@ runFilterPreflight({
8485
parse: parseCLI,
8586
});
8687

88+
// #18788 — the same narrowing, the same failure direction, a different input.
89+
// `projects` also swallows a CLI TIMEOUT OVERRIDE: vitest 4.1.11 carries only a
90+
// closed twenty-name allowlist into a project config, `hookTimeout` and
91+
// `teardownTimeout` are not on it, and a run naming one silently uses the
92+
// DEFAULT budget and reports a pass that MEASURED NOTHING. That flag is the
93+
// instrument this repo's own prior art reaches for to witness a cold load
94+
// leaving a clocked window, so the green it returns in a `projects` package is
95+
// indistinguishable from one that passed. The run is REFUSED instead, loudly,
96+
// naming the spellings that DO bite here — ⛔ never forwarded into the projects
97+
// below; the shared module's header carries the measured table, the three
98+
// routes and why this is the one. Every package that declares `projects` calls
99+
// this, and `test/config-wiring-sweep.test.ts` spawns a real vitest child per
100+
// package to prove the refusal is a behaviour rather than a spelling.
101+
runProjectCliOverridePreflight({
102+
argv: process.argv,
103+
packageName: '@objectstack/objectql',
104+
parse: parseCLI,
105+
});
106+
87107
export default defineConfig({
88108
test: {
89109
// Each project re-declares the root block's test options: a ROOT-level

‎packages/qa/dogfood/vitest.config.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { parseCLI } from 'vitest/node';
4444
import {
4545
exactAndGlobPopulations,
4646
runFilterPreflight,
47+
runProjectCliOverridePreflight,
4748
} from '../vitest-filter-preflight/src/index.js';
4849

4950
// Files proven eligible for the worker-shared plain showcase stack.
@@ -91,6 +92,25 @@ runFilterPreflight({
9192
parse: parseCLI,
9293
});
9394

95+
// #18788 — the same narrowing, the same failure direction, a different input.
96+
// `projects` also swallows a CLI TIMEOUT OVERRIDE: vitest 4.1.11 carries only a
97+
// closed twenty-name allowlist into a project config, `hookTimeout` and
98+
// `teardownTimeout` are not on it, and a run naming one silently uses the
99+
// DEFAULT budget and reports a pass that MEASURED NOTHING. That flag is the
100+
// instrument this repo's own prior art reaches for to witness a cold load
101+
// leaving a clocked window, so the green it returns in a `projects` package is
102+
// indistinguishable from one that passed. The run is REFUSED instead, loudly,
103+
// naming the spellings that DO bite here — ⛔ never forwarded into the projects
104+
// below; the shared module's header carries the measured table, the three
105+
// routes and why this is the one. Every package that declares `projects` calls
106+
// this, and `test/config-wiring-sweep.test.ts` spawns a real vitest child per
107+
// package to prove the refusal is a behaviour rather than a spelling.
108+
runProjectCliOverridePreflight({
109+
argv: process.argv,
110+
packageName: '@objectstack/dogfood',
111+
parse: parseCLI,
112+
});
113+
94114
export default defineConfig({
95115
test: {
96116
projects: [

‎packages/qa/vitest-filter-preflight/src/index.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,3 +482,30 @@ export function runFilterPreflight(options: {
482482
process.on('exit', () => write(notice));
483483
return notice;
484484
}
485+
486+
/**
487+
* The SECOND preflight this package owns, re-exported here so every consumer
488+
* keeps exactly one relative import line (`config-wiring-sweep.test.ts` asserts
489+
* that spelling, and its header carries the measurement behind it).
490+
*
491+
* Different defect, same shape and same seam: `runFilterPreflight` catches a
492+
* positional filter that a `projects` narrowing silently discarded, and
493+
* `runProjectCliOverridePreflight` catches a CLI TIMEOUT OVERRIDE that the same
494+
* narrowing silently discards (#18788). Both fail in the one direction AGENTS.md
495+
* ranks below having no verifier at all — green, having measured nothing — and
496+
* both are available in exactly the packages that declare `projects`, which is
497+
* why the two transcriptions of vitest's project resolution live in one package
498+
* rather than in eight configs.
499+
*/
500+
export {
501+
TIMEOUT_OVERRIDE_OPTIONS,
502+
PROJECT_CLI_OVERRIDES,
503+
inertTimeoutOverrides,
504+
renderInertOverrideNotice,
505+
runProjectCliOverridePreflight,
506+
} from './project-cli-override-preflight.js';
507+
export type {
508+
TimeoutCliOptions,
509+
TimeoutCliParse,
510+
TimeoutOverrideOption,
511+
} from './project-cli-override-preflight.js';

0 commit comments

Comments
 (0)