B4: --case selects loop-registered cases; an unknown id exits before any process

caseSelection() resolves the one paid file that registers a case and how a
trial selects it: by its literal Bun test name, by the id a registration loop
names its test at runtime (coverage audits, TPA, journeys), or by running the
whole file when it registers no other case (plan-mode-no-op, whose test names
are not the id, so the old name pattern ran nothing). A whole-file case runs
one file shard per trial and is judged by shard status, as CI judges it.
--list prints the selection. Every E2E_TIERS id now resolves except
carve-section-loading, which fifteen files register (CI runs each as its own
shard) and which still fails with the owning files named.
This commit is contained in:
garrytan committed 2026-10-04 17:54:29 +00:00
1 parent c8115df6dd
commit 4ade622630
3 files changed
+102 -23

No files matched your search

+53 -19
View File
@@ -64,14 +64,39 @@ import { DEFAULT_JOBS, type PaidCaseSelection, type PaidTier, ROOT, type RunShar
// ─── Local diagnosis: one case through the CI panel runner (A9) ────────────
/** The one paid file that statically registers `id`, else a thrown reason. */
/** How --case runs exactly one case: trials selected by its Bun test name, or its whole file when that file registers no other case. */
export interface CaseSelection { file: string; mode: 'name' | 'file'; reason: string }
const escapeRe = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
/**
* The paid file that registers `id` and how a trial selects it. A file whose
* registration is complete wins over one that also lists the id; a test
* registered literally under the id (or its CASE_TEST_NAMES label) is
* selected by name; a loop that registers ids at runtime is selected by the
* id it names its test; a file registering only this case runs whole (its
* test names are not the id). Anything else throws before any process starts.
*/
export function caseSelection(id: string, rootDir = ROOT, discovered = collectPaidTestFiles(rootDir)): CaseSelection {
const files = discovered.map(file => {
const source = fs.readFileSync(path.join(rootDir, file), 'utf8');
return { file, source, ...fileCaseRegistration(file, source) };
}).filter(f => f.registered.includes(id));
const owners = files.filter(f => f.known).length ? files.filter(f => f.known) : files;
if (owners.length !== 1) throw new Error(`--case ${id}: ${owners.length ? `registered by ${owners.map(f => f.file).join(', ')}` : 'no paid file registers it'}; it needs exactly one`);
const owner = owners[0]!;
const name = escapeRe(CASE_TEST_NAMES[id] ?? id);
if (new RegExp(`\\b(?:test|it|test\\.\\w+|test(?:Concurrent)?IfSelected)\\s*\\(\\s*(['"\`])${name}\\1|testName\\s*:\\s*(['"\`])${name}\\2`).test(owner.source)) {
return { file: owner.file, mode: 'name', reason: `its Bun test is named ${CASE_TEST_NAMES[id] ?? id}` };
}
if (owner.registered.length === 1) return { file: owner.file, mode: 'file', reason: 'the whole file (it registers no other case)' };
if (owner.computed) return { file: owner.file, mode: 'name', reason: `its loop names the Bun test ${id} at runtime` };
throw new Error(`--case ${id}: ${owner.file} registers several cases and none of its tests is named ${id}; add its test name to CASE_TEST_NAMES`);
}
/** The one paid file that registers `id`, else a thrown reason. */
export function caseFile(id: string, rootDir = ROOT, discovered = collectPaidTestFiles(rootDir)): string {
const owners = discovered.filter(file => {
const { registered, known } = fileCaseRegistration(file, fs.readFileSync(path.join(rootDir, file), 'utf8'));
return known && registered.includes(id);
});
if (owners.length !== 1) throw new Error(`--case ${id}: ${owners.length ? `registered by ${owners.join(', ')}` : 'no paid file statically registers it'}; it needs exactly one`);
return owners[0]!;
return caseSelection(id, rootDir, discovered).file;
}
/**
@@ -87,25 +112,34 @@ export async function runCaseDiagnosis(id: string, options: {
} = {}): Promise<PanelVerdict> {
const rootDir = options.rootDir ?? ROOT;
const log = options.log ?? ((line: string) => console.log(line));
const file = options.file ?? caseFile(id, rootDir);
const testName = CASE_TEST_NAMES[id] ?? id;
if (!options.commandFor && !fs.readFileSync(path.join(rootDir, file), 'utf8').includes(testName)) {
throw new Error(`--case ${id}: ${file} has no literal Bun test named "${testName}", so a trial could not select it. `
+ 'Run the whole file (bun test <file> with EVALS=1) or add its literal name to CASE_TEST_NAMES.');
}
const selection = options.file ? { file: options.file, mode: 'name' as const } : caseSelection(id, rootDir);
const file = selection.file;
const policy = caseTrialPlan(id);
const n = options.trials ?? policy.panel.n;
const plan: CaseTrialPlan = { ...policy, panel: { n, k: policy.panel.k === policy.panel.n ? n : Math.min(policy.panel.k, n) } };
const keys = Array.from({ length: n }, (_, i) => trialShardKey(file, id, i + 1));
const tier = E2E_TIERS[id] as PaidTier;
log(`[test:paid] --case ${id}: ${n} trial(s) of ${file} (kind ${plan.kind}, PASS at ${plan.panel.k}/${n}${plan.quarantined ? ', quarantined' : ''}), tier=${tier}`);
const summary = await runPaidShards(keys.map(key => [key]), {
jobs: Math.min(options.jobs ?? DEFAULT_JOBS, n), withinShardConcurrency: options.withinShardConcurrency, timeoutMs: options.timeoutMs,
rootDir, log, commandFor: options.commandFor, evalDirBase: options.evalDirBase,
trials: Object.fromEntries(keys.map(key => [key, plan])),
const shardOptions = { withinShardConcurrency: options.withinShardConcurrency, timeoutMs: options.timeoutMs, rootDir, log, commandFor: options.commandFor,
env: { ...(options.env ?? process.env), EVALS: '1', EVALS_TIER: tier, EVALS_PREFLIGHT_OK: '1', EVALS_ALL: '1',
...paidSelectionEnv('full', { e2e: [id], judges: [] }, `--case ${id}`) },
});
...paidSelectionEnv('full', { e2e: [id], judges: [] }, `--case ${id}`) } };
if (selection.mode === 'file') {
// The whole file is the case: one file shard per trial, each with its own eval dir, judged by shard status as CI does.
const fileTrials = [];
for (let trial = 1; trial <= n; trial++) {
const [outcome] = (await runPaidShards([[file]], { ...shardOptions, jobs: 1,
...(options.evalDirBase ? { evalDirBase: path.join(options.evalDirBase, `t${trial}`) } : {}) })).outcomes;
if (!outcome || outcome.runnerError !== undefined || isAllSkippedPass(outcome) || !['passed', 'failed', 'timed-out'].includes(outcome.status)) continue;
fileTrials.push({ trial, outcome: outcome.status === 'passed' ? 'passed' as const : 'failed' as const,
...(outcome.status === 'passed' ? {} : { failure_class: outcome.status === 'timed-out' ? 'timeout' as const : 'assertion' as const }) });
}
const verdict = panelVerdict({ case: id, kind: plan.kind, panel: plan.panel, trials: fileTrials, quarantined: plan.quarantined });
log(formatPanelLine({ ...verdict, file, slices: {} }, tier));
return verdict;
}
const summary = await runPaidShards(keys.map(key => [key]), { ...shardOptions,
jobs: Math.min(options.jobs ?? DEFAULT_JOBS, n), evalDirBase: options.evalDirBase,
trials: Object.fromEntries(keys.map(key => [key, plan])) });
const trials = summary.outcomes.flatMap(outcome => outcome.trial && outcome.trial.outcome !== null ? [{
trial: outcome.trial.trial, outcome: outcome.trial.outcome, ...(outcome.trial.failure_class ? { failure_class: outcome.trial.failure_class } : {}),
...(outcome.trial.exit_reason ? { exit_reason: outcome.trial.exit_reason } : {}), ...(outcome.trial.error ? { error: outcome.trial.error } : {}),
+4 -4
View File
@@ -109,7 +109,7 @@ export { PERIODIC_CI_EXCLUDE };
import { scopeCodexAccess, shardFile, shardCaseId, shardTrial, trialShardKey, type CaseTrialPlan, caseTrialPlan, excludedCasesNamePattern, caseTestNamePattern, expandCaseShards, expandTrialShards, partitionCaseExclusions } from './lib/paid-cases';
import { retriesForFiles, trialPanelKey, sliceExecutionOrder, buildRunManifest, parseRunManifest, type SliceResult, sliceExitCode, guardTrialRecords, formatSlicePlan, formatCapacityPreflight } from './lib/paid-plan';
import { caseFile, runCaseDiagnosis, formatPanelLine, runPaidReport } from './lib/paid-report';
import { caseSelection, runCaseDiagnosis, formatPanelLine, runPaidReport } from './lib/paid-report';
export * from './lib/paid-cases';
export * from './lib/paid-plan';
export * from './lib/paid-report';
@@ -1497,11 +1497,11 @@ async function main(): Promise<number> {
if (options.reportDir) return runPaidReport(options.reportDir, { writeDurations: options.writeDurations });
if (options.caseId && options.listOnly) {
const file = caseFile(options.caseId);
const { file, mode, reason } = caseSelection(options.caseId);
const plan = caseTrialPlan(options.caseId);
const n = options.trials ?? plan.panel.n;
console.log(`[test:paid] --case ${options.caseId}: ${n} trial(s) of ${file} (kind ${plan.kind}), list only`);
for (let trial = 1; trial <= n; trial++) console.log(` ${trialShardKey(file, options.caseId, trial)}`);
console.log(`[test:paid] --case ${options.caseId}: ${n} trial(s) of ${file} (kind ${plan.kind}), selects ${reason}, list only`);
for (let trial = 1; trial <= n; trial++) console.log(` ${mode === 'file' ? `${file} (trial ${trial})` : trialShardKey(file, options.caseId, trial)}`);
return 0;
}
if (options.caseId) {
+45
View File
@@ -13,6 +13,7 @@ import { describe, test, expect } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { spawnSync } from 'child_process';
import { E2E_TIERS, E2E_TOUCHFILES } from './helpers/touchfiles';
const ROOT = path.resolve(import.meta.dir, '..');
@@ -56,6 +57,7 @@ import {
excludedCasesNamePattern,
runCaseDiagnosis,
caseFile,
caseSelection,
parseCliOptions,
loadPaidTestDurations,
shardDurationViolations,
@@ -777,6 +779,49 @@ console.log("Ran 1 tests across 1 files. [1ms]"); process.exit(${fail ? 1 : 0});
expect(caseFile('review-sql-injection')).toBe('test/skill-e2e-review.test.ts');
});
test('B4: every registered E2E case resolves to exactly itself, loop-registered cases included', () => {
const unresolved: string[] = [];
for (const id of Object.keys(E2E_TIERS)) {
let selection: ReturnType<typeof caseSelection>;
try { selection = caseSelection(id); } catch (error) { unresolved.push(`${id}: ${(error as Error).message}`); continue; }
const { registered, computed } = fileCaseRegistration(selection.file, fs.readFileSync(path.join(ROOT, selection.file), 'utf8'));
expect(registered, id).toContain(id);
if (selection.mode === 'file') expect(registered, id).toEqual([id]);
else expect(computed || fs.readFileSync(path.join(ROOT, selection.file), 'utf8').includes(CASE_TEST_NAMES[id] ?? id), id).toBe(true);
}
// Fifteen carve files each register this one case; CI runs each file as its own shard.
expect(unresolved).toEqual([expect.stringMatching(/^carve-section-loading: --case carve-section-loading: registered by test\/carve-section-loading-/)]);
expect(caseSelection('plan-mode-no-op')).toEqual({ file: 'test/skill-e2e-plan-mode-no-op.test.ts', mode: 'file', reason: 'the whole file (it registers no other case)' });
expect(caseSelection('review-coverage-audit')).toMatchObject({ file: 'test/skill-e2e-coverage-audit.test.ts', mode: 'name' });
expect(caseSelection('plan-eng-multi-finding-batching').file).toBe('test/skill-e2e-plan-eng-multi-finding-batching.test.ts');
expect(caseSelection('review-sql-injection')).toMatchObject({ file: 'test/skill-e2e-review.test.ts', mode: 'name' });
});
test('B4: --list prints the selection; an unknown id exits non-zero before any process starts', () => {
const run = (args: string[]) => spawnSync(process.execPath, [path.join(ROOT, 'scripts/test-paid-shards.ts'), ...args], { encoding: 'utf8', timeout: 60_000, cwd: ROOT });
const listed = run(['--tier', 'gate', '--case', 'plan-mode-no-op', '--list']);
expect(listed.status, listed.stderr).toBe(0);
expect(listed.stdout).toContain('--case plan-mode-no-op: 1 trial(s) of test/skill-e2e-plan-mode-no-op.test.ts (kind rule), selects the whole file (it registers no other case), list only');
const unknown = run(['--tier', 'gate', '--case', 'no-such-case']);
expect(unknown.status).toBe(1);
expect(unknown.stderr).toContain('--case needs a live E2E case id. Received: no-such-case');
expect(unknown.stdout).not.toContain('shard');
});
test('B4: a whole-file case runs one file shard per trial and judges each by shard status', async () => {
const evalDirBase = fs.mkdtempSync(path.join(os.tmpdir(), 'case-file-diagnosis-'));
const lines: string[] = [];
let call = 0;
try {
const verdict = await runCaseDiagnosis('plan-mode-no-op', { trials: 2, evalDirBase, log: line => lines.push(line),
commandFor: files => { call++; expect(files).toEqual(['test/skill-e2e-plan-mode-no-op.test.ts']);
return { command: process.execPath, args: ['-e', `console.log("Ran 5 tests across 1 files. [1ms]"); process.exit(${call === 2 ? 1 : 0});`] }; } });
expect(call).toBe(2);
expect(verdict).toMatchObject({ case: 'plan-mode-no-op', kind: 'rule', panel: { n: 2, k: 2 }, passed: 1, status: 'FAIL' });
expect(lines.join('\n')).toContain('FAIL 1/2 (✓✗)');
} finally { fs.rmSync(evalDirBase, { recursive: true, force: true }); }
});
test('--case runs the CI panel runner and prints its panelVerdict', async () => {
const evalDirBase = fs.mkdtempSync(path.join(os.tmpdir(), 'case-diagnosis-'));
const lines: string[] = [];