mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
* fix(runtime): let a scoped worktree listing report the host it could not cover
`orca worktree list --repo <id>` passed `[]` as `knownHostIds`, so a scoped
listing could never report a gap — for any host kind, reachable or not. With zero
matched rows both scope lists are empty by construction, and the answer is
`{hostIds: [], omittedHostIds: []}`: byte-identical to a repo that genuinely has
no worktrees. docs/reference/ssh-execution-boundary.md forbids a listing from
implying exactly that.
Measured on one runtime with one refusing SSH host, in the same second:
unscoped totalCount 0 omittedHostIds ["local","ssh:<target>"] (+ --host selectors)
scoped totalCount 0 hostScope {"hostIds":[],"omittedHostIds":[]}
The same runtime reports nine omitted hosts unscoped and zero scoped against a
live profile, so this is not a subtle inconsistency: it is one runtime giving two
contradictory answers about its own coverage.
A scoped listing now names the one host the caller asked about. That costs
nothing when rows come back — the host lands in `covered`, so it is never
reported omitted — and is the whole answer when they do not. Hosts the caller
scoped out are still never named, which a test pins, because naming them all is
the obvious over-correction.
* test(runtime): pin the scoped host derivation for local and executionHostId repos
Review flagged that the host-scope cases only covered a connectionId repo.
getRepoExecutionHostId reads two spellings, and a scoped listing naming the
wrong host would be worse than naming none, so both are pinned. Both fail with
the fix reverted.
224 lines
7.4 KiB
TypeScript
224 lines
7.4 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import type { Repo } from '../../shared/repo-types'
|
|
import type { WorktreeMeta } from '../../shared/worktree/meta-types'
|
|
import { RuntimeManagedWorktreeQueries } from './runtime-managed-worktree-queries'
|
|
import type { RuntimeStore } from './runtime-store-contract'
|
|
|
|
const settings = {
|
|
workspaceDir: '/worktrees',
|
|
nestWorkspaces: true,
|
|
refreshLocalBaseRefOnWorktreeCreate: false,
|
|
branchPrefix: 'none',
|
|
branchPrefixCustom: ''
|
|
}
|
|
|
|
function folderRepo(overrides: Partial<Repo> = {}): Repo {
|
|
return {
|
|
id: 'repo-1',
|
|
path: '/workspace/app',
|
|
displayName: 'Local app',
|
|
badgeColor: '#000000',
|
|
addedAt: 1,
|
|
kind: 'folder',
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
function metadata(overrides: Partial<WorktreeMeta> = {}): WorktreeMeta {
|
|
return {
|
|
displayName: '',
|
|
comment: '',
|
|
linkedIssue: null,
|
|
linkedPR: null,
|
|
linkedLinearIssue: null,
|
|
isArchived: false,
|
|
isUnread: false,
|
|
isPinned: false,
|
|
sortOrder: 0,
|
|
lastActivityAt: 0,
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
function queries(
|
|
store: RuntimeStore,
|
|
overrides: Partial<ConstructorParameters<typeof RuntimeManagedWorktreeQueries>[0]> = {}
|
|
): RuntimeManagedWorktreeQueries {
|
|
return new RuntimeManagedWorktreeQueries({
|
|
getStore: () => store,
|
|
listResolved: async () => [],
|
|
resolveRepo: async () => store.getRepos()[0]!,
|
|
selectRepos: () => store.getRepos(),
|
|
scanRepo: async () => ({ ok: true, worktrees: [] }),
|
|
listKnownHostIds: () => [],
|
|
...overrides
|
|
})
|
|
}
|
|
|
|
describe('RuntimeManagedWorktreeQueries.listDetected', () => {
|
|
it("does not project another host's folder metadata", async () => {
|
|
const local = folderRepo()
|
|
const remote = folderRepo({ connectionId: 'build-box', displayName: 'Remote app' })
|
|
const rootId = `${local.id}::${local.path}`
|
|
const foreignMeta = metadata({ displayName: 'Wrong host', hostId: 'ssh:build-box' })
|
|
const store = {
|
|
getRepos: () => [local, remote],
|
|
getRepo: () => local,
|
|
getAllWorktreeMeta: () => ({ [rootId]: foreignMeta }),
|
|
getWorktreeMeta: () => foreignMeta,
|
|
setWorktreeMeta: vi.fn(),
|
|
getAllWorktreeLineage: () => ({}),
|
|
getSettings: () => settings
|
|
} as unknown as RuntimeStore
|
|
|
|
const result = await queries(store).listDetected(local)
|
|
|
|
expect(result.worktrees).toHaveLength(1)
|
|
expect(result.worktrees[0]).toMatchObject({
|
|
id: rootId,
|
|
hostId: 'local',
|
|
displayName: 'Local app'
|
|
})
|
|
})
|
|
|
|
it('omits host-owned source defaults for clients that do not support them', async () => {
|
|
const repo = folderRepo({ path: '/source/app' })
|
|
const store = {
|
|
getRepos: () => [repo],
|
|
getRepo: () => repo,
|
|
getAllWorktreeMeta: () => ({}),
|
|
getWorktreeMeta: () => undefined,
|
|
setWorktreeMeta: vi.fn((_id, updates) => metadata(updates)),
|
|
getAllWorktreeLineage: () => ({}),
|
|
getSettings: () => ({
|
|
...settings,
|
|
worktreeVisibilityDefaults: {
|
|
external: 'show' as const,
|
|
customSources: [{ id: 'host-source', rootPath: '/source' }],
|
|
sourcePreferences: { custom: { 'host-source': 'show' as const } }
|
|
}
|
|
})
|
|
} as unknown as RuntimeStore
|
|
|
|
const current = await queries(store).listDetected(repo, true)
|
|
const legacy = await queries(store).listDetected(repo, false)
|
|
|
|
expect(current.worktrees[0]).toMatchObject({
|
|
visibilitySource: { kind: 'custom', id: 'host-source' }
|
|
})
|
|
expect(legacy.worktrees[0]).not.toHaveProperty('visibilitySource')
|
|
})
|
|
})
|
|
|
|
describe('RuntimeManagedWorktreeQueries.list host scope', () => {
|
|
// Measured on hardware before this fix, same runtime and same refusing SSH host in the same
|
|
// second: the UNSCOPED listing reported `omittedHostIds: ["local","ssh:ssh-scope-refused"]` with
|
|
// `--host` selectors, while the SCOPED listing reported `{"hostIds":[],"omittedHostIds":[]}`.
|
|
// A listing that covered nothing, reporting no gaps, is indistinguishable from a repo that
|
|
// genuinely has no worktrees -- the thing docs/reference/ssh-execution-boundary.md forbids.
|
|
function sshStore(): RuntimeStore {
|
|
const repo = folderRepo({
|
|
id: 'repo-ssh',
|
|
kind: 'git',
|
|
connectionId: 'conn-1',
|
|
path: '/home/dev/app'
|
|
})
|
|
return {
|
|
getRepos: () => [repo],
|
|
getRepo: () => repo,
|
|
getAllWorktreeMeta: () => ({}),
|
|
getWorktreeMeta: () => undefined,
|
|
setWorktreeMeta: vi.fn(),
|
|
getAllWorktreeLineage: () => ({}),
|
|
getSettings: () => settings
|
|
} as unknown as RuntimeStore
|
|
}
|
|
|
|
it('names the scoped repo host as omitted when the listing covered nothing', async () => {
|
|
const result = await queries(sshStore()).list('repo-ssh', 50)
|
|
|
|
expect(result.totalCount).toBe(0)
|
|
expect(result.hostScope).toEqual({
|
|
hostIds: [],
|
|
omittedHostIds: ['ssh:conn-1']
|
|
})
|
|
})
|
|
|
|
it('does not report the scoped host as omitted once it contributes rows', async () => {
|
|
const store = sshStore()
|
|
const result = await queries(store, {
|
|
listResolved: async () =>
|
|
[
|
|
{
|
|
id: 'repo-ssh::/home/dev/app',
|
|
repoId: 'repo-ssh',
|
|
path: '/home/dev/app',
|
|
hostId: 'ssh:conn-1'
|
|
}
|
|
] as never
|
|
}).list('repo-ssh', 50)
|
|
|
|
expect(result.hostScope?.hostIds).toEqual(['ssh:conn-1'])
|
|
expect(result.hostScope?.omittedHostIds).toEqual([])
|
|
})
|
|
|
|
// The caller scoped the listing, so the hosts they excluded must not come back as gaps.
|
|
it('never names a host the caller scoped out', async () => {
|
|
const scoped = await queries(sshStore(), {
|
|
listKnownHostIds: () => ['local', 'ssh:other', 'runtime:elsewhere'] as never
|
|
}).list('repo-ssh', 50)
|
|
|
|
expect(scoped.hostScope?.omittedHostIds).toEqual(['ssh:conn-1'])
|
|
})
|
|
|
|
// `getRepoExecutionHostId` derives the host from two spellings, and a scoped listing that named
|
|
// the wrong one would be worse than naming none. These pin both.
|
|
it('names the local host for a scoped local repo', async () => {
|
|
const repo = folderRepo({ id: 'repo-local', kind: 'git', path: '/workspace/local' })
|
|
const store = {
|
|
getRepos: () => [repo],
|
|
getRepo: () => repo,
|
|
getAllWorktreeMeta: () => ({}),
|
|
getWorktreeMeta: () => undefined,
|
|
setWorktreeMeta: vi.fn(),
|
|
getAllWorktreeLineage: () => ({}),
|
|
getSettings: () => settings
|
|
} as unknown as RuntimeStore
|
|
|
|
const result = await queries(store).list('repo-local', 50)
|
|
|
|
expect(result.hostScope?.omittedHostIds).toEqual(['local'])
|
|
})
|
|
|
|
it('prefers executionHostId over connectionId for the scoped host', async () => {
|
|
const repo = folderRepo({
|
|
id: 'repo-runtime',
|
|
kind: 'git',
|
|
connectionId: 'conn-legacy',
|
|
executionHostId: 'runtime:env-1',
|
|
path: '/workspace/runtime'
|
|
})
|
|
const store = {
|
|
getRepos: () => [repo],
|
|
getRepo: () => repo,
|
|
getAllWorktreeMeta: () => ({}),
|
|
getWorktreeMeta: () => undefined,
|
|
setWorktreeMeta: vi.fn(),
|
|
getAllWorktreeLineage: () => ({}),
|
|
getSettings: () => settings
|
|
} as unknown as RuntimeStore
|
|
|
|
const result = await queries(store).list('repo-runtime', 50)
|
|
|
|
expect(result.hostScope?.omittedHostIds).toEqual(['runtime:env-1'])
|
|
})
|
|
|
|
it('still reports every configured host when the listing is unscoped', async () => {
|
|
const unscoped = await queries(sshStore(), {
|
|
listKnownHostIds: () => ['local', 'ssh:conn-1'] as never
|
|
}).list(undefined, 50)
|
|
|
|
expect(unscoped.hostScope?.omittedHostIds).toEqual(['local', 'ssh:conn-1'])
|
|
})
|
|
})
|