Files
stack/packages/bus/tests/process.test.mjs
T
jason.woltje c216f47dbb plugins, cli, bus, business, webui: harness, model and thinking from the instance vars, a fallback route, and a harness plug-in kind (row 65, #1558)
Adds the harness plug-in kind (interface 1). packages/plugins/harness/pi
and claude-code hold what core branched on: the adapter, thinking levels,
preflight, version probe, sandbox mounts, builtins line, typed tool names,
gate timeout, bundle files, adapter env, turn end and session-files path.
Core reaches a module only through the loader.

A session's route is the instance's harness, model and thinking vars, or
the three fallback. vars; a missing one refuses no-route, and the system
file's execution keys are never read for a session. The launcher checks
the primary route, then the fallback: harness-off, harness-refused,
thinking-unsupported, then preflight. A failed preflight falls back when
a fallback is set, and any refusal of the fallback names the primary's
failure too. Account checks run
only when the route's module counts an account. The manifest records
route, routeReason and harnessVersion, replacing piVersion and
claudeVersion. pi gets --thinking, Claude Code --effort, both from
MOSAIC_THINKING.

The business file refuses a partial fallback. routes and routes.set are
human-only bus verbs outside ORGANIZATION_KEYS; checkRoutes is shared by
routes.set, instances.set and limits.set and checks only changed routes.
The chief of staff changes an instance's model, and the assistant's whole
vars (Q169), through limits.set, so its route write runs the same checks.
A model outside launch.models refuses unknown-model, primary or fallback,
and routes.set passes that code through.
A new run binds only on a switched-on harness module name and records
its transcript path, which the usage reader uses. Settings gains a
Routing table with six selects per instance and loses the Role models
fieldset; it posts changed rows to /api/routes. webui/src/bus.mjs is
unchanged.

Until Jason's `mosaic plugins set` adds "harness": ["claude-code"], a new
launch refuses harness-off.
2026-10-19 11:27:19 -05:00

214 lines
9.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { fork } from 'node:child_process';
import { mkdtempSync, rmSync, existsSync, writeFileSync, mkdirSync, readFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { once } from 'node:events';
import { Client } from '../src/client.mjs';
const businesses = {
demo: {
id: 'demo',
human: 'jason',
// Row 65: runtime launches bind only on a harness the business switches on.
plugins: { harness: ['pi'] },
arbiters: { technical: 'cto', delivery: 'cto' },
roles: { cto: { authority: { withinRole: ['message.send'], crossRole: [] } } },
},
};
function launch(t) {
const child = fork(new URL('../src/process.mjs', import.meta.url), [], {
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
});
let logs = '';
child.stdout.on('data', (b) => (logs += b));
child.stderr.on('data', (b) => (logs += b));
t.after(() => {
if (child.exitCode === null) child.kill('SIGKILL');
});
return { child, logs: () => logs };
}
async function boot(child, config) {
const reply = Promise.race([
once(child, 'message'),
once(child, 'exit').then(() => {
throw Error('exited-before-reply');
}),
]);
child.send({ op: 'boot', config });
return (await reply)[0];
}
test('broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly', async (t) => {
const root = mkdtempSync(join(tmpdir(), 'bus-process-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const { child } = launch(t);
const ready = await boot(child, {
dataRoot: root,
businesses,
launches: [{ business: 'demo', role: 'cto', run: 'r1', harness: 'pi', pid: process.pid, startTime: '1' }],
readers: ['demo'],
});
assert.equal(ready.ok, true);
const agent = new Client({ path: ready.path, cap: ready.launches[0].cap });
await agent.call('role.claim');
const reader = new Client({ path: ready.path, cap: ready.readers[0].cap });
assert.equal((await reader.call('agents'))[0].holder_run, 'r1');
await assert.rejects(reader.call('launch.revoke'), /read-only/);
await assert.rejects(
new Client({
path: ready.path,
human: { business: 'demo', pid: process.pid, startTime: '1', nonce: 'f'.repeat(64) },
}).call('launch.revoke'),
/human-required/,
);
const exit = once(child, 'exit');
child.send({ op: 'close' });
assert.equal((await exit)[0], 0);
assert.equal(existsSync(join(root, 'bus/writer.lock')), false);
});
test('startup token refusal returns safe code without value or partial listening broker', async (t) => {
const root = mkdtempSync(join(tmpdir(), 'bus-boot-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const data = join(root, 'data');
mkdirSync(data);
const secret = 'fixture-token-never-in-db-948723';
const file = join(root, 'secret');
writeFileSync(file, secret, { mode: 0o644 });
const config = structuredClone(businesses);
config.demo.roles.cto.credentials = { gitea: { file, rotateBy: '2099-01-01' } };
const { child, logs } = launch(t);
const exit = once(child, 'exit');
const ready = await boot(child, { dataRoot: data, businesses: config, launches: [], readers: [] });
assert.equal(ready.ok, false);
assert.equal(ready.error, 'credential-file');
assert.equal((await exit)[0], 2);
assert.ok(!logs().includes(secret));
assert.equal(existsSync(join(data, 'bus/broker.sock')), false);
});
test('loaded fixture token is absent from socket replies and SQLite, including refusal evidence', async (t) => {
const root = mkdtempSync(join(tmpdir(), 'bus-secret-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const data = join(root, 'data');
mkdirSync(data);
const token = 'fixture-opaque-token-e9c39140';
const file = join(root, 'token');
writeFileSync(file, token, { mode: 0o600 });
const config = structuredClone(businesses);
config.demo.roles.cto.credentials = { gitea: { file, rotateBy: '2099-01-01' } };
const { child, logs } = launch(t);
const ready = await boot(child, {
dataRoot: data,
businesses: config,
launches: [{ business: 'demo', role: 'cto', run: 'r2', harness: 'pi', pid: process.pid, startTime: '1' }],
});
assert.equal(ready.ok, true);
const c = new Client({ path: ready.path, cap: ready.launches[0].cap });
await c.call('role.claim');
await assert.rejects(c.call('message.send', { to: 'cto', body: token }), /credential-leak/);
const exit = once(child, 'exit');
child.send({ op: 'close' });
await exit;
assert.ok(!readFileSync(join(data, 'bus/bus.sqlite')).includes(Buffer.from(token)));
assert.ok(!logs().includes(token));
});
test('killed broker leaves an explicit stale lock; another process cannot silently reclaim it', async (t) => {
const root = mkdtempSync(join(tmpdir(), 'bus-crash-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const first = launch(t);
const config = { dataRoot: root, businesses, launches: [] };
assert.equal((await boot(first.child, config)).ok, true);
const killed = once(first.child, 'exit');
first.child.kill('SIGKILL');
await killed;
assert.equal(existsSync(join(root, 'bus/writer.lock')), true);
const second = launch(t),
ended = once(second.child, 'exit');
const refusal = await boot(second.child, config);
assert.equal(refusal.ok, false);
assert.equal((await ended)[0], 2);
assert.equal(existsSync(join(root, 'bus/writer.lock')), true);
});
test('trusted host registers later launches; socket clients never have a registration verb', async (t) => {
const root = mkdtempSync(join(tmpdir(), 'bus-add-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const { child } = launch(t);
const ready = await boot(child, { dataRoot: root, businesses, launches: [] });
assert.equal(ready.ok, true);
const message = once(child, 'message');
child.send({
op: 'bindLaunch',
record: { business: 'demo', role: 'cto', run: 'later', harness: 'pi', pid: process.pid, startTime: '1' },
});
const bound = (await message)[0];
assert.equal(bound.ok, true);
const c = new Client({ path: ready.path, cap: bound.launch.cap });
await c.call('role.claim');
await assert.rejects(c.call('bindLaunch', { role: 'cto' }), /unknown-verb/);
const exit = once(child, 'exit');
child.send({ op: 'close' });
assert.equal((await exit)[0], 0);
});
test('REQ-LIMIT-4 the broker process answers the launcher\'s mayLaunch op with the limits\' awaited decision', async (t) => {
const root = mkdtempSync(join(tmpdir(), 'bus-may-launch-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const { child } = launch(t);
const ready = await boot(child, { dataRoot: root, businesses, launches: [] });
assert.equal(ready.ok, true);
const ask = async (m) => {
const message = once(child, 'message');
child.send(m);
return (await message)[0];
};
// No limit check is switched on for demo, so the launch is allowed.
assert.deepEqual(await ask({ op: 'mayLaunch', id: 7, business: 'demo', instance: 'cto', provider: 'p', model: 'm' }),
{ ok: true, result: { allow: true }, id: 7 });
const exit = once(child, 'exit');
child.send({ op: 'close' });
assert.equal((await exit)[0], 0);
});
test('runtime excludes declared project roots even when host supplies no repoRoots', async (t) => {
const root = mkdtempSync(join(tmpdir(), 'bus-project-token-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const data = join(root, 'data');
mkdirSync(data);
const project = join(root, 'project');
mkdirSync(project);
const file = join(project, 'token');
writeFileSync(file, 'fixture-do-not-load-from-project', { mode: 0o600 });
const config = structuredClone(businesses);
config.demo.projects = { stack: { root: project } };
config.demo.roles.cto.credentials = { gitea: { file, rotateBy: '2099-01-01' } };
const { child } = launch(t),
exit = once(child, 'exit');
const result = await boot(child, { dataRoot: data, businesses: config });
assert.equal(result.ok, false);
assert.equal(result.error, 'credential-location');
assert.equal((await exit)[0], 2);
});
test('a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it', async (t) => {
const root = mkdtempSync(join(tmpdir(), 'bus-bind-refusal-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const { child } = launch(t);
const record = {
business: 'demo',
role: 'cto',
run: 'one',
harness: 'pi',
pid: process.pid,
startTime: '1',
};
const ready = await boot(child, { dataRoot: root, businesses, launches: [record] });
assert.equal(ready.ok, true);
const client = new Client({ path: ready.path, cap: ready.launches[0].cap });
await client.call('role.claim');
const reply = once(child, 'message');
child.send({ op: 'bindLaunch', record });
assert.deepEqual((await reply)[0], { ok: false, error: 'duplicate-run' });
assert.equal((await client.call('agents'))[0].holder_run, 'one');
const bad = once(child, 'message'),
exit = once(child, 'exit');
child.send({ op: 'not-a-protocol-verb' });
assert.equal((await bad)[0].ok, false);
assert.equal((await exit)[0], 2);
});