Skip to content

Commit 492598e

Browse files
test(lint): compute the SYSTEM_FIELDS live census once per file, under its own hook timeout (#19813)
Fixes #19807 Clause-②: no ## What is red and why The hourly full run on `main` (run 35829772446, `Test Core (2/6)` on `de4ed33f`) failed on exactly one test: `packages/lint/src/system-fields-consumers.test.ts:586` (the #8999 SYSTEM_FIELDS consumer census, 「the live population › is anchored to a real export…」) — `Error: Test timed out in 5000ms`. The shard then stopped and six scheduled packages never ran. The same failure, same shard, same test, is the only red in the last 100 hourly runs (09-21 04:01Z, 09-21 16:00Z, 09-23 07:03Z); no commit between the last green run (`fae870352`) and the red head touches `packages/lint/**`, and the file is green on PR shards, where `@objectstack/lint` runs with few neighbours. Cause, read from the file: `liveSources()` reads every `.ts` under `packages/lint/src` (201 files) and `censusUnionConsumers` parses each with the TypeScript compiler (`setParentNodes: true`); the six live tests each recomputed that whole census under vitest's default 5,000 ms per-test wall, and the cold run crossed it on a loaded full-battery shard (13 packages, `--concurrency=4` on 4 vCPUs). ## What changed One file, test-only: the live census is computed ONCE per file in a `beforeAll` that carries its own explicit timeout (60,000 ms, sized from the measured cold run of ~1 s on a quiet box with headroom for a contended shard), and the six live tests read that one result. `censusUnionConsumers`, `liveSources()`, the LEDGER rows, the PROVENANCE_API pin and every assertion are unchanged; no walk is narrowed, no test skipped, no file-wide or package-wide `testTimeout` raised. ## Verification - Branch pushed by the dispatched dev (`session_01XPUwWGnmky4PXPofNsf2w7`); its report with the measured before/after durations follows on #19807. - CI on this head is the judge: `Test Core` shards (the `@objectstack/lint` package suite), `Lint & Repo Gates`, type checks. - Not governed (`packages/lint/src/**` is on no register surface); ordinary queue landing after the dispatching seat's ACCEPT. Related, not changed here: #16465 / #16468 (shard-duration alarm and per-package suite-duration ratchet, `pm:queue` in `domain:devx`), and ten other test files that walk directories or parse with the TypeScript compiler under the default wall (cli ×5, spec ×4, service-analytics ×1) — none has gone red; the same one-computation shape applies if one does. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_0129ZpnaBcYZZ51rCvQiXg6C Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0e06f3b commit 492598e

1 file changed

Lines changed: 20 additions & 7 deletions

File tree

‎packages/lint/src/system-fields-consumers.test.ts‎

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ import { fileURLToPath } from 'node:url';
8484
// (`lint-startup-registry-verdict.ts`). A test never ships — `tsup.config.ts`
8585
// builds only `src/index.ts` and `src/runtime.ts` — so it can import it plainly.
8686
import ts from 'typescript';
87-
import { describe, expect, it } from 'vitest';
87+
import { beforeAll, describe, expect, it } from 'vitest';
8888

8989
const srcDir = dirname(fileURLToPath(import.meta.url));
9090

@@ -523,6 +523,19 @@ const FIXTURES = new Map<string, string>([
523523
const fileOf = (c: Census): string[] => c.consumers.map((x) => x.file);
524524

525525
describe('SYSTEM_FIELDS consumer census (#8999)', () => {
526+
// ── The live census, computed ONCE per file. `liveSources()` reads every
527+
// `.ts` under `src/` and `censusUnionConsumers` parses each one with the
528+
// TypeScript compiler (`setParentNodes: true`); recomputing that inside
529+
// each of the six live tests put the cold run under vitest's default
530+
// 5,000 ms per-test wall, which a loaded CI shard crossed. The hook
531+
// carries its own explicit timeout, sized from the measured cold run
532+
// (~1 s on a quiet box) with headroom for a contended one; the tests read
533+
// the one result. The walk and the analyzer are unchanged.
534+
let live: Census;
535+
beforeAll(() => {
536+
live = censusUnionConsumers(liveSources());
537+
}, 60_000);
538+
526539
// ── Positive controls. These run FIRST and on synthetic input, because a
527540
// matcher that stopped matching produces the same clean green as a clean
528541
// tree, and this whole card exists because an instrument could not see
@@ -585,14 +598,14 @@ describe('SYSTEM_FIELDS consumer census (#8999)', () => {
585598
describe('the live population', () => {
586599
it('is anchored to a real export, so an empty answer cannot be a false green', () => {
587600
expect(
588-
censusUnionConsumers(liveSources()).seeded,
601+
live.seeded,
589602
`${ORIGIN} no longer exports ${ORIGIN_EXPORT}. Every assertion below judges a population seeded from ` +
590603
'that name; unseeded, they would all pass on an empty set. Repoint the seed, do not delete this file.',
591604
).toBe(true);
592605
});
593606

594607
it('matches the ledger', () => {
595-
const found = censusUnionConsumers(liveSources());
608+
const found = live;
596609
const undeclared = found.consumers.filter((c) => !(c.file in LEDGER));
597610
expect(
598611
undeclared.map((c) => `${c.file} [${c.reach.join('+')}] via ${c.bindings.map((b) => `${b.name}@${b.line} <- ${b.via}`).join('; ')}`),
@@ -607,7 +620,7 @@ describe('SYSTEM_FIELDS consumer census (#8999)', () => {
607620
});
608621

609622
it('records how each consumer reaches the union', () => {
610-
for (const c of censusUnionConsumers(liveSources()).consumers) {
623+
for (const c of live.consumers) {
611624
// An un-ledgered file is already named, with its full path to the
612625
// union, by `matches the ledger`. Skipping it here keeps that one
613626
// actionable message from being buried under TypeErrors from the
@@ -624,14 +637,14 @@ describe('SYSTEM_FIELDS consumer census (#8999)', () => {
624637
// token. If this file ever stops being in-scope, the census is broken —
625638
// not fixed.
626639
const control = 'validate-flow-node-writes.ts';
627-
const found = censusUnionConsumers(liveSources()).consumers.find((c) => c.file === control);
640+
const found = live.consumers.find((c) => c.file === control);
628641
expect(found, `${control} is the #8999 positive control and must be in scope`).toBeDefined();
629642
expect(found?.reach).toEqual(['transitive']);
630643
expect(readFileSync(join(srcDir, control), 'utf8')).not.toContain(ORIGIN_EXPORT);
631644
});
632645

633646
it('covers all three laundering shapes, so a half-broken analyzer cannot pass', () => {
634-
const reaches = new Set(censusUnionConsumers(liveSources()).consumers.flatMap((c) => c.reach));
647+
const reaches = new Set(live.consumers.flatMap((c) => c.reach));
635648
for (const shape of ['direct', 'derived', 'transitive'] as const) {
636649
expect(reaches, `no consumer reached the union by '${shape}' — the analyzer lost a propagation path`).toContain(shape);
637650
}
@@ -650,7 +663,7 @@ describe('SYSTEM_FIELDS consumer census (#8999)', () => {
650663
});
651664

652665
it('each consumer asks it, or the ledger says why not', () => {
653-
for (const c of censusUnionConsumers(liveSources()).consumers) {
666+
for (const c of live.consumers) {
654667
const row = LEDGER[c.file]; // see `records how each consumer reaches the union`
655668
if (!row) continue;
656669
const src = readFileSync(join(srcDir, c.file), 'utf8');

0 commit comments

Comments
 (0)