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
+82
View File
@@ -203,6 +203,88 @@ test("a second host for the same data root refuses with exit 3 while the first r
assert.equal(await host.close(0), 0);
});
test("a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1", { timeout: 30000 }, async (t) => {
const root = tmp(t);
const f = fixture(root);
makeDeployment(root);
const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env });
const logs = [];
const host = await startHost({ boot, business: "acme", requestTimeoutMs: 1000, log: (l) => logs.push(l) });
// Hooks run in order: the broker resumes before the close, which a
// stopped broker would hold.
t.after(() => {
try {
process.kill(host.pids.broker, "SIGCONT");
} catch {}
});
t.after(() => host.close(0));
const record = (role, run) => ({ business: "acme", role, run, harness: "pi", pid: process.pid, startTime: startTimeOf(process.pid) });
const coder = await host.bindLaunch(record("coder", "coder-run"));
assert.equal((await host.op({ op: "identity", cap: coder.cap })).role, "coder");
// Stall the broker with a request waiting and a bind queued behind it.
process.kill(host.pids.broker, "SIGSTOP");
const code = (p) => p.then((v) => ({ answered: v }), (e) => e.code);
const first = code(host.op({ op: "identity", cap: "bogus" }));
const second = code(host.bindLaunch(record("reviewer", "reviewer-run")));
assert.equal(await first, "broker-channel-broken");
assert.equal(await second, "broker-channel-broken", "a queued request never reaches the broker");
process.kill(host.pids.broker, "SIGCONT");
// The broker answers the stalled request late; nothing takes that reply.
assert.equal(await host.done, 1);
assert.ok(logs.some((l) => /^broker channel broken \(no reply within 1 s\); stopping the host$/.test(l)), logs.join("\n"));
assert.equal(await code(host.op({ op: "identity", cap: coder.cap })), "broker-channel-broken");
});
test("a broker reply with another request's id, or none, breaks the channel and the host exits 1", { timeout: 30000 }, async (t) => {
// The broker echoes whatever id it's sent, so changing the id on the way
// out gives the host the reply a confused broker would send.
let what, tamper;
spySends(t, (m) => m?.cap === "tamper" && tamper(m));
for ([what, tamper] of [
["another id", (m) => (m.id += 1000)],
["no id", (m) => delete m.id],
]) {
const root = tmp(t);
const f = fixture(root);
makeDeployment(root);
const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env });
const logs = [];
const host = await startHost({ boot, business: "acme", log: (l) => logs.push(l) });
t.after(() => host.close(0));
const record = (role, run) => ({ business: "acme", role, run, harness: "pi", pid: process.pid, startTime: startTimeOf(process.pid) });
const coder = await host.bindLaunch(record("coder", "coder-run"));
await assert.rejects(host.op({ op: "identity", cap: "tamper" }), (e) => e.code === "broker-channel-broken", what);
await assert.rejects(host.op({ op: "identity", cap: coder.cap }), (e) => e.code === "broker-channel-broken", what);
assert.equal(await host.done, 1, what);
assert.ok(logs.includes("broker channel broken (reply for another request); stopping the host"), `${what}: ${logs.join("\n")}`);
}
});
test("a broker reply with no request waiting breaks the channel and the host exits 1", { timeout: 30000 }, async (t) => {
// A request sent to the real broker past the host's queue: its reply
// arrives when nothing is waiting (Darkwing round 2, Mr).
const children = [];
const onChild = ({ process: child }) => children.push(child);
subscribe("child_process", onChild);
t.after(() => unsubscribe("child_process", onChild));
const root = tmp(t);
const f = fixture(root);
makeDeployment(root);
const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env });
const logs = [];
const host = await startHost({ boot, business: "acme", log: (l) => logs.push(l) });
t.after(() => host.close(0));
const record = { business: "acme", role: "coder", run: "coder-run", harness: "pi", pid: process.pid, startTime: startTimeOf(process.pid) };
const coder = await host.bindLaunch(record);
children.find((c) => c.pid === host.pids.broker).send({ op: "identity", cap: coder.cap, id: 1_000_000 });
const late = new Promise((r) => setTimeout(r, 5000, "still running").unref());
assert.equal(await Promise.race([host.done, late]), 1);
assert.ok(logs.includes("broker channel broken (reply with no request waiting); stopping the host"), logs.join("\n"));
await assert.rejects(host.op({ op: "identity", cap: coder.cap }), (e) => e.code === "broker-channel-broken");
});
test("a notifier that refuses stops the broker and the host refuses with exit 3", async (t) => {
const root = tmp(t);
const f = fixture(root);