A replaced installed harness answers null for countsAccount until the bus restarts and reads the new module. A runtime test pins the startBroker wiring for claude-code (covered) and pi (refused). The plugin README says countsAccount also decides authority coverage and is read once at start.
142 lines
13 KiB
JavaScript
142 lines
13 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {mkdtempSync,mkdirSync,readFileSync,rmSync} from 'node:fs';
|
|
import {tmpdir} from 'node:os';
|
|
import {join} from 'node:path';
|
|
import {Broker} from '../src/broker.mjs';
|
|
import {Store} from '../src/store.mjs';
|
|
import {autonomyHost,authorityHost} from '../src/autonomy-host.mjs';
|
|
import {organizationWriter} from '../../organization/src/organization.mjs';
|
|
import {instanceSettings} from '../../organization/src/instance-settings.mjs';
|
|
import {hireDecisions} from '../../organization/src/hire-decisions.mjs';
|
|
import {loadBusiness} from '../../organization/src/organization-config.mjs';
|
|
import {businessDoc,rolesCopy,writeJson} from '../../organization/tests/helpers.mjs';
|
|
const inline={tools:['read'],network:'none',authority:{withinRole:[],crossRole:[]},credentials:[],instructions:'Changed role'};
|
|
const cap={limit:100000,since:'2026-01-01T00:00:00.000Z'};
|
|
function world(t,options={}) {
|
|
const root=mkdtempSync(join(tmpdir(),'au-authority-')),rolesDir=rolesCopy(root),dataRoot=join(root,'data');mkdirSync(dataRoot,{mode:0o700});
|
|
const doc=businessDoc(root,'demo');doc.plugins={harness:['pi'],'limit-check':['token-limit']};
|
|
doc.launch.limits.tokens={providers:{zai:cap}};
|
|
// #1873: an account quota alone, covered only through the host's declared harness fact.
|
|
if(options.account){doc.launch.account={name:'shared',credential:'FIXTURE_TOKEN'};doc.launch.limits.tokens={keys:{shared:cap}};}
|
|
doc.definitions={judge:{rev:1,role:{...inline,authority:{withinRole:['decision.resolve.authority'],crossRole:[]}},instructions:'Judge'},scout:{rev:3,role:structuredClone(inline),instructions:'Prior role'}};
|
|
// Definition instructions live beside the role composition.
|
|
for(const d of Object.values(doc.definitions))delete d.role.instructions;
|
|
doc.roles.judge={definition:'org:judge',vars:{harness:'pi',model:'claude-opus-5-5'}};
|
|
doc.roles.scout={definition:'org:scout',vars:{harness:'pi',model:'claude-opus-5-5'}};
|
|
doc.launch.instances.push('scout');doc.autonomy={authority:'near the loop',authorityResolver:'judge',notices:true,reverseWindowHours:1};
|
|
writeJson(join(root,'businesses/demo.json'),doc);
|
|
let reversing=false,broker,now=Date.now;
|
|
const writer=organizationWriter({dir:root,rolesDir,barrier:point=>{if(reversing && point==='prepared' && options.expirePrepared)Date.now=()=>now()+3600001;if(!reversing && point==='prepared' && options.removeResolverPrepared){const b=structuredClone(loaded());b.roles.judge.authority.withinRole=[];broker.reloadBusiness('demo',b);}}});
|
|
const store=new Store(dataRoot),order=[];
|
|
const loaded=()=>{const b=loadBusiness('demo',{dir:root,rolesDir});return {...b,roles:Object.fromEntries(Object.entries(b.roles).map(([n,e])=>[n,{...b.definitions[e.definition],...e}]))};};
|
|
const reload=id=>{if(reversing && options.crossAfterCommit)Date.now=()=>now()+3600001;if(reversing && options.reloadRefused)return false;broker.reloadBusiness(id,loaded());return true;};
|
|
const service=hireDecisions({writer,rolesDir,configDir:root,dataRoot,assertClean:()=>{}});
|
|
const hires=authorityHost(service,{reload,modelKeys:{prepareRevoke:async(instance,guard)=>{guard();assert.equal(instance,'demo/scout');order.push('prepare');},completeRevoke:instance=>{assert.equal(instance,'demo/scout');assert.ok(store.get("SELECT 1 FROM events WHERE json_extract(body,'$.action')='fire.stopped'"));assert.equal(writer.read('demo').document.definitions.scout.instructions,'Changed role');order.push('complete');}}});
|
|
const host=autonomyHost({writer,dataRoot,business:()=>loaded(),hires,reload,...(options.countsAccount?{countsAccount:options.countsAccount}:{})});
|
|
const instances=instanceSettings({dir:root,rolesDir,writer,revoke:()=>{order.push('fire-revoke');}});
|
|
broker=new Broker({store,businesses:{demo:loaded()},hires,autonomy:host,organization:(id,verb,args,actor,context)=>{if(verb==='instance.fireCheck'){instances.checkFire(id,args.name);return {};}if(verb==='instance.fire')return instances.fire(id,args,{actor,context});throw Error('unexpected organization verb');}});
|
|
const caps={};const call=(who,verb,args={})=>broker.request(caps[who],{verb,args});
|
|
for(const role of ['chief','judge','scout']){caps[role]=broker.bindLaunch({business:'demo',role,run:role+'-run',harness:'pi'});call(role,'role.claim');}
|
|
caps.owner=broker.bindConsole({business:'demo',human:'jason',subject:{provider:'test',iss:'test',sub:'owner'}});
|
|
t.after(()=>{Date.now=now;store.close();rmSync(root,{recursive:true,force:true});});
|
|
const raise=(extra={})=>call('chief','hire.raise',{op:'change',level:'org',name:'scout',role:{inline},route:{harness:'pi',model:'claude-opus-5-5'},reason:'Change the task',...extra});
|
|
const reverse=id=>{reversing=true;return call('owner','decision.reverse.console',{id});};
|
|
return {writer,store,order,broker,call,raise,reverse,loaded};
|
|
}
|
|
for(const crossAfterCommit of [false,true])test(`AU authority reverse uses runtime key wrapper and relaunches after commit (deadline crossed: ${crossAfterCommit})`,async t=>{
|
|
const f=world(t,{crossAfterCommit}),d=await f.raise();assert.deepEqual(d.hire.flagged,[]);assert.equal(d.route_to,'judge');
|
|
await f.call('judge','decision.resolve',{id:d.id,choice:'yes'});assert.equal(f.writer.read('demo').document.definitions.scout.rev,4);
|
|
const r=await f.reverse(d.id);assert.equal(r.committed,true);assert.equal(r.relaunch,'requested');assert.deepEqual(f.order,['prepare','complete']);
|
|
const restored=f.writer.read('demo').document.definitions.scout;assert.equal(restored.rev,5);assert.equal(restored.instructions,'Prior role');
|
|
assert.equal(f.store.get("SELECT count(*) n FROM events WHERE json_extract(body,'$.operation')='autonomy.reverse'").n,1);
|
|
assert.equal(f.store.get("SELECT count(*) n FROM events WHERE json_extract(body,'$.operation')='autonomy.reverse.failed'").n,0);
|
|
assert.ok(f.store.get("SELECT 1 FROM events WHERE json_extract(body,'$.action')='lifecycle.launch' AND subject='scout'"));
|
|
});
|
|
test('AU a committed authority reverse reports pending recovery when reload refuses',async t=>{
|
|
const f=world(t,{reloadRefused:true}),d=await f.raise();await f.call('judge','decision.resolve',{id:d.id,choice:'yes'});
|
|
const r=await f.reverse(d.id);assert.equal(r.committed,true);assert.equal(r.restart,true);assert.equal(r.relaunch,'pending');assert.equal(f.writer.read('demo').document.definitions.scout.instructions,'Prior role');
|
|
assert.equal(f.store.get("SELECT count(*) n FROM events WHERE json_extract(body,'$.operation')='autonomy.reverse.failed'").n,0);
|
|
});
|
|
test('AU authority reverse crossing deadline in prepared IO never publishes',async t=>{
|
|
const f=world(t,{expirePrepared:true}),d=await f.raise();await f.call('judge','decision.resolve',{id:d.id,choice:'yes'});const prior=f.writer.read('demo').revision;
|
|
await assert.rejects(f.reverse(d.id),{code:'reverse-window-closed'});assert.equal(f.writer.read('demo').revision,prior);f.writer.recover('demo');assert.equal(f.writer.read('demo').revision,prior);
|
|
});
|
|
test('AU authority resolver rechecks budget removal after a proposal was raised',async t=>{
|
|
const f=world(t),d=await f.raise(),b=structuredClone(f.loaded());b.plugins['limit-check']=[];f.broker.reloadBusiness('demo',b);
|
|
const before=f.writer.read('demo').revision;await assert.rejects(f.call('judge','decision.resolve',{id:d.id,choice:'yes'}),{code:'autonomy-precondition'});
|
|
assert.equal(f.writer.read('demo').revision,before);assert.equal(f.store.get('SELECT count(*) n FROM decision_events WHERE decision=?',d.id).n,0);
|
|
});
|
|
|
|
test('AU authority apply rechecks a removed resolve right at publication',async t=>{
|
|
const f=world(t,{removeResolverPrepared:true}),d=await f.raise(),before=f.writer.read('demo').revision;
|
|
await assert.rejects(f.call('judge','decision.resolve',{id:d.id,choice:'yes'}),{code:'not-resolver'});
|
|
assert.equal(f.writer.read('demo').revision,before);f.writer.recover('demo');assert.equal(f.writer.read('demo').revision,before);
|
|
});
|
|
test('AU authority hire reverse runs normal fire/revoke and records committed delisting',async t=>{
|
|
const f=world(t),d=await f.raise({op:'hire',name:'helper'});await f.call('judge','decision.resolve',{id:d.id,choice:'yes'});
|
|
assert.ok(f.writer.read('demo').document.launch.instances.includes('helper'));
|
|
const r=await f.reverse(d.id);assert.equal(r.committed,true);assert.ok(!f.writer.read('demo').document.launch.instances.includes('helper'));assert.deepEqual(f.order,['fire-revoke']);
|
|
assert.equal(f.store.get("SELECT count(*) n FROM events WHERE json_extract(body,'$.operation')='autonomy.reverse'").n,1);
|
|
});
|
|
|
|
test('AU completion-event failure after durable authority inverse records committed recovery, never reverse.failed',async t=>{
|
|
const f=world(t),d=await f.raise();await f.call('judge','decision.resolve',{id:d.id,choice:'yes'});
|
|
const run=f.store.run.bind(f.store);let injected=false;
|
|
f.store.run=(sql,...args)=>{
|
|
if(!injected && sql.includes('INSERT INTO events') && args.some(x=>typeof x==='string' && x.startsWith('{') && JSON.parse(x).action==='fire.done')){
|
|
injected=true;throw new Error('injected completion event failure');
|
|
}
|
|
return run(sql,...args);
|
|
};
|
|
const r=await f.reverse(d.id);assert.equal(injected,true);assert.equal(r.committed,true);assert.equal(r.completion,'failed');assert.equal(r.restart,true);assert.equal(r.relaunch,'pending');
|
|
assert.equal(f.writer.read('demo').document.definitions.scout.instructions,'Prior role');
|
|
assert.equal(f.writer.read('demo').document.definitions.scout.rev,5);
|
|
assert.equal(f.store.get("SELECT count(*) n FROM events WHERE json_extract(body,'$.operation')='autonomy.reverse'").n,1);
|
|
assert.equal(f.store.get("SELECT count(*) n FROM events WHERE json_extract(body,'$.operation')='autonomy.reverse.failed'").n,0);
|
|
});
|
|
|
|
test('AU inverse receipt-write failure returns committed with trailPending and never writes reverse.failed',async t=>{
|
|
const f=world(t),d=await f.raise();await f.call('judge','decision.resolve',{id:d.id,choice:'yes'});
|
|
const run=f.store.run.bind(f.store);let injected=false;
|
|
f.store.run=(sql,...args)=>{
|
|
if(sql.includes('INSERT INTO events') && args.some(x=>typeof x==='string' && x.startsWith('{') && JSON.parse(x).operation==='autonomy.reverse')){
|
|
injected=true;throw new Error('injected receipt write failure');
|
|
}
|
|
return run(sql,...args);
|
|
};
|
|
const r=await f.reverse(d.id);assert.equal(injected,true);assert.equal(r.committed,true);assert.equal(r.trailPending,true);assert.equal(r.relaunch,'requested');
|
|
assert.equal(f.writer.read('demo').document.definitions.scout.instructions,'Prior role');
|
|
assert.equal(f.store.get("SELECT count(*) n FROM events WHERE json_extract(body,'$.operation')='autonomy.reverse.failed'").n,0);
|
|
});
|
|
test('AU an account-quota authority raise routes to the resolver only when the host says every harness spends the account (#1873)',async t=>{
|
|
const covered=world(t,{account:true,countsAccount:name=>name==='pi'}),d=await covered.raise();assert.equal(d.route_to,'judge');
|
|
for(const countsAccount of [undefined,()=>false,()=>null]){
|
|
const f=world(t,{account:true,countsAccount});
|
|
await assert.rejects(f.raise(),{code:'autonomy-precondition'});
|
|
assert.equal(f.store.get("SELECT count(*) n FROM decisions").n,0);
|
|
}
|
|
});
|
|
// #1873 review finding 2: the runtime itself carries each harness module's countsAccount into the
|
|
// broker's authority budget check. Only harness modules decide; the business names none here but
|
|
// through plugins.harness.
|
|
for(const [harness,code] of [['claude-code',null],['pi','autonomy-precondition']])test(`AU runtime: an account-quota authority setting with only ${harness} enabled ${code?'refuses':'saves'} (#1873)`,async t=>{
|
|
const {startBroker}=await import('../src/runtime.mjs');
|
|
const {systemFor}=await import('../../organization/tests/helpers.mjs');
|
|
const root=mkdtempSync(join(tmpdir(),'au-runtime-')),rolesDir=rolesCopy(root);t.after(()=>rmSync(root,{recursive:true,force:true}));
|
|
const doc=businessDoc(root,'demo');doc.plugins={harness:[harness],'limit-check':['token-limit']};
|
|
for(const r of Object.values(doc.roles))if(r.vars?.harness)r.vars.harness=harness;
|
|
doc.launch.account={name:'shared',credential:'FIXTURE_TOKEN'};doc.launch.limits.tokens={keys:{shared:cap}};
|
|
doc.definitions={judge:{rev:1,role:{...inline,authority:{withinRole:['decision.resolve.authority'],crossRole:[]}},instructions:'Judge'}};
|
|
delete doc.definitions.judge.role.instructions;
|
|
doc.roles.judge={definition:'org:judge',vars:{harness,model:'claude-opus-5-5'}};
|
|
writeJson(join(root,'businesses/demo.json'),doc);mkdirSync(join(root,'data'),{mode:0o700});
|
|
const loaded=()=>{const b=loadBusiness('demo',{dir:root,rolesDir});return {...b,roles:Object.fromEntries(Object.entries(b.roles).map(([n,e])=>[n,{...b.definitions[e.definition],...e}]))};};
|
|
const bus=await startBroker({dataRoot:join(root,'data'),businesses:{demo:loaded()},organization:{dir:root,rolesDir,system:systemFor(root)}});t.after(()=>bus.close());
|
|
const owner=bus.broker.bindConsole({business:'demo',human:'jason',subject:{provider:'test',iss:'test',sub:'owner'}});
|
|
const {revision}=await bus.broker.request(owner,{verb:'autonomy.get.console'});
|
|
const set=bus.broker.request(owner,{verb:'autonomy.set.console',args:{revision,autonomy:{authority:'near the loop',authorityResolver:'judge',notices:true}}});
|
|
if(code)await assert.rejects(set,{code});
|
|
else {await set;assert.equal(JSON.parse(readFileSync(join(root,'businesses/demo.json'),'utf8')).autonomy.authority,'near the loop');}
|
|
});
|