50 lines
5.4 KiB
JavaScript
50 lines
5.4 KiB
JavaScript
import { approveTemplateHire } from './hire-fixtures.mjs';
|
|
// #651 actual runtime + organization.hire publication with orphan private records.
|
|
// In-process trusted Broker, real SQLite/organization writer/HTTP; not process IPC.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { startBroker } from '../src/runtime.mjs';
|
|
import { busOrganization } from '../src/organization-context.mjs';
|
|
import { loadBusiness } from '../../organization/src/organization-config.mjs';
|
|
import { resolveInstance } from '../../organization/src/resolve.mjs';
|
|
import { businessDoc, rolesCopy, systemFor, writeJson } from '../../organization/tests/helpers.mjs';
|
|
import { lifecycleFixture } from './model-lifecycle-fixtures.mjs';
|
|
async function hireFixture(t,{revoking=false}={}){
|
|
const f=await lifecycleFixture(t),old=f.create(['demo/orphan']);await old.manager.reconcile();const issued=await old.manager.ensure('demo/orphan');
|
|
if(revoking){const r=old.credentials.modelRecord('demo/orphan');old.credentials.writeModel('demo/orphan',{...r,state:'revoking',revokingAt:'2026-10-25T00:00:00.000Z'});}old.credentials.close();
|
|
const rolesDir=rolesCopy(f.root),system=systemFor(f.root),doc=businessDoc(f.root,'demo');
|
|
// Scratch aide: shipped chief hireable:[] and review.verdict is outside HIRE_ALLOWED.
|
|
fs.writeFileSync(join(rolesDir,'aide.md'),'# Aide\n\n## Authority\n');writeJson(join(rolesDir,'aide.json'),{roleVersion:2,name:'aide',title:'Aide',contract:'aide.md',tools:['read'],network:'api-only',authority:{withinRole:['message.send'],crossRole:[]},credentials:[]});
|
|
const chiefFile=join(rolesDir,'chief-of-staff.json');doc.plugins={...doc.plugins,harness:['pi']};
|
|
const file=writeJson(join(f.configDir,'businesses','demo.json'),doc);fs.writeFileSync(join(f.configDir,'secrets','proxy-management.key'),'fx651-manage',{mode:0o600});
|
|
const business=loadBusiness('demo',{dir:f.configDir,rolesDir}),resolved={};for(const instance of Object.keys(business.roles))resolved[instance]=resolveInstance({system,business,instance});
|
|
const dataRoot=join(f.root,'data');fs.mkdirSync(dataRoot,{mode:0o700});
|
|
// Capture only value-free runtime proxy receipts; never print a leaked value.
|
|
const log=[],original=process.stderr.write;process.stderr.write=function(chunk,...args){if(String(chunk).startsWith('model-proxy: ')){log.push(String(chunk));const callback=args.find(a=>typeof a==='function');callback?.();return true;}return original.call(this,chunk,...args);};f.own(()=>{process.stderr.write=original;});
|
|
const bus=await startBroker({dataRoot,businesses:{demo:busOrganization(business,resolved)},organization:{dir:f.configDir,rolesDir,system},proxy:{port:Number(new URL(f.baseUrl).port),binary:'/fixture-unused'}});
|
|
f.own(()=>bus.close());
|
|
assert.equal(f.state.keys.includes(issued.key),false,'runtime boot reconciled omitted orphan');assert.equal(bus.credentials.modelRecord('demo/orphan')===null,true,'orphan remains unregistered before hire');
|
|
const cap=bus.broker.bindLaunch({business:'demo',role:'chief',run:'orphan-hire-chief',harness:'pi'});bus.broker.request(cap,{verb:'role.claim'});
|
|
const listed=()=>JSON.parse(fs.readFileSync(file,'utf8')).launch.instances.includes('orphan'),observed=[];f.state.observePut=()=>observed.push(listed());f.state.calls=[];
|
|
const hire=()=>approveTemplateHire(bus.broker,rolesDir,{name:'orphan',definition:'aide'});
|
|
return {...f,bus,issued,file,log,listed,observed,hire};
|
|
}
|
|
test('L3b runtime hire publishes orphan only after fresh remote confirmation',async t=>{
|
|
const f=await hireFixture(t);const result=await f.hire();assert.equal(typeof result.transaction,'string');const record=f.bus.credentials.modelRecord('demo/orphan');
|
|
assert.equal(record.key===f.issued.key,false,'runtime hire must replace removed orphan credential');assert.equal(f.state.keys.includes(record.key),true,'published member has confirmed remote key');assert.equal(f.listed(),true);
|
|
assert.ok(f.observed.length>0,'actual fresh issue reached proxy PUT');assert.equal(f.observed.every(published=>published===false),true,'membership absent during remote writes');assert.deepEqual(f.state.calls.slice(0,3),['GET','PUT','GET']);
|
|
const proxy=await f.bus.modelPreflight({business:'demo',role:'orphan'});assert.equal(proxy.key===record.key,true);f.valueFree(f.log);f.valueFree();
|
|
});
|
|
test('L3b runtime orphan fresh PUT failure preserves membership exactly',async t=>{
|
|
const f=await hireFixture(t),before=fs.readFileSync(f.file);f.state.failPut=true;
|
|
await assert.rejects(f.hire(),e=>e.code==='proxy-key-issue-failed','runtime orphan issue failure refuses before membership publication');
|
|
assert.equal(fs.readFileSync(f.file).equals(before),true);assert.equal(f.listed(),false);assert.equal(f.state.keys.includes(f.issued.key),false);assert.ok(f.observed.length>0,'rejected fresh PUT observed');assert.equal(f.observed.every(published=>published===false),true,'membership absent through failed issue and existing cleanup');f.valueFree(f.log);f.valueFree();
|
|
});
|
|
test('L3b runtime unregistered revoking orphan cannot publish membership',async t=>{
|
|
const f=await hireFixture(t,{revoking:true}),before=fs.readFileSync(f.file);
|
|
await assert.rejects(f.hire(),e=>e.code==='proxy-key-revoking','runtime post-register revoking hold refuses hire');
|
|
assert.equal(fs.readFileSync(f.file).equals(before),true);assert.equal(f.listed(),false);assert.equal(f.state.keys.includes(f.issued.key),false);f.valueFree(f.log);f.valueFree();
|
|
});
|