Files
stack/packages/harness/tests/bundle.test.mjs
T
jason.woltjeandClaude Opus 5.5 d240645df0 cli, seat, harness: a project clone in each role workspace (#1585)
Row 91, round 3. Darkwing built it, Dewey approved it (comment 28150) on
manifest ca570607 (8 paths, base 2b2a9e14). At each launch the host
makes or updates a git clone of the project in the role's workspace,
and the session prompt names each clone. Round 3 closes Filbert's round
2 escape: a clone with a submodule is kept as it is, and no host git
recurses into one (decision 130 thread). Live at the next bus restart;
the prompt lines appear only when the launcher passes checkouts.
Sage gate on fc6d5822 plus the patch, 14:18:25Z to 14:22:27Z: bus 115,
business 60, cli 131, control-board 124, conversation 182, discord 178,
harness 67, ledger 78, mosaic 69, queue 150, runs 43, seat 89, tasks 89,
webui 104, all fail 0. Every scripts/test-*.sh exited 0; test-release
ran 14 cases.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 09:23:10 -05:00

146 lines
8.1 KiB
JavaScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { BundleError, buildBundle, sessionModel, SOURCES } from "../src/bundle.mjs";
import { resolvedFor, scratch } from "./helpers.mjs";
const sha = (p) => createHash("sha256").update(readFileSync(p)).digest("hex");
const mode = (p) => statSync(p).mode & 0o777;
function build(t, resolved, extra = {}) {
const dir = scratch(t);
const workspace = join(dir, "ws");
mkdirSync(workspace);
const bundle = buildBundle({
dir: join(dir, "bundle"),
resolved,
contract: "# Contract\n\nDo the work.\n",
tracker: true,
credentials: [{ service: "gitea", date: "2099-01-01", state: "valid" }],
session: { run: "run-1", launchedBy: "pm", workspace, toolSocket: join(dir, "tools.sock"), turnMarker: join(dir, "turn.done") },
piVersion: "0.85.1",
claudeVersion: "2.1.0 (Claude Code)",
...extra,
});
return { dir, workspace, ...bundle };
}
test("sessionModel: agent vars win, then the system's execution settings", (t) => {
assert.deepEqual(sessionModel(resolvedFor(t, "pm")), { harness: "pi", provider: "zai", model: "claude-opus-5-5", thinking: null });
assert.deepEqual(sessionModel(resolvedFor(t, "coder")), { harness: "pi", provider: "zai", model: "glm-5.3-flash", thinking: null });
// The test system's adapter is "mock", which isn't a session harness.
assert.throws(() => sessionModel(resolvedFor(t, "reviewer")), (e) => e instanceof BundleError && /harness "mock"/.test(e.message));
});
test("a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory", (t) => {
const b = build(t, resolvedFor(t, "pm"));
assert.equal(mode(join(b.dir, "bundle")), 0o700);
for (const f of Object.values(b.files)) assert.equal(mode(f), 0o600, f);
assert.deepEqual(Object.keys(b.files).sort(), ["manifest", "policy", "prompt", "tools"]);
const prompt = readFileSync(b.files.prompt, "utf8");
assert.match(prompt, /^# Contract\n\nDo the work\.\n\n## This session\n/);
assert.match(prompt, /Role instance: pm \(definition pm\)\. Run: run-1\./);
assert.match(prompt, /Bus tools: send_message, launch, /);
// Row 90 (#1584): PM fault 10 (#228 c91).
assert.match(prompt, /^- Only the human stops a run, with `mosaic stop`\. If you think a run should stop, raise a role\.revoke decision with blocking false/m);
assert.deepEqual(JSON.parse(readFileSync(b.files.policy, "utf8")), {
harness: "pi",
workspace: b.workspace,
tools: [...b.manifest.builtins],
typed: b.tools.map((x) => x.name),
network: "api-only",
needs: ["gitea", "vikunja"],
credentials: [{ service: "gitea", date: "2099-01-01", state: "valid" }],
});
const m = b.manifest;
assert.equal(m.harness, "pi");
assert.equal(m.model, "claude-opus-5-5");
assert.equal(m.piVersion, "0.85.1");
assert.equal(m.claudeVersion, null);
assert.equal(m.gateTimeout, null);
assert.deepEqual(m.skills, []);
assert.equal(m.reliesOn.length, 5);
assert.ok(m.typed.includes("launch"));
assert.deepEqual(Object.keys(m.sources).sort(), ["gate", "piExtension", "tools"]);
assert.equal(m.sources.piExtension.sha256, sha(SOURCES.piExtension));
for (const [name, digest] of Object.entries(m.files)) assert.equal(digest, sha(join(b.dir, "bundle", name)), name);
assert.deepEqual(JSON.parse(readFileSync(b.files.manifest, "utf8")), JSON.parse(JSON.stringify(m)));
});
test("a claude-code bundle adds the wrapped gate hook and the MCP config", (t) => {
const r = resolvedFor(t, "coder", (d) => (d.roles.coder.vars = { harness: "claude-code", model: "claude-sonnet-5-5" }));
const b = build(t, r);
assert.deepEqual(Object.keys(b.files).sort(), ["manifest", "mcp", "policy", "prompt", "settings", "tools"]);
const settings = JSON.parse(readFileSync(b.files.settings, "utf8"));
const hook = settings.hooks.PreToolUse[0];
assert.equal(hook.matcher, "*");
assert.equal(hook.hooks[0].timeout, 20);
assert.equal(hook.hooks[0].command, `timeout -k 2 10 '${process.execPath}' '${SOURCES.claudeGate}' '${b.files.policy}' || exit 2`);
const mcp = JSON.parse(readFileSync(b.files.mcp, "utf8"));
assert.deepEqual(mcp, { mcpServers: { mosaic: { type: "stdio", command: process.execPath, args: [SOURCES.mcpServer, b.files.tools, join(b.dir, "tools.sock")] } } });
const m = b.manifest;
assert.equal(m.piVersion, null);
assert.equal(m.claudeVersion, "2.1.0 (Claude Code)");
assert.deepEqual(m.gateTimeout, { n: 10, k: 2, hook: 20 });
assert.ok(m.builtins.includes("Bash"));
assert.ok(m.typed.every((n) => n.startsWith("mcp__mosaic__")));
assert.deepEqual(Object.keys(m.sources).sort(), ["claudeGate", "gate", "mcpServer", "tools"]);
});
// Row 89 (#1583): the prompt names the built-ins as the session's harness names them.
test("the prompt's built-in tools line is the harness's own names, as the manifest lists them", (t) => {
const line = (b) => readFileSync(b.files.prompt, "utf8").split("\n").find((l) => l.startsWith("- Built-in tools: "));
const pi = build(t, resolvedFor(t, "pm"));
assert.equal(line(pi), "- Built-in tools: read, write, edit, grep, find, ls.");
const claude = build(t, resolvedFor(t, "pm", (d) => (d.roles.pm.vars = { ...d.roles.pm.vars, harness: "claude-code", model: "claude-opus-5-5" })));
assert.equal(line(claude), "- Built-in tools: Read, Write, Edit, Grep, Glob.");
for (const b of [pi, claude]) assert.equal(line(b), `- Built-in tools: ${b.manifest.builtins.join(", ")}.`);
for (const harness of ["pi", "claude-code"]) {
const none = build(t, resolvedFor(t, "pm", (d) => (d.roles.pm.vars = { ...d.roles.pm.vars, harness, model: "claude-opus-5-5", "limits.tools": [] })));
assert.equal(line(none), "- Built-in tools: none.", harness);
}
});
// Row 91 (#1585): the prompt names each project clone, and why a kept one wasn't updated.
test("the prompt names the project clones in the workspace, and none when there are none", (t) => {
const line = (b) => readFileSync(b.files.prompt, "utf8").split("\n").filter((l) => l.startsWith("- Project clones: "));
const plain = build(t, resolvedFor(t, "pm"));
assert.deepEqual(line(plain), []);
const dir = scratch(t);
const session = (checkouts) => ({ run: "run-1", launchedBy: "pm", workspace: join(dir, "ws"), toolSocket: join(dir, "tools.sock"), turnMarker: join(dir, "turn.done"), checkouts });
const two = build(t, resolvedFor(t, "pm"), { session: session([
{ project: "stack", path: join(dir, "ws", "stack"), outcome: "updated", branch: "main" },
{ project: "site", path: join(dir, "ws", "site"), outcome: "kept", reason: "dirty" },
]) });
assert.deepEqual(line(two), [`- Project clones: stack at ${join(dir, "ws", "stack")}, on branch main; site at ${join(dir, "ws", "site")}, not updated at this launch (dirty). Each is a git clone of the project. The host fetches from its origin at each launch; the origin is outside this sandbox.`]);
const prompt = readFileSync(two.files.prompt, "utf8");
assert.ok(prompt.indexOf("- Workspace: ") < prompt.indexOf("- Project clones: "));
assert.deepEqual(line(build(t, resolvedFor(t, "pm"), { session: session([]) })), []);
});
test("a bundle is written once: an existing file refuses", (t) => {
const dir = scratch(t);
mkdirSync(join(dir, "bundle"));
writeFileSync(join(dir, "bundle", "prompt.md"), "old");
assert.throws(() => buildBundle({
dir: join(dir, "bundle"),
resolved: resolvedFor(t, "pm"),
contract: "c",
session: { run: "r", launchedBy: "pm", workspace: dir, toolSocket: join(dir, "s"), turnMarker: join(dir, "m") },
}), /EEXIST/);
assert.equal(readFileSync(join(dir, "bundle", "prompt.md"), "utf8"), "old");
});
test("a path with a single quote can't go into the hook command", (t) => {
const r = resolvedFor(t, "coder", (d) => (d.roles.coder.vars = { harness: "claude-code", model: "claude-sonnet-5-5" }));
const dir = scratch(t);
assert.throws(() => buildBundle({
dir: join(dir, "it's"),
resolved: r,
contract: "c",
session: { run: "r", launchedBy: "pm", workspace: dir, toolSocket: join(dir, "s"), turnMarker: join(dir, "m") },
}), (e) => e instanceof BundleError && /single quote/.test(e.message));
});