refactor(ipc): remove unreachable runtime-environment provisioning and control-retry IPC (#32960)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
OrcaWin
2026-10-23 17:54:11 -07:00
committed by GitHub
co-authored by OrcaWin Claude
parent f5f7f172a8
commit 861299e6cc
23 changed files with 4 additions and 593 deletions
@@ -3,8 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
handle: vi.fn(),
deploy: vi.fn(),
status: vi.fn(),
registerProvisioning: vi.fn()
status: vi.fn()
}))
vi.mock('electron', () => ({ ipcMain: { handle: mocks.handle } }))
@@ -14,9 +13,6 @@ vi.mock('../../managed-server/lifecycle/orcad-runtime-deployment', () => ({
vi.mock('../../managed-server/lifecycle/orcad-runtime-status', () => ({
getManagedOrcadRuntimeStatus: mocks.status
}))
vi.mock('./orcad-ssh-provisioning-handlers', () => ({
registerOrcadSshProvisioningHandlers: mocks.registerProvisioning
}))
const { registerOrcadRuntimeLifecycleHandlers } = await import('./orcad-runtime-lifecycle-handlers')
@@ -34,12 +30,11 @@ describe('managed orcad lifecycle IPC', () => {
registerOrcadRuntimeLifecycleHandlers({ getUserDataPath: () => '/profile' })
})
it('registers only deploy, status and provisioning; maintenance and stop are not exposed', () => {
it('registers only deploy and status; maintenance and stop are not exposed', () => {
expect(mocks.handle.mock.calls.map(([channel]) => channel)).toEqual([
'runtimeEnvironments:deployOrcad',
'runtimeEnvironments:getOrcadStatus'
])
expect(mocks.registerProvisioning).toHaveBeenCalledOnce()
})
it('trims deploy input and treats only a literal true as force', async () => {
@@ -2,12 +2,10 @@ import { ipcMain } from 'electron'
import type { OrcadManagedRuntimeStatus } from '../../../shared/orcad-managed-runtime'
import { createManagedOrcadEnvironment } from '../../managed-server/lifecycle/orcad-runtime-deployment'
import { getManagedOrcadRuntimeStatus } from '../../managed-server/lifecycle/orcad-runtime-status'
import { registerOrcadSshProvisioningHandlers } from './orcad-ssh-provisioning-handlers'
export function registerOrcadRuntimeLifecycleHandlers(options: {
getUserDataPath: () => string
}): void {
registerOrcadSshProvisioningHandlers(options.getUserDataPath)
ipcMain.handle(
'runtimeEnvironments:deployOrcad',
async (_event, args: { name: string; sshTargetId: string; force?: boolean }) =>
@@ -1,40 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
handle: vi.fn(),
create: vi.fn(),
resume: vi.fn(),
list: vi.fn()
}))
vi.mock('electron', () => ({ ipcMain: { handle: mocks.handle } }))
vi.mock('../../ssh/orcad-ssh-provisioning', () => ({
createOrcadSshHost: mocks.create,
resumeOrcadSshHost: mocks.resume,
listPendingOrcadSshProvisioning: mocks.list
}))
import { registerOrcadSshProvisioningHandlers } from './orcad-ssh-provisioning-handlers'
describe('managed SSH provisioning IPC', () => {
beforeEach(() => vi.clearAllMocks())
it('registers typed create, resume and pending discovery without changing legacy SSH channels', async () => {
registerOrcadSshProvisioningHandlers(() => '/active-profile')
const handlers = new Map(mocks.handle.mock.calls.map(([name, handler]) => [name, handler]))
expect([...handlers.keys()]).toEqual([
'runtimeEnvironments:createOrcadSshHost',
'runtimeEnvironments:resumeOrcadSshHost',
'runtimeEnvironments:listPendingOrcadSshProvisioning'
])
const request = { requestId: 'request-1', name: 'host', target: { host: 'builder' } }
const pending = { result: { outcome: 'pending', reason: 'unverifiable' } }
mocks.create.mockResolvedValue(pending)
expect(await handlers.get('runtimeEnvironments:createOrcadSshHost')!(null, request)).toBe(
pending
)
expect(mocks.create).toHaveBeenCalledWith('/active-profile', request)
await handlers.get('runtimeEnvironments:resumeOrcadSshHost')!(null, { requestId: 'request-1' })
expect(mocks.resume).toHaveBeenCalledWith('/active-profile', 'request-1')
handlers.get('runtimeEnvironments:listPendingOrcadSshProvisioning')!()
expect(mocks.list).toHaveBeenCalledWith('/active-profile')
})
})
@@ -1,20 +0,0 @@
import { ipcMain } from 'electron'
import type { OrcadSshProvisioningRequest } from '../../../shared/orcad-ssh-provisioning'
import {
createOrcadSshHost,
listPendingOrcadSshProvisioning,
resumeOrcadSshHost
} from '../../ssh/orcad-ssh-provisioning'
export function registerOrcadSshProvisioningHandlers(getUserDataPath: () => string): void {
ipcMain.handle(
'runtimeEnvironments:createOrcadSshHost',
(_event, args: OrcadSshProvisioningRequest) => createOrcadSshHost(getUserDataPath(), args)
)
ipcMain.handle('runtimeEnvironments:resumeOrcadSshHost', (_event, args: { requestId: string }) =>
resumeOrcadSshHost(getUserDataPath(), args?.requestId)
)
ipcMain.handle('runtimeEnvironments:listPendingOrcadSshProvisioning', () =>
listPendingOrcadSshProvisioning(getUserDataPath())
)
}
@@ -24,8 +24,7 @@ import { verifyAndAddRuntimeEnvironmentFromPairingCode } from './runtime-environ
import { publishVerifiedPairing } from './runtime-environment-verified-pairing-publish'
import {
closeRemoteRuntimeRequestConnection,
getRuntimeEnvironmentStatusSnapshots,
retryRemoteRuntimeSharedControlConnectionNow
getRuntimeEnvironmentStatusSnapshots
} from './runtime-environment-request-connections'
import {
clearRuntimeEnvironmentManualDisconnect,
@@ -174,15 +173,6 @@ export function registerRuntimeEnvironmentConnectivityHandlers({
})
}
)
ipcMain.handle(
'runtimeEnvironments:retryControlConnection',
(_event, args: { selector: string }): void => {
const environment = resolveEnvironment(getUserDataPath(), args.selector)
if (!isRuntimeEnvironmentManuallyDisconnected(environment.id)) {
retryRemoteRuntimeSharedControlConnectionNow(environment.id)
}
}
)
}
export function registerRuntimeEnvironmentPassiveHandlers(getUserDataPath: () => string): void {
@@ -26,7 +26,6 @@ vi.mock('./runtime-environment-request-connections', async () => {
sendRemoteRuntimeConnectionRequest: vi.fn(),
sendRemoteRuntimeSharedControlRequest: sendRemoteRuntimeSharedControlRequestMock,
reconnectRemoteRuntimeSharedControlConnection: vi.fn(),
retryRemoteRuntimeSharedControlConnectionNow: vi.fn(),
ensureRemoteRuntimeSharedControlConnection: vi.fn(),
pauseRemoteRuntimeSharedControlRetry: vi.fn()
})
@@ -7,7 +7,6 @@ export const RUNTIME_ENVIRONMENT_HANDLER_CHANNELS = [
'runtimeEnvironments:remove',
'runtimeEnvironments:disconnect',
'runtimeEnvironments:connect',
'runtimeEnvironments:retryControlConnection',
'runtimeEnvironments:prepareBrowserClientHostPlacement',
'runtimeEnvironments:getStatus',
'runtimeEnvironments:getStatusSnapshots',
@@ -30,8 +29,5 @@ export const RUNTIME_ENVIRONMENT_HANDLER_CHANNELS = [
'runtimeEnvironments:previewOrcadDeltaMove',
'runtimeEnvironments:moveOrcadDelta',
'runtimeEnvironments:keepOrcadServerVersion',
'runtimeEnvironments:allowOrcadMoveLeavingBehind',
'runtimeEnvironments:createOrcadSshHost',
'runtimeEnvironments:resumeOrcadSshHost',
'runtimeEnvironments:listPendingOrcadSshProvisioning'
'runtimeEnvironments:allowOrcadMoveLeavingBehind'
] as const
@@ -240,10 +240,6 @@ export function reconnectRemoteRuntimeSharedControlConnection(environmentId: str
sharedControlConnections.get(environmentId)?.connection.reconnectNow()
}
export function retryRemoteRuntimeSharedControlConnectionNow(environmentId: string): void {
sharedControlConnections.get(environmentId)?.connection.retryNow()
}
export function pauseRemoteRuntimeSharedControlRetry(environmentId: string): void {
sharedControlConnections.get(environmentId)?.connection.pauseStandingRetry()
}
@@ -69,7 +69,6 @@ vi.mock('./runtime-environment-request-connections', async () => {
reconnectRemoteRuntimeSharedControlConnectionMock,
retryRemoteRuntimeSharedControlConnectionsNow:
retryRemoteRuntimeSharedControlConnectionsNowMock,
retryRemoteRuntimeSharedControlConnectionNow: vi.fn(),
ensureRemoteRuntimeSharedControlConnection: vi.fn(),
pauseRemoteRuntimeSharedControlRetry: vi.fn(),
closeRemoteRuntimeRequestConnection: closeRemoteRuntimeRequestConnectionMock
@@ -64,7 +64,6 @@ vi.mock('./runtime-environment-request-connections', async () => {
reconnectRemoteRuntimeSharedControlConnectionMock,
retryRemoteRuntimeSharedControlConnectionsNow:
retryRemoteRuntimeSharedControlConnectionsNowMock,
retryRemoteRuntimeSharedControlConnectionNow: vi.fn(),
ensureRemoteRuntimeSharedControlConnection: vi.fn(),
pauseRemoteRuntimeSharedControlRetry: vi.fn(),
closeRemoteRuntimeRequestConnection: closeRemoteRuntimeRequestConnectionMock
@@ -27,7 +27,6 @@ const {
getRemoteRuntimeSharedControlDiagnosticsMock,
reconnectRemoteRuntimeSharedControlConnectionMock,
retryRemoteRuntimeSharedControlConnectionsNowMock,
retryRemoteRuntimeSharedControlConnectionNowMock,
closeRemoteRuntimeRequestConnectionMock
} = vi.hoisted(() => ({
handleMock: vi.fn(),
@@ -43,7 +42,6 @@ const {
getRemoteRuntimeSharedControlDiagnosticsMock: vi.fn(),
reconnectRemoteRuntimeSharedControlConnectionMock: vi.fn(),
retryRemoteRuntimeSharedControlConnectionsNowMock: vi.fn(),
retryRemoteRuntimeSharedControlConnectionNowMock: vi.fn(),
closeRemoteRuntimeRequestConnectionMock: vi.fn()
}))
@@ -74,7 +72,6 @@ vi.mock('./runtime-environment-request-connections', async () => {
reconnectRemoteRuntimeSharedControlConnectionMock,
retryRemoteRuntimeSharedControlConnectionsNow:
retryRemoteRuntimeSharedControlConnectionsNowMock,
retryRemoteRuntimeSharedControlConnectionNow: retryRemoteRuntimeSharedControlConnectionNowMock,
ensureRemoteRuntimeSharedControlConnection: vi.fn(),
pauseRemoteRuntimeSharedControlRetry: vi.fn(),
closeRemoteRuntimeRequestConnection: closeRemoteRuntimeRequestConnectionMock
@@ -134,7 +131,6 @@ describe('registerRuntimeEnvironmentHandlers', () => {
getRemoteRuntimeSharedControlDiagnosticsMock.mockReturnValue(null)
reconnectRemoteRuntimeSharedControlConnectionMock.mockReset()
retryRemoteRuntimeSharedControlConnectionsNowMock.mockReset()
retryRemoteRuntimeSharedControlConnectionNowMock.mockReset()
closeRemoteRuntimeRequestConnectionMock.mockReset()
})
@@ -189,29 +185,6 @@ describe('registerRuntimeEnvironmentHandlers', () => {
expect(retryRemoteRuntimeSharedControlConnectionsNowMock).toHaveBeenCalledTimes(1)
})
it('retries one control connection without reversing manual disconnect intent', async () => {
registerRuntimeEnvironmentHandlers(store as never)
const add = handler<{ name: string; pairingCode: string }, { environment: { id: string } }>(
'runtimeEnvironments:addFromPairingCode'
)
const added = await add(null, { name: 'desk', pairingCode: pairingCode() })
const retry = handler<{ selector: string }, void>('runtimeEnvironments:retryControlConnection')
await retry(null, { selector: 'desk' })
expect(retryRemoteRuntimeSharedControlConnectionNowMock).toHaveBeenCalledWith(
added.environment.id
)
expect(sendRemoteRuntimeRequestMock).not.toHaveBeenCalled()
const disconnect = handler<{ selector: string }, unknown>('runtimeEnvironments:disconnect')
await disconnect(null, { selector: 'desk' })
retryRemoteRuntimeSharedControlConnectionNowMock.mockClear()
await retry(null, { selector: 'desk' })
expect(retryRemoteRuntimeSharedControlConnectionNowMock).not.toHaveBeenCalled()
expect(sendRemoteRuntimeRequestMock).not.toHaveBeenCalled()
})
it('stores, resolves, lists, and removes environments under Electron userData', async () => {
registerRuntimeEnvironmentHandlers(store as never)
@@ -36,7 +36,6 @@ vi.mock('./runtime-environment-request-connections', async () => {
getRemoteRuntimeSharedControlDiagnostics: vi.fn(() => null),
reconnectRemoteRuntimeSharedControlConnection: vi.fn(),
retryRemoteRuntimeSharedControlConnectionsNow: vi.fn(),
retryRemoteRuntimeSharedControlConnectionNow: vi.fn(),
ensureRemoteRuntimeSharedControlConnection: vi.fn(),
pauseRemoteRuntimeSharedControlRetry: vi.fn(),
closeRemoteRuntimeRequestConnection: vi.fn()
@@ -69,7 +69,6 @@ vi.mock('./runtime-environment-request-connections', async () => {
reconnectRemoteRuntimeSharedControlConnectionMock,
retryRemoteRuntimeSharedControlConnectionsNow:
retryRemoteRuntimeSharedControlConnectionsNowMock,
retryRemoteRuntimeSharedControlConnectionNow: vi.fn(),
ensureRemoteRuntimeSharedControlConnection: ensureRemoteRuntimeSharedControlConnectionMock,
pauseRemoteRuntimeSharedControlRetry: pauseRemoteRuntimeSharedControlRetryMock,
closeRemoteRuntimeRequestConnection: closeRemoteRuntimeRequestConnectionMock
@@ -65,7 +65,6 @@ vi.mock('./runtime-environment-request-connections', async () => {
reconnectRemoteRuntimeSharedControlConnectionMock,
retryRemoteRuntimeSharedControlConnectionsNow:
retryRemoteRuntimeSharedControlConnectionsNowMock,
retryRemoteRuntimeSharedControlConnectionNow: vi.fn(),
ensureRemoteRuntimeSharedControlConnection: vi.fn(),
pauseRemoteRuntimeSharedControlRetry: vi.fn(),
closeRemoteRuntimeRequestConnection: closeRemoteRuntimeRequestConnectionMock
@@ -65,7 +65,6 @@ vi.mock('./runtime-environment-request-connections', async () => {
reconnectRemoteRuntimeSharedControlConnectionMock,
retryRemoteRuntimeSharedControlConnectionsNow:
retryRemoteRuntimeSharedControlConnectionsNowMock,
retryRemoteRuntimeSharedControlConnectionNow: vi.fn(),
ensureRemoteRuntimeSharedControlConnection: vi.fn(),
pauseRemoteRuntimeSharedControlRetry: vi.fn(),
closeRemoteRuntimeRequestConnection: closeRemoteRuntimeRequestConnectionMock
@@ -65,7 +65,6 @@ vi.mock('./runtime-environment-request-connections', async () => {
reconnectRemoteRuntimeSharedControlConnectionMock,
retryRemoteRuntimeSharedControlConnectionsNow:
retryRemoteRuntimeSharedControlConnectionsNowMock,
retryRemoteRuntimeSharedControlConnectionNow: vi.fn(),
ensureRemoteRuntimeSharedControlConnection: vi.fn(),
pauseRemoteRuntimeSharedControlRetry: vi.fn(),
closeRemoteRuntimeRequestConnection: closeRemoteRuntimeRequestConnectionMock
-1
View File
@@ -68,7 +68,6 @@ vi.mock('./runtime-environments/runtime-environment-request-connections', async
getRemoteRuntimeSharedControlDiagnostics: vi.fn(() => null),
reconnectRemoteRuntimeSharedControlConnection: vi.fn(),
retryRemoteRuntimeSharedControlConnectionsNow: vi.fn(),
retryRemoteRuntimeSharedControlConnectionNow: vi.fn(),
ensureRemoteRuntimeSharedControlConnection: mocks.ensureSharedControl,
pauseRemoteRuntimeSharedControlRetry: vi.fn(),
closeRemoteRuntimeRequestConnection: vi.fn()
-247
View File
@@ -1,247 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { SshTarget } from '../../shared/ssh-types'
import { normalizeSshTarget } from '../persistence/leasing-ssh-ptys/ssh-normalization'
const mocks = vi.hoisted(() => {
const targets: SshTarget[] = []
const deployedTargets: string[] = []
const targetStore: Record<string, unknown> = {}
return {
targets,
deployedTargets,
update: vi.fn(),
deploy: vi.fn(),
flush: vi.fn(),
add: vi.fn(),
rotate: vi.fn(),
targetStore
}
})
vi.mock('../managed-server/lifecycle/orcad-managed-runtime-context', () => ({
requireManagedOrcadTargetStore: () => mocks.targetStore
}))
vi.mock('../managed-server/lifecycle/orcad-runtime-deployment', () => ({
createManagedOrcadEnvironment: mocks.deploy
}))
vi.mock('./ssh-provider-authority', () => ({ rotateSshProviderAuthority: mocks.rotate }))
vi.mock('../../shared/runtime-environment-store', () => ({
listEnvironments: () =>
mocks.deployedTargets.map((sshTargetId) => ({ orcadDeployment: { sshTargetId } }))
}))
import {
createOrcadSshHost,
listPendingOrcadSshProvisioning,
resumeOrcadSshHost
} from './orcad-ssh-provisioning'
const request = {
requestId: 'request-1',
name: 'Build host',
target: { label: 'Build host', host: 'builder', port: 22, username: 'dev' }
}
const deployed = {
outcome: 'created',
environment: { id: 'environment-1' },
activeVersion: 'node-1'
}
beforeEach(() => {
vi.clearAllMocks()
mocks.targets.length = 0
mocks.deployedTargets.length = 0
mocks.flush.mockReset().mockResolvedValue(undefined)
mocks.deploy.mockReset().mockResolvedValue(deployed)
mocks.add.mockImplementation((input) => {
const target = normalizeSshTarget({
...input,
id: `ssh-${mocks.targets.length + 1}`,
generation: 1
})
mocks.targets.push(target)
return target
})
mocks.update.mockImplementation((id: string, updates: Partial<SshTarget>) => {
const index = mocks.targets.findIndex((target) => target.id === id)
mocks.targets[index] = { ...mocks.targets[index]!, ...updates }
return mocks.targets[index]
})
mocks.targetStore = {
addTarget: mocks.add,
updateTarget: mocks.update,
lastRepoReadoptions: [],
getOrcadRuntimeClaims: () => ({ listTargets: () => mocks.targets, flush: mocks.flush })
}
})
describe('managed SSH host provisioning', () => {
it('persists intent before running the existing migration/deployment preflight', async () => {
mocks.deploy.mockImplementation(async () => {
expect(mocks.flush).toHaveBeenCalledOnce()
expect(mocks.targets[0]?.orcadProvisioning).toEqual({
requestId: 'request-1',
name: 'Build host'
})
return deployed
})
await expect(createOrcadSshHost('/profile', request)).resolves.toMatchObject({
requestId: 'request-1',
name: 'Build host',
sshTargetId: 'ssh-1',
result: deployed
})
expect(mocks.deploy).toHaveBeenCalledWith('/profile', {
name: 'Build host',
sshTargetId: 'ssh-1'
})
})
it('does not contact a host if durable intent publication fails', async () => {
mocks.flush.mockRejectedValueOnce(new Error('disk full'))
await expect(createOrcadSshHost('/profile', request)).rejects.toThrow('disk full')
expect(mocks.deploy).not.toHaveBeenCalled()
await resumeOrcadSshHost('/profile', 'request-1')
expect(mocks.add).toHaveBeenCalledOnce()
})
it('retains the same request across preflight refusal and a reconstructed target store', async () => {
mocks.deploy.mockRejectedValueOnce(new Error('live-or-unverifiable terminal leases'))
await expect(createOrcadSshHost('/profile', request)).resolves.toMatchObject({
result: { outcome: 'pending', reason: 'live-or-unverifiable terminal leases' }
})
mocks.targets.splice(0, 1, JSON.parse(JSON.stringify(mocks.targets[0])))
expect(listPendingOrcadSshProvisioning('/profile')).toEqual([
{ requestId: 'request-1', name: 'Build host', sshTargetId: 'ssh-1' }
])
await expect(resumeOrcadSshHost('/profile', 'request-1')).resolves.toMatchObject({
result: deployed
})
expect(mocks.add).toHaveBeenCalledOnce()
expect(mocks.deploy).toHaveBeenCalledTimes(2)
})
it('restores a host with re-adopted projects as direct SSH instead of hiding them', async () => {
const readoptions = [{ oldTargetId: 'old', newTargetId: 'ssh-1', repoIds: ['repo-1'] }]
mocks.targetStore.lastRepoReadoptions = readoptions
const result = await createOrcadSshHost('/profile', request)
expect(result.repoReadoptions).toEqual(readoptions)
expect(result.result).toMatchObject({ outcome: 'pending' })
expect(mocks.rotate).toHaveBeenCalledWith('old')
expect(mocks.rotate).toHaveBeenCalledWith('ssh-1')
expect(mocks.targets[0]?.orcadProvisioning).toBeUndefined()
expect(listPendingOrcadSshProvisioning('/profile')).toEqual([])
expect(mocks.deploy).not.toHaveBeenCalled()
})
it('returns activation deferral without force, deletion, or another target', async () => {
const deferred = {
outcome: 'deferred',
reason: 'unverifiable',
code: 'blocked',
candidateVersion: 'node-1'
}
mocks.deploy.mockResolvedValueOnce(deferred)
await expect(createOrcadSshHost('/profile', request)).resolves.toMatchObject({
result: deferred
})
expect(listPendingOrcadSshProvisioning('/profile')).toHaveLength(1)
expect(mocks.targets).toHaveLength(1)
})
it('serializes repeated requests and preserves normalization on exact retries', async () => {
const normalizedRequest = {
...request,
target: { ...request.target, relayGracePeriodSeconds: 10800 }
}
await Promise.all([
createOrcadSshHost('/profile', normalizedRequest),
createOrcadSshHost('/profile', normalizedRequest)
])
expect(mocks.add).toHaveBeenCalledOnce()
expect(mocks.targets).toHaveLength(1)
})
it('rejects request identity reuse for different host data or name', async () => {
await createOrcadSshHost('/profile', request)
await expect(createOrcadSshHost('/profile', { ...request, name: 'Other' })).rejects.toThrow(
'already belongs'
)
await expect(
createOrcadSshHost('/profile', {
...request,
target: { ...request.target, identityFile: 'other' }
})
).rejects.toThrow('already belongs')
expect(mocks.deploy).toHaveBeenCalledOnce()
})
it('does not create a duplicate raw host when another request registered the endpoint', async () => {
await createOrcadSshHost('/profile', request)
await expect(
createOrcadSshHost('/profile', { ...request, requestId: 'request-2' })
).rejects.toThrow('already registered')
expect(mocks.add).toHaveBeenCalledOnce()
})
it('does not duplicate an occupied config alias or endpoint under a different label', async () => {
await createOrcadSshHost('/profile', request)
await expect(
createOrcadSshHost('/profile', {
...request,
requestId: 'request-2',
target: { ...request.target, configHost: 'BUILDER', host: 'resolved-address' }
})
).rejects.toThrow('already registered')
await expect(
createOrcadSshHost('/profile', {
...request,
requestId: 'request-3',
target: { ...request.target, label: 'Different', configHost: 'other-alias' }
})
).rejects.toThrow('already registered')
})
it.each([
{ configHost: 3 },
{ identityFile: false },
{ gssapiAuthentication: 'yes' },
{ systemSshConnectionReuse: 1 },
{ portForwards: [{ localPort: -1 }] }
])('rejects malformed optional connection fields before durable registration: %j', (invalid) => {
expect(() =>
createOrcadSshHost('/profile', {
...request,
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: deliberately malformed renderer input.
target: { ...request.target, ...invalid } as never
})
).toThrow()
expect(mocks.add).not.toHaveBeenCalled()
})
it('omits completed requests from pending discovery but retains their retry identity', async () => {
await createOrcadSshHost('/profile', request)
mocks.deployedTargets.push('ssh-1')
expect(listPendingOrcadSshProvisioning('/profile')).toEqual([])
await resumeOrcadSshHost('/profile', 'request-1')
expect(mocks.add).toHaveBeenCalledOnce()
expect(mocks.deploy).toHaveBeenCalledTimes(2)
})
it('does not provision imported config entries without explicit intent', () => {
mocks.targets.push({ ...request.target, id: 'imported', source: 'ssh-config' })
expect(listPendingOrcadSshProvisioning('/profile')).toEqual([])
expect(mocks.deploy).not.toHaveBeenCalled()
})
it('rejects invalid input and unknown retry IDs without writing a target', async () => {
expect(() => createOrcadSshHost('/profile', { ...request, requestId: '' })).toThrow(
'request id'
)
expect(() =>
createOrcadSshHost('/profile', { ...request, target: { ...request.target, port: 0 } })
).toThrow('port')
await expect(resumeOrcadSshHost('/profile', 'missing')).rejects.toThrow('not found')
expect(mocks.add).not.toHaveBeenCalled()
})
})
-201
View File
@@ -1,201 +0,0 @@
import { z } from 'zod'
import type {
OrcadSshPendingProvisioning,
OrcadSshProvisioningRequest,
OrcadSshProvisioningResult
} from '../../shared/orcad-ssh-provisioning'
import type { SshRepoReadoption, SshTarget, SshTargetCreateInput } from '../../shared/ssh-types'
import { EphemeralVmRecipeSshTargetSchema } from '../../shared/ephemeral-vm-recipes'
import { listEnvironments } from '../../shared/runtime-environment-store'
import { getOrcadSshDeployment } from '../../shared/runtime-environments'
import { normalizeSshConfigAlias } from '../../shared/ssh-config-alias'
import { runTargetLifecycle } from '../ipc/ssh/ssh-target-lifecycle-queue'
import { normalizeSshTarget } from '../persistence/leasing-ssh-ptys/ssh-normalization'
import { requireManagedOrcadTargetStore } from '../managed-server/lifecycle/orcad-managed-runtime-context'
import { createManagedOrcadEnvironment } from '../managed-server/lifecycle/orcad-runtime-deployment'
import { rotateSshProviderAuthority } from './ssh-provider-authority'
// Why no port forwards: a managed server is only created on an empty host.
const provisioningTargetSchema = EphemeralVmRecipeSshTargetSchema.omit({
portForwards: true
}).extend({
gssapiAuthentication: z.boolean().optional(),
systemSshConnectionReuse: z.boolean().optional(),
source: z.enum(['manual', 'ssh-config']).optional(),
lastRequiredPassphrase: z.boolean().optional()
})
/** Requests whose server is not registered yet; a completed one keeps its intent for idempotent retries. */
export function listPendingOrcadSshProvisioning(
userDataPath: string
): OrcadSshPendingProvisioning[] {
const deployed = new Set(
listEnvironments(userDataPath).flatMap(
(environment) => getOrcadSshDeployment(environment)?.sshTargetId ?? []
)
)
return requireManagedOrcadTargetStore()
.getOrcadRuntimeClaims()
.listTargets()
.flatMap((target) =>
target.orcadProvisioning && !deployed.has(target.id)
? [{ ...target.orcadProvisioning, sshTargetId: target.id }]
: []
)
}
export function createOrcadSshHost(
userDataPath: string,
request: OrcadSshProvisioningRequest
): Promise<OrcadSshProvisioningResult> {
const requestId = requireRequestId(request?.requestId)
const name = requireText(request?.name, 'Server name')
const targetInput = parseTarget(request?.target)
return runTargetLifecycle(`orcad-provision:${userDataPath}:${requestId}`, async () => {
const targetStore = requireManagedOrcadTargetStore()
const targets = targetStore.getOrcadRuntimeClaims().listTargets()
const existing = targets.find((entry) => entry.orcadProvisioning?.requestId === requestId)
if (existing) {
if (existing.orcadProvisioning?.name !== name || !matchesRequest(existing, targetInput)) {
throw new Error('This provisioning request already belongs to another host or server name.')
}
return provision(userDataPath, existing, [])
}
if (targets.some((entry) => sameEndpoint(entry, targetInput))) {
throw new Error('That SSH host is already registered. Use its existing server or SSH entry.')
}
const target = targetStore.addTarget({
...targetInput,
source: 'manual',
orcadProvisioning: { requestId, name }
})
const repoReadoptions = [...targetStore.lastRepoReadoptions]
targetStore.lastRepoReadoptions = []
for (const targetId of new Set(
repoReadoptions.flatMap(({ oldTargetId, newTargetId }) => [oldTargetId, newTargetId])
)) {
rotateSshProviderAuthority(targetId)
}
if (repoReadoptions.length > 0) {
// Why visible: re-adopted projects make the host non-empty; hiding it would hide them too.
targetStore.updateTarget(target.id, { orcadProvisioning: undefined })
return {
requestId,
name,
sshTargetId: target.id,
repoReadoptions,
result: {
outcome: 'pending',
reason:
'This host already has Orca projects, so it was restored as a direct SSH host. ' +
'Orca can only be set up on an empty host.'
}
}
}
return provision(userDataPath, target, repoReadoptions)
})
}
export function resumeOrcadSshHost(
userDataPath: string,
requestIdInput: string
): Promise<OrcadSshProvisioningResult> {
const requestId = requireRequestId(requestIdInput)
return runTargetLifecycle(`orcad-provision:${userDataPath}:${requestId}`, async () => {
const target = requireManagedOrcadTargetStore()
.getOrcadRuntimeClaims()
.listTargets()
.find((entry) => entry.orcadProvisioning?.requestId === requestId)
if (!target) {
throw new Error('The managed SSH provisioning request was not found.')
}
return provision(userDataPath, target, [])
})
}
async function provision(
userDataPath: string,
target: SshTarget,
repoReadoptions: SshRepoReadoption[]
): Promise<OrcadSshProvisioningResult> {
const intent = target.orcadProvisioning
if (!intent) {
throw new Error('The managed SSH provisioning request was not found.')
}
// Why first: the intent must survive a crash before the host is contacted.
await requireManagedOrcadTargetStore().getOrcadRuntimeClaims().flush()
const base = { ...intent, sshTargetId: target.id, repoReadoptions }
try {
return {
...base,
result: await createManagedOrcadEnvironment(userDataPath, {
name: intent.name,
sshTargetId: target.id
})
}
} catch (error) {
return {
...base,
result: {
outcome: 'pending',
reason: error instanceof Error ? error.message : 'Managed SSH provisioning failed.'
}
}
}
}
function requireText(value: unknown, label: string): string {
if (typeof value !== 'string' || !value.trim() || value.length > 1_024) {
throw new Error(`${label} is required and must not exceed 1024 characters.`)
}
return value.trim()
}
function requireRequestId(value: unknown): string {
if (typeof value !== 'string' || !/^[a-zA-Z0-9_-]{1,128}$/.test(value)) {
throw new Error('A stable managed SSH provisioning request id is required.')
}
return value
}
function parseTarget(value: unknown): SshTargetCreateInput {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error('A new unowned SSH host is required.')
}
const {
id: _id,
generation: _generation,
orcadProvisioning: _intent,
owner,
...raw
}: Record<string, unknown> = { ...value }
if (owner !== undefined) {
throw new Error('A new unowned SSH host is required.')
}
const target = provisioningTargetSchema.parse(raw)
return {
...target,
label: requireText(target.label, 'SSH host label'),
host: requireText(target.host, 'SSH host'),
username: target.username.trim(),
configHost: target.configHost?.trim() || target.host.trim()
}
}
function sameEndpoint(left: SshTarget, right: SshTargetCreateInput): boolean {
const alias = normalizeSshConfigAlias(right.configHost ?? right.host)
if (
alias &&
[left.configHost, left.label].some((value) => normalizeSshConfigAlias(value) === alias)
) {
return true
}
return left.host === right.host && left.port === right.port && left.username === right.username
}
function matchesRequest(target: SshTarget, input: SshTargetCreateInput): boolean {
const stored: Record<string, unknown> = { ...target }
return Object.entries(normalizeSshTarget({ ...input, id: target.id })).every(
([key, value]) => key === 'source' || JSON.stringify(stored[key]) === JSON.stringify(value)
)
}
-1
View File
@@ -113,7 +113,6 @@ export type RuntimeApi = {
timeoutMs?: number
observeOnly?: true
}) => Promise<RuntimeRpcResponse<RuntimeStatus>>
retryControlConnection?: (args: { selector: string }) => Promise<void>
onSharedControlDiagnostics?: (
callback: (event: {
environmentId: string
@@ -69,8 +69,6 @@ export const runtimeEnvironmentsApi = {
observeOnly?: true
}): Promise<RuntimeRpcResponse<RuntimeStatus>> =>
ipcRenderer.invoke('runtimeEnvironments:getStatus', args),
retryControlConnection: (args: { selector: string }): Promise<void> =>
ipcRenderer.invoke('runtimeEnvironments:retryControlConnection', args),
onSharedControlDiagnostics: (
callback: (event: {
environmentId: string
@@ -210,7 +210,6 @@ export function createRuntimeEnvironmentsApi(): NonNullable<
observeOnly
? observeWebRuntimeStatus(selector, timeoutMs)
: callEnvironmentEnvelope<RuntimeStatus>(selector, 'status.get', undefined, timeoutMs),
retryControlConnection: () => Promise.resolve(),
prepareBrowserClientHostPlacement: async () => ({ kind: 'server' }),
call: ({
selector,
-16
View File
@@ -1,20 +1,4 @@
import type { OrcadManagedDeployResult } from './orcad-managed-runtime'
import type { SshRepoReadoption, SshTargetCreateInput } from './ssh-types'
export type OrcadSshProvisioningIntent = Readonly<{
requestId: string
name: string
}>
export type OrcadSshProvisioningRequest = OrcadSshProvisioningIntent & {
target: SshTargetCreateInput
}
export type OrcadSshPendingProvisioning = OrcadSshProvisioningIntent & {
sshTargetId: string
}
export type OrcadSshProvisioningResult = OrcadSshPendingProvisioning & {
repoReadoptions: SshRepoReadoption[]
result: OrcadManagedDeployResult | { outcome: 'pending'; reason: string }
}