mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
Recovery checkpoint, not completion: focused checks passed in surviving logs; installed normal journeys currently fail the newly added Restart revision observation (10 failures in focused-final-3-installed.log). Full final gate and refreshed drift probe remain pending. Signed-off-by: Logan Johnson <loganj@squareup.com>
117 lines
7.6 KiB
TypeScript
117 lines
7.6 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, realpathSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join, resolve } from 'node:path';
|
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
import { provisionSetup } from './provision.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, open } from '../src/protocol.ts';
|
|
|
|
test('Restart preserves identity, preflights before Stop, replays once and obeys same-batch Stop fencing', async () => {
|
|
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'bh-restart-')));
|
|
const secret = newKey(), agentSecret = newKey(), agent = publicKey(agentSecret);
|
|
provisionSetup(join(dir, 'setup.json'), { host: 'restart-host', ownerSecret: secret, agentSecret, runner: realpathSync(process.execPath), args: [resolve('test/runner.ts')], workspace: dir, mode: 'fixture' });
|
|
const server = await relay(0, publicKey(secret), 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); const seen: Message[] = [];
|
|
const client = connect(url, secret, m => seen.push(m)); await client.ready;
|
|
const journal = () => JSON.parse(readFileSync(join(dir, 'journal.json'), 'utf8'));
|
|
async function receipt(m: Message) {
|
|
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(`Missing receipt for ${m.type}`);
|
|
}
|
|
try {
|
|
assert.equal((await receipt(message('start', 'restart-host', agent, 0))).body.result, 'accepted');
|
|
const original = journal();
|
|
const setup = readFileSync(join(dir, 'setup.json'));
|
|
unlinkSync(join(dir, 'setup.json'));
|
|
const rejected = await receipt(message('restart', 'restart-host', agent, 1));
|
|
assert.match(String(rejected.body.result), /key\/setup missing/);
|
|
assert.deepEqual(journal().actual, original.actual);
|
|
assert.equal(journal().phase, 'running');
|
|
assert.equal(journal().revision, 1);
|
|
// Only this local test operator restores the file; the host never does.
|
|
writeFileSync(join(dir, 'setup.json'), setup, { mode: 0o600 });
|
|
const restart = message('restart', 'restart-host', agent, 1);
|
|
assert.equal((await receipt(restart)).body.result, 'accepted');
|
|
assert.equal(journal().actual.run, restart.id);
|
|
assert.deepEqual(journal().binding, original.binding);
|
|
assert.deepEqual(journal().assignment, original.assignment);
|
|
const after = readFileSync(join(dir, 'journal.json'), 'utf8');
|
|
client.send(restart); await delay(100);
|
|
assert.equal(readFileSync(join(dir, 'journal.json'), 'utf8'), after);
|
|
const cancelled = message('restart', 'restart-host', agent, 2);
|
|
client.send(cancelled);
|
|
assert.equal((await receipt(message('stop', 'restart-host', agent, 2))).body.result, 'accepted');
|
|
assert.match(journal().operations[cancelled.id].reply.body.result, /cancelled/);
|
|
assert.equal(journal().actual, null); assert.equal(journal().phase, 'stopped');
|
|
await delay(150); assert.equal(journal().actual, null);
|
|
} finally {
|
|
await h.close(); client.close(); for (const c of server.clients) c.terminate();
|
|
await new Promise<void>(r => server.close(() => r())); rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('ACP Restart preflight rejection preserves actual run; authorized Stop interrupts delayed probe without revival', async () => {
|
|
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'bh-restart-acp-')));
|
|
const secret = newKey(), agentSecret = newKey(), agent = publicKey(agentSecret);
|
|
const wrapper = join(dir, 'fixture.mjs');
|
|
writeFileSync(wrapper, `import { readFileSync } from 'node:fs'; process.argv[2] = readFileSync('mode', 'utf8'); await import(${JSON.stringify(new URL(`file://${resolve('test/acp-fixture.ts')}`).href)});`);
|
|
writeFileSync(join(dir, 'mode'), 'ok');
|
|
provisionSetup(join(dir, 'setup.json'), { host: 'acp-restart', ownerSecret: secret, agentSecret, runner: realpathSync(process.execPath), args: [wrapper], workspace: dir, mode: 'buzz-agent-databricks-v2', serviceHome: dir, configDirectory: dir, databricksHost: 'https://fixture.invalid' });
|
|
const storagePhases: { phase: string; at: number }[] = [];
|
|
const server = await relay(0, publicKey(secret), join(dir, 'relay.json'), async phase => {
|
|
storagePhases.push({ phase, at: Date.now() });
|
|
if (storagePhases.length > 64) storagePhases.shift();
|
|
});
|
|
const address = server.address(); assert.ok(address && typeof address !== 'string');
|
|
const url = `ws://127.0.0.1:${address.port}`, seen: Message[] = [];
|
|
const h = await host(dir, url), client = connect(url, secret, m => seen.push(m)); await client.ready;
|
|
const journal = () => JSON.parse(readFileSync(join(dir, 'journal.json'), 'utf8'));
|
|
async function until(predicate: () => boolean) {
|
|
for (let i = 0; i < 200; i++) { if (predicate()) return; await delay(25); }
|
|
throw Error('Missing ACP Restart evidence');
|
|
}
|
|
async function request(m: Message) { client.send(m); await until(() => seen.some(r => r.type === 'receipt' && r.body.operation === m.id)); return journal().operations[m.id].reply.body.result; }
|
|
try {
|
|
assert.equal(await request(message('start', 'acp-restart', agent, 0)), 'accepted');
|
|
const original = journal().actual;
|
|
writeFileSync(join(dir, 'mode'), 'reject');
|
|
assert.notEqual(await request(message('restart', 'acp-restart', agent, 1)), 'accepted');
|
|
assert.deepEqual(journal().actual, original); assert.equal(journal().phase, 'running');
|
|
for (const mode of ['current-drift', 'config-drift', 'coalesced-drift', 'teardown-drift']) {
|
|
writeFileSync(join(dir, 'mode'), mode);
|
|
assert.notEqual(await request(message('restart', 'acp-restart', agent, 1)), 'accepted', mode);
|
|
assert.deepEqual(journal().actual, original, mode);
|
|
assert.equal(journal().phase, 'running', mode);
|
|
assert.equal(journal().revision, 1, mode);
|
|
}
|
|
writeFileSync(join(dir, 'mode'), 'delayed');
|
|
const restart = message('restart', 'acp-restart', agent, 1); client.send(restart);
|
|
await until(() => { try { return readFileSync(join(dir, 'prompt-started'), 'utf8') === '1'; } catch { return false; } });
|
|
// Invalid retractions cannot interrupt the prerequisite probe or old run.
|
|
client.send(message('stop', 'acp-restart', publicKey(newKey()), 1));
|
|
client.send(message('stop', 'acp-restart', agent, 99));
|
|
client.send(message('stop', 'acp-restart', agent, 1, { invalid: true }));
|
|
await delay(100); assert.deepEqual(journal().actual, original);
|
|
const began = Date.now();
|
|
assert.equal(await request(message('stop', 'acp-restart', agent, 1)), 'accepted');
|
|
assert.ok(Date.now() - began < 2500, `Stop interrupts four-second preflight: ${JSON.stringify({ elapsed: Date.now() - began, storagePhases, receipts: seen.filter(m => m.type === 'receipt'), order: JSON.parse(readFileSync(join(dir, 'relay.json'), 'utf8')).map((e: unknown) => { const m = open(e, secret); return { type: m.type, id: m.id, revision: m.revision }; }) })}`);
|
|
assert.notEqual(journal().operations[restart.id].reply.body.result, 'accepted');
|
|
assert.equal(journal().actual, null); assert.equal(journal().phase, 'stopped');
|
|
await delay(4200); assert.equal(journal().actual, null);
|
|
} finally {
|
|
await h.close(); client.close(); for (const c of server.clients) c.terminate();
|
|
await new Promise<void>(r => server.close(() => r())); rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|