Files
ECC/tests/lib/context-profile-eval.test.js

595 lines
38 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const test = require('node:test');
const { preregister, runEvaluation, parseCodexJsonl, parseClaudeJson, summarize, wilson, runCheck, runScoredCheck,
createAuthLease, createCodexProvider, createClaudeProvider, prepareClaudeEnvironments,
resolveFamily } = require('../../docker/context-profiles/ai-eval-lib');
const { withFixture, write } = require('./helpers/context-fixture');
const root = path.resolve(__dirname, '../..');
const jsonl = (text = '{}', tokens = 10) => [
{ type: 'item.completed', item: { type: 'agent_message', text } },
{ type: 'turn.completed', usage: { input_tokens: tokens, cached_input_tokens: 2, output_tokens: 3 } },
].map(JSON.stringify).join('\n');
const claudeJson = (text = '{}', tokens = 10) => JSON.stringify({ type: 'result', result: text, is_error: false,
usage: { input_tokens: tokens, cache_creation_input_tokens: 3, cache_read_input_tokens: 4, output_tokens: 5 } });
const posix = process.platform !== 'win32';
const permissionModel = Number(process.versions.node.split('.')[0]) >= 20;
// A tiny v2 corpus over the fixture registry. The fix lives only in this test, never in provider inputs.
const FIX = 'module.exports = (a, b) => a + b;\n';
function tinyCorpus(overrides = {}) {
return { schemaVersion: 'ecc.context-eval-corpus.v2', id: 'tiny@1', sampling: 'test', minimumDistinctTasks: 30,
nonInferiorityMargin: 0.05,
selection: [{ id: 'plain', category: 'no-workflow', query: 'Add two numbers.', noWorkflow: true, expectedIds: [] }],
tasks: [{ id: 'add', category: 'errors', manualIds: ['skill:feature'], query: 'Fix add.js so it returns the sum.',
files: { 'add.js': 'module.exports = (a, b) => a - b;\n' },
check: "const assert = require('node:assert/strict');\nassert.equal(require(require('node:path').join(process.cwd(), 'add.js'))(2, 3), 5);\n" }],
...overrides };
}
function providerFor(seen = [], { fix = true } = {}) {
return request => {
seen.push({ ...request, env: { ...request.env } });
if (request.phase === 'selection') return { status: 0, stdout: jsonl('{"selectedIds":[]}') };
if (fix) fs.writeFileSync(path.join(request.cwd, 'add.js'), FIX);
return { status: 0, stdout: jsonl('secret transcript must never be stored') };
};
}
function privateHome(bytes = '{"tokens":{"refresh_token":"old"}}') {
const home = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-eval-auth-')));
fs.chmodSync(home, 0o700);
fs.writeFileSync(path.join(home, 'auth.json'), bytes, { mode: 0o600 });
return home;
}
test('JSONL collects usage only from completion events and fails closed on missing/malformed usage', () => {
const parsed = parseCodexJsonl(jsonl('private text'));
assert.deepEqual(parsed.usage, { inputTokens: 10, cachedInputTokens: 2, outputTokens: 3 });
assert.equal(parsed.text, 'private text');
for (const raw of ['private text', '{}', '{"type":"turn.completed","usage":{"input_tokens":-1}}',
jsonl() + '\n{"type":"turn.failed"}', jsonl() + '\nnot json']) {
assert.equal(parseCodexJsonl(raw).valid, false);
}
});
test('registration pins corpus, source, native-install design and paired order before execution', () => withFixture(repoRoot => {
const corpus = tinyCorpus();
const registration = preregister({ repoRoot, corpus });
assert.equal(registration.schemaVersion, 'ecc.context-eval-registration.v2');
assert.equal(registration.design, 'paired-native-installs-hidden-graded-coding-tasks');
assert.match(registration.corpusDigest, /^[a-f0-9]{64}$/);
assert.deepEqual(registration.arms, ['full', 'manual-lean', 'auto-lean', 'ecc-legacy', 'baseline']);
assert.throws(() => runEvaluation({ registration: { ...registration, corpusDigest: '0'.repeat(64) },
repoRoot, corpus, provider: () => assert.fail('called') }), /pin|registration/i);
}));
test('injected paired run grades hidden checks, gives Full no ECC bodies, removes workspaces and sanitizes metrics', () => withFixture(repoRoot => {
assert.throws(() => runEvaluation(), /opt.in|provider/i);
const seen = [];
const result = runEvaluation({ repoRoot, corpus: tinyCorpus(), provider: providerFor(seen) });
assert.equal(result.outcomes.length, 5);
assert.ok(result.outcomes.every(row => row.passed), JSON.stringify(result.outcomes));
assert.equal(result.selection[0].passed, true);
assert.equal(result.gate.status, 'insufficient-sample');
assert.equal(result.authentication, 'injected');
assert.equal(result.credentialsRetained, false);
assert.equal(result.artifactRetention, 'none');
assert.ok(seen.every(call => !fs.existsSync(call.cwd)));
const saved = JSON.stringify(result);
for (const forbidden of ['secret transcript', 'resources', 'stdout', 'HOME', os.tmpdir()]) assert.ok(!saved.includes(forbidden), forbidden);
const task = arm => seen.find(call => call.phase === 'task' && call.cwd.includes(`--${arm}--`));
assert.deepEqual(result.outcomes.find(row => row.arm === 'full').selectedIds, []);
assert.deepEqual(result.outcomes.find(row => row.arm === 'baseline').selectedIds, []);
assert.deepEqual(result.outcomes.find(row => row.arm === 'ecc-legacy').selectedIds, []);
assert.deepEqual(result.outcomes.find(row => row.arm === 'manual-lean').selectedIds, ['skill:feature']);
assert.match(task('manual-lean').input, /skill:feature/);
assert.doesNotMatch(task('full').input, /skill:feature/);
assert.doesNotMatch(task('baseline').input, /ecc.selected-context|resources/);
assert.doesNotMatch(task('ecc-legacy').input, /ecc.selected-context|resources/);
assert.notEqual(task('full').env.CODEX_HOME, task('manual-lean').env.CODEX_HOME);
assert.equal(task('manual-lean').env.CODEX_HOME, task('auto-lean').env.CODEX_HOME);
assert.notEqual(task('baseline').env.CODEX_HOME, task('full').env.CODEX_HOME);
assert.notEqual(task('baseline').env.CODEX_HOME, task('manual-lean').env.CODEX_HOME);
for (const other of ['full', 'manual-lean', 'baseline']) assert.notEqual(task('ecc-legacy').env.CODEX_HOME, task(other).env.CODEX_HOME);
}));
test('claimed success without the required change fails the hidden check', () => withFixture(repoRoot => {
const result = runEvaluation({ repoRoot, corpus: tinyCorpus(), provider: providerFor([], { fix: false }) });
assert.ok(result.outcomes.every(row => !row.passed && row.failure === 'hidden-check'));
}));
test('hidden check refuses an agent-planted grader and runs read-only where Node supports it', () => withFixture(cwd => {
fs.writeFileSync(path.join(cwd, '.ecc-eval-check.cjs'), 'process.exit(0)');
assert.equal(runCheck(cwd, 'process.exit(0)'), false);
fs.unlinkSync(path.join(cwd, '.ecc-eval-check.cjs'));
assert.equal(runCheck(cwd, "require('node:fs').writeFileSync('planted.txt', 'x');"), !permissionModel);
assert.equal(fs.existsSync(path.join(cwd, 'planted.txt')), !permissionModel);
}));
test('call budget stops work without dropping scheduled failures', () => withFixture(repoRoot => {
let calls = 0;
const result = runEvaluation({ repoRoot, corpus: tinyCorpus(), maxCalls: 1,
provider: () => { calls++; return { status: 0, stdout: jsonl() }; } });
assert.equal(calls, 1);
assert.equal(result.outcomes.length, 5);
assert.ok(result.outcomes.some(row => row.failure === 'call-budget'));
}));
test('deadline, provider exceptions and malformed streams remain sanitized scheduled failures', () => withFixture(repoRoot => {
for (const [provider, failure, options] of [
[() => { throw new Error('SECRET_CREDENTIAL'); }, 'provider-failed', {}],
[() => ({ status: 1, stdout: jsonl(), stderr: 'SECRET_CREDENTIAL' }), 'provider-failed', {}],
[() => ({ status: 0, stdout: 'SECRET_CREDENTIAL' }), 'invalid-jsonl', {}],
[() => assert.fail('expired call'), 'deadline', { deadlineMs: 1 }],
]) {
const result = runEvaluation({ repoRoot, corpus: tinyCorpus(), provider, ...options });
assert.ok(result.outcomes.every(row => row.failure === failure), failure);
assert.doesNotMatch(JSON.stringify(result), /SECRET_CREDENTIAL/);
assert.equal(result.usage, null);
}
}));
test('source drift before and during calls invalidates evidence', () => withFixture(repoRoot => {
const corpus = tinyCorpus();
const registration = preregister({ repoRoot, corpus });
write(repoRoot, 'skills/feature/SKILL.md', '---\nname: feature\ndescription: Changed.\n---\nChanged.');
assert.throws(() => runEvaluation({ repoRoot, corpus, registration, provider: () => assert.fail('called') }), /pin|registration/i);
let calls = 0;
const result = runEvaluation({ repoRoot, corpus, provider: () => {
calls++;
write(repoRoot, 'skills/feature/SKILL.md', `---\nname: feature\ndescription: Drift ${calls}.\n---\nDrift.`);
return { status: 0, stdout: jsonl() };
} });
assert.equal(calls, 1);
assert.ok(result.outcomes.every(row => row.failure === 'source-drift'));
}));
test('a changed native install stops later calls as environment drift', () => withFixture(repoRoot => {
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-eval-env-'));
try {
const { fingerprintExecutable } = require('../../scripts/lib/context-profile-native-executable');
const env = name => ({ profileId: `${name}@1`, skills: 1, restore() {}, verify: () => { const e = new Error('x'); e.code = 'environment-drift'; throw e; },
launch: { home: temp, codexHome: temp, codexPath: process.execPath, executableDigest: fingerprintExecutable(process.execPath).digest } });
const result = runEvaluation({ repoRoot, corpus: tinyCorpus(), environments: { full: env('full'), lean: env('lean'), 'ecc-legacy': env('ecc-legacy'), baseline: env('baseline') },
provider: () => assert.fail('called') });
assert.ok(result.outcomes.every(row => row.failure === 'environment-drift'));
} finally { fs.rmSync(temp, { recursive: true, force: true }); }
}));
test('prepared install config is restored after every call, including failed calls', () => withFixture(repoRoot => {
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-eval-env-'));
try {
const { fingerprintExecutable } = require('../../scripts/lib/context-profile-native-executable');
let restores = 0;
const env = name => ({ profileId: `${name}@1`, skills: 1, verify() {}, restore() { restores++; },
launch: { home: temp, codexHome: temp, codexPath: process.execPath, executableDigest: fingerprintExecutable(process.execPath).digest } });
let calls = 0;
const result = runEvaluation({ repoRoot, corpus: tinyCorpus(), environments: { full: env('full'), lean: env('lean'), 'ecc-legacy': env('ecc-legacy'), baseline: env('baseline') },
provider: request => { calls++; if (calls === 1) throw new Error('crash'); return providerFor()(request); } });
assert.equal(restores, calls);
assert.equal(result.outcomes.filter(row => row.failure === 'provider-failed').length, 1);
} finally { fs.rmSync(temp, { recursive: true, force: true }); }
}));
test('subscription lease copies private auth into the call home, returns refreshed tokens and always removes it', { skip: !posix }, () => {
const authHome = privateHome();
const codexHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-eval-codex-'));
try {
const lease = createAuthLease(authHome);
lease.run(codexHome, () => {
const leased = path.join(codexHome, 'auth.json');
assert.equal(fs.readFileSync(leased, 'utf8'), '{"tokens":{"refresh_token":"old"}}');
assert.equal(fs.statSync(leased).mode & 0o777, 0o600);
fs.writeFileSync(leased, '{"tokens":{"refresh_token":"new"}}');
});
assert.equal(fs.existsSync(path.join(codexHome, 'auth.json')), false);
assert.equal(fs.readFileSync(path.join(authHome, 'auth.json'), 'utf8'), '{"tokens":{"refresh_token":"new"}}');
const staleTemp = path.join(authHome, `auth.json.${process.pid}.tmp`);
fs.writeFileSync(staleTemp, 'stale', { mode: 0o600 });
lease.run(codexHome, () => fs.writeFileSync(path.join(codexHome, 'auth.json'), '{"tokens":{"refresh_token":"latest"}}'));
assert.equal(fs.existsSync(staleTemp), false);
assert.equal(fs.readFileSync(path.join(authHome, 'auth.json'), 'utf8'), '{"tokens":{"refresh_token":"new"}}');
lease.run(codexHome, () => fs.writeFileSync(path.join(codexHome, 'auth.json'), '{"tokens":{"refresh_token":"latest"}}'));
assert.equal(fs.readFileSync(path.join(authHome, 'auth.json'), 'utf8'), '{"tokens":{"refresh_token":"latest"}}');
assert.throws(() => lease.run(codexHome, () => { throw new Error('provider crashed'); }), /crashed/);
assert.equal(fs.existsSync(path.join(codexHome, 'auth.json')), false);
fs.chmodSync(authHome, 0o755);
assert.throws(() => createAuthLease(authHome), /private/);
assert.throws(() => createAuthLease('relative/home'), /absolute/);
assert.throws(() => createAuthLease(path.join(os.homedir(), '.codex')), /dedicated|ENOENT|private/);
} finally {
fs.rmSync(authHome, { recursive: true, force: true });
fs.rmSync(codexHome, { recursive: true, force: true });
}
});
test('real provider needs opt-in, pins and a credential source, and never ignores the native install config', { skip: !posix }, () => withFixture(cwd => {
assert.throws(() => createCodexProvider({}), /opt.in/);
assert.throws(() => createCodexProvider({ allowRealProvider: true }), /model|executable/);
assert.throws(() => createCodexProvider({ allowRealProvider: true, executable: process.execPath, model: 'm', apiKey: '' }), /auth-home|CODEX_API_KEY/);
const authHome = privateHome();
try {
const calls = [];
const provider = createCodexProvider({ allowRealProvider: true, executable: process.execPath, model: 'pinned-model', authHome, apiKey: '',
execute(command, args, options) {
calls.push({ args, options, leased: fs.existsSync(path.join(options.env.CODEX_HOME, 'auth.json')) });
return { status: 0, stdout: jsonl() };
} });
assert.equal(provider.authentication, 'subscription-lease');
const codexHome = path.join(cwd, 'codex-home');
fs.mkdirSync(codexHome);
const request = { phase: 'selection', input: 'request', cwd, timeoutMs: 5, maxBuffer: 1000,
env: { PATH: '/bin', HOME: cwd, CODEX_HOME: codexHome, SECRET: 'x', NODE_OPTIONS: '--inspect' } };
provider(request);
provider({ ...request, phase: 'task' });
assert.ok(calls[0].args.includes('read-only'));
assert.ok(calls[1].args.includes('workspace-write'));
for (const call of calls) {
assert.equal(call.leased, true);
for (const flag of ['--json', '--ephemeral']) assert.ok(call.args.includes(flag));
for (const flag of ['--ignore-user-config', '--ignore-rules']) assert.ok(!call.args.includes(flag));
assert.ok(call.args.join(' ').includes('--disable apps --disable remote_plugin'));
assert.deepEqual(Object.keys(call.options.env).sort(), ['CODEX_HOME', 'HOME', 'PATH']);
assert.equal(call.options.cwd, cwd);
assert.equal(call.options.killSignal, 'SIGKILL');
assert.equal(call.options.shell, false);
}
assert.equal(fs.existsSync(path.join(codexHome, 'auth.json')), false);
const keyed = createCodexProvider({ allowRealProvider: true, executable: process.execPath, model: 'pinned-model', apiKey: 'k',
execute(command, args, options) { calls.push(options.env); return { status: 0, stdout: jsonl() }; } });
keyed(request);
assert.equal(keyed.authentication, 'api-key');
assert.equal(calls.at(-1).CODEX_API_KEY, 'k');
const effortful = createCodexProvider({ allowRealProvider: true, executable: process.execPath, model: 'pinned-model', effort: 'high', apiKey: 'k',
execute(command, args) { calls.push(args); return { status: 0, stdout: jsonl() }; } });
effortful(request);
assert.ok(calls.at(-1).includes('model_reasoning_effort="high"'));
assert.throws(() => createCodexProvider({ allowRealProvider: true, executable: process.execPath, model: 'm', effort: 'huge', apiKey: 'k' }), /effort/);
assert.equal(preregister({ repoRoot: cwd, corpus: tinyCorpus(), executable: process.execPath, model: 'm', effort: 'high' }).providerPin.effort, 'high');
} finally { fs.rmSync(authHome, { recursive: true, force: true }); }
}));
test('confidence intervals use distinct task clusters, not repeated calls as independent samples', () => {
const rows = Array.from({ length: 100 }, (_, repeat) => ['full', 'manual-lean', 'auto-lean', 'ecc-legacy', 'baseline']
.map(arm => ({ id: 'one-task', repeat, arm, passed: true }))).flat();
const report = summarize(rows);
assert.equal(report.distinctTasks, 1);
assert.equal(report.pairs.length, 4);
assert.equal(report.pairs[0].n, 1);
assert.ok(report.pairs[0].interval[0] < 0 && report.pairs[0].interval[1] > 0);
assert.deepEqual(wilson(0, 0), [0, 1]);
assert.equal(summarize([]).pairs[0].delta, null);
});
test('CLI plan is credential-free JSON and rejects unknown or incomplete flags', () => {
const cli = path.join(root, 'docker/context-profiles/ai-eval.js');
const plan = spawnSync(process.execPath, [cli, '--plan'], { encoding: 'utf8' });
assert.equal(plan.status, 0, plan.stderr);
assert.equal(JSON.parse(plan.stdout).schemaVersion, 'ecc.context-eval-registration.v2');
for (const args of [['--live'], ['--unknown'], ['--max-calls'], ['--auth-home']]) {
const result = spawnSync(process.execPath, [cli, ...args], { encoding: 'utf8' });
assert.equal(result.status, 1);
assert.doesNotMatch(result.stderr, /\/Users\/| at /);
}
});
test('CLI injection runs the actual workflow using retained preregistration', () => withFixture(repoRoot => {
const { main } = require('../../docker/context-profiles/ai-eval');
const corpus = tinyCorpus();
const filename = path.join(repoRoot, 'registration.json');
fs.writeFileSync(filename, JSON.stringify(preregister({ repoRoot, corpus })));
const result = main(['--registration', filename, '--max-calls', '10', '--deadline-ms', '60000'],
{ repoRoot, corpus, provider: providerFor() });
assert.ok(result.outcomes.every(row => row.passed));
assert.ok(main(['--help']).usage.includes('--auth-home'));
assert.throws(() => main(['--plan', '--allow-real-provider']), /separate/);
assert.throws(() => main(['--plan', '--plan']), /Invalid/);
}));
test('invalid corpus, unsafe workspace paths, bounds and repeats fail before provider calls', () => withFixture(repoRoot => {
const base = { repoRoot, corpus: tinyCorpus(), provider: () => assert.fail('called') };
const task = base.corpus.tasks[0];
for (const options of [{ maxCalls: 0 }, { deadlineMs: 0 }, { callTimeoutMs: 600001 }, { repeats: 0 },
{ corpus: {} }, { corpus: { ...base.corpus, schemaVersion: 'ecc.context-eval-corpus.v1' } },
{ corpus: tinyCorpus({ tasks: [task, task] }) },
...['../escape.js', '/abs.js', '.hidden.js', 'a/../b.js'].map(file => ({ corpus: tinyCorpus({ tasks: [{ ...task, files: { [file]: 'x' } }] }) })),
{ corpus: tinyCorpus({ tasks: [{ ...task, manualIds: ['skill:a', 'skill:b'] }] }) },
{ corpus: tinyCorpus({ tasks: [{ ...task, check: '' }] }) }]) {
assert.throws(() => runEvaluation({ ...base, ...options }));
}
}));
test('Claude result JSON maps cache-corrected usage and separates provider errors from parse errors', () => {
const parsed = parseClaudeJson(claudeJson('private text'));
assert.deepEqual(parsed.usage, { inputTokens: 13, cachedInputTokens: 4, outputTokens: 5 });
assert.equal(parsed.text, 'private text');
const denied = parseClaudeJson(JSON.stringify({ type: 'result', result: 'Not logged in', is_error: true,
usage: { input_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0, output_tokens: 0 } }));
assert.equal(denied.valid, false);
assert.equal(denied.error, true);
for (const raw of ['private text', '{}', '{"type":"result"}', '{"type":"result","result":"x","is_error":false,"usage":{"input_tokens":-1,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":0}}',
claudeJson() + '\n' + claudeJson(), 'not json']) {
assert.equal(parseClaudeJson(raw).valid, false);
}
});
test('provider family resolves from an explicit flag or the executable name, and effort stays Codex-only', () => {
assert.equal(resolveFamily('claude', '/x/anything'), 'claude');
assert.equal(resolveFamily(undefined, '/opt/codex-cli'), 'codex');
assert.equal(resolveFamily(undefined, '/usr/local/bin/claude'), 'claude');
assert.equal(resolveFamily(undefined, undefined), 'codex');
assert.throws(() => resolveFamily('gpt', undefined), /claude or codex/);
assert.throws(() => resolveFamily(undefined, '/bin/ls'), /Claude or Codex/);
withFixture(repoRoot => {
const registration = preregister({ repoRoot, corpus: tinyCorpus(), executable: process.execPath, model: 'm', effort: 'high' });
assert.throws(() => runEvaluation({ repoRoot, corpus: tinyCorpus(), registration, allowRealProvider: true,
executable: process.execPath, model: 'm', effort: 'high', family: 'claude' }), /Codex/);
});
});
test('Claude provider runs tool-free selection and permissioned tasks with a sanitized isolated env', { skip: !posix }, () => withFixture(cwd => {
assert.throws(() => createClaudeProvider({}), /opt.in/);
assert.throws(() => createClaudeProvider({ allowRealProvider: true }), /model|executable/);
const calls = [];
const provider = createClaudeProvider({ allowRealProvider: true, executable: process.execPath, model: 'pinned-model',
oauthToken: 'test-token', tokenSource: null,
execute(command, args, options) { calls.push({ args, options }); return { status: 0, stdout: claudeJson() }; } });
assert.equal(provider.authentication, 'oauth-env');
const request = { phase: 'selection', input: 'request', cwd, timeoutMs: 5, maxBuffer: 1000,
env: { PATH: '/bin', HOME: cwd, CLAUDE_CONFIG_DIR: path.join(cwd, 'cfg'), TMPDIR: '/tmp', CODEX_HOME: '/tmp/x', SECRET: 's' } };
provider(request);
assert.throws(() => provider({ ...request, phase: 'task' }), /credentialed-tool opt-in/);
const credentialed = createClaudeProvider({ allowRealProvider: true, allowCredentialedTools: true,
executable: process.execPath, model: 'pinned-model', oauthToken: 'test-token', tokenSource: null,
execute(command, args, options) { calls.push({ args, options }); return { status: 0, stdout: claudeJson() }; } });
credentialed({ ...request, phase: 'task' });
assert.ok(calls[0].args.includes('--tools'));
assert.ok(!calls[0].args.join(' ').includes('bypassPermissions'));
assert.ok(calls[1].args.includes('--permission-mode') && calls[1].args.includes('bypassPermissions'));
for (const call of calls) {
for (const flag of ['--print', '--output-format', 'json', '--no-session-persistence', '--model', 'pinned-model']) assert.ok(call.args.includes(flag));
assert.deepEqual(Object.keys(call.options.env).sort(), ['CLAUDE_CODE_OAUTH_TOKEN', 'CLAUDE_CONFIG_DIR', 'DISABLE_NON_ESSENTIAL_MODEL_CALLS', 'HOME', 'PATH', 'TMPDIR']);
assert.equal(call.options.env.CLAUDE_CODE_OAUTH_TOKEN, 'test-token');
assert.equal(call.options.cwd, cwd);
assert.equal(call.options.killSignal, 'SIGKILL');
assert.equal(call.options.shell, false);
}
const keyed = createClaudeProvider({ allowRealProvider: true, executable: process.execPath, model: 'm', oauthToken: '',
apiKey: 'k', tokenSource: null,
execute(command, args, options) { calls.push({ args, options }); return { status: 0, stdout: claudeJson() }; } });
keyed(request);
assert.equal(keyed.authentication, 'api-key');
assert.equal(calls.at(-1).options.env.ANTHROPIC_API_KEY, 'k');
assert.ok(!('CLAUDE_CODE_OAUTH_TOKEN' in calls.at(-1).options.env));
const leased = createClaudeProvider({ allowRealProvider: true, executable: process.execPath, model: 'm', oauthToken: '',
apiKey: '', tokenSource: () => 'leased-token',
execute(command, args, options) { calls.push({ args, options }); return { status: 0, stdout: claudeJson() }; } });
leased(request);
assert.equal(leased.authentication, 'subscription-keychain-lease');
assert.equal(calls.at(-1).options.env.CLAUDE_CODE_OAUTH_TOKEN, 'leased-token');
const denied = createClaudeProvider({ allowRealProvider: true, executable: process.execPath, model: 'm', oauthToken: '',
apiKey: '', tokenSource: () => { throw new Error('Claude Keychain login is unavailable; provide CLAUDE_CODE_OAUTH_TOKEN'); },
execute() { return { status: 0, stdout: claudeJson() }; } });
assert.throws(() => denied(request), /unavailable/);
}));
test('Claude native installs materialize managed skills and detect tampering as environment drift', { skip: !posix }, () => withFixture(repoRoot => {
const temp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-eval-claude-')));
try {
const envs = prepareClaudeEnvironments({ repoRoot, executable: process.execPath, root: temp });
assert.deepEqual(Object.keys(envs).sort(), ['baseline', 'full', 'lean']);
for (const name of ['full', 'lean']) {
assert.equal(envs[name].profileId, `${name}@1`);
assert.ok(envs[name].skills > 0);
const installed = fs.readdirSync(path.join(envs[name].launch.claudeConfigDir, 'skills'));
assert.equal(installed.length, envs[name].skills);
}
assert.equal(envs.baseline.profileId, null);
assert.equal(envs.baseline.skills, 0);
for (const name of ['baseline', 'full', 'lean']) {
envs[name].verify();
envs[name].restore();
}
const tampered = path.join(envs.lean.launch.claudeConfigDir, 'skills',
fs.readdirSync(path.join(envs.lean.launch.claudeConfigDir, 'skills'))[0], 'SKILL.md');
fs.appendFileSync(tampered, 'tamper');
assert.throws(() => envs.lean.verify(), /environment-drift/);
} finally { fs.rmSync(temp, { recursive: true, force: true }); }
}));
test('injected Claude-family run parses Claude JSON, isolates config homes, grades checks and maps usage', { skip: !posix }, () => withFixture(repoRoot => {
const temp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-eval-claude-run-')));
try {
const legacyRoot = path.join(temp, 'legacy-src');
for (const id of ['feature', 'shared']) {
fs.mkdirSync(path.join(legacyRoot, 'skills', id), { recursive: true });
fs.writeFileSync(path.join(legacyRoot, 'skills', id, 'SKILL.md'), `---\nname: ${id}\ndescription: Legacy ${id}.\n---\n`);
}
const environments = prepareClaudeEnvironments({ repoRoot, executable: process.execPath, root: temp,
legacySource: { root: legacyRoot, sha: '0'.repeat(40) } });
const seen = [];
const provider = request => {
seen.push({ ...request, env: { ...request.env } });
if (request.phase === 'selection') return { status: 0, stdout: claudeJson('{"selectedIds":[]}') };
fs.writeFileSync(path.join(request.cwd, 'add.js'), FIX);
return { status: 0, stdout: claudeJson('done') };
};
const result = runEvaluation({ repoRoot, corpus: tinyCorpus(), provider, family: 'claude', environments });
assert.equal(result.evidence, 'injected-provider');
assert.equal(result.outcomes.length, 5);
assert.ok(result.outcomes.every(row => row.passed), JSON.stringify(result.outcomes));
assert.equal(result.installs.full.skills, 5);
assert.equal(result.installs.lean.skills, 3);
assert.equal(result.installs['ecc-legacy'].skills, 2);
assert.equal(result.installs['ecc-legacy'].sourceSha, '0'.repeat(40));
assert.equal(result.installs.baseline.skills, 0);
assert.deepEqual(result.usage, { inputTokens: 13 * result.calls, cachedInputTokens: 4 * result.calls, outputTokens: 5 * result.calls });
const task = arm => seen.find(call => call.phase === 'task' && call.cwd.includes(`--${arm}--`));
assert.equal(typeof task('full').env.CLAUDE_CONFIG_DIR, 'string');
assert.equal(task('full').env.CODEX_HOME, undefined);
assert.notEqual(task('full').env.CLAUDE_CONFIG_DIR, task('manual-lean').env.CLAUDE_CONFIG_DIR);
assert.equal(task('manual-lean').env.CLAUDE_CONFIG_DIR, task('auto-lean').env.CLAUDE_CONFIG_DIR);
assert.notEqual(task('baseline').env.CLAUDE_CONFIG_DIR, task('full').env.CLAUDE_CONFIG_DIR);
for (const other of ['full', 'manual-lean', 'baseline']) assert.notEqual(task('ecc-legacy').env.CLAUDE_CONFIG_DIR, task(other).env.CLAUDE_CONFIG_DIR);
assert.doesNotMatch(task('baseline').input, /ecc.selected-context|resources/);
assert.doesNotMatch(task('ecc-legacy').input, /ecc.selected-context|resources/);
assert.deepEqual(result.outcomes.find(row => row.arm === 'full').selectedIds, []);
assert.deepEqual(result.outcomes.find(row => row.arm === 'baseline').selectedIds, []);
assert.deepEqual(result.outcomes.find(row => row.arm === 'ecc-legacy').selectedIds, []);
assert.deepEqual(result.outcomes.find(row => row.arm === 'manual-lean').selectedIds, ['skill:feature']);
const saved = JSON.stringify(result);
for (const forbidden of ['CLAUDE_CONFIG_DIR', os.tmpdir(), 'leased-token']) assert.ok(!saved.includes(forbidden), forbidden);
} finally { fs.rmSync(temp, { recursive: true, force: true }); }
}));
const SCORED_CHECK = "const path = require('node:path');\nlet ok = 0;\n"
+ "try { if (require(path.join(process.cwd(), 'add.js'))(2, 3) === 5) ok++; } catch {}\n"
+ "try { if (require(path.join(process.cwd(), 'sub.js'))(5, 3) === 2) ok++; } catch {}\n"
+ "console.log(`ECC_EVAL_SCORE ${JSON.stringify({ score: ok / 2 })}`);\nprocess.exit(0);\n";
function tinyComplexCorpus() {
return { schemaVersion: 'ecc.context-eval-complex-corpus.v1', id: 'tiny-complex@1', sampling: 'test',
minimumDistinctTasks: 1, nonInferiorityMargin: 0.05,
selection: [{ id: 'complex-addsub', category: 'complex-test', query: 'Fix add.js and sub.js.', expectedIds: ['skill:feature'] }],
tasks: [{ id: 'addsub', category: 'complex-test', manualIds: ['skill:feature', 'skill:shared'], query: 'Fix add.js and sub.js.',
files: { 'add.js': 'module.exports = (a, b) => a - b;\n', 'sub.js': 'module.exports = (a, b) => a * b;\n' },
check: SCORED_CHECK }] };
}
test('complex corpora register a scored design and keep partial credit per arm', () => withFixture(repoRoot => {
const corpus = tinyComplexCorpus();
const registration = preregister({ repoRoot, corpus });
assert.equal(registration.design, 'paired-native-installs-hidden-scored-complex-tasks');
assert.equal(registration.minimumDistinctTasks, 1);
const result = runEvaluation({ repoRoot, corpus, provider: providerFor() });
assert.equal(result.outcomes.length, 5);
assert.ok(result.outcomes.every(row => !row.passed && row.score === 0.5), JSON.stringify(result.outcomes));
assert.equal(result.summary.rates.find(row => row.arm === 'full').meanScore, 0.5);
assert.equal(result.gate.status, 'synthetic-only');
assert.deepEqual(result.outcomes.find(row => row.arm === 'manual-lean').selectedIds, ['skill:feature', 'skill:shared']);
}));
test('complex corpus validation rejects wrong minimums and oversized manual picks', () => withFixture(repoRoot => {
const corpus = tinyComplexCorpus();
const base = { repoRoot, provider: () => assert.fail('called') };
assert.throws(() => runEvaluation({ ...base, corpus: { ...corpus, minimumDistinctTasks: 2 } }));
assert.throws(() => runEvaluation({ ...base, corpus: { ...corpus,
tasks: [{ ...corpus.tasks[0], manualIds: ['skill:a', 'skill:b', 'skill:c', 'skill:d'] }] } }));
assert.throws(() => runEvaluation({ ...base, corpus: { ...corpus, schemaVersion: 'ecc.context-eval-corpus.v9' } }));
assert.throws(() => runEvaluation({ ...base, corpus: { ...corpus, tasks: [{ ...corpus.tasks[0], checkTimeoutMs: 120001 }] } }));
}));
test('scored checks parse the partial-credit line and fall back to exit status', () => withFixture(root => {
const dir = name => { const made = path.join(root, name); fs.mkdirSync(made); return made; };
assert.deepEqual(runScoredCheck(dir('a'), "console.log('ECC_EVAL_SCORE {\"score\":0.25}');"), { passed: true, score: 0.25 });
assert.deepEqual(runScoredCheck(dir('b'), "console.log('ECC_EVAL_SCORE not-json');"), { passed: true, score: 1 });
assert.deepEqual(runScoredCheck(dir('c'), "console.log('ECC_EVAL_SCORE {\"score\":1.5}');"), { passed: true, score: 1 });
assert.deepEqual(runScoredCheck(dir('d'), "console.log('ECC_EVAL_SCORE {\"score\":0.9}');\nprocess.exit(1);"), { passed: false, score: 0 });
assert.deepEqual(runScoredCheck(dir('e'), 'process.exit(0);'), { passed: true, score: 1 });
// A grader that advertises ECC_EVAL_SCORE but dies before printing it scores zero, never a silent pass.
assert.deepEqual(runScoredCheck(dir('f'), "throw new Error('agent server crashed the process'); // ECC_EVAL_SCORE\n"),
{ passed: false, score: 0 });
assert.deepEqual(runScoredCheck(dir('g'), "process.exit(0); // ECC_EVAL_SCORE\n"), { passed: true, score: 0 });
}));
test('an explicit selector decline injects nothing, even when a tier-2 fallback exists', () => {
// The rbac-middleware query exposes a tier-2 fallback candidate on the real
// registry (pinned in context-selection.test.js). A selector that explicitly
// returns [] has DECLINED: neither the selection probe nor the auto-lean
// task launch may admit the fallback anyway.
const { tasks } = require('../../docker/context-profiles/ai-corpus.json');
const query = tasks.find(item => item.id === 'rbac-middleware').query;
const corpus = { schemaVersion: 'ecc.context-eval-corpus.v2', id: 'decline@1', sampling: 'test',
minimumDistinctTasks: 30, nonInferiorityMargin: 0.05,
selection: [{ id: 'decline-probe', category: 'decline', query, expectedIds: [] }],
tasks: [{ id: 'decline-task', category: 'decline', manualIds: [], query,
files: { 'add.js': 'module.exports = (a, b) => a - b;\n' },
check: "const assert = require('node:assert/strict');\nassert.equal(require(require('node:path').join(process.cwd(), 'add.js'))(2, 3), 5);\n" }] };
const seen = [];
const result = runEvaluation({ corpus, arms: ['auto-lean', 'baseline'],
provider: request => {
seen.push({ ...request });
if (request.phase === 'selection') return { status: 0, stdout: jsonl('{"selectedIds":[]}') };
fs.writeFileSync(path.join(request.cwd, 'add.js'), FIX);
return { status: 0, stdout: jsonl('done') };
} });
assert.deepEqual(result.selection[0].selectedIds, []);
assert.equal(result.selection[0].passed, true);
assert.deepEqual(result.outcomes.find(row => row.arm === 'auto-lean').selectedIds, []);
const taskInput = seen.find(call => call.phase === 'task' && call.cwd.includes('--auto-lean--')).input;
assert.doesNotMatch(taskInput, /skill:/);
});
test('the legacy source pin is validated before any git export', () => { const { exportLegacySource } = require('../../docker/context-profiles/ai-eval-lib');
assert.throws(() => exportLegacySource({ destination: 'relative/path' }), /absolute/);
assert.throws(() => exportLegacySource({ destination: path.join(os.tmpdir(), 'ecc-legacy-pin'), pin: { sha: 'not-a-sha' } }), /pin/);
});
test('arm subsets register and run only the requested arms, paired against the last arm', () => withFixture(repoRoot => {
const corpus = tinyCorpus();
const registration = preregister({ repoRoot, corpus, arms: ['auto-lean', 'baseline'] });
assert.deepEqual(registration.arms, ['auto-lean', 'baseline']);
assert.throws(() => preregister({ repoRoot, corpus, arms: ['nope'] }), /arm/i);
assert.throws(() => preregister({ repoRoot, corpus, arms: [] }), /arm/i);
const result = runEvaluation({ repoRoot, corpus, arms: ['auto-lean', 'baseline'], provider: providerFor() });
assert.equal(result.outcomes.length, 2);
assert.deepEqual(result.summary.rates.map(row => row.arm), ['auto-lean', 'baseline']);
assert.equal(result.summary.pairs.length, 1);
assert.equal(result.summary.pairs[0].reference, 'baseline');
}));
const STEPPED_CHECK = want => "const fs=require('node:fs');const n=Number(fs.readFileSync('n.txt','utf8'));\n"
+ `console.log(\`ECC_EVAL_SCORE \${JSON.stringify({score: n >= ${want} ? 1 : 0})}\`);\nprocess.exit(0);\n`;
function steppedCorpus() {
return { schemaVersion: 'ecc.context-eval-complex-corpus.v1', id: 'stepped@1', sampling: 'test',
minimumDistinctTasks: 1, nonInferiorityMargin: 0.05, selection: [],
tasks: [{ id: 'chain', category: 'test', manualIds: [], files: { 'n.txt': '1\n' },
steps: [{ query: 'Increment the number in n.txt.', check: STEPPED_CHECK(2) },
{ query: 'Increment the number in n.txt again.', check: STEPPED_CHECK(3) }] }] };
}
test('stepped tasks grade each ticket in the accumulating workspace with per-step metrics', () => withFixture(repoRoot => {
const result = runEvaluation({ repoRoot, corpus: steppedCorpus(), arms: ['baseline'],
provider: request => {
const file = path.join(request.cwd, 'n.txt');
fs.writeFileSync(file, String(Number(fs.readFileSync(file, 'utf8')) + 1) + '\n');
return { status: 0, stdout: jsonl('done') };
} });
assert.equal(result.outcomes.length, 1);
const row = result.outcomes[0];
assert.equal(row.passed, true);
assert.equal(row.score, 1);
assert.equal(row.steps.length, 2);
assert.ok(row.steps.every(step => step.score === 1 && step.calls === 1 && step.usage));
assert.equal(row.calls, 2);
}));
test('a failed step ends the chain and remaining tickets score zero', () => withFixture(repoRoot => {
const result = runEvaluation({ repoRoot, corpus: steppedCorpus(), arms: ['baseline'],
provider: () => ({ status: 0, stdout: jsonl('nothing done') }) });
const row = result.outcomes[0];
assert.equal(row.passed, false);
assert.equal(row.score, 0);
assert.deepEqual(row.steps.map(step => step.score), [0, 0]);
}));
test('step graders use distinct files and are removed after running so later tickets cannot read them', () => withFixture(root => {
const dir = path.join(root, 'stepped');
fs.mkdirSync(dir);
assert.deepEqual(runScoredCheck(dir, "console.log('ECC_EVAL_SCORE {\"score\":1}');", 10000, 1), { passed: true, score: 1 });
assert.equal(fs.existsSync(path.join(dir, '.ecc-eval-check-1.cjs')), false);
assert.deepEqual(runScoredCheck(dir, "console.log('ECC_EVAL_SCORE {\"score\":1}');", 10000, 2), { passed: true, score: 1 });
// A grader planted by the agent before its step still fails closed.
fs.writeFileSync(path.join(dir, '.ecc-eval-check-3.cjs'), 'process.exit(0);');
assert.deepEqual(runScoredCheck(dir, "console.log('ECC_EVAL_SCORE {\"score\":1}');", 10000, 3), { passed: false, score: 0 });
}));
test('stepped corpus validation rejects bad steps before provider calls', () => withFixture(repoRoot => {
const corpus = steppedCorpus();
const base = { repoRoot, arms: ['baseline'], provider: () => assert.fail('called') };
assert.throws(() => runEvaluation({ ...base, corpus: { ...corpus, tasks: [{ ...corpus.tasks[0], steps: [corpus.tasks[0].steps[0]] }] } }));
assert.throws(() => runEvaluation({ ...base, corpus: { ...corpus, tasks: [{ ...corpus.tasks[0], steps: [{ query: '', check: 'x' }, corpus.tasks[0].steps[1]] }] } }));
}));