{"repo": "andy20010101/multi-coding-agent-symphony", "files": {"tests/v65-provider-readiness-codex-claude-only.test.js": {"e45cdc32f58064ae2b4a6415a318b80d9c5763ab": {"t": 1786068272.8502052, "n": 15210, "h": "93bba35526544553", "c": "import { describe, it } from 'node:test';\nimport assert from 'node:assert/strict';\nimport { readFileSync } from 'node:fs';\nimport { mkdir, mkdtemp, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { createSymphonyConsoleServer } from '../src/symphony/console.js';\nimport {\n ACTIVE_PROVIDER_IDS,\n PROVIDER_READINESS_BOUNDARIES,\n PROVIDER_READINESS_CONTRACT_NAME,\n ProviderReadinessContractError,\n assertProviderReadinessContract,\n buildProviderReadiness,\n buildProviderReadinessProjection,\n deriveProviderReadinessBlockedReasons,\n validateProviderReadinessContract\n} from '../src/symphony/provider-readiness-contracts.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst REPO_ROOT = join(__dirname, '..');\nconst FIXTURE_DIR = join(REPO_ROOT, 'fixtures/contracts/provider-readiness');\n\nconst VALID_FIXTURES = Object.freeze([\n 'provider-readiness.both-ready.v1.json',\n 'provider-readiness.codex-missing.v1.json',\n 'provider-readiness.claude-missing.v1.json',\n 'provider-readiness.claude-provider-mismatch.v1.json',\n 'provider-readiness.missing-deepseek-config.v1.json',\n 'provider-readiness.kiro-historical.v1.json',\n 'provider-readiness.unsupported-provider-claim-blocked.v1.json'\n]);\n\nconst INVALID_FIXTURES = Object.freeze([\n [\n 'provider-readiness.secret-like-value.invalid.v1.json',\n 'configuration.secretValue is not allowed'\n ],\n [\n 'provider-readiness.local-session-path.invalid.v1.json',\n 'must not contain secrets, raw provider output, local session refs, or command material'\n ],\n [\n 'provider-readiness.raw-provider-output.invalid.v1.json',\n 'helpSmoke.rawStdout is not allowed'\n ],\n [\n 'provider-readiness.unsupported-active-provider.invalid.v1.json',\n 'activeProviders[2].providerId must be one of codex-cli, claude-code-cli'\n ]\n]);\n\ndescribe('v65 Provider Readiness: Codex and Claude Code only', () => {\n it('validates provider readiness fixtures without exposing secrets, sessions, or raw output', () => {\n for (const name of VALID_FIXTURES) {\n const contract = fixture(name);\n const validation = validateProviderReadinessContract(contract);\n\n assert.equal(validation.ok, true, `${name}: ${validation.errors.join('; ')}`);\n assert.equal(contract.contractName, PROVIDER_READINESS_CONTRACT_NAME);\n assert.equal(contract.readOnly, true, name);\n assert.equal(contract.willMutate, false, name);\n assert.deepEqual(contract.boundaries, PROVIDER_READINESS_BOUNDARIES, name);\n assert.equal(contract.boundaries.providerExecutionFromReadinessAvailable, false, name);\n assert.equal(contract.boundaries.genericProviderPickerAvailable, false, name);\n assert.equal(contract.boundaries.rawProviderCliLauncherAvailable, false, name);\n assert.equal(contract.boundaries.frontendLocalJsonlReadAvailable, false, name);\n assert.equal(contract.boundaries.frontendProviderFolderReadAvailable, false, name);\n assert.equal(contract.boundaries.rawTranscriptExposureAvailable, false, name);\n assert.equal(contract.boundaries.rawStdoutExposureAvailable, false, name);\n assert.equal(contract.boundaries.rawStderrExposureAvailable, false, name);\n assert.equal(contract.boundaries.secretValueExposureAvailable, false, name);\n assert.equal(contract.boundaries.gitMergeAvailable, false, name);\n assert.equal(contract.boundaries.githubReleaseAutomationAvailable, false, name);\n assert.equal(contract.evidencePolicy.sanitizedReadinessOnly, true, name);\n assert.equal(contract.evidencePolicy.rawProviderOutputAllowed, false, name);\n assert.equal(contract.evidencePolicy.localSessionPathAllowed, false, name);\n assertNoUnsafeStringValues(contract, name);\n }\n });\n\n it('keeps the active provider line to Codex worker and Claude Code reviewer only', () => {\n const ready = fixture('provider-readiness.both-ready.v1.json');\n const [codex, claude] = ready.activeProviders;\n\n assert.deepEqual(ready.activeProviders.map((provider) => provider.providerId), ACTIVE_PROVIDER_IDS);\n assert.equal(codex.role, 'worker');\n assert.equal(codex.lane, 'codex-worker-candidate');\n assert.equal(codex.configuration.kind, 'codex-cli');\n assert.equal(codex.configuration.deepSeekConfigStatus, 'not-required');\n assert.equal(claude.role, 'reviewer');\n assert.equal(claude.lane, 'claude-code-reviewer-candidate');\n assert.equal(claude.configuration.kind, 'claude-code-provider-config');\n assert.equal(claude.configuration.deepSeekConfigStatus, 'present');\n assert.equal(claude.configuration.deepSeekAsIndependentProvider, false);\n assert.equal(ready.operatorRole.providerId, 'operator');\n assert.equal(ready.operatorRole.role, 'main-verifier');\n assert.equal(ready.operatorRole.willMutateInProduct, false);\n assert.deepEqual(deriveProviderReadinessBlockedReasons(ready), []);\n });\n\n it('records missing and mismatched readiness as explicit blockers', () => {\n const codexMissing = fixture('provider-readiness.codex-missing.v1.json');\n const claudeMissing = fixture('provider-readiness.claude-missing.v1.json');\n const claudeMismatch = fixture('provider-readiness.claude-provider-mismatch.v1.json');\n const missingDeepSeek = fixture('provider-readiness.missing-deepseek-config.v1.json');\n\n assert.equal(codexMissing.state, 'missing');\n assert.ok(codexMissing.blockedReasons.includes('codex-cli-missing'));\n assert.ok(codexMissing.blockedReasons.includes('codex-cli-binary-missing'));\n assert.ok(codexMissing.blockedReasons.includes('codex-cli-help-smoke-not-run'));\n\n assert.equal(claudeMissing.state, 'missing');\n assert.ok(claudeMissing.blockedReasons.includes('claude-code-cli-missing'));\n assert.ok(claudeMissing.blockedReasons.includes('claude-code-cli-deepseek-config-missing'));\n\n assert.equal(claudeMismatch.state, 'blocked');\n assert.ok(claudeMismatch.blockedReasons.includes('claude-code-cli-deepseek-config-mismatch'));\n\n assert.equal(missingDeepSeek.state, 'blocked');\n assert.ok(missingDeepSeek.blockedReasons.includes('claude-code-cli-deepseek-config-missing'));\n });\n\n it('keeps Kiro historical and blocks unsupported provider claims without making them active', () => {\n const kiroHistorical = fixture('provider-readiness.kiro-historical.v1.json');\n const unsupportedClaim = fixture('provider-readiness.unsupported-provider-claim-blocked.v1.json');\n\n assert.equal(kiroHistorical.historicalProviders[0].providerId, 'kiro-cli');\n assert.equal(kiroHistorical.historicalProviders[0].status, 'historical');\n assert.equal(kiroHistorical.historicalProviders[0].activeWorkbenchProvider, false);\n assert.deepEqual(\n deriveProviderReadinessBlockedReasons({\n ...kiroHistorical,\n historicalProviders: [{\n ...kiroHistorical.historicalProviders[0],\n activeWorkbenchProvider: true\n }]\n }),\n ['kiro-cli-historical-active-claim']\n );\n\n assert.equal(unsupportedClaim.state, 'blocked');\n assert.equal(unsupportedClaim.unsupportedProviders[0].providerId, 'gemini-cli');\n assert.equal(unsupportedClaim.unsupportedProviders[0].status, 'blocked');\n assert.equal(unsupportedClaim.unsupportedProviders[0].activeWorkbenchProvider, false);\n assert.ok(unsupportedClaim.blockedReasons.includes('unsupported-provider-active-claim-gemini-cli'));\n });\n\n it('rejects invalid fixtures with secret values, local session paths, raw provider output, and unsupported active providers', () => {\n for (const [name, expectedError] of INVALID_FIXTURES) {\n const validation = validateProviderReadinessContract(fixture(name));\n\n assert.equal(validation.ok, false, name);\n assert.ok(\n validation.errors.some((error) => error.includes(expectedError)),\n `${name}: expected ${expectedError}; got ${validation.errors.join('; ')}`\n );\n }\n });\n\n it('builds provider readiness contracts from sanitized backend-owned status inputs', () => {\n const ready = fixture('provider-readiness.both-ready.v1.json');\n const builtReady = buildProviderReadiness({\n generatedAt: ready.generatedAt,\n currentProject: ready.currentProject,\n activeProviders: ready.activeProviders,\n historicalProviders: ready.historicalProviders,\n operatorRole: ready.operatorRole,\n evidencePolicy: ready.evidencePolicy\n });\n\n assertProviderReadinessContract(builtReady);\n assert.equal(builtReady.state, 'ready');\n assert.deepEqual(builtReady.blockedReasons, []);\n\n const missingCodex = buildProviderReadiness({\n generatedAt: ready.generatedAt,\n currentProject: ready.currentProject,\n activeProviders: ready.activeProviders.map((provider) => provider.providerId === 'codex-cli'\n ? {\n ...provider,\n status: 'missing',\n binaryPresence: { state: 'missing', checked: true, evidenceRef: null, reason: 'codex cli missing' },\n modelProfile: { state: 'missing', checked: true, evidenceRef: null, reason: 'codex profile missing' },\n helpSmoke: { state: 'not-run', checked: false, evidenceRef: null, reason: 'codex cli missing' }\n }\n : provider),\n historicalProviders: ready.historicalProviders,\n operatorRole: ready.operatorRole,\n evidencePolicy: ready.evidencePolicy\n });\n\n assert.equal(missingCodex.state, 'missing');\n assert.ok(missingCodex.blockedReasons.includes('codex-cli-missing'));\n });\n\n it('projects provider readiness from backend provider health without running provider CLIs', async () => {\n const env = {\n OPENAI_API_KEY: 'sk-test-secret-value',\n ANTHROPIC_API_KEY: 'sk-test-secret-value',\n DEEPSEEK_API_KEY: 'sk-test-secret-value'\n };\n const readiness = buildProviderReadinessProjection({\n generatedAt: '2026-06-15T03:05:00.000Z',\n env\n });\n\n assert.equal(validateProviderReadinessContract(readiness).ok, true);\n assert.equal(readiness.contractName, PROVIDER_READINESS_CONTRACT_NAME);\n assert.equal(readiness.state, 'degraded');\n assert.deepEqual(readiness.activeProviders.map((provider) => provider.providerId), ACTIVE_PROVIDER_IDS);\n assert.equal(readiness.activeProviders[0].binaryPresence.state, 'unknown');\n assert.equal(readiness.activeProviders[0].helpSmoke.state, 'not-run');\n assert.equal(readiness.activeProviders[1].configuration.kind, 'claude-code-provider-config');\n assert.equal(readiness.activeProviders[1].configuration.deepSeekConfigStatus, 'present');\n assert.equal(readiness.activeProviders[1].configuration.deepSeekAsIndependentProvider, false);\n assert.equal(readiness.unsupportedProviders.find((provider) => provider.providerId === 'deepseek-cli').status, 'blocked');\n assert.equal(readiness.evidencePolicy.rawProviderOutputAllowed, false);\n assert.equal(readiness.boundaries.providerExecutionFromReadinessAvailable, false);\n assert.doesNotMatch(JSON.stringify(readiness), /sk-test-secret-value/u);\n\n const root = await mkdtemp(join(tmpdir(), 'symphony-v65-provider-readiness-api-'));\n\n try {\n await mkdir(join(root, '.git'));\n const server = createSymphonyConsoleServer({\n stateDir: join(root, '.symphony'),\n cwd: root,\n env\n });\n const baseUrl = await listenOnRandomPort(server);\n\n try {\n const response = await fetch(`${baseUrl}/api/providers/readiness`);\n\n assert.equal(response.status, 200);\n\n const apiReadiness = await response.json();\n\n assert.equal(validateProviderReadinessContract(apiReadiness).ok, true);\n assert.equal(apiReadiness.contractName, PROVIDER_READINESS_CONTRACT_NAME);\n assert.doesNotMatch(JSON.stringify(apiReadiness), /sk-test-secret-value/u);\n\n const postResponse = await fetch(`${baseUrl}/api/providers/readiness`, { method: 'POST' });\n const queryResponse = await fetch(`${baseUrl}/api/providers/readiness?provider=deepseek-cli`);\n\n assert.equal(postResponse.status, 405);\n assert.equal((await postResponse.json()).contractName, 'error-envelope.v1');\n assert.equal(queryResponse.status, 400);\n assert.equal((await queryResponse.json()).error.code, 'invalid-provider-readiness-request');\n } finally {\n await closeServer(server);\n }\n } finally {\n await rm(root, { recursive: true, force: true });\n }\n });\n\n it('throws before building from unsafe provider readiness source material', () => {\n const ready = fixture('provider-readiness.both-ready.v1.json');\n\n for (const source of [\n {\n activeProviders: [{\n ...ready.activeProviders[0],\n configuration: { ...ready.activeProviders[0].configuration, apiKey: 'sk-test-secret-value' }\n }, ready.activeProviders[1]]\n },\n {\n activeProviders: [{\n ...ready.activeProviders[0],\n sourceRefs: ['/Users/andy/.codex/sessions/provider.jsonl']\n }, ready.activeProviders[1]]\n },\n {\n activeProviders: [{\n ...ready.activeProviders[0],\n helpSmoke: { ...ready.activeProviders[0].helpSmoke, rawStdout: 'raw provider output' }\n }, ready.activeProviders[1]]\n }\n ]) {\n assert.throws(\n () => buildProviderReadiness({\n generatedAt: ready.generatedAt,\n currentProject: ready.currentProject,\n activeProviders: ready.activeProviders,\n historicalProviders: ready.historicalProviders,\n operatorRole: ready.operatorRole,\n evidencePolicy: ready.evidencePolicy,\n ...source\n }),\n (error) => {\n assert.equal(error instanceof ProviderReadinessContractError, true);\n assert.equal(error.code, 'unsafe-provider-readiness-source');\n return true;\n }\n );\n }\n });\n});\n\nfunction fixture(name) {\n return JSON.parse(readFileSync(join(FIXTURE_DIR, name), 'utf8'));\n}\n\nfunction assertNoUnsafeStringValues(value, label) {\n for (const text of collectStrings(value)) {\n assert.doesNotMatch(text, /\\/Users\\/|\\.jsonl|sk-[A-Za-z0-9_-]{8,}|raw provider output|raw stdout|raw stderr|raw transcript|raw model output/iu, label);\n }\n}\n\nfunction collectStrings(value) {\n if (Array.isArray(value)) {\n return value.flatMap((item) => collectStrings(item));\n }\n\n if (value !== null && typeof value === 'object') {\n return Object.values(value).flatMap((item) => collectStrings(item));\n }\n\n return typeof value === 'string' ? [value] : [];\n}\n\nasync function listenOnRandomPort(server) {\n await new Promise((resolvePromise, reject) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', () => {\n server.off('error', reject);\n resolvePromise();\n });\n });\n\n const address = server.address();\n\n assert.equal(typeof address, 'object');\n assert.notEqual(address, null);\n\n return `http://127.0.0.1:${address.port}`;\n}\n\nasync function closeServer(server) {\n await new Promise((resolvePromise, reject) => {\n server.close((error) => {\n if (error) {\n reject(error);\n return;\n }\n\n resolvePromise();\n });\n });\n}\n"}}}}