// Row 165 (#1662, Q171, REQ-ROLE-3): instances and instances.set are human-only broker actions, // logged with the verified actor. They stay outside ORGANIZATION_KEYS, so neither organization.set // nor the chief of staff's change_organization reaches them (sage-0918). import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, mkdirSync, 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, LIFECYCLE_HUMAN_VERBS, ORGANIZATION_KEYS, TESTER_ORGANIZATION_VERBS } from '../src/broker.mjs'; const chiefAuthority = JSON.parse(readFileSync(fileURLToPath(new URL('../../../roles/chief-of-staff.json', import.meta.url)), 'utf8')).authority; const none = { authority: { withinRole: ['message.send'], crossRole: [] } }; const biz = () => ({ demo: { id: 'demo', human: 'jason', arbiters: { technical: 'pm', delivery: 'chief' }, definitions: {}, roles: { chief: { definition: 'chief-of-staff', authority: chiefAuthority }, pm: { definition: 'pm', ...none } }, projects: { stack: { root: '/nonexistent' } }, vars: { 'bus.tester': true }, }, }); const ADD = { revision: 'a'.repeat(64), instance: 'sec-1', op: 'add', definition: 'security-reviewer', launch: true }; test('row 165: instances and instances.set refuse every non-human session but the tester, the chief of staff included, and pass the verified actor', (t) => { const root = mkdtempSync(join(tmpdir(), 'instances-verb-')); const store = new Store(root); t.after(() => { store.close(); rmSync(root, { recursive: true, force: true }); }); const calls = []; const b = new Broker({ store, businesses: biz(), organization: (id, verb, args, actor) => (calls.push([id, verb, args, actor]), { ok: true }) }); const human = b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }); const tester = b.bindTester({ business: 'demo' }); const caps = { reader: b.bindReader({ business: 'demo' }) }; for (const role of ['chief', 'pm']) { caps[role] = b.bindLaunch({ business: 'demo', role, run: `${role}-run`, harness: 'pi' }); b.request(caps[role], { verb: 'role.claim' }); } for (const [who, cap] of Object.entries(caps)) for (const verb of ['instances', 'instances.set']) assert.throws(() => b.request(cap, { verb, args: verb === 'instances' ? {} : ADD }), /human-required/, `${who} ${verb}`); // The chief of staff holds organization.change, but organization.set takes ORGANIZATION_KEYS only: // the instance keys are refused before the writer, so change_organization can't add an instance. for (const key of ['instance', 'op', 'definition', 'launch']) assert.equal(ORGANIZATION_KEYS.includes(key), false, key); for (const args of [ADD, { revision: ADD.revision, instance: 'pm', op: 'change', definition: 'merge-gate' }]) assert.throws(() => b.request(caps.chief, { verb: 'organization.set', args }), /invalid-request/); assert.equal(calls.length, 0); assert.throws(() => b.request(human, { verb: 'instances.set', args: { ...ADD, actor: 'forged' } }), /invalid-request/); assert.throws(() => b.request(human, { verb: 'instances.set', args: { ...ADD, holder: 'mos' } }), /invalid-request/); assert.throws(() => b.request(human, { verb: 'instances.set', args: { ...ADD, credentials: {} } }), /invalid-request/); assert.throws(() => b.request(human, { verb: 'instances.set', args: { revision: ADD.revision, op: 'add' } }), /invalid-request/); assert.throws(() => b.request(human, { verb: 'instances', args: { x: 1 } }), /invalid-request/); assert.equal(calls.length, 0); b.request(human, { verb: 'instances' }); b.request(human, { verb: 'instances.set', args: ADD }); // Row 175 (#1673): the tester reads and writes instances through the same checks, as `tester`. assert.throws(() => b.request(tester, { verb: 'instances.set', args: { ...ADD, actor: 'forged' } }), /invalid-request/); b.request(tester, { verb: 'instances' }); b.request(tester, { verb: 'instances.set', args: ADD }); assert.deepEqual(calls, [['demo', 'instances', {}, 'jason'], ['demo', 'instances.set', ADD, 'jason'], ['demo', 'instances', {}, 'tester'], ['demo', 'instances.set', ADD, 'tester']]); }); test('row 165 runtime: an add and a change reach the organization file with the human as actor; each refusal keeps the bytes and logs nothing', async (t) => { const { startBroker } = await import('../src/runtime.mjs'); const { businessDoc, rolesCopy, systemFor, writeJson } = await import('../../organization/tests/helpers.mjs'); const root = mkdtempSync(join(tmpdir(), 'instances-runtime-')); const rolesDir = rolesCopy(root); writeJson(join(root, 'businesses/demo.json'), businessDoc(root, 'demo')); mkdirSync(join(root, 'data'), { mode: 0o700 }); const bus = await startBroker({ dataRoot: join(root, 'data'), businesses: { demo: { id: 'demo', 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: root, rolesDir, system: systemFor(root) } }); t.after(async () => { await bus.close(); rmSync(root, { recursive: true, force: true }); }); const human = bus.broker.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }); const file = join(root, 'businesses/demo.json'); const log = () => readFileSync(join(root, 'businesses/.history/demo/changes.jsonl'), 'utf8').trim().split('\n').filter(Boolean).map(JSON.parse); const view = bus.broker.request(human, { verb: 'instances' }); assert.equal(view.instances.chief.launcher, true); assert.equal(view.definitions['merge-gate'].accepted, true); const added = bus.broker.request(human, { verb: 'instances.set', args: { ...ADD, revision: view.revision } }); assert.equal(added.instances['sec-1'].launched, true); // Row 194 (D3 item 3): this boot copy's coder has no credential references and the file's has, so // the bus doesn't reload and asks for a restart. reload.test.mjs boots from the file. assert.equal(added.restart, true); assert.deepEqual(JSON.parse(readFileSync(file, 'utf8')).roles['sec-1'], { definition: 'security-reviewer' }); const first = log().at(-1); assert.deepEqual([first.actor, first.state, first.transaction], ['jason', 'committed', added.transaction]); const changed = bus.broker.request(human, { verb: 'instances.set', args: { revision: added.revision, instance: 'sec-1', op: 'change', definition: 'merge-gate' } }); assert.deepEqual(log().at(-1).changes.map(c => [c.path, c.old, c.new]), [['roles.sec-1.definition', 'security-reviewer', 'merge-gate']]); const before = readFileSync(file, 'utf8'); const entries = log().length; const code = (args) => { try { bus.broker.request(human, { verb: 'instances.set', args: { revision: changed.revision, ...args } }); } catch (e) { return e.code; } return null; }; // Row 194 (brief item 7, sage-1610 a): an add of a vikunja definition needs its bot. assert.equal(code({ instance: 'reviewer-2', op: 'add', definition: 'reviewer' }), 'organization-tracker-required'); assert.equal(code({ instance: 'sec-1', op: 'change', definition: 'coder' }), 'organization-credentials-out-of-scope'); assert.equal(code({ instance: 'sec-1', op: 'add', definition: 'merge-gate' }), 'organization-instance-exists'); assert.equal(code({ instance: 'ghost', op: 'change', vars: {} }), 'organization-unknown-instance'); assert.equal(code({ instance: 'thread-2', op: 'add', definition: 'assistant' }), 'organization-definition-refused'); assert.equal(code({ instance: 'chief', op: 'change', launch: true }), 'invalid-request'); assert.equal(code({ instance: 'gate-2', op: 'add', definition: 'merge-gate', revision: view.revision }), 'revision-conflict'); assert.equal(readFileSync(file, 'utf8'), before); assert.equal(log().length, entries, 'a refused write logs nothing'); }); // Row 194 (#1692, D4, D4.1): the human lifecycle verbs so far, veto.set and instances.set's three ops. test('row 194 T12: agent and reader sessions refuse veto.set; the tester gets add and change but not delete or veto.set; the human passes the hook for each listed verb', (t) => { const root = mkdtempSync(join(tmpdir(), 'instances-verb-')); const store = new Store(root); t.after(() => { store.close(); rmSync(root, { recursive: true, force: true }); }); const calls = []; const b = new Broker({ store, businesses: biz(), organization: (id, verb, args, actor) => (calls.push([verb, args.op, actor]), verb === 'instances.check' ? { revision: ADD.revision } : { ok: true }) }); const human = b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }); const tester = b.bindTester({ business: 'demo' }); const caps = { reader: b.bindReader({ business: 'demo' }) }; for (const role of ['chief', 'pm']) caps[role] = b.bindLaunch({ business: 'demo', role, run: `${role}-run`, harness: 'pi' }); const VETO = { revision: ADD.revision, op: 'keep', name: 'pm' }; for (const [who, cap] of Object.entries(caps)) { assert.throws(() => b.request(cap, { verb: 'veto.set', args: VETO }), /human-required/, who); assert.throws(() => b.request(cap, { verb: 'instances.set', args: { ...ADD, op: 'delete' } }), /human-required/, who); } assert.throws(() => b.request(tester, { verb: 'veto.set', args: VETO }), /tester-refused/); assert.throws(() => b.request(tester, { verb: 'instances.set', args: { revision: ADD.revision, instance: 'pm', op: 'delete' } }), /tester-refused/); assert.equal(TESTER_ORGANIZATION_VERBS.includes('veto.set'), false); assert.equal(calls.length, 0); // veto.set takes exactly its three keys, and instances.set only its three ops. for (const args of [{ ...VETO, actor: 'forged' }, { revision: VETO.revision, op: 'keep' }, { ...VETO, holder: 'mos' }]) assert.throws(() => b.request(human, { verb: 'veto.set', args }), /invalid-request/); assert.throws(() => b.request(human, { verb: 'instances.set', args: { ...ADD, op: 'remove' } }), /invalid-request/); assert.equal(calls.length, 0); // The hook refuses a verb missing from LIFECYCLE_HUMAN_VERBS, so each call below passing shows the // hook got that exact entry. assert.deepEqual([...LIFECYCLE_HUMAN_VERBS].sort(), ['instance.end', 'instance.launch', 'instance.regenerate', 'instances.set add', 'instances.set change', 'instances.set delete', 'veto.set']); b.request(human, { verb: 'veto.set', args: VETO }); b.request(human, { verb: 'instances.set', args: ADD }); b.request(human, { verb: 'instances.set', args: { revision: ADD.revision, instance: 'pm', op: 'change', vars: {} } }); // S2c: the delete is a request after the writer's check, and writes nothing yet. assert.ok(b.request(human, { verb: 'instances.set', args: { revision: ADD.revision, instance: 'pm', op: 'delete' } }).request); b.request(tester, { verb: 'instances.set', args: ADD }); b.request(tester, { verb: 'instances.set', args: { revision: ADD.revision, instance: 'pm', op: 'change', vars: {} } }); assert.deepEqual(calls, [['veto.set', 'keep', 'jason'], ['instances.set', 'add', 'jason'], ['instances.set', 'change', 'jason'], ['instances.check', undefined, 'jason'], ['instances.set', 'add', 'tester'], ['instances.set', 'change', 'tester']]); });