93 lines
5.3 KiB
JavaScript
93 lines
5.3 KiB
JavaScript
// Row 125 (#1619, REQ-LIMIT-3, REQ-LIMIT-4): the launcher asks the bus's limit checks before
|
|
// every launch, after the agent limits. A stub host answers the mayLaunch op; no sandbox, Docker
|
|
// or session process. Any answer that isn't an allow refuses (fail closed, lead decision 150).
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {readFileSync} from 'node:fs';
|
|
import {fileURLToPath} from 'node:url';
|
|
import {createLauncher} from '../src/launcher.mjs';
|
|
import {initializeControl} from '../../processing-control/src/state.mjs';
|
|
import {fixture,tmp,withoutHostCredentials} from './helpers.mjs';
|
|
import {launchLogFile} from '../../seat/src/session.mjs';
|
|
|
|
const HIT={path:['tokens','instances','coder'],limit:100,count:120,unknownTurns:1};
|
|
|
|
// answer is what the mayLaunch op returns, or an Error it throws.
|
|
function setup(t,answer,{initialize=true}={}){
|
|
const root=tmp(t),f=fixture(root,'acme',doc=>{doc.roles.coder.vars.model='claude-sonnet-5-5';return doc;}),ops=[];
|
|
withoutHostCredentials(f.env,{piKey:true});
|
|
if(initialize)initializeControl(f.dataRoot);
|
|
const host={business:'acme',beforeClose:()=>{},op:async x=>{
|
|
ops.push(x);
|
|
if(x.op==='identity')return {business:'acme',role:'pm'};
|
|
if(x.op==='mayLaunch'){if(answer instanceof Error)throw answer;return answer;}
|
|
if(x.op==='authorizeLaunch')throw Object.assign(Error('fixture authorization stop'),{code:'fixture-policy'});
|
|
return {};
|
|
}};
|
|
const launcher=createLauncher({host,system:JSON.parse(readFileSync(f.config,'utf8')),env:f.env,bwrap:'/usr/bin/true',log:()=>{}});
|
|
const last=()=>JSON.parse(readFileSync(launchLogFile(f.dataRoot,'acme'),'utf8').trim().split('\n').at(-1));
|
|
return {launcher,ops,last};
|
|
}
|
|
|
|
test('REQ-LIMIT-4 the launcher asks mayLaunch with the instance, provider, model and account (row 126), and goes on to authorization on an allow',async t=>{
|
|
const {launcher,ops}=setup(t,{allow:true});
|
|
assert.equal((await launcher.fromSession('fixture-cap','coder')).error,'fixture-policy');
|
|
assert.deepEqual(ops.find(x=>x.op==='mayLaunch'),{op:'mayLaunch',business:'acme',instance:'coder',provider:'zai',model:'claude-sonnet-5-5',account:null});
|
|
assert.ok(ops.findIndex(x=>x.op==='mayLaunch')<ops.findIndex(x=>x.op==='authorizeLaunch'));
|
|
});
|
|
|
|
test('REQ-LIMIT-3 REQ-LIMIT-4 a launch whose subject is at its limit is refused limit-reached and logged with the check, limit and count',async t=>{
|
|
const {launcher,ops,last}=setup(t,{allow:false,check:'token-limit',hit:HIT});
|
|
assert.deepEqual(await launcher.fromSession('fixture-cap','coder'),{ok:false,error:'limit-reached'});
|
|
const event=last();
|
|
assert.equal(event.event,'refused');
|
|
assert.equal(event.code,'limit-reached');
|
|
assert.deepEqual(event.limit,{check:'token-limit',...HIT});
|
|
assert.deepEqual(JSON.parse(event.why),HIT);
|
|
assert.ok(ops.find(x=>x.op==='refuse'&&x.code==='limit-reached'));
|
|
assert.equal(ops.find(x=>x.op==='authorizeLaunch'),undefined);
|
|
assert.deepEqual(launcher.registry.sessions,[]);
|
|
});
|
|
|
|
test('REQ-LIMIT-4 fail closed: a refused op, a check failure or a malformed answer refuses limit-check-failed, naming the check and the code',async t=>{
|
|
for(const [answer,why] of [
|
|
[Object.assign(Error('op refused'),{code:'quota-gone'}),'bus: quota-gone'],
|
|
[Error('no code'),'bus: launch-op-refused'],
|
|
[{allow:false,check:'token-limit',code:'not-loaded'},'token-limit: not-loaded'],
|
|
[{allow:false,check:'token-limit',code:'check-threw'},'token-limit: check-threw'],
|
|
[{allow:false,check:null,code:'organization-unreadable'},'bus: organization-unreadable'],
|
|
// Row 333 R3 (#1831) ruling 2: an agent with no model key under a key quota, by its own code.
|
|
[{allow:false,check:null,code:'key-quota-no-model-key'},'bus: key-quota-no-model-key'],
|
|
[{allow:'yes'},'bus: malformed-answer'],
|
|
[{},'bus: malformed-answer'],
|
|
[null,'bus: malformed-answer'],
|
|
[undefined,'bus: malformed-answer'],
|
|
]){
|
|
const {launcher,ops,last}=setup(t,answer);
|
|
assert.deepEqual(await launcher.fromSession('fixture-cap','coder'),{ok:false,error:'limit-check-failed'},why);
|
|
const event=last();
|
|
assert.equal(event.code,'limit-check-failed',why);
|
|
assert.equal(event.why,why);
|
|
assert.equal(event.limit,undefined,why);
|
|
assert.equal(ops.find(x=>x.op==='authorizeLaunch'),undefined,why);
|
|
}
|
|
});
|
|
|
|
test('REQ-LIMIT-4 the launcher reaches token-limit only through the bus: launcher.mjs imports no limit check',()=>{
|
|
const src=readFileSync(fileURLToPath(new URL('../src/launcher.mjs',import.meta.url)),'utf8');
|
|
assert.doesNotMatch(src,/token-limit/);
|
|
assert.doesNotMatch(src,/limit-check\//);
|
|
// The launcher loads model providers and harnesses, but never a limit check.
|
|
assert.deepEqual([...src.matchAll(/\.load\(([^)]*)\)/g)].map(m=>m[1]),["{ 'model-provider': providerNames }",'{ harness: [m.harness] }']);
|
|
assert.doesNotMatch(src,/modules\(\s*["'](?!harness["'])/);
|
|
assert.doesNotMatch(src,/\bimport\s*\(/);
|
|
});
|
|
|
|
// Row 191: only trusted bus startup initializes control; an early seat launch refuses.
|
|
test('a launcher before trusted control initialization refuses processing-uninitialized',async t=>{
|
|
const {launcher,ops}=setup(t,{allow:true},{initialize:false});
|
|
assert.deepEqual(await launcher.fromSession('fixture-cap','coder'),{ok:false,error:'processing-uninitialized'});
|
|
assert.equal(ops.some(x=>x.op==='mayLaunch'||x.op==='authorizeLaunch'),false);
|
|
assert.deepEqual(launcher.registry.sessions,[]);
|
|
});
|