Review P1: the CLI wrote definitions with a no-op secret check. setDefinition now refuses definition-secret-check-required when a write that adds or changes an entry has no assertClean. New bus verbs definition.import|invent|edit|remove run the writes for the business's human at the cli with the credential store's assertClean; the tester, agents and readers refuse. The CLI's definition writes call the bus and map its codes to messages that never quote the definition.
75 lines
5.3 KiB
JavaScript
75 lines
5.3 KiB
JavaScript
// #1873 T1: the definition writes on the bus. Only the business's human at the cli reaches them,
|
|
// and the writer runs the credential store's held-secret check on what it would write.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { appendFileSync, chmodSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { DEFINITION_BUS_KEYS } from '../src/broker.mjs';
|
|
import { startBroker } from '../src/runtime.mjs';
|
|
import { DEFINITION_BUS_VERBS } from '../../organization/src/definition-verbs.mjs';
|
|
import { businessDoc, rolesCopy, systemFor, writeJson } from '../../organization/tests/helpers.mjs';
|
|
|
|
const TOKEN = 'fixture-held-token-t1-5c0e9a';
|
|
const SCOUT = { role: { tools: ['read'], network: 'none', authority: { withinRole: [], crossRole: [] }, credentials: [] }, instructions: 'Read and report.\n' };
|
|
|
|
function files(dir) {
|
|
return readdirSync(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? files(join(dir, e.name)) : e.isFile() ? [join(dir, e.name)] : []);
|
|
}
|
|
|
|
test('T1 the broker checks the same four definition verbs the runtime maps', () => {
|
|
assert.deepEqual(Object.keys(DEFINITION_BUS_KEYS).sort(), Object.keys(DEFINITION_BUS_VERBS).sort());
|
|
});
|
|
|
|
test('T1 definition writes: the human at the cli writes, every other caller refuses, and a held token never lands', async (t) => {
|
|
const root = mkdtempSync(join(tmpdir(), 'mosaic-def-bus-'));
|
|
const rolesDir = rolesCopy(root);
|
|
const doc = businessDoc(root, 'demo');
|
|
const file = writeJson(join(root, 'businesses/demo.json'), { ...doc, plugins: { ...doc.plugins, harness: ['pi'] } });
|
|
const data = join(root, 'data');
|
|
mkdirSync(data, { mode: 0o700 });
|
|
const token = join(root, 'held.token');
|
|
writeFileSync(token, TOKEN, { mode: 0o600 });
|
|
chmodSync(token, 0o600);
|
|
const bus = await startBroker({ dataRoot: data, businesses: { demo: { id: 'demo', human: 'jason', arbiters: { technical: 'chief', delivery: 'chief' }, vars: { 'bus.tester': true }, roles: {
|
|
chief: { definition: 'chief-of-staff', authority: { withinRole: ['decision.resolve.technical', 'decision.resolve.delivery'], crossRole: [] } },
|
|
coder: { authority: { withinRole: [], crossRole: [] }, credentials: { gitea: { file: token, rotateBy: '2099-01-01' } } },
|
|
} } }, organization: { dir: root, rolesDir, system: systemFor(root) } });
|
|
t.after(async () => { await bus.close(); rmSync(root, { recursive: true, force: true }); });
|
|
const b = bus.broker;
|
|
const human = b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true });
|
|
const revision = () => b.request(human, { verb: 'limits' }).revision;
|
|
const invent = (name) => ({ verb: 'definition.invent', args: { revision: revision(), name, definition: SCOUT } });
|
|
|
|
// An agent, a reader and the tester never reach the writer.
|
|
const before = readFileSync(file);
|
|
const agent = b.bindLaunch({ business: 'demo', role: 'coder', run: 'coder-run', harness: 'pi', address: 'coder-run' });
|
|
for (const cap of [agent, b.bindReader({ business: 'demo' })]) assert.throws(() => b.request(cap, invent('scout')), /human-required/);
|
|
assert.throws(() => b.request(b.bindTester({ business: 'demo' }), invent('scout')), /tester-refused/);
|
|
// Unknown and missing arguments refuse; an actor can't be forged through them.
|
|
assert.throws(() => b.request(human, { verb: 'definition.invent', args: { ...invent('scout').args, actor: 'mallory' } }), /invalid-request/);
|
|
assert.throws(() => b.request(human, { verb: 'definition.remove', args: { revision: revision(), name: 'scout' } }), /invalid-request/);
|
|
assert.deepEqual(readFileSync(file), before);
|
|
|
|
// The human's invent commits, as the human, with a receipt that carries no role text.
|
|
const receipt = b.request(human, invent('scout'));
|
|
assert.deepEqual({ ...receipt, revision: 0, transaction: 0 }, { verb: 'invent', organization: 'demo', definition: 'scout', rev: 1, revision: 0, transaction: 0 });
|
|
assert.equal(JSON.parse(readFileSync(file, 'utf8')).definitions.scout.instructions, SCOUT.instructions);
|
|
const last = JSON.parse(readFileSync(join(root, 'businesses/.history/demo/changes.jsonl'), 'utf8').trim().split('\n').at(-1));
|
|
assert.equal(last.actor, 'jason');
|
|
assert.equal(last.state, 'committed');
|
|
// A writer refusal passes by its code.
|
|
assert.throws(() => b.request(human, invent('scout')), /definition-exists/);
|
|
|
|
// The bus refuses a request that carries the held token, and the writer refuses a template whose
|
|
// contract carries it (the import request itself holds no token). Neither writes.
|
|
const written = readFileSync(file);
|
|
assert.throws(() => b.request(human, { verb: 'definition.invent', args: { revision: revision(), name: 'leak', definition: { ...SCOUT, instructions: `Use ${TOKEN}.\n` } } }),
|
|
(e) => e.code === 'credential-leak' && !e.message.includes(TOKEN));
|
|
appendFileSync(join(rolesDir, 'coder.md'), `\nUse ${TOKEN} for the forge.\n`);
|
|
assert.throws(() => b.request(human, { verb: 'definition.import', args: { revision: revision(), template: 'coder' } }),
|
|
(e) => e.code === 'invalid-request' && !e.message.includes(TOKEN) && !String(e.stack).includes(TOKEN));
|
|
assert.deepEqual(readFileSync(file), written);
|
|
for (const path of [...files(data), ...files(join(root, 'businesses'))]) assert.ok(!readFileSync(path).includes(TOKEN), path);
|
|
});
|