Source-candidate: poe d9a564e6d20f5f0fd75ce45b12cbbf8bb4c9d568\nSource-candidate: magica38ba5762b06716db1dee00d0afec162656488f5c\nValidated321whole-file phase plus whole bus1349/model-proxy100/webui490; final gate tooling checks pending, not landed.
66 lines
6.4 KiB
JavaScript
66 lines
6.4 KiB
JavaScript
// #652 STATIC preparation: no execution yet. Real runtime, HTTP and original sweeps.
|
|
// Interceptions affect ONLY recorded scratch-owned /proc identities; no synthetic signals.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import { syncBuiltinESMExports } from 'node:module';
|
|
import { spawn } from 'node:child_process';
|
|
import { once } from 'node:events';
|
|
import { join } from 'node:path';
|
|
import { reviewFixture } from './model-review-fixtures.mjs';
|
|
import { Credentials } from '../src/credentials.mjs';
|
|
import { ManagementClient } from '../../model-proxy/src/client.mjs';
|
|
import { startTimeOf } from '../../seat/src/proc.mjs';
|
|
const agent='demo/chief';
|
|
function procFault(t,pid,kind){
|
|
const original=fs.readFileSync;let active=true,reads=0;
|
|
fs.readFileSync=function(file,...args){
|
|
if(active&&file===`/proc/${pid}/stat`){reads++;if(kind==='read'){const error=Error('owned proc read refused');error.code='EACCES';throw error;}
|
|
const raw=original.call(this,file,'utf8'),end=raw.lastIndexOf(')')+2,fields=raw.slice(end).trim().split(/\s+/);fields[19]=String(BigInt(fields[19])+1n);const changed=raw.slice(0,end)+fields.join(' ')+'\n';return args[0]==='utf8'?changed:Buffer.from(changed);
|
|
}
|
|
return original.call(this,file,...args);
|
|
};syncBuiltinESMExports();
|
|
const restore=()=>{active=false;fs.readFileSync=original;syncBuiltinESMExports();};t.after(restore);
|
|
return {restore,reads:()=>reads};
|
|
}
|
|
async function ownPair(t){
|
|
const children=[];t.after(async()=>{const failures=[];for(const {child,closed} of children){try{if(child.exitCode===null&&child.signalCode===null)child.kill('SIGTERM');await closed;}catch{failures.push(Error('owned extra child cleanup'));}}if(failures.length)throw new AggregateError(failures,'owned pair cleanup failed');});
|
|
for(let i=0;i<2;i++){const child=spawn('/usr/bin/sleep',['120'],{stdio:'ignore'}),closed=new Promise(resolve=>child.once('close',resolve));children.push({child,closed});await once(child,'spawn');}
|
|
return {pid:children[0].child.pid,startTime:startTimeOf(children[0].child.pid),runnerPid:children[1].child.pid,runnerStartTime:startTimeOf(children[1].child.pid),exit:async()=>{for(const {child,closed} of children){if(child.exitCode===null&&child.signalCode===null)child.kill('SIGTERM');await closed;}}};
|
|
}
|
|
const absent=pid=>assert.throws(()=>fs.lstatSync(`/proc/${pid}`),e=>e.code==='ENOENT','own process actually reaped');
|
|
async function setup(t,{failure=null}={}){
|
|
const f=await reviewFixture(t,{overdue:true}),own=new Credentials({storeRoot:join(f.configDir,'secrets','agents'),dataRoot:f.dataRoot,modelAgents:[agent]});let record;try{record=own.modelRecord(agent);}finally{own.close();}
|
|
f.entry.modelKeyId=record.keyId;let entry={...f.entry},extra,fault,binding;
|
|
if(failure==='runner ticks')entry.runnerStartTime='0';
|
|
if(failure==='later entry')extra=await ownPair(t);
|
|
f.writeSessions([entry,...(extra?[{...entry,...extra,exit:undefined,run:'proof-second',modelKeyId:record.retiring[0].keyId}]:[])]);
|
|
if(failure==='runner read')fault=procFault(t,entry.runnerPid,'read');
|
|
if(extra)fault=procFault(t,extra.runnerPid,'read');
|
|
const bus=await f.boot();const proxy=await bus.modelPreflight({business:'demo',role:'chief'});
|
|
if(failure==='unpublished bound'||failure==='bound mismatch'){
|
|
binding={business:'demo',role:'chief',run:failure==='unpublished bound'?'proof-unpublished':entry.run,harness:'pi',pid:entry.runnerPid,startTime:entry.runnerStartTime,modelKeyId:record.keyId};bus.bindLaunch(binding);
|
|
}
|
|
const first=await f.observeNextSweep(),manager=f.sweptManager;
|
|
assert.equal(first.outcome,failure?'proxy-stop-failed':'fulfilled','first complete checked view outcome');
|
|
if(!failure)assert.equal(bus.credentials.modelRecord(agent).retiring.length,0,'associated live view actually completed');
|
|
const client=new ManagementClient({baseUrl:proxy.baseUrl,key:'fx647-manage'});
|
|
const lose=async()=>{
|
|
const old=bus.credentials.modelRecord(agent),before=await client.getKeys();assert.equal(before.includes(old.key),true);await client.putKeys(before.filter(k=>k!==old.key));assert.equal((await client.getKeys()).includes(old.key),false);assert.equal(await client.probeKey(old.key),401);
|
|
await manager.ensure(agent);const fresh=bus.credentials.modelRecord(agent);assert.notEqual(fresh.keyId,old.keyId);assert.equal(fresh.retiring.some(r=>r.keyId===old.keyId),false);assert.equal(f.state.keys.includes(old.key),false);
|
|
// Keep failure armed until association is gone; no accidental successful view.
|
|
if(binding)bus.endLaunch({business:'demo',run:binding.run,reason:'host-lost',exitCode:null});
|
|
await manager.rotate(agent);const current=bus.credentials.modelRecord(agent);bus.credentials.writeModel(agent,{...current,retiring:current.retiring.map(r=>({...r,retireAt:new Date(Date.now()-60000).toISOString()}))});return {key:fresh.key,keyId:fresh.keyId};
|
|
};
|
|
const retained=later=>{assert.equal(f.state.keys.includes(later.key),true,'later remote generation retained');assert.equal(bus.credentials.modelRecord(agent).retiring.some(r=>r.keyId===later.keyId),true,'later local retiring record retained');};
|
|
const exit=async()=>{await f.exit();await extra?.exit();absent(entry.pid);absent(entry.runnerPid);if(extra){absent(extra.pid);absent(extra.runnerPid);}};
|
|
return {...f,bus,entry,lose,retained,exit,fault};
|
|
}
|
|
for(const failure of ['runner ticks','runner read','later entry','unpublished bound','bound mismatch'])test(`652 provenance not committed after failed whole view: ${failure}`,async t=>{
|
|
const f=await setup(t,{failure}),later=await f.lose();await f.exit();f.fault?.restore();const seen=await f.observeNextSweep();assert.equal(seen.outcome,'proxy-stop-failed','failed first view cannot grant association provenance after key loss');f.retained(later);
|
|
});
|
|
for(const kind of ['read','ticks'])test(`652 prior validated identity still strictly checks ${kind} after association loss`,async t=>{
|
|
const f=await setup(t),later=await f.lose();f.writeSessions([]);const fault=procFault(t,f.entry.runnerPid,kind);const seen=await f.observeNextSweep();assert.equal(seen.outcome,'proxy-stop-failed');f.retained(later);
|
|
assert.ok(fault.reads()>0,'prior validated identity is rechecked strictly after association loss');fault.restore();assert.equal(startTimeOf(f.entry.runnerPid),f.entry.runnerStartTime,'live counterpart remains exact and unsignalled');await f.exit();const done=await f.observeNextSweep();assert.equal(done.outcome,'fulfilled');assert.equal(f.state.keys.includes(later.key),false,'strict exits after restored reads permit later retirement');
|
|
});
|