mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
123 lines
8.1 KiB
TypeScript
123 lines
8.1 KiB
TypeScript
import { provisionSetup } from './provision.ts';
|
|
import { test } from 'node:test';
|
|
import { spawnSync } from 'node:child_process';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, realpathSync, rmSync, mkdirSync, readFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join, resolve } from 'node:path';
|
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
import { AgentSession, prepareAgent, type AgentLaunch } from '../src/acp.ts';
|
|
import { host } from '../src/host.ts';
|
|
import { relay } from '../src/relay.ts';
|
|
import { connect } from '../src/client.ts';
|
|
import { message, newKey, publicKey, type Message } from '../src/protocol.ts';
|
|
import { writePrivate } from '../src/storage.ts';
|
|
|
|
const fixture = resolve('test/acp-fixture.ts');
|
|
function plan(dir: string, mode = 'ok'): AgentLaunch {
|
|
return { executable: realpathSync(process.execPath), args: [fixture, mode], workspace: dir, home: dir, configDirectory: dir, databricksHost: 'https://fixture.invalid', model: 'databricks-claude-haiku-4-5' };
|
|
}
|
|
async function cleanup(s: AgentSession) { await s.owned.stop(); }
|
|
test('external ACP boundary: snapshot, catalog provenance, exact-model same-session evidence and failures', async () => {
|
|
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'beehive-acp-')));
|
|
try {
|
|
const input = plan(dir); const prepared = prepareAgent(input);
|
|
(input.args as string[]).push('mutated');
|
|
assert.equal(prepared.plan.args.length, 2);
|
|
assert.equal(Object.hasOwn(prepared.env, 'DATABRICKS_TOKEN'), false);
|
|
assert.equal(Object.hasOwn(prepared.env, 'BUZZ_PRIVATE_KEY'), false);
|
|
assert.throws(() => prepareAgent({ ...plan(dir), databricksHost: 'https://user:secret@example.com' }));
|
|
for (const mode of ['ok', 'empty', 'filtered', 'wrong-model', 'wrong-session', 'cancelled', 'reject', 'malformed', 'timeout', 'flood', 'bad-tail', 'current-drift', 'config-drift', 'coalesced-drift', 'foreign-drift', 'non-model']) {
|
|
const s = new AgentSession(plan(dir, mode), 1500);
|
|
try {
|
|
if (['malformed', 'timeout', 'flood'].includes(mode)) { await assert.rejects(s.catalog()); continue; }
|
|
const catalog = await s.catalog();
|
|
assert.equal(catalog.authentication, 'unverified');
|
|
assert.equal(catalog.state, mode === 'empty' ? 'empty' : mode === 'filtered' ? 'filtered' : 'reported');
|
|
assert.ok(!JSON.stringify(catalog).includes('DO-NOT-RELAY'));
|
|
if (['wrong-model', 'wrong-session', 'cancelled', 'reject', 'bad-tail', 'current-drift', 'config-drift', 'coalesced-drift'].includes(mode)) {
|
|
await assert.rejects(s.verify(), e => e instanceof Error && !e.message.includes('DO-NOT-RELAY'));
|
|
} else {
|
|
const evidence = await s.verify();
|
|
assert.equal(evidence.model, plan(dir).model);
|
|
assert.equal(evidence.session, 'fixture-session');
|
|
assert.match(evidence.responseHash, /^[0-9a-f]{64}$/);
|
|
assert.equal(evidence.executableHash, s.prepared.executableHash);
|
|
}
|
|
} catch (error) { throw new Error(`ACP fixture mode ${mode} failed`, { cause: error }); } finally { await cleanup(s); }
|
|
}
|
|
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('production host Start uses external ACP evidence; Save and Stop do not need auth', async () => {
|
|
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'beehive-acp-host-')));
|
|
const hostDir = join(dir, 'host'); mkdirSync(hostDir);
|
|
const ownerSecret = newKey(); const agentSecret = newKey(); const agent = publicKey(agentSecret);
|
|
const p = plan(dir);
|
|
const authDir = join(dir, 'isolated-auth'); mkdirSync(authDir);
|
|
provisionSetup(join(hostDir, 'setup.json'), { host: 'acp-host', ownerSecret, agentSecret, runner: p.executable, args: p.args, workspace: dir, mode: 'buzz-agent-databricks-v2', serviceHome: dir, configDirectory: authDir, databricksHost: p.databricksHost });
|
|
const authInfo = spawnSync(process.execPath, ['src/cli.ts', 'auth-info', hostDir], { encoding: 'utf8', timeout: 3000 });
|
|
assert.equal(authInfo.status, 0); assert.match(authInfo.stdout, /acp-host/); assert.match(authInfo.stdout, /auth databricks/);
|
|
assert.ok(!authInfo.stdout.includes(ownerSecret) && !authInfo.stdout.includes(agentSecret));
|
|
const server = await relay(0, publicKey(ownerSecret), join(dir, 'relay.json'));
|
|
const address = server.address(); assert.ok(address && typeof address !== 'string');
|
|
const url = `ws://127.0.0.1:${address.port}`;
|
|
const h = await host(hostDir, url);
|
|
const seen: Message[] = [];
|
|
const client = connect(url, ownerSecret, m => seen.push(m)); await client.ready;
|
|
async function request(type: 'start' | 'save' | 'stop', revision: number, body = {}) {
|
|
const m = message(type, 'acp-host', agent, revision, body); client.send(m);
|
|
for (let i = 0; i < 200; i++) {
|
|
const r = seen.find(r => r.type === 'receipt' && r.body.operation === m.id);
|
|
if (r) return r;
|
|
await delay(25);
|
|
}
|
|
throw Error('No host receipt');
|
|
}
|
|
try {
|
|
assert.equal((await request('save', 0, { model: p.model, workspace: dir, profile: 'default' })).body.result, 'saved; running configuration unchanged');
|
|
assert.equal((await request('start', 1)).body.result, 'accepted');
|
|
const state = JSON.parse(readFileSync(join(hostDir, 'journal.json'), 'utf8'));
|
|
assert.equal(state.actual.evidence.model, p.model);
|
|
assert.equal(state.actual.evidence.session, 'fixture-session');
|
|
assert.equal(state.actual.selection.model, p.model);
|
|
assert.equal(state.actual.executableHash, state.actual.evidence.executableHash);
|
|
rmSync(authDir, { recursive: true });
|
|
assert.equal((await request('save', 2, { model: p.model, workspace: dir, profile: 'default' })).body.result, 'saved; running configuration unchanged');
|
|
// The auth directory is already absent; both Save and Stop remain usable.
|
|
assert.equal((await request('stop', 3)).body.result, 'accepted');
|
|
const stopped = JSON.parse(readFileSync(join(hostDir, 'journal.json'), 'utf8'));
|
|
assert.equal(stopped.binding.host, 'acp-host'); assert.equal(stopped.phase, 'stopped');
|
|
assert.ok(!readFileSync(join(dir, 'relay.json'), 'utf8').includes('fixture-session'));
|
|
} finally {
|
|
await h.close(); client.close(); for (const c of server.clients) c.terminate();
|
|
await new Promise<void>(resolve => server.close(() => resolve())); rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
for (const mode of ['current-drift', 'config-drift', 'coalesced-drift', 'foreign-drift', 'non-model']) test(`diagnostic Start consuming boundary: ${mode}`, async () => {
|
|
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'beehive-model-host-')));
|
|
const ownerSecret = newKey(), agentSecret = newKey(), agent = publicKey(agentSecret);
|
|
const p = plan(dir, mode);
|
|
provisionSetup(join(dir, 'setup.json'), { host: 'model-host', ownerSecret, agentSecret, runner: p.executable, args: p.args, workspace: dir, mode: 'buzz-agent-databricks-v2', serviceHome: dir, configDirectory: dir, databricksHost: p.databricksHost });
|
|
const server = await relay(0, publicKey(ownerSecret), join(dir, 'relay.json'));
|
|
const address = server.address(); assert.ok(address && typeof address !== 'string');
|
|
const url = `ws://127.0.0.1:${address.port}`;
|
|
const h = await host(dir, url), seen: Message[] = [];
|
|
const client = connect(url, ownerSecret, m => seen.push(m)); await client.ready;
|
|
try {
|
|
const op = message('start', 'model-host', agent, 0); client.send(op);
|
|
for (let n = 0; n < 200 && !seen.some(m => m.type === 'receipt' && m.body.operation === op.id); n++) await delay(25);
|
|
const receipt = seen.find(m => m.type === 'receipt' && m.body.operation === op.id); assert.ok(receipt);
|
|
const state = JSON.parse(readFileSync(join(dir, 'journal.json'), 'utf8'));
|
|
if (mode === 'foreign-drift' || mode === 'non-model') {
|
|
assert.equal(receipt.body.result, 'accepted'); assert.equal(state.actual.evidence.model, p.model);
|
|
} else {
|
|
assert.notEqual(receipt.body.result, 'accepted'); assert.equal(state.actual, null); assert.equal(state.phase, 'stopped');
|
|
}
|
|
} finally {
|
|
await h.close(); client.close(); for (const c of server.clients) c.terminate();
|
|
await new Promise<void>(resolve => server.close(() => resolve())); rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|