Files
goodbuddy/src/agent-daemon/runtime-bundle-registry.test.ts
T

290 lines
7.9 KiB
TypeScript

import {
chmodSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
statSync,
writeFileSync
} from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import type { RemoteRuntimeBundleManifest } from '../shared/remote-runtime-launch-contracts'
import { RuntimeBundleRegistry } from './runtime-bundle-registry'
import type { VerifiedRuntimeBundle } from './runtime-bundle-verifier'
const temporaryPaths: string[] = []
afterEach(() => {
for (const path of temporaryPaths.splice(0)) {
rmSync(path, { recursive: true, force: true })
}
})
describe('Runtime bundle registry', () => {
it('registers and resolves the current signed digest atomically', () => {
const fixture = createRegistry()
const verified = runtimeBundle(fixture, '1.18.29', 'a')
const entry = fixture.registry.register(verified)
expect(entry).toMatchObject({
runtimeId: 'opencode',
runtimeVersion: '1.18.29',
architecture: 'x64',
bundleDigest: verified.manifest.bundleDigest
})
expect(
fixture.registry.resolve(
'opencode',
verified.manifest.bundleDigest,
'x64'
)
).toEqual({
entry,
bundleDirectory: verified.bundleDirectory
})
expect(
new RuntimeBundleRegistry({
runtimeRoot: fixture.runtimeRoot
}).snapshot()
).toEqual(fixture.registry.snapshot())
})
it('reuses an exact bundle and replaces it with another signed identity', () => {
const fixture = createRegistry()
const first = runtimeBundle(fixture, '1.18.29', 'a')
const second = runtimeBundle(fixture, '1.19.0', 'b')
expect(fixture.registry.register(first)).toEqual(
fixture.registry.register(first)
)
fixture.registry.register(second)
expect(fixture.registry.snapshot().current).toEqual([
expect.objectContaining({
runtimeVersion: '1.19.0',
bundleDigest: second.manifest.bundleDigest
})
])
expect(() =>
fixture.registry.resolve(
'opencode',
first.manifest.bundleDigest,
'x64'
)
).toThrow('not the verified current bundle')
})
it('rewrites the previous sequence registry into the minimal format', () => {
const fixture = createRegistry()
writeFileSync(
fixture.storagePath,
`${JSON.stringify({
formatVersion: 1,
minimumTrustedReleaseSequence: 1,
current: [{
runtimeId: 'opencode',
provider: 'opencode',
runtimeVersion: '1.18.29',
releaseSequence: 3,
architecture: 'x64',
signingKeyId: 'test-key',
bundleDigest: `sha256:${'a'.repeat(64)}`,
manifestDigest: `sha256:${'b'.repeat(64)}`,
acpCapabilitiesDigest: `sha256:${'c'.repeat(64)}`
}]
}, null, 2)}\n`,
{ mode: 0o600 }
)
expect(
new RuntimeBundleRegistry({
runtimeRoot: fixture.runtimeRoot
}).snapshot()
).toEqual({
formatVersion: 1,
current: [{
runtimeId: 'opencode',
runtimeVersion: '1.18.29',
architecture: 'x64',
bundleDigest: `sha256:${'a'.repeat(64)}`,
manifestDigest: `sha256:${'b'.repeat(64)}`,
acpCapabilitiesDigest: `sha256:${'c'.repeat(64)}`
}]
})
expect(readFileSync(fixture.storagePath, 'utf8')).not.toContain(
'releaseSequence'
)
})
it('observes activations from another registry instance without no-op writes', () => {
const fixture = createRegistry()
const activator = new RuntimeBundleRegistry({
runtimeRoot: fixture.runtimeRoot
})
const first = runtimeBundle(fixture, '1.18.29', 'a')
const firstEntry = activator.register(first)
expect(fixture.registry.current('x64')).toEqual([firstEntry])
expect(
fixture.registry.resolve(
'opencode',
first.manifest.bundleDigest,
'x64'
)
).toEqual({
entry: firstEntry,
bundleDirectory: first.bundleDirectory
})
const beforeNoOp = fileIdentity(fixture.storagePath)
expect(fixture.registry.register(first)).toEqual(firstEntry)
expect(fileIdentity(fixture.storagePath)).toEqual(beforeNoOp)
const second = runtimeBundle(fixture, '1.19.0', 'b')
const secondEntry = new RuntimeBundleRegistry({
runtimeRoot: fixture.runtimeRoot
}).register(second)
const beforeSecondNoOp = fileIdentity(fixture.storagePath)
expect(fixture.registry.register(second)).toEqual(secondEntry)
expect(fileIdentity(fixture.storagePath)).toEqual(
beforeSecondNoOp
)
expect(fixture.registry.current('x64')).toEqual([secondEntry])
expect(() =>
fixture.registry.resolve(
'opencode',
first.manifest.bundleDigest,
'x64'
)
).toThrow('not the verified current bundle')
})
it('fails closed when the live registry becomes corrupt', () => {
const fixture = createRegistry()
writeFileSync(fixture.storagePath, '{"formatVersion":1}', {
mode: 0o600
})
expect(() => fixture.registry.current('x64')).toThrow(
/corrupt/iu
)
expect(() =>
new RuntimeBundleRegistry({
runtimeRoot: fixture.runtimeRoot
})
).toThrow(/corrupt/iu)
expect(readFileSync(fixture.storagePath, 'utf8')).toContain(
'formatVersion'
)
})
})
function fileIdentity(path: string): {
ino: bigint
mtimeNs: bigint
ctimeNs: bigint
} {
const stat = statSync(path, { bigint: true })
return {
ino: stat.ino,
mtimeNs: stat.mtimeNs,
ctimeNs: stat.ctimeNs
}
}
function createRegistry(): {
registry: RuntimeBundleRegistry
runtimeRoot: string
storagePath: string
} {
const root = resolve(
mkdtempSync(join(tmpdir(), 'goodbuddy-runtime-registry-'))
)
temporaryPaths.push(root)
if (process.platform !== 'win32') {
chmodSync(root, 0o700)
}
const runtimeRoot = resolve(root, 'runtimes')
const storagePath = resolve(runtimeRoot, 'registry.json')
return {
registry: new RuntimeBundleRegistry({ runtimeRoot }),
runtimeRoot,
storagePath
}
}
function runtimeBundle(
fixture: ReturnType<typeof createRegistry>,
runtimeVersion: string,
digestCharacter: string
): VerifiedRuntimeBundle {
const rawBundleDigest = digestCharacter.repeat(64)
const bundleDigest = `sha256:${rawBundleDigest}`
const bundleDirectory =
fixture.registry.bundleDirectory('opencode', bundleDigest)
mkdirSync(bundleDirectory, {
recursive: true,
mode: 0o700
})
const manifest = {
formatVersion: 2,
product: 'GoodBuddy',
runtimeId: 'opencode',
runtimeVersion,
provider: 'opencode',
platform: 'linux',
architecture: 'x64',
signingKeyId: 'runtime-test',
bundleDigest,
adapterDigest: `sha256:${'a'.repeat(64)}`,
sourcePackage: {
name: 'opencode-linux-x64-baseline',
integrity:
'sha512-x4KiJk9EF7ktM18Ru5Jue4kTntxMvlhWb7tHniQGGRvY2KeoK1iIkyAFd7ri5H/fSkM22hNv/Gg1Jk6/h9IlxQ=='
},
entrypoint: {
identity: 'opencode-acp',
path: 'bin/opencode',
sha256: 'c'.repeat(64),
argvPrefix: ['acp']
},
files: [
{
path: 'bin/opencode',
size: 64,
sha256: 'c'.repeat(64),
mode: '0755'
},
{
path: 'licenses/opencode.txt',
size: 4,
sha256: 'd'.repeat(64),
mode: '0644'
}
],
licenses: [
{
package: 'opencode-ai',
version: runtimeVersion,
spdx: 'MIT',
path: 'licenses/opencode.txt'
}
],
allowedEnvironmentNames: ['HOME'],
protocol: { major: 1, minor: 0 },
acpCapabilitiesDigest: `sha256:${'e'.repeat(64)}`,
limits: {
maximumPromptRuntimeMilliseconds: 60_000,
maximumPromptInputBytes: 4096,
maximumPromptOutputBytes: 1024 * 1024
}
} as const satisfies RemoteRuntimeBundleManifest
return {
bundleDirectory,
executablePath: resolve(bundleDirectory, 'bin', 'opencode'),
manifest,
manifestDigest: `sha256:${rawBundleDigest}`
}
}