Distinguish declared goals, open sessions and overlap risk in coordination inventory (#3028)

* feat: add read-only coordination inventory and overlap evaluation

* test: make coordination process fixtures platform explicit

* test: report bounded Stop wrapper failure diagnostics

* test: clean up failed memory MCP sessions deterministically

* fix: update js-yaml to patched 4.3.2

* feat(coordination): distinguish declared goals from open sessions
This commit is contained in:
Affaan Mustafa
2026-09-10 14:11:51 +03:00
committed by GitHub
parent 052ddcb988
commit c7d62c0c6a
13 changed files with 1405 additions and 105 deletions
+124
View File
@@ -0,0 +1,124 @@
'use strict';
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { buildInventory, normalizeManifest } = require('../../scripts/lib/coordination-inventory');
const now = '2026-09-09T01:00:00.000Z';
const fixture = () => ({ version: 1,
repositories: [{ id: 'repo', sources: {} }],
tasks: [{ id: 'worker', repoId: 'repo', paths: ['src/shared.js'], status: 'running', pid: 42 },
{ id: 'peer', repoId: 'repo', paths: ['src/shared.js'] }], leases: [] });
const inventory = value => buildInventory(value, { now });
test('goal collections distinguish missing observations from explicit empty declarations', () => {
const missing = inventory(fixture());
const empty = inventory({ ...fixture(), goals: [], sessions: [] });
assert.equal(missing.coverage.goals, 'missing');
assert.equal(missing.coverage.sessions, 'missing');
assert.equal(empty.coverage.goals, 'declared-only');
assert.equal(empty.coverage.sessions, 'declared-only');
assert.deepEqual(missing.goals, []);
assert.deepEqual(missing.sessions, []);
assert.deepEqual(missing.activity, empty.activity);
assert.equal(missing.activity.freshActiveNativeGoalDeclarations, 0);
});
test('goal activity is never inferred from an open session, running task, heartbeat or observed PID', () => {
const input = fixture(); input.tasks[0].heartbeatAt = now;
input.sessions = [{ id: 'terminal', taskId: 'worker', status: 'open', updatedAt: now }];
const report = buildInventory(input, { now, resources: {
memory: null, processStatus: 'ok', processes: [{ pid: 42, ppid: 1, rssBytes: 1024 }] } });
assert.equal(report.tasks[0].process.state, 'observed');
assert.equal(report.tasks[0].heartbeat.state, 'fresh');
assert.equal(report.activity.declaredSessionsByStatus.open, 1);
assert.equal(report.activity.openSessionsWithoutGoalDeclaration, 1);
assert.deepEqual(report.activity.declaredGoalsByStatus, { active: 0, complete: 0, blocked: 0, unknown: 0 });
assert.equal(report.coverage.goals, 'missing');
});
test('goal and session declarations remain independent and count a shared goal once', () => {
const input = { ...fixture(), goals: [
{ id: 'active', taskId: 'worker', kind: 'native', status: 'active', updatedAt: now },
{ id: 'done', kind: 'native', status: 'complete', updatedAt: now },
{ id: 'unverified', status: 'active', updatedAt: now },
{ id: 'blocked', kind: 'native', status: 'blocked' }, { id: 'unknown' }
], sessions: [
{ id: 'closed', goalId: 'active', status: 'closed' },
{ id: 'other', goalId: 'active', taskId: 'peer', status: 'open' },
{ id: 'open-done', goalId: 'done', status: 'open' }, { id: 'unknown-session' }
] };
const before = JSON.stringify(input); const report = inventory(input);
assert.deepEqual(report.activity.declaredGoalsByStatus, { active: 2, complete: 1, blocked: 1, unknown: 1 });
assert.deepEqual(report.activity.declaredNativeGoalsByStatus, { active: 1, complete: 1, blocked: 1, unknown: 0 });
assert.deepEqual(report.activity.declaredSessionsByStatus, { open: 2, closed: 1, unknown: 1 });
assert.equal(report.activity.freshActiveNativeGoalDeclarations, 1);
assert.equal(report.activity.openSessionsWithoutGoalDeclaration, 0);
assert.equal(report.goals[2].kind, 'unknown');
assert.equal(report.goals[4].status, 'unknown');
assert.equal(report.sessions[3].status, 'unknown');
assert.equal(report.goals[0].authority, 'declared-only');
assert.equal(report.sessions[0].authority, 'declared-only');
assert.equal(JSON.stringify(input), before);
assert.deepEqual(inventory(input), report);
});
test('goal freshness exposes missing stale future and boundary observations without rewriting status', () => {
const times = [null, '2026-09-09T00:54:59.999Z', '2026-09-09T01:00:00.001Z',
'2026-09-09T00:55:00.000Z', now];
const report = inventory({ ...fixture(), goals: times.map((updatedAt, i) =>
({ id: `g${i}`, kind: 'native', status: 'active', updatedAt })) });
assert.deepEqual(report.goals.map(g => g.freshness.state), ['unknown', 'stale', 'clock-skew', 'fresh', 'fresh']);
assert.equal(report.activity.declaredNativeGoalsByStatus.active, 5);
assert.equal(report.activity.freshActiveNativeGoalDeclarations, 2);
assert.ok(report.goals.every(g => g.status === 'active'));
});
test('goal declarations do not change existing task resource lease or overlap outputs', () => {
const base = fixture();
base.leases = [{ resource: 'browser', owner: 'worker', expiresAt: now }];
const legacy = inventory(base);
const report = inventory({ ...base, goals: [{ id: 'completed', status: 'complete' }],
sessions: [{ id: 'closed', status: 'closed', goalId: 'completed' }] });
for (const key of ['tasks', 'warnings', 'resources', 'leases', 'leaseConflicts']) {
assert.deepEqual(report[key], legacy[key]);
}
assert.equal(report.warnings.length, 1);
assert.equal(report.warnings[0].action, 'review-declared-work');
});
test('goal metadata drops objectives commands native blobs and other unrecognized fields', () => {
const report = inventory({ ...fixture(), goals: [{ id: 'g', objective: 'CANARY',
tool_result: { secret: 'CANARY' }, status: 'active', authority: 'CANARY' }],
sessions: [{ id: 's', goalId: 'g', command: 'CANARY', environment: 'CANARY' }] });
assert.ok(!JSON.stringify(report).includes('CANARY'));
assert.equal(report.goals[0].authority, 'declared-only');
});
test('goal input rejects malformed scalars enums dates duplicate IDs and dangling links', () => {
for (const collection of ['goals', 'sessions']) {
for (const value of [null, false, '', {}, 1]) {
assert.throws(() => normalizeManifest({ ...fixture(), [collection]: value }), /Invalid coordination input/);
}
for (const value of [null, false, [], 1, { id: 'bad/id' }, { id: '__proto__' },
{ id: 'x', status: null }, { id: 'x', status: true }, { id: 'x', status: 'running' },
{ id: 'x', updatedAt: '2026-02-30T00:00:00Z' }, { id: 'x', updatedAt: true },
{ id: 'x', taskId: 'missing' }, { id: 'x', taskId: 1 }]) {
assert.throws(() => normalizeManifest({ ...fixture(), [collection]: [value] }), /Invalid coordination input/);
}
assert.throws(() => normalizeManifest({ ...fixture(), [collection]: [{ id: 'same' }, { id: 'same' }] }));
}
for (const kind of [null, true, 1, 'verified', 'declared']) {
assert.throws(() => normalizeManifest({ ...fixture(), goals: [{ id: 'g', kind }] }));
}
assert.throws(() => normalizeManifest({ ...fixture(), sessions: [{ id: 's', goalId: 'missing' }] }));
assert.throws(() => normalizeManifest({ ...fixture(), sessions: [{ id: 's', goalId: 1 }] }));
});
test('goal and session cardinality and total input bounds remain enforced', () => {
const declarations = Array.from({ length: 64 }, (_, i) => ({ id: `item${i}` }));
const report = inventory({ ...fixture(), goals: declarations, sessions: declarations });
assert.equal(report.goals.length, 64); assert.equal(report.sessions.length, 64);
for (const collection of ['goals', 'sessions']) {
assert.throws(() => inventory({ ...fixture(), [collection]: [...declarations, { id: 'extra' }] }));
}
assert.throws(() => inventory({ ...fixture(), goals: [{ id: 'g', ignored: 'x'.repeat(1024 * 1024) }] }));
});
@@ -0,0 +1,155 @@
'use strict';
const { test } = require('node:test');
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 { normalizeManifest, buildInventory, collectResources, collectTaskFiles, readJson } = require('../../scripts/lib/coordination-inventory');
const now = '2026-09-08T06:30:00.000Z';
const task = (id, paths, extra = {}) => ({ id, repoId: 'repo', paths, ...extra });
const fixture = () => ({ version: 1, repositories: [{ id: 'repo', sources: { 'src/a.js': "require('../lib/b')", 'lib/b.js': '' } }], tasks: [task('a', ['src/a.js']), task('b', ['lib/b.js'])], leases: [] });
const run = value => buildInventory(value, { now });
test('direct import warns when exact-path baseline would miss it; deterministic JSON', () => {
const f = fixture(); const before = JSON.stringify(f); const r = run(f);
assert.equal(r.warnings.length, 1); assert.deepEqual(r.warnings[0].reasons, ['import_dependency']);
assert.equal(r.warnings[0].channels.dependency, 1);
assert.equal(JSON.stringify(run(f)), JSON.stringify(r)); assert.equal(JSON.stringify(f), before);
});
test('normalized exact paths warn, tree-only neighbors and cross-repo pairs do not', () => {
const f = fixture(); f.tasks[1].paths = ['./src/a.js'];
assert.deepEqual(run(f).warnings[0].reasons, ['path_overlap']);
f.tasks[1].paths = ['src/c.js']; assert.equal(run(f).warnings.length, 0);
f.repositories.push({ id: 'other', sources: {} }); f.tasks[1] = task('b', ['src/a.js'], { repoId: 'other' });
assert.equal(run(f).warnings.length, 0);
});
test('leases show owner, expiry, conflicts and do not grant authority', () => {
const f = fixture(); f.leases = [
{ resource: 'browser:chrome', owner: 'root', expiresAt: '2026-09-08T07:00:00Z' },
{ resource: 'browser:chrome', owner: 'worker', expiresAt: '2026-09-08T07:00:00Z' },
{ resource: 'browser:chrome', owner: 'old', expiresAt: now }
]; const r = run(f);
assert.equal(r.leases[2].state, 'expired');
assert.deepEqual(r.leaseConflicts, [{ resource: 'browser:chrome', owners: ['root', 'worker'] }]);
assert.equal(r.mode, 'read-only'); assert.equal(r.leases[0].authority, 'declared-only');
});
test('stale heartbeat is not a proven stuck process; absent/future telemetry stays unknown', () => {
const f = fixture(); f.tasks = [task('a', [], { heartbeatAt: '2026-09-08T06:00:00Z', pid: 12 }), task('b', [], { heartbeatAt: '2026-09-08T07:00:00Z' }), task('c', [])];
const r = run(f); assert.equal(r.tasks[0].heartbeat.state, 'stale'); assert.equal(r.tasks[0].process.state, 'unknown');
assert.equal(r.tasks[1].heartbeat.state, 'clock-skew'); assert.equal(r.tasks[2].heartbeat.state, 'unknown');
});
test('task parents, status and bounded observations survive without source payload', () => {
const f = fixture(); f.tasks[1].parentId = 'a'; f.tasks[0].status = 'running'; f.tasks[0].unexpectedSecret = 'CANARY_SECRET';
f.repositories[0].sources['lib/b.js'] = 'CANARY_SOURCE';
const r = run(f); assert.equal(r.tasks[1].parentId, 'a'); assert.equal(r.tasks[0].status, 'running');
assert.ok(!JSON.stringify(r).includes('CANARY')); assert.equal(r.coverage.workingSets, 'declared-paths-only');
});
test('invalid shapes, IDs, paths, dates and missing repos fail closed', () => {
for (const mutate of [
f => { f.version = 2; }, f => { f.tasks = null; }, f => { f.tasks.push(f.tasks[0]); },
f => { f.tasks[0].paths = ['../escape']; }, f => { f.tasks[0].paths = ['/absolute']; },
f => { f.tasks[0].paths = ['C:\\secret']; }, f => { f.tasks[0].paths = ['a/../b']; },
f => { f.tasks[0].paths = ['__proto__']; }, f => { f.tasks[0].pid = '-1'; },
f => { f.tasks[0].heartbeatAt = 'yesterday'; }, f => { f.tasks[0].repoId = 'absent'; },
f => { f.repositories[0].sources = []; }, f => { f.tasks[0].id = '\n'; },
f => { f.tasks[0].parentId = 'a'; }, f => { f.tasks = Array(65).fill(f.tasks[0]); },
f => { f.leases = [{resource:'chrome',owner:'root',expiresAt:'bad'}]; }
]) { const f = fixture(); mutate(f); assert.throws(() => normalizeManifest(f), /Invalid/); }
});
test('process collection uses metadata-only argv, bounded timeout and no shell', () => {
let call; const r = collectResources([task('a', [], { pid: 12 })], { platform: 'darwin', totalmem: () => 1024, freemem: () => 512, execFileSync: (...args) => { call = args; return '12 1 32 01:30 S\n'; } });
assert.equal(call[0], 'ps'); assert.deepEqual(call[1], ['-p','12','-o','pid=,ppid=,rss=,etime=,stat=']);
assert.equal(call[2].timeout, 2000); assert.equal(call[2].shell, false);
assert.equal(r.processes[0].rssBytes, 32768); assert.equal(r.memory.freeBytes, 512);
});
test('unavailable, empty, malformed and unsupported process snapshots remain explicit', () => {
const tasks = [task('a', [], { pid: 12 })];
let runnerCalls = 0;
const unsupportedDeps = { platform: 'win32', execFileSync: () => { runnerCalls += 1; return ''; } };
const unsupported = collectResources(tasks, unsupportedDeps);
assert.equal(unsupported.processStatus, 'unsupported');
assert.equal(buildInventory({ ...fixture(), tasks }, { now, resources: unsupported }).tasks[0].process.state, 'unknown');
// Runner fixtures must select a supported platform independently of the host.
assert.equal(collectResources(tasks, { platform: 'darwin', execFileSync: () => { throw new Error('SECRET'); } }).processStatus, 'unavailable');
assert.equal(collectResources(tasks, { platform: 'darwin', execFileSync: () => '' }).processStatus, 'ok');
assert.equal(collectResources(tasks, { platform: 'darwin', execFileSync: () => 'bad row' }).processStatus, 'unavailable');
assert.equal(collectResources([], unsupportedDeps).processStatus, 'not-requested');
assert.equal(runnerCalls, 0);
});
test('live process snapshot enriches matching tasks and marks missing PID as unobserved', () => {
const f = fixture(); f.tasks[0].pid = 12; f.tasks[1].pid = 13;
const resources = collectResources(f.tasks, { platform: 'linux', execFileSync: () => '12 1 32 01:30 S\n' });
const r = buildInventory(f, { now, resources });
assert.equal(r.tasks[0].process.state, 'observed'); assert.equal(r.tasks[1].process.state, 'not-observed');
});
test('task file adapter reads structured status, labels mtime, skips symlinks and rejects oversized JSON', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'coordination-test-'));
try {
fs.mkdirSync(path.join(dir, 'worker')); fs.writeFileSync(path.join(dir, 'worker', 'STATUS.md'), '- State: running\n- Updated: 2026-09-08T06:29:00Z\n');
fs.symlinkSync(path.join(dir, 'worker'), path.join(dir, 'linked'));
const r = collectTaskFiles(dir); assert.equal(r.tasks.length, 1); assert.equal(r.tasks[0].status, 'running');
assert.ok(r.tasks[0].statusFileModifiedAt); assert.equal(r.tasks[0].heartbeatAt, '2026-09-08T06:29:00Z');
fs.writeFileSync(path.join(dir, 'large.json'), ' '.repeat(1024 * 1024 + 1));
assert.throws(() => readJson(path.join(dir, 'large.json')), /limit/);
assert.equal(collectTaskFiles(path.join(dir, 'missing')).status, 'unavailable');
} finally { fs.rmSync(dir, { recursive: true, force: true }); }
});
test('CLI JSON end to end, no output file changes and safe errors', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'coordination-cli-'));
const cli = path.resolve(__dirname, '../../scripts/coordination-inventory.js');
try {
const file = path.join(dir,'input.json'); fs.writeFileSync(file, JSON.stringify(fixture()));
const r = spawnSync(process.execPath, [cli, '--manifest', file, '--now', now], { encoding:'utf8' });
assert.equal(r.status,0,r.stderr); assert.equal(JSON.parse(r.stdout).warnings.length,1);
assert.deepEqual(fs.readdirSync(dir),['input.json']);
const bad = spawnSync(process.execPath,[cli,'--unknown','CANARY_SECRET'],{encoding:'utf8'});
assert.equal(bad.status,1); assert.ok(!bad.stderr.includes('CANARY_SECRET'));
const help = spawnSync(process.execPath,[cli,'--help'],{encoding:'utf8'}); assert.equal(help.status,0);
} finally { fs.rmSync(dir,{recursive:true,force:true}); }
});
test('prototype-named paths and strict calendar dates are safe', () => {
const f = fixture(); f.repositories[0].sources = {}; f.tasks[0].paths = ['toString']; f.tasks[1].paths = ['valueOf'];
assert.equal(run(f).warnings.length, 0);
for (const invalid of ['2026-02-30T00:00:00Z', '2026-09-08T24:00:00Z']) {
f.tasks[0].heartbeatAt = invalid; assert.throws(() => run(f), /Invalid/);
}
f.tasks[0].heartbeatAt = '2026-09-08T06:00:00.1Z'; assert.equal(run(f).tasks[0].heartbeat.state, 'stale');
});
test('aggregate comparison budget rejects compact but computationally excessive input', () => {
const f = fixture(); f.repositories[0].sources = {};
f.tasks = Array.from({length:64}, (_,i) => task(`task${i}`, Array.from({length:128}, (_,j) => `src/${i}/${j}.js`)));
assert.throws(() => run(f), /budget/);
});
test('CLI discovery composes normalized tasks and reports missing telemetry honestly', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'coordination-discovery-'));
try {
fs.mkdirSync(path.join(dir,'worker')); fs.writeFileSync(path.join(dir,'worker','STATUS.md'),'Freeform progress.\n');
const r = spawnSync(process.execPath,[path.resolve(__dirname,'../../scripts/coordination-inventory.js'),'--coordination',dir,'--now',now],{encoding:'utf8'});
assert.equal(r.status,0,r.stderr); const report=JSON.parse(r.stdout);
assert.equal(report.tasks[0].status,'unknown'); assert.equal(report.tasks[0].heartbeat.state,'unknown');
assert.ok(report.tasks[0].statusFileModifiedAt); assert.equal(report.tasks[0].process.state,'unknown');
} finally { fs.rmSync(dir,{recursive:true,force:true}); }
});
test('source snippets are bounded before invoking inherited regex extractor', () => {
const f = fixture(); f.repositories[0].sources = { 'a.js': `import ${' '.repeat(32000)}x` }; f.tasks=[];
assert.throws(() => run(f), /Invalid/);
f.repositories[0].sources = Object.fromEntries(Array.from({length:33},(_,i) => [`${i}.js`, ' '.repeat(1024)]));
assert.throws(() => run(f), /Invalid/);
});
test('maximum accepted whitespace snippets complete within bounded subprocess timeout', () => {
const code = `const {buildInventory}=require('./scripts/lib/coordination-inventory');
const source='import '+' '.repeat(1016)+'x';
const sources=Object.fromEntries(Array.from({length:32},(_,i)=>[i+'.js',source]));
const r=buildInventory({version:1,repositories:[{id:'r',sources}],tasks:[{id:'a',repoId:'r',paths:['0.js']},{id:'b',repoId:'r',paths:['1.js']}]});
if(r.warnings.length) process.exitCode=1;`;
const r=spawnSync(process.execPath,['-e',code],{cwd:path.resolve(__dirname,'../..'),encoding:'utf8',timeout:2000});
assert.equal(r.status,0,r.error?.message || r.stderr);
});
test('bounded import parsing preserves supported JS and TS import forms', () => {
const { buildDependencyGraphFromSources } = require('../../scripts/lib/agent-proximity/graph');
for (const source of ["import './b'", "import b from './b'", "import { b as c } from './b'", "import * as b from './b'", "import b, { c } from './b'", "import type { B } from './b'", "import {\n b\n} from './b'", "import('./b')"]) {
assert.deepEqual(buildDependencyGraphFromSources({'a.js':source,'b.js':''}).adjacency['a.js'],['b.js']);
}
});
+269 -103
View File
@@ -25,32 +25,43 @@ async function test(name, fn) {
passed += 1;
} catch (error) {
console.log(` FAIL ${name}`);
console.log(` ${error.stack || error.message}`);
console.log(` ${error.mcpDiagnostic ? JSON.stringify(error.mcpDiagnostic) : error.stack || error.message}`);
failed += 1;
}
}
function createFixture(extraEnv = {}) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-memory-mcp-'));
const projectRoot = path.join(root, 'project');
const homeDir = path.join(root, 'home');
fs.mkdirSync(path.join(projectRoot, '.git'), { recursive: true });
fs.mkdirSync(homeDir, { recursive: true });
return {
root,
projectRoot,
env: Object.fromEntries(
Object.entries({
...process.env,
HOME: homeDir,
USERPROFILE: homeDir,
ECC_MEMORY_PROJECT_ROOT: path.join(projectRoot, '.ecc', 'memory'),
ECC_MEMORY_USER_ROOT: path.join(homeDir, '.ecc', 'memory'),
ECC_MEMORY_HARNESS: 'claude',
...extraEnv,
}).filter(([, value]) => typeof value === 'string')
),
};
try {
const projectRoot = path.join(root, 'project');
const homeDir = path.join(root, 'home');
fs.mkdirSync(path.join(projectRoot, '.git'), { recursive: true });
fs.mkdirSync(homeDir, { recursive: true });
return {
root,
projectRoot,
env: Object.fromEntries(
Object.entries({
...process.env,
HOME: homeDir,
USERPROFILE: homeDir,
ECC_MEMORY_PROJECT_ROOT: path.join(projectRoot, '.ecc', 'memory'),
ECC_MEMORY_USER_ROOT: path.join(homeDir, '.ecc', 'memory'),
ECC_MEMORY_HARNESS: 'claude',
ECC_MEMORY_ALLOW_USER_SCOPE: '0',
...extraEnv,
}).filter(([, value]) => typeof value === 'string')
),
};
} catch (error) {
try { fs.rmSync(root, { recursive: true, force: true }); }
catch {
const failure = new Error('MCP fixture cleanup failed', { cause: error });
failure.mcpCleanupFailure = 'fixture_removal_error';
throw failure;
}
throw error;
}
}
function parseTextResult(result) {
@@ -60,105 +71,260 @@ function parseTextResult(result) {
}
async function withClient(fn, options = {}) {
const fixture = createFixture(options.env);
const child = spawn(process.execPath, [options.server || SERVER], {
cwd: fixture.projectRoot,
env: fixture.env,
stdio: ['pipe', 'pipe', 'pipe'],
});
const started = Date.now();
const pending = new Map();
const mode = options.env?.ECC_MEMORY_ALLOW_USER_SCOPE === '1' ? 'allow' : 'deny';
let fixture;
let child;
let phase = 'setup';
let nextId = 1;
let stdout = '';
let stderr = '';
let stdout = Buffer.alloc(0);
let stdoutBytes = 0;
let stderrBytes = 0;
let closed = false;
let tearingDown = false;
let transportError;
let primaryError;
let primaryFailed = false;
let failureKind;
let failureElapsedMs;
let teardownStarted;
let failurePhase;
let cleanupFailure;
let killStatus = 'not_attempted';
let notifyClose;
const closePromise = new Promise(resolve => { notifyClose = resolve; });
let rejectTransport;
const transportFailure = new Promise((_, reject) => { rejectTransport = reject; });
// The child may fail before the initialize or callback race is installed.
transportFailure.catch(() => {});
child.stdout.on('data', chunk => {
stdout += chunk.toString('utf8');
let newlineIndex = stdout.indexOf('\n');
while (newlineIndex >= 0) {
const line = stdout.slice(0, newlineIndex);
stdout = stdout.slice(newlineIndex + 1);
if (line.trim()) {
const message = JSON.parse(line);
const bounded = value => Math.min(2147483647, Math.max(0, Math.trunc(value)));
const safeCode = error => [
'EPIPE', 'ENOENT', 'EACCES', 'EPERM', 'EINVAL', 'ECONNRESET',
'ERR_STREAM_DESTROYED', 'ERR_STREAM_WRITE_AFTER_END', 'ERR_ASSERTION',
].includes(error?.code) ? error.code : null;
const diagnostic = () => ({
phase: failurePhase || phase,
mode,
reason: failureKind || cleanupFailure || 'assertion_or_callback',
failureElapsedMs: failureElapsedMs ?? null,
teardownElapsedMs: bounded(Date.now() - teardownStarted),
elapsedMs: bounded(Date.now() - started),
stdoutBytes,
stderrBytes,
pendingRequests: pending.size,
childStarted: Boolean(child?.pid),
childClosed: closed,
exitCode: Number.isInteger(child?.exitCode) ? child.exitCode : null,
signal: ['SIGTERM', 'SIGKILL', 'SIGINT'].includes(child?.signalCode) ? child.signalCode : null,
errorCode: safeCode(primaryError),
cleanupFailure: cleanupFailure || null,
killStatus,
});
function settleAll(error) {
for (const waiter of pending.values()) waiter.reject(error);
pending.clear();
}
function fail(kind, cause) {
if (tearingDown) {
cleanupFailure ||= kind;
return;
}
if (transportError) return;
transportError = new Error(`MCP test client ${kind}`);
if (safeCode(cause)) transportError.code = safeCode(cause);
failurePhase = phase;
failureKind = kind;
settleAll(transportError);
rejectTransport(transportError);
}
function send(message) {
if (transportError) throw transportError;
try {
child.stdin.write(`${JSON.stringify(message)}\n`, error => {
if (error) fail('stdin_write_error', error);
});
} catch (error) {
fail('stdin_write_error', error);
throw transportError;
}
}
function request(method, params = {}) {
const id = nextId++;
const promise = new Promise((resolve, reject) => {
if (transportError || tearingDown || closed) {
reject(transportError || new Error('MCP test client is closed'));
return;
}
const timer = setTimeout(() => {
fail('request_timeout');
}, 5000);
function settle(fn, value) {
clearTimeout(timer);
pending.delete(id);
fn(value);
}
pending.set(id, {
resolve: value => settle(resolve, value),
reject: error => settle(reject, error),
});
send({ jsonrpc: '2.0', id, method, params });
});
// Teardown rejects abandoned requests too, without an unhandled rejection.
promise.catch(() => {});
return promise;
}
try {
fixture = createFixture(options.env);
phase = 'spawn';
child = spawn(process.execPath, [options.server || SERVER], {
cwd: fixture.projectRoot,
env: fixture.env,
stdio: ['pipe', 'pipe', 'pipe'],
});
child.on('error', error => fail('child_error', error));
child.on('exit', () => {
if (!tearingDown) fail('child_exit');
});
child.once('close', () => {
closed = true;
notifyClose();
if (!tearingDown) fail('child_close');
});
for (const stream of ['stdin', 'stdout', 'stderr']) {
child[stream].on('error', error => fail(`${stream}_error`, error));
}
child.stdout.on('end', () => { if (!tearingDown) fail('stdout_end'); });
for (const stream of ['stdin', 'stdout']) {
child[stream].on('close', () => { if (!tearingDown) fail(`${stream}_close`); });
}
child.stderr.on('data', chunk => {
stderrBytes = bounded(stderrBytes + chunk.length);
});
child.stdout.on('data', chunk => {
stdoutBytes = bounded(stdoutBytes + chunk.length);
if (transportError || tearingDown) return;
// Decode complete lines, so a UTF-8 character split across chunks survives.
stdout = Buffer.concat([stdout, chunk]);
let newlineIndex;
while ((newlineIndex = stdout.indexOf(10)) >= 0) {
if (newlineIndex > 1024 * 1024) { fail('oversized_frame'); return; }
const line = stdout.subarray(0, newlineIndex).toString('utf8');
stdout = stdout.subarray(newlineIndex + 1);
if (!line.trim()) continue;
let message;
try {
message = JSON.parse(line);
if (!message || message.jsonrpc !== '2.0' || !Number.isInteger(message.id)
|| (Object.hasOwn(message, 'result') === Object.hasOwn(message, 'error'))
|| (Object.hasOwn(message, 'error') && (!message.error
|| !Number.isInteger(message.error.code) || typeof message.error.message !== 'string'))) {
fail('invalid_frame');
return;
}
} catch {
fail('malformed_frame');
return;
}
const waiter = pending.get(message.id);
if (waiter) {
pending.delete(message.id);
if (message.error) {
// Existing authorization/protocol assertions inspect this RPC error.
// The test logger emits only mcpDiagnostic when it escapes the helper.
waiter.reject(new Error(`${message.error.code}: ${message.error.message}`));
} else {
waiter.resolve(message.result);
}
}
}
newlineIndex = stdout.indexOf('\n');
if (stdout.length > 1024 * 1024) fail('oversized_frame');
});
phase = 'initialize';
const initialized = await request('initialize', {
protocolVersion: '2025-11-25',
capabilities: {},
clientInfo: { name: 'ecc-memory-test', version: '1.0.0' },
});
phase = 'protocol';
assert.strictEqual(initialized.protocolVersion, '2025-11-25');
phase = 'notification';
send({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} });
const client = {
listTools: () => request('tools/list'),
listToolsRaw: params => request('tools/list', params),
callTool: ({ name, arguments: toolArguments }) => request(
'tools/call',
{ name, arguments: toolArguments }
),
callToolRaw: params => request('tools/call', params),
};
phase = 'callback';
await Promise.race([Promise.resolve().then(() => fn(client, fixture)), transportFailure]);
if (transportError) throw transportError;
assert.strictEqual(pending.size, 0, 'MCP callback must await its requests');
} catch (error) {
primaryError = error;
primaryFailed = true;
failurePhase ||= phase;
failureElapsedMs = bounded(Date.now() - started);
if (error?.mcpCleanupFailure === 'fixture_removal_error') {
cleanupFailure ||= 'fixture_removal_error';
}
});
child.stderr.on('data', chunk => {
stderr += chunk.toString('utf8');
});
function send(message) {
child.stdin.write(`${JSON.stringify(message)}\n`);
}
function request(method, params = {}) {
const id = nextId;
nextId += 1;
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
pending.delete(id);
reject(new Error(`Timed out waiting for ${method}. stderr: ${stderr}`));
}, 5000);
pending.set(id, {
resolve: value => {
clearTimeout(timeout);
resolve(value);
},
reject: error => {
clearTimeout(timeout);
reject(error);
},
});
send({ jsonrpc: '2.0', id, method, params });
});
}
const initialized = await request('initialize', {
protocolVersion: '2025-11-25',
capabilities: {},
clientInfo: { name: 'ecc-memory-test', version: '1.0.0' },
});
assert.strictEqual(initialized.protocolVersion, '2025-11-25');
send({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} });
const client = {
listTools: () => request('tools/list'),
listToolsRaw: params => request('tools/list', params),
callTool: ({ name, arguments: toolArguments }) => request(
'tools/call',
{ name, arguments: toolArguments }
),
callToolRaw: params => request('tools/call', params),
};
try {
await fn(client, fixture);
} finally {
child.stdin.end();
await new Promise(resolve => {
if (child.exitCode !== null) {
resolve();
return;
tearingDown = true;
teardownStarted = Date.now();
phase = 'teardown';
settleAll(new Error('MCP test client is closing'));
stdout = Buffer.alloc(0);
if (child && !closed) {
// Keep the original total 2000 ms budget. Reserve its latter half for
// direct-child termination and stdio close, including on Windows.
let killTimer;
let deadlineTimer;
function terminate() {
try { killStatus = child.kill() ? 'requested' : 'not_sent'; }
catch { killStatus = 'error'; }
}
const timeout = setTimeout(() => {
child.kill();
resolve();
}, 2000);
child.once('exit', () => {
clearTimeout(timeout);
resolve();
const deadline = new Promise(resolve => {
deadlineTimer = setTimeout(resolve, 2000);
killTimer = setTimeout(terminate, 1000);
});
});
fs.rmSync(fixture.root, { recursive: true, force: true });
try {
try { child.stdin.end(); }
catch {
cleanupFailure ||= 'stdin_end_error';
clearTimeout(killTimer);
terminate();
}
await Promise.race([closePromise, deadline]);
} finally {
clearTimeout(killTimer);
clearTimeout(deadlineTimer);
}
if (!closed) cleanupFailure ||= 'child_close_timeout';
}
if (fixture && (!child || closed)) {
try { fs.rmSync(fixture.root, { recursive: true, force: true }); }
catch { cleanupFailure ||= 'fixture_removal_error'; }
}
}
if (primaryFailed || cleanupFailure) {
if (!primaryFailed) primaryError = new Error('MCP test client cleanup failed');
// Keep the primary assertion/RPC/callback error; cleanup must not replace it.
// A wrapper retains non-extensible or non-Error thrown values as its cause.
if (!primaryError || typeof primaryError !== 'object' || !Object.isExtensible(primaryError)
|| Object.getOwnPropertyDescriptor(primaryError, 'mcpDiagnostic')?.configurable === false
|| Object.getOwnPropertyDescriptor(primaryError, 'mcpCleanupFailure')?.configurable === false) {
primaryError = new Error('MCP test client failed', { cause: primaryError });
}
Object.defineProperty(primaryError, 'mcpDiagnostic', { value: diagnostic(), configurable: true });
if (cleanupFailure) {
Object.defineProperty(primaryError, 'mcpCleanupFailure', { value: cleanupFailure, configurable: true });
}
throw primaryError;
}
}