From 16b3d0ea92d89db7043ad71239173d82e6277a00 Mon Sep 17 00:00:00 2001 From: Dan Shapiro <3732858+danshapiro@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:55:25 -0700 Subject: [PATCH] fix(testing): use typed OneCLI auth for OpenCode qualification --- docs/development/managed-runtime-rollout.md | 11 ++-- packages/freshell-mcp-runtime/package.json | 5 +- pnpm-lock.yaml | 4 ++ scripts/testing/runtime-sandbox.ts | 1 - .../e2e-browser/helpers/opencode-auth-file.ts | 39 ++++++++++++ .../helpers/opencode-native-history.ts | 7 +++ ...encode-provider-qualification-rust.spec.ts | 49 ++++++++++++--- .../runtime-terminal-continuity-rust.spec.ts | 22 ++----- test/runtime/gate-manifest.json | 2 +- .../testing/opencode-auth-file.test.ts | 63 +++++++++++++++++++ .../testing/opencode-native-history.test.ts | 9 ++- .../testing/runtime-amplifier-onecli.test.ts | 10 +++ 12 files changed, 186 insertions(+), 36 deletions(-) create mode 100644 test/e2e-browser/helpers/opencode-auth-file.ts create mode 100644 test/unit/tooling/testing/opencode-auth-file.test.ts diff --git a/docs/development/managed-runtime-rollout.md b/docs/development/managed-runtime-rollout.md index 1d7cdaa19..3a7f871e3 100644 --- a/docs/development/managed-runtime-rollout.md +++ b/docs/development/managed-runtime-rollout.md @@ -118,12 +118,11 @@ session, or sole remaining session as ownership or identity proof. 1. Run the dry run and resolve all blockers. 2. Run the repository's managed provider acceptance tests for the intended provider/mode. - The P2-G04 isolated browser test harness currently requires - `FRESHELL_MANAGED_OPENCODE_AUTH_FILE` to point to its existing OpenCode - `auth.json`; its temporary server home cannot discover the host credential. - This is a test-harness-only legacy input, not the managed provider contract. - Production launches use `FRESHELL_MANAGED__ONECLI_ENV_FILE` or - `FRESHELL_MANAGED__ONECLI_AUTH_FILE` as typed OneCLI references. + The P2-G04 isolated browser test harness requires + `FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE` to point to the private + existing OpenCode `auth.json`; its temporary server home cannot discover + the host credential. This uses the same typed OneCLI reference as managed + provider launches. 3. Apply `managed-opt-in` with the current control epoch and a unique request ID. 4. Verify the returned inventory revision and rollout mode. 5. Open one managed agent for every provider currently marked diff --git a/packages/freshell-mcp-runtime/package.json b/packages/freshell-mcp-runtime/package.json index 72b0c452a..90ccd723f 100644 --- a/packages/freshell-mcp-runtime/package.json +++ b/packages/freshell-mcp-runtime/package.json @@ -7,5 +7,8 @@ "@modelcontextprotocol/sdk": "1.30.0", "zod": "4.3.6" }, - "files": ["generated"] + "files": ["generated"], + "devDependencies": { + "esbuild": "0.28.2" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec2ca9f19..95d9cc33d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -253,6 +253,10 @@ importers: zod: specifier: 4.3.6 version: 4.3.6 + devDependencies: + esbuild: + specifier: 0.28.2 + version: 0.28.2 packages: diff --git a/scripts/testing/runtime-sandbox.ts b/scripts/testing/runtime-sandbox.ts index dc3017d98..300351edb 100644 --- a/scripts/testing/runtime-sandbox.ts +++ b/scripts/testing/runtime-sandbox.ts @@ -81,7 +81,6 @@ export function phase2BootstrapFiles( } for (const key of [ 'FRESHELL_MANAGED_CLAUDE_CREDENTIAL_FILE', - 'FRESHELL_MANAGED_OPENCODE_AUTH_FILE', 'FRESHELL_MANAGED_CODEX_AUTH_FILE', ]) { addRegularFile(env[key]?.trim()) diff --git a/test/e2e-browser/helpers/opencode-auth-file.ts b/test/e2e-browser/helpers/opencode-auth-file.ts new file mode 100644 index 000000000..f257ba841 --- /dev/null +++ b/test/e2e-browser/helpers/opencode-auth-file.ts @@ -0,0 +1,39 @@ +import fs from 'node:fs' +import path from 'node:path' + +/** Require the explicit typed OneCLI auth-file grant used by isolated OpenCode gates. */ +export function requireOpenCodeAuthFile( + env: NodeJS.ProcessEnv = process.env, + qualification = 'P2-G04', +): string { + const configured = env.FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE?.trim() + if (!configured) { + throw new Error( + `${qualification} requires FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE to point at a private OpenCode auth.json OneCLI grant`, + ) + } + + const authFile = path.resolve(configured) + let stat: fs.Stats + try { + stat = fs.lstatSync(authFile) + } catch { + throw new Error(`${qualification} OpenCode OneCLI auth grant is not an existing file: ${authFile}`) + } + if (!stat.isFile()) { + throw new Error(`${qualification} OpenCode OneCLI auth grant must be a regular file: ${authFile}`) + } + if ((stat.mode & 0o077) !== 0) { + throw new Error(`${qualification} OpenCode OneCLI auth grant must be private (mode 0600 or stricter): ${authFile}`) + } + if ((stat.mode & 0o400) === 0) { + throw new Error(`${qualification} OpenCode OneCLI auth grant must be owner-readable: ${authFile}`) + } + try { + const descriptor = fs.openSync(authFile, 'r') + fs.closeSync(descriptor) + return fs.realpathSync(authFile) + } catch { + throw new Error(`${qualification} OpenCode OneCLI auth grant is unreadable: ${authFile}`) + } +} diff --git a/test/e2e-browser/helpers/opencode-native-history.ts b/test/e2e-browser/helpers/opencode-native-history.ts index a5c663d38..ebd20ebfe 100644 --- a/test/e2e-browser/helpers/opencode-native-history.ts +++ b/test/e2e-browser/helpers/opencode-native-history.ts @@ -4,6 +4,13 @@ export type { NativeAssistantTurn, NativeHistory } from './provider-native-histo export { nativeTurnProof } from './provider-native-history/proof.js' import type { NativeAssistantTurn } from './provider-native-history/types.js' +export function openCodeCredentialFailureMessage(output: string): string | null { + if (/\bToken refresh failed:\s*401\b/i.test(stripVTControlCharacters(output))) { + return 'OpenCode credential refresh was rejected with HTTP 401; provide a currently valid OpenAI auth grant' + } + return null +} + /** * Self-contained query-only form of the current OpenCode native-history reader. * Runtime chaos tests execute it only through an ownership-checked provider diff --git a/test/e2e-browser/specs/runtime-opencode-provider-qualification-rust.spec.ts b/test/e2e-browser/specs/runtime-opencode-provider-qualification-rust.spec.ts index 1757163bf..a2813d5d0 100644 --- a/test/e2e-browser/specs/runtime-opencode-provider-qualification-rust.spec.ts +++ b/test/e2e-browser/specs/runtime-opencode-provider-qualification-rust.spec.ts @@ -22,10 +22,19 @@ import { P2_OPENCODE_VERSION, type ManagedRuntimeView, } from '../helpers/managed-runtime.js' +import { requireOpenCodeAuthFile } from '../helpers/opencode-auth-file.js' import { openPanePicker } from '../helpers/pane-picker.js' import { TerminalHelper } from '../helpers/terminal-helpers.js' import { TestHarness } from '../helpers/test-harness.js' -import { hasOpenCodePromptModelText, nativeTurnProof, openCodeTerminalReady, selectNativeAssistantTurn, type NativeAssistantTurn } from '../helpers/opencode-native-history.js' +import { + hasOpenCodePromptModelText, + nativeTurnProof, + openCodeCredentialFailureMessage, + openCodeTerminalReady, + OPENCODE_NATIVE_HISTORY_SCRIPT, + selectNativeAssistantTurn, + type NativeAssistantTurn, +} from '../helpers/opencode-native-history.js' import type { ProviderQualificationRow } from '../../../scripts/testing/provider-qualification-receipt.js' function leavesByMode(node: any, mode: string): any[] { @@ -41,10 +50,13 @@ async function waitForValue( description: string, probe: () => T | null | undefined | Promise, timeoutMs: number, + abortProbe?: () => Error | null | undefined | Promise, ): Promise { const deadline = Date.now() + timeoutMs let lastError: unknown while (Date.now() < deadline) { + const abortError = await abortProbe?.() + if (abortError) throw abortError try { const value = await probe() if (value !== null && value !== undefined) return value @@ -275,10 +287,8 @@ async function paneSessionId( function nativeAssistantTurns(rig: ManagedRuntimeBrowserRig, view: ManagedRuntimeView, sessionId: string): NativeAssistantTurn[] { if (!view.containerId) throw new Error('native evidence probe has no exact owned container') - const probe = path.join(rig.repoRoot, 'test/e2e-browser/helpers/provider-native-history/probe-cli.ts') - const tsxLoader = path.join(rig.repoRoot, 'node_modules/tsx/dist/loader.mjs') const raw = rig.ownedProviderExec(view.containerId, [ - 'node', '--no-warnings', '--import', tsxLoader, probe, 'opencode', + 'node', '--no-warnings', '-e', OPENCODE_NATIVE_HISTORY_SCRIPT, '/home/freshell/provider/.local/share/opencode/opencode.db', sessionId, ]) const evidence = JSON.parse(raw) @@ -288,12 +298,20 @@ function nativeAssistantTurns(rig: ManagedRuntimeBrowserRig, view: ManagedRuntim } async function nextNativeAssistantTurn( + page: Page, + terminalId: string, rig: ManagedRuntimeBrowserRig, view: ManagedRuntimeView, sessionId: string, priorMessageIds: ReadonlySet, expectedText: string, ): Promise { return waitForValue('the correlated completed native assistant response, not a rendered echo', () => ( selectNativeAssistantTurn(nativeAssistantTurns(rig, view, sessionId), priorMessageIds, expectedText) - ), 180_000) + ), 180_000, async () => { + const terminalOutput = await page.evaluate((id) => ( + window.__FRESHELL_TEST_HARNESS__?.getTerminalBuffer?.(id) ?? '' + ), terminalId) + const failure = openCodeCredentialFailureMessage(terminalOutput) + return failure ? new Error(failure) : undefined + }) } function verifyMemoryAnswer(turn: NativeAssistantTurn, projectName: string): void { @@ -345,11 +363,15 @@ test.describe.serial('OpenCode provider qualification', () => { ) test.setTimeout(1_800_000) + const authFile = requireOpenCodeAuthFile(process.env, 'OpenCode provider qualification') const blockerEvidence = runBlockerMatrixTests(process.cwd()) const rig = new ManagedRuntimeBrowserRig( process.cwd(), 5, - {}, + { + FRESHELL_BIND_HOST: '0.0.0.0', + FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE: authFile, + }, { FRESHELL_RUNTIME_OBSERVER_INTERVAL_MS: '750' }, 'release', ) @@ -391,6 +413,13 @@ test.describe.serial('OpenCode provider qualification', () => { .toBe(P2_OPENCODE_VERSION) const processArgs = rig.ownedContainerProcessTable(first.view.containerId) expect(processArgs).toContain(P2_OPENCODE_MODEL) + const authProbe = rig.ownedProviderExec(first.view.containerId, [ + 'node', '--no-warnings', '-e', + "const fs=require('node:fs');const auth=JSON.parse(fs.readFileSync('/home/freshell/provider/.local/share/opencode/auth.json','utf8'));const openai=auth.openai;if(!openai||typeof openai.access!=='string'||typeof openai.refresh!=='string')process.exit(2);process.stdout.write('OpenAI credential present')", + ]) + expect(authProbe.trim()).toBe('OpenAI credential present') + const availableModels = rig.ownedProviderExec(first.view.containerId, ['opencode', 'models', 'openai']) + expect(availableModels).toContain(P2_OPENCODE_MODEL.split('/')[1]) const exactLimits = cgroupLimitEvidence(rig, first.view) expect(exactLimits.swapMax).toBe('0') @@ -406,7 +435,7 @@ test.describe.serial('OpenCode provider qualification', () => { await paneSessionId(harness, tabId, first.paneId) ), 120_000) expect(nativeSessionId).toMatch(/^ses_/) - const firstAnswer = await nextNativeAssistantTurn(rig, first.view, nativeSessionId, new Set(), nonce) + const firstAnswer = await nextNativeAssistantTurn(page, first.terminalId, rig, first.view, nativeSessionId, new Set(), nonce) verifyMemoryAnswer(firstAnswer, nonce) const nativeTurnProofs = [nativeTurnProof('initial', nativeSessionId, firstAnswer, nonce)] @@ -435,7 +464,7 @@ test.describe.serial('OpenCode provider qualification', () => { await waitForReplacementPrompt(page, harness, rig, tabId, first.paneId, afterHostCrash) const beforeRecall = new Set(nativeAssistantTurns(rig, afterHostCrash, nativeSessionId).map((turn) => turn.messageId)) await executeInPane(page, first.paneId, 'What is the name of the project we chose earlier?') - const recalledAnswer = await nextNativeAssistantTurn(rig, afterHostCrash, nativeSessionId, beforeRecall, nonce) + const recalledAnswer = await nextNativeAssistantTurn(page, first.terminalId, rig, afterHostCrash, nativeSessionId, beforeRecall, nonce) verifyMemoryAnswer(recalledAnswer, nonce) expect(recalledAnswer.messageId).not.toBe(firstAnswer.messageId) nativeTurnProofs.push(nativeTurnProof('after_session_host_crash', nativeSessionId, recalledAnswer, nonce)) @@ -469,7 +498,7 @@ test.describe.serial('OpenCode provider qualification', () => { await waitForReplacementPrompt(page, harness, rig, tabId, first.paneId, afterProviderCrash) const beforeProviderFollowup = new Set(nativeAssistantTurns(rig, afterProviderCrash, nativeSessionId).map((turn) => turn.messageId)) await executeInPane(page, first.paneId, 'Please remind me of the project name we selected.') - const providerAnswer = await nextNativeAssistantTurn(rig, afterProviderCrash, nativeSessionId, beforeProviderFollowup, nonce) + const providerAnswer = await nextNativeAssistantTurn(page, first.terminalId, rig, afterProviderCrash, nativeSessionId, beforeProviderFollowup, nonce) verifyMemoryAnswer(providerAnswer, nonce) expect(providerAnswer.messageId).not.toBe(recalledAnswer.messageId) nativeTurnProofs.push(nativeTurnProof('after_provider_process_crash', nativeSessionId, providerAnswer, nonce)) @@ -486,7 +515,7 @@ test.describe.serial('OpenCode provider qualification', () => { const secondSessionId = await waitForValue('second exact OpenCode session id', async () => ( await paneSessionId(harness, tabId, second.paneId) ), 120_000) - const secondAnswer = await nextNativeAssistantTurn(rig, second.view, secondSessionId, new Set(), secondNonce) + const secondAnswer = await nextNativeAssistantTurn(page, second.terminalId, rig, second.view, secondSessionId, new Set(), secondNonce) verifyMemoryAnswer(secondAnswer, secondNonce) expect(secondAnswer.text).not.toContain(nonce) expect(second.view.soulId).not.toBe(first.view.soulId) diff --git a/test/e2e-browser/specs/runtime-terminal-continuity-rust.spec.ts b/test/e2e-browser/specs/runtime-terminal-continuity-rust.spec.ts index 9650a3d5b..b709ec4ba 100644 --- a/test/e2e-browser/specs/runtime-terminal-continuity-rust.spec.ts +++ b/test/e2e-browser/specs/runtime-terminal-continuity-rust.spec.ts @@ -13,23 +13,12 @@ import fs from 'node:fs' import path from 'node:path' import { ManagedRuntimeBrowserRig, P2_OPENCODE_MODEL, P2_OPENCODE_VERSION, type ManagedRuntimeView } from '../helpers/managed-runtime.js' +import { requireOpenCodeAuthFile } from '../helpers/opencode-auth-file.js' import { OPENCODE_NATIVE_HISTORY_SCRIPT, type NativeHistory } from '../helpers/opencode-native-history.js' import { TestHarness } from '../helpers/test-harness.js' import { TerminalHelper } from '../helpers/terminal-helpers.js' import { openPanePicker } from '../helpers/pane-picker.js' -function requireOpenCodeAuthFile(): string { - const configured = process.env.FRESHELL_MANAGED_OPENCODE_AUTH_FILE - if (!configured) { - throw new Error('P2-G04 requires FRESHELL_MANAGED_OPENCODE_AUTH_FILE to point at the existing OpenCode auth.json') - } - const authFile = path.resolve(configured) - if (!fs.statSync(authFile).isFile()) { - throw new Error(`P2-G04 OpenCode auth reference is not a file: ${authFile}`) - } - return authFile -} - function findTerminalLeaves(node: any, out: any[] = []): any[] { if (!node) return out if (node.type === 'leaf' && node.content?.kind === 'terminal') out.push(node) @@ -334,12 +323,13 @@ test.describe.serial('Phase 2 managed runtime continuity', () => { test('P2-G04: OpenAI-authenticated OpenCode tool turn survives web replacement in one native session', async ({ page }) => { test.setTimeout(900_000) - // The rig runs the web server with an isolated HOME. Pass the host auth - // file explicitly; otherwise the managed runtime starts without the - // configured provider credential and OpenCode silently uses its default. + // The rig runs the web server with an isolated HOME. Pass the existing + // private auth file as a typed OneCLI grant; otherwise OpenCode silently + // uses its default model in the managed provider volume. const authFile = requireOpenCodeAuthFile() const rig = new ManagedRuntimeBrowserRig(process.cwd(), 2, { - FRESHELL_MANAGED_OPENCODE_AUTH_FILE: authFile, + FRESHELL_BIND_HOST: '0.0.0.0', + FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE: authFile, }) try { const info = await rig.start() diff --git a/test/runtime/gate-manifest.json b/test/runtime/gate-manifest.json index 9cbe82df5..f72ca3264 100644 --- a/test/runtime/gate-manifest.json +++ b/test/runtime/gate-manifest.json @@ -283,7 +283,7 @@ { "id": "P2-G04", "required": true, - "procedure": "Start OpenCode 1.18.21 with openai/gpt-5.6-luna using the explicit FRESHELL_MANAGED_OPENCODE_AUTH_FILE reference to the configured OpenAI OAuth credential; verify the managed provider volume contains the OpenAI credential and the requested model is available; complete a first Bash-tool turn, begin a controlled long Bash tool, SIGKILL/restart web while the tool is running, then run a provider-side tool in a follow-up.", + "procedure": "Start OpenCode 1.18.21 with openai/gpt-5.6-luna using the explicit FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE reference to the configured private OpenAI OAuth credential; verify the managed provider volume contains the OpenAI credential and the requested model is available; complete a first Bash-tool turn, begin a controlled long Bash tool, SIGKILL/restart web while the tool is running, then run a provider-side tool in a follow-up.", "pass_assertion": "Same native ses_* session, soul, OS incarnation, container and host boot; tool effects occur exactly once; provider launch count stays one; no paid-model fallback or CLI auto-update.", "status": "NOT_RUN" }, diff --git a/test/unit/tooling/testing/opencode-auth-file.test.ts b/test/unit/tooling/testing/opencode-auth-file.test.ts new file mode 100644 index 000000000..85dd6d4a2 --- /dev/null +++ b/test/unit/tooling/testing/opencode-auth-file.test.ts @@ -0,0 +1,63 @@ +// @vitest-environment node +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' + +import { requireOpenCodeAuthFile } from '../../../e2e-browser/helpers/opencode-auth-file.js' + +let root: string + +beforeEach(() => { + root = fs.mkdtempSync(path.join(os.tmpdir(), 'opencode-auth-reference-')) +}) + +afterEach(() => { + fs.rmSync(root, { recursive: true, force: true }) +}) + +describe('OpenCode qualification auth reference', () => { + it('fails fast when no explicit OneCLI auth grant is configured', () => { + expect(() => requireOpenCodeAuthFile({})).toThrow(/FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE/) + }) + + it('returns the resolved path of a private regular OneCLI auth grant', () => { + const authFile = path.join(root, 'auth.json') + fs.writeFileSync(authFile, '{}') + fs.chmodSync(authFile, 0o600) + + expect(requireOpenCodeAuthFile({ FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE: authFile })) + .toBe(path.resolve(authFile)) + }) + + it('rejects missing paths, directories, linked grants, and unusable file permissions', () => { + expect(() => requireOpenCodeAuthFile({ + FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE: path.join(root, 'missing.json'), + })).toThrow(/existing file/) + expect(() => requireOpenCodeAuthFile({ + FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE: root, + })).toThrow(/regular file/) + + const privateFile = path.join(root, 'private.json') + fs.writeFileSync(privateFile, '{}', { mode: 0o600 }) + const linkedFile = path.join(root, 'linked.json') + fs.symlinkSync(privateFile, linkedFile) + expect(() => requireOpenCodeAuthFile({ + FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE: linkedFile, + })).toThrow(/regular file/) + + const publicFile = path.join(root, 'public.json') + fs.writeFileSync(publicFile, '{}', { mode: 0o644 }) + fs.chmodSync(publicFile, 0o644) + expect(() => requireOpenCodeAuthFile({ + FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE: publicFile, + })).toThrow(/private/) + + const unreadableFile = path.join(root, 'unreadable.json') + fs.writeFileSync(unreadableFile, '{}', { mode: 0o000 }) + fs.chmodSync(unreadableFile, 0o000) + expect(() => requireOpenCodeAuthFile({ + FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE: unreadableFile, + })).toThrow(/owner-readable/) + }) +}) diff --git a/test/unit/tooling/testing/opencode-native-history.test.ts b/test/unit/tooling/testing/opencode-native-history.test.ts index d4ab6834f..b7781704d 100644 --- a/test/unit/tooling/testing/opencode-native-history.test.ts +++ b/test/unit/tooling/testing/opencode-native-history.test.ts @@ -5,7 +5,7 @@ import os from 'node:os' import path from 'node:path' import { DatabaseSync } from 'node:sqlite' import { afterEach, beforeEach, describe, expect, it } from 'vitest' -import { hasOpenCodePromptModelText, nativeTurnProof, openCodeTerminalReady, selectNativeAssistantTurn } from '../../../e2e-browser/helpers/opencode-native-history.js' +import { hasOpenCodePromptModelText, nativeTurnProof, openCodeCredentialFailureMessage, openCodeTerminalReady, selectNativeAssistantTurn } from '../../../e2e-browser/helpers/opencode-native-history.js' import { readOpenCodeNativeHistory } from '../../../e2e-browser/helpers/provider-native-history/opencode.js' let root: string @@ -36,6 +36,13 @@ function read(session = 'ses_owned') { } describe('live recovery proves new native assistant responses, never TUI echo or replay', () => { + it('surfaces an OpenAI token refresh rejection instead of waiting for the native response timeout', () => { + expect(openCodeCredentialFailureMessage('\u001b[31mToken refresh failed: 401\u001b[0m')) + .toMatch(/credential refresh was rejected.*401/i) + expect(openCodeCredentialFailureMessage('Token refresh failed: 403')).toBeNull() + expect(openCodeCredentialFailureMessage('GPT-5.6 Luna')).toBeNull() + }) + it('reads only completed assistant messages for the exact native session', () => { message('echo', 'ses_owned', 'user', 'nonce-in-echo') message('unfinished', 'ses_owned', 'assistant', 'nonce-unfinished', null) diff --git a/test/unit/tooling/testing/runtime-amplifier-onecli.test.ts b/test/unit/tooling/testing/runtime-amplifier-onecli.test.ts index f7c0aade2..6bae5b813 100644 --- a/test/unit/tooling/testing/runtime-amplifier-onecli.test.ts +++ b/test/unit/tooling/testing/runtime-amplifier-onecli.test.ts @@ -49,6 +49,16 @@ describe('live Amplifier OneCLI qualification grants', () => { expect(phase2BootstrapFiles(env)).toEqual([environmentGrant, authGrant]) }) + it('admits only the typed OpenCode OneCLI auth grant, not the legacy raw auth path', () => { + const onecliGrant = privateGrant('onecli-auth.json') + const legacyAuth = privateGrant('legacy-auth.json') + + expect(phase2BootstrapFiles({ + FRESHELL_MANAGED_OPENCODE_ONECLI_AUTH_FILE: onecliGrant, + FRESHELL_MANAGED_OPENCODE_AUTH_FILE: legacyAuth, + })).toEqual([onecliGrant]) + }) + it('rejects a missing grant even when the old keys-file input is set', () => { const legacyKeys = privateGrant('keys.env') const env = { FRESHELL_MANAGED_AMPLIFIER_ONECLI_KEYS_FILE: legacyKeys }