diff --git a/src/main/ipc/runtime-environments/orcad-runtime-lifecycle-handlers.test.ts b/src/main/ipc/runtime-environments/orcad-runtime-lifecycle-handlers.test.ts index 1268aba535b2..c12d45848dec 100644 --- a/src/main/ipc/runtime-environments/orcad-runtime-lifecycle-handlers.test.ts +++ b/src/main/ipc/runtime-environments/orcad-runtime-lifecycle-handlers.test.ts @@ -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 () => { diff --git a/src/main/ipc/runtime-environments/orcad-runtime-lifecycle-handlers.ts b/src/main/ipc/runtime-environments/orcad-runtime-lifecycle-handlers.ts index 243c60251fff..7b49728998fe 100644 --- a/src/main/ipc/runtime-environments/orcad-runtime-lifecycle-handlers.ts +++ b/src/main/ipc/runtime-environments/orcad-runtime-lifecycle-handlers.ts @@ -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 }) => diff --git a/src/main/ipc/runtime-environments/orcad-ssh-provisioning-handlers.test.ts b/src/main/ipc/runtime-environments/orcad-ssh-provisioning-handlers.test.ts deleted file mode 100644 index 0f88d8261117..000000000000 --- a/src/main/ipc/runtime-environments/orcad-ssh-provisioning-handlers.test.ts +++ /dev/null @@ -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') - }) -}) diff --git a/src/main/ipc/runtime-environments/orcad-ssh-provisioning-handlers.ts b/src/main/ipc/runtime-environments/orcad-ssh-provisioning-handlers.ts deleted file mode 100644 index 4077ca38d85b..000000000000 --- a/src/main/ipc/runtime-environments/orcad-ssh-provisioning-handlers.ts +++ /dev/null @@ -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()) - ) -} diff --git a/src/main/ipc/runtime-environments/runtime-environment-connectivity-handlers.ts b/src/main/ipc/runtime-environments/runtime-environment-connectivity-handlers.ts index 7030eb4e7dfc..49616aa5e7c4 100644 --- a/src/main/ipc/runtime-environments/runtime-environment-connectivity-handlers.ts +++ b/src/main/ipc/runtime-environments/runtime-environment-connectivity-handlers.ts @@ -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 { diff --git a/src/main/ipc/runtime-environments/runtime-environment-federated-read-routing.test.ts b/src/main/ipc/runtime-environments/runtime-environment-federated-read-routing.test.ts index 30bd6f5c74a1..c0a0ae4e492b 100644 --- a/src/main/ipc/runtime-environments/runtime-environment-federated-read-routing.test.ts +++ b/src/main/ipc/runtime-environments/runtime-environment-federated-read-routing.test.ts @@ -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() }) diff --git a/src/main/ipc/runtime-environments/runtime-environment-handler-channels.ts b/src/main/ipc/runtime-environments/runtime-environment-handler-channels.ts index 5c486c025d63..ba9dba5aff60 100644 --- a/src/main/ipc/runtime-environments/runtime-environment-handler-channels.ts +++ b/src/main/ipc/runtime-environments/runtime-environment-handler-channels.ts @@ -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 diff --git a/src/main/ipc/runtime-environments/runtime-environment-request-connections.ts b/src/main/ipc/runtime-environments/runtime-environment-request-connections.ts index c208c185774b..7538a7486512 100644 --- a/src/main/ipc/runtime-environments/runtime-environment-request-connections.ts +++ b/src/main/ipc/runtime-environments/runtime-environment-request-connections.ts @@ -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() } diff --git a/src/main/ipc/runtime-environments/runtime-environments-call-routing.test.ts b/src/main/ipc/runtime-environments/runtime-environments-call-routing.test.ts index 893e9296d74b..7fb73813b385 100644 --- a/src/main/ipc/runtime-environments/runtime-environments-call-routing.test.ts +++ b/src/main/ipc/runtime-environments/runtime-environments-call-routing.test.ts @@ -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 diff --git a/src/main/ipc/runtime-environments/runtime-environments-capability-cache.test.ts b/src/main/ipc/runtime-environments/runtime-environments-capability-cache.test.ts index 81ccf16a7971..bf718eb574d4 100644 --- a/src/main/ipc/runtime-environments/runtime-environments-capability-cache.test.ts +++ b/src/main/ipc/runtime-environments/runtime-environments-capability-cache.test.ts @@ -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 diff --git a/src/main/ipc/runtime-environments/runtime-environments-pairing.test.ts b/src/main/ipc/runtime-environments/runtime-environments-pairing.test.ts index 28571e27f0fe..6272f4e56120 100644 --- a/src/main/ipc/runtime-environments/runtime-environments-pairing.test.ts +++ b/src/main/ipc/runtime-environments/runtime-environments-pairing.test.ts @@ -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) diff --git a/src/main/ipc/runtime-environments/runtime-environments-repair.test.ts b/src/main/ipc/runtime-environments/runtime-environments-repair.test.ts index 0a6b82b61500..26c5c3a64200 100644 --- a/src/main/ipc/runtime-environments/runtime-environments-repair.test.ts +++ b/src/main/ipc/runtime-environments/runtime-environments-repair.test.ts @@ -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() diff --git a/src/main/ipc/runtime-environments/runtime-environments-status-diagnostics.test.ts b/src/main/ipc/runtime-environments/runtime-environments-status-diagnostics.test.ts index 72f7d08d3d93..c920f79fb7f4 100644 --- a/src/main/ipc/runtime-environments/runtime-environments-status-diagnostics.test.ts +++ b/src/main/ipc/runtime-environments/runtime-environments-status-diagnostics.test.ts @@ -69,7 +69,6 @@ vi.mock('./runtime-environment-request-connections', async () => { reconnectRemoteRuntimeSharedControlConnectionMock, retryRemoteRuntimeSharedControlConnectionsNow: retryRemoteRuntimeSharedControlConnectionsNowMock, - retryRemoteRuntimeSharedControlConnectionNow: vi.fn(), ensureRemoteRuntimeSharedControlConnection: ensureRemoteRuntimeSharedControlConnectionMock, pauseRemoteRuntimeSharedControlRetry: pauseRemoteRuntimeSharedControlRetryMock, closeRemoteRuntimeRequestConnection: closeRemoteRuntimeRequestConnectionMock diff --git a/src/main/ipc/runtime-environments/runtime-environments-subscription-lifecycle.test.ts b/src/main/ipc/runtime-environments/runtime-environments-subscription-lifecycle.test.ts index c2f4472be198..e097127b2857 100644 --- a/src/main/ipc/runtime-environments/runtime-environments-subscription-lifecycle.test.ts +++ b/src/main/ipc/runtime-environments/runtime-environments-subscription-lifecycle.test.ts @@ -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 diff --git a/src/main/ipc/runtime-environments/runtime-environments-subscription-routing.test.ts b/src/main/ipc/runtime-environments/runtime-environments-subscription-routing.test.ts index 783ae5efdaf0..21aa179348e9 100644 --- a/src/main/ipc/runtime-environments/runtime-environments-subscription-routing.test.ts +++ b/src/main/ipc/runtime-environments/runtime-environments-subscription-routing.test.ts @@ -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 diff --git a/src/main/ipc/runtime-environments/runtime-environments-subscription-teardown.test.ts b/src/main/ipc/runtime-environments/runtime-environments-subscription-teardown.test.ts index 4062c982c99b..74d52cb100e7 100644 --- a/src/main/ipc/runtime-environments/runtime-environments-subscription-teardown.test.ts +++ b/src/main/ipc/runtime-environments/runtime-environments-subscription-teardown.test.ts @@ -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 diff --git a/src/main/ipc/wsl-host-gate-off.test.ts b/src/main/ipc/wsl-host-gate-off.test.ts index bb5a456fe50b..dd4e12120c7d 100644 --- a/src/main/ipc/wsl-host-gate-off.test.ts +++ b/src/main/ipc/wsl-host-gate-off.test.ts @@ -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() diff --git a/src/main/ssh/orcad-ssh-provisioning.test.ts b/src/main/ssh/orcad-ssh-provisioning.test.ts deleted file mode 100644 index 4f13e5632a9d..000000000000 --- a/src/main/ssh/orcad-ssh-provisioning.test.ts +++ /dev/null @@ -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 = {} - 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) => { - 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() - }) -}) diff --git a/src/main/ssh/orcad-ssh-provisioning.ts b/src/main/ssh/orcad-ssh-provisioning.ts deleted file mode 100644 index 632af60852fa..000000000000 --- a/src/main/ssh/orcad-ssh-provisioning.ts +++ /dev/null @@ -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 { - 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 { - 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 { - 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 = { ...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 = { ...target } - return Object.entries(normalizeSshTarget({ ...input, id: target.id })).every( - ([key, value]) => key === 'source' || JSON.stringify(stored[key]) === JSON.stringify(value) - ) -} diff --git a/src/preload/api/runtime-api.ts b/src/preload/api/runtime-api.ts index 571fe2244f30..4b45dfbae03e 100644 --- a/src/preload/api/runtime-api.ts +++ b/src/preload/api/runtime-api.ts @@ -113,7 +113,6 @@ export type RuntimeApi = { timeoutMs?: number observeOnly?: true }) => Promise> - retryControlConnection?: (args: { selector: string }) => Promise onSharedControlDiagnostics?: ( callback: (event: { environmentId: string diff --git a/src/preload/api/runtime-environments-bridge.ts b/src/preload/api/runtime-environments-bridge.ts index 7934d892f363..2e678bde36a5 100644 --- a/src/preload/api/runtime-environments-bridge.ts +++ b/src/preload/api/runtime-environments-bridge.ts @@ -69,8 +69,6 @@ export const runtimeEnvironmentsApi = { observeOnly?: true }): Promise> => ipcRenderer.invoke('runtimeEnvironments:getStatus', args), - retryControlConnection: (args: { selector: string }): Promise => - ipcRenderer.invoke('runtimeEnvironments:retryControlConnection', args), onSharedControlDiagnostics: ( callback: (event: { environmentId: string diff --git a/src/renderer/src/web/preload-api/web-runtime-environments-api.ts b/src/renderer/src/web/preload-api/web-runtime-environments-api.ts index 506810d00bde..6ca8e0e56f48 100644 --- a/src/renderer/src/web/preload-api/web-runtime-environments-api.ts +++ b/src/renderer/src/web/preload-api/web-runtime-environments-api.ts @@ -210,7 +210,6 @@ export function createRuntimeEnvironmentsApi(): NonNullable< observeOnly ? observeWebRuntimeStatus(selector, timeoutMs) : callEnvironmentEnvelope(selector, 'status.get', undefined, timeoutMs), - retryControlConnection: () => Promise.resolve(), prepareBrowserClientHostPlacement: async () => ({ kind: 'server' }), call: ({ selector, diff --git a/src/shared/orcad-ssh-provisioning.ts b/src/shared/orcad-ssh-provisioning.ts index 11798f75e4c8..c7a30a2b5208 100644 --- a/src/shared/orcad-ssh-provisioning.ts +++ b/src/shared/orcad-ssh-provisioning.ts @@ -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 } -}