Files
orca/src/cli/handlers/orchestration-gate-cli.test.ts
T
Brennan Benson 913509edeb fix(orchestration): prevent slow worker-start stalls (#16300)
* Extend orchestration agent submission timing budgets

* fix(orchestration): preserve mutation recovery identity

* fix(orchestration): preserve recovery executable identity

* fix(orchestration): keep worker starts and recovery commands safe

* test(orchestration): cover federated worker preflight

* fix(orchestration): harden mutation recovery

* fix(orchestration): redact dispatch recovery credentials

* chore: preserve upstream skill dialog formatting

* test(orchestration): stabilize agent prompt submit e2e

* fix(orchestration): validate federated start receipts

* perf(runtime): cache unchanged prompt verification tail

* fix(orchestration): reject worker-start timer overflow

* fix(orchestration): normalize worker-start timeout defaults

* fix(orchestration): normalize worker-start readiness budgets

* fix(orchestration): normalize federated readiness timeout

* test(runtime): tolerate current-main degradation exports

* chore: preserve current-main orcad formatting

* chore: drop unrelated formatting carryover
2026-08-27 15:25:30 -07:00

252 lines
8.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const { callMock, getTerminalHandleMock } = vi.hoisted(() => ({
callMock: vi.fn(),
getTerminalHandleMock: vi.fn()
}))
vi.mock('../runtime-client', async () => {
// Why: re-export the REAL error classes so format.ts `instanceof` narrowing still matches.
const { RuntimeClientError, RuntimeRpcFailureError } = await import('../runtime/types.js')
class RuntimeClient {
readonly isRemote = false
call = callMock
getCliStatus = vi.fn()
openOrca = vi.fn()
}
return {
RuntimeClient,
RuntimeClientError,
RuntimeRpcFailureError,
serveOrcaApp: vi.fn(),
getDefaultUserDataPath: vi.fn(() => '/tmp/orca-user-data')
}
})
vi.mock('../selectors', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
getTerminalHandle: getTerminalHandleMock
}))
import { main } from '../index'
import { RuntimeClientError } from '../runtime/types'
import { okFixture, queueFixtures } from '../test-fixtures'
const originalTerminalHandle = process.env.ORCA_TERMINAL_HANDLE
const originalPaneKey = process.env.ORCA_PANE_KEY
const restoreEnv = (name: string, value: string | undefined): void => {
if (value === undefined) {
delete process.env[name]
} else {
process.env[name] = value
}
}
describe('orchestration gate commands carry caller identity', () => {
let logSpy: ReturnType<typeof vi.spyOn>
let errorSpy: ReturnType<typeof vi.spyOn>
beforeEach(() => {
callMock.mockReset()
getTerminalHandleMock.mockReset()
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
delete process.env.ORCA_TERMINAL_HANDLE
delete process.env.ORCA_PANE_KEY
process.exitCode = 0
})
afterEach(() => {
logSpy.mockRestore()
errorSpy.mockRestore()
restoreEnv('ORCA_TERMINAL_HANDLE', originalTerminalHandle)
restoreEnv('ORCA_PANE_KEY', originalPaneKey)
process.exitCode = 0
})
const paramsFor = (method: string): Record<string, unknown> =>
callMock.mock.calls.find((call) => call[0] === method)?.[1] as Record<string, unknown>
it('sends the bound coordinator handle to gateCreate', async () => {
process.env.ORCA_TERMINAL_HANDLE = 'term_coord'
queueFixtures(
callMock,
okFixture('req_show', { terminal: { handle: 'term_coord' } }),
okFixture('req_gate', { gate: { id: 'gate_1', task_id: 'task_1', status: 'pending' } })
)
await main(
['orchestration', 'gate-create', '--task', 'task_1', '--question', 'ship?', '--json'],
'/tmp/repo'
)
expect(process.exitCode).toBe(0)
expect(paramsFor('orchestration.gateCreate')).toEqual(
expect.objectContaining({ task: 'task_1', question: 'ship?', from: 'term_coord' })
)
})
it('remints a stale environment handle before authorizing gateCreate', async () => {
process.env.ORCA_TERMINAL_HANDLE = 'term_stale'
process.env.ORCA_PANE_KEY = 'tab_coord:leaf_coord'
callMock.mockImplementation(async (method: string) => {
if (method === 'terminal.show') {
throw new RuntimeClientError('terminal_handle_stale', 'stale')
}
if (method === 'terminal.resolvePane') {
return okFixture('req_pane', { terminal: { handle: 'term_live' } })
}
return okFixture('req_gate', {
gate: { id: 'gate_1', task_id: 'task_1', status: 'pending' }
})
})
await main(
['orchestration', 'gate-create', '--task', 'task_1', '--question', 'ship?', '--json'],
'/tmp/repo'
)
expect(paramsFor('orchestration.gateCreate')).toEqual(
expect.objectContaining({ from: 'term_live' })
)
})
it('accepts an explicit --from without probing terminal liveness', async () => {
queueFixtures(
callMock,
okFixture('req_gate', {
gate: { id: 'gate_1', task_id: 'task_1', status: 'resolved', resolution: 'go' }
})
)
await main(
[
'orchestration',
'gate-resolve',
'--id',
'gate_1',
'--resolution',
'go',
'--from',
'term_explicit',
'--json'
],
'/tmp/repo'
)
expect(process.exitCode).toBe(0)
expect(callMock).toHaveBeenCalledTimes(1)
expect(paramsFor('orchestration.gateResolve')).toEqual(
expect.objectContaining({ id: 'gate_1', resolution: 'go', from: 'term_explicit' })
)
})
it('scopes gate-list to the caller when no Run is named', async () => {
process.env.ORCA_TERMINAL_HANDLE = 'term_coord'
queueFixtures(
callMock,
okFixture('req_show', { terminal: { handle: 'term_coord' } }),
okFixture('req_list', { gates: [], count: 0 })
)
await main(['orchestration', 'gate-list', '--json'], '/tmp/repo')
expect(process.exitCode).toBe(0)
expect(paramsFor('orchestration.gateList')).toEqual(
expect.objectContaining({ from: 'term_coord', run: undefined })
)
})
it('inspects a named Run without resolving a caller terminal', async () => {
// Why: read-only inspection must stay reachable from a pane with no bound Run.
getTerminalHandleMock.mockRejectedValue(
new RuntimeClientError('no_active_terminal', 'no active terminal')
)
queueFixtures(
callMock,
okFixture('req_list', {
gates: [{ id: 'gate_1', task_id: 'task_1', question: 'ship?', status: 'pending' }],
count: 1
})
)
await main(['orchestration', 'gate-list', '--run', 'run_adopted', '--json'], '/tmp/repo')
expect(process.exitCode).toBe(0)
expect(getTerminalHandleMock).not.toHaveBeenCalled()
expect(paramsFor('orchestration.gateList')).toEqual(
expect.objectContaining({ run: 'run_adopted', from: undefined })
)
})
it('fails an unbound gate-create with an actionable error and no mutation', async () => {
getTerminalHandleMock.mockRejectedValue(
new RuntimeClientError('no_active_terminal', 'no active terminal')
)
await main(
['orchestration', 'gate-create', '--task', 'task_1', '--question', 'ship?'],
'/tmp/repo'
)
expect(process.exitCode).toBe(1)
const stderr = errorSpy.mock.calls.map((call) => String(call[0])).join('\n')
expect(stderr).toContain('Pass --from <terminal-handle>')
expect(callMock).not.toHaveBeenCalledWith('orchestration.gateCreate', expect.anything())
})
it('reports idempotent recovery when a mutation connection drops', async () => {
process.env.ORCA_TERMINAL_HANDLE = 'term_coord'
callMock
.mockResolvedValueOnce(okFixture('req_show', { terminal: { handle: 'term_coord' } }))
.mockRejectedValueOnce(
new RuntimeClientError(
'runtime_unavailable',
'The Orca runtime closed the connection before responding. Restart Orca and try again. Orchestration mutation request ID: mutation_1.',
{
orchestrationRequestId: 'mutation_1',
originalCommand: [
'orca',
'orchestration',
'gate-create',
'--task',
'task_1',
'--question',
'ship?',
'--json'
],
failedStage: 'dispatch_input',
residualResources: [
{ kind: 'worktree', id: 'repo::child' },
{ kind: 'terminal', id: 'term_worker' }
]
}
)
)
await main(
['orchestration', 'gate-create', '--task', 'task_1', '--question', 'ship?', '--json'],
'/tmp/repo'
)
expect(process.exitCode).toBe(1)
const output = JSON.parse(String(logSpy.mock.calls[0]?.[0])) as {
error: { message: string; data: Record<string, unknown> }
}
expect(output.error.message).toContain('--retry-request mutation_1')
expect(output.error.message).toContain('may already have taken effect')
expect(output.error.message).toContain('Failed stage: dispatch_input')
expect(output.error.message).toMatch(/Residual resources:.*repo::child.*term_worker/)
expect(output.error.message).not.toMatch(/restart Orca/i)
expect(output.error.data).toMatchObject({
orchestrationRequestId: 'mutation_1',
failedStage: 'dispatch_input',
residualResources: expect.arrayContaining([
expect.objectContaining({ kind: 'worktree', id: 'repo::child' }),
expect.objectContaining({ kind: 'terminal', id: 'term_worker' })
])
})
})
})