Files
jason.woltje 079ced93f3 feat(packs): pack, board and workflow verbs, routes and CLI words (#1873 SP4)
Design section 8. The bus answers packs, pack.preview, pack.apply,
board.set and workflow.set to the organization's human, and their
.console forms to the owner's Console cap. The broker runs the section 3
merge, the plan digest (pack-plan-stale on a mismatch) and the held
credential check. The organization writer keeps packs, board and
workflows out of the generic write and refuses definition writes to
every actor name an agent carries, project agents included.

mosaic org pack list|preview|apply, board set and workflow set|remove
go over the organization's bus as its human and refuse inside an agent
shell before any file is read. The Console serves GET /api/orgs/<id>/packs,
packs/preview, packs/apply, board and workflows/<name> (POST and
DELETE) to the root owner's session only, writes under --send only.
2026-11-12 16:51:46 -06:00

85 lines
4.9 KiB
JavaScript

// Starter packs SP4: the writer's fences for the pack keys. The generic write never changes packs,
// board or workflows (only applyPack does), and every actor name an agent carries, a project agent's
// included, is refused a write of definitions.
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync, rmSync } from "node:fs";
import { join } from "node:path";
import { organizationWriter } from "../src/organization.mjs";
import { agentActors } from "../src/definitions.mjs";
import { PACKS_MAX, WORKFLOWS_MAX, checkPacks, checkWorkflows } from "../src/organization-config.mjs";
import { businessDoc, rolesCopy, tmp, writeJson } from "./helpers.mjs";
const assertClean = () => {};
const PLAIN = { role: { tools: ["read"], network: "none", authority: { withinRole: [], crossRole: [] }, credentials: [] }, instructions: "Read and report.\n" };
const BOARD = {
columns: [
{ state: "todo", label: "Backlog" }, { state: "in-progress", label: "Building" },
{ state: "in-review", label: "Review" }, { state: "blocked", label: "Blocked" }, { state: "done", label: "Merged" },
],
layout: "kanban",
};
const PACK = { id: "coding", version: "1.0.0", sha256: "a".repeat(64), origin: "shipped", scope: "org", project: null, proposedHires: [] };
const FLOW = { description: "One.", steps: [{ title: "Do it", state: "todo", roles: ["human"] }] };
function fixture(t) {
const root = tmp();
t.after(() => rmSync(root, { recursive: true, force: true }));
const dir = join(root, "config");
const rolesDir = rolesCopy(root);
const file = writeJson(join(dir, "businesses/acme.json"), businessDoc(root));
return { file, writer: organizationWriter({ dir, rolesDir }) };
}
const rev = (f) => f.writer.read("acme").revision;
const doc = (f) => JSON.parse(readFileSync(f.file, "utf8"));
test("SP4 the generic write refuses any change to packs, board or workflows", (t) => {
const f = fixture(t);
const before = readFileSync(f.file, "utf8");
for (const [key, value] of [["board", BOARD], ["workflows", { one: FLOW }], ["packs", []]]) {
const next = { ...doc(f), [key]: value };
assert.throws(() => f.writer.write("acme", next, { revision: rev(f), actor: "jason" }), { code: "organization-packs-protected" }, key);
}
assert.equal(readFileSync(f.file, "utf8"), before);
// Once applyPack wrote them, the generic write may carry them unchanged, but not remove them.
f.writer.applyPack("acme", { board: BOARD, workflows: { one: FLOW } }, { revision: rev(f), actor: "jason", assertClean });
const kept = doc(f);
kept.vars = { ...kept.vars, "tracker.pollSeconds": 90 };
f.writer.write("acme", kept, { revision: rev(f), actor: "jason" });
assert.equal(doc(f).vars["tracker.pollSeconds"], 90);
const { board: _board, ...dropped } = doc(f);
assert.throws(() => f.writer.write("acme", dropped, { revision: rev(f), actor: "jason" }), { code: "organization-packs-protected" });
});
test("SP4 agentActors names root instances and project agents in every form", () => {
const document = {
roles: { coder: { definition: "coder" }, chief: { definition: "chief-of-staff" } },
projects: { stack: { agents: { checker: { definition: "fact-checker" } } }, docs: {} },
};
assert.deepEqual(agentActors(document).sort(), ["checker", "checker.stack", "chief", "chief-of-staff.chief", "coder", "coder.coder", "fact-checker.checker.stack"].sort());
assert.deepEqual(agentActors(null), []);
});
test("SP4 a project agent's actor name refuses a definition write through setDefinition and applyPack", (t) => {
const f = fixture(t);
let { revision } = f.writer.read("acme");
({ revision } = f.writer.setProjectAgent("acme", { revision, project: "stack", name: "checker", entry: { definition: "fact-checker" } }, "jason"));
const before = readFileSync(f.file, "utf8");
for (const actor of ["checker", "checker.stack", "fact-checker.checker.stack"]) {
assert.throws(() => f.writer.setDefinition("acme", "scout", () => PLAIN, { revision, actor, assertClean }), { code: "human-required" }, actor);
assert.throws(() => f.writer.applyPack("acme", { definitions: { scout: PLAIN }, packs: PACK }, { revision, actor, assertClean }), { code: "human-required" }, actor);
}
assert.equal(readFileSync(f.file, "utf8"), before);
f.writer.setDefinition("acme", "scout", () => PLAIN, { revision, actor: "jason", assertClean });
assert.ok(doc(f).definitions.scout);
});
test("SP4 caps: at most 32 workflows in a scope and 256 packs entries", () => {
const flows = (n) => Object.fromEntries(Array.from({ length: n }, (_, i) => [`w${i}`, FLOW]));
assert.equal(WORKFLOWS_MAX, 32);
assert.equal(PACKS_MAX, 256);
assert.equal(Object.keys(checkWorkflows(flows(32), "workflows")).length, 32);
assert.throws(() => checkWorkflows(flows(33), "workflows"), /at most 32 workflows/);
assert.throws(() => checkPacks(Array.from({ length: 257 }, () => ({})), "packs"), /at most 256 entries/);
});