Files
stack/packages/cli/tests/launch-routes.test.mjs
T
jason.woltje c216f47dbb plugins, cli, bus, business, webui: harness, model and thinking from the instance vars, a fallback route, and a harness plug-in kind (row 65, #1558)
Adds the harness plug-in kind (interface 1). packages/plugins/harness/pi
and claude-code hold what core branched on: the adapter, thinking levels,
preflight, version probe, sandbox mounts, builtins line, typed tool names,
gate timeout, bundle files, adapter env, turn end and session-files path.
Core reaches a module only through the loader.

A session's route is the instance's harness, model and thinking vars, or
the three fallback. vars; a missing one refuses no-route, and the system
file's execution keys are never read for a session. The launcher checks
the primary route, then the fallback: harness-off, harness-refused,
thinking-unsupported, then preflight. A failed preflight falls back when
a fallback is set, and any refusal of the fallback names the primary's
failure too. Account checks run
only when the route's module counts an account. The manifest records
route, routeReason and harnessVersion, replacing piVersion and
claudeVersion. pi gets --thinking, Claude Code --effort, both from
MOSAIC_THINKING.

The business file refuses a partial fallback. routes and routes.set are
human-only bus verbs outside ORGANIZATION_KEYS; checkRoutes is shared by
routes.set, instances.set and limits.set and checks only changed routes.
The chief of staff changes an instance's model, and the assistant's whole
vars (Q169), through limits.set, so its route write runs the same checks.
A model outside launch.models refuses unknown-model, primary or fallback,
and routes.set passes that code through.
A new run binds only on a switched-on harness module name and records
its transcript path, which the usage reader uses. Settings gains a
Routing table with six selects per instance and loses the Role models
fieldset; it posts changed rows to /api/routes. webui/src/bus.mjs is
unchanged.

Until Jason's `mosaic plugins set` adds "harness": ["claude-code"], a new
launch refuses harness-off.
2026-10-19 11:27:19 -05:00

142 lines
8.7 KiB
JavaScript

// Row 65 (#1558, REQ-PLUG-2, Q156): the launcher takes a launch's route from the instance's
// harness, model and thinking vars and its harness from the plug-in loader. A stub host answers
// the ops, as in launch-limit-checks: mayLaunch records the route's provider, model and account,
// and authorizeLaunch stops the launch before anything spawns. The fixture modules live in a
// scratch plug-in tree; no core file names them.
import test from 'node:test';
import assert from 'node:assert/strict';
import {readFileSync} from 'node:fs';
import {createLauncher} from '../src/launcher.mjs';
import {fakeHarnesses,fixture,tmp} from './helpers.mjs';
import {launchLogFile,SESSION_CREDENTIALS} from '../../seat/src/session.mjs';
const PI=JSON.stringify(new URL('../../plugins/harness/pi/index.mjs',import.meta.url).href);
// down fails its preflight; sparse takes one thinking level; metered spends launch.account; broken
// lacks the kind's exports; throws throws from its preflight. Each is the pi module otherwise.
const MODULES={
down:`export * from ${PI};\nexport const preflight = () => ({ code: "fixture-down", message: "fixture preflight fails" });\n`,
sparse:`export * from ${PI};\nexport const levels = () => ["low"];\n`,
metered:`export * from ${PI};\nexport const countsAccount = () => true;\n`,
broken:'export const interfaceVersion = 1;\nexport const adapter = () => "/nowhere";\n',
throws:`export * from ${PI};\nexport function preflight() { throw new Error("fixture preflight threw"); }\n`,
};
const ON=['pi','claude-code','down','sparse','metered','broken','throws','absent'];
// vars are the coder's whole agent vars; edit changes the rest of the file.
async function setup(t,vars,{on=ON,edit=doc=>doc,env:more={}}={}){
const root=tmp(t),f=fixture(root,'acme',doc=>{doc.roles.coder.vars=vars;doc.plugins={...doc.plugins,harness:on};return edit(doc);}),ops=[];
Object.assign(f.env,more);
const host={business:'acme',beforeClose:()=>{},op:async x=>{
ops.push(x);
if(x.op==='identity')return {business:'acme',role:'pm'};
if(x.op==='mayLaunch')return {allow:true};
if(x.op==='authorizeLaunch')throw Object.assign(Error('fixture authorization stop'),{code:'fixture-policy'});
return {};
}};
const makeLoader=await fakeHarnesses(t,{pi:null,'claude-code':null},write=>{for(const [name,body] of Object.entries(MODULES))write('harness',name,body);});
// The system config's execution keys are set and must never reach a route.
const system={...JSON.parse(readFileSync(f.config,'utf8'))};
system.execution={...system.execution,adapter:'pi',model:'glm-5.3-flash'};
const launcher=createLauncher({host,system,env:f.env,makeLoader,bwrap:'/usr/bin/true',log:()=>{}});
const events=()=>readFileSync(launchLogFile(f.dataRoot,'acme'),'utf8').trim().split('\n').map(l=>JSON.parse(l));
const launch=()=>launcher.fromSession('fixture-cap','coder');
const may=()=>ops.filter(x=>x.op==='mayLaunch').map(({provider,model,account})=>({provider,model,account}));
return {launch,ops,may,last:()=>events().at(-1)};
}
const route=(harness,model,thinking)=>({harness,model,thinking});
const fallback=(harness,model,thinking)=>({'fallback.harness':harness,'fallback.model':model,'fallback.thinking':thinking});
const SONNET={provider:'zai',model:'claude-sonnet-5-5',account:null}, FLASH={provider:'zai',model:'glm-5.3-flash',account:null};
test('row 65 a passing primary is the route: its provider and model reach mayLaunch, and a stated fallback is never used',async t=>{
const s=await setup(t,{...route('pi','claude-sonnet-5-5','high'),...fallback('down','glm-5.3-flash','low')});
assert.equal((await s.launch()).error,'fixture-policy');
assert.deepEqual(s.may(),[SONNET]);
});
test('row 65 a failed primary preflight takes the stated fallback, and the limit checks see the fallback route',async t=>{
const s=await setup(t,{...route('down','claude-sonnet-5-5','high'),...fallback('pi','glm-5.3-flash','low')});
assert.equal((await s.launch()).error,'fixture-policy');
assert.deepEqual(s.may(),[FLASH]);
});
test('row 65 a failed primary with no fallback refuses with the preflight code and never asks mayLaunch',async t=>{
const s=await setup(t,route('down','claude-sonnet-5-5','high'));
assert.deepEqual(await s.launch(),{ok:false,error:'fixture-down'});
assert.equal(s.last().why,'fixture preflight fails');
assert.deepEqual(s.may(),[]);
});
test('row 65 a fallback whose own preflight fails refuses with its code and names both reasons',async t=>{
const s=await setup(t,{...route('down','claude-sonnet-5-5','high'),...fallback('down','glm-5.3-flash','low')});
assert.deepEqual(await s.launch(),{ok:false,error:'fixture-down'});
assert.equal(s.last().why,'fallback down: fixture preflight fails (primary down: fixture preflight fails)');
assert.deepEqual(s.may(),[]);
});
test('row 65 the fallback route spends launch.account when its module counts the account',async t=>{
const env=Object.fromEntries(SESSION_CREDENTIALS.flat().map(k=>[k,'']));
Object.assign(env,{ANTHROPIC_AUTH_TOKEN:'stand-in',ANTHROPIC_BASE_URL:'http://127.0.0.1:9'});
const s=await setup(t,{...route('down','claude-sonnet-5-5','high'),...fallback('metered','glm-5.3-flash','low')},{env,edit:doc=>{doc.launch.account={name:'proxy-login',credential:'ANTHROPIC_AUTH_TOKEN+ANTHROPIC_BASE_URL'};return doc;}});
assert.equal((await s.launch()).error,'fixture-policy');
assert.deepEqual(s.may(),[{...FLASH,account:'proxy-login'}]);
});
test('row 65 a missing harness, model or thinking var refuses no-route naming it, and the system execution keys never fill it',async t=>{
for(const missing of ['harness','model','thinking']){
const vars=route('pi','claude-sonnet-5-5','high');delete vars[missing];
const s=await setup(t,vars);
assert.deepEqual(await s.launch(),{ok:false,error:'no-route'},missing);
assert.equal(s.last().why,`instance coder: no ${missing} var`);
assert.deepEqual(s.may(),[],missing);
}
});
test('row 65 a harness that is not switched on refuses harness-off, primary or fallback, and an off primary never falls back',async t=>{
const cases=[
[route('pi','claude-sonnet-5-5','high'),'pi'],
[{...route('pi','claude-sonnet-5-5','high'),...fallback('down','glm-5.3-flash','low')},'down'],
[{...route('down','claude-sonnet-5-5','high'),...fallback('pi','glm-5.3-flash','low')},'down'],
];
for(const [vars,off] of cases){
const s=await setup(t,vars,{on:ON.filter(n=>n!==off)});
assert.deepEqual(await s.launch(),{ok:false,error:'harness-off'},off);
assert.equal(s.last().why,`harness ${off} isn't switched on in plugins.harness`);
assert.deepEqual(s.may(),[],off);
}
// A file with no plugins.harness at all refuses the same way.
const s=await setup(t,route('pi','claude-sonnet-5-5','high'),{on:undefined,edit:doc=>{delete doc.plugins.harness;return doc;}});
assert.deepEqual(await s.launch(),{ok:false,error:'harness-off'});
});
test('row 65 the loader refusing a module refuses harness-refused and names it; a module that throws refuses harness-failed',async t=>{
for(const [name,reason] of [['broken',/^harness broken: missing-export: /],['absent',/^harness absent: /]]){
const s=await setup(t,route(name,'claude-sonnet-5-5','high'));
assert.deepEqual(await s.launch(),{ok:false,error:'harness-refused'},name);
assert.match(s.last().why,reason);
}
const s=await setup(t,route('throws','claude-sonnet-5-5','high'));
assert.deepEqual(await s.launch(),{ok:false,error:'harness-failed'});
assert.equal(s.last().why,'harness throws preflight: fixture preflight threw');
});
test('row 65 a thinking level the route harness does not apply refuses thinking-unsupported, on the primary or a fallback taken',async t=>{
// A refused fallback names the primary's failure too, so the reason it was tried stays visible.
for(const [vars,why] of [
[route('sparse','claude-sonnet-5-5','high'),'harness sparse takes thinking low, not high'],
[{...route('down','claude-sonnet-5-5','high'),...fallback('sparse','glm-5.3-flash','high')},'harness sparse takes thinking low, not high (primary down: fixture preflight fails)'],
]){
const s=await setup(t,vars);
assert.deepEqual(await s.launch(),{ok:false,error:'thinking-unsupported'});
assert.equal(s.last().why,why);
assert.deepEqual(s.may(),[]);
}
// A fallback the loader refuses names both too.
const refused=await setup(t,{...route('down','claude-sonnet-5-5','high'),...fallback('broken','glm-5.3-flash','high')});
assert.deepEqual(await refused.launch(),{ok:false,error:'harness-refused'});
assert.match(refused.last().why,/^harness broken: missing-export: .* \(primary down: fixture preflight fails\)$/);
// claude-code has no off: its module's own levels refuse it.
const s=await setup(t,route('claude-code','claude-sonnet-5-5','off'));
assert.deepEqual(await s.launch(),{ok:false,error:'thinking-unsupported'});
assert.equal(s.last().why,'harness claude-code takes thinking low, medium, high, xhigh, max, not off');
});