Files
stack/packages/bus/tests/pack-hire.test.mjs
jason.woltje b913f0f4c8 fix(bus,webui): pr2 review round, WebUI receipt carries routeFrom, design note, member and project tests (#1873, lane pr2)
The WebUI packs receipt passes routeFrom through on each hire line; the WebUI
README and TOOLS.md name it. The starter packs design doc gets a dated note
for the 2026-11-14 route ruling. New bus tests: a member pack hire on the
assistant route marks hired and awaiting-owner lines routeFrom assistant, and
a project pack with no chief route hires into the project on the assistant's
harness and model.
2026-11-14 18:37:28 -06:00

368 lines
26 KiB
JavaScript

// Ruling 2026-11-14 ("If I choose a starter pack and apply it, that is a decision!"): a person's
// apply records one hire decision per proposed hire in the same act and applies it, so the agents
// exist when the call returns. The owner approves every line; a member with org.hire gets the
// unflagged hires and the rest wait in the owner's inbox; an agent's apply never hires.
import test from 'node:test';
import assert from 'node:assert/strict';
import { chmodSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import { pluginHash } from '../../plugins/src/pinned.mjs';
import { installPlugin } from '../../plugins/src/install.mjs';
import { starterPack as codingPack } from '../../plugins/starter-pack/coding/index.mjs';
import { templateHash } from '../../organization/src/definitions.mjs';
const SUBJECT = { provider: 'test', iss: 'test', sub: 'owner' };
const ROUTE = { harness: 'pi', model: 'claude-opus-5-5' };
const ASSISTANT_ROUTE = { harness: 'pi', model: 'glm-5.3-flash', thinking: 'low' };
// assistant: false, no assistant instance; {} an assistant with no vars; any other object the
// assistant's vars. Lane pr2: a pack hire falls back to the org assistant's route.
async function harness(t, { route = true, assistant = false, mutate = () => {} } = {}) {
const { startBroker } = await import('../src/runtime.mjs');
const { businessDoc, rolesCopy, systemFor, writeJson } = await import('../../organization/tests/helpers.mjs');
const root = mkdtempSync(join(tmpdir(), 'packhire-'));
const rolesDir = rolesCopy(root);
const doc = businessDoc(root, 'demo');
doc.plugins = { harness: ['pi'] };
if (route) doc.roles.chief.vars = { ...ROUTE };
if (assistant) doc.roles.assistant = { definition: 'assistant', ...(Object.keys(assistant).length ? { vars: { ...assistant } } : {}) };
mutate(doc);
writeJson(join(root, 'businesses/demo.json'), doc);
const data = join(root, 'data');
mkdirSync(data, { mode: 0o700 });
const roles = { chief: { definition: 'chief-of-staff', authority: { withinRole: ['decision.resolve.technical', 'decision.resolve.delivery', 'organization.change'], crossRole: [] } },
coder: { definition: 'coder', authority: { withinRole: ['message.send'], crossRole: [] } } };
const businesses = { demo: { id: 'demo', human: 'jason', arbiters: { technical: 'chief', delivery: 'chief' }, roles } };
const bus = await startBroker({ dataRoot: data, businesses, organization: { dir: root, rolesDir, system: systemFor(root) } });
const thaw = (p) => { const st = lstatSync(p); if (st.isSymbolicLink()) return; if (st.isDirectory()) { chmodSync(p, 0o700); for (const e of readdirSync(p)) thaw(join(p, e)); } };
t.after(async () => { await bus.close(); thaw(root); rmSync(root, { recursive: true, force: true }); });
const b = bus.broker;
const consoleOwner = b.bindConsole({ business: 'demo', human: 'jason', subject: SUBJECT });
const human = b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true });
const member = b.bindHireConsole({ business: 'demo', actor: 'alice', subject: { provider: 'test', iss: 'test', sub: 'alice' }, rights: ['org.hire', 'board.read'] });
const call = async (verb, args = {}, cap = consoleOwner) => b.request(cap, { verb, args });
const path = join(root, 'businesses/demo.json');
const file = () => JSON.parse(readFileSync(path, 'utf8'));
const decisions = list => b.request(human, { verb: 'decisions', args: { list } });
// The broker's own database, read-only.
const db = (sql, ...a) => { const d = new DatabaseSync(join(data, 'bus/bus.sqlite'), { readOnly: true }); try { return d.prepare(sql).all(...a); } finally { d.close(); } };
const store = { all: db, get: (sql, ...a) => db(sql, ...a)[0] };
const events = () => store.all("SELECT kind, body FROM events WHERE business='demo' ORDER BY seq").map(r => ({ kind: r.kind, body: JSON.parse(r.body) }));
let n = 0;
const install = (pack) => {
const src = join(root, `src-${n++}`);
mkdirSync(join(src, 'starter-pack'), { recursive: true });
writeFileSync(join(src, 'starter-pack', 'index.mjs'), `export const interfaceVersion = 1;\nexport function starterPack() { return ${JSON.stringify(pack)}; }\n`);
const manifest = { name: pack.id, version: '1.0.0', kinds: ['starter-pack'], interfaceVersions: { 'starter-pack': 1 }, sha256: '0'.repeat(64),
source: { path: 'fixture', version: '1.0.0' }, signature: null, tools: [], actions: [], services: [], roleTemplates: [] };
writeFileSync(join(src, 'plugin.json'), JSON.stringify(manifest));
manifest.sha256 = pluginHash(src);
writeFileSync(join(src, 'plugin.json'), JSON.stringify(manifest));
installPlugin({ dataRoot: data, source: src, expectedHash: manifest.sha256 });
};
const op = (name) => events().filter(e => e.kind === 'action.allowed' && e.body.operation === name).map(e => e.body);
return { bus, b, root, rolesDir, path, consoleOwner, human, member, call, file, decisions, events, op, store, install };
}
const apply = async (call, args = { pack: 'coding' }, cap) => {
const preview = await call('pack.preview.console', args, cap);
return { preview, out: await call('pack.apply.console', { ...args, digest: preview.digest }, cap) };
};
// The evidence an inbox-approved hire leaves: a decision.raise naming the hire, a console
// resolution yes, a hire.consume and a hire.apply, and the instance's hireDecision.
function assertHireRecord(h, name, decision) {
const raise = h.op('decision.raise').find(r => r.decision === decision);
assert.ok(raise, `decision.raise for ${name}`);
assert.equal(raise.hire.name, name);
assert.equal(raise.action, 'role.hire');
assert.ok(h.op('hire.consume').some(r => r.decision === decision), `hire.consume for ${name}`);
assert.ok(h.op('hire.apply').some(r => r.decision === decision), `hire.apply for ${name}`);
const resolved = h.store.all('SELECT op, choice, via, by FROM decision_events WHERE decision=?', decision);
assert.deepEqual(resolved.map(r => [r.op, r.choice]), [['resolved', 'yes']]);
assert.equal(h.file().roles[name].hireDecision, decision);
}
test('PD the owner applies a pack with flagged hires on a fresh org; the agents exist on return, each with its decision record', async t => {
const h = await harness(t);
const { preview, out } = await apply(h.call);
// The preview lists the flagged and credential lines the owner's apply approves.
assert.ok(preview.hires.some(x => x.flagged.length || x.credentialLines.length), JSON.stringify(preview.hires));
assert.deepEqual(out.hires.map(x => [x.name, x.state]), [['pm-1', 'hired'], ['coder-1', 'hired'], ['reviewer-1', 'hired']]);
const doc = h.file();
for (const x of out.hires) {
assert.ok(Object.hasOwn(doc.roles, x.name), `${x.name} exists`);
assert.ok(doc.launch.instances.includes(x.name), `${x.name} is launched`);
assertHireRecord(h, x.name, x.decision);
}
// The coder's credential lines are approved, not bound: the instance is written with no credential.
assert.equal(doc.roles['coder-1'].credentials, undefined);
// Each hire names its pack.
assert.deepEqual(h.op('pack.hire').map(e => [e.hire, e.pack, e.origin, e.project]), out.hires.map(x => [x.name, 'coding', 'shipped', null]));
// The instances read carries the pack each agent came from.
const inst = await h.call('instances', {}, h.human);
assert.deepEqual(inst.instances['coder-1'].pack, { id: 'coding', origin: 'shipped', project: null });
assert.equal(inst.instances.pm.pack, undefined);
// The packs read counts the hires and lists nothing open.
const packs = await h.call('packs.console');
const applied = packs.applied.find(a => a.id === 'coding');
assert.deepEqual(applied.hired.sort(), ['coder-1', 'pm-1', 'reviewer-1']);
assert.deepEqual(applied.open, []);
});
test('PD a re-apply hires nothing twice', async t => {
const h = await harness(t);
await apply(h.call);
const raised = h.op('decision.raise').length;
const again = await apply(h.call);
// The second apply proposes nothing: each proposal's instance exists.
assert.deepEqual(again.out.hires, []);
assert.deepEqual((await h.call('packs.console')).applied.find(a => a.id === 'coding').hired.sort(), ['coder-1', 'pm-1', 'reviewer-1']);
// The pack action on the same entry skips each instance that exists.
const hired = await h.call('pack.hire.console', { pack: 'coding' });
assert.equal(h.op('decision.raise').length, raised);
assert.ok(hired.hires.every(x => x.state === 'exists'), JSON.stringify(hired));
});
test('PD the owner CLI apply hires through the same path, resolved via cli', async t => {
const h = await harness(t);
const preview = await h.call('pack.preview', { pack: 'coding' }, h.human);
const out = await h.call('pack.apply', { pack: 'coding', digest: preview.digest }, h.human);
assert.deepEqual(out.hires.map(x => x.state), ['hired', 'hired', 'hired']);
for (const x of out.hires) {
assertHireRecord(h, x.name, x.decision);
assert.equal(h.store.get('SELECT via FROM decision_events WHERE decision=?', x.decision).via, 'cli');
assert.equal(h.store.get('SELECT raised_by_run FROM decisions WHERE id=?', x.decision).raised_by_run, 'human-cli');
}
});
test('PD with no chief route the apply records the pack, hires nothing and says why; the hire action on the existing record hires once a route exists', async t => {
const h = await harness(t, { route: false });
const { out } = await apply(h.call);
assert.deepEqual(out.hires.map(x => [x.name, x.state, x.code]), [['pm-1', 'refused', 'pack-hire-route-missing'], ['coder-1', 'refused', 'pack-hire-route-missing'], ['reviewer-1', 'refused', 'pack-hire-route-missing']]);
assert.equal(h.op('decision.raise').length, 0);
const packs = await h.call('packs.console');
assert.deepEqual(packs.applied.find(a => a.id === 'coding').open.map(o => [o.name, o.state, o.code, typeof o.reason]),
[['pm-1', 'refused', 'pack-hire-route-missing', 'string'], ['coder-1', 'refused', 'pack-hire-route-missing', 'string'], ['reviewer-1', 'refused', 'pack-hire-route-missing', 'string']]);
// The owner gives the chief a route; the pack action hires from the packs entry already recorded.
const doc = h.file();
doc.roles.chief.vars = { ...ROUTE };
writeFileSync(h.path, JSON.stringify(doc, null, 2) + '\n');
const hired = await h.call('pack.hire.console', { pack: 'coding' });
assert.deepEqual(hired.hires.map(x => [x.name, x.state]), [['pm-1', 'hired'], ['coder-1', 'hired'], ['reviewer-1', 'hired']]);
for (const x of hired.hires) assertHireRecord(h, x.name, x.decision);
assert.deepEqual((await h.call('packs.console')).applied.find(a => a.id === 'coding').open, []);
// The CLI's pack hire is the same action; nothing is left to hire.
const cli = await h.call('pack.hire', { pack: 'coding' }, h.human);
assert.ok(cli.hires.every(x => x.state === 'exists'));
await assert.rejects(h.call('pack.hire.console', { pack: 'nosuch' }), /pack-not-applied/);
});
// A pack with one hire inside the chief's own grants and two above them.
// An installed pack's own definition narrows a template; the scout keeps only read.
const mixkit = (rolesDir) => {
const SCOUT = { from: { template: 'coder', sha256: templateHash(rolesDir, 'coder') }, role: { tools: ['read'], network: 'none', authority: { withinRole: [], crossRole: [] }, credentials: [] }, instructions: 'Read and report.' };
const p = structuredClone(codingPack());
p.id = 'mixkit';
p.definitions = { scout: SCOUT, coder: p.definitions.coder, reviewer: p.definitions.reviewer };
p.hires = [{ name: 'scout-1', definition: 'scout', reason: 'reads and reports' }, { name: 'coder-1', definition: 'coder', reason: 'implements each change' }, { name: 'reviewer-1', definition: 'reviewer', reason: 'reviews each change' }];
p.workflows = { change: { description: 'One change.', steps: [{ id: 'build', title: 'Build it', roles: ['coder'], state: 'in-progress' }, { id: 'review', title: 'Review it', roles: ['reviewer'], state: 'in-review' }] } };
return p;
};
test('PD a member with org.hire gets the unflagged hires; the flagged ones become open decisions in the owner inbox', async t => {
const h = await harness(t, { route: false });
h.install(mixkit(h.rolesDir));
await apply(h.call, { pack: 'mixkit' });
const doc = h.file();
doc.roles.chief.vars = { ...ROUTE };
writeFileSync(h.path, JSON.stringify(doc, null, 2) + '\n');
const out = await h.call('pack.hire.console', { pack: 'mixkit' }, h.member);
assert.deepEqual(out.hires.map(x => [x.name, x.state]), [['scout-1', 'hired'], ['coder-1', 'awaiting-owner'], ['reviewer-1', 'awaiting-owner']]);
const after = h.file();
assert.ok(after.launch.instances.includes('scout-1'));
assert.equal(Object.hasOwn(after.roles, 'coder-1'), false);
assert.equal(Object.hasOwn(after.roles, 'reviewer-1'), false);
assertHireRecord(h, 'scout-1', out.hires[0].decision);
assert.equal(h.store.get('SELECT via FROM decision_events WHERE decision=?', out.hires[0].decision).via, 'console');
// Each flagged hire is an open decision routed to the human, raised by the member's act.
const waiting = out.hires.filter(x => x.state === 'awaiting-owner');
for (const x of waiting) {
const d = h.store.get('SELECT route_to, raised_by_role, action FROM decisions WHERE id=?', x.decision);
assert.deepEqual([d.route_to, d.raised_by_role, d.action], ['human', 'human', 'role.hire']);
assert.equal(h.store.all('SELECT 1 FROM decision_events WHERE decision=?', x.decision).length, 0);
}
// The packs read lists both as open, awaiting the owner, each with the pack's reason.
const applied = (await h.call('packs.console')).applied.find(a => a.id === 'mixkit');
assert.deepEqual(applied.hired, ['scout-1']);
assert.deepEqual(applied.open.map(o => [o.name, o.state, o.reason, o.decision]), [['coder-1', 'awaiting-owner', 'implements each change', waiting[0].decision], ['reviewer-1', 'awaiting-owner', 'reviews each change', waiting[1].decision]]);
// The member's pack action never re-raises a hire an open decision names.
const again = await h.call('pack.hire.console', { pack: 'mixkit' }, h.member);
assert.deepEqual(again.hires.map(x => [x.name, x.state, x.decision ?? null]), [['scout-1', 'exists', null], ['coder-1', 'open', waiting[0].decision], ['reviewer-1', 'open', waiting[1].decision]]);
// The member cannot approve a flagged hire; the owner can, from the inbox, and the agent exists.
const view = await h.call('hire.preview.console', { id: waiting[0].decision });
await assert.rejects(h.call('hire.resolve.console', { id: waiting[0].decision, choice: 'yes', digest: view.digest }, h.member), /hire-owner-required|human-required/);
await h.call('hire.resolve.console', { id: waiting[0].decision, choice: 'yes', digest: view.digest });
assert.ok(h.file().launch.instances.includes('coder-1'));
// The owner's pack action approves the rest: the open pack decision is withdrawn by the owner and
// replaced by one approved in the same act.
const owner = await h.call('pack.hire.console', { pack: 'mixkit' });
assert.deepEqual(owner.hires.map(x => [x.name, x.state, x.supersedes ?? null]), [['scout-1', 'exists', null], ['coder-1', 'exists', null], ['reviewer-1', 'hired', waiting[1].decision]]);
assert.deepEqual(h.store.all('SELECT op, by FROM decision_events WHERE decision=?', waiting[1].decision).map(r => [r.op, r.by]), [['withdrawn', 'jason']]);
assertHireRecord(h, 'reviewer-1', owner.hires[2].decision);
const done = (await h.call('packs.console')).applied.find(a => a.id === 'mixkit');
assert.deepEqual(done.hired.sort(), ['coder-1', 'reviewer-1', 'scout-1']);
assert.deepEqual(done.open, []);
});
test('PD an agent apply still refuses human-required and hires nothing; the tester and a cli cap on the console name refuse', async t => {
const h = await harness(t);
const agent = h.b.bindLaunch({ business: 'demo', role: 'coder', run: 'coder-run', harness: 'pi', address: 'coder-run' });
h.b.request(agent, { verb: 'role.claim' });
assert.throws(() => h.b.request(agent, { verb: 'pack.apply.console', args: { pack: 'coding', digest: 'a'.repeat(64) } }), /human-required/);
await assert.rejects(Promise.resolve().then(() => h.b.request(agent, { verb: 'pack.hire', args: { pack: 'coding' } })), /human-required/);
await assert.rejects(Promise.resolve().then(() => h.b.request(agent, { verb: 'pack.hire.console', args: { pack: 'coding' } })), /human-required/);
await assert.rejects(Promise.resolve().then(() => h.b.request(h.human, { verb: 'pack.hire.console', args: { pack: 'coding' } })), /human-required/);
await assert.rejects(h.call('pack.hire', { pack: 'coding' }), /human-required/);
for (const args of [{}, { pack: 7 }, { pack: 'coding', extra: 1 }, { pack: 'coding', project: 'Bad Name' }])
await assert.rejects(h.call('pack.hire.console', args), /invalid-request/);
assert.equal(h.op('decision.raise').length, 0);
});
test('PD a project pack hires into projects.<p>.agents', async t => {
const h = await harness(t);
const pack = structuredClone(codingPack());
pack.id = 'projkit'; pack.scope = 'project';
h.install(pack);
const { out } = await apply(h.call, { pack: 'projkit', project: 'stack' });
assert.deepEqual(out.hires.map(x => x.state), ['hired', 'hired', 'hired']);
const agents = h.file().projects.stack.agents;
for (const x of out.hires) { assert.equal(agents[x.name].hireDecision, x.decision); assert.equal(agents[x.name].credentials, undefined); }
assert.equal(Object.hasOwn(h.file().roles, 'coder-1'), false);
assert.deepEqual(h.op('pack.hire').map(e => e.project), ['stack', 'stack', 'stack']);
const applied = (await h.call('packs.console')).applied.find(a => a.id === 'projkit');
assert.deepEqual(applied.hired.sort(), ['coder-1', 'pm-1', 'reviewer-1']);
const again = await h.call('pack.hire.console', { pack: 'projkit', project: 'stack' });
assert.ok(again.hires.every(x => x.state === 'exists'));
});
test('PD the new codes are in the refusal table with a rail and a next step that names no path', async () => {
const { REFUSALS } = await import('../src/refusals.mjs');
for (const c of ['pack-not-applied', 'pack-hire-route-missing', 'hire-owner-required', 'no-chief-of-staff']) {
assert.ok(Object.hasOwn(REFUSALS, c), c);
assert.ok(REFUSALS[c].rail?.length > 5);
assert.ok(REFUSALS[c].nextStep?.length > 20);
assert.ok(!/(^|[\s("'`])(\/|~\/)/.test(REFUSALS[c].nextStep));
}
});
test('PD the owner supersede withdraws an inbox decision only after its replacement hires; a refused replacement leaves it open', async t => {
const h = await harness(t, { route: false });
h.install(mixkit(h.rolesDir));
await apply(h.call, { pack: 'mixkit' });
const setRoute = (vars) => { const doc = h.file(); if (vars) doc.roles.chief.vars = { ...vars }; else delete doc.roles.chief.vars; writeFileSync(h.path, JSON.stringify(doc, null, 2) + '\n'); };
setRoute(ROUTE);
const out = await h.call('pack.hire.console', { pack: 'mixkit' }, h.member);
const waiting = out.hires.filter(x => x.state === 'awaiting-owner');
assert.equal(waiting.length, 2);
// The packs read marks each member-raised line as raised by a pack hire.
const open = (await h.call('packs.console')).applied.find(a => a.id === 'mixkit').open;
assert.deepEqual(open.map(o => [o.name, o.state, o.fromPack]), [['coder-1', 'awaiting-owner', true], ['reviewer-1', 'awaiting-owner', true]]);
// The chief loses its route; the owner's pack hire cannot build a replacement.
setRoute(null);
const owner = await h.call('pack.hire.console', { pack: 'mixkit' });
assert.deepEqual(owner.hires.map(x => [x.name, x.state, x.code ?? null]), [['scout-1', 'exists', null], ['coder-1', 'refused', 'pack-hire-route-missing'], ['reviewer-1', 'refused', 'pack-hire-route-missing']]);
// Both inbox decisions are still open: nothing withdrew them.
for (const x of waiting) assert.deepEqual(h.store.all('SELECT op FROM decision_events WHERE decision=?', x.decision), []);
// With the route back, the owner's pack hire replaces them, withdrawing each only once its replacement applied.
setRoute(ROUTE);
const done = await h.call('pack.hire.console', { pack: 'mixkit' });
assert.deepEqual(done.hires.map(x => [x.name, x.state, x.supersedes ?? null]), [['scout-1', 'exists', null], ['coder-1', 'hired', waiting[0].decision], ['reviewer-1', 'hired', waiting[1].decision]]);
for (const x of waiting) assert.deepEqual(h.store.all('SELECT op, by FROM decision_events WHERE decision=?', x.decision).map(r => [r.op, r.by]), [['withdrawn', 'jason']]);
});
test('PD a second pack hire on the same organization while one runs refuses organization-busy and raises nothing', async t => {
const h = await harness(t, { route: false });
await apply(h.call);
const doc = h.file();
doc.roles.chief.vars = { ...ROUTE };
writeFileSync(h.path, JSON.stringify(doc, null, 2) + '\n');
const first = h.call('pack.hire.console', { pack: 'coding' });
const second = h.call('pack.hire.console', { pack: 'coding' });
await assert.rejects(second, /organization-busy/);
const hired = await first;
assert.deepEqual(hired.hires.map(x => [x.name, x.state]), [['pm-1', 'hired'], ['coder-1', 'hired'], ['reviewer-1', 'hired']]);
// One decision per hire: the refused call raised nothing.
assert.equal(h.op('decision.raise').length, 3);
// Once the loop ends the latch is free again: the next pack hire runs and finds nothing to hire.
const later = await h.call('pack.hire.console', { pack: 'coding' });
assert.ok(later.hires.every(x => x.state === 'exists'), JSON.stringify(later));
});
// Lane pr2 (lead ruling 2026-11-14): a pack hire takes the chief of staff's harness and model, else
// the org assistant's, else refuses pack-hire-route-missing.
test('PR2 with no chief route the apply hires every proposal with the org assistant harness and model', async t => {
const h = await harness(t, { route: false, assistant: ASSISTANT_ROUTE });
const { out } = await apply(h.call);
assert.deepEqual(out.hires.map(x => [x.name, x.state, x.routeFrom]), [['pm-1', 'hired', 'assistant'], ['coder-1', 'hired', 'assistant'], ['reviewer-1', 'hired', 'assistant']]);
const doc = h.file();
for (const x of out.hires) {
assertHireRecord(h, x.name, x.decision);
assert.deepEqual(doc.roles[x.name].vars, ASSISTANT_ROUTE);
assert.deepEqual(h.op('decision.raise').find(r => r.decision === x.decision).hire.route, ASSISTANT_ROUTE);
}
});
test('PR2 a chief route wins over a different assistant route', async t => {
const h = await harness(t, { assistant: ASSISTANT_ROUTE });
const { out } = await apply(h.call);
assert.deepEqual(out.hires.map(x => [x.name, x.state, x.routeFrom]), [['pm-1', 'hired', 'chief'], ['coder-1', 'hired', 'chief'], ['reviewer-1', 'hired', 'chief']]);
for (const x of out.hires) assert.deepEqual(h.file().roles[x.name].vars, ROUTE);
});
test('PR2 an assistant with no harness and model gives no route: the apply refuses as before', async t => {
for (const assistant of [{}, { harness: 'pi' }]) {
const h = await harness(t, { route: false, assistant });
const { out } = await apply(h.call);
assert.deepEqual(out.hires.map(x => [x.name, x.state, x.code]), [['pm-1', 'refused', 'pack-hire-route-missing'], ['coder-1', 'refused', 'pack-hire-route-missing'], ['reviewer-1', 'refused', 'pack-hire-route-missing']]);
assert.equal(h.op('decision.raise').length, 0);
}
});
test('PR2 with several assistants the one named assistant gives the route, else the first in key order', async t => {
const OTHER = { harness: 'pi', model: 'claude-sonnet-5-5' };
const named = await harness(t, { route: false, assistant: ASSISTANT_ROUTE, mutate: doc => { doc.roles = { aide: { definition: 'assistant', vars: { ...OTHER } }, ...doc.roles }; } });
const a = await apply(named.call);
for (const x of a.out.hires) assert.deepEqual(named.file().roles[x.name].vars, ASSISTANT_ROUTE);
const first = await harness(t, { route: false, mutate: doc => { doc.roles.aide = { definition: 'assistant', vars: { ...OTHER } }; doc.roles.zed = { definition: 'assistant', vars: { ...ASSISTANT_ROUTE } }; } });
const b = await apply(first.call);
assert.deepEqual(b.out.hires.map(x => [x.state, x.routeFrom]), [['hired', 'assistant'], ['hired', 'assistant'], ['hired', 'assistant']]);
for (const x of b.out.hires) assert.deepEqual(first.file().roles[x.name].vars, OTHER);
});
test('PR2 a member pack hire on the assistant route marks hired and awaiting-owner lines routeFrom assistant', async t => {
const h = await harness(t, { route: false, assistant: ASSISTANT_ROUTE });
h.install(mixkit(h.rolesDir));
// The member's apply would not run; the owner's apply would hire everything. Record the pack first
// with the assistant route absent, then give it back and let the member hire.
const doc0 = h.file(); delete doc0.roles.assistant.vars; writeFileSync(h.path, JSON.stringify(doc0, null, 2) + '\n');
await apply(h.call, { pack: 'mixkit' });
const doc = h.file(); doc.roles.assistant.vars = { ...ASSISTANT_ROUTE }; writeFileSync(h.path, JSON.stringify(doc, null, 2) + '\n');
const out = await h.call('pack.hire.console', { pack: 'mixkit' }, h.member);
assert.deepEqual(out.hires.map(x => [x.name, x.state, x.routeFrom]), [['scout-1', 'hired', 'assistant'], ['coder-1', 'awaiting-owner', 'assistant'], ['reviewer-1', 'awaiting-owner', 'assistant']]);
assert.deepEqual(h.file().roles['scout-1'].vars, ASSISTANT_ROUTE);
});
test('PR2 a project pack with no chief route hires into the project on the assistant route', async t => {
const h = await harness(t, { route: false, assistant: ASSISTANT_ROUTE });
const pack = structuredClone(codingPack());
pack.id = 'projkit'; pack.scope = 'project';
h.install(pack);
const { out } = await apply(h.call, { pack: 'projkit', project: 'stack' });
assert.deepEqual(out.hires.map(x => [x.state, x.routeFrom]), [['hired', 'assistant'], ['hired', 'assistant'], ['hired', 'assistant']]);
const agents = h.file().projects.stack.agents;
for (const x of out.hires) { assert.equal(agents[x.name].hireDecision, x.decision); assert.deepEqual(h.op('decision.raise').find(r => r.decision === x.decision).hire.route, ASSISTANT_ROUTE); }
});