Files
goodbuddy/tests/agent-package.test.ts
T

2403 lines
67 KiB
TypeScript

import {
createHash,
createPrivateKey,
sign,
type KeyObject
} from 'node:crypto'
import { execFileSync } from 'node:child_process'
import {
chmodSync,
lstatSync,
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
renameSync,
rmSync,
symlinkSync,
writeFileSync
} from 'node:fs'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { basename, dirname, join, resolve } from 'node:path'
import { c as createTar } from 'tar'
import { unzipSync, zipSync } from 'fflate'
import {
afterAll,
beforeAll,
describe,
expect,
it,
vi
} from 'vitest'
import {
agentPackageCatalogSchema,
agentPackageDescriptorSchema,
agentPackageArchiveName,
type AgentPackageCatalog,
type AgentPackageCatalogEntry,
type AgentPackageDescriptor
} from '../src/shared/agent-package-contracts'
import type {
AgentReleaseKeyRegistry,
AgentRuntimeLock
} from '../src/shared/agent-installation-contracts'
import type {
RemoteRuntimeLock
} from '../src/shared/remote-runtime-launch-contracts'
import {
compareSemanticVersions,
extractAndVerifyAgentPackage
} from '../src/main/remote-agent/agent-package-verifier'
import {
AgentPackageManager,
selectLatestCompatibleEntry
} from '../src/main/remote-agent/agent-package-manager'
type AgentBundleModule = {
canonicalManifestBytes(manifest: unknown): Buffer
createManifest(
directory: string,
metadata: Record<string, unknown>
): AgentPackageDescriptor
publicKeySpkiBase64(key: KeyObject): string
signManifestForTest(bytes: Buffer, key: KeyObject): Buffer
}
type RuntimeBundleModule = {
buildRuntimeBundle(options: {
projectRoot: string
architecture: 'x64' | 'arm64'
runtimeArchive: string
outputRoot: string
lock: RemoteRuntimeLock
registry: AgentReleaseKeyRegistry
testSigningIdentity: {
keyId: string
privateKey: KeyObject
}
enforceFilesystemMode: false
}): {
bundleDirectory: string
}
}
type AgentPackageModule = {
assembleAgentPackage(options: {
projectRoot: string
architecture: 'x64' | 'arm64'
minimumDesktopVersion: string
output: string
agentBundle: string
runtimeBundle: string
agentLock: AgentRuntimeLock
runtimeLock: RemoteRuntimeLock
registry: AgentReleaseKeyRegistry
testSigningIdentity: {
keyId: string
privateKey: KeyObject
}
}): {
descriptor: AgentPackageDescriptor
archive: string
size: number
sha256: string
}
descriptorBytes(descriptor: AgentPackageDescriptor): Buffer
descriptorContentDigest(
descriptor: AgentPackageDescriptor
): string
signatureDomain: Buffer
}
type AgentCatalogModule = {
readPackageMetadata(
archivePath: string,
registry: AgentReleaseKeyRegistry
): {
descriptor: AgentPackageDescriptor
size: number
sha256: string
}
createCatalog(options: {
projectRoot: string
x64Package: string
arm64Package: string
previousCatalog?: string
previousSignature?: string
outputCatalog: string
outputSignature: string
generatedAt: string
registry: AgentReleaseKeyRegistry
signingIdentity: {
keyId: string
privateKey: KeyObject
}
}): {
version: string
catalog: string
signature: string
entries: number
}
readVerifiedCatalog(
catalogPath: string,
signaturePath: string,
registry: AgentReleaseKeyRegistry
): AgentPackageCatalog
}
type PackageInstallerModule = {
preparePackage(options: {
operationRoot: string
archive: string
expectedSha256: string
homeDirectory: string
emit?: (event: unknown) => void
}): {
type: 'result'
command: 'prepare'
status: 'prepared'
archiveSha256: string
agent: {
installationId: string
agentVersion: string
manifestSha256: string
binaryDigest: string
platform: 'linux'
architecture: 'x64' | 'arm64'
protocol: { major: number; minor: number }
supervisor: 'detached-on-demand'
}
runtime: {
runtimeId: 'opencode'
runtimeVersion: string
bundleDigest: string
manifestDigest: string
runtimeAdapterDigest: string
acpCapabilitiesDigest: string
platform: 'linux'
architecture: 'x64' | 'arm64'
protocol: { major: number; minor: number }
}
}
commitPackage(options: {
operationRoot: string
archive: string
expectedSha256: string
homeDirectory: string
emit?: (event: unknown) => void
}): {
command: 'commit'
status: 'committed'
agent: PackageInstallerModule[
'preparePackage'
] extends (...args: never[]) => infer Result
? Result extends { agent: infer Agent }
? Agent
: never
: never
runtime: PackageInstallerModule[
'preparePackage'
] extends (...args: never[]) => infer Result
? Result extends { runtime: infer Runtime }
? Runtime
: never
: never
}
}
const require = createRequire(import.meta.url)
const agentBundle = require(
'../build/agent-bundle.cjs'
) as AgentBundleModule
const runtimeBundle = require(
'../build/remote-runtime-bundle.cjs'
) as RuntimeBundleModule
const agentPackage = require(
'../build/agent-package.cjs'
) as AgentPackageModule
const agentCatalog = require(
'../build/agent-catalog.cjs'
) as AgentCatalogModule
const catalogSignatureDomain = Buffer.from(
'GoodBuddy Agent Package Catalog Signature v1\0',
'utf8'
)
let temporaryRoot = ''
let privateKey: KeyObject
let registry: AgentReleaseKeyRegistry
let agentLock: AgentRuntimeLock
let remoteRuntimeLock: RemoteRuntimeLock
let packagePath = ''
let arm64PackagePath = ''
let packageResult: ReturnType<
AgentPackageModule['assembleAgentPackage']
>
let arm64PackageResult: ReturnType<
AgentPackageModule['assembleAgentPackage']
>
let packageInstaller: PackageInstallerModule
let installerBundle = ''
beforeAll(() => {
temporaryRoot = resolve(
mkdtempSync(join(tmpdir(), 'goodbuddy-agent-package-test-'))
)
installerBundle = join(
temporaryRoot,
'package-installer-fixture.cjs'
)
execFileSync(process.execPath, [
'-e',
[
"require('esbuild').buildSync({",
`entryPoints:[${JSON.stringify(
join(
process.cwd(),
'src',
'main',
'remote-agent',
'control-plane-package-installer.ts'
)
)}],`,
`outfile:${JSON.stringify(installerBundle)},`,
"bundle:true,platform:'node',target:['node24.19'],",
"format:'cjs',logLevel:'silent'})"
].join('')
])
packageInstaller = require(installerBundle) as PackageInstallerModule
privateKey = createPrivateKey({
key: Buffer.from(
'302e020100300506032b657004220420404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f',
'hex'
),
format: 'der',
type: 'pkcs8'
})
registry = {
formatVersion: 1,
keys: [{
keyId: 'agent-package-fixture',
publicKeySpkiBase64:
agentBundle.publicKeySpkiBase64(privateKey),
environment: 'production'
}],
revocations: []
}
agentLock = JSON.parse(
readFileSync(
join(process.cwd(), 'agent-runtime-lock.json'),
'utf8'
)
) as AgentRuntimeLock
const agentDirectory = join(temporaryRoot, 'agent')
const arm64AgentDirectory = join(temporaryRoot, 'agent-arm64')
writeAgentBundle(agentDirectory, 'x64')
writeAgentBundle(arm64AgentDirectory, 'arm64')
const runtimeInput = createRuntimeInput('x64')
const arm64RuntimeInput = createRuntimeInput('arm64')
remoteRuntimeLock = createRemoteRuntimeLock(
runtimeInput,
arm64RuntimeInput
)
const buildRuntime = (
architecture: 'x64' | 'arm64',
archive: string
) => runtimeBundle.buildRuntimeBundle({
projectRoot: process.cwd(),
architecture,
runtimeArchive: archive,
outputRoot: join(temporaryRoot, 'runtime'),
lock: remoteRuntimeLock,
registry,
testSigningIdentity: {
keyId: 'agent-package-fixture',
privateKey
},
enforceFilesystemMode: false
})
const builtRuntime = buildRuntime('x64', runtimeInput)
const builtArm64Runtime = buildRuntime(
'arm64',
arm64RuntimeInput
)
packagePath = join(
temporaryRoot,
agentPackageArchiveName(agentLock.agentVersion, 'x64')
)
packageResult = agentPackage.assembleAgentPackage({
projectRoot: process.cwd(),
architecture: 'x64',
minimumDesktopVersion: '0.11.0',
output: packagePath,
agentBundle: agentDirectory,
runtimeBundle: builtRuntime.bundleDirectory,
agentLock,
runtimeLock: remoteRuntimeLock,
registry,
testSigningIdentity: {
keyId: 'agent-package-fixture',
privateKey
}
})
arm64PackagePath = join(
temporaryRoot,
agentPackageArchiveName(agentLock.agentVersion, 'arm64')
)
arm64PackageResult = agentPackage.assembleAgentPackage({
projectRoot: process.cwd(),
architecture: 'arm64',
minimumDesktopVersion: '0.11.0',
output: arm64PackagePath,
agentBundle: arm64AgentDirectory,
runtimeBundle: builtArm64Runtime.bundleDirectory,
agentLock,
runtimeLock: remoteRuntimeLock,
registry,
testSigningIdentity: {
keyId: 'agent-package-fixture',
privateKey
}
})
})
afterAll(() => {
rmSync(temporaryRoot, { recursive: true, force: true })
})
describe('compound Agent packages', () => {
it('assembles deterministic signed archives with private extraction', async () => {
const secondRoot = join(temporaryRoot, 'second')
mkdirSync(secondRoot)
const secondPath = join(
secondRoot,
agentPackageArchiveName(agentLock.agentVersion, 'x64')
)
const archive = unzipSync(
new Uint8Array(readFileSync(packagePath))
)
expect(Object.keys(archive).sort()).toEqual(
expect.arrayContaining([
'agent-package.json',
'agent-package.sig',
'agent-release-keys.json',
'agent-runtime-lock.json',
'remote-runtime-lock.json'
])
)
expect(packageResult.descriptor.files).toContainEqual(
expect.objectContaining({
path: 'agent/helpers/fixture-helper',
mode: '0755'
})
)
for (const [result, archivePath] of [
[packageResult, packagePath],
[arm64PackageResult, arm64PackagePath]
] as const) {
expect(result.descriptor.files).toContainEqual(
expect.objectContaining({
path: 'agent/node',
mode: '0755'
})
)
expect(result.descriptor.files).not.toContainEqual(
expect.objectContaining({
path: 'agent/lib/package-installer.cjs'
})
)
expect(
Object.keys(
unzipSync(new Uint8Array(readFileSync(archivePath)))
)
).not.toContain('agent/lib/package-installer.cjs')
}
const contentRoot = join(temporaryRoot, 'verified')
const verified = await extractAndVerifyAgentPackage({
archivePath: packagePath,
destinationDirectory: contentRoot,
architecture: 'x64',
desktopVersion: '0.11.0',
trustedRegistry: registry
})
expect(verified).toMatchObject({
descriptor: {
version: agentLock.agentVersion,
architecture: 'x64',
remoteRuntime: {
version:
remoteRuntimeLock.runtimes.opencode.version
}
}
})
if (process.platform !== 'win32') {
for (const directory of [verified.rootDirectory]) {
expect(lstatSync(directory).mode & 0o777).toBe(0o700)
}
}
const second = agentPackage.assembleAgentPackage({
projectRoot: process.cwd(),
architecture: 'x64',
minimumDesktopVersion: '0.11.0',
output: secondPath,
agentBundle: join(temporaryRoot, 'agent'),
runtimeBundle: findRuntimeBundleDirectory(),
agentLock,
runtimeLock: remoteRuntimeLock,
registry,
testSigningIdentity: {
keyId: 'agent-package-fixture',
privateKey
}
})
expect(second.archive).toBe(
agentPackageArchiveName(agentLock.agentVersion, 'x64')
)
expect(readFileSync(secondPath)).toEqual(
readFileSync(packagePath)
)
})
it('prepares and commits a format-v1 package without an embedded installer', () => {
const operationRoot = join(temporaryRoot, 'remote-operation')
const homeDirectory = join(temporaryRoot, 'remote-home')
const globalMetadata = seedGlobalManagedMetadata(
homeDirectory,
'a'
)
const events: unknown[] = []
const prepared = packageInstaller.preparePackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory,
emit: (event) => events.push(event)
})
expect(prepared).toMatchObject({
type: 'result',
command: 'prepare',
status: 'prepared',
archiveSha256: packageResult.sha256,
agent: {
installationId: expect.stringMatching(/^agent-[a-f0-9]{64}$/u),
agentVersion: agentLock.agentVersion,
architecture: 'x64',
protocol: agentLock.protocol
},
runtime: {
runtimeId: 'opencode',
runtimeVersion:
remoteRuntimeLock.runtimes.opencode.version,
bundleDigest:
packageResult.descriptor.remoteRuntime.bundleDigest,
architecture: 'x64'
}
})
expect(events.at(-1)).toEqual(prepared)
expect(readdirSync(join(operationRoot, 'prepared')))
.toEqual([
'agent-package.json',
'agent-package.sig',
'agent-release-keys.json',
'agent-runtime-lock.json',
'payload',
'remote-runtime-lock.json',
'result.json'
])
expectGlobalMetadataUnchanged(globalMetadata)
const cliOperationRoot = join(temporaryRoot, 'remote-cli-operation')
const cliLines = execFileSync(
process.execPath,
[
installerBundle,
'prepare',
'--operation-root',
cliOperationRoot,
'--archive',
packagePath,
'--expected-sha256',
packageResult.sha256
],
{ encoding: 'utf8' }
)
.trim()
.split('\n')
.map((line) => JSON.parse(line) as Record<string, unknown>)
expect(cliLines[0]).toMatchObject({
type: 'progress',
command: 'prepare',
phase: 'validating'
})
expect(cliLines.at(-1)).toMatchObject({
type: 'result',
command: 'prepare',
status: 'prepared',
agent: prepared.agent,
runtime: prepared.runtime
})
const commitEvents: Array<Record<string, unknown>> = []
const committed = packageInstaller.commitPackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory,
emit: (event) => {
commitEvents.push(event as Record<string, unknown>)
}
})
expect(committed).toMatchObject({
command: 'commit',
status: 'committed',
agent: prepared.agent,
runtime: prepared.runtime
})
const agentDestination = join(
homeDirectory,
'.goodbuddy',
'agent',
'installations',
prepared.agent.installationId
)
const runtimeDestination = join(
homeDirectory,
'.goodbuddy',
'runtimes',
'opencode',
prepared.runtime.bundleDigest.slice('sha256:'.length)
)
expect(lstatSync(join(agentDestination, 'lib', 'agent.cjs')).isFile())
.toBe(true)
expect(lstatSync(join(runtimeDestination, 'bin', 'opencode')).isFile())
.toBe(true)
expect(
commitEvents.filter(
(event) =>
event.type === 'progress' &&
event.phase === 'publishing-content'
)
).toHaveLength(1)
expect(readdirSync(join(operationRoot, 'prepared')))
.toEqual([
'agent-package.json',
'agent-package.sig',
'agent-release-keys.json',
'agent-runtime-lock.json',
'payload',
'remote-runtime-lock.json',
'result.json'
])
expect(readdirSync(operationRoot)).toEqual(['prepared'])
expectRegistriesUnchangedAndPackageTrustPublished(
globalMetadata
)
packageInstaller.preparePackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
expect(() =>
packageInstaller.commitPackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
).not.toThrow()
expectRegistriesUnchangedAndPackageTrustPublished(
globalMetadata
)
})
it('resumes commit after an Agent directory was already published', () => {
const operationRoot = join(
temporaryRoot,
'partial-commit-operation'
)
const homeDirectory = join(
temporaryRoot,
'partial-commit-home'
)
const prepared = packageInstaller.preparePackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
const agentSource = join(
operationRoot,
'prepared',
'payload',
'agent'
)
const agentDestination = join(
homeDirectory,
'.goodbuddy',
'agent',
'installations',
prepared.agent.installationId
)
mkdirSync(dirname(agentDestination), {
recursive: true,
mode: 0o700
})
renameSync(agentSource, agentDestination)
expect(
packageInstaller.commitPackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
).toMatchObject({
status: 'committed',
agent: prepared.agent,
runtime: prepared.runtime
})
expect(
lstatSync(join(agentDestination, 'goodbuddy-agent'))
.isFile()
).toBe(true)
expect(
lstatSync(
join(
homeDirectory,
'.goodbuddy',
'runtimes',
'opencode',
prepared.runtime.bundleDigest.slice(
'sha256:'.length
),
'bin',
'opencode'
)
).isFile()
).toBe(true)
})
it('requires untampered prepared state before commit mutation', () => {
const homeDirectory = join(temporaryRoot, 'prepared-state-home')
const tamperedOperation = join(
temporaryRoot,
'tampered-prepared-state-operation'
)
packageInstaller.preparePackage({
operationRoot: tamperedOperation,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
const statePath = join(tamperedOperation, 'prepared', 'result.json')
const state = JSON.parse(
readFileSync(statePath, 'utf8')
) as Record<string, unknown>
const stateAgent = state.agent as Record<string, unknown>
stateAgent.agentVersion = '99.0.0'
writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`)
expect(() =>
packageInstaller.commitPackage({
operationRoot: tamperedOperation,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
).toThrow('does not match the authenticated archive')
expect(readdirSync(tamperedOperation)).toEqual(['prepared'])
const missingOperation = join(
temporaryRoot,
'missing-prepared-state-operation'
)
packageInstaller.preparePackage({
operationRoot: missingOperation,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
rmSync(join(missingOperation, 'prepared', 'result.json'))
expect(() =>
packageInstaller.commitPackage({
operationRoot: missingOperation,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
).toThrow()
expect(readdirSync(missingOperation)).toEqual(['prepared'])
})
it('preserves current registries when commit fails', () => {
const operationRoot = join(temporaryRoot, 'failed-commit-operation')
const homeDirectory = join(temporaryRoot, 'failed-commit-home')
const globalMetadata = seedGlobalManagedMetadata(
homeDirectory,
'e'
)
const prepared = packageInstaller.preparePackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
const conflictingAgentDestination = join(
homeDirectory,
'.goodbuddy',
'agent',
'installations',
prepared.agent.installationId
)
mkdirSync(dirname(conflictingAgentDestination), {
recursive: true,
mode: 0o700
})
writeFileSync(
conflictingAgentDestination,
'different'
)
expect(() =>
packageInstaller.commitPackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
).toThrow('conflicting content')
expectGlobalMetadataUnchanged(globalMetadata)
})
it.runIf(process.platform !== 'win32')(
'rejects symbolic-link ancestors before publishing managed payloads',
() => {
const cases = [
{
name: 'managed-root',
link(homeDirectory: string, linkTarget: string) {
symlinkSync(
linkTarget,
join(homeDirectory, '.goodbuddy')
)
}
},
{
name: 'agent-installations',
link(homeDirectory: string, linkTarget: string) {
const agentRoot = join(
homeDirectory,
'.goodbuddy',
'agent'
)
mkdirSync(agentRoot, {
recursive: true,
mode: 0o700
})
symlinkSync(
linkTarget,
join(agentRoot, 'installations')
)
}
},
{
name: 'runtime-root',
link(homeDirectory: string, linkTarget: string) {
const runtimes = join(
homeDirectory,
'.goodbuddy',
'runtimes'
)
mkdirSync(runtimes, {
recursive: true,
mode: 0o700
})
symlinkSync(
linkTarget,
join(runtimes, 'opencode')
)
}
}
]
for (const fixture of cases) {
const operationRoot = join(
temporaryRoot,
`symlink-${fixture.name}-operation`
)
const homeDirectory = join(
temporaryRoot,
`symlink-${fixture.name}-home`
)
const linkTarget = join(
temporaryRoot,
`symlink-${fixture.name}-target`
)
mkdirSync(homeDirectory, { mode: 0o700 })
mkdirSync(linkTarget, { mode: 0o700 })
fixture.link(homeDirectory, linkTarget)
packageInstaller.preparePackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
expect(() =>
packageInstaller.commitPackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
).toThrow('Managed installation directory is unsafe')
expect(readdirSync(linkTarget)).toEqual([])
}
}
)
it('replaces a conflicting managed digest directory during explicit repair', () => {
const operationRoot = join(temporaryRoot, 'repair-commit-operation')
const homeDirectory = join(temporaryRoot, 'repair-commit-home')
const globalMetadata = seedGlobalManagedMetadata(
homeDirectory,
'f'
)
const prepared = packageInstaller.preparePackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
const agentDestination = join(
homeDirectory,
'.goodbuddy',
'agent',
'installations',
prepared.agent.installationId
)
mkdirSync(agentDestination, { recursive: true, mode: 0o700 })
writeFileSync(
join(agentDestination, 'conflicting-content'),
'different'
)
expect(
packageInstaller.commitPackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
).toMatchObject({
status: 'committed',
agent: prepared.agent,
runtime: prepared.runtime
})
expect(
readdirSync(agentDestination)
).not.toContain('conflicting-content')
expect(
readFileSync(join(agentDestination, 'manifest.json'))
).toBeTruthy()
expectRegistriesUnchangedAndPackageTrustPublished(
globalMetadata
)
})
it('restores a replaced Agent directory when Runtime publication fails', () => {
const operationRoot = join(temporaryRoot, 'repair-rollback-operation')
const homeDirectory = join(temporaryRoot, 'repair-rollback-home')
const prepared = packageInstaller.preparePackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
const agentDestination = join(
homeDirectory,
'.goodbuddy',
'agent',
'installations',
prepared.agent.installationId
)
mkdirSync(agentDestination, { recursive: true, mode: 0o700 })
writeFileSync(join(agentDestination, 'old-marker'), 'old')
const runtimeDestination = join(
homeDirectory,
'.goodbuddy',
'runtimes',
'opencode',
prepared.runtime.bundleDigest.slice('sha256:'.length)
)
mkdirSync(dirname(runtimeDestination), {
recursive: true,
mode: 0o700
})
writeFileSync(runtimeDestination, 'unsafe destination type')
expect(() =>
packageInstaller.commitPackage({
operationRoot,
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
).toThrow('conflicting content')
expect(readdirSync(agentDestination)).toEqual(['old-marker'])
})
it('publishes each Agent payload independently of existing installations', () => {
const variantAgent = join(temporaryRoot, 'reuse-variant-agent')
writeAgentBundle(variantAgent, 'x64')
writeFileSync(
join(variantAgent, 'lib', 'agent.cjs'),
'module.exports={variant:true}\n'
)
signAgentBundleManifest(variantAgent, 'x64')
const variantPath = join(
temporaryRoot,
'reuse-variant',
agentPackageArchiveName(agentLock.agentVersion, 'x64')
)
mkdirSync(dirname(variantPath), { recursive: true })
const variant = agentPackage.assembleAgentPackage({
projectRoot: process.cwd(),
architecture: 'x64',
minimumDesktopVersion: '0.11.0',
output: variantPath,
agentBundle: variantAgent,
runtimeBundle: findRuntimeBundleDirectory(),
agentLock,
runtimeLock: remoteRuntimeLock,
registry,
testSigningIdentity: {
keyId: 'agent-package-fixture',
privateKey
}
})
const changedNodeAgent = join(temporaryRoot, 'reuse-changed-node-agent')
writeAgentBundle(changedNodeAgent, 'x64')
writeFileSync(
join(changedNodeAgent, 'node'),
Buffer.concat([elf('x64'), Buffer.from('different Node payload')])
)
chmodSync(join(changedNodeAgent, 'node'), 0o755)
signAgentBundleManifest(changedNodeAgent, 'x64')
const changedNodePath = join(
temporaryRoot,
'reuse-changed-node',
agentPackageArchiveName(agentLock.agentVersion, 'x64')
)
mkdirSync(dirname(changedNodePath), { recursive: true })
const changedNodeVariant = agentPackage.assembleAgentPackage({
projectRoot: process.cwd(),
architecture: 'x64',
minimumDesktopVersion: '0.11.0',
output: changedNodePath,
agentBundle: changedNodeAgent,
runtimeBundle: findRuntimeBundleDirectory(),
agentLock,
runtimeLock: remoteRuntimeLock,
registry,
testSigningIdentity: {
keyId: 'agent-package-fixture',
privateKey
}
})
const prepareAndCommit = (
options: Parameters<PackageInstallerModule['preparePackage']>[0]
) => {
packageInstaller.preparePackage(options)
return packageInstaller.commitPackage(options)
}
const installCurrent = (
name: string
): {
homeDirectory: string
currentNode: string
globalMetadata: Map<string, Buffer>
} => {
const homeDirectory = join(temporaryRoot, `reuse-${name}-home`)
const globalMetadata = seedGlobalManagedMetadata(homeDirectory, 'b')
const current = prepareAndCommit({
operationRoot: join(temporaryRoot, `reuse-${name}-current-operation`),
archive: packagePath,
expectedSha256: packageResult.sha256,
homeDirectory
})
const registryPath = join(
homeDirectory,
'.goodbuddy',
'agent',
'registry.json'
)
const registryBytes = Buffer.from(
`${JSON.stringify({
formatVersion: 1,
current: {
installationId: current.agent.installationId,
agentVersion: current.agent.agentVersion,
manifestSha256: current.agent.manifestSha256,
arch: current.agent.architecture
}
}, null, 2)}\n`
)
writeFileSync(registryPath, registryBytes)
globalMetadata.set(registryPath, registryBytes)
return {
homeDirectory,
currentNode: join(
homeDirectory,
'.goodbuddy',
'agent',
'installations',
current.agent.installationId,
'node'
),
globalMetadata
}
}
const commitVariant = (name: string, homeDirectory: string) =>
prepareAndCommit({
operationRoot: join(temporaryRoot, `reuse-${name}-variant-operation`),
archive: variantPath,
expectedSha256: variant.sha256,
homeDirectory
})
const installedVariantNode = (
homeDirectory: string,
installationId: string
) => join(
homeDirectory,
'.goodbuddy',
'agent',
'installations',
installationId,
'node'
)
const identical = installCurrent('identical')
const identicalResult = commitVariant(
'identical',
identical.homeDirectory
)
expect(
lstatSync(identical.currentNode).ino
).not.toBe(
lstatSync(
installedVariantNode(
identical.homeDirectory,
identicalResult.agent.installationId
)
).ino
)
expectRegistriesUnchangedAndPackageTrustPublished(
identical.globalMetadata
)
const changed = installCurrent('changed')
const changedResult = prepareAndCommit({
operationRoot: join(
temporaryRoot,
'reuse-changed-variant-operation'
),
archive: changedNodePath,
expectedSha256: changedNodeVariant.sha256,
homeDirectory: changed.homeDirectory
})
expect(
lstatSync(changed.currentNode).ino
).not.toBe(
lstatSync(
installedVariantNode(
changed.homeDirectory,
changedResult.agent.installationId
)
).ino
)
expectRegistriesUnchangedAndPackageTrustPublished(
changed.globalMetadata
)
const corrupt = installCurrent('corrupt')
writeFileSync(corrupt.currentNode, Buffer.from('corrupt Node'))
const corruptResult = commitVariant('corrupt', corrupt.homeDirectory)
expect(
lstatSync(corrupt.currentNode).ino
).not.toBe(
lstatSync(
installedVariantNode(
corrupt.homeDirectory,
corruptResult.agent.installationId
)
).ino
)
expectRegistriesUnchangedAndPackageTrustPublished(
corrupt.globalMetadata
)
const untrusted = installCurrent('untrusted')
const currentRoot = dirname(untrusted.currentNode)
const signaturePath = join(currentRoot, 'manifest.sig')
const signature = readFileSync(signaturePath)
signature[0] = signature[0] === 65 ? 66 : 65
writeFileSync(signaturePath, signature)
const untrustedResult = commitVariant('untrusted', untrusted.homeDirectory)
expect(
lstatSync(untrusted.currentNode).ino
).not.toBe(
lstatSync(
installedVariantNode(
untrusted.homeDirectory,
untrustedResult.agent.installationId
)
).ino
)
expectRegistriesUnchangedAndPackageTrustPublished(
untrusted.globalMetadata
)
})
it('fails the remote installer closed on traversal, duplicates, corruption, and hash mismatch', () => {
const run = (archive: string, expectedSha256?: string) =>
packageInstaller.preparePackage({
operationRoot: join(
temporaryRoot,
`rejected-${basename(archive)}`
),
archive,
expectedSha256:
expectedSha256 ??
createHash('sha256').update(readFileSync(archive)).digest('hex'),
homeDirectory: join(temporaryRoot, 'rejected-home')
})
const traversal = join(temporaryRoot, 'installer-traversal.gbagent')
writeFileSync(
traversal,
renameStoredZipEntry(
readFileSync(packagePath),
'agent/node',
'../outside'
)
)
expect(() => run(traversal)).toThrow(/unsafe|unordered/u)
const duplicate = join(temporaryRoot, 'installer-duplicate.gbagent')
const names = Object.keys(
unzipSync(new Uint8Array(readFileSync(packagePath)))
)
const duplicatePair = names
.flatMap((left) =>
names.map((right) => [left, right] as const)
)
.find(
([left, right]) =>
left !== right && Buffer.byteLength(left) === Buffer.byteLength(right)
)
expect(duplicatePair).toBeDefined()
writeFileSync(
duplicate,
renameStoredZipEntry(
readFileSync(packagePath),
duplicatePair![0],
duplicatePair![1]
)
)
expect(() => run(duplicate)).toThrow(/duplicate|unordered/u)
const corrupt = join(temporaryRoot, 'installer-corrupt.gbagent')
const corruptBytes = corruptStoredZipEntry(
readFileSync(packagePath),
'agent/lib/agent.cjs'
)
writeFileSync(corrupt, corruptBytes)
expect(() => run(corrupt)).toThrow(/checksum|digest|header/u)
expect(() =>
run(packagePath, '0'.repeat(64))
).toThrow('SHA-256 does not match')
})
it('rejects traversal, undeclared payload, and untrusted outer signatures', async () => {
const traversal = join(temporaryRoot, 'traversal.gbagent')
writeFileSync(
traversal,
Buffer.from(
zipSync({
'../escape': new Uint8Array([1])
})
)
)
await expect(
extractAndVerifyAgentPackage({
archivePath: traversal,
destinationDirectory: join(
temporaryRoot,
'traversal-output'
),
desktopVersion: '0.11.0',
trustedRegistry: registry
})
).rejects.toThrow('unsafe')
const files = unzipSync(
new Uint8Array(readFileSync(packagePath))
)
files['undeclared.txt'] = new Uint8Array([1])
const undeclared = join(temporaryRoot, 'undeclared.gbagent')
writeFileSync(undeclared, Buffer.from(zipSync(files)))
await expect(
extractAndVerifyAgentPackage({
archivePath: undeclared,
destinationDirectory: join(
temporaryRoot,
'undeclared-output'
),
desktopVersion: '0.11.0',
trustedRegistry: registry
})
).rejects.toThrow('undeclared or missing')
await expect(
extractAndVerifyAgentPackage({
archivePath: packagePath,
destinationDirectory: join(
temporaryRoot,
'untrusted-output'
),
desktopVersion: '0.11.0',
trustedRegistry: {
formatVersion: 1,
keys: [],
revocations: []
}
})
).rejects.toThrow('signing key is not trusted')
})
it('enforces desktop compatibility and descriptor-to-inner-bundle identity', async () => {
await expect(
extractAndVerifyAgentPackage({
archivePath: packagePath,
destinationDirectory: join(
temporaryRoot,
'old-desktop-output'
),
desktopVersion: '0.10.9',
trustedRegistry: registry
})
).rejects.toThrow('not compatible')
const files = unzipSync(
new Uint8Array(readFileSync(packagePath))
)
const descriptor = agentPackageDescriptorSchema.parse(
JSON.parse(
Buffer.from(files['agent-package.json']!).toString('utf8')
)
)
const mismatched = {
...descriptor,
version:
descriptor.version === '999.0.0'
? '999.0.1'
: '999.0.0'
}
mismatched.contentDigest =
agentPackage.descriptorContentDigest(mismatched)
const descriptorBytes =
agentPackage.descriptorBytes(mismatched)
files['agent-package.json'] = new Uint8Array(descriptorBytes)
files['agent-package.sig'] = new Uint8Array(
Buffer.from(
`${sign(
null,
Buffer.concat([
agentPackage.signatureDomain,
descriptorBytes
]),
privateKey
).toString('base64')}\n`
)
)
const mismatchPath = join(temporaryRoot, 'mismatch.gbagent')
writeFileSync(mismatchPath, Buffer.from(zipSync(files)))
await expect(
extractAndVerifyAgentPackage({
archivePath: mismatchPath,
destinationDirectory: join(
temporaryRoot,
'mismatch-output'
),
desktopVersion: '0.11.0',
trustedRegistry: registry
})
).rejects.toThrow('does not match its Agent bundle')
})
it('accepts a signed package whose key registry uses CRLF', async () => {
const files = unzipSync(
new Uint8Array(readFileSync(packagePath))
)
const registryBytes = Buffer.from(
Buffer.from(files['agent-release-keys.json']!)
.toString('utf8')
.replace(/\n/gu, '\r\n')
)
files['agent-release-keys.json'] =
new Uint8Array(registryBytes)
const descriptor = agentPackageDescriptorSchema.parse(
JSON.parse(
Buffer.from(files['agent-package.json']!).toString('utf8')
)
)
const updatedDescriptor = {
...descriptor,
files: descriptor.files.map((file) =>
file.path === 'agent-release-keys.json'
? {
...file,
size: registryBytes.byteLength,
sha256: createHash('sha256')
.update(registryBytes)
.digest('hex')
}
: file
)
}
updatedDescriptor.contentDigest =
agentPackage.descriptorContentDigest(updatedDescriptor)
const descriptorBytes =
agentPackage.descriptorBytes(updatedDescriptor)
files['agent-package.json'] = new Uint8Array(descriptorBytes)
files['agent-package.sig'] = new Uint8Array(
Buffer.from(
`${sign(
null,
Buffer.concat([
agentPackage.signatureDomain,
descriptorBytes
]),
privateKey
).toString('base64')}\n`
)
)
const crlfPath = join(
temporaryRoot,
'crlf-registry.gbagent'
)
writeFileSync(crlfPath, Buffer.from(zipSync(files)))
await expect(
extractAndVerifyAgentPackage({
archivePath: crlfPath,
destinationDirectory: join(
temporaryRoot,
'crlf-registry-output'
),
desktopVersion: '0.11.0',
trustedRegistry: registry
})
).resolves.toMatchObject({
descriptor: { architecture: 'x64' }
})
})
it('orders SemVer prereleases and selects the latest compatible protocol', () => {
expect(compareSemanticVersions('1.0.0-e2e.10', '1.0.0-e2e.9'))
.toBeGreaterThan(0)
expect(compareSemanticVersions('1.0.0', '1.0.0-rc.1'))
.toBeGreaterThan(0)
expect(compareSemanticVersions('1.0.0-1', '1.0.0-alpha'))
.toBeLessThan(0)
const compatible = catalogEntry(packageResult.descriptor, {
version: '1.1.0',
minimumDesktopVersion: '0.11.0'
})
const tooNew = catalogEntry(packageResult.descriptor, {
version: '2.0.0',
minimumDesktopVersion: '0.12.0'
})
const incompatibleProtocol = catalogEntry(
packageResult.descriptor,
{
version: '3.0.0',
minimumDesktopVersion: '0.11.0',
agentProtocol: {
...packageResult.descriptor.agentProtocol,
major:
packageResult.descriptor.agentProtocol.major + 1
}
}
)
const catalog = agentPackageCatalogSchema.parse({
formatVersion: 1,
product: 'GoodBuddy',
component: 'agent',
signingKeyId: 'agent-package-fixture',
generatedAt: '2026-08-24T00:00:00.000Z',
entries: [tooNew, incompatibleProtocol, compatible]
})
expect(
selectLatestCompatibleEntry(catalog, 'x64', '0.11.0')
.version
).toBe('1.1.0')
})
it('builds and verifies a cumulative immutable dual-architecture catalog', () => {
const firstRoot = join(temporaryRoot, 'catalog-first')
const secondRoot = join(temporaryRoot, 'catalog-second')
mkdirSync(firstRoot)
mkdirSync(secondRoot)
const firstCatalog = join(firstRoot, 'agent-catalog.json')
const firstSignature = join(firstRoot, 'agent-catalog.sig')
const result = agentCatalog.createCatalog({
projectRoot: process.cwd(),
x64Package: packagePath,
arm64Package: arm64PackagePath,
outputCatalog: firstCatalog,
outputSignature: firstSignature,
generatedAt: '2026-08-24T00:00:00.000Z',
registry,
signingIdentity: {
keyId: 'agent-package-fixture',
privateKey
}
})
expect(result).toEqual({
version: agentLock.agentVersion,
catalog: 'agent-catalog.json',
signature: 'agent-catalog.sig',
entries: 2
})
const catalog = agentCatalog.readVerifiedCatalog(
firstCatalog,
firstSignature,
registry
)
expect(catalog.entries.map((entry) => entry.architecture))
.toEqual(['arm64', 'x64'])
expect(catalog.entries).toEqual(
expect.arrayContaining([
expect.objectContaining({
architecture: 'x64',
sha256: packageResult.sha256
}),
expect.objectContaining({
architecture: 'arm64',
sha256: arm64PackageResult.sha256
})
])
)
const secondCatalog = join(secondRoot, 'agent-catalog.json')
const secondSignature = join(secondRoot, 'agent-catalog.sig')
agentCatalog.createCatalog({
projectRoot: process.cwd(),
x64Package: packagePath,
arm64Package: arm64PackagePath,
previousCatalog: firstCatalog,
previousSignature: firstSignature,
outputCatalog: secondCatalog,
outputSignature: secondSignature,
generatedAt: '2026-08-24T00:00:00.000Z',
registry,
signingIdentity: {
keyId: 'agent-package-fixture',
privateKey
}
})
expect(readFileSync(secondCatalog)).toEqual(
readFileSync(firstCatalog)
)
expect(readFileSync(secondSignature)).toEqual(
readFileSync(firstSignature)
)
const changedRoot = join(temporaryRoot, 'changed-package')
mkdirSync(changedRoot)
const changedPath = join(
changedRoot,
agentPackageArchiveName(agentLock.agentVersion, 'x64')
)
const originalPackage = readFileSync(packagePath)
const changedPackage = Buffer.concat([
originalPackage,
Buffer.from([0])
])
changedPackage.writeUInt16LE(1, originalPackage.length - 2)
writeFileSync(
changedPath,
changedPackage
)
expect(() =>
agentCatalog.createCatalog({
projectRoot: process.cwd(),
x64Package: changedPath,
arm64Package: arm64PackagePath,
previousCatalog: firstCatalog,
previousSignature: firstSignature,
outputCatalog: join(
temporaryRoot,
'changed-catalog.json'
),
outputSignature: join(
temporaryRoot,
'changed-catalog.sig'
),
generatedAt: '2026-08-24T00:00:00.000Z',
registry,
signingIdentity: {
keyId: 'agent-package-fixture',
privateKey
}
})
).toThrow('identity is immutable')
})
it('verifies stored payload bytes that contain a ZIP descriptor signature', () => {
expect(
agentCatalog.readPackageMetadata(packagePath, registry)
).toMatchObject({
descriptor: {
architecture: 'x64'
},
size: packageResult.size,
sha256: packageResult.sha256
})
})
it('imports and exports a fully verified offline package atomically', async () => {
const keyRegistryPath = join(
temporaryRoot,
'trusted-registry.json'
)
writeFileSync(
keyRegistryPath,
`${JSON.stringify(registry, null, 2)}\n`
)
const manager = new AgentPackageManager({
userDataPath: join(temporaryRoot, 'user-data'),
desktopVersion: '0.11.0',
keyRegistryPath,
getUpdateSource: vi.fn(async () => 'github' as const)
})
expect(
(await manager.getInventory()).entries.find(
(entry) => entry.architecture === 'x64'
)
).toMatchObject({
architecture: 'x64',
state: 'not-downloaded'
})
const invalidArm64 = join(
temporaryRoot,
'user-data',
'remote-components',
'agent-packages',
'linux-arm64',
'broken'
)
mkdirSync(invalidArm64, { recursive: true })
writeFileSync(join(invalidArm64, 'junk'), 'invalid')
expect(
(
await manager.getInventory({ refresh: true })
).entries.find(
(entry) => entry.architecture === 'arm64'
)
).toMatchObject({
architecture: 'arm64',
state: 'invalid'
})
await manager.importArchive(packagePath)
expect(
(await manager.getInventory()).entries.find(
(entry) => entry.architecture === 'x64'
)
).toMatchObject({
architecture: 'x64',
state: 'verified',
version: agentLock.agentVersion,
remoteRuntimeVersion:
remoteRuntimeLock.runtimes.opencode.version
})
const exported = join(temporaryRoot, 'offline-export.gbagent')
writeFileSync(exported, 'previous')
await manager.exportArchive('x64', exported)
expect(readFileSync(exported)).toEqual(readFileSync(packagePath))
})
it.each(['github', 'mirror'] as const)(
'checks the selected %s catalog without downloading until requested',
async (source) => {
const root = join(temporaryRoot, `online-${source}`)
mkdirSync(root)
const keyRegistryPath = join(root, 'registry.json')
writeFileSync(
keyRegistryPath,
`${JSON.stringify(registry, null, 2)}\n`
.replace(/\n/gu, '\r\n')
)
const catalog = signedCatalog()
const requested: string[] = []
const transport = vi.fn(async (input: string | URL) => {
const url = input.toString()
requested.push(url)
if (url.includes('/releases?per_page=10&page=1')) {
return jsonResponse([{
tag_name: `agent-v${agentLock.agentVersion}`,
draft: false,
prerelease: false,
assets: [
{
name: 'agent-catalog.json',
browser_download_url:
'https://github.com/mesalogo/goodbuddy/releases/download/' +
`agent-v${agentLock.agentVersion}/agent-catalog.json`
},
{
name: 'agent-catalog.sig',
browser_download_url:
'https://github.com/mesalogo/goodbuddy/releases/download/' +
`agent-v${agentLock.agentVersion}/agent-catalog.sig`
}
]
}])
}
if (url.endsWith('/latest.json')) {
return bytesResponse(mirrorPointer())
}
if (url.endsWith('/agent-catalog.json')) {
return bytesResponse(catalog.bytes)
}
if (url.endsWith('/agent-catalog.sig')) {
return bytesResponse(catalog.signature)
}
if (url.endsWith(packageResult.archive)) {
return bytesResponse(readFileSync(packagePath))
}
return new Response(null, { status: 404 })
})
const manager = new AgentPackageManager({
userDataPath: join(root, 'user-data'),
desktopVersion: '0.11.0',
keyRegistryPath,
getUpdateSource: vi.fn(async () => source),
fetch: transport as typeof fetch
})
await manager.getInventory()
expect(requested).toEqual([])
const snapshot = await manager.getSnapshot()
expect(snapshot.catalog).toMatchObject({
state: 'available',
error: null
})
expect(
snapshot.entries.find(
(entry) => entry.architecture === 'x64'
)
).toMatchObject({
state: 'not-downloaded',
latestVersion: agentLock.agentVersion,
updateAvailable: false
})
expect(requested).toContain(
source === 'github'
? 'https://api.github.com/repos/mesalogo/goodbuddy/releases?per_page=10&page=1'
: 'https://goodbuddy.oss-cn-beijing.aliyuncs.com/agent-releases/latest.json'
)
expect(requested).not.toContain(
source === 'github'
? packageEntry().downloads.github.url
: packageEntry().downloads.mirror.url
)
await manager.download('x64')
const installedSnapshot = await manager.getInventory()
expect(
installedSnapshot.entries.find(
(entry) => entry.architecture === 'x64'
)
).toMatchObject({
state: 'verified',
version: agentLock.agentVersion,
latestVersion: agentLock.agentVersion,
updateAvailable: false
})
await manager.download('x64')
expect(requested).toContain(
source === 'github'
? 'https://api.github.com/repos/mesalogo/goodbuddy/releases?per_page=10&page=1'
: 'https://goodbuddy.oss-cn-beijing.aliyuncs.com/agent-releases/latest.json'
)
expect(requested).toContain(
source === 'github'
? packageEntry().downloads.github.url
: packageEntry().downloads.mirror.url
)
expect(
requested.filter(
(url) =>
url === (
source === 'github'
? packageEntry().downloads.github.url
: packageEntry().downloads.mirror.url
)
)
).toHaveLength(1)
}
)
it('rejects a catalog with an invalid signature before package download', async () => {
const root = join(temporaryRoot, 'invalid-catalog')
mkdirSync(root)
const keyRegistryPath = join(root, 'registry.json')
writeFileSync(
keyRegistryPath,
`${JSON.stringify(registry, null, 2)}\n`
)
const catalog = signedCatalog()
let packageRequested = false
const transport = vi.fn(async (input: string | URL) => {
const url = input.toString()
if (url.includes('/releases?per_page=10&page=1')) {
return jsonResponse([{
tag_name: `agent-v${agentLock.agentVersion}`,
draft: false,
prerelease: false,
assets: [
{
name: 'agent-catalog.json',
browser_download_url:
'https://github.com/mesalogo/goodbuddy/releases/download/' +
`agent-v${agentLock.agentVersion}/agent-catalog.json`
},
{
name: 'agent-catalog.sig',
browser_download_url:
'https://github.com/mesalogo/goodbuddy/releases/download/' +
`agent-v${agentLock.agentVersion}/agent-catalog.sig`
}
]
}])
}
if (url.endsWith('/agent-catalog.json')) {
return bytesResponse(catalog.bytes)
}
if (url.endsWith('/agent-catalog.sig')) {
return bytesResponse(Buffer.from('invalid\n'))
}
packageRequested = true
return bytesResponse(readFileSync(packagePath))
})
const manager = new AgentPackageManager({
userDataPath: join(root, 'user-data'),
desktopVersion: '0.11.0',
keyRegistryPath,
getUpdateSource: vi.fn(async () => 'github' as const),
fetch: transport as typeof fetch
})
await expect(manager.getSnapshot()).resolves.toMatchObject({
catalog: {
state: 'unavailable',
error: 'Agent 发布目录签名校验失败'
},
entries: expect.any(Array)
})
expect(packageRequested).toBe(false)
await expect(manager.download('x64')).rejects.toThrow(
'Agent 发布目录签名校验失败'
)
expect(packageRequested).toBe(false)
})
it('rejects an online catalog rollback before package download', async () => {
const root = join(temporaryRoot, 'rollback-catalog')
mkdirSync(root)
const keyRegistryPath = join(root, 'registry.json')
writeFileSync(
keyRegistryPath,
`${JSON.stringify(registry, null, 2)}\n`
)
const rollback = signedCatalog([
catalogEntry(packageResult.descriptor, {
version: '0.11.1',
minimumDesktopVersion: '0.11.0'
})
])
let packageRequested = false
const transport = vi.fn(async (input: string | URL) => {
const url = input.toString()
if (url.includes('/releases?per_page=10&page=1')) {
return jsonResponse([{
tag_name: `agent-v${agentLock.agentVersion}`,
draft: false,
prerelease: false,
assets: [
{
name: 'agent-catalog.json',
browser_download_url:
'https://github.com/mesalogo/goodbuddy/releases/download/' +
`agent-v${agentLock.agentVersion}/agent-catalog.json`
},
{
name: 'agent-catalog.sig',
browser_download_url:
'https://github.com/mesalogo/goodbuddy/releases/download/' +
`agent-v${agentLock.agentVersion}/agent-catalog.sig`
}
]
}])
}
if (url.endsWith('/agent-catalog.json')) {
return bytesResponse(rollback.bytes)
}
if (url.endsWith('/agent-catalog.sig')) {
return bytesResponse(rollback.signature)
}
packageRequested = true
return bytesResponse(readFileSync(packagePath))
})
const manager = new AgentPackageManager({
userDataPath: join(root, 'user-data'),
desktopVersion: '0.11.0',
keyRegistryPath,
getUpdateSource: vi.fn(async () => 'github' as const),
fetch: transport as typeof fetch
})
await manager.importArchive(packagePath)
await expect(manager.download('x64')).rejects.toThrow(
'Agent 发布目录不能降级'
)
expect(packageRequested).toBe(false)
})
})
function writeAgentBundle(
directory: string,
architecture: 'x64' | 'arm64'
): void {
mkdirSync(join(directory, 'lib'), { recursive: true })
mkdirSync(join(directory, 'licenses'), { recursive: true })
mkdirSync(join(directory, 'helpers'), { recursive: true })
writeFileSync(join(directory, 'node'), elf(architecture))
writeFileSync(
join(directory, 'goodbuddy-agent'),
'#!/bin/sh\n'
)
writeFileSync(
join(directory, 'lib', 'agent.cjs'),
'module.exports={}\n'
)
writeFileSync(
join(directory, 'helpers', 'fixture-helper'),
'#!/bin/sh\n'
)
for (const [name, contents] of [
['GoodBuddy-0BSD.txt', '0BSD'],
['Node.js-MIT.txt', 'MIT'],
['zod-MIT.txt', 'MIT'],
['koffi-MIT.txt', 'MIT'],
['koffi-native-MIT.txt', 'MIT']
] as const) {
writeFileSync(join(directory, 'licenses', name), contents)
}
writeKoffiPayload(directory, architecture)
for (const path of [
'node',
'goodbuddy-agent',
'helpers/fixture-helper'
]) {
chmodSync(join(directory, path), 0o755)
}
signAgentBundleManifest(directory, architecture)
}
function signAgentBundleManifest(
directory: string,
architecture: 'x64' | 'arm64'
): void {
const manifest = agentBundle.createManifest(directory, {
agentVersion: agentLock.agentVersion,
nodeVersion: agentLock.node.version,
zodVersion: '4.4.3',
koffiVersion: agentLock.koffi.version,
koffiNativePackage:
`@koromix/koffi-linux-${architecture}`,
arch: architecture,
protocol: agentLock.protocol,
signingKeyId: 'agent-package-fixture'
})
const bytes = agentBundle.canonicalManifestBytes(manifest)
writeFileSync(join(directory, 'manifest.json'), bytes)
writeFileSync(
join(directory, 'manifest.sig'),
`${agentBundle
.signManifestForTest(bytes, privateKey)
.toString('base64')}\n`
)
chmodSync(join(directory, 'manifest.json'), 0o644)
chmodSync(join(directory, 'manifest.sig'), 0o644)
}
function writeKoffiPayload(
directory: string,
architecture: 'x64' | 'arm64'
): void {
const packageRoot = join(
directory,
'lib',
'node_modules',
'koffi'
)
const nativePackage = `@koromix/koffi-linux-${architecture}`
const nativeRoot = join(
directory,
'lib',
'node_modules',
...nativePackage.split('/')
)
mkdirSync(join(packageRoot, 'src', 'koffi', 'src'), {
recursive: true
})
mkdirSync(join(nativeRoot, `linux_${architecture}`), {
recursive: true
})
mkdirSync(join(nativeRoot, `musl_${architecture}`), {
recursive: true
})
const files = new Map<string, string | Buffer>([
[
join(packageRoot, 'package.json'),
JSON.stringify({
name: 'koffi',
version: agentLock.koffi.version,
type: 'module',
exports: { '.': { import: './index.js' } }
})
],
[
join(packageRoot, 'index.js'),
'export { default } from "./src/koffi/index.js";\n'
],
[
join(packageRoot, 'src', 'koffi', 'index.js'),
'export default {};\n'
],
[
join(packageRoot, 'src', 'koffi', 'src', 'static.js'),
'export function loadStatic() { return null }\n'
],
[
join(nativeRoot, 'package.json'),
JSON.stringify({
name: nativePackage,
version: agentLock.koffi.version,
main: './index.js',
os: ['linux'],
cpu: [architecture]
})
],
[
join(nativeRoot, 'index.js'),
`module.exports = require('./linux_${architecture}/koffi.node');\n`
],
[
join(nativeRoot, `linux_${architecture}`, 'koffi.node'),
elf(architecture)
],
[
join(nativeRoot, `musl_${architecture}`, 'koffi.node'),
elf(architecture)
]
])
for (const [filePath, contents] of files) {
writeFileSync(filePath, contents)
chmodSync(filePath, 0o644)
}
}
function createRuntimeInput(
architecture: 'x64' | 'arm64'
): string {
const sourceRoot = join(
temporaryRoot,
`runtime-input-${architecture}`
)
const packageRoot = join(sourceRoot, 'package')
mkdirSync(join(packageRoot, 'bin'), { recursive: true })
writeFileSync(
join(packageRoot, 'package.json'),
`${JSON.stringify({
name:
architecture === 'x64'
? 'opencode-linux-x64-baseline'
: 'opencode-linux-arm64',
version: '1.18.29'
})}\n`
)
writeFileSync(
join(packageRoot, 'bin', 'opencode'),
Buffer.concat([
elf(architecture),
Buffer.from([0x50, 0x4b, 0x07, 0x08]),
Buffer.from('runtime payload')
])
)
chmodSync(join(packageRoot, 'bin', 'opencode'), 0o755)
const archive = join(
temporaryRoot,
architecture === 'x64'
? 'opencode-linux-x64-baseline-1.18.29.tgz'
: 'opencode-linux-arm64-1.18.29.tgz'
)
createTar(
{
cwd: sourceRoot,
file: archive,
sync: true,
portable: true,
noPax: true,
gzip: true,
mtime: new Date(0)
},
['package/package.json', 'package/bin/opencode']
)
return archive
}
function createRemoteRuntimeLock(
runtimeArchive: string,
arm64RuntimeArchive: string
): RemoteRuntimeLock {
return {
formatVersion: 1,
runtimes: {
opencode: {
version: '1.18.29',
provider: 'opencode',
entrypoint: 'bin/opencode',
entrypointIdentity: 'opencode-acp',
argvPrefix: ['acp'],
allowedEnvironmentNames: [
'HOME',
'LANG',
'LC_ALL',
'PATH',
'TMPDIR',
'XDG_CACHE_HOME',
'XDG_CONFIG_HOME',
'XDG_DATA_HOME',
'XDG_STATE_HOME'
],
protocol: { major: 1, minor: 0 },
targets: {
x64: {
package: 'opencode-linux-x64-baseline',
integrity: `sha512-${createHash('sha512')
.update(readFileSync(runtimeArchive))
.digest('base64')}`
},
arm64: {
package: 'opencode-linux-arm64',
integrity: `sha512-${createHash('sha512')
.update(readFileSync(arm64RuntimeArchive))
.digest('base64')}`
}
}
}
}
}
}
function elf(architecture: 'x64' | 'arm64'): Buffer {
const contents = Buffer.alloc(64)
contents.set([0x7f, 0x45, 0x4c, 0x46])
contents[5] = 1
contents.writeUInt16LE(architecture === 'x64' ? 62 : 183, 18)
return contents
}
function findRuntimeBundleDirectory(): string {
const digest = packageResult.descriptor.remoteRuntime.bundleDigest
.slice('sha256:'.length)
return join(
temporaryRoot,
'runtime',
'linux-x64',
'opencode',
digest
)
}
function packageEntry(): AgentPackageCatalogEntry {
return catalogEntry(packageResult.descriptor, {
archive: packageResult.archive,
size: packageResult.size,
sha256: packageResult.sha256
})
}
function catalogEntry(
descriptor: AgentPackageDescriptor,
overrides: Partial<AgentPackageCatalogEntry> = {}
): AgentPackageCatalogEntry {
const {
contentDigest: _contentDigest,
files: _files,
signingKeyId: _signingKeyId,
...identity
} = descriptor
void _contentDigest
void _files
void _signingKeyId
const version = overrides.version ?? identity.version
const architecture =
overrides.architecture ?? identity.architecture
const archive =
overrides.archive ??
agentPackageArchiveName(version, architecture)
return {
...identity,
...overrides,
version,
architecture,
archive,
size: overrides.size ?? packageResult.size,
sha256: overrides.sha256 ?? packageResult.sha256,
downloads: overrides.downloads ?? {
github: {
url:
'https://github.com/mesalogo/goodbuddy/releases/download/' +
`agent-v${version}/${archive}`
},
mirror: {
url:
'https://goodbuddy.oss-cn-beijing.aliyuncs.com/' +
`agent-releases/v${version}/${archive}`
}
}
}
}
function signedCatalog(
entries: AgentPackageCatalogEntry[] = [packageEntry()]
): {
catalog: AgentPackageCatalog
bytes: Buffer
signature: Buffer
} {
const catalog = agentPackageCatalogSchema.parse({
formatVersion: 1,
product: 'GoodBuddy',
component: 'agent',
signingKeyId: 'agent-package-fixture',
generatedAt: '2026-08-24T00:00:00.000Z',
entries
})
const bytes = Buffer.from(
`${JSON.stringify(catalog, null, 2)}\n`
)
const signature = Buffer.from(
`${sign(
null,
Buffer.concat([catalogSignatureDomain, bytes]),
privateKey
).toString('base64')}\n`
)
return { catalog, bytes, signature }
}
function bytesResponse(bytes: Buffer): Response {
return new Response(new Uint8Array(bytes), {
status: 200,
headers: {
'content-length': String(bytes.byteLength)
}
})
}
function jsonResponse(value: unknown): Response {
return bytesResponse(Buffer.from(JSON.stringify(value)))
}
function mirrorPointer(): Buffer {
return Buffer.from(
`${JSON.stringify({
formatVersion: 1,
version: agentLock.agentVersion,
catalog:
`v${agentLock.agentVersion}/agent-catalog.json`,
signature:
`v${agentLock.agentVersion}/agent-catalog.sig`
}, null, 2)}\n`
)
}
function renameStoredZipEntry(
archive: Buffer,
oldName: string,
newName: string
): Buffer {
const oldBytes = Buffer.from(oldName, 'utf8')
const newBytes = Buffer.from(newName, 'utf8')
if (oldBytes.length !== newBytes.length) {
throw new Error('ZIP fixture names must have equal byte lengths')
}
const output = Buffer.from(archive)
let replacements = 0
for (let offset = 0; offset <= output.length - 46; offset += 1) {
const signature = output.readUInt32LE(offset)
const nameOffset =
signature === 0x04034b50
? offset + 30
: signature === 0x02014b50
? offset + 46
: -1
const lengthOffset =
signature === 0x04034b50
? offset + 26
: signature === 0x02014b50
? offset + 28
: -1
if (
nameOffset >= 0 &&
output.readUInt16LE(lengthOffset) === oldBytes.length &&
output.subarray(nameOffset, nameOffset + oldBytes.length)
.equals(oldBytes)
) {
newBytes.copy(output, nameOffset)
replacements += 1
}
}
if (replacements !== 2) {
throw new Error(
`Expected local and central ZIP names, received ${replacements}`
)
}
return output
}
function corruptStoredZipEntry(
archive: Buffer,
name: string
): Buffer {
const output = Buffer.from(archive)
const nameBytes = Buffer.from(name, 'utf8')
for (let offset = 0; offset <= output.length - 30; offset += 1) {
if (output.readUInt32LE(offset) !== 0x04034b50) {
continue
}
const nameLength = output.readUInt16LE(offset + 26)
const extraLength = output.readUInt16LE(offset + 28)
if (
nameLength === nameBytes.length &&
output.subarray(offset + 30, offset + 30 + nameLength)
.equals(nameBytes)
) {
const dataOffset = offset + 30 + nameLength + extraLength
output[dataOffset] = (output[dataOffset] ?? 0) ^ 1
return output
}
}
throw new Error(`ZIP fixture entry was not found: ${name}`)
}
function seedGlobalManagedMetadata(
homeDirectory: string,
digestCharacter: string
): Map<string, Buffer> {
const managedRoot = join(homeDirectory, '.goodbuddy')
const files = new Map<string, Buffer>([
[
join(managedRoot, 'agent', 'release-keys.json'),
Buffer.from(`${JSON.stringify(registry, null, 4)}\r\n`)
],
[
join(managedRoot, 'agent', 'registry.json'),
Buffer.from(
`${JSON.stringify({
formatVersion: 1,
current: {
installationId:
`agent-${digestCharacter.repeat(64)}`,
agentVersion: '0.10.0',
manifestSha256: digestCharacter.repeat(64),
arch: 'x64'
}
}, null, 4)}\r\n`
)
],
[
join(managedRoot, 'runtimes', 'release-keys.json'),
Buffer.from(`${JSON.stringify(registry, null, 3)}\r\n`)
],
[
join(
managedRoot,
'runtimes',
'remote-runtime-lock.json'
),
Buffer.from(`${JSON.stringify(remoteRuntimeLock, null, 3)}\r\n`)
],
[
join(managedRoot, 'runtimes', 'registry.json'),
Buffer.from(
`${JSON.stringify({
formatVersion: 1,
current: [{
runtimeId: 'opencode',
runtimeVersion: '1.0.0',
architecture: 'x64',
bundleDigest:
`sha256:${digestCharacter.repeat(64)}`,
manifestDigest: `sha256:${'1'.repeat(64)}`,
acpCapabilitiesDigest: `sha256:${'2'.repeat(64)}`
}]
}, null, 4)}\r\n`
)
]
])
for (const [path, bytes] of files) {
mkdirSync(dirname(path), { recursive: true, mode: 0o700 })
writeFileSync(path, bytes, { mode: 0o600 })
}
return files
}
function expectGlobalMetadataUnchanged(
files: ReadonlyMap<string, Buffer>
): void {
for (const [path, bytes] of files) {
expect(readFileSync(path)).toEqual(bytes)
}
}
function expectRegistriesUnchangedAndPackageTrustPublished(
files: ReadonlyMap<string, Buffer>
): void {
for (const [path, bytes] of files) {
if (path.endsWith('registry.json')) {
expect(readFileSync(path)).toEqual(bytes)
continue
}
const actual = JSON.parse(readFileSync(path, 'utf8'))
expect(actual).toEqual(
path.endsWith('remote-runtime-lock.json')
? remoteRuntimeLock
: registry
)
}
}