Files
orca/src/main/node-runtime/managed-runtime-installation.test.ts
T
Brennan Benson 6fd2172b3f feat(runtime): remove an agent's downloaded runtime only once nothing on the host can still run it (#29139)
* feat(runtime): remove an agent's downloaded runtime only once nothing on the host can still run it

managedRuntime.remove {agent} (host-admin, capability managed-runtime.remove.v1)
deletes the agent's releases inside the launch gate. Before anything is deleted the
gate aborts starts under way (chat starts and conversation copies), stops the
agent's loaded chats on this host, and proves every retained child gone: failed
chat starts and sign-in children whose close was never proven. Every close settles
before the gate releases; if any owner stays unproven, Remove fails and keeps the
files. Stopping chats by agent is now one generic host helper that sign-in shares.
Install refuses while a removal runs, Remove refuses while an install runs, and a
removal cannot be cancelled.

* fix(runtime): status says whether a copy is there to remove, damaged copies included

canRemove is set whenever Orca's copy is present, whole or damaged, so Settings offers Remove for a
damaged copy too. The failed-start retain is dropped: the launch gate already holds every child it
admitted until it exits.
2026-10-18 01:15:11 -07:00

141 lines
5.7 KiB
TypeScript

import { createHash } from 'node:crypto'
import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { strToU8, zipSync } from 'fflate'
import { afterEach, expect, it, vi } from 'vitest'
import type * as ReleaseModule from './managed-runtime-release'
import { reclaimRuntimeReleases, RUNTIME_RELEASE_RECEIPT } from './managed-runtime-release'
import { ManagedRuntimeInstallation } from './managed-runtime-installation'
vi.mock('./managed-runtime-release', async (importOriginal) => {
const actual = await importOriginal<typeof ReleaseModule>()
return { ...actual, reclaimRuntimeReleases: vi.fn(actual.reclaimRuntimeReleases) }
})
const sha256 = (bytes: string | Uint8Array) => createHash('sha256').update(bytes).digest('hex')
const ARCHIVE = zipSync({ server: strToU8('server bytes') })
const roots: string[] = []
afterEach(() => Promise.all(roots.splice(0).map((r) => rm(r, { recursive: true, force: true }))))
async function rig(bodies: ('hang' | 'whole')[]) {
const dataDirectory = await mkdtemp(join(tmpdir(), 'orca-runtime-install-'))
roots.push(dataDirectory)
const root = join(dataDirectory, 'agent-runtimes', 'agent')
const pin = {
version: '1.0',
url: 'https://example.test/agent-1.0.zip',
archiveSha256: sha256(ARCHIVE),
archiveBytes: ARCHIVE.length,
members: [{ path: 'server', sha256: sha256('server bytes'), bytes: 12 }]
}
const runtime = { label: 'Agent', root: () => root, pin: () => pin }
const fetcher = async () => {
const body = bodies.shift()
return new Response(
new ReadableStream({
start(controller) {
controller.enqueue(ARCHIVE.slice(0, 5))
if (body === 'whole') {
controller.enqueue(ARCHIVE.slice(5))
controller.close()
}
}
})
)
}
const host = () =>
new ManagedRuntimeInstallation('agent', runtime, {
dataDirectory,
findExternal: async () => false,
fence: (work) => work(),
fetcher
})
const phase = async (installation: ManagedRuntimeInstallation) =>
(await installation.status()).operation?.phase
return { root, host, phase }
}
it('installs only when asked, with progress, cancel, retry, and a status read from disk', async () => {
const { root, host, phase } = await rig(['hang', 'whole'])
const installation = host()
expect(await installation.status()).toMatchObject({ installedVersion: null, source: null })
const started = installation.install()
await vi.waitFor(async () =>
expect((await installation.status()).operation?.downloadedBytes).toBe(5)
)
expect(installation.install().id).toBe(started.id)
expect(await installation.cancel(started.id)).toMatchObject({ phase: 'cancelled' })
expect(await readdir(root)).toEqual([])
expect(installation.install().id).not.toBe(started.id)
await vi.waitFor(async () => expect(await phase(installation)).toBe('succeeded'))
expect(await host().status()).toMatchObject({
installedVersion: '1.0',
installedCurrent: true,
source: 'managed',
operation: null
})
expect(await readdir(root)).toEqual(['1.0'])
// Whole files without a receipt looped as damaged; Install now rewrites it, with no download.
await rm(join(root, '1.0', RUNTIME_RELEASE_RECEIPT))
expect(await installation.status()).toMatchObject({ damaged: true })
const rewrite = installation.install()
await vi.waitFor(async () =>
expect((await installation.status()).operation).toMatchObject({
id: rewrite.id,
phase: 'succeeded'
})
)
expect(await installation.status()).toMatchObject({ installedCurrent: true })
})
it('publishes an update only once the old releases were reclaimed', async () => {
const { root, host, phase } = await rig(['whole'])
for (const version of ['0.8', '0.9']) {
await mkdir(join(root, version), { recursive: true })
await writeFile(join(root, version, 'server'), 'old server', { mode: 0o755 })
const members = [{ path: 'server', bytes: 10 }]
const receipt = { releaseId: sha256(version), version, members }
await writeFile(
join(root, version, RUNTIME_RELEASE_RECEIPT),
JSON.stringify({ ...receipt, platform: process.platform, arch: process.arch })
)
}
let resume = (): void => {}
const reclaim = vi.mocked(reclaimRuntimeReleases)
const paused = new Promise<void>((resolve) => (resume = resolve))
const { reclaimRuntimeReleases: reclaimNow } = await vi.importActual<typeof ReleaseModule>(
'./managed-runtime-release'
)
reclaim.mockImplementationOnce(async (...args) => paused.then(() => reclaimNow(...args)))
const installation = host()
installation.install()
await new Promise((resolve) => setTimeout(resolve, 100))
expect((await installation.status()).operation).toMatchObject({ downloadedBytes: 0 })
resume()
await vi.waitFor(async () => expect(await phase(installation)).toBe('succeeded'))
expect((await readdir(root)).sort()).toEqual(['0.9', '1.0'])
})
it('repairs a damaged copy when Install is clicked again', async () => {
const { root, host, phase } = await rig(['whole', 'whole'])
const installation = host()
installation.install()
await vi.waitFor(async () => expect(await phase(installation)).toBe('succeeded'))
await writeFile(join(root, '1.0', 'server'), 'cut')
expect(await installation.status()).toMatchObject({
installedVersion: null,
damaged: true,
canRemove: true
})
installation.install()
await vi.waitFor(async () => expect(await phase(installation)).toBe('succeeded'))
const repaired = await installation.status()
expect(repaired).toMatchObject({ installedVersion: '1.0', installedCurrent: true })
expect(repaired.damaged).toBeUndefined()
expect(await readdir(root)).toEqual(['1.0'])
})