Files
stack/packages/organization/tests/csuite.test.mjs

99 lines
5.6 KiB
JavaScript

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