// `mosaic access` (FED R5, #414). Every write goes to a scratch config dir and a // scratch data root. The success paths run on a stand-in human CLI (fakeCli). // The one test that runs the real human-cli.mjs does it on a scratch bus from // agent ancestry, so the bus must refuse, and nothing is written. import test from "node:test"; import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import * as nodeFs from "node:fs"; import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { startBroker } from "../../bus/src/runtime.mjs"; import { accessCommand, accessContextFor, withLock } from "../src/access.mjs"; import { CliError } from "../src/errors.mjs"; import { socketPath } from "../src/config.mjs"; import { HUMAN_CLI, humanTransport } from "../src/transport.mjs"; import { fakeCli, humanEnv, io, tmp, fixture } from "./helpers.mjs"; import { systemFor, writeJson } from "../../business/tests/helpers.mjs"; const PROJECT = "3f1c2a64-0d8e-4b7a-9c55-1a2b3c4d5e6f"; const ROWS = [{ id: PROJECT, name: "hub", repo: null }]; const CONTEXT = { providers: ["authentik"], peers: [], businesses: { acme: { human: "jason", instances: ["coder"] } }, ssoSubjects: [{ provider: "authentik", sub: "jason-sub" }], }; const DOC = { accessVersion: 1, revision: 1, users: [{ id: "alice", home: "local", subjects: [{ provider: "authentik", sub: "alice-sub" }] }], grants: [], }; // The stand-in human CLI. It answers project.list with ROWS, or refuses like a bus // that sees no human shell when FAKE_MODE is "refuse". const BODY = ` import { readFileSync, writeFileSync } from "node:fs"; const input = JSON.parse(readFileSync(0, "utf8")); writeFileSync(process.env.FAKE_LOG, JSON.stringify(input)); if (process.env.FAKE_MODE === "refuse") { process.stderr.write("human-required\\n"); process.exit(3); } process.stdout.write(JSON.stringify(${JSON.stringify(ROWS)})); `; function setup(t, { mode = "ok", doc = DOC } = {}) { const root = tmp(t, "mosaic-access-"); const config = join(root, "config"); mkdirSync(config, { recursive: true }); const dataRoot = join(root, "data"); const log = join(root, "fake.log"); const cli = fakeCli(root, BODY); const env = humanEnv({ FAKE_LOG: log, FAKE_MODE: mode }); const file = join(config, "access.json"); if (doc) writeFileSync(file, JSON.stringify(doc, null, 2), { mode: 0o600 }); const deps = { system: { dataRoot }, configDir: config, contextFor: () => CONTEXT, now: () => new Date("2026-10-20T20:00:00Z"), transport: (business) => humanTransport({ socket: join(root, "bus.sock"), business, env, cli, timeoutMs: 5000 }), }; const receipts = () => (existsSync(join(dataRoot, "state", "access-log.jsonl")) ? readFileSync(join(dataRoot, "state", "access-log.jsonl"), "utf8") : ""); return { root, config, file, deps, env, log, receipts, read: () => JSON.parse(readFileSync(file, "utf8")) }; } const GRANT_BASE = ["grant", "--business", "acme", "--user", "alice"]; const GRANT = [...GRANT_BASE, "--projects", PROJECT, "--instances", "coder", "--level", "collaborator"]; async function run(args, deps, env = humanEnv()) { const i = io(env); await accessCommand(args, i, deps); return i.out.text; } async function refusal(promise, pattern) { await assert.rejects(promise, (e) => e instanceof CliError && pattern.test(e.message)); return promise.catch((e) => e); } test("list prints the revision and the users", async (t) => { const s = setup(t); const out = await run(["list", "--business", "acme"], s.deps); assert.match(out, /access file revision 1/); assert.match(out, /user alice home local/); }); test("grant writes one revision up, a receipt line, and the id in the file", async (t) => { const s = setup(t); const out = await run([...GRANT, "--revision", "1"], s.deps); assert.match(out, /access grant: g-[0-9a-f]{8} at acme, revision 2/); const doc = s.read(); assert.equal(doc.revision, 2); assert.equal(doc.grants.length, 1); assert.deepEqual(doc.grants[0].projects, [PROJECT]); const [line] = s.receipts().trim().split("\n").map((l) => JSON.parse(l)); assert.equal(line.verb, "grant"); assert.equal(line.revisionBefore, 1); assert.equal(line.revisionAfter, 2); assert.equal(line.actor, "human"); }); test("a viewer grant writes the viewer level, one revision up", async (t) => { const s = setup(t); await run([...GRANT_BASE, "--projects", PROJECT, "--instances", "coder", "--level", "viewer", "--revision", "1"], s.deps); const doc = s.read(); assert.equal(doc.revision, 2); assert.equal(doc.grants[0].level, "viewer"); }); test("a second write appends a second receipt and leaves the first intact", async (t) => { const s = setup(t); await run([...GRANT, "--revision", "1"], s.deps); await run(["revoke", "--business", "acme", "--user", "alice", "--revision", "2"], s.deps); const lines = s.receipts().trim().split("\n").map((l) => JSON.parse(l)); assert.deepEqual(lines.map((l) => l.verb), ["grant", "revoke"]); assert.equal(lines[0].revisionAfter, 2); }); test("a stale revision refuses with revision-conflict and writes nothing", async (t) => { const s = setup(t); await run([...GRANT, "--revision", "1"], s.deps); const before = readFileSync(s.file, "utf8"); await refusal(run([...GRANT, "--revision", "1"], s.deps), /revision-conflict/); assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(s.receipts().trim().split("\n").length, 1); }); test("a live holder's lock refuses and leaves the file and the lock alone", async (t) => { const s = setup(t); writeFileSync(`${s.file}.lock`, `${process.pid}\n`, { mode: 0o600 }); const before = readFileSync(s.file, "utf8"); await refusal(run([...GRANT, "--revision", "1"], s.deps), /being written by another command/); assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(readFileSync(`${s.file}.lock`, "utf8"), `${process.pid}\n`); assert.equal(readdirSync(s.config).filter((f) => f.startsWith(".access-")).length, 0); }); test("an empty or unreadable lock refuses, since its holder may still be writing", async (t) => { const s = setup(t); writeFileSync(`${s.file}.lock`, ""); await refusal(run([...GRANT, "--revision", "1"], s.deps), /is unreadable or not a pid/); writeFileSync(`${s.file}.lock`, "not-a-pid\n"); await refusal(run([...GRANT, "--revision", "1"], s.deps), /is unreadable or not a pid/); assert.equal(existsSync(`${s.file}.lock`), true); }); test("a lock left by a dead pid refuses, names the pid, and is not reclaimed", async (t) => { const s = setup(t); const dead = spawnSync(process.execPath, ["-e", ""]); writeFileSync(`${s.file}.lock`, `${dead.pid}\n`, { mode: 0o600 }); const before = readFileSync(s.file, "utf8"); await refusal(run([...GRANT, "--revision", "1"], s.deps), new RegExp(`^refused: access file lock is left by pid ${dead.pid}, which is not running`)); assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(readFileSync(`${s.file}.lock`, "utf8"), `${dead.pid}\n`); assert.equal(s.receipts(), ""); }); test("a failed rename leaves the old bytes, removes the temp file, and writes no receipt", async (t) => { const s = setup(t); const before = readFileSync(s.file, "utf8"); const fs = { ...nodeFs, renameSync: () => { throw Object.assign(new Error("injected rename failure"), { code: "EXDEV" }); } }; await assert.rejects(run([...GRANT, "--revision", "1"], { ...s.deps, fs }), /injected rename failure/); assert.equal(readFileSync(s.file, "utf8"), before); assert.deepEqual(readdirSync(s.config).filter((f) => f.startsWith(".access-")), []); assert.equal(existsSync(`${s.file}.lock`), false); assert.equal(s.receipts(), ""); }); test("a group-writable access file refuses with exit 3, names the file by label, never by path, and changes nothing", async (t) => { const s = setup(t); chmodSync(s.file, 0o664); const before = readFileSync(s.file, "utf8"); const err = await refusal(run([...GRANT, "--revision", "1"], s.deps), /must not be writable by group or other/); assert.equal(err.exitCode, 3); assert.equal(err.message, "refused: access file must not be writable by group or other (mode 664)"); assert.ok(!err.message.includes(s.file), err.message); assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(s.receipts(), ""); assert.equal(existsSync(`${s.file}.lock`), false); }); test("an agent or tester caller is refused before the context or the access file is read", async (t) => { const s = setup(t); chmodSync(s.file, 0o664); const deps = { ...s.deps, contextFor: () => { throw new Error("context was read"); } }; const env = { ...humanEnv(), MOSAIC_RUN_ID: "run-1" }; await refusal(run([...GRANT, "--revision", "1"], deps, env), /human shell/); await refusal(run(["list", "--business", "acme", "--tester"], deps), /not --tester/); }); test("the lock is mode 0600 while held and removed after a refused write", async (t) => { const s = setup(t); let mode; withLock(s.file, () => { mode = statSync(`${s.file}.lock`).mode & 0o777; }); assert.equal(mode, 0o600); assert.equal(existsSync(`${s.file}.lock`), false); await refusal(run([...GRANT, "--revision", "9"], s.deps), /revision-conflict/); assert.equal(existsSync(`${s.file}.lock`), false); }); test("a grant naming an unknown instance is refused before any write", async (t) => { const s = setup(t); const before = readFileSync(s.file, "utf8"); const bad = [...GRANT_BASE, "--projects", PROJECT, "--instances", "nobody", "--level", "collaborator", "--revision", "1"]; await refusal(run(bad, s.deps), /refused: /); assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(s.receipts(), ""); }); test("a project name resolves to its id at grant time", async (t) => { const s = setup(t); const args = [...GRANT_BASE, "--projects", "hub", "--instances", "coder", "--level", "collaborator", "--revision", "1"]; await run(args, s.deps); assert.deepEqual(s.read().grants[0].projects, [PROJECT]); }); test("an unknown project name refuses before any write", async (t) => { const s = setup(t); const before = readFileSync(s.file, "utf8"); const args = [...GRANT_BASE, "--projects", "nope", "--instances", "coder", "--level", "collaborator", "--revision", "1"]; await refusal(run(args, s.deps), /project-not-found/); assert.equal(readFileSync(s.file, "utf8"), before); }); test("revoke removes the user's grants and leaves a revision behind", async (t) => { const s = setup(t); await run([...GRANT, "--revision", "1"], s.deps); await run(["revoke", "--business", "acme", "--user", "alice", "--revision", "2"], s.deps); const doc = s.read(); assert.equal(doc.revision, 3); assert.deepEqual(doc.grants, []); }); test("revoke of a user with no grant refuses and writes nothing", async (t) => { const s = setup(t); const before = readFileSync(s.file, "utf8"); await refusal(run(["revoke", "--business", "acme", "--user", "alice", "--revision", "1"], s.deps), /no grant/); assert.equal(readFileSync(s.file, "utf8"), before); }); test("an agent caller is refused before the bus is asked", async (t) => { const s = setup(t); const before = readFileSync(s.file, "utf8"); const env = { ...humanEnv(), MOSAIC_RUN_ID: "run-1" }; const err = await refusal(run([...GRANT, "--revision", "1"], s.deps, env), /human shell/); assert.equal(err.exitCode, 3); assert.equal(existsSync(s.log), false); assert.equal(readFileSync(s.file, "utf8"), before); }); test("--tester is refused on every verb before the bus is asked", async (t) => { const s = setup(t); for (const verb of [["list"], [...GRANT, "--revision", "1"], ["revoke", "--user", "alice", "--revision", "1"]]) { // GRANT already names the business; a repeated --business refuses (rename design r2). const args = verb.includes("--business") ? [...verb, "--tester"] : [...verb, "--business", "acme", "--tester"]; const err = await refusal(run(args, s.deps), /not --tester/); assert.equal(err.exitCode, 3); } assert.equal(existsSync(s.log), false); }); test("a bus that refuses the human proof blocks the write", async (t) => { const s = setup(t, { mode: "refuse" }); const before = readFileSync(s.file, "utf8"); const err = await refusal(run([...GRANT, "--revision", "1"], s.deps), /human-required/); assert.equal(err.exitCode, 3); assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(s.receipts(), ""); }); test("the context reads console.sso subjects and the business files, and peers stay empty", async (t) => { const root = tmp(t, "mosaic-access-ctx-"); const f = fixture(root, "acme"); const system = { console: { sso: { providers: [{ id: "authentik", subjects: [{ sub: "jason-sub" }] }] } } }; const ctx = accessContextFor(system, { MOSAIC_CONFIG: join(root, "config", "config.json") }); assert.deepEqual(ctx.providers, ["authentik"]); assert.deepEqual(ctx.ssoSubjects, [{ provider: "authentik", sub: "jason-sub" }]); assert.deepEqual(ctx.peers, []); assert.equal(ctx.businesses.acme.human, "jason"); assert.ok(Array.isArray(ctx.businesses.acme.instances) && ctx.businesses.acme.instances.length > 0); const bare = accessContextFor({}, { MOSAIC_CONFIG: join(root, "config", "config.json") }); assert.deepEqual(bare.ssoSubjects, []); assert.deepEqual(bare.providers, []); }); // Real human-cli.mjs on a scratch bus, run from agent ancestry. The proof must fail. test("the real human-cli, run from agent ancestry, refuses and writes nothing", async (t) => { const root = tmp(t, "mosaic-access-real-"); const f = fixture(root, "acme"); const config = join(root, "config"); const bus = await startBroker({ dataRoot: f.dataRoot, businesses: { acme: { id: "acme", human: "jason", arbiters: { technical: 'chief', delivery: 'chief' }, roles: {chief: { definition: 'chief-of-staff', authority: { withinRole: ['decision.resolve.technical', 'decision.resolve.delivery'], crossRole: [] } }, coder: { authority: { withinRole: [], crossRole: [] } } } } }, organization: { dir: config, rolesDir: f.roles, system: systemFor(root) }, }); t.after(() => bus.close()); writeJson(join(config, "access.json"), DOC); const file = join(config, "access.json"); const before = readFileSync(file, "utf8"); const agentEnv = { ...humanEnv(), CLAUDECODE: "1" }; const deps = { system: { dataRoot: f.dataRoot }, configDir: config, contextFor: () => CONTEXT, transport: (business) => humanTransport({ socket: socketPath(f.dataRoot), business, env: agentEnv, cli: HUMAN_CLI, timeoutMs: 10000 }), }; // The bus closes the socket on a failed human proof, so the client reports // outcome-unknown (client.mjs:32-33), not human-required. That is a bus behavior // outside R5 (BUILD.md). The test pins it, and nothing is written. const err = await refusal(run([...GRANT, "--revision", "1"], deps), /^outcome-unknown/); assert.equal(err.code, "outcome-unknown"); assert.equal(err.exitCode, 1); assert.equal(readFileSync(file, "utf8"), before); assert.equal(existsSync(join(f.dataRoot, "state", "access-log.jsonl")), false); }); // Row 193c (#1705): `user add` and `user remove`. BUILD.md (agents/megavolt/work/queue-207) names // each test T1-T9 and the mutant it kills. const USER_ADD = (id, { subject = "authentik:bob-sub", home = "local", revision = "1" } = {}) => [ "user", "add", id, "--business", "acme", "--home", home, "--subject", subject, "--revision", revision, ]; const USER_REMOVE = (id, revision = "1") => ["user", "remove", id, "--business", "acme", "--revision", revision]; test("T1 user add writes the user one revision up, prints it and appends a user.add receipt", async (t) => { const s = setup(t); const out = await run(USER_ADD("bob", { subject: "authentik:bob:x" }), s.deps); assert.match(out, /access user add: bob, revision 2/); const doc = s.read(); assert.equal(doc.revision, 2); assert.deepEqual(doc.users[1], { id: "bob", home: "local", subjects: [{ provider: "authentik", sub: "bob:x" }] }); assert.deepEqual(doc.users[0], DOC.users[0]); const lines = s.receipts().trim().split("\n").map((l) => JSON.parse(l)); assert.equal(lines.length, 1); assert.deepEqual(lines[0], { at: "2026-10-20T20:00:00.000Z", verb: "user.add", business: "acme", user: "bob", actor: "human", revisionBefore: 1, revisionAfter: 2, grants: [], }); }); test("T2 a bad user add refuses before any write", async (t) => { const s = setup(t); const before = readFileSync(s.file, "utf8"); const usage = [ ["user", "add", "bob", "--business", "acme", "--subject", "authentik:bob-sub", "--revision", "1"], ["user", "add", "bob", "--business", "acme", "--home", "local", "--revision", "1"], USER_ADD("bob", { subject: "authentik" }), USER_ADD("bob", { subject: ":bob-sub" }), USER_ADD("bob", { subject: "authentik:" }), ]; for (const args of usage) { const err = await refusal(run(args, s.deps), /user add needs --home and --subject PROVIDER:SUB/); assert.equal(err.exitCode, 4, args.join(" ")); } const checked = [ [USER_ADD("bob", { subject: "github:bob-sub" }), /provider must be a configured provider/], [USER_ADD("alice", { subject: "authentik:alice-2" }), /duplicate user id/], [USER_ADD("tester"), /a bus principal/], [USER_ADD("jason"), /a business's human/], [USER_ADD("bob", { subject: "authentik:jason-sub" }), /owner sign-in subject/], [USER_ADD("bob", { subject: "authentik:alice-sub" }), /appears more than once/], [USER_ADD("bob", { home: "peer-one" }), /home must be a configured peer/], ]; for (const [args, pattern] of checked) { const err = await refusal(run(args, s.deps), pattern); assert.equal(err.exitCode, 3, args.join(" ")); } assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(s.receipts(), ""); }); test("T3 user remove of a user with no grant drops them one revision up, with a user.remove receipt", async (t) => { const s = setup(t); const out = await run(USER_REMOVE("alice"), s.deps); assert.match(out, /access user remove: alice, revision 2/); const doc = s.read(); assert.equal(doc.revision, 2); assert.deepEqual(doc.users, []); const [line] = s.receipts().trim().split("\n").map((l) => JSON.parse(l)); assert.equal(line.verb, "user.remove"); assert.equal(line.user, "alice"); assert.equal(line.revisionBefore, 1); assert.equal(line.revisionAfter, 2); assert.deepEqual(line.grants, []); }); // M1's second fence: without the user-has-grants refusal, checkAccess refuses the file with exit 3 // (a grant naming a missing user). The test pins the reason and exit 2, so only the verb's refusal passes. test("T4 user remove refuses with user-has-grants while the user holds a grant, and writes nothing", async (t) => { const s = setup(t); await run([...GRANT, "--revision", "1"], s.deps); const id = s.read().grants[0].id; const before = readFileSync(s.file, "utf8"); const err = await refusal(run(USER_REMOVE("alice", "2"), s.deps), /user-has-grants: alice still holds /); assert.equal(err.exitCode, 2); assert.ok(err.message.includes(id)); assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(s.receipts().trim().split("\n").length, 1); }); test("T5 user remove of an id not in the file refuses and writes nothing", async (t) => { const s = setup(t); const before = readFileSync(s.file, "utf8"); const err = await refusal(run(USER_REMOVE("carol"), s.deps), /no user carol to remove/); assert.equal(err.exitCode, 2); assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(s.receipts(), ""); }); test("T6 a stale revision refuses both user verbs with revision-conflict", async (t) => { const s = setup(t); const before = readFileSync(s.file, "utf8"); for (const args of [USER_ADD("bob", { revision: "2" }), USER_REMOVE("alice", "5")]) { const err = await refusal(run(args, s.deps), /revision-conflict/); assert.equal(err.exitCode, 3); } assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(s.receipts(), ""); }); test("T7 --tester and an agent caller are refused on both user verbs before the bus is asked", async (t) => { const s = setup(t); const before = readFileSync(s.file, "utf8"); for (const args of [USER_ADD("bob"), USER_REMOVE("alice")]) { const tester = await refusal(run([...args, "--tester"], s.deps), /not --tester/); assert.equal(tester.exitCode, 3); const agent = await refusal(run(args, s.deps, { ...humanEnv(), MOSAIC_RUN_ID: "run-1" }), /human shell/); assert.equal(agent.exitCode, 3); } assert.equal(existsSync(s.log), false); assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(s.receipts(), ""); }); test("T8 a user verb of the wrong shape prints the help", async (t) => { const s = setup(t); for (const shape of [["user"], ["user", "add"], ["user", "list", "x"], ["user", "add", "a", "b"]]) { const err = await refusal(run([...shape, "--business", "acme", "--revision", "1"], s.deps), /mosaic access user add ID/); assert.equal(err.exitCode, 4, shape.join(" ")); } assert.equal(existsSync(s.log), false); }); test("T9 a bus that refuses the human proof blocks both user verbs", async (t) => { const s = setup(t, { mode: "refuse" }); const before = readFileSync(s.file, "utf8"); for (const args of [USER_ADD("bob"), USER_REMOVE("alice")]) { const err = await refusal(run(args, s.deps), /human-required/); assert.equal(err.exitCode, 3); } assert.equal(readFileSync(s.file, "utf8"), before); assert.equal(s.receipts(), ""); }); // FED R4 (#1801, design 3.7 trigger 3): grant and revoke ask the bus to re-read the file after the // write. This stand-in CLI logs every call, one line each, and with FAKE_MODE "refuse-reload" // refuses access.reload alone, as a bus would refuse a call it can't take. BUILD.md // (agents/morgana/work/queue-295) names the mutant each test kills. const RELOAD_BODY = ` import { appendFileSync, readFileSync } from "node:fs"; const input = JSON.parse(readFileSync(0, "utf8")); appendFileSync(process.env.FAKE_LOG, JSON.stringify(input) + "\\n"); if (input.verb === "access.reload") { if (process.env.FAKE_MODE === "refuse-reload") { process.stderr.write("human-required\\n"); process.exit(3); } process.stdout.write(JSON.stringify({ ended: [] })); } else process.stdout.write(JSON.stringify(${JSON.stringify(ROWS)})); `; function reloading(t, mode = "ok") { const s = setup(t); mkdirSync(join(s.root, "reload")); const cli = fakeCli(join(s.root, "reload"), RELOAD_BODY); const env = humanEnv({ FAKE_LOG: s.log, FAKE_MODE: mode }); s.deps.transport = (business) => humanTransport({ socket: join(s.root, "bus.sock"), business, env, cli, timeoutMs: 5000 }); const calls = () => readFileSync(s.log, "utf8").trim().split("\n").map((l) => JSON.parse(l)); return { ...s, calls }; } test("R4 grant and revoke call access.reload after the write, and nothing else does", async (t) => { const s = reloading(t); await run([...GRANT, "--revision", "1"], s.deps); assert.deepEqual(s.calls().map((c) => [c.verb, c.business, c.args]), [["project.list", "acme", {}], ["access.reload", "acme", {}]]); await run(["revoke", "--business", "acme", "--user", "alice", "--revision", "2"], s.deps); await run(["list", "--business", "acme"], s.deps); assert.deepEqual(s.calls().map((c) => c.verb), ["project.list", "access.reload", "project.list", "access.reload", "project.list"]); // A refused write never asks. await refusal(run([...GRANT, "--revision", "1"], s.deps), /revision-conflict/); assert.equal(s.calls().at(-1).verb, "project.list"); assert.equal(s.calls().filter((c) => c.verb === "access.reload").length, 2); }); // sage-3139 Q2: the write stands, so a refused reload is exit 0. stdout reports the write as before, // and stderr names the refusal and the timer. test("R4 a refused access.reload after a good write exits 0, stdout reports the write, stderr names the code and the 30 s timer", async (t) => { const s = reloading(t, "refuse-reload"); const i = io(humanEnv()); assert.equal(await accessCommand([...GRANT, "--revision", "1"], i, s.deps), undefined, "no error: exit 0"); assert.match(i.out.text, /^access grant: g-[0-9a-f]{8} at acme, revision 2\n$/); assert.equal(i.err.text, "access.reload refused: human-required; the bus's timer applies the change within 30 s\n"); assert.equal(s.read().revision, 2, "the write stands"); assert.equal(s.receipts().trim().split("\n").length, 1); }); test("R4 the help keeps the provider-removal order and says what a refused reload means", async (t) => { const s = setup(t); const err = await refusal(run(["help"], s.deps), /revoke them here first/); assert.equal(err.exitCode, 4); const text = err.message.replace(/\n/g, " "); assert.match(text, /To cut a user off, revoke them here first\. It acts at once\. Removing a provider from console\.sso takes effect only after a Console restart, so do that second\./); assert.match(text, /grant and revoke ask the bus to re-read the file after the write\. If the bus refuses, the write still stands \(exit 0\), stderr names the refusal, and the bus's timer applies the change within 30 s\./); assert.ok(text.indexOf("so do that second") < text.indexOf("re-read the file"), "next to the provider-removal order"); }); test('v1 grant receipt preserves business identity and exact JSON line shape', async t => { const s = setup(t); await run([...GRANT, '--revision', '1'], s.deps); const raw = s.receipts(); const grant = s.read().grants[0].id; assert.match(grant, /^g-[0-9a-f]{8}$/); assert.equal(raw, JSON.stringify({ at: '2026-10-20T20:00:00.000Z', verb: 'grant', business: 'acme', user: 'alice', actor: 'human', revisionBefore: 1, revisionAfter: 2, grants: [grant] }) + '\n'); });