Row 90, round 2. Dewey built it, Filbert approved it (comment 28068) on manifest 95611274 (21 paths, baseb8539480). The approved PRD's requirement ids live in packages/tasks/requirements.json. task_create refuses an id outside it, and the PM session gets a list_requirements tool. Round 2 adds requirements.mjs to the sandbox's RUNNER_FILES (decision 127). Sage gate onc047d028plus the patch, 23:50:16Z to 23:54:26Z: tasks 89, bus 111, harness 66, webui 101, cli 123, seat 33, business 60, control-board 124, queue 150, conversation 182, discord 178, ledger 78, runs 43, mosaic 69, all fail 0. Every scripts/test-*.sh exited 0. Co-Authored-By: Claude Opus 5.5 <[email protected]>
79 lines
3.9 KiB
JavaScript
79 lines
3.9 KiB
JavaScript
// Row 90 (#1584): `requirements`, the approved PRD's ids that task.create takes, for the role
|
|
// sessions that create tasks; and the PM's two no-task decisions, a request no id fits
|
|
// (prd.approve) and a run it thinks should stop (role.revoke), which reach the human's Inbox.
|
|
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 { Store } from '../src/store.mjs';
|
|
import { Broker } from '../src/broker.mjs';
|
|
import { loadRequirements } from '../../tasks/src/requirements.mjs';
|
|
|
|
const options = [
|
|
{ key: 'yes', text: 'Approve' },
|
|
{ key: 'no', text: 'Decline' },
|
|
];
|
|
const roles = {
|
|
pm: { authority: { withinRole: ['message.send', 'task.create'], crossRole: [] } },
|
|
coder: { authority: { withinRole: ['message.send'], crossRole: [] } },
|
|
};
|
|
const businesses = { demo: { id: 'demo', human: 'jason', arbiters: { technical: 'pm', delivery: 'pm' }, roles } };
|
|
function setup(t) {
|
|
const root = mkdtempSync(join(tmpdir(), 'bus-requirements-'));
|
|
const store = new Store(root);
|
|
const b = new Broker({ store, businesses });
|
|
t.after(() => {
|
|
store.close();
|
|
rmSync(root, { recursive: true, force: true });
|
|
});
|
|
const agent = (role) => {
|
|
const cap = b.bindLaunch({ business: 'demo', role, run: `demo-${role}-run`, harness: 'pi', address: role });
|
|
b.request(cap, { verb: 'role.claim' });
|
|
return cap;
|
|
};
|
|
const human = b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true });
|
|
const reader = b.bindReader({ business: 'demo' });
|
|
return { b, agent, human, reader };
|
|
}
|
|
const call = (b, cap, verb, args = {}) => b.request(cap, { verb, args });
|
|
const raise = (b, cap, action, extra = {}) =>
|
|
call(b, cap, 'decision.raise', { action, question: `${action}?`, options, recommendation: 'no', blocking: false, ...extra });
|
|
|
|
test('requirements returns the committed list to a holding role session, and no one else', (t) => {
|
|
const { b, agent, human, reader } = setup(t);
|
|
const pm = agent('pm');
|
|
const list = call(b, pm, 'requirements');
|
|
assert.deepEqual(list, loadRequirements());
|
|
assert.equal(list.requirements.length, 24);
|
|
assert.deepEqual(call(b, agent('coder'), 'requirements'), list);
|
|
assert.throws(() => call(b, human, 'requirements'), /agent-required/);
|
|
assert.throws(() => call(b, reader, 'requirements'), /read-only/);
|
|
// A second launch of the role that never claimed it holds nothing.
|
|
const stale = b.bindLaunch({ business: 'demo', role: 'pm', run: 'demo-pm-other', harness: 'pi', address: 'pm2' });
|
|
assert.throws(() => call(b, stale, 'requirements'), /not-holder/);
|
|
assert.throws(() => call(b, pm, 'requirements', { prd: 'docs/prd/mosaic-stack.md' }), /invalid-request/);
|
|
});
|
|
|
|
test("the PM's prd.approve with no task, when no id fits, reaches the human's Inbox", (t) => {
|
|
const { b, agent, human, reader } = setup(t);
|
|
const pm = agent('pm');
|
|
const d = raise(b, pm, 'prd.approve', { target: 'mosaic-stack-prd' });
|
|
assert.deepEqual([d.class, d.route_to, d.blocking, d.task_ref], ['gated', 'human', false, null]);
|
|
for (const cap of [human, reader]) assert.deepEqual(call(b, cap, 'inbox').map((r) => r.id), [d.id]);
|
|
assert.deepEqual(call(b, pm, 'inbox'), []);
|
|
// Blocking needs a task to hold.
|
|
assert.throws(() => raise(b, pm, 'prd.approve', { blocking: true }), /task-required/);
|
|
});
|
|
|
|
test("the PM's non-blocking role.revoke with no task reaches the human's Inbox", (t) => {
|
|
const { b, agent, human } = setup(t);
|
|
const pm = agent('pm');
|
|
agent('coder');
|
|
const d = raise(b, pm, 'role.revoke', { target: 'coder' });
|
|
assert.deepEqual([d.class, d.route_to, d.blocking, d.task_ref], ['gated', 'human', false, null]);
|
|
assert.deepEqual(d.authorization, { action: 'role.revoke', target: 'coder', approvalChoice: 'yes' });
|
|
assert.deepEqual(call(b, human, 'inbox').map((r) => r.id), [d.id]);
|
|
assert.throws(() => raise(b, pm, 'role.revoke', { target: 'coder', blocking: true }), /task-required/);
|
|
});
|