From d8ab841e5bddfb91d99b662e094b32e1e685bb1f Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Sun, 27 Sep 2026 20:09:24 -0400 Subject: [PATCH] Align runtime adapters with installed CLIs --- ...grity-note-2026-09-27-portable-kernel.yaml | 7 +++-- src/adapters/claude.mjs | 20 +++++++++--- src/adapters/codex.mjs | 8 +++-- test/fleet/bootstrap-acceptance.test.mjs | 12 ++++--- test/fleet/lifecycle.test.mjs | 31 ++++++++++--------- test/fleet/mcp-adapter.test.mjs | 28 +++++++++++++---- 6 files changed, 73 insertions(+), 33 deletions(-) diff --git a/reports/test-audit/test-integrity-note-2026-09-27-portable-kernel.yaml b/reports/test-audit/test-integrity-note-2026-09-27-portable-kernel.yaml index b568207..1a5f5fb 100644 --- a/reports/test-audit/test-integrity-note-2026-09-27-portable-kernel.yaml +++ b/reports/test-audit/test-integrity-note-2026-09-27-portable-kernel.yaml @@ -102,11 +102,11 @@ test_integrity_note: - id: SCN-claude-adapter given: a Claude runtime adapter with a virtualized command runner when: sessions are planned and runtime state is queried - then: command construction is deterministic, capabilities are explicit, planning launches no process, and stop requires matching live session and worktree ownership evidence + then: background creation omits the ignored synthetic session flag, captures the sole safe manager-assigned ID, launches no process during planning, and stop requires matching live session and worktree ownership evidence - id: SCN-codex-adapter given: a Codex runtime adapter with a virtualized command runner and an identity-bound TORCH control plane when: create, resume, MCP configuration, runtime-ID capture, and steering are planned or attempted - then: no provider call occurs, thread IDs come from JSONL, every message is durable, and unsupported native controls fail explicitly + then: no provider call occurs, mutually exclusive safety flags are absent, global options precede exec and resume options follow resume, thread IDs come from JSONL, every message is durable, and unsupported native controls fail explicitly - id: SCN-fleet-fresh-resume given: approved identities and managed worktrees with a virtualized Claude executor when: the fleet starts fresh, winds down safely, and plans its next start @@ -267,6 +267,7 @@ test_integrity_note: - SQLite state after process restart - CLI and MCP output over real process boundaries - runtime command plans and execution order + - installed-CLI-compatible option sets, subcommand ordering, and manager-assigned Claude background IDs - runtime stop session identity, reported working directory, and fail-closed ownership mismatch - local resume snapshot, tracked resume brief, final worker message, preserved task identity, and presence transitions - immutable check receipt commit and result @@ -312,6 +313,8 @@ test_integrity_note: - repository content redirecting machine-local database writes - MCP and CLI drifting onto separate business logic - launch plans that execute provider commands during preview + - virtualized tests blessing commands that the installed provider CLI rejects during parsing + - a synthetic Claude UUID being persisted when background mode ignored it - stopping a runtime by PID, parentage, or session label without matching managed-worktree evidence - a mixed-runtime claim implemented as renamed Claude-only commands - runtime sessions launching without their identity-bound TORCH tool surface diff --git a/src/adapters/claude.mjs b/src/adapters/claude.mjs index 6aaad07..e0d1121 100644 --- a/src/adapters/claude.mjs +++ b/src/adapters/claude.mjs @@ -42,6 +42,16 @@ function mcpName(areaId) { return `torch-${areaId.toLowerCase().replace(/[^a-z0-9_-]+/g, '-').replace(/^-|-$/g, '')}`; } +function backgroundSessionId(output) { + const lines = String(output ?? '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean); + if (lines.length !== 1 || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(lines[0])) { + throw new TorchError('Claude background launch did not return exactly one safe session ID', { + code: 'RUNTIME_ID_NOT_CAPTURED', details: { adapter: 'claude' }, + }); + } + return lines[0]; +} + export class ClaudeRuntimeAdapter { constructor({ env = process.env, @@ -96,12 +106,12 @@ export class ClaudeRuntimeAdapter { } createSession({ areaId, title, worktree, promptFile, firstMessage, model = 'opus', background = true, mcp } = {}) { - const runtimeSessionId = this.idFactory(); + const runtimeSessionId = background ? null : this.idFactory(); const args = []; if (background) args.push('--bg'); args.push( '--model', text(model, 'model'), - '--session-id', runtimeSessionId, + ...(!background ? ['--session-id', runtimeSessionId] : []), '-n', `TORCH · ${text(title ?? areaId, 'title')}`, '--append-system-prompt-file', text(promptFile, 'promptFile'), ...mcpArgs(mcp), @@ -109,6 +119,7 @@ export class ClaudeRuntimeAdapter { if (firstMessage) args.push(text(firstMessage, 'firstMessage')); return { adapter: this.name, areaId: text(areaId, 'areaId'), runtimeSessionId, + requiresRuntimeIdCapture: background, launch: commandRecord(this.executable, args, text(worktree, 'worktree')), }; } @@ -188,8 +199,9 @@ export class ClaudeRuntimeAdapter { }; } - captureRuntimeId({ areaId, runtimeSessionId } = {}) { - return { areaId: text(areaId, 'areaId'), runtimeSessionId: text(runtimeSessionId, 'runtimeSessionId') }; + captureRuntimeId({ areaId, runtimeSessionId, stdout, output } = {}) { + const captured = runtimeSessionId ?? backgroundSessionId(stdout ?? output); + return { areaId: text(areaId, 'areaId'), runtimeSessionId: text(captured, 'runtimeSessionId') }; } installHooks() { diff --git a/src/adapters/codex.mjs b/src/adapters/codex.mjs index f31f4c9..afdc9bf 100644 --- a/src/adapters/codex.mjs +++ b/src/adapters/codex.mjs @@ -113,8 +113,9 @@ export class CodexRuntimeAdapter { const id = text(areaId, 'areaId'); const cwd = text(worktree, 'worktree'); const args = [ - 'exec', '--json', '--cd', cwd, '--sandbox', 'workspace-write', '--approve-for-me', + '--cd', cwd, '--approve-for-me', ...mcpArgs(mcp), + 'exec', '--json', ...optionalModelArgs(model), `${text(firstMessage, 'firstMessage')}\n\nRead and follow the TORCH domain prompt at ${text(promptFile, 'promptFile')}.`, ]; @@ -133,9 +134,10 @@ export class CodexRuntimeAdapter { const sessionId = text(runtimeSessionId, 'runtimeSessionId'); const cwd = text(worktree, 'worktree'); const args = [ - 'exec', '--json', '--cd', cwd, '--sandbox', 'workspace-write', '--approve-for-me', + '--cd', cwd, '--approve-for-me', ...mcpArgs(mcp), - ...optionalModelArgs(model), 'resume', sessionId, text(message, 'message'), + 'exec', 'resume', '--json', + ...optionalModelArgs(model), sessionId, text(message, 'message'), ]; return { adapter: this.name, diff --git a/test/fleet/bootstrap-acceptance.test.mjs b/test/fleet/bootstrap-acceptance.test.mjs index 725d535..281437c 100644 --- a/test/fleet/bootstrap-acceptance.test.mjs +++ b/test/fleet/bootstrap-acceptance.test.mjs @@ -76,12 +76,16 @@ test('SCN-bootstrap-acceptance: one installed fixture proves the complete organi assert.equal(existsSync(join(worktree.path, '.torch', 'prompts', `${agent.areaId}.md`)), true); } - let runtime = 0; - const adapter = createClaudeAdapter({ idFactory: () => `bootstrap-runtime-${++runtime}` }); - const up = planFleetUp({ repositoryRoot: context.root, controlPlane: control, adapter, fresh: true }); + const adapter = createClaudeAdapter(); + const adapters = new Map([['claude', adapter]]); + const up = planFleetUp({ repositoryRoot: context.root, controlPlane: control, adapters, fresh: true }); assert.equal(up.canProceed, true, JSON.stringify(up.blockers)); assert.equal(up.actions.at(-1).areaId, 'session-manager'); - const started = startFleet({ plan: up, controlPlane: control, executor: () => ({ status: 0 }) }); + let runtime = 0; + const started = startFleet({ + plan: up, controlPlane: control, adapters, + executor: () => ({ status: 0, stdout: `bootstrap-runtime-${++runtime}\n` }), + }); assert.equal(new Set(started.started.map((entry) => entry.runtimeSessionId)).size, roster.agents.length); for (const action of up.actions) { assert.match(readFileSync(action.promptFile, 'utf8'), new RegExp(`Area ID: ${action.areaId}|TORCH Session Manager`)); diff --git a/test/fleet/lifecycle.test.mjs b/test/fleet/lifecycle.test.mjs index 1da5d47..13a2819 100644 --- a/test/fleet/lifecycle.test.mjs +++ b/test/fleet/lifecycle.test.mjs @@ -46,9 +46,9 @@ function fleetFixture({ workerRuntime = 'claude' } = {}) { test('SCN-fleet-fresh-resume: workers start before manager and stable identities resume captured runtime sessions', () => { const fixture = fleetFixture(); const control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env }); - let session = 0; - const adapter = createClaudeAdapter({ executable: 'claude', idFactory: () => `runtime-${++session}` }); - const fresh = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapter, fresh: true }); + const adapter = createClaudeAdapter({ executable: 'claude' }); + const adapters = new Map([['claude', adapter]]); + const fresh = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapters, fresh: true }); assert.equal(fresh.actions.at(-1).areaId, 'session-manager'); assert.equal(fresh.actions.every((action) => action.mode === 'create'), true); assert.equal(fresh.actions.every((action) => action.mcp.args.includes(action.areaId)), true); @@ -58,8 +58,11 @@ test('SCN-fleet-fresh-resume: workers start before manager and stable identities const launches = []; const started = startFleet({ - plan: fresh, controlPlane: control, - executor: (launch) => { launches.push(launch); return { status: 0 }; }, + plan: fresh, controlPlane: control, adapters, + executor: (launch) => { + launches.push(launch); + return { status: 0, stdout: `runtime-${launches.length}\n` }; + }, }); assert.equal(started.started.at(-1).areaId, 'session-manager'); assert.equal(launches.length, fresh.actions.length); @@ -97,13 +100,13 @@ test('SCN-fleet-fresh-resume: workers start before manager and stable identities }).stateRoot, 'project.json'); assert.equal(JSON.parse(readFileSync(metadataPath, 'utf8')).detachedAt, detached.detachedAt); - const resumed = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapter, fresh: false }); + const resumed = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapters, fresh: false }); assert.equal(resumed.actions.every((action) => action.mode === 'resume'), true); assert.deepEqual( resumed.actions.map((action) => action.runtimeSessionId), - fresh.actions.map((action) => action.runtimeSessionId), + started.started.map((action) => action.runtimeSessionId), ); - startFleet({ plan: resumed, controlPlane: control, executor: () => ({ status: 0 }) }); + startFleet({ plan: resumed, controlPlane: control, adapters, executor: () => ({ status: 0 }) }); assert.equal(JSON.parse(readFileSync(metadataPath, 'utf8')).detachedAt, null); control.close(); }); @@ -111,8 +114,7 @@ test('SCN-fleet-fresh-resume: workers start before manager and stable identities test('SCN-fleet-wind-down-safety: active work blocks runtime shutdown and failed startup prevents manager launch', () => { const fixture = fleetFixture(); const control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env }); - let session = 0; - const adapter = createClaudeAdapter({ idFactory: () => `failure-${++session}` }); + const adapter = createClaudeAdapter(); const plan = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapter, fresh: true }); assert.throws( () => startFleet({ @@ -148,7 +150,7 @@ test('SCN-mixed-runtime: Codex and Claude share stable identities while planning const fixture = fleetFixture({ workerRuntime: 'codex' }); const control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env }); const adapters = new Map([ - ['claude', createClaudeAdapter({ idFactory: () => 'claude-manager-1' })], + ['claude', createClaudeAdapter()], ['codex', createCodexAdapter({ executable: 'codex' })], ]); const plan = planFleetUp({ @@ -157,18 +159,19 @@ test('SCN-mixed-runtime: Codex and Claude share stable identities while planning assert.equal(plan.canProceed, true); assert.equal(plan.actions[0].areaId, fixture.worker); assert.equal(plan.actions[0].runtime, 'codex'); - assert.equal(plan.actions[0].launch.args[0], 'exec'); + assert.equal(plan.actions[0].launch.args.includes('exec'), true); assert.equal(plan.actions[0].launch.args.some((arg) => arg.includes('mcp_servers.')), true); assert.equal(plan.actions[0].launch.args.some((arg) => arg.includes(fixture.worker)), true); assert.equal(plan.actions.at(-1).runtime, 'claude'); assert.equal(plan.actions.at(-1).launch.args.includes('--mcp-config'), true); - assert.equal(plan.actions.at(-1).runtimeSessionId, 'claude-manager-1'); + assert.equal(plan.actions.at(-1).runtimeSessionId, null); + assert.equal(plan.actions.at(-1).requiresRuntimeIdCapture, true); const started = startFleet({ plan, controlPlane: control, adapters, executor: (launch) => launch.areaId === fixture.worker ? { status: 0, stdout: '{"type":"thread.started","thread_id":"codex-worker-1"}\n' } - : { status: 0 }, + : { status: 0, stdout: 'claude-manager-1\n' }, }); assert.deepEqual(started.started.map((item) => item.runtimeSessionId), [ 'codex-worker-1', 'claude-manager-1', diff --git a/test/fleet/mcp-adapter.test.mjs b/test/fleet/mcp-adapter.test.mjs index c4b45e1..54f13b9 100644 --- a/test/fleet/mcp-adapter.test.mjs +++ b/test/fleet/mcp-adapter.test.mjs @@ -85,15 +85,24 @@ test('SCN-claude-adapter: planning is deterministic, capabilities are honest, an areaId: context.worker, title: 'Core', worktree: '/tmp/core', promptFile: '/tmp/core.md', firstMessage: 'Resume TASK-1.', model: 'opus', mcp: configuration.mcp, }); - assert.equal(launch.runtimeSessionId, 'runtime-123'); - assert.deepEqual(launch.launch.args.slice(0, 7), [ - '--bg', '--model', 'opus', '--session-id', 'runtime-123', '-n', 'TORCH · Core', + assert.equal(launch.runtimeSessionId, null); + assert.equal(launch.requiresRuntimeIdCapture, true); + assert.deepEqual(launch.launch.args.slice(0, 5), [ + '--bg', '--model', 'opus', '-n', 'TORCH · Core', ]); + assert.equal(launch.launch.args.includes('--session-id'), false); const mcpIndex = launch.launch.args.indexOf('--mcp-config'); const launchMcp = JSON.parse(launch.launch.args[mcpIndex + 1]).mcpServers[`torch-${context.worker}`]; assert.equal(launchMcp.command, process.execPath); assert.equal(launchMcp.args.at(-1), context.worker); assert.equal(calls.length, 0, 'creating a launch plan must not execute Claude'); + assert.equal(adapter.captureRuntimeId({ + areaId: context.worker, stdout: 'runtime-123\n', + }).runtimeSessionId, 'runtime-123'); + assert.throws( + () => adapter.captureRuntimeId({ areaId: context.worker, stdout: 'starting\nruntime-123\n' }), + (error) => error.code === 'RUNTIME_ID_NOT_CAPTURED', + ); assert.equal(adapter.getStatus({ runtimeSessionId: 'runtime-123' }).status, 'idle'); assert.equal(calls.length, 1); assert.throws(() => adapter.installHooks(), (error) => error.code === 'RUNTIME_CAPABILITY_UNSUPPORTED'); @@ -153,8 +162,11 @@ test('SCN-codex-adapter: turns are resumable, identity-bound, durable, and capab assert.equal(launch.runtimeSessionId, null); assert.equal(launch.requiresRuntimeIdCapture, true); assert.equal(launch.completionState, 'idle'); - assert.deepEqual(launch.launch.args.slice(0, 7), [ - 'exec', '--json', '--cd', '/tmp/codex-core', '--sandbox', 'workspace-write', '--approve-for-me', + assert.deepEqual(launch.launch.args.slice(0, 3), ['--cd', '/tmp/codex-core', '--approve-for-me']); + assert.equal(launch.launch.args.includes('--sandbox'), false); + assert.equal(launch.launch.args.indexOf('--approve-for-me') < launch.launch.args.indexOf('exec'), true); + assert.deepEqual(launch.launch.args.slice(launch.launch.args.indexOf('exec'), launch.launch.args.indexOf('exec') + 2), [ + 'exec', '--json', ]); assert.equal(launch.launch.args.includes(`mcp_servers.torch-${context.worker}.command=${JSON.stringify(process.execPath)}`), true); assert.equal(launch.launch.args.some((arg) => arg.includes(MCP_SERVER)), true); @@ -168,7 +180,11 @@ test('SCN-codex-adapter: turns are resumable, identity-bound, durable, and capab areaId: context.worker, runtimeSessionId: 'codex-thread-123', worktree: '/tmp/codex-core', message: 'Continue.', mcp: configuration.mcp, }); - assert.equal(resumed.launch.args.includes('resume'), true); + assert.equal(resumed.launch.args.includes('--sandbox'), false); + assert.equal(resumed.launch.args.indexOf('--cd') < resumed.launch.args.indexOf('exec'), true); + assert.equal(resumed.launch.args.indexOf('--approve-for-me') < resumed.launch.args.indexOf('exec'), true); + assert.equal(resumed.launch.args.indexOf('exec') < resumed.launch.args.indexOf('resume'), true); + assert.equal(resumed.launch.args.indexOf('resume') < resumed.launch.args.indexOf('--json'), true); assert.equal(resumed.launch.args.includes('codex-thread-123'), true); assert.equal(resumed.launch.args.some((arg) => arg.includes(MCP_SERVER)), true);