Files
stack/packages/cli/tests/launch-routes.test.mjs

158 lines
10 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 {initializeControl} from '../../processing-control/src/state.mjs';
import {fakeHarnesses,fixture,tmp,withoutHostCredentials} 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={},modelAdmission=null}={}){
const root=tmp(t),f=fixture(root,'acme',doc=>{doc.roles.coder.vars=vars;doc.plugins={...doc.plugins,harness:on};return edit(doc);}),ops=[];
initializeControl(f.dataRoot);
withoutHostCredentials(f.env,{piKey:true});
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==='modelAdmission' && modelAdmission) return modelAdmission;
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);write('model-provider','cliproxy',`export * from ${JSON.stringify(new URL('../../plugins/model-provider/cliproxy/index.mjs',import.meta.url).href)};`);});
// 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');
});
test('module-backed unavailable selection refuses before run creation',async t=>{
const s=await setup(t,route('pi','zai-fixture-id','low'),{edit:doc=>{doc.plugins['model-provider']=['cliproxy'];doc.launch.models['zai-fixture-id']={provider:'zai',model:'fixture-id',module:'cliproxy',upstream:'zai'};return doc;}});
assert.deepEqual(await s.launch(),{ok:false,error:'model-provider-unavailable'});
assert.equal(s.ops.some(op=>op.op==='modelAdmission'),true);
assert.deepEqual(s.may(),[{provider:'zai',model:'fixture-id',account:null}]);
});
test('a proxy uncertainty refusal survives launcher admission without creating a run',async t=>{
const s=await setup(t,route('pi','zai-fixture-id','low'),{modelAdmission:{allow:false,code:'proxy-uncertain'},edit:doc=>{doc.plugins['model-provider']=['cliproxy'];doc.launch.models['zai-fixture-id']={provider:'zai',model:'fixture-id',module:'cliproxy',upstream:'zai'};return doc;}});
assert.deepEqual(await s.launch(),{ok:false,error:'proxy-uncertain'});
assert.equal(s.last().code,'proxy-uncertain');
});