mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-04 02:16:56 +02:00
- C0/C1: the five never-green evals (skill-e2e-autoplan-chain and
skill-e2e-plan-{ceo,eng,design,devex}-finding-count) failed on harness and
budget, never on skill behavior; delete them, their touchfile/tier ids,
AUTOPLAN_CHAIN_BUDGET and the dedicated eighth periodic slice (--slices 7).
- C2: delete the helper groups whose only paid consumers were those files
(11 modules), trim claude-pty-runner and eng-seeded-coverage to the paid
closure, and delete the free replay tests whose assertions exercised only
that dead code (89 files, 135 orphaned fixtures). Blocks that used dead code
only as input for a live subject keep their assertions: the multiSelect
default moved to plan-review-decisions, runner PTY tests use inline caller
policies, and the timer-safe budget checks moved to eng-finding-retry-budget.
- The eight production-touching files stay except ceo-current-decision-record
(its template read only feeds the retired counter).
- CARVE_GUARDS.autoplan is behavioral 'none'; TODOS records the lost chain
and per-finding cadence coverage with their re-entry tests.
240 lines
13 KiB
TypeScript
240 lines
13 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { OVERLAY_CASE_FILES, OVERLAY_MIN_FILE_WALL_MS } from './helpers/overlay-case-policy';
|
|
import {
|
|
applyHollowShardGuard, buildPaidShardArgs, buildRunManifest,
|
|
DEFAULT_SHARD_TIMEOUT_MS, isOverlayTestFile, OVERLAY_MAX_ACTIVE_SHARDS,
|
|
parseCliOptions, parseRunManifest, planPaidShards, resolvePaidShardBudget, resolvePaidShardTimeoutMs,
|
|
retriesForFiles, runPaidShards, summarize, summaryExitCode,
|
|
} from '../scripts/test-paid-shards';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
const overlayFiles = Object.values(OVERLAY_CASE_FILES).map(id => `test/skill-e2e-overlay-harness-${id}.test.ts`);
|
|
const normalFile = 'test/skill-e2e-normal-fixture.test.ts';
|
|
const fakeEnv = {
|
|
PATH: path.dirname(process.execPath),
|
|
GSTACK_CLAUDE_CLI_VERSION: 'free-fixture',
|
|
GSTACK_SDK_MAX_CONCURRENCY: '7',
|
|
};
|
|
|
|
describe('overlay file policy', () => {
|
|
test('grouped planning isolates every overlay and preserves ordinary retries', () => {
|
|
const workflow = 'test/skill-e2e-workflow.test.ts';
|
|
const files = [...overlayFiles, normalFile, workflow];
|
|
for (const maxFilesPerShard of [2, 3, 10]) {
|
|
const shards = planPaidShards(files, { maxFilesPerShard });
|
|
expect(shards.flat().sort()).toEqual([...files].sort());
|
|
for (const file of overlayFiles) expect(shards).toContainEqual([file]);
|
|
const workflowShard = shards.find(shard => shard.includes(workflow))!;
|
|
expect(workflowShard.some(isOverlayTestFile)).toBe(false);
|
|
expect(retriesForFiles(workflowShard)).toBe(2);
|
|
const args = buildPaidShardArgs(workflowShard, resolvePaidShardTimeoutMs(workflowShard), 2, retriesForFiles(workflowShard));
|
|
expect(args[args.indexOf('--retry') + 1]).toBe('2');
|
|
expect(planPaidShards(files.map(file => file.replaceAll('/', '\\')), { maxFilesPerShard })).toEqual(shards);
|
|
}
|
|
});
|
|
|
|
test('mixed or grouped overlay jobs reject before any child starts', async () => {
|
|
const invalidGroups = [[overlayFiles[0], normalFile], [overlayFiles[0], overlayFiles[1]]];
|
|
for (const files of invalidGroups) {
|
|
expect(() => resolvePaidShardBudget(files)).toThrow('own shard');
|
|
expect(() => resolvePaidShardBudget(files, 1_900_000)).toThrow('own shard');
|
|
let launched = 0;
|
|
await expect(runPaidShards([[normalFile], files], {
|
|
commandFor: () => { launched++; throw new Error('must never launch'); },
|
|
})).rejects.toThrow('own shard');
|
|
expect(launched).toBe(0);
|
|
}
|
|
});
|
|
|
|
test('only the exact wrapper family gets one attempt and the extra process grace', () => {
|
|
expect(overlayFiles).toHaveLength(4);
|
|
expect(OVERLAY_MAX_ACTIVE_SHARDS).toBe(1);
|
|
expect(OVERLAY_MIN_FILE_WALL_MS).toBe(1_830_000);
|
|
for (const file of overlayFiles) {
|
|
expect(isOverlayTestFile(file)).toBe(true);
|
|
expect(isOverlayTestFile(file.replaceAll('/', '\\'))).toBe(true);
|
|
expect(resolvePaidShardTimeoutMs([file])).toBe(1_830_000);
|
|
expect(retriesForFiles([file])).toBe(0);
|
|
expect(buildPaidShardArgs([file], resolvePaidShardTimeoutMs([file]), 2, retriesForFiles([file])))
|
|
.toContain('--timeout=1830000');
|
|
}
|
|
for (const file of [normalFile, 'test/skill-e2e-overlay-harness.test.ts', 'test/model-overlay-opus-4-7.test.ts']) {
|
|
expect(isOverlayTestFile(file)).toBe(false);
|
|
expect(resolvePaidShardTimeoutMs([file])).toBe(DEFAULT_SHARD_TIMEOUT_MS);
|
|
expect(retriesForFiles([file])).toBe(1);
|
|
}
|
|
expect(resolvePaidShardTimeoutMs([normalFile], 1234)).toBe(1234);
|
|
expect(resolvePaidShardTimeoutMs([overlayFiles[0]], 1_900_000)).toBe(1_900_000);
|
|
expect(() => resolvePaidShardTimeoutMs([overlayFiles[0]], 1_800_000)).toThrow('explicit wall');
|
|
});
|
|
|
|
test('default, env and CLI walls retain their distinct meanings and CLI precedence', () => {
|
|
expect(parseCliOptions([], {}).timeoutExplicit).toBe(false);
|
|
const env = { EVALS_SHARD_TIMEOUT_MS: '1800000' };
|
|
const configured = parseCliOptions([], env);
|
|
expect(configured.timeoutExplicit).toBe(true);
|
|
expect(() => resolvePaidShardTimeoutMs([overlayFiles[0]], configured.timeoutMs)).toThrow('explicit wall');
|
|
const overridden = parseCliOptions(['--timeout', '1900'], env);
|
|
expect(overridden.timeoutExplicit).toBe(true);
|
|
expect(resolvePaidShardTimeoutMs([overlayFiles[0]], overridden.timeoutMs)).toBe(1_900_000);
|
|
expect(env.EVALS_SHARD_TIMEOUT_MS).toBe('1800000');
|
|
});
|
|
|
|
test('an invalid explicit wall rejects the whole batch before starting any normal or overlay child', async () => {
|
|
let launched = 0;
|
|
await expect(runPaidShards([[normalFile], [overlayFiles[0]]], {
|
|
timeoutMs: 1_800_000,
|
|
commandFor: () => { launched++; throw new Error('must never launch'); },
|
|
})).rejects.toThrow('explicit wall');
|
|
expect(launched).toBe(0);
|
|
});
|
|
|
|
test('CLI rejects insufficient overlay walls before API preflight', () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'overlay-wall-preflight-'));
|
|
try {
|
|
const manifest = {
|
|
version: 1, tier: 'periodic', evalsAll: true, sliceCount: 1, selectionReason: 'free fixture',
|
|
entries: [{ file: overlayFiles[0], slice: 1, status: 'planned' }],
|
|
};
|
|
const planPath = path.join(dir, 'manifest.json');
|
|
fs.writeFileSync(planPath, JSON.stringify(manifest));
|
|
const result = spawnSync(process.execPath, [path.join(ROOT, 'scripts/test-paid-shards.ts'),
|
|
'--tier', 'periodic', '--plan', planPath, '--slice', '1', '--timeout', '1800'], {
|
|
cwd: ROOT, encoding: 'utf8', timeout: 10_000,
|
|
// No credentials or preflight bypass. A nonempty missing PATH avoids
|
|
// runtime fallback to a default shell search path during negative probes.
|
|
env: { PATH: path.join(dir, 'no-executables'), HOME: dir, GSTACK_EVAL_DIR: dir, GSTACK_CLAUDE_CLI_VERSION: 'free-fixture',
|
|
...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}) },
|
|
});
|
|
expect(result.error).toBeUndefined();
|
|
expect(result.status).toBe(1);
|
|
expect(result.stderr).toContain('Overlay shard requires at least 1830000ms; explicit wall 1800000ms');
|
|
expect(result.stderr).not.toContain('preflight');
|
|
expect(fs.existsSync(path.join(dir, 'slice-1.json'))).toBe(false);
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('overlay manifest affinity and CI capacity', () => {
|
|
test('actual lifecycle wrapper guards include all four in periodic and exclude all four from gate', () => {
|
|
for (const tier of ['periodic', 'gate'] as const) {
|
|
const manifest = buildRunManifest({ tier, sliceCount: 6, evalsAll: true, env: { EVALS_ALL: '1' } });
|
|
const entries = manifest.entries.filter(entry => isOverlayTestFile(entry.file));
|
|
expect(entries).toHaveLength(4);
|
|
expect(entries.every(entry => entry.status === (tier === 'periodic' ? 'planned' : 'excluded'))).toBe(true);
|
|
}
|
|
});
|
|
|
|
test('93 files retain every case, reserve slice six, and fit 330 minutes with actual family walls', () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'overlay-affinity-'));
|
|
const normalFiles = Array.from({ length: 89 }, (_, i) => `test/skill-e2e-normal-${i.toString().padStart(2, '0')}.test.ts`);
|
|
const discovered = [...normalFiles, ...overlayFiles];
|
|
try {
|
|
fs.mkdirSync(path.join(dir, 'test'));
|
|
for (const file of discovered) {
|
|
fs.writeFileSync(path.join(dir, file), isOverlayTestFile(file)
|
|
? "const shouldRun = process.env.EVALS_TIER === 'periodic';\n" : '// Free census fixture only.\n');
|
|
}
|
|
const opts = { tier: 'periodic' as const, sliceCount: 6, evalsAll: true, discovered, rootDir: dir, env: { EVALS_ALL: '1' } };
|
|
const manifest = buildRunManifest(opts);
|
|
expect(manifest.entries).toHaveLength(93);
|
|
expect(new Set(manifest.entries.map(e => e.file)).size).toBe(93);
|
|
expect(manifest.entries.every(e => e.status === 'planned')).toBe(true);
|
|
const counts = [1, 2, 3, 4, 5, 6].map(slice => manifest.entries.filter(e => e.slice === slice).length);
|
|
expect(counts).toEqual([18, 18, 18, 18, 17, 4]);
|
|
expect(manifest.entries.filter(e => e.slice === 6).map(e => e.file).sort()).toEqual([...overlayFiles].sort());
|
|
expect(buildRunManifest({ ...opts, discovered: [...discovered].reverse() })).toEqual(manifest);
|
|
expect(parseRunManifest(JSON.stringify(manifest))).toEqual(manifest);
|
|
const stale = { ...manifest, entries: manifest.entries.map(e => isOverlayTestFile(e.file) ? { ...e, slice: 1 } : e) };
|
|
expect(() => parseRunManifest(JSON.stringify(stale))).toThrow('final ordinary slice');
|
|
|
|
const workflow = Bun.YAML.parse(fs.readFileSync(path.join(ROOT, '.github/workflows/evals-periodic.yml'), 'utf8')) as {
|
|
jobs: Record<string, {
|
|
steps: Array<{ run?: string; env?: NodeJS.ProcessEnv }>;
|
|
strategy: { matrix: { slice: number[] } };
|
|
'timeout-minutes': number;
|
|
}>;
|
|
};
|
|
const job = workflow.jobs['eval-slices'];
|
|
const step = job.steps.find(step => step.run?.includes('--plan '))!;
|
|
const jobs = parseCliOptions([], step.env).jobs;
|
|
expect(jobs).toBe(2);
|
|
expect(parseCliOptions([], step.env).withinShardConcurrency).toBe(2);
|
|
expect(job.strategy.matrix.slice).toEqual([1, 2, 3, 4, 5, 6, 7]);
|
|
const normalMinutes = Math.ceil(18 / jobs) * resolvePaidShardTimeoutMs([normalFiles[0]]) / 60_000;
|
|
const overlayMinutes = Math.ceil(overlayFiles.length / OVERLAY_MAX_ACTIVE_SHARDS)
|
|
* Math.max(...overlayFiles.map(file => resolvePaidShardTimeoutMs([file]))) / 60_000;
|
|
expect(normalMinutes).toBe(270);
|
|
expect(overlayMinutes).toBe(122);
|
|
expect(job['timeout-minutes']).toBeGreaterThanOrEqual(Math.max(normalMinutes, overlayMinutes) + 20);
|
|
|
|
// Gate selection keeps its original periodic exclusion and all six
|
|
// ordinary slices; reservation does not spend an empty slot in gate.
|
|
const gate = buildRunManifest({ ...opts, tier: 'gate' });
|
|
expect(gate.entries.filter(e => e.status === 'excluded').map(e => e.file).sort()).toEqual([...overlayFiles].sort());
|
|
expect(gate.entries.filter(e => e.status === 'planned' && e.slice === 6)).toHaveLength(14);
|
|
const single = buildRunManifest({ ...opts, sliceCount: 1 });
|
|
expect(single.entries.every(e => e.slice === 1)).toBe(true);
|
|
expect(parseRunManifest(JSON.stringify(single))).toEqual(single);
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('overlay admission uses real child processes', () => {
|
|
test('one overlay holds its slot while ordinary work proceeds; failure releases admission', async () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'overlay-admission-'));
|
|
const receipts = path.join(dir, 'events.jsonl');
|
|
const lines: string[] = [];
|
|
try {
|
|
const summary = await runPaidShards([[overlayFiles[0]], [overlayFiles[1]], [normalFile]], {
|
|
jobs: 2, env: fakeEnv, logDir: dir, log: line => lines.push(line),
|
|
commandFor: files => {
|
|
const role = files[0] === overlayFiles[0] ? 'first' : files[0] === overlayFiles[1] ? 'second' : 'normal';
|
|
return {
|
|
command: process.execPath,
|
|
args: [path.join(ROOT, 'test/fixtures/overlay-admission-child.ts'), dir, role, receipts],
|
|
};
|
|
},
|
|
});
|
|
expect(summary.outcomes.map(o => o.status)).toEqual(['failed', 'passed', 'passed']);
|
|
expect(summary.outcomes[0].exitCode).toBe(3);
|
|
const events = fs.readFileSync(receipts, 'utf8').trim().split('\n').map(line => JSON.parse(line));
|
|
expect(events.map(e => `${e.role}:${e.event}`)).toEqual([
|
|
'first:start', 'normal:ran-during-overlay', 'first:end', 'second:start', 'second:end',
|
|
]);
|
|
expect(events.every(e => e.sdk === '7')).toBe(true);
|
|
expect(lines.filter(line => line.includes('START') && line.includes('timeout 1830s'))).toHaveLength(2);
|
|
expect(lines.filter(line => line.includes('START') && line.includes('timeout 1800s'))).toHaveLength(1);
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
}, 30_000);
|
|
|
|
test('a thrown overlay launch releases admission and hollow output remains a failing verdict', async () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'overlay-admission-throw-'));
|
|
try {
|
|
const summary = await runPaidShards([[overlayFiles[0]], [overlayFiles[1]], [normalFile]], {
|
|
jobs: 8, env: fakeEnv, logDir: dir, log: () => {},
|
|
commandFor: files => {
|
|
if (files[0] === overlayFiles[0]) throw new Error('fake overlay launch failure');
|
|
const count = files[0] === overlayFiles[1] ? 0 : 1;
|
|
return { command: process.execPath, args: ['-e', `console.log('Ran ${count} tests across 1 file. [1ms]')`] };
|
|
},
|
|
});
|
|
const guarded = applyHollowShardGuard(summary.outcomes, { evalsAll: true });
|
|
expect(guarded.map(o => o.status)).toEqual(['failed', 'passed-empty', 'passed']);
|
|
expect(summaryExitCode(summarize(guarded))).toBe(1);
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|