183 lines
9.8 KiB
JavaScript
183 lines
9.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { Store } from '../src/store.mjs';
|
|
import { Broker } from '../src/broker.mjs';
|
|
|
|
// Row 141 (#1635, REQ-MSG-2): messages are addressed by an instance's name. The human sets names
|
|
// through the CLI; the chief of staff sets them because its role file grants `instance.name`
|
|
// within-role. The grant is read from the shipped roles/chief-of-staff.json, so removing it there
|
|
// fails the chief's test. Instance keys differ from definitions here, so each refusal is its own.
|
|
const shipped = (name) =>
|
|
JSON.parse(readFileSync(fileURLToPath(new URL(`../../../roles/${name}.json`, import.meta.url)), 'utf8'));
|
|
const none = { authority: { withinRole: ['message.send'], crossRole: [] } };
|
|
|
|
const roles = {
|
|
chief: { definition: 'chief-of-staff', authority: shipped('chief-of-staff').authority },
|
|
lead: { definition: 'pm', ...none },
|
|
dev: { definition: 'coder', authority: { withinRole: ['message.send'], crossRole: ['instance.name'] } },
|
|
};
|
|
const demo = () => ({
|
|
demo: {
|
|
id: 'demo',
|
|
human: 'jason',
|
|
arbiters: { technical: 'chief', delivery: 'chief' },
|
|
roles,
|
|
definitions: { reviewer: {} },
|
|
vars: { 'bus.tester': true },
|
|
},
|
|
});
|
|
|
|
function setup(t) {
|
|
const root = mkdtempSync(join(tmpdir(), 'bus-names-'));
|
|
const store = new Store(root);
|
|
t.after(() => {
|
|
store.close();
|
|
rmSync(root, { recursive: true, force: true });
|
|
});
|
|
const b = new Broker({ store, businesses: demo() });
|
|
const caps = { human: b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }) };
|
|
for (const role of Object.keys(roles)) {
|
|
caps[role] = b.bindLaunch({ business: 'demo', role, run: `${role}-run`, harness: 'pi' });
|
|
b.request(caps[role], { verb: 'role.claim' });
|
|
}
|
|
caps.reader = b.bindReader({ business: 'demo' });
|
|
caps.tester = b.bindTester({ business: 'demo' });
|
|
const call = (who, verb, args = {}) => b.request(caps[who], { verb, args });
|
|
return { b, store, caps, call };
|
|
}
|
|
|
|
test('REQ-MSG-2: the chief of staff names an instance under its within-role grant, and the write is logged', (t) => {
|
|
const { store, call } = setup(t);
|
|
assert.ok(shipped('chief-of-staff').authority.withinRole.includes('instance.name'), 'the shipped chief file grants instance.name');
|
|
assert.deepEqual(call('chief', 'name.set', { instance: 'dev', name: 'rex' }), { instance: 'dev', name: 'rex' });
|
|
const [event, ...rest] = store
|
|
.all("SELECT actor_role, subject, body FROM events WHERE kind='action.allowed' AND json_extract(body,'$.action')='instance.name'")
|
|
.map((e) => ({ ...e, body: JSON.parse(e.body) }));
|
|
assert.equal(rest.length, 0);
|
|
assert.equal(event.actor_role, 'chief');
|
|
assert.equal(event.subject, 'dev');
|
|
assert.equal(event.body.class, 'within-role');
|
|
assert.equal(event.body.target, 'dev');
|
|
assert.deepEqual({ ...store.get('SELECT business, instance, name, by FROM instance_names') }, {
|
|
business: 'demo',
|
|
instance: 'dev',
|
|
name: 'rex',
|
|
by: 'chief-run',
|
|
});
|
|
assert.deepEqual(
|
|
call('chief', 'names').map(({ instance, name }) => ({ instance, name })),
|
|
[{ instance: 'dev', name: 'rex' }],
|
|
);
|
|
});
|
|
|
|
test('REQ-MSG-2: the human names an instance through the CLI', (t) => {
|
|
const { store, call } = setup(t);
|
|
assert.deepEqual(call('human', 'name.set', { instance: 'lead', name: 'patty' }), { instance: 'lead', name: 'patty' });
|
|
assert.equal(store.get('SELECT by FROM instance_names').by, 'jason');
|
|
assert.deepEqual(
|
|
call('human', 'names').map(({ instance, name }) => ({ instance, name })),
|
|
[{ instance: 'lead', name: 'patty' }],
|
|
);
|
|
});
|
|
|
|
test('REQ-MSG-2: a role without the grant, or holding it cross-role, is refused not-authorized', (t) => {
|
|
const { store, call } = setup(t);
|
|
// lead holds no instance.name at all; dev holds it cross-role. Neither may set a name: the refusal
|
|
// comes before #checkAuthority, so dev gets not-authorized, not decision-required, and name.set
|
|
// takes no decision argument at all.
|
|
assert.throws(() => call('lead', 'name.set', { instance: 'dev', name: 'rex' }), /not-authorized/);
|
|
assert.throws(() => call('dev', 'name.set', { instance: 'dev', name: 'rex' }), /not-authorized/);
|
|
assert.throws(
|
|
() => call('dev', 'name.set', { instance: 'dev', name: 'rex', decision: '00000000-0000-4000-8000-000000000000' }),
|
|
/invalid-request/,
|
|
);
|
|
assert.throws(() => call('reader', 'name.set', { instance: 'dev', name: 'rex' }), /read-only/);
|
|
assert.equal(store.get('SELECT count(*) AS n FROM instance_names').n, 0);
|
|
// Row 175 (#1673): the tester sets a name like the human, recorded by `tester`.
|
|
call('tester', 'name.set', { instance: 'dev', name: 'rex' });
|
|
assert.equal(store.get('SELECT by FROM instance_names').by, 'tester');
|
|
});
|
|
|
|
test('REQ-MSG-2: a name is unique in the business and is never an instance key or a role name', (t) => {
|
|
const { store, call } = setup(t);
|
|
call('human', 'name.set', { instance: 'dev', name: 'rex' });
|
|
assert.throws(() => call('human', 'name.set', { instance: 'lead', name: 'rex' }), /name-taken/);
|
|
assert.throws(() => call('chief', 'name.set', { instance: 'lead', name: 'rex' }), /name-taken/);
|
|
for (const key of ['chief', 'lead', 'human', 'tester'])
|
|
assert.throws(() => call('human', 'name.set', { instance: 'dev', name: key }), /name-is-instance/, key);
|
|
// A definition an instance uses, and one the business only defines.
|
|
for (const definition of ['coder', 'chief-of-staff', 'reviewer'])
|
|
assert.throws(() => call('human', 'name.set', { instance: 'dev', name: definition }), /name-is-role/, definition);
|
|
for (const bad of ['', 'Rex', '_bus', 'a b', 'x'.repeat(65), 7])
|
|
assert.throws(() => call('human', 'name.set', { instance: 'dev', name: bad }), /invalid-request/, String(bad));
|
|
assert.throws(() => call('human', 'name.set', { instance: 'nobody', name: 'zed' }), /unknown-role/);
|
|
// Renaming frees the old name for another instance.
|
|
call('human', 'name.set', { instance: 'dev', name: 'rexy' });
|
|
call('human', 'name.set', { instance: 'lead', name: 'rex' });
|
|
assert.deepEqual(
|
|
call('human', 'names').map(({ instance, name }) => ({ instance, name })),
|
|
[{ instance: 'dev', name: 'rexy' }, { instance: 'lead', name: 'rex' }],
|
|
);
|
|
// The store refuses a duplicate on its own, and the table is append-only.
|
|
assert.throws(
|
|
() => store.run("INSERT INTO instance_names(at,business,instance,name,by) VALUES('2026-01-01T00:00:00.000Z','demo','chief','rex','t')"),
|
|
/unique in its business/,
|
|
);
|
|
assert.throws(() => store.run("UPDATE instance_names SET name='zed'"), /append-only/);
|
|
assert.throws(() => store.run('DELETE FROM instance_names'), /append-only/);
|
|
});
|
|
|
|
test('REQ-MSG-2: a send by name reaches the instance and records both; a send by instance key is unchanged', (t) => {
|
|
const { call } = setup(t);
|
|
call('human', 'name.set', { instance: 'dev', name: 'rex' });
|
|
const byName = call('lead', 'message.send', { to: 'rex', body: 'by name' });
|
|
const byKey = call('lead', 'message.send', { to: 'dev', body: 'by key' });
|
|
const fromHuman = call('human', 'message.send', { to: 'rex', body: 'from the human' });
|
|
const got = call('dev', 'message.receive');
|
|
assert.deepEqual(
|
|
got.map(({ id, to_role, to_name, body }) => ({ id, to_role, to_name, body })),
|
|
[
|
|
{ id: byName.id, to_role: 'dev', to_name: 'rex', body: 'by name' },
|
|
{ id: byKey.id, to_role: 'dev', to_name: null, body: 'by key' },
|
|
{ id: fromHuman.id, to_role: 'dev', to_name: 'rex', body: 'from the human' },
|
|
],
|
|
);
|
|
// The tester may send by name too; a name no instance holds is an unknown role.
|
|
call('tester', 'message.send', { to: 'rex', body: '[mos test] by name' });
|
|
assert.equal(call('dev', 'message.receive')[0].to_role, 'dev');
|
|
assert.throws(() => call('lead', 'message.send', { to: 'nobody', body: 'x' }), /unknown-role/);
|
|
// After a rename, the old name no longer reaches anyone.
|
|
call('human', 'name.set', { instance: 'dev', name: 'rexy' });
|
|
assert.throws(() => call('lead', 'message.send', { to: 'rex', body: 'x' }), /unknown-role/);
|
|
assert.equal(call('lead', 'message.send', { to: 'rexy', body: 'renamed' }).id.length, 36);
|
|
});
|
|
|
|
test('REQ-MSG-2: an instance key beats a name that equals it, as when a later instance takes the key', (t) => {
|
|
const { store, call } = setup(t);
|
|
// name.set refuses this name; the store's trigger doesn't know the business, so a row stands in for
|
|
// a name set before an instance with that key joined.
|
|
store.run("INSERT INTO instance_names(at,business,instance,name,by) VALUES('2026-01-01T00:00:00.000Z','demo','dev','lead','jason')");
|
|
call('chief', 'message.send', { to: 'lead', body: 'to the key' });
|
|
const [m] = call('lead', 'message.receive');
|
|
assert.equal(m.body, 'to the key');
|
|
assert.equal(m.to_role, 'lead');
|
|
assert.equal(m.to_name, null);
|
|
assert.deepEqual(call('dev', 'message.receive'), []);
|
|
});
|
|
|
|
test('REQ-MSG-2: a role name beats a name that equals it, as when a later definition takes the word (sage-0590)', (t) => {
|
|
const { store, call } = setup(t);
|
|
// As above, rows stand in for names set before the business knew these definitions: one an
|
|
// instance uses (coder) and one the business only defines (reviewer). Neither is an instance key.
|
|
store.run("INSERT INTO instance_names(at,business,instance,name,by) VALUES('2026-01-01T00:00:00.000Z','demo','dev','reviewer','jason')");
|
|
store.run("INSERT INTO instance_names(at,business,instance,name,by) VALUES('2026-01-01T00:00:00.000Z','demo','lead','coder','jason')");
|
|
for (const word of ['reviewer', 'coder'])
|
|
assert.throws(() => call('chief', 'message.send', { to: word, body: 'to the role' }), /unknown-role/, word);
|
|
assert.deepEqual(call('dev', 'message.receive'), []);
|
|
assert.deepEqual(call('lead', 'message.receive'), []);
|
|
});
|