feat(harness): meta-harness and PM launching off T3 (#1523, row 41)

Slice 1 S6. packages/harness runs a role session under the pi or claude
adapter behind a tool gate, and packages/seat holds the session and its
process. The bus host gains a launch socket: `mosaic bus start
<business> --pm <harness>:<model>` runs the PM without a window, the PM
launches role sessions through the broker within the business file's
`launch.max`, and every launch, refusal and end is a launch-log line and
a broker event. `mosaic talk`, `mosaic launches off|on|list` and
`mosaic stop <run>` are new CLI verbs. packages/bus gains the IPC ops
identity, authorize, refuse, endLaunch and credentialStatus, plus
Broker.endLaunch (decision 77); no socket verb, event kind or schema
changed. The session environment is an allowlist, and the runner exits
before claiming if a founder credential reaches it.

Filbert built it (agents/filbert/work/s6). Darkwing reviewed on #1523:
round 1 changes (26991/26995), round 2 changes (27004/27010), round 3
changes (27012/27032), round 4 approve (27033/27034) on manifest
08a78972, 42 paths.

Sage's gate on 64b0f9f1 (12:32Z), sequential and teed: node suites
business 60, bus 74, cli 83, control-board 124, conversation 182,
discord 178, harness 56, ledger 78, mosaic 69, queue 149, runs 41,
seat 27, tasks 51, webui 39, every one 0 failed; test-auth 15,
test-conductor 17, test-config 24, test-discord 66,
test-extension-package 18, test-foundation 44, test-queue 27,
test-release 14, test-task 98 and 26 without Docker, all 0 failed.
64b0f9f1..ee7f909b touches none of the 42 paths. It landed after the
Q14 hold ended at 2026-10-11T17:19:23.857Z. The recorded PM-to-coder
run on a scratch data root is still owed before row 41 moves to done.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-11 12:22:50 -05:00
co-authored by Claude Opus 5.5
parent ee7f909bd3
commit fe5c68fa03
42 changed files with 4998 additions and 63 deletions
+25
View File
@@ -60,6 +60,31 @@ The embedded runtime offers the same `bindLaunch(record)` wrapper, which also
updates the human ancestry registry. S6 must use that wrapper. Do not use the
underlying Broker's test-level binding API to bypass runtime process checks.
A rebind of a run that already has `session.ended` refuses with `run-ended`,
also after a restart, and a refused bind leaves the run unbound.
A request that carries a safe-integer `id` gets the same `id` on its reply,
refusals included, so the host can match each reply to its request
(`packages/cli/README.md`). A request without one gets a reply without one.
S6 adds launch ops on the same IPC channel, one reply each
(`{ok:true,result}` or `{ok:false,error}`), none of them socket verbs:
- `{op:'identity',cap}`: the business, role and run a capability names.
- `{op:'authorizeLaunch',cap,instance}`: `authorize(cap,'role.launch',
{target:instance})`; a refusal is also recorded as `action.refused`.
- `{op:'refuse',cap,code}`: records a refusal the host decided itself
(instance list, capacity, a failed spawn) as `action.refused` against the
caller, the code only.
- `{op:'endLaunch',record}`: `{business,run,reason,exitCode?}` for a run
this broker bound. It writes `session.ended` `{reason,exitCode,released}`,
releases the run's claim if it still holds it, and drops every capability
of the run. A second end refuses with `run-ended`, an unbound run with
`unknown-run`. `reason` is an identifier the launcher picks
(`packages/cli/README.md`, "Sessions").
- `{op:'credentialStatus',business,role}`: the role's `{service,date,state}`
rows, never a token.
`{op:'close'}`, SIGTERM and SIGINT close the socket and DB and remove the owned
writer lock. Losing the host IPC channel closes with exit 2. Startup or host
protocol failures return a static error code and exit 2, never a raw exception.
+36 -2
View File
@@ -81,7 +81,8 @@ export class Broker {
#store;
#businesses;
#sessions = new Map();
#runs = new Set();
#runs = new Map();
#ended = new Set();
#secretCheck;
#clock = 0;
constructor({ store, businesses, secretCheck = () => {} }) {
@@ -169,7 +170,6 @@ export class Broker {
if (record.address !== undefined) string(record.address, 1024);
const key = record.business + ':' + record.run;
if (this.#runs.has(key)) fail('duplicate-run');
this.#runs.add(key);
const session = { ...record, address: record.address ?? null, human: false };
this.#secretCheck(record);
const old = this.#store.get(
@@ -185,9 +185,43 @@ export class Broker {
};
if (old) {
if (old.actor_role !== record.role || old.body !== JSON.stringify(body)) fail('launch-record-mismatch');
// An ended run stays ended across broker restarts.
if (this.#store.get("SELECT 1 FROM events WHERE business=? AND kind='session.ended' AND subject=?", record.business, record.run))
fail('run-ended');
} else this.#store.transaction(() => this.#event(session, 'session.launched', body, record.run));
this.#runs.set(key, session);
return this.#cap(session);
}
// Trusted launcher API, the counterpart of bindLaunch and likewise NOT a socket verb. The S6 host
// calls it once when a launched run's process exits: session.ended is written, the run's claim is
// released if it still holds one, and every capability of the run stops working.
endLaunch(record) {
keys(record, ['business', 'run', 'reason', 'exitCode'], ['business', 'run', 'reason']);
this.#business(identifier(record.business));
const key = record.business + ':' + identifier(record.run);
const session = this.#runs.get(key) ?? fail('unknown-run');
if (this.#ended.has(key)) fail('run-ended');
identifier(record.reason);
const exitCode = record.exitCode ?? null;
if (exitCode !== null && !Number.isSafeInteger(exitCode)) fail('invalid-request');
this.#ended.add(key);
for (const [cap, s] of this.#sessions)
if (!s.human && !s.reader && s.business === record.business && s.run === record.run) this.#sessions.delete(cap);
return this.#store.transaction(() => {
const h = this.#holder(session.business, session.role);
const released = h?.op === 'claim' && h.holder_run === session.run;
if (released) this.#claimEnd(session, h, 'release', null);
this.#event(session, 'session.ended', { reason: record.reason, exitCode, released }, session.run);
return { released };
});
}
// Trusted launcher API: records a refusal the host decided itself (instance list, capacity,
// credentials) as action.refused against the caller, the way request() records its own.
refuse(cap, code) {
const s = this.#session(cap);
if (!/^[a-z-]{1,64}$/.test(code)) fail('invalid-request');
return this.#refused(s, new BusError(code), null).code;
}
// Trusted human transport invokes only AFTER checking its CLI process and launch ancestry.
bindHuman(record) {
keys(record, ['business', 'human', 'via', 'outsideAgent'], ['business', 'human', 'via', 'outsideAgent']);
+23 -3
View File
@@ -2,6 +2,17 @@
// never command arguments, stdout or service-token-bearing environment variables.
import { startBroker } from './runtime.mjs';
import { BusError } from './broker.mjs';
const LAUNCH_OPS = new Set(['identity', 'authorizeLaunch', 'refuse', 'endLaunch', 'credentialStatus']);
function launchOp(m) {
if (m.op === 'identity') return runtime.identity(m.cap);
if (m.op === 'authorizeLaunch') return runtime.authorizeLaunch(m.cap, m.instance);
if (m.op === 'refuse') return runtime.refuse(m.cap, m.code);
if (m.op === 'endLaunch') return runtime.endLaunch(m.record);
return runtime.credentialStatus(m.business, m.role);
}
// The host's request id comes back on the reply, so a late reply can't
// answer a later request (host.mjs). A message without one gets none.
const tag = (message, reply) => (Number.isSafeInteger(message?.id) ? { ...reply, id: message.id } : reply);
let runtime,
booted = false,
closing = false;
@@ -25,9 +36,18 @@ if (!process.send) {
try {
if (message?.op === 'bindLaunch' && runtime) {
try {
process.send({ ok: true, launch: runtime.bindLaunch(message.record) });
process.send(tag(message, { ok: true, launch: runtime.bindLaunch(message.record) }));
} catch (e) {
process.send({ ok: false, error: e instanceof BusError ? e.code : 'bind-refused' });
process.send(tag(message, { ok: false, error: e instanceof BusError ? e.code : 'bind-refused' }));
}
return;
}
// S6 launcher ops, one reply each, like bindLaunch.
if (LAUNCH_OPS.has(message?.op) && runtime) {
try {
process.send(tag(message, { ok: true, result: launchOp(message) }));
} catch (e) {
process.send(tag(message, { ok: false, error: e instanceof BusError ? e.code : 'launch-op-refused' }));
}
return;
}
@@ -52,7 +72,7 @@ if (!process.send) {
}
process.send({ ok: true, path: runtime.path, launches: runtime.launches, readers: runtime.readers });
} catch (e) {
process.send?.({ ok: false, error: e instanceof BusError ? e.code : 'startup-refused' }, () =>
process.send?.(tag(message, { ok: false, error: e instanceof BusError ? e.code : 'startup-refused' }), () =>
close(2),
);
}
+24 -1
View File
@@ -5,7 +5,7 @@ import { Broker, BusError } from './broker.mjs';
import { Credentials } from './credentials.mjs';
import { serve } from './server.mjs';
import { verifyHuman } from './human.mjs';
// Trusted host API. S1 supplies resolved definitions, S6 supplies launch records.
// Trusted host API. S1 supplies resolved definitions, S6 supplies launch records and ends them.
// Neither a business-file writer nor a socket-accessible configuration endpoint.
// `tasks` is S3's adapter factory: ({broker, credentials, businesses}) => {handle, timeout, close}.
export async function startBroker({
@@ -68,11 +68,34 @@ export async function startBroker({
return broker.bindHuman({ business, human, via: 'cli', outsideAgent: true });
},
});
// S6 launcher operations. The host decides capacity and the instance list; the broker
// authorizes role.launch and keeps the evidence, refusals included.
function authorizeLaunch(cap, instance) {
try {
return broker.authorize(cap, 'role.launch', { target: instance });
} catch (e) {
if (!(e instanceof BusError)) throw e;
broker.refuse(cap, e.code);
throw e;
}
}
// Metadata only (service, date, state); the token stays in Credentials.
function credentialStatus(business, role) {
return credentials
.status()
.filter((c) => c.instance === business + '/' + role)
.map(({ service, date, state }) => ({ service, date, state }));
}
return {
broker,
credentials,
path,
bindLaunch,
endLaunch: (r) => broker.endLaunch(r),
identity: (cap) => broker.identity(cap),
authorizeLaunch,
refuse: (cap, code) => broker.refuse(cap, code),
credentialStatus,
launches: bound,
readers: readCaps,
async close() {
+192
View File
@@ -0,0 +1,192 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { fork } from 'node:child_process';
import { once } from 'node:events';
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Store } from '../src/store.mjs';
import { Broker } from '../src/broker.mjs';
import { Client } from '../src/client.mjs';
const businesses = {
demo: {
id: 'demo',
human: 'jason',
arbiters: { technical: 'cto', delivery: 'pm' },
launch: { by: 'pm', instances: ['coder'], max: { opus: 2, sonnet: 2 } },
roles: {
pm: { authority: { withinRole: ['message.send', 'role.launch'], crossRole: [] } },
cto: { authority: { withinRole: ['message.send'], crossRole: [] } },
coder: { authority: { withinRole: ['message.send'], crossRole: [] } },
},
},
};
function setup(t) {
const root = mkdtempSync(join(tmpdir(), 'bus-end-'));
const store = new Store(root);
const b = new Broker({ store, businesses });
t.after(() => {
store.close();
rmSync(root, { recursive: true, force: true });
});
const agent = (role, run = role + '-run') => b.bindLaunch({ business: 'demo', role, run, harness: 'pi' });
return { b, store, agent };
}
const call = (b, cap, verb, args = {}) => b.request(cap, { verb, args });
test('endLaunch writes session.ended, releases the run claim and kills its capabilities', (t) => {
const { b, store, agent } = setup(t);
const c = agent('coder');
call(b, c, 'role.claim');
assert.deepEqual(b.endLaunch({ business: 'demo', run: 'coder-run', reason: 'stopped', exitCode: 0 }), {
released: true,
});
const ended = store.get("SELECT * FROM events WHERE kind='session.ended'");
assert.equal(ended.subject, 'coder-run');
assert.equal(ended.actor_role, 'coder');
assert.equal(ended.actor_run, 'coder-run');
assert.deepEqual(JSON.parse(ended.body), { reason: 'stopped', exitCode: 0, released: true });
const claim = store.get("SELECT * FROM role_claims WHERE role='coder' ORDER BY seq DESC LIMIT 1");
assert.equal(claim.op, 'release');
assert.equal(claim.by, 'coder-run');
assert.throws(() => call(b, c, 'message.send', { to: 'pm', body: 'still here?' }), /unauthenticated/);
assert.throws(() => b.identity(c), /unauthenticated/);
});
test('endLaunch refuses an unknown run, a second end and a rebind of the ended run', (t) => {
const { b, agent } = setup(t);
agent('coder');
assert.throws(() => b.endLaunch({ business: 'demo', run: 'nope', reason: 'exited' }), /unknown-run/);
assert.throws(() => b.endLaunch({ business: 'demo', run: 'coder-run', reason: 'bad reason' }), /invalid-request/);
assert.throws(
() => b.endLaunch({ business: 'demo', run: 'coder-run', reason: 'exited', exitCode: 1.5 }),
/invalid-request/,
);
assert.deepEqual(b.endLaunch({ business: 'demo', run: 'coder-run', reason: 'exited', exitCode: null }), {
released: false,
});
assert.throws(() => b.endLaunch({ business: 'demo', run: 'coder-run', reason: 'exited' }), /run-ended/);
assert.throws(() => agent('coder'), /duplicate-run/);
});
test('a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound', (t) => {
const { b, store, agent } = setup(t);
agent('coder');
agent('pm');
b.endLaunch({ business: 'demo', run: 'coder-run', reason: 'stopped', exitCode: 0 });
const again = new Broker({ store, businesses });
assert.throws(() => again.bindLaunch({ business: 'demo', role: 'coder', run: 'coder-run', harness: 'pi' }), /run-ended/);
assert.throws(() => again.endLaunch({ business: 'demo', run: 'coder-run', reason: 'host-lost' }), /unknown-run/);
assert.throws(() => again.bindLaunch({ business: 'demo', role: 'cto', run: 'pm-run', harness: 'pi' }), /launch-record-mismatch/);
again.bindLaunch({ business: 'demo', role: 'pm', run: 'pm-run', harness: 'pi' });
assert.deepEqual(again.endLaunch({ business: 'demo', run: 'pm-run', reason: 'host-lost' }), { released: false });
assert.equal(store.get("SELECT count(*) n FROM events WHERE kind='session.ended'").n, 2);
});
test('endLaunch leaves a claim another run took alone', (t) => {
const { b, store, agent } = setup(t);
const first = agent('coder', 'c1');
call(b, first, 'role.claim');
call(b, first, 'role.release');
call(b, agent('coder', 'c2'), 'role.claim');
assert.deepEqual(b.endLaunch({ business: 'demo', run: 'c1', reason: 'exited', exitCode: 0 }), { released: false });
assert.equal(store.get("SELECT holder_run FROM role_claims ORDER BY seq DESC LIMIT 1").holder_run, 'c2');
});
test('refuse records action.refused against the caller with the code only', (t) => {
const { b, store, agent } = setup(t);
const pm = agent('pm');
assert.equal(b.refuse(pm, 'capacity-full'), 'capacity-full');
const e = store.get("SELECT * FROM events WHERE kind='action.refused'");
assert.equal(e.actor_role, 'pm');
assert.deepEqual(JSON.parse(e.body), { code: 'capacity-full' });
assert.throws(() => b.refuse(pm, 'Not A Code'), /invalid-request/);
assert.throws(() => b.refuse('f'.repeat(64), 'capacity-full'), /unauthenticated/);
});
function launch(t) {
const child = fork(new URL('../src/process.mjs', import.meta.url), [], { stdio: ['ignore', 'pipe', 'pipe', 'ipc'] });
t.after(() => {
if (child.exitCode === null) child.kill('SIGKILL');
});
return child;
}
async function ask(child, message) {
const reply = once(child, 'message');
child.send(message);
return (await reply)[0];
}
test('launches off refuses role.launch with launch-revoked until launches on', (t) => {
const { b, agent } = setup(t);
const pm = agent('pm');
call(b, pm, 'role.claim');
const human = b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true });
assert.equal(b.authorize(pm, 'role.launch', { target: 'coder' }).class, 'within-role');
call(b, human, 'launch.revoke');
assert.throws(() => b.authorize(pm, 'role.launch', { target: 'coder' }), { code: 'launch-revoked' });
// Other actions are untouched; only launching stops.
assert.equal(b.authorize(pm, 'message.send').class, 'within-role');
call(b, human, 'launch.restore');
assert.equal(b.authorize(pm, 'role.launch', { target: 'coder' }).class, 'within-role');
});
test('broker process: launch ops authorize role.launch, record refusals and end runs', async (t) => {
const root = mkdtempSync(join(tmpdir(), 'bus-launch-ops-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const token = join(root, 'token');
writeFileSync(token, 'fixture-token-6c1d', { mode: 0o600 });
const config = structuredClone(businesses);
config.demo.roles.coder.credentials = { vikunja: { file: token, expires: '2099-01-01' } };
const child = launch(t);
const data = join(root, 'data');
mkdirSync(data);
const ready = await ask(child, {
op: 'boot',
config: { dataRoot: data, businesses: config, launches: [], readers: [] },
});
assert.equal(ready.ok, true, ready.error);
const record = (role, run) => ({ business: 'demo', role, run, harness: 'pi', pid: process.pid, startTime: '1' });
const pm = (await ask(child, { op: 'bindLaunch', record: record('pm', 'pm-1') })).launch.cap;
const cto = (await ask(child, { op: 'bindLaunch', record: record('cto', 'cto-1') })).launch.cap;
assert.deepEqual((await ask(child, { op: 'identity', cap: pm })).result, {
business: 'demo',
role: 'pm',
run: 'pm-1',
human: null,
});
assert.equal((await ask(child, { op: 'identity', cap: 'f'.repeat(64) })).error, 'unauthenticated');
// A launch is authorized for the role's holder only, so both runs claim first, as the runner does.
const reader = new Client({ path: ready.path, cap: cto });
await new Client({ path: ready.path, cap: pm }).call('role.claim');
await reader.call('role.claim');
const ok = await ask(child, { op: 'authorizeLaunch', cap: pm, instance: 'coder' });
assert.equal(ok.ok, true);
assert.equal(ok.result.class, 'within-role');
// The cto is not the business's launcher, so role.launch is gated for it and the refusal is evidence.
const no = await ask(child, { op: 'authorizeLaunch', cap: cto, instance: 'coder' });
assert.equal(no.ok, false);
assert.equal(no.error, 'decision-required');
assert.equal((await ask(child, { op: 'refuse', cap: pm, code: 'capacity-full' })).result, 'capacity-full');
assert.deepEqual((await ask(child, { op: 'credentialStatus', business: 'demo', role: 'coder' })).result, [
{ service: 'vikunja', date: '2099-01-01', state: 'valid' },
]);
assert.deepEqual((await ask(child, { op: 'credentialStatus', business: 'demo', role: 'pm' })).result, []);
const end = await ask(child, { op: 'endLaunch', record: { business: 'demo', run: 'pm-1', reason: 'stopped', exitCode: 0 } });
assert.deepEqual(end, { ok: true, result: { released: true } });
// The host's request id comes back on every launch-op and bind reply, refusals too.
assert.equal((await ask(child, { op: 'identity', cap: cto, id: 7 })).id, 7);
assert.deepEqual(await ask(child, { op: 'identity', cap: 'f'.repeat(64), id: 8 }), { ok: false, error: 'unauthenticated', id: 8 });
assert.equal((await ask(child, { op: 'bindLaunch', record: record('coder', 'coder-1'), id: 9 })).id, 9);
assert.deepEqual(await ask(child, { op: 'bindLaunch', record: record('coder', 'coder-1'), id: 10 }), { ok: false, error: 'duplicate-run', id: 10 });
await assert.rejects(new Client({ path: ready.path, cap: pm }).call('agents'), /unauthenticated/);
const trail = await reader.call('trail', { subject: 'pm-1' });
assert.deepEqual(
trail.filter((r) => r.table === 'events').map((r) => r.kind),
['session.launched', 'session.ended'],
);
const exit = once(child, 'exit');
child.send({ op: 'close' });
assert.equal((await exit)[0], 0);
});