Skip to content

Commit 0dd87b9

Browse files
claude[bot]claude
andauthored
test(lint): pay the startup-verdict corpus sweep once, under a stated hook budget (#10911)
The corpus read + scan (1880 files / 28.15 MB) ran twice, once inside each of the two corpus cases, sharing nothing — and both cases sat under vitest's default 5000ms per-test timeout. On a loaded merge-queue shard the first case measured 9144ms and ejected PR #10733, which never touched packages/lint, from the queue; every entry behind it rebuilt. Hoist the sweep into one beforeAll and give it an explicit, commented budget: - Work removed: file `tests` total 1441ms -> 816ms locally; the largest thing measured against the per-test budget drops from 972ms to 2ms. - The budget is a hook timeout, deliberately loose (60s ~= 6.5x the worst wall-clock ever observed for this sweep). It is a liveness backstop, not a performance tripwire — a snug budget is what ejected an unrelated PR, and the work grows with the repo (1872 files at 12:23Z, 1880 four hours later) while the wall-clock varies with shard load. Sharing is only sound because neither case mutates what the other reads, so the findings array and every finding in it are frozen, and the shared value starts `undefined` rather than `[]` behind a `corpusFindings()` accessor that throws. That last part is a third false green the file now refuses: a case reading a sweep that never ran would otherwise print as a clean audit over nothing. Both gate cases still fire individually — proven by ablation, see the PR body. Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt Co-authored-by: Claude <noreply@anthropic.com>
1 parent e85182d commit 0dd87b9

1 file changed

Lines changed: 77 additions & 8 deletions

File tree

packages/lint/src/lint-startup-registry-verdict.corpus.test.ts

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// enforces the SERVICE-registry half of the same family and is untouched; see
1414
// the rule module for the measured division of labour between the two.
1515
//
16-
// Two false greens this is built to refuse:
16+
// Three false greens this is built to refuse:
1717
//
1818
// 1. **A corpus that was never read.** An unreadable directory would silently
1919
// shrink the sweep while the file count stayed comfortably non-zero, and the
@@ -24,10 +24,16 @@
2424
// cannot be told apart from a dead one (#4690), and this one has been green
2525
// from its first commit. `the sweep can still fire` therefore pushes a
2626
// known-bad source through the SAME sweep function the corpus goes through.
27+
// 3. **A shared sweep that never ran.** The corpus read + scan is paid ONCE
28+
// for the whole file (#10838) instead of once per case, so the two corpus
29+
// cases now read the same findings. That introduces (1)'s failure shape one
30+
// level up: a case reading a sweep that did not happen sees zero findings
31+
// and prints as a clean audit. So the shared value starts `undefined`, not
32+
// `[]`, and every reader goes through `corpusFindings()`, which throws.
2733
import { readdirSync, readFileSync, statSync } from 'node:fs';
2834
import { dirname, join, relative } from 'node:path';
2935
import { fileURLToPath } from 'node:url';
30-
import { describe, expect, it } from 'vitest';
36+
import { beforeAll, describe, expect, it } from 'vitest';
3137

3238
import {
3339
findStartupRegistryVerdicts,
@@ -81,23 +87,88 @@ function sweep(sources: Array<{ file: string; source: string }>): StartupRegistr
8187
return sources.flatMap(({ file, source }) => findStartupRegistryVerdicts(source, { file }));
8288
}
8389

90+
/**
91+
* The corpus sweep's budget, in milliseconds. Deliberately a HOOK timeout and
92+
* deliberately loose — both halves are the decision, so both are written down.
93+
*
94+
* What it is sized against (#10838). The read + scan used to run TWICE, once
95+
* inside each corpus case, under vitest's default 5000ms PER-TEST timeout. On a
96+
* loaded merge-queue shard (that run reported `import 106.30s`) the first case
97+
* measured 9144ms: the sweep is synchronous, so the timer cannot interrupt it —
98+
* vitest lets it run to completion and then fails it for overrunning. PR #10733,
99+
* whose diff never touched `packages/lint`, was ejected from the merge queue for
100+
* it and passed on requeue; every entry queued behind it rebuilt.
101+
*
102+
* Measured on this repo 2026-08-21, corpus 1880 files / 28.15 MB: cold sweep
103+
* (including the lazy ~9 MB `typescript` load the rule defers until it has
104+
* source in hand) 986ms; warm sweep 465ms / 440ms. The queue-shard wall-clock
105+
* above is ~9.2x the local cold number. Note what that says about the fix:
106+
* sharing removes one WARM sweep (locally, file `tests` total 1441ms -> 816ms),
107+
* while relocating the budget is what removes the ejection. Projecting the
108+
* ejecting run onto the new shape, the hook would do what its case 1 did
109+
* (~9144ms) and clear vitest's default 10000ms hook timeout by 856ms — still a
110+
* near-threshold budget on a shard whose load is the variable, which is why the
111+
* number below is stated rather than defaulted.
112+
*
113+
* Why 60s and not something snug. This is a LIVENESS backstop — a wedged sweep
114+
* must not pin a worker forever — and explicitly NOT a performance tripwire. A
115+
* budget sized close to the observed cost is exactly what ejected an unrelated
116+
* PR: the work grows with the repo (1872 files when the card was written at
117+
* 12:23Z, 1880 four hours later) while the wall-clock varies with shard load, so
118+
* a snug number is guaranteed to red on somebody else's PR eventually. 60s is
119+
* ~6.5x the worst wall-clock this sweep has ever been observed to take and ~60x
120+
* the local one. The cost stays visible without a tripwire: it is paid in one
121+
* hook now, and a hook's time lands in the file's own duration (the `tests`
122+
* aggregate vitest prints per run), which is where a corpus-cost trend shows up.
123+
* The per-case numbers, by contrast, now read ~1ms — measured, not assumed: a
124+
* green run does NOT print hook durations, so do not go looking for one.
125+
*/
126+
const CORPUS_SWEEP_BUDGET_MS = 60_000;
127+
84128
describe('startup open-vocabulary verdicts across packages/ (#4776)', () => {
85129
const stat = statSync(packagesDir);
86130
expect(stat.isDirectory(), `${packagesDir} must be a directory — the sweep's verdict is drawn from reading it`).toBe(
87131
true,
88132
);
89133
const files = collectSourceFiles(packagesDir);
90134

135+
/**
136+
* The findings, swept once for the whole file.
137+
*
138+
* Sharing is sound only because neither reader mutates what the other reads:
139+
* both derive (`filter`, `map`) and write nothing. A comment cannot hold that
140+
* open against a later edit, so the array and every finding in it are frozen —
141+
* a mutating edit throws here (this module is ESM, so strict mode) instead of
142+
* silently draining the other case of what it was supposed to check.
143+
*
144+
* `undefined` rather than `[]` on purpose: see false green 3 in the header.
145+
*/
146+
let sweepResult: readonly StartupRegistryVerdictFinding[] | undefined;
147+
148+
beforeAll(() => {
149+
const findings = sweep(
150+
files.map((file) => ({ file: relative(repoRoot, file), source: readFileSync(file, 'utf8') })),
151+
).map((finding) => Object.freeze(finding));
152+
sweepResult = Object.freeze(findings);
153+
}, CORPUS_SWEEP_BUDGET_MS);
154+
155+
function corpusFindings(): readonly StartupRegistryVerdictFinding[] {
156+
if (sweepResult === undefined) {
157+
throw new Error(
158+
'the corpus sweep did not run — this case would otherwise report a clean audit over a corpus it never swept (#10838)',
159+
);
160+
}
161+
return sweepResult;
162+
}
163+
91164
it('reads a non-empty corpus', () => {
92165
// A zero-file sweep returns zero findings and would otherwise print as a
93166
// clean audit over nothing at all.
94167
expect(files.length).toBeGreaterThan(500);
95168
});
96169

97170
it('no package records a verdict the boot can still contradict', () => {
98-
const findings = sweep(
99-
files.map((file) => ({ file: relative(repoRoot, file), source: readFileSync(file, 'utf8') })),
100-
);
171+
const findings = corpusFindings();
101172
const unledgered = findings.filter((f) => !(`${f.path}::${f.rule}` in LEDGER));
102173

103174
expect(
@@ -112,9 +183,7 @@ describe('startup open-vocabulary verdicts across packages/ (#4776)', () => {
112183

113184
it('no ledger entry is stale', () => {
114185
// A ledger that outlives its site is a standing permission nobody reviewed.
115-
const findings = sweep(
116-
files.map((file) => ({ file: relative(repoRoot, file), source: readFileSync(file, 'utf8') })),
117-
);
186+
const findings = corpusFindings();
118187
const live = new Set(findings.map((f) => `${f.path}::${f.rule}`));
119188
const stale = Object.keys(LEDGER).filter((key) => !live.has(key));
120189
expect(stale, `stale LEDGER entr(ies) — the site is fixed, delete the line: ${stale.join(', ')}`).toEqual([]);

0 commit comments

Comments
 (0)