// 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/); });