diff --git a/docs/TEST_INTEGRITY.md b/docs/TEST_INTEGRITY.md index c426613..d3111ed 100644 --- a/docs/TEST_INTEGRITY.md +++ b/docs/TEST_INTEGRITY.md @@ -2290,3 +2290,31 @@ test_integrity_note: did_relax_any_assertion: false if_true_explain_spec_basis: "" ``` + +# Codex startup runtime diagnostics (2026-09-30) + +```yaml +test_integrity_note: + change_type: new_tests + scenarios: + - id: SCN-codex-startup-diagnostics + given: "A real Codex adapter and lifecycle plan receive deterministic nonzero child-process results" + when: "Codex stdout contains a model-rejection event, malformed JSON, or output above the diagnostic bound" + then: "Owner-visible failure details prefer the structured model rejection, redact stderr secrets and prompt fields, and label malformed or oversized output unknown with an explicit reason" + observable_outcomes: + - "FLEET_START_FAILED status, persisted offline identity state and owner-visible diagnostic details" + - "Structured model rejection over noisy MCP stderr; explicit unknown outcomes without retained oversized stdout" + - "No synthetic prompt, token or password value in serialized error details" + determinism_controls: + - "Disposable local Git/XDG fixtures and fixed injected child-process output" + - "Real createCodexAdapter and startFleet boundary; no provider calls, retries, sleeps or network" + anti_cheat_rationale: + prevents: + - "Returning a hard-coded success while losing the real nonzero execution status" + - "Displaying unrelated MCP noise instead of a structured model rejection" + - "Leaking raw prompt or credential values through diagnostics" + - "Treating malformed or oversized stdout as trusted structured evidence" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` diff --git a/src/adapters/codex.mjs b/src/adapters/codex.mjs index e106e09..8992c38 100644 --- a/src/adapters/codex.mjs +++ b/src/adapters/codex.mjs @@ -86,7 +86,7 @@ function redactDiagnosticText(value, limit = MAX_DIAGNOSTIC_TEXT_LENGTH) { const compact = value.replace(/\s+/g, ' ').trim(); if (!compact) return null; return compact - .replace(/\bBearer\s+[A-Za-z0-9._~+\/-]+=*/gi, 'Bearer [REDACTED]') + .replace(/\bBearer\s+[-A-Za-z0-9._~+/]+=*/gi, 'Bearer [REDACTED]') .replace(/((?:api[_ -]?key|access[_ -]?token|refresh[_ -]?token|token|password|secret|credential|authorization|cookie)\s*(?:=|:|\s)\s*)([^\s,;]+)/gi, '$1[REDACTED]') .replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/gi, '$1[REDACTED]@') .slice(0, limit); diff --git a/test/fleet/lifecycle.test.mjs b/test/fleet/lifecycle.test.mjs index 14effb5..b90260d 100644 --- a/test/fleet/lifecycle.test.mjs +++ b/test/fleet/lifecycle.test.mjs @@ -360,3 +360,96 @@ test('SCN-mixed-runtime: Codex and Claude share stable identities while planning assert.equal(control.identity(fixture.worker).state, 'offline'); control.close(); }); + +test('SCN-codex-startup-diagnostics: owner-visible startup failures prefer bounded structured Codex evidence without disclosure', () => { + const modelFixture = fleetFixture({ workerRuntime: 'codex' }); + const modelControl = openControlPlane({ repositoryRoot: modelFixture.root, env: modelFixture.env }); + const modelAdapters = new Map([ + ['claude', createClaudeAdapter()], + ['codex', createCodexAdapter({ executable: 'codex' })], + ]); + const modelPlan = planFleetUp({ + repositoryRoot: modelFixture.root, controlPlane: modelControl, adapters: modelAdapters, fresh: true, + }); + let modelFailure; + assert.throws( + () => startFleet({ + plan: modelPlan, controlPlane: modelControl, adapters: modelAdapters, + executor: (launch) => launch.areaId === modelFixture.worker + ? { + status: 23, + stdout: '{"type":"error","code":"MODEL_NOT_SUPPORTED","message":"Requested model fixture-model is not supported","prompt":"fixture prompt must not leak"}\n', + stderr: 'Vercel MCP authentication warning: token=fixture-token password=fixture-password', + } + : { status: 0, stdout: 'claude-manager-1\n' }, + }), + (error) => { + modelFailure = error; + return error.code === 'FLEET_START_FAILED' && error.details.status === 23; + }, + ); + assert.deepEqual(modelFailure.details.diagnostic, { + schema: 'torch.dev/runtime-startup-diagnostic/v1alpha1', outcome: 'known', + category: 'model-rejection', code: 'MODEL_NOT_SUPPORTED', + message: 'Requested model fixture-model is not supported', + stderr: 'Vercel MCP authentication warning: token=[REDACTED] password=[REDACTED]', + }); + assert.equal(modelFailure.details.stderr, modelFailure.details.diagnostic.stderr); + assert.doesNotMatch(JSON.stringify(modelFailure.details), /fixture prompt|fixture-token|fixture-password/); + assert.equal(modelControl.identity(modelFixture.worker).state, 'offline'); + modelControl.close(); + + const malformedFixture = fleetFixture({ workerRuntime: 'codex' }); + const malformedControl = openControlPlane({ repositoryRoot: malformedFixture.root, env: malformedFixture.env }); + const malformedAdapters = new Map([ + ['claude', createClaudeAdapter()], + ['codex', createCodexAdapter({ executable: 'codex' })], + ]); + const malformedPlan = planFleetUp({ + repositoryRoot: malformedFixture.root, controlPlane: malformedControl, adapters: malformedAdapters, fresh: true, + }); + let malformedFailure; + assert.throws( + () => startFleet({ + plan: malformedPlan, controlPlane: malformedControl, adapters: malformedAdapters, + executor: (launch) => launch.areaId === malformedFixture.worker + ? { status: 24, stdout: '{malformed Codex event', stderr: 'MCP warning' } + : { status: 0, stdout: 'claude-manager-2\n' }, + }), + (error) => { + malformedFailure = error; + return error.code === 'FLEET_START_FAILED' && error.details.status === 24; + }, + ); + assert.equal(malformedFailure.details.diagnostic.outcome, 'unknown'); + assert.equal(malformedFailure.details.diagnostic.reason, 'stdout-malformed-json'); + assert.equal(malformedFailure.details.diagnostic.stderr, 'MCP warning'); + malformedControl.close(); + + const oversizedFixture = fleetFixture({ workerRuntime: 'codex' }); + const oversizedControl = openControlPlane({ repositoryRoot: oversizedFixture.root, env: oversizedFixture.env }); + const oversizedAdapters = new Map([ + ['claude', createClaudeAdapter()], + ['codex', createCodexAdapter({ executable: 'codex' })], + ]); + const oversizedPlan = planFleetUp({ + repositoryRoot: oversizedFixture.root, controlPlane: oversizedControl, adapters: oversizedAdapters, fresh: true, + }); + let oversizedFailure; + assert.throws( + () => startFleet({ + plan: oversizedPlan, controlPlane: oversizedControl, adapters: oversizedAdapters, + executor: (launch) => launch.areaId === oversizedFixture.worker + ? { status: 25, stdout: 'x'.repeat((16 * 1024) + 1), stderr: 'MCP warning' } + : { status: 0, stdout: 'claude-manager-3\n' }, + }), + (error) => { + oversizedFailure = error; + return error.code === 'FLEET_START_FAILED' && error.details.status === 25; + }, + ); + assert.equal(oversizedFailure.details.diagnostic.outcome, 'unknown'); + assert.equal(oversizedFailure.details.diagnostic.reason, 'stdout-exceeds-bound'); + assert.doesNotMatch(JSON.stringify(oversizedFailure.details), /x{512}/); + oversizedControl.close(); +});