Files
stack/packages/organization/tests/hard-waits.test.mjs

68 lines
3.7 KiB
JavaScript

// PRD 1.5 REQ-WAIT-1 to REQ-WAIT-3 (row 139, #1633): the hard waits in the
// closed vocabulary, and the role validator's refusal of a grant of one.
import test from "node:test";
import assert from "node:assert/strict";
import { writeFileSync } from "node:fs";
import { join } from "node:path";
import { ACTIONS, GATED_ONLY, ROUTED, THREAD_DEFINITION, END_PROTECTED } from "../src/vocabulary.mjs";
import { validateRoleDocument } from "../src/index.mjs";
import { tmp } from "./helpers.mjs";
const WAITS = ["spend", "data.delete", "network.expose", "account.create", "credential.mint", "message.external",
"git.push.protected", "deploy", "git.push.remote", "host.sudo", "project.bootstrap"];
const LEFT = ["git.merge.protected", "policy.change", "prd.approve", "role.revoke"];
const role = (authority) => {
const dir = tmp();
writeFileSync(join(dir, "r.md"), "# contract\n\n## Authority\n");
const doc = { roleVersion: 2, name: "r", title: "R", contract: "r.md", tools: ["read"], network: "none", authority,
credentials: [{ service: "gitea", scopes: ["read:issue"] }] };
return validateRoleDocument(doc, join(dir, "r.json"));
};
test("REQ-WAIT-1: GATED_ONLY is exactly the hard waits; the new actions are in the vocabulary", () => {
assert.deepEqual([...GATED_ONLY].sort(), [...WAITS].sort());
for (const action of ["data.delete", "network.expose", "account.create"]) assert.ok(ACTIONS.includes(action), action);
for (const action of LEFT) assert.ok(ACTIONS.includes(action) && !GATED_ONLY.includes(action), action);
});
// Row 155 (#1649): the broker and the organization writer read both from here, so the assistant
// can't lose its end protection by an edit to one list.
test("REQ-ROLE-2: END_PROTECTED includes the assistant's thread definition", () => {
assert.equal(THREAD_DEFINITION, "assistant");
assert.ok(END_PROTECTED.includes(THREAD_DEFINITION));
assert.ok(Object.isFrozen(END_PROTECTED));
});
// PRD 1.7 REQ-COS-3 (row 194, #1692): the chief's hire and fire left the hard waits and are routed.
test("REQ-COS-3: role.hire and role.fire are routed, not hard waits", () => {
for (const action of ["role.hire", "role.fire"]) assert.ok(ROUTED.includes(action) && !GATED_ONLY.includes(action), action);
});
// PRD 1.16 REQ-WAIT-3 and REQ-LIB-6 (row 285, #1790): library.write is in the vocabulary and routed.
test("REQ-WAIT-3: library.write is routed under REQ-LIB-6 (1.16), not a hard wait", () => {
assert.ok(ACTIONS.includes("library.write"));
assert.ok(ROUTED.includes("library.write"));
assert.ok(!GATED_ONLY.includes("library.write"));
});
test("REQ-WAIT-3: every action is routed or a hard wait, never both, and the routed list names only actions", () => {
for (const action of ROUTED) assert.ok(ACTIONS.includes(action), action);
for (const action of ACTIONS) assert.notEqual(ROUTED.includes(action), GATED_ONLY.includes(action), action);
});
test("REQ-WAIT-3: instance.name is routed under REQ-MSG-2 (1.5), not a hard wait (row 141, sage-0589)", () => {
assert.ok(ACTIONS.includes("instance.name"));
assert.ok(ROUTED.includes("instance.name"));
assert.ok(!GATED_ONLY.includes("instance.name"));
});
test("REQ-WAIT-2: a role listing a hard wait at either class is refused; the actions that left can be granted", () => {
for (const action of WAITS) {
assert.throws(() => role({ withinRole: [action], crossRole: [] }), /always gated/, action);
assert.throws(() => role({ withinRole: [], crossRole: [action] }), /always gated/, action);
}
assert.deepEqual(role({ withinRole: [], crossRole: ["git.merge.protected"] }).authority.crossRole,
["git.merge.protected"]);
assert.deepEqual(role({ withinRole: LEFT.slice(1), crossRole: [] }).authority.withinRole, LEFT.slice(1));
});