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.
368 lines
26 KiB
JavaScript
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); }
|
|
});
|