test(#2126): fix #1259 real-eslint CPU starvation — lint a non-type-aware .cjs clean target

The prohibition-enforcement real-runner tests linted src/clock.cts (a .cts) as
their clean target. Under eslint.config.mjs's type-aware block for src/**/*.cts
(recommendedTypeChecked + parserOptions.project: tsconfig.build.json), each eslint
spawn loaded the WHOLE tsconfig.build.json program (~2s, CPU-heavy). The
real-runner tests spawn eslint repeatedly; under --test-concurrency those
full-program type-checks oversubscribed the bench CPU and blew the 60s subprocess
bound -> fail-closed (intermittent, load-dependent — passed 24241/24241 in an
earlier run, failed here).

Root fix (not a retry/timeout bandaid; measured projectService = no faster since
a single-file .cts lint still loads type info): add tests/_ff_lint_clean.cjs, a
KNOWN-CLEAN lint-scoped .cjs companion to _ff_lint_violation.cjs, with a
flat-config block enabling local/no-source-grep so the clean pass stays
non-vacuous. Repoint the 6 src/clock.cts real-runner usages (5 targets + the FF-02
toothless violationFixture) at it. Each spawn is now ~0.8s non-type-aware (no
whole-program load) — starvation removed. All 6 tests' semantics verified
in-process (SF-01 greens; toothless/fail-closed stay unverified); full-repo
`eslint .` green.

Refs #2126, #1259

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-07-09 23:41:04 -04:00
parent 119702ff29
commit 6e7e3111fb
3 changed files with 44 additions and 12 deletions

View File

@@ -399,4 +399,17 @@ export default tseslint.config(
languageOptions: { sourceType: 'commonjs', globals: { ...globals.node } },
rules: { 'local/no-source-grep': 'error' },
},
// ── #2126 lint-rule CLEAN fixture ───────────────────────────────────────────
// `tests/_ff_lint_clean.cjs` is the KNOWN-CLEAN companion to the violation fixture: the
// prohibition-enforcement real-runner tests lint it as their non-vacuous "clean target" instead of
// a type-aware `src/**/*.cts` file, so each eslint spawn is ~0.8s (non-type-aware) not ~2s
// (whole-tsconfig-program load) — removing the CPU starvation that blew the 60s bound under
// --test-concurrency. Rule enabled (as error) so the pass is non-vacuous; the file is clean so it
// greens. PLAIN `.cjs`, kept OFF the `*.test.cjs` runner glob. (#2126)
{
files: ['tests/_ff_lint_clean.cjs'],
plugins: { local: localPlugin },
languageOptions: { sourceType: 'commonjs', globals: { ...globals.node } },
rules: { 'local/no-source-grep': 'error' },
},
);

19
tests/_ff_lint_clean.cjs Normal file
View File

@@ -0,0 +1,19 @@
// PERMANENT LOAD-BEARING FIXTURE for #1259 / #2126 — DO NOT delete or rename to `*.test.cjs`.
//
// A KNOWN-CLEAN, lint-scoped `.cjs` companion to `_ff_lint_violation.cjs`. It has NO
// `local/no-source-grep` violation, so the prohibition-enforcement real-runner tests can use it as
// the "clean target" for a NON-VACUOUS pass — the rule RUNS on it (enabled via the flat-config
// block below) and finds nothing.
//
// Why a `.cjs` and not `src/clock.cts`: linting a `src/**/*.cts` file is type-aware
// (`recommendedTypeChecked` + `parserOptions.project`), which loads the whole `tsconfig.build.json`
// program on every eslint spawn (~2s, CPU-heavy). The real-runner tests spawn eslint repeatedly and,
// under `--test-concurrency`, those full-program type-checks oversubscribe the bench CPU and blow the
// 60s subprocess bound (#2126). A plain `.cjs` is linted non-type-aware (~0.8s) — same coverage of
// the AST-only `no-source-grep` rule, no starvation.
//
// PLAIN `.cjs` (NOT `*.test.cjs`) on purpose — same reason as the violation fixture: keep it OFF the
// `node --test` runner glob so it is only ever linted, never executed.
'use strict';
module.exports = {};

View File

@@ -716,13 +716,13 @@ describe('prohibition-enforcement REAL runner end-to-end (#1259)', () => {
const enforce = require(ENFORCEMENT_LIB);
// Migrated to the SHIPPING prover (#1279): the default real prover lints the committed
// `_ff_lint_violation.cjs` violationFixture (the rule fires -> fail-first proven) while the
// clean runCheck lints src/clock.cts (no violation -> non-vacuous pass). Both via real eslint.
// clean runCheck lints tests/_ff_lint_clean.cjs (no violation -> non-vacuous pass). Both via real eslint.
const result = enforce.runProhibitionEnforcement(
TEST_TIER,
{
kind: 'lint-rule',
rule: 'local/no-source-grep',
target: 'src/clock.cts',
target: 'tests/_ff_lint_clean.cjs',
failFirst: true,
violationFixture: path.join('tests', '_ff_lint_violation.cjs'),
},
@@ -759,13 +759,13 @@ describe('prohibition-enforcement REAL runner end-to-end (#1259)', () => {
const enforce = require(ENFORCEMENT_LIB);
// No injected runCheck/proveFailFirst: the default prover lints the committed
// `_ff_lint_violation.cjs` (the rule fires -> fail-first proven) AND the default runner lints
// the clean `src/clock.cts` (no violation -> non-vacuous pass). BOTH directions via real eslint.
// the clean `tests/_ff_lint_clean.cjs` (no violation -> non-vacuous pass). BOTH directions via real eslint.
const result = enforce.runProhibitionEnforcement(
TEST_TIER,
{
kind: 'lint-rule',
rule: 'local/no-source-grep',
target: 'src/clock.cts',
target: 'tests/_ff_lint_clean.cjs',
violationFixture: path.join('tests', '_ff_lint_violation.cjs'),
},
{ cwd: process.cwd() },
@@ -780,7 +780,7 @@ describe('prohibition-enforcement REAL runner end-to-end (#1259)', () => {
test('FULL producer (real): lint-rule hard-gates on a TOOTHLESS violationFixture (rule does not flag it) (FF-02 wrong-direction)', () => {
const enforce = require(ENFORCEMENT_LIB);
// The "violation fixture" is a CLEAN in-tree file (src/clock.cts) the rule does NOT flag, so the
// The "violation fixture" is a CLEAN in-tree file (tests/_ff_lint_clean.cjs) the rule does NOT flag, so the
// default prover cannot prove fail-first -> the producer must hard-gate (never green), even though
// the clean target itself would pass the runner. A toothless guard is not a guard.
const result = enforce.runProhibitionEnforcement(
@@ -788,8 +788,8 @@ describe('prohibition-enforcement REAL runner end-to-end (#1259)', () => {
{
kind: 'lint-rule',
rule: 'local/no-source-grep',
target: 'src/clock.cts',
violationFixture: 'src/clock.cts',
target: 'tests/_ff_lint_clean.cjs',
violationFixture: 'tests/_ff_lint_clean.cjs',
},
{ cwd: process.cwd() },
);
@@ -807,8 +807,8 @@ describe('prohibition-enforcement REAL runner end-to-end (#1259)', () => {
{
kind: 'lint-rule',
rule: 'local/no-source-grep',
target: 'src/clock.cts',
violationFixture: 'src/clock.cts',
target: 'tests/_ff_lint_clean.cjs',
violationFixture: 'tests/_ff_lint_clean.cjs',
},
{ cwd: process.cwd(), mode },
);
@@ -1009,11 +1009,11 @@ describe('prohibition-enforcement defaultProveFailFirst REAL prover (#1279)', ()
test('lint-rule: a CLEAN violationFixture (rule does not flag) is NOT proven (FF-02 toothless direction)', () => {
const enforce = require(ENFORCEMENT_LIB);
// src/clock.cts is a clean in-tree source with no no-source-grep violation. If a "violation
// tests/_ff_lint_clean.cjs is a clean in-tree source with no no-source-grep violation. If a "violation
// fixture" does not actually trigger the rule, the rule is toothless on it → not a guard → not
// proven → must hard-gate.
const proof = enforce.defaultProveFailFirst(
{ kind: 'lint-rule', rule: 'local/no-source-grep', target: 'src/clock.cts', violationFixture: 'src/clock.cts' },
{ kind: 'lint-rule', rule: 'local/no-source-grep', target: 'tests/_ff_lint_clean.cjs', violationFixture: 'tests/_ff_lint_clean.cjs' },
process.cwd(),
);
assert.equal(proof.provenFailFirst, false,
@@ -1023,7 +1023,7 @@ describe('prohibition-enforcement defaultProveFailFirst REAL prover (#1279)', ()
test('lint-rule: no violationFixture -> not proven (FF-05 fail-closed)', () => {
const enforce = require(ENFORCEMENT_LIB);
const proof = enforce.defaultProveFailFirst(
{ kind: 'lint-rule', rule: 'local/no-source-grep', target: 'src/clock.cts' }, // no violationFixture
{ kind: 'lint-rule', rule: 'local/no-source-grep', target: 'tests/_ff_lint_clean.cjs' }, // no violationFixture
process.cwd(),
);
assert.equal(proof.provenFailFirst, false, 'no violationFixture -> cannot prove -> hard-gate');