git mv packages/business -> packages/organization (@mosaic/organization). The old package stays as one-line re-export shims of each moved module, with src/business.mjs re-exporting src/organization-config.mjs and src/cli.mjs forwarding to the moved executable. BusinessError is OrganizationConfigError (one constructor, name kept BusinessError); OrganizationError stays distinct. The bus adapter gains organization-context.mjs (busOrganization), with business.mjs (busBusiness) as the same function. Import consumers, manifests, the lockfile, scripts/mosaic, stack-copy.sh COPY_PATHS (both roots), seat sandbox RUNNER_FILES and the copy closure walker (organization-imports.mjs, both roots required) move to the new path. v1 serialization, v1 writers and the stage A fixtures are unchanged; the stage A contract tests stay where they were and pass as before.
125 lines
8.0 KiB
JavaScript
125 lines
8.0 KiB
JavaScript
// Row 298 (#1804, Q228): the chief of staff's grant scope, launch.grantScope. The shape check, the
|
|
// readers' fallback, the match rules (sage-3594 Q2) and the one writer. The broker's verbs, the
|
|
// in-scope close and the executors are bus tests (packages/bus/tests/grant-scope.test.mjs).
|
|
import test, { after } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync, rmSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { instanceSettings } from "../src/instance-settings.mjs";
|
|
import { launchSettings } from "../src/launch-settings.mjs";
|
|
import { loadBusiness } from "../src/organization-config.mjs";
|
|
import { checkGrantScope, grantInScope, grantScopeOf } from "../src/vocabulary.mjs";
|
|
import { businessDoc, rolesCopy, systemFor, tmp, writeJson } from "./helpers.mjs";
|
|
|
|
const roots = [];
|
|
after(() => roots.forEach(root => rmSync(root, { recursive: true, force: true })));
|
|
|
|
const GITEA = { gitea: { agents: ["coder"], projects: ["app", "lib"] } };
|
|
const HOST = { host: { agents: ["ops"], hosts: ["web1"], commands: [["systemctl", "status", "nginx"], ["ls"]] } };
|
|
const gitea = (over = {}) => ({ method: "gitea", agent: "coder", target: { projects: ["app"] }, ...over });
|
|
const host = (commands, over = {}) => ({ method: "host", agent: "ops", host: "web1", commands, ...over });
|
|
|
|
function fixture(mutate) {
|
|
const root = tmp();
|
|
roots.push(root);
|
|
const rolesDir = rolesCopy(root);
|
|
const doc = businessDoc(root, "acme");
|
|
mutate?.(doc);
|
|
const dir = join(root, "config");
|
|
const file = writeJson(join(dir, "businesses", "acme.json"), doc);
|
|
const settings = instanceSettings({ dir, rolesDir, system: systemFor(root) });
|
|
const limits = launchSettings({ dir, rolesDir, system: systemFor(root) });
|
|
const onDisk = () => JSON.parse(readFileSync(file, "utf8"));
|
|
const log = () => readFileSync(settings.writer.logFile("acme"), "utf8").trim().split("\n").map(line => JSON.parse(line));
|
|
const revision = () => settings.read("acme").revision;
|
|
return { root, dir, rolesDir, file, settings, limits, onDisk, log, revision };
|
|
}
|
|
|
|
test("row 298: checkGrantScope takes the D2 shape and refuses anything else", () => {
|
|
assert.deepEqual(checkGrantScope({}), {});
|
|
assert.deepEqual(checkGrantScope(GITEA), GITEA);
|
|
assert.deepEqual(checkGrantScope(HOST), HOST);
|
|
assert.deepEqual(checkGrantScope({ host: { agents: ["ops"], hosts: ["web1"], commands: [["uptime"]] } }).host.commands, [["uptime"]]);
|
|
for (const bad of [
|
|
null, [], "x", { other: {} }, { gitea: [] }, { gitea: { agents: ["coder"] } }, { gitea: { agents: [], projects: ["app"] } },
|
|
{ gitea: { agents: ["coder", "coder"], projects: ["app"] } }, { gitea: { agents: ["Coder"], projects: ["app"] } },
|
|
{ gitea: { agents: ["coder"], projects: ["app"], extra: 1 } }, { host: { agents: ["ops"], hosts: ["web1"] } },
|
|
{ host: { agents: ["ops"], hosts: ["web_1"], commands: [["ls"]] } }, { host: { agents: ["ops"], hosts: ["web1"], commands: [[]] } },
|
|
{ host: { agents: ["ops"], hosts: ["web1"], commands: [["ls"], ["ls"]] } }, { host: { agents: ["ops"], hosts: ["web1"], commands: [["bin/ls"]] } },
|
|
{ host: { agents: ["ops"], hosts: ["web1"], commandPrefixes: [["ls"]] } }, { "host-enroll": {} },
|
|
])
|
|
assert.equal(checkGrantScope(bad), null, JSON.stringify(bad));
|
|
});
|
|
|
|
test("row 298: a missing scope is empty and a bad one falls back to empty, which covers nothing", () => {
|
|
assert.deepEqual(grantScopeOf(undefined), { scope: {}, fallback: false });
|
|
assert.deepEqual(grantScopeOf(GITEA), { scope: GITEA, fallback: false });
|
|
const bad = { gitea: { agents: ["coder"], projects: ["app"], extra: true } };
|
|
assert.deepEqual(grantScopeOf(bad), { scope: {}, fallback: true });
|
|
assert.equal(grantInScope(grantScopeOf(bad).scope, gitea()), false);
|
|
assert.equal(grantInScope({}, gitea()), false);
|
|
assert.equal(grantInScope({}, host([["ls"]])), false);
|
|
});
|
|
|
|
test("row 298: a Gitea grant is in scope only when its agent and every project are listed", () => {
|
|
assert.equal(grantInScope(GITEA, gitea()), true);
|
|
assert.equal(grantInScope(GITEA, gitea({ target: { projects: ["app", "lib"] } })), true);
|
|
assert.equal(grantInScope(GITEA, gitea({ target: { projects: ["app", "old"] } })), false);
|
|
assert.equal(grantInScope(GITEA, gitea({ agent: "research" })), false);
|
|
assert.equal(grantInScope(GITEA, gitea({ target: { projects: [] } })), false);
|
|
assert.equal(grantInScope(HOST, gitea()), false);
|
|
});
|
|
|
|
test("row 298 (sage-3594 Q2): host commands match whole argv tokens, and enrollment is never in scope", () => {
|
|
assert.equal(grantInScope(HOST, host([["systemctl", "status", "nginx"]])), true);
|
|
// A commands entry covers only itself: no extra trailing arguments, no shorter prefix.
|
|
assert.equal(grantInScope(HOST, host([["systemctl", "status", "nginx", "--now"]])), false);
|
|
assert.equal(grantInScope(HOST, host([["systemctl", "status"]])), false);
|
|
// No scope entry permits extending the requested command prefix.
|
|
assert.equal(grantInScope(HOST, host([["ls"]])), true);
|
|
assert.equal(grantInScope(HOST, host([["ls", "-l", "/var"]])), false);
|
|
assert.equal(grantInScope(HOST, host([["lsblk"]])), false);
|
|
assert.equal(grantInScope(HOST, host([["ls"], ["lsblk"]])), false);
|
|
assert.equal(grantInScope(HOST, host([["ls"]], { host: "web2" })), false);
|
|
assert.equal(grantInScope(HOST, host([["ls"]], { agent: "coder" })), false);
|
|
assert.equal(grantInScope(HOST, { method: "host-enroll", host: "web1" }), false);
|
|
assert.equal(grantInScope({ ...HOST, "host-enroll": true }, { method: "host-enroll", host: "web1" }), false);
|
|
});
|
|
|
|
test("row 298: a bad grantScope in the file still loads, and the read shows the fallback", () => {
|
|
const f = fixture(doc => { doc.launch.grantScope = { gitea: "everything" }; });
|
|
const business = loadBusiness("acme", { dir: f.dir, rolesDir: f.rolesDir });
|
|
assert.deepEqual(business.launch.grantScope, { gitea: "everything" });
|
|
assert.deepEqual(f.settings.grantScopeRead("acme"), { business: "acme", revision: f.revision(), scope: {}, fallback: true });
|
|
});
|
|
|
|
test("row 298: the writer checks the scope, logs the change and removes the key for {}", () => {
|
|
const f = fixture();
|
|
assert.deepEqual(f.settings.grantScopeRead("acme").scope, {});
|
|
const r = f.settings.grantScope("acme", { revision: f.revision(), scope: GITEA }, "jason");
|
|
assert.deepEqual(r.scope, GITEA);
|
|
assert.deepEqual(r.old, {});
|
|
assert.ok(typeof r.transaction === "string" && r.transaction.length > 0);
|
|
assert.deepEqual(f.onDisk().launch.grantScope, GITEA);
|
|
assert.equal(f.log().at(-1).actor, "jason");
|
|
const back = f.settings.grantScope("acme", { revision: f.revision(), scope: {} }, "jason");
|
|
assert.deepEqual(back.old, GITEA);
|
|
assert.equal(Object.hasOwn(f.onDisk().launch, "grantScope"), false);
|
|
const bytes = readFileSync(f.file, "utf8");
|
|
assert.throws(() => f.settings.grantScope("acme", { revision: f.revision(), scope: { gitea: {} } }, "jason"), e => e.code === "invalid-request");
|
|
assert.throws(() => f.settings.grantScope("acme", { revision: f.revision(), scope: GITEA, extra: 1 }, "jason"), e => e.code === "invalid-request");
|
|
assert.throws(() => f.settings.grantScope("acme", { revision: "0".repeat(64), scope: GITEA }, "jason"), e => e.code === "revision-conflict");
|
|
assert.throws(() => f.settings.grantScope("acme", { revision: f.revision(), scope: {} }, "jason"), e => e.code === "organization-no-change");
|
|
assert.equal(readFileSync(f.file, "utf8"), bytes);
|
|
});
|
|
|
|
test("row 298 (sage-3594 Q1): the general writers can't write grantScope", () => {
|
|
const f = fixture();
|
|
const bytes = readFileSync(f.file, "utf8");
|
|
assert.throws(() => f.limits.set("acme", { revision: f.revision(), grantScope: GITEA }, "jason"));
|
|
assert.throws(() => f.limits.set("acme", { revision: f.revision(), grantScope: GITEA }, "chief-of-staff.chief"));
|
|
assert.throws(() => f.settings.set("acme", { revision: f.revision(), instance: "coder", op: "change", grantScope: GITEA }, "jason"), e => e.code === "invalid-request");
|
|
assert.throws(() => f.settings.veto("acme", { revision: f.revision(), op: "keep", name: "coder", grantScope: GITEA }, "jason"), e => e.code === "invalid-request");
|
|
assert.equal(readFileSync(f.file, "utf8"), bytes);
|
|
});
|