// The five C-suite roles (REQ-ROLE-4, REQ-ROLE-1, vikunja:19/301; Mos Q123). Each role file is // policy at least privilege: read-only tools, message.send and record.write within role, no // credentials. Only the chief of staff launches them (REQ-COS-1, REQ-LAUNCH-1). A widened file // fails here. Fixtures live in a temp directory and nothing reads the real config. import { test } from "node:test"; import assert from "node:assert/strict"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { ACTIONS, BusinessError, classify, loadBusiness, loadRole, resolveInstance, validateRoleDocument } from "../src/index.mjs"; import { businessDoc, launchDoc, REPO_ROLES, systemFor, tmp, writeJson } from "./helpers.mjs"; const CSUITE = ["chief-executive-officer", "chief-financial-officer", "chief-information-officer", "chief-information-security-officer", "chief-marketing-officer"]; const APPROVED = Object.freeze({ tools: ["read", "grep", "find", "ls"], network: "api-only", authority: { withinRole: ["message.send", "record.write"], crossRole: [] }, credentials: [], // Least privilege (vikunja:19/306): an advisory role talks to the user, the chief of staff, the pm // and the cto, so it can't send work to the coder or reviewer around the pm. messaging: { sendTo: ["human", "chief-of-staff", "pm", "cto"], receiveFrom: ["human", "chief-of-staff", "pm", "cto"] }, }); // Jason's business file has the same shape: the five instances by definition name, no credentials // or tracker bot, listed in launch.instances beside the existing launchable instances. function fixture(mutate = () => {}) { const root = tmp(); const doc = businessDoc(root, "acme"); for (const name of CSUITE) doc.roles[name] = { definition: name }; doc.launch = launchDoc("chief", ["coder", "reviewer", ...CSUITE]); mutate(doc); writeJson(join(root, "businesses", "acme.json"), doc); return { root, business: loadBusiness("acme", { dir: root, rolesDir: REPO_ROLES }) }; } const resolved = ({ root, business }, instance) => resolveInstance({ system: systemFor(root), business, instance }); test("each definition loads, with a contract file, and is exactly the approved list", () => { for (const name of CSUITE) { const role = loadRole(REPO_ROLES, name); assert.equal(role.roleVersion, 2); assert.equal(role.name, name); assert.equal(role.contract, `${name}.md`); assert.ok(existsSync(join(REPO_ROLES, `${name}.md`)), name); const { title, ...rest } = JSON.parse(readFileSync(join(REPO_ROLES, `${name}.json`), "utf8")); assert.equal(typeof title, "string"); assert.deepEqual(rest, { roleVersion: 2, name, contract: `${name}.md`, ...APPROVED }, name); const text = readFileSync(join(REPO_ROLES, `${name}.md`), "utf8"); assert.match(text, new RegExp(`^# Role contract: .* \\(${name}\\)\\n`), name); assert.match(text, /launched by the chief of staff and by no other\s+role/, name); } }); test("an organization file that declares them passes, and each resolves to least privilege", () => { const f = fixture(); for (const name of CSUITE) { const record = resolved(f, name); assert.equal(record.definition, name); assert.deepEqual(record.limits, { tools: APPROVED.tools, network: "api-only", authority: APPROVED.authority }); assert.deepEqual(record.credentials, {}); assert.equal(record.launch, null); for (const action of ACTIONS) assert.equal(classify(record, action), ["message.send", "record.write"].includes(action) ? "within" : "gated", `${name} ${action}`); assert.ok(!record.limits.tools.includes("web") && !record.limits.tools.includes("bash") && !record.limits.tools.includes("write"), name); } }); test("the chief of staff launches each one; no other role does", () => { const f = fixture(); const chief = resolved(f, "chief"); assert.deepEqual([...chief.launch.instances].sort(), ["coder", "reviewer", ...CSUITE].sort()); assert.equal(classify(chief, "role.launch"), "within"); for (const other of ["pm", "cto", "coder", "reviewer", ...CSUITE]) { const record = resolved(f, other); assert.equal(record.launch, null, other); assert.equal(classify(record, "role.launch"), "gated", other); } }); test("a C-suite role can't be made the launcher: launch.by must hold role.launch", () => { assert.throws(() => fixture((doc) => (doc.launch = launchDoc("chief-executive-officer", ["coder"]))), (e) => e instanceof BusinessError && /doesn't hold role\.launch/.test(e.message)); }); test("limits can narrow a C-suite role but never widen it", () => { const f = fixture((doc) => (doc.roles["chief-financial-officer"].vars = { "limits.authority": ["message.send", "spend", "task.create"], "limits.tools": ["read", "bash"] })); const record = resolved(f, "chief-financial-officer"); assert.deepEqual(record.limits.authority, { withinRole: ["message.send"], crossRole: [] }); assert.deepEqual(record.limits.tools, ["read"]); }); test("any unknown key, tool, action or version in a role file is refused", () => { const base = JSON.parse(readFileSync(join(REPO_ROLES, "chief-executive-officer.json"), "utf8")); const root = tmp(); writeFileSync(join(root, "chief-executive-officer.md"), "# contract\n\n## Authority\n"); const check = (extra) => validateRoleDocument({ ...base, ...extra }, join(root, "chief-executive-officer.json")); assert.equal(check({}).name, "chief-executive-officer"); for (const extra of [{ arbiter: "technical" }, { launch: {} }, { tools: ["read", "browser"] }, { roleVersion: 3 }, { authority: { withinRole: ["message.send", "deploy.prod"], crossRole: [] } }]) { assert.throws(() => check(extra), BusinessError, JSON.stringify(extra)); } });