diff --git a/docs/TEST_INTEGRITY.md b/docs/TEST_INTEGRITY.md index f1cd4fc..7a04bea 100644 --- a/docs/TEST_INTEGRITY.md +++ b/docs/TEST_INTEGRITY.md @@ -2992,3 +2992,28 @@ test_integrity_note: did_relax_any_assertion: false if_true_explain_spec_basis: "" ``` + +```yaml +test_integrity_note: + change_type: new_tests + scenarios: + - id: SCN-codex-registered-mcp-runtime-dir + given: "The registered Codex adapter receives a Linux parent environment with a valid runtime directory, then the same environment with that variable removed" + when: "It produces create and resume plans for an identity-bound TORCH MCP server under an explicit model, reasoning, sandbox, and approval profile" + then: "Both valid plans name exactly XDG_RUNTIME_DIR; both removed-context plans omit env_vars while preserving the MCP identity, profile tokens, create/resume mode, and resume session token" + observable_outcomes: + - "The registry-backed Codex launch records contain only the one-name runtime allowlist when the real parent context has a valid value" + - "Removing XDG_RUNTIME_DIR removes only that allowlist, not the model, reasoning, approval, sandbox, MCP identity, or session token" + - "No runtime directory, DBUS address, home, PATH, token, or general parent environment value is serialized" + determinism_controls: + - "Hermetic fixture repository and explicit process-like parent environments passed to the registered adapter; no provider execution, host service, native runtime, retry, or process-wide environment mutation" + anti_cheat_rationale: + prevents: + - "Bypassing the registered Codex adapter to preserve launch profile or identity fields" + - "Hard-coding a runtime directory or forwarding DBUS, secrets, home, PATH, or the full environment" + - "Retaining env_vars after the parent runtime context is removed" + - "Claiming plan-only evidence as installed or native qualification" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` diff --git a/test/fleet/mcp-adapter.test.mjs b/test/fleet/mcp-adapter.test.mjs index e196697..bea5825 100644 --- a/test/fleet/mcp-adapter.test.mjs +++ b/test/fleet/mcp-adapter.test.mjs @@ -6,6 +6,7 @@ import { join } from 'node:path'; import test from 'node:test'; import { createClaudeAdapter } from '../../src/adapters/claude.mjs'; import { createCodexAdapter } from '../../src/adapters/codex.mjs'; +import { createRuntimeAdapterRegistry } from '../../src/adapters/registry.mjs'; import { openControlPlane } from '../../src/control-plane/service.mjs'; import { analyzeRepository } from '../../src/kernel/analyze.mjs'; import { proposeDomains } from '../../src/kernel/domains.mjs'; @@ -292,6 +293,74 @@ test('SCN-codex-mcp-runtime-dir: only a valid Linux parent runtime directory is } }); +test('SCN-codex-registered-mcp-runtime-dir: registered create and resume keep identity and profile tokens while an inherited runtime name is removed safely', () => { + const context = fixture(); + const parent = { + ...context.env, + XDG_RUNTIME_DIR: '/run/user/1000', + DBUS_SESSION_BUS_ADDRESS: 'unix:path=/run/user/1000/bus', + HOME: '/home/fixture', PATH: '/usr/bin', TOKEN: 'not-forwarded', + }; + const profile = { + model: 'gpt-registered', reasoning: 'high', + launchPolicy: { sandbox: 'workspace-write', approval: 'on-request' }, + }; + const plansFor = (env) => { + const adapter = createRuntimeAdapterRegistry({ env }).get('codex'); + const mcp = adapter.configure({ + repositoryRoot: context.root, areaId: context.worker, mcpEntry: MCP_SERVER, + }).mcp; + return { + mcp, + create: adapter.createSession({ + areaId: context.worker, worktree: '/tmp/codex-registered-runtime-dir', promptFile: '/tmp/prompt.md', + firstMessage: 'Start registered.', mcp, ...profile, + }), + resume: adapter.resumeSession({ + areaId: context.worker, runtimeSessionId: 'thread-registered-runtime-dir', + worktree: '/tmp/codex-registered-runtime-dir', message: 'Resume registered.', mcp, ...profile, + }), + }; + }; + const expectedEnvVars = `mcp_servers.torch-${context.worker}.env_vars=["XDG_RUNTIME_DIR"]`; + const expectedMcpIdentity = `mcp_servers.torch-${context.worker}.command=${JSON.stringify(process.execPath)}`; + const assertPreservedLaunchTokens = (plan, { expectsRuntimeName }) => { + const args = plan.launch.args; + assert.equal(args.includes('--cd'), true); + assert.equal(args.includes('/tmp/codex-registered-runtime-dir'), true); + assert.equal(args.includes('--sandbox'), true); + assert.equal(args.includes('workspace-write'), true); + assert.equal(args.includes('--ask-for-approval'), true); + assert.equal(args.includes('on-request'), true); + assert.equal(args.includes('--model'), true); + assert.equal(args.includes('gpt-registered'), true); + assert.equal(args.includes('model_reasoning_effort="high"'), true); + assert.equal(args.includes(expectedMcpIdentity), true); + assert.equal(args.includes(expectedEnvVars), expectsRuntimeName); + assert.equal(args.some((arg) => /env_vars=/.test(arg) && arg !== expectedEnvVars), false); + assert.equal(args.some((arg) => arg.includes(parent.XDG_RUNTIME_DIR) + || arg.includes(parent.DBUS_SESSION_BUS_ADDRESS) || arg.includes(parent.HOME) + || arg.includes(parent.PATH) || arg.includes(parent.TOKEN)), false); + }; + + const inherited = plansFor(parent); + assertPreservedLaunchTokens(inherited.create, { expectsRuntimeName: true }); + assertPreservedLaunchTokens(inherited.resume, { expectsRuntimeName: true }); + assert.equal(inherited.create.launch.args.includes('exec'), true); + assert.equal(inherited.create.launch.args.includes('resume'), false); + assert.equal(inherited.resume.launch.args.includes('exec'), true); + assert.equal(inherited.resume.launch.args.includes('resume'), true); + assert.equal(inherited.resume.launch.args.includes('thread-registered-runtime-dir'), true); + + const withoutRuntime = { ...parent }; + delete withoutRuntime.XDG_RUNTIME_DIR; + const removed = plansFor(withoutRuntime); + assertPreservedLaunchTokens(removed.create, { expectsRuntimeName: false }); + assertPreservedLaunchTokens(removed.resume, { expectsRuntimeName: false }); + assert.equal(removed.mcp.name, inherited.mcp.name); + assert.equal(removed.resume.launch.args.includes('thread-registered-runtime-dir'), true); +}); + test('SCN-mcp-stdio: an MCP host can initialize and list the TORCH tool surface over stdio', async () => { const context = fixture(); const child = spawn(process.execPath, [MCP_SERVER, '--root', context.root, '--area', context.worker], {