Review "changes 8" fix round for org-redact6. - launcher: a harness module's throw names its system code or class, never its message; preflight, loader, bundle, checkout, sandbox and route text is relayed with host paths replaced (relay()). - claude-code and pi preflights name MOSAIC_CLAUDE_BIN and the repository's node_modules, never a path; persona and skill bundle errors and sandboxProblem name their file or the sandbox by label. - seat session: thread store, legacy store, overlap, busy lock and newRun refusals name the place under the data root. - discord: unknown keys, binding names and turn ids are described when not plain; approvals, journal and repository read errors name the file and code; git drops stderr lines that carry a host path and a spawn failure names its code only. - seat, control-board: registration fields and seen keys are described when not plain; invalid seat name and layout state the constraint. - organization: the registry names the config directory and registry file by label; --project is quoted only when it is a plain name; systemConfigProblem moves to its own module. - scripts/mosaic-config.mjs: the config by where it came from, the data root and its marker by role, keys and arguments only when plain, and error codes in place of raw messages. Exit codes unchanged. - webui sso-config: an unknown console key is named only when plain. Sentinel tests in each package's path-redaction-labels file fail on BASE; pins that quoted a path now assert the label and its absence.
158 lines
10 KiB
JavaScript
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: threw Error without a system code');
|
|
});
|
|
|
|
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');
|
|
});
|