Files
stack/packages/bus/tests/message-citation.test.mjs
T
jason.woltjeandClaude Opus 5.5 d27042faf2 feat(bus): a within-role message cites a decision without consuming it (row 44, S2c, rocko)
A within-role message.send stores its decision as a citation: the broker
checks it exists in the business, and doesn't class-match it, consume it
or put it in action.allowed (lead decision 65). Sends that aren't
within-role keep the S2b authority rules. The README wording on
within-role sends is fixed.

Candidate agents/rocko/work/slice1-s2c-r1, build.patch 2c4f8d9f,
manifest aa249ad9. Darkwing approved round 1 on #1526 (comment 26778).
Integration gate in a worktree on ac4a6499: every package green on
Node 26; bus 58/58 on Node 24; every scripts/test-*.sh green. Node 24
failures in conversation, ledger, queue, seat and webui match the base.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 18:13:46 -05:00

109 lines
4.4 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Broker } from '../src/broker.mjs';
import { Store } from '../src/store.mjs';
function fixture(t, cross = false) {
const root = mkdtempSync(join(tmpdir(), 'bus-citation-')),
store = new Store(root);
const role = (withinRole = [], crossRole = []) => ({ authority: { withinRole, crossRole } });
const business = (id) => ({
id,
human: 'jason',
arbiters: { technical: 'cto', delivery: 'pm' },
roles: {
pm: role(cross ? [] : ['message.send'], cross ? ['message.send'] : []),
cto: role(),
coder: role(),
},
});
const b = new Broker({ store, businesses: { demo: business('demo'), other: business('other') } });
t.after(() => {
store.close();
rmSync(root, { recursive: true, force: true });
});
const call = (cap, verb, args = {}) => b.request(cap, { verb, args });
const agent = (role, business = 'demo') => {
const cap = b.bindLaunch({ business, role, run: business + role, harness: 'pi', address: role });
call(cap, 'role.claim');
return cap;
};
const pm = agent('pm'),
human = b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true });
const raise = (cap = pm, action = 'role.launch', target = 'coder') =>
call(cap, 'decision.raise', {
action,
target,
domain: 'technical',
question: 'Launch?',
options: [
{ key: 'yes', text: 'Yes' },
{ key: 'no', text: 'No' },
],
recommendation: 'yes',
blocking: true,
task_ref: 'vikunja:3/44',
});
const send = (id, body = 'Blocking: launch?') =>
call(pm, 'message.send', { to: 'human', class: 'DECISION', decision: id, body });
return { b, store, call, agent, pm, human, raise, send };
}
test('within-role sends cite an open gated launch decision without spending it or naming it in grants', (t) => {
const f = fixture(t),
d = f.raise();
assert.equal(d.class, 'gated');
const first = f.send(d.id),
second = f.send(d.id);
for (const id of [first.id, second.id])
assert.equal(f.store.get('SELECT decision FROM messages WHERE id=?', id).decision, d.id);
const events = f.store.all(
"SELECT subject,body FROM events WHERE kind='action.allowed' AND json_extract(body,'$.action')='message.send'",
);
assert.equal(events.length, 2);
for (const e of events) {
assert.equal(e.subject, 'human');
assert.equal(Object.hasOwn(JSON.parse(e.body), 'decision'), false);
}
f.call(f.human, 'decision.resolve', { id: d.id, choice: 'yes' });
assert.equal(f.b.authorize(f.pm, 'role.launch', { decision: d.id, target: 'coder' }).decision, d.id);
assert.throws(
() => f.b.authorize(f.pm, 'role.launch', { decision: d.id, target: 'coder' }),
/decision-consumed/,
);
// A citation is still valid after consumption: it grants no authority.
f.send(d.id, 'The launch was authorized.');
});
test('missing and foreign-business citations refuse and roll back message and grant', (t) => {
const f = fixture(t),
foreign = f.raise(f.agent('pm', 'other'));
for (const id of ['missing-decision', foreign.id]) assert.throws(() => f.send(id), /decision-not-found/);
assert.equal(f.store.get('SELECT count(*) AS n FROM messages').n, 0);
assert.equal(
f.store.get(
"SELECT count(*) AS n FROM events WHERE kind='action.allowed' AND json_extract(body,'$.action')='message.send'",
).n,
0,
);
});
test('cross-role sends still need a matching resolved decision and consume it once', (t) => {
const f = fixture(t, true),
unrelated = f.raise();
assert.throws(() => f.send(undefined), /decision-required/);
assert.throws(() => f.send(unrelated.id), /decision-mismatch/);
const d = f.raise(f.pm, 'message.send', 'human');
assert.throws(() => f.send(d.id), /decision-not-approved/);
f.call(f.agent('cto'), 'decision.resolve', { id: d.id, choice: 'yes' });
f.send(d.id);
assert.throws(() => f.send(d.id), /decision-consumed/);
const e = f.store.get(
"SELECT body FROM events WHERE kind='action.allowed' AND json_extract(body,'$.action')='message.send' AND json_extract(body,'$.operation') IS NOT 'decision.raise'",
);
assert.equal(JSON.parse(e.body).decision, d.id);
assert.throws(
() => f.b.authorize(f.pm, 'message.send', { decision: d.id, target: 'human' }),
/decision-consumed/,
);
});