Files
orca/src/main/provisioned-root-ssh-adoption.test.ts
Jinwoo Hong 8f15f217a2 Preserve user-set workspace names across branch changes (#17448)
* fix(worktrees): preserve user workspace names across branch changes

* test(worktrees): cover pinned rename metadata

* fix(workspaces): address display-name review edge cases

* fix(workspaces): keep automatic names fresh across refreshes

* fix(workspaces): preserve legacy CLI labels

* fix(workspaces): preserve display-name provenance across hosts

* fix(workspaces): honor legacy display-name provenance

* fix(workspaces): fence display-name refresh races

* fix(workspaces): accept peer renames from provenance-less hosts

The old-host preserve fence kept a pinned local label on every refresh,
which also suppressed a legitimate rename another client persisted
through the same host until app restart. Narrow it to labels the host
re-derived itself (branch short name, or path basename when detached);
any other changed label in a mode-less response is explicit meta a peer
wrote there. Stale prior-label responses stay covered by the downstream
staleness fence, in-flight writes by the pending fence.

* refactor(workspaces): unify display-name pin derivation

Three call sites (renderer optimistic update, local IPC updateMeta
handler, remote worktree.set handler) each restated the same formula;
a future edit to one would silently skew provenance between paths.
2026-08-31 19:08:05 -04:00

441 lines
13 KiB
TypeScript

import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { Store } from './persistence'
import type { Repo } from '../shared/repo-types'
import type { AdoptProvisionedRootArgs } from '../shared/worktree/create-types'
import type { WorktreeMeta } from '../shared/worktree/meta-types'
import type { GitWorktreeInfo } from '../shared/worktree/types'
import {
listEphemeralVmRuntimes,
upsertEphemeralVmRuntime
} from '../shared/ephemeral-vm-runtime-store'
import { registerSshGitProvider, unregisterSshGitProvider } from './providers/ssh-git-dispatch'
import {
resetSshProviderAuthorities,
rotateSshProviderAuthority
} from './ssh/ssh-provider-authority'
import { adoptProvisionedRootSshCheckout } from './provisioned-root-ssh-adoption'
const connectionId = 'runtime-ssh-test'
const projectRoot = '/workspace/orca'
describe('adoptProvisionedRootSshCheckout', () => {
let userDataPath: string
beforeEach(() => {
userDataPath = mkdtempSync(join(tmpdir(), 'orca-provisioned-root-'))
resetSshProviderAuthorities()
})
afterEach(() => {
unregisterSshGitProvider(connectionId)
resetSshProviderAuthorities()
rmSync(userDataPath, { recursive: true, force: true })
})
it('adopts the exact primary checkout, persists host metadata, and attaches the runtime', async () => {
seedRuntime(userDataPath, projectRoot)
registerSshGitProvider(connectionId, {
listWorktrees: vi.fn().mockResolvedValue([gitWorktree(projectRoot)]),
exec: sparseCheckoutProbe(false)
} as never)
const { store, setWorktreeMeta } = makeStore()
const result = await adoptProvisionedRootSshCheckout({
userDataPath,
request: request(projectRoot),
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
expect(result.worktree).toMatchObject({
id: `repo-1::${projectRoot}`,
path: projectRoot,
isMainWorktree: true,
hostId: `ssh:${connectionId}`,
ephemeralVmCheckoutMode: 'provisioned-root',
linkedGitLabIssue: 17
})
expect(setWorktreeMeta).toHaveBeenCalledWith(
`repo-1::${projectRoot}`,
expect.objectContaining({
hostId: `ssh:${connectionId}`,
ephemeralVmCheckoutMode: 'provisioned-root',
linkedGitLabIssue: 17
})
)
expect(listEphemeralVmRuntimes(userDataPath)[0]).toMatchObject({
workspaceId: `repo-1::${projectRoot}`,
status: 'running'
})
})
it('pins an explicit label even when it equals the adopted branch', async () => {
seedRuntime(userDataPath, projectRoot)
registerSshGitProvider(connectionId, {
listWorktrees: vi.fn().mockResolvedValue([gitWorktree(projectRoot)]),
exec: sparseCheckoutProbe(false)
} as never)
const { store, setWorktreeMeta } = makeStore()
const result = await adoptProvisionedRootSshCheckout({
userDataPath,
request: {
...request(projectRoot),
displayName: 'fix-sandbox',
displayNameKind: 'user'
},
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
expect(result.worktree.displayName).toBe('fix-sandbox')
expect(setWorktreeMeta).toHaveBeenCalledWith(
`repo-1::${projectRoot}`,
expect.objectContaining({ displayName: 'fix-sandbox', displayNameIsPinned: true })
)
})
it('rejects a recipe checkout on a branch Orca did not request', async () => {
seedRuntime(userDataPath, projectRoot)
registerSshGitProvider(connectionId, {
listWorktrees: vi
.fn()
.mockResolvedValue([gitWorktree(projectRoot, { branch: 'refs/heads/wrong-branch' })]),
exec: sparseCheckoutProbe(false)
} as never)
const { store, setWorktreeMeta } = makeStore()
await expect(
adoptProvisionedRootSshCheckout({
userDataPath,
request: request(projectRoot),
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
).rejects.toThrow('requested branch')
expect(setWorktreeMeta).not.toHaveBeenCalled()
})
it('rejects a recipe checkout that did not start from the requested ref', async () => {
seedRuntime(userDataPath, projectRoot)
const exec = sparseCheckoutProbe(false)
registerSshGitProvider(connectionId, {
listWorktrees: vi.fn().mockResolvedValue([gitWorktree(projectRoot, { head: 'wrong-head' })]),
exec
} as never)
const { store, setWorktreeMeta } = makeStore()
await expect(
adoptProvisionedRootSshCheckout({
userDataPath,
request: {
...request(projectRoot),
baseBranch: 'v1.2.3',
expectedRefHead: 'expected-head'
},
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
).rejects.toThrow('requested ref')
expect(exec).toHaveBeenCalledOnce()
expect(setWorktreeMeta).not.toHaveBeenCalled()
})
it('accepts an attached requested branch created from a tag or commit ref', async () => {
seedRuntime(userDataPath, projectRoot)
const exec = sparseCheckoutProbe(false)
registerSshGitProvider(connectionId, {
listWorktrees: vi.fn().mockResolvedValue([gitWorktree(projectRoot)]),
exec
} as never)
const { store } = makeStore()
await expect(
adoptProvisionedRootSshCheckout({
userDataPath,
request: {
...request(projectRoot),
baseBranch: 'v1.2.3',
expectedRefHead: 'abc123'
},
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
).resolves.toMatchObject({ worktree: { branch: 'refs/heads/fix-sandbox' } })
})
it('rejects a requested ref without its source-host commit identity', async () => {
seedRuntime(userDataPath, projectRoot)
registerSshGitProvider(connectionId, {
listWorktrees: vi.fn().mockResolvedValue([gitWorktree(projectRoot)]),
exec: sparseCheckoutProbe(false)
} as never)
const { store, setWorktreeMeta } = makeStore()
await expect(
adoptProvisionedRootSshCheckout({
userDataPath,
request: { ...request(projectRoot), baseBranch: 'missing-ref' },
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
).rejects.toThrow('ref identity is missing')
expect(setWorktreeMeta).not.toHaveBeenCalled()
})
it('uses an explicit branch override as the requested local branch', async () => {
seedRuntime(userDataPath, projectRoot)
registerSshGitProvider(connectionId, {
listWorktrees: vi
.fn()
.mockResolvedValue([gitWorktree(projectRoot, { branch: 'refs/heads/feature/review' })]),
exec: sparseCheckoutProbe(false)
} as never)
const { store } = makeStore()
await expect(
adoptProvisionedRootSshCheckout({
userDataPath,
request: { ...request(projectRoot), branchNameOverride: 'feature/review' },
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
).resolves.toMatchObject({ worktree: { branch: 'refs/heads/feature/review' } })
})
it('rejects a linked worktree and sparse checkout', async () => {
seedRuntime(userDataPath, projectRoot)
const listWorktrees = vi
.fn()
.mockResolvedValue([gitWorktree(projectRoot, { isMainWorktree: false })])
registerSshGitProvider(connectionId, {
listWorktrees,
exec: sparseCheckoutProbe(false)
} as never)
const { store, setWorktreeMeta } = makeStore()
await expect(
adoptProvisionedRootSshCheckout({
userDataPath,
request: request(projectRoot),
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
).rejects.toThrow('must be the repository primary checkout')
await expect(
adoptProvisionedRootSshCheckout({
userDataPath,
request: { ...request(projectRoot), sparseCheckout: { directories: ['src'] } },
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
).rejects.toThrow('do not support sparse checkout')
expect(setWorktreeMeta).not.toHaveBeenCalled()
})
it('rejects path and runtime target mismatches', async () => {
seedRuntime(userDataPath, projectRoot)
registerSshGitProvider(connectionId, {
listWorktrees: vi.fn().mockResolvedValue([gitWorktree(projectRoot)]),
exec: sparseCheckoutProbe(false)
} as never)
const { store } = makeStore()
await expect(
adoptProvisionedRootSshCheckout({
userDataPath,
request: request('/workspace/other'),
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
).rejects.toThrow('does not match')
await expect(
adoptProvisionedRootSshCheckout({
userDataPath,
request: { ...request(projectRoot), executionHostId: 'ssh:runtime-ssh-other' },
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
).rejects.toThrow('host does not match')
})
it('rejects provider rotation during verification', async () => {
seedRuntime(userDataPath, projectRoot)
let resolveList: (value: ReturnType<typeof gitWorktree>[]) => void = () => undefined
const listWorktrees = vi.fn(
() =>
new Promise<ReturnType<typeof gitWorktree>[]>((resolve) => {
resolveList = resolve
})
)
registerSshGitProvider(connectionId, {
listWorktrees,
exec: sparseCheckoutProbe(false)
} as never)
const { store } = makeStore()
const adoption = adoptProvisionedRootSshCheckout({
userDataPath,
request: request(projectRoot),
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
await vi.waitFor(() => expect(listWorktrees).toHaveBeenCalledOnce())
rotateSshProviderAuthority(connectionId)
resolveList([gitWorktree(projectRoot)])
await expect(adoption).rejects.toThrow('changed during checkout verification')
})
it('compares Windows checkout roots using runtime path semantics', async () => {
const windowsRoot = 'C:\\Workspace\\Orca'
seedRuntime(userDataPath, windowsRoot)
registerSshGitProvider(connectionId, {
listWorktrees: vi.fn().mockResolvedValue([gitWorktree('c:/workspace/orca/')]),
exec: sparseCheckoutProbe(false)
} as never)
const { store } = makeStore()
const result = await adoptProvisionedRootSshCheckout({
userDataPath,
request: request('C:/WORKSPACE/ORCA'),
repo: repo('c:\\workspace\\orca'),
store,
isRepoCurrent: () => true
})
expect(result.worktree.path).toBe('c:/workspace/orca/')
})
it('rejects sparse checkout enabled in the remote Git config', async () => {
seedRuntime(userDataPath, projectRoot)
const exec = sparseCheckoutProbe(true)
registerSshGitProvider(connectionId, {
listWorktrees: vi.fn().mockResolvedValue([gitWorktree(projectRoot)]),
exec
} as never)
const { store, setWorktreeMeta } = makeStore()
await expect(
adoptProvisionedRootSshCheckout({
userDataPath,
request: request(projectRoot),
repo: repo(projectRoot),
store,
isRepoCurrent: () => true
})
).rejects.toThrow('cannot adopt a sparse checkout')
expect(exec).toHaveBeenCalledWith(
['config', '--bool', '--get', '--default=false', 'core.sparseCheckout'],
projectRoot
)
expect(setWorktreeMeta).not.toHaveBeenCalled()
})
})
function seedRuntime(userDataPath: string, root: string): void {
upsertEphemeralVmRuntime(userDataPath, {
id: 'runtime-1',
recipeId: 'sandbox',
recipe: {
id: 'sandbox',
name: 'Sandbox',
create: 'sandbox create',
checkoutMode: 'provisioned-root'
},
connectionMode: 'ssh',
sshTargetId: connectionId,
status: 'running',
cleanupStatus: 'not_started',
createdAt: 1,
updatedAt: 1,
recipeResult: {
schemaVersion: 2,
checkoutMode: 'provisioned-root',
connection: {
type: 'ssh',
target: { label: 'Sandbox', host: '127.0.0.1', port: 22, username: 'orca' },
projectRoot: root
}
}
})
}
function repo(path: string): Repo {
return {
id: 'repo-1',
path,
displayName: 'orca',
badgeColor: '#000000',
addedAt: 1,
connectionId,
executionHostId: `ssh:${connectionId}`
}
}
function request(expectedPath: string): AdoptProvisionedRootArgs {
return {
repoId: 'repo-1',
name: 'fix-sandbox',
runtimeId: 'runtime-1',
executionHostId: `ssh:${connectionId}`,
expectedPath,
linkedGitLabIssue: 17
}
}
function gitWorktree(path: string, overrides: Partial<GitWorktreeInfo> = {}): GitWorktreeInfo {
return {
path,
head: 'abc123',
branch: 'refs/heads/fix-sandbox',
isBare: false,
isMainWorktree: true,
...overrides
}
}
function sparseCheckoutProbe(enabled: boolean): ReturnType<typeof vi.fn> {
return vi.fn().mockResolvedValue({ stdout: `${enabled}\n`, stderr: '' })
}
function makeStore(): {
store: Store
setWorktreeMeta: ReturnType<typeof vi.fn>
} {
const setWorktreeMeta = vi.fn((_: string, updates: Partial<WorktreeMeta>) => ({
displayName: '',
comment: '',
linkedIssue: null,
linkedPR: null,
linkedLinearIssue: null,
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0,
...updates
}))
return {
store: {
getSettings: () => ({ nestWorkspaces: false, workspaceDir: '.orca/worktrees' }),
setWorktreeMeta
} as unknown as Store,
setWorktreeMeta
}
}