Files
orca/tests/e2e/orchestration-worker-workspace-resolution.unit.test.ts
T
Neil 3a0e9a27d0 refactor: organize backend runtime by defining owner (#39625)
* refactor(main): organize runtime implementation owners

* test: retire wall-clock CLI title recovery case

* refactor: route new folder mutation to runtime store owner

* test: import folder mutation store contract from its owner
2026-11-07 22:24:34 -08:00

469 lines
17 KiB
TypeScript

import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const electronMocks = vi.hoisted(() => {
const ipcMain = {
on: vi.fn(() => ipcMain),
removeListener: vi.fn(() => ipcMain),
emit: vi.fn(() => true)
}
return {
BrowserWindow: { fromId: vi.fn((): unknown => null) },
webContents: { fromId: vi.fn((): unknown => null) },
ipcMain,
app: { getPath: vi.fn(() => '/tmp'), isPackaged: false }
}
})
vi.mock('electron', () => electronMocks)
const scanLocalRepoWorktreesForResolution = vi.hoisted(() => vi.fn())
vi.mock('../../src/main/runtime/workspaces/catalog/repo-worktree-resolution-scan', () => ({
scanLocalRepoWorktreesForResolution
}))
import { MIXED_FOLDER } from '../../src/shared/folder-workspaces/folder-workspace-host-routing.test-fixture'
import { formatCliError } from '../../src/cli/cli-error'
import { RuntimeRpcFailureError } from '../../src/cli/runtime/types'
import { ORCHESTRATION_CONTRACT_VERSION } from '../../src/shared/protocol-version'
import { RpcDispatcher } from '../../src/main/runtime/rpc/dispatcher'
import { ORCHESTRATION_METHODS } from '../../src/main/runtime/rpc/methods/orchestration/orchestration'
import { OrchestrationDb } from '../../src/main/runtime/orchestration/db/orchestration-db'
import '../../src/main/runtime/orchestration/db/contract-constants'
import '../../src/main/runtime/orchestration/db/messages/mailbox-routing-page'
import '../../src/main/runtime/orchestration/db/dispatch-row-writer'
import '../../src/main/runtime/orchestration/db/attempt-outcome-projection'
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../src/shared/constants'
import type { FolderWorkspace } from '../../src/shared/folder-workspaces/folder-workspace-types'
import type { ProjectGroup } from '../../src/shared/project-group-types'
import type { Repo } from '../../src/shared/repo-types'
import type { WorktreeMeta } from '../../src/shared/worktree/meta-types'
import type { RuntimeStore } from '../../src/main/runtime/construction/runtime-store-contract'
import { OrcaRuntimeService } from '../../src/main/runtime/orca-runtime'
import type { HostWorktreeTarget } from '../../src/shared/worktree/host-qualified-identity'
const REPO_ID = 'repo-1'
const REPO_PATH = '/repo'
const WORKTREE_PATH = '/repo/feature'
const WORKTREE_ID = `${REPO_ID}::${WORKTREE_PATH}`
function makeMeta(displayName: string, hostId?: WorktreeMeta['hostId']): WorktreeMeta {
return {
displayName,
comment: '',
linkedIssue: null,
linkedPR: null,
linkedLinearIssue: null,
linkedGitLabMR: null,
linkedGitLabIssue: null,
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0,
...(hostId ? { hostId } : {})
}
}
function makeStore(
options: {
repos?: Repo[]
meta?: Record<string, WorktreeMeta>
folderWorkspaces?: FolderWorkspace[]
projectGroups?: ProjectGroup[]
} = {}
) {
const repos = options.repos ?? [
{
id: REPO_ID,
path: REPO_PATH,
displayName: 'App',
badgeColor: 'blue',
addedAt: 1
}
]
const meta = options.meta ?? { [WORKTREE_ID]: makeMeta('Feature') }
const store = {
addRepo: vi.fn<RuntimeStore['addRepo']>(),
updateRepo: vi.fn<RuntimeStore['updateRepo']>(),
removeWorktreeMeta: vi.fn<RuntimeStore['removeWorktreeMeta']>(),
getRepo: (id: string) => repos.find((repo) => repo.id === id),
getRepos: () => repos,
getAllWorktreeMeta: () => meta,
getWorktreeMeta: (id: string) => meta[id],
setWorktreeMeta: ({ id }: HostWorktreeTarget, patch: Partial<WorktreeMeta>) => {
meta[id] = { ...(meta[id] ?? makeMeta('')), ...patch }
return meta[id]
},
getAllWorktreeLineage: () => ({}),
getAllWorkspaceLineage: () => ({}),
removeWorktreeLineage: vi.fn(),
removeWorkspaceLineage: vi.fn(),
getGitHubCache: () => ({ pr: {}, issue: {} }),
getSettings: () => ({
workspaceDir: '/tmp/workspaces',
nestWorkspaces: false,
refreshLocalBaseRefOnWorktreeCreate: false,
branchPrefix: 'none' as const,
branchPrefixCustom: ''
}),
getProjects: () => [],
getFolderWorkspaces: () => options.folderWorkspaces ?? [],
getProjectGroups: () => options.projectGroups ?? []
}
return store satisfies RuntimeStore
}
class FolderConsumerRuntime extends OrcaRuntimeService {
browserWorkspace(selector: string) {
return this.resolveBrowserWorkspace(selector)
}
emulatorWorkspace(selector: string) {
return this.resolveEmulatorWorkspaceId(selector)
}
}
describe('orchestration worker workspace resolution', () => {
const tempPaths: string[] = []
beforeEach(() => {
scanLocalRepoWorktreesForResolution.mockReset().mockResolvedValue({
ok: true,
worktrees: [
{
path: WORKTREE_PATH,
head: 'abc',
branch: 'feature',
isBare: false,
isMainWorktree: false
}
]
})
})
afterEach(async () => {
await Promise.all(tempPaths.splice(0).map((path) => rm(path, { recursive: true, force: true })))
})
it.each([
['full id', `id:${WORKTREE_ID}`],
['path', `path:${WORKTREE_PATH}`],
['name', 'name:Feature']
])('resolves a local worktree by %s with one catalog scan', async (_label, selector) => {
const runtime = new OrcaRuntimeService(makeStore() as never)
await expect(runtime.showManagedTerminalWorkspace(selector)).resolves.toMatchObject({
id: WORKTREE_ID,
path: WORKTREE_PATH
})
expect(scanLocalRepoWorktreesForResolution).toHaveBeenCalledOnce()
})
it('preserves a disconnected SSH worktree with unknown legacy ownership', async () => {
const remoteRepo = {
id: REPO_ID,
path: REPO_PATH,
displayName: 'Remote app',
badgeColor: 'blue',
addedAt: 1,
connectionId: 'ssh-1'
} satisfies Repo
const runtime = new OrcaRuntimeService(
makeStore({
repos: [remoteRepo],
meta: { [WORKTREE_ID]: makeMeta('Remote feature') }
}) as never
)
await expect(runtime.showManagedTerminalWorkspace(`id:${WORKTREE_ID}`)).resolves.toMatchObject({
id: WORKTREE_ID,
hostId: 'ssh:ssh-1'
})
})
it('never infers a disconnected owner for unstamped same-id host twins', async () => {
const repo: Repo = {
id: REPO_ID,
path: REPO_PATH,
displayName: 'Remote',
badgeColor: 'blue',
addedAt: 1,
connectionId: 'ssh-1'
}
const runtime = new OrcaRuntimeService(
makeStore({
repos: [repo, { ...repo, connectionId: 'ssh-2' }],
meta: { [WORKTREE_ID]: makeMeta('Remote feature') }
})
)
await expect(runtime.showManagedTerminalWorkspace(`id:${WORKTREE_ID}`)).rejects.toThrow(
'selector_not_found'
)
})
it('does not fall back from the floating terminal sentinel to another workspace', async () => {
const runtime = new OrcaRuntimeService(makeStore() as never)
await expect(
runtime.showManagedTerminalWorkspace(`id:${FLOATING_TERMINAL_WORKTREE_ID}`)
).rejects.toThrow('selector_not_found')
expect(scanLocalRepoWorktreesForResolution).not.toHaveBeenCalled()
})
it('rejects an ambiguous worktree name', async () => {
const secondPath = '/repo/other'
scanLocalRepoWorktreesForResolution.mockResolvedValue({
ok: true,
worktrees: [
{ path: WORKTREE_PATH, head: 'a', branch: 'one', isBare: false, isMainWorktree: false },
{ path: secondPath, head: 'b', branch: 'two', isBare: false, isMainWorktree: false }
]
})
const runtime = new OrcaRuntimeService(
makeStore({
meta: {
[WORKTREE_ID]: makeMeta('Duplicate'),
[`${REPO_ID}::${secondPath}`]: makeMeta('Duplicate')
}
}) as never
)
await expect(runtime.showManagedTerminalWorkspace('name:Duplicate')).rejects.toThrow(
'selector_ambiguous'
)
})
it('rejects the same worktree path on different execution hosts', async () => {
const remoteRepo = {
id: 'repo-remote',
path: '/remote-repo',
displayName: 'Remote app',
badgeColor: 'blue',
addedAt: 1,
connectionId: 'ssh-1'
} satisfies Repo
const runtime = new OrcaRuntimeService(
makeStore({
repos: [makeStore().getRepos()[0], remoteRepo],
meta: {
[WORKTREE_ID]: makeMeta('Local feature'),
[`${remoteRepo.id}::${WORKTREE_PATH}`]: makeMeta('Remote feature')
}
}) as never
)
await expect(runtime.showManagedTerminalWorkspace(`path:${WORKTREE_PATH}`)).rejects.toThrow(
'selector_ambiguous'
)
})
it('resolves a local folder workspace and refuses an SSH one without a Git catalog scan', async () => {
const localPath = await mkdtemp(join(tmpdir(), 'orca-worker-local-folder-'))
tempPaths.push(localPath)
const group = { id: 'group-1', name: 'Group', parentPath: localPath } as ProjectGroup
const localFolder = {
id: 'local-folder',
projectGroupId: group.id,
name: 'Local folder',
folderPath: localPath
} as FolderWorkspace
const remoteFolder = {
...localFolder,
id: 'remote-folder',
name: 'Remote folder',
folderPath: '/srv/app',
connectionId: 'ssh-folder'
}
const runtime = new OrcaRuntimeService(
makeStore({
folderWorkspaces: [localFolder, remoteFolder],
projectGroups: [group]
}) as never
)
await expect(
runtime.showManagedTerminalWorkspace('folder:local-folder')
).resolves.toMatchObject({
id: 'folder:local-folder',
path: localPath,
hostId: 'local'
})
// No SSH filesystem provider is installed, so the remote folder is unavailable.
await expect(runtime.showManagedTerminalWorkspace('id:folder:remote-folder')).rejects.toThrow(
'folder_workspace_path_unavailable:/srv/app'
)
expect(scanLocalRepoWorktreesForResolution).not.toHaveBeenCalled()
})
it.each([false, true])(
'preserves worker selector recovery before dispatch or launch (reversed=%s)',
async (reverse) => {
const folder = {
...MIXED_FOLDER,
id: 'dup',
folderPath: '/workspace'
}
let folders: FolderWorkspace[] = [
{ ...folder, executionHostId: 'local' },
{ ...folder, executionHostId: 'ssh:prod' }
]
if (reverse) {
folders.reverse()
}
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the real selector reads the catalog methods supplied by this fixture.
const runtime = new OrcaRuntimeService({
...makeStore(),
getFolderWorkspaces: () => folders
} as never)
const db = new OrchestrationDb(':memory:')
runtime.setOrchestrationDb(db)
const paneKey = 'tab_coord:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
const run = db.createRun({
objective: 'folder placement',
coordinatorHandle: 'term_coord',
coordinatorPaneKey: paneKey
})
vi.spyOn(runtime, 'getTerminalPaneKey').mockReturnValue(paneKey)
vi.spyOn(runtime, 'getLiveTerminalPaneKey').mockReturnValue(paneKey)
vi.spyOn(runtime, 'getTerminalProcessIncarnation').mockReturnValue(
'runtime_test:term_coord:1'
)
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: caller placement reads only this coordinator handle and workspace identity.
vi.spyOn(runtime, 'showTerminal').mockResolvedValue({
handle: 'term_coord',
worktreeId: WORKTREE_ID
} as never)
vi.spyOn(runtime, 'validateOrchestrationAgentLauncher').mockImplementation(() => {})
const createDispatch = vi.spyOn(db, 'createStartingWorkerDispatch')
const createTerminal = vi
.spyOn(runtime, 'createTerminal')
.mockRejectedValue(new Error('unexpected launch'))
const sendPrompt = vi.spyOn(runtime, 'sendTerminalAgentPrompt')
const dispatcher = new RpcDispatcher({ runtime, methods: ORCHESTRATION_METHODS })
try {
for (const selector of ['folder:dup', 'id:folder:dup']) {
for (const missing of [false, true]) {
const task = db.createTask({ spec: 'existing folder', runId: run.id })
const response = await dispatcher.dispatch({
id: `${selector}:${missing}`,
authToken: 'fixture-token',
orchestrationContractVersion: ORCHESTRATION_CONTRACT_VERSION,
orchestrationRequestId: `${selector}:${missing}`,
method: 'orchestration.workerStart',
params: { from: 'term_coord', task: task.id, worktree: selector, agent: 'codex' }
})
expect(response.ok).toBe(false)
if (response.ok) {
throw new Error('placement unexpectedly succeeded')
}
expect(response.error.code).toBe(missing ? 'selector_not_found' : 'selector_ambiguous')
expect(
formatCliError(new RuntimeRpcFailureError(response), { worktreeSelector: selector })
).toContain('Next step:')
expect(db.getDispatchContext(task.id)).toBeUndefined()
folders = []
}
folders = reverse
? [
{ ...folder, executionHostId: 'ssh:prod' },
{ ...folder, executionHostId: 'local' }
]
: [
{ ...folder, executionHostId: 'local' },
{ ...folder, executionHostId: 'ssh:prod' }
]
}
expect(createDispatch).not.toHaveBeenCalled()
expect(createTerminal).not.toHaveBeenCalled()
expect(sendPrompt).not.toHaveBeenCalled()
} finally {
db.close()
}
}
)
it('rejects a folder workspace whose execution host is ambiguous', async () => {
const folderPath = '/workspace'
const group = { id: 'group-1', name: 'Group', parentPath: folderPath } as ProjectGroup
const folder = {
id: 'folder-1',
projectGroupId: group.id,
name: 'Ambiguous folder',
folderPath
} as FolderWorkspace
const repos = [
{ id: 'local', path: '/workspace/local', projectGroupId: group.id },
{ id: 'remote', path: '/workspace/remote', projectGroupId: group.id, connectionId: 'ssh-1' }
].map((repo) => ({
displayName: repo.id,
badgeColor: 'blue',
addedAt: 1,
...repo
})) as Repo[]
const runtime = new OrcaRuntimeService(
makeStore({ repos, folderWorkspaces: [folder], projectGroups: [group] }) as never
)
await expect(runtime.showManagedTerminalWorkspace('folder:folder-1')).rejects.toThrow(
'selector_ambiguous'
)
expect(scanLocalRepoWorktreesForResolution).not.toHaveBeenCalled()
})
it.each([false, true])(
'keeps physical folder refusal separate from emulator identity (reversed=%s)',
async (reversed) => {
const folderPath = await mkdtemp(join(tmpdir(), 'orca-worker-folder-consumers-'))
tempPaths.push(folderPath)
const group = { id: 'group-1', name: 'Group', parentPath: folderPath } as ProjectGroup
const folders: FolderWorkspace[] = [
{
id: 'folder-1',
projectGroupId: group.id,
name: 'Folder',
folderPath,
linkedTask: null,
comment: '',
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 1,
createdAt: 1,
updatedAt: 1
}
]
const repos: Repo[] = [
{ id: 'local', executionHostId: 'local' as const },
{ id: 'remote', executionHostId: 'ssh:remote' as const }
].map((repo) => ({
...repo,
path: folderPath,
projectGroupId: group.id,
displayName: repo.id,
badgeColor: 'blue',
addedAt: 1
}))
const runtime = new FolderConsumerRuntime(
makeStore({
repos: reversed ? repos.toReversed() : repos,
folderWorkspaces: folders
})
)
const selector = 'id:folder:folder-1'
await expect(runtime.resolveRuntimeFileTarget(selector)).rejects.toThrow('selector_ambiguous')
await expect(runtime.browserWorkspace(selector)).rejects.toThrow('selector_ambiguous')
await expect(runtime.emulatorWorkspace(selector)).resolves.toBe('folder:folder-1')
folders.push({ ...folders[0], executionHostId: 'ssh:remote' })
await expect(runtime.emulatorWorkspace(selector)).rejects.toThrow('selector_ambiguous')
folders.pop()
folders.splice(0)
await expect(runtime.resolveRuntimeFileTarget(selector)).rejects.toThrow('selector_not_found')
await expect(runtime.browserWorkspace(selector)).rejects.toThrow('selector_not_found')
await expect(runtime.emulatorWorkspace(selector)).rejects.toThrow('selector_not_found')
expect(scanLocalRepoWorktreesForResolution).not.toHaveBeenCalled()
}
)
})