// Row 149 (#1643): raise_decision takes an optional domain, technical or delivery, so a raise can // reach arbiters.technical. The tool sends what it is given; the broker defaults to delivery. import test from 'node:test'; import assert from 'node:assert/strict'; import { typedTools, callTool } from '../src/tools.mjs'; import { resolvedFor, fakeToolSocket } from './helpers.mjs'; import { fixture } from '../../bus/tests/spec-fixtures.mjs'; const options = [{ key: 'yes', text: 'Yes' }, { key: 'no', text: 'No' }]; test('raise_decision offers domain as technical or delivery only, optional, and says where a raise goes', t => { for (const role of ['coder', 'reviewer', 'pm', 'chief']) { const raise = typedTools({ resolved: resolvedFor(t, role) }).find(x => x.name === 'raise_decision'); const { domain } = raise.parameters.properties; assert.equal(domain.type, 'string'); assert.deepEqual(domain.enum, ['technical', 'delivery']); for (const other of ['Technical', 'DELIVERY', 'human', 'security', '']) assert.ok(!domain.enum.includes(other), other); assert.match(domain.description, /Leaving it out means delivery/); assert.ok(!raise.parameters.required.includes('domain')); assert.equal(raise.parameters.additionalProperties, false); assert.match(raise.description, /A cross-role raise, or a gated raise citing the project's current spec commit, goes to the business's arbiter for its domain/); assert.match(raise.description, /an uncited gated raise or a hard wait goes to the human/); } }); test('raise_decision forwards domain to decision.raise, and a call without it sends none', async t => { const f = await fixture(t, { arbiters: { technical: 'cto', delivery: 'pm' }, roles: { coder: { definition: 'coder', authority: { withinRole: ['message.send'], crossRole: ['task.reassign'] } }, cto: { definition: 'cto', authority: { withinRole: ['message.send'], crossRole: [] } }, pm: { definition: 'pm', authority: { withinRole: ['message.send'], crossRole: [] } }, }, }); const byName = new Map(typedTools({ resolved: { definition: 'coder', limits: { tools: [], authority: { withinRole: ['message.send'], crossRole: ['task.reassign'], } } } }).map(tool => [tool.name, tool])); const server = await fakeToolSocket(t, f.root, (name, args) => f.broker.request(f.coder, { verb: byName.get(name).verb, args })); const base = { action: 'task.reassign', question: 'Hand T-1 to another coder?', options, recommendation: 'yes', blocking: false, target: 'T-1' }; const technical = await callTool(server.path, 'raise_decision', { ...base, domain: 'technical' }); const delivery = await callTool(server.path, 'raise_decision', { ...base, domain: 'delivery' }); const omitted = await callTool(server.path, 'raise_decision', base); assert.deepEqual([technical.route_to, delivery.route_to, omitted.route_to], ['cto', 'pm', 'pm']); assert.equal(server.calls[0].args.domain, 'technical'); assert.equal(server.calls[1].args.domain, 'delivery'); assert.equal(Object.hasOwn(server.calls[2].args, 'domain'), false); // The harness doesn't check its own schema, so a value outside it reaches the broker, which refuses it. await assert.rejects(callTool(server.path, 'raise_decision', { ...base, domain: 'security' }), /invalid-domain/); assert.equal(f.store.get('SELECT count(*) n FROM decisions').n, 3); });