fix(proof): match network names and preflight Docker watchers
This commit is contained in:
@@ -46,8 +46,9 @@ unresolved and points to the log rather than guessing. It stops earlier than the
|
||||
two-failure limit. TERM/INT/HUP stop owned supervisors; cleanup waits for them.
|
||||
|
||||
Container and network names come from Compose's random recorded project. The
|
||||
project and service configuration have no fixed global names. Exact-label Docker
|
||||
event streams record create/destroy IDs and names, including --rm containers.
|
||||
project and service configuration have no fixed global names. Container event
|
||||
streams filter the exact project label; network streams match the exact project_
|
||||
name prefix. Both record create/destroy IDs and names, including --rm containers.
|
||||
An exit trap inventories names/labels, removes only exact-project objects with
|
||||
the approved cleanup helper, verifies emptiness and stops its own event streams.
|
||||
The driver's Docker inspection never reads Config.Env or credential contents, and never builds, pulls,
|
||||
@@ -124,3 +125,117 @@ At 9aa6d52d9:
|
||||
|
||||
This was a static source trace only. No auth file was opened, no credential
|
||||
value was read, and no Docker command, image build or proof was executed.
|
||||
|
||||
|
||||
## Proof4 watcher change (sage-2029, mos-1174)
|
||||
|
||||
Proof3's eight gates passed and cleaned up, but Docker29 network events do
|
||||
not carry Compose project labels, so the driver's combined label filter
|
||||
missed their creation. Only this proof packet changes; none of the37
|
||||
c45ddeb6b candidate paths changes.
|
||||
|
||||
Container events retain the label filter. Network events use a type filter
|
||||
and a local exact project_ name-prefix match, accepting default and other
|
||||
owned network names. The existing recorded-name project guard stays.
|
||||
The same exported watcher/parser powers unit tests, the proof and a daemon
|
||||
preflight before wave1. Importing the module does not execute a proof.
|
||||
Direct execution still installs signal handlers before preparation.
|
||||
|
||||
The preflight creates one named and labelled mg197 probe network and one
|
||||
create-only container from the pinned imageId, --pull never, network none,
|
||||
no mounts or start. Exact-project label-checked cleanup removes both,
|
||||
including on failure. All four create/destroy observations must arrive
|
||||
within5000ms. Docker commands share that deadline; cleanup has a separate
|
||||
3000ms cap. Watcher shutdown is bounded. Failure prevents all gate waves.
|
||||
Probe metadata and outcomes go into docker-summary.json and summary.json.
|
||||
No Config.Env, credential file contents or raw foreign events are saved.
|
||||
|
||||
Tests cover network events with name/type only, all project_ network names,
|
||||
foreign names/labels, recorded-name ownership, fragmented streams, early
|
||||
exit, missing create/destroy observations, deadline and cleanup failures.
|
||||
Sage-2034 accepted this scope. Grant sage-2035 supplied the runtime evidence below.
|
||||
|
||||
## Granted verification, sage-2035
|
||||
|
||||
On 2026-10-21, the whole events.test.mjs passed 8/0, with zero skipped or
|
||||
cancelled. node --check passed on run-proof.mjs and events.test.mjs. One
|
||||
exported probeDockerWatchers call used the supplied proof3 image ID and
|
||||
cleanupProject from the unchanged r8 launcher. No container was started;
|
||||
no pull, build, lane or wave ran. Both filtered post-probe Docker listings
|
||||
were empty, and the owned /proc cwd/project scan was empty. The slot was
|
||||
returned at 2026-10-21T15:16:52Z.
|
||||
|
||||
Driver SHA-256:
|
||||
`998f41948355ea86169c1711324a3c9e6cf4bdbe5157dcae9e78e414c1c16342`.
|
||||
Sage updates their proof-phase.sh digest pin to this value.
|
||||
|
||||
The complete standalone probe output was:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"project": "mg197-a-724f1800af2c",
|
||||
"imageId": "sha256:1b83521d092a3f005ea2192e9e6268099e6e0cdab85d41a5d107b4fb824131a6",
|
||||
"timeoutMs": 5000,
|
||||
"names": {
|
||||
"container": "mg197-a-724f1800af2c-watcher-probe",
|
||||
"network": "mg197-a-724f1800af2c_default"
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"project": "mg197-a-724f1800af2c",
|
||||
"kind": "network",
|
||||
"id": "930b1c80596fc93b4dc104ab1e278e5b1e1b2bc6373a5cb9cba7bba46f230ef8",
|
||||
"name": "mg197-a-724f1800af2c_default",
|
||||
"action": "create",
|
||||
"observedMs": 38.03536499999999
|
||||
},
|
||||
{
|
||||
"project": "mg197-a-724f1800af2c",
|
||||
"kind": "container",
|
||||
"id": "8fcdf9d06c437e162698095b2234402ce75fd9744669ced4017f823b5a99fc83",
|
||||
"name": "mg197-a-724f1800af2c-watcher-probe",
|
||||
"action": "create",
|
||||
"observedMs": 123.93732700000001
|
||||
},
|
||||
{
|
||||
"project": "mg197-a-724f1800af2c",
|
||||
"kind": "container",
|
||||
"id": "8fcdf9d06c437e162698095b2234402ce75fd9744669ced4017f823b5a99fc83",
|
||||
"name": "mg197-a-724f1800af2c-watcher-probe",
|
||||
"action": "destroy",
|
||||
"observedMs": 297.033782
|
||||
},
|
||||
{
|
||||
"project": "mg197-a-724f1800af2c",
|
||||
"kind": "network",
|
||||
"id": "930b1c80596fc93b4dc104ab1e278e5b1e1b2bc6373a5cb9cba7bba46f230ef8",
|
||||
"name": "mg197-a-724f1800af2c_default",
|
||||
"action": "destroy",
|
||||
"observedMs": 297.07703999999995
|
||||
}
|
||||
],
|
||||
"errors": [],
|
||||
"cleanup": {
|
||||
"clean": true,
|
||||
"removed": [
|
||||
{
|
||||
"kind": "container",
|
||||
"id": "8fcdf9d06c43"
|
||||
},
|
||||
{
|
||||
"kind": "network",
|
||||
"id": "930b1c80596f"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ok": true,
|
||||
"missing": [],
|
||||
"wallMs": 308.567205
|
||||
},
|
||||
"inventories": {
|
||||
"containers": "",
|
||||
"networks": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { PassThrough } from 'node:stream';
|
||||
import { dockerEvent, watchDockerProject, probeDockerWatchers } from './run-proof.mjs';
|
||||
const project='mg197-a-0123456789ab', imageId='sha256:'+'a'.repeat(64);
|
||||
const ids={container:'b'.repeat(64),network:'c'.repeat(64)};
|
||||
const event=(kind,action,name=kind==='network'?project+'_default':project+'-probe')=>({Type:kind,Action:action,Actor:{ID:ids[kind],Attributes:{name,type:'bridge',...(kind==='container'?{'com.docker.compose.project':project}:{})}}});
|
||||
function streams(){
|
||||
const children=new Map(),calls=[];
|
||||
const spawnImpl=(binary,args)=>{const kind=args.includes('type=network')?'network':'container';calls.push(args);const child=new EventEmitter();child.stdout=new PassThrough();child.stderr=new PassThrough();child.kill=()=>{queueMicrotask(()=>child.emit('close',0));return true;};children.set(kind,child);return child;};
|
||||
const emit=(kind,action,name)=>children.get(kind).stdout.write(JSON.stringify(event(kind,action,name))+'\n');
|
||||
return {children,calls,spawnImpl,emit};
|
||||
}
|
||||
test('network create/destroy with only name and type match every exact project_ network',()=>{
|
||||
for(const name of [project+'_default',project+'_other_network'])for(const action of ['create','destroy']){
|
||||
const value=dockerEvent(project,'network',event('network',action,name));
|
||||
assert.deepEqual(value,{project,kind:'network',id:ids.network,name,action});
|
||||
}
|
||||
for(const name of ['foreign_default',project+'x_default',project+'-other',project])assert.equal(dockerEvent(project,'network',event('network','create',name)),null);
|
||||
assert.equal(dockerEvent(project,'network',event('network','connect')),null);
|
||||
});
|
||||
test('container labels and recorded name ownership remain strict',()=>{
|
||||
assert.equal(dockerEvent(project,'container',event('container','create')).id,ids.container);
|
||||
const foreign=event('container','create');foreign.Actor.Attributes['com.docker.compose.project']='other';assert.equal(dockerEvent(project,'container',foreign),null);
|
||||
assert.throws(()=>dockerEvent(project,'container',event('container','create','foreign-container')),/name outside/);
|
||||
const invalid=event('network','create');invalid.Actor.ID='not-an-id';assert.throws(()=>dockerEvent(project,'network',invalid),/ownership metadata/);
|
||||
});
|
||||
test('two real watcher shapes decode fragmented lines and network stream has no label filter',async()=>{
|
||||
const s=streams(),seen=[],errors=[];const watcher=watchDockerProject(project,{docker:'/fixture/docker',spawnImpl:s.spawnImpl,onEvent:e=>seen.push(e),onError:e=>errors.push(e)});
|
||||
const text=JSON.stringify(event('network','create'))+'\n';s.children.get('network').stdout.write(text.slice(0,17));s.children.get('network').stdout.write(text.slice(17));s.emit('network','destroy');s.emit('container','create');s.emit('container','destroy');
|
||||
assert.equal(seen.length,4);assert.ok(s.calls.find(a=>a.includes('type=container')).includes('label=com.docker.compose.project='+project));assert.ok(!s.calls.find(a=>a.includes('type=network')).some(a=>a.startsWith('label=')));
|
||||
await watcher.stop();assert.deepEqual(errors,[]);
|
||||
});
|
||||
test('an event watcher ending early reports an error',async()=>{
|
||||
const s=streams(),errors=[];const watcher=watchDockerProject(project,{docker:'/fixture/docker',spawnImpl:s.spawnImpl,onEvent:()=>{},onError:e=>errors.push(e)});s.children.get('network').emit('close',1);assert.match(errors[0],/ended early/);await watcher.stop();
|
||||
});
|
||||
function probeFixture({missing=null,commandFails=false,cleanupFails=false}={}){
|
||||
const s=streams(),calls=[];let cleaned=false;
|
||||
const invoke=async args=>{calls.push(args);const kind=args[0];if(commandFails)throw Error('fixture daemon error');if(missing!==kind+'.create')s.emit(kind,'create');return ids[kind]+'\n';};
|
||||
const cleanupProject=run=>{assert.equal(run.project,project);cleaned=true;if(cleanupFails)throw Error('fixture cleanup refusal');for(const kind of ['container','network'])if(missing!==kind+'.destroy')s.emit(kind,'destroy');return {clean:true,removed:Object.entries(ids).map(([kind,id])=>({kind,id}))};};
|
||||
return {options:{docker:'/fixture/docker',imageId,project,spawnImpl:s.spawnImpl,invoke,cleanupProject,timeoutMs:80},calls,get cleaned(){return cleaned;}};
|
||||
}
|
||||
test('preflight uses the same watchers, creates only pinned no-pull objects, removes both and sees all four events',async()=>{
|
||||
const f=probeFixture(),result=await probeDockerWatchers(f.options);assert.equal(result.ok,true);assert.equal(f.cleaned,true);assert.deepEqual(result.missing,[]);assert.equal(result.events.length,4);
|
||||
const create=f.calls.find(a=>a[0]==='container');assert.equal(create[1],'create');assert.equal(create[create.indexOf('--pull')+1],'never');assert.ok(create.includes(imageId));assert.ok(create.includes('--network')&&create.includes('none'));assert.ok(f.calls.every(a=>!a.includes('start')&&!a.includes('build')));
|
||||
});
|
||||
test('missing either create or destroy fails within the bounded wait and still cleans up',async()=>{
|
||||
for(const missing of ['container.create','container.destroy','network.create','network.destroy']){
|
||||
const f=probeFixture({missing}),start=performance.now(),result=await probeDockerWatchers(f.options);assert.equal(result.ok,false);assert.ok(result.missing.includes(missing));assert.ok(performance.now()-start<1500);assert.equal(f.cleaned,true);
|
||||
}
|
||||
});
|
||||
test('probe command and cleanup failures refuse and retain their own result',async()=>{
|
||||
for(const options of [{commandFails:true},{cleanupFails:true}]){const f=probeFixture(options),result=await probeDockerWatchers(f.options);assert.equal(result.ok,false);assert.ok(result.errors.length);assert.equal(f.cleaned,true);}
|
||||
});
|
||||
|
||||
test('late observations cannot turn a missed deadline into success',async()=>{
|
||||
const f=probeFixture(),invoke=f.options.invoke;f.options.invoke=async args=>{await new Promise(r=>setTimeout(r,100));return invoke(args);};
|
||||
const result=await probeDockerWatchers(f.options);assert.equal(result.ok,false);assert.ok(result.missing.includes('network.create'));assert.equal(f.cleaned,true);
|
||||
});
|
||||
@@ -2,7 +2,68 @@
|
||||
import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, realpathSync, openSync, closeSync, symlinkSync, readdirSync, statSync, lstatSync, readSync, rmSync } from 'node:fs';
|
||||
import { resolve, join, dirname, relative, isAbsolute } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { execFileSync, spawn } from 'node:child_process';
|
||||
import { execFileSync, execFile, spawn } from 'node:child_process';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
const projectPattern=/^mg197-[ab]-[a-f0-9]{12}$/;
|
||||
export function dockerEvent(project,kind,event){
|
||||
if(!projectPattern.test(project)||!['container','network'].includes(kind))throw Error('invalid Docker ownership metadata');
|
||||
if(event.Type!==kind||!['create','destroy'].includes(event.Action))return null;
|
||||
const attrs=event.Actor?.Attributes??{},name=typeof attrs.name==='string'?attrs.name.replace(/^\//,''):null;
|
||||
if(kind==='network'){
|
||||
if(!name?.startsWith(project+'_'))return null;
|
||||
if(attrs['com.docker.compose.project']&&attrs['com.docker.compose.project']!==project)throw Error('foreign project event');
|
||||
}else if(attrs['com.docker.compose.project']!==project)return null;
|
||||
if(!name?.startsWith(project))throw Error('Docker name outside its recorded project');
|
||||
const id=event.Actor?.ID;if(typeof id!=='string'||!/^[a-f0-9]{12,64}$/.test(id))throw Error('invalid Docker ownership metadata');
|
||||
return {project,kind,id,name,action:event.Action};
|
||||
}
|
||||
export function watchDockerProject(project,{docker,onEvent,onError,spawnImpl=spawn}){
|
||||
if(!projectPattern.test(project))throw Error('invalid Docker ownership project');
|
||||
const streams=[];
|
||||
for(const kind of ['container','network']){
|
||||
// Network events on Docker29 have name/type but no Compose labels. Filter
|
||||
// their exact project_ name prefix locally, never by a missing label.
|
||||
const args=['events','--since',String(Math.floor(Date.now()/1000)-5),'--filter','type='+kind];
|
||||
if(kind==='container')args.push('--filter','label=com.docker.compose.project='+project);
|
||||
args.push('--filter','event=create','--filter','event=destroy','--format','{{json .}}');
|
||||
const child=spawnImpl(docker,args,{stdio:['ignore','pipe','pipe']}),stream={child,stopping:false,buffer:'',closed:false};streams.push(stream);
|
||||
child.stdout.on('data',bytes=>{stream.buffer+=bytes;let end;while((end=stream.buffer.indexOf('\n'))>=0){const line=stream.buffer.slice(0,end);stream.buffer=stream.buffer.slice(end+1);if(!line)continue;try{const event=dockerEvent(project,kind,JSON.parse(line));if(event)onEvent(event);}catch(error){onError(error.message);}}});
|
||||
child.stderr.resume();stream.done=new Promise(resolve=>{child.once('error',error=>{stream.closed=true;onError(error.message);resolve();});child.once('close',code=>{stream.closed=true;if(!stream.stopping)onError('Docker '+kind+' event stream ended early: '+code);resolve();});});
|
||||
}
|
||||
return {async stop(){
|
||||
for(const stream of streams){stream.stopping=true;if(!stream.closed)stream.child.kill('SIGTERM');}
|
||||
let timer;await Promise.race([Promise.all(streams.map(s=>s.done)),new Promise(resolve=>{timer=setTimeout(resolve,1000);})]);clearTimeout(timer);
|
||||
for(const stream of streams)if(!stream.closed){stream.child.kill('SIGKILL');onError('Docker event watcher did not stop promptly');}
|
||||
}};
|
||||
}
|
||||
export async function probeDockerWatchers({docker,imageId,cleanupProject,project='mg197-a-'+randomBytes(6).toString('hex'),timeoutMs=5000,spawnImpl=spawn,invoke}){
|
||||
if(!projectPattern.test(project)||!/^sha256:[a-f0-9]{64}$/.test(imageId??''))throw Error('invalid watcher probe ownership or image');
|
||||
const start=performance.now(),deadline=start+timeoutMs,events=[],errors=[],names={container:project+'-watcher-probe',network:project+'_default'};
|
||||
const result={project,imageId,timeoutMs,names,events,errors,cleanup:null,ok:false};
|
||||
const watcher=watchDockerProject(project,{docker,spawnImpl,onEvent:e=>events.push({...e,observedMs:performance.now()-start}),onError:e=>errors.push(e)});
|
||||
const remaining=()=>{const ms=Math.ceil(deadline-performance.now());if(ms<=0)throw Error('Docker watcher probe deadline');if(errors.length)throw Error('Docker watcher probe stream failed');return ms;};
|
||||
const call=invoke??((args,timeout)=>new Promise((resolve,reject)=>execFile(docker,args,{encoding:'utf8',timeout,killSignal:'SIGKILL',maxBuffer:65536},(error,stdout)=>error?reject(Error('Docker probe '+args.slice(0,2).join(' ')+' failed: '+error.code)):resolve(stdout))));
|
||||
try{
|
||||
for(const args of [
|
||||
['network','create','--label','com.docker.compose.project='+project,names.network],
|
||||
['container','create','--pull','never','--name',names.container,'--label','com.docker.compose.project='+project,'--network','none',imageId,'/bin/true'],
|
||||
]){const id=(await call(args,remaining())).trim();if(!/^[a-f0-9]{12,64}$/.test(id))throw Error('invalid Docker probe create ID');}
|
||||
}catch(error){errors.push(error.message);}
|
||||
finally{
|
||||
// Label-checked cleanup also covers an uncertain create result. Each CLI
|
||||
// call shares a separate bounded cleanup budget and never reads Config.Env.
|
||||
const cleanupDeadline=performance.now()+3000;
|
||||
try{result.cleanup=cleanupProject({project},args=>{const timeout=Math.ceil(cleanupDeadline-performance.now());if(timeout<=0)throw Error('probe cleanup deadline');return execFileSync(docker,args,{encoding:'utf8',stdio:['ignore','pipe','pipe'],timeout,killSignal:'SIGKILL',maxBuffer:65536});});if(!result.cleanup?.clean)throw Error('probe cleanup not confirmed');}
|
||||
catch(error){errors.push('probe cleanup: '+error.message);}
|
||||
}
|
||||
const missing=()=>['container.create','container.destroy','network.create','network.destroy'].filter(key=>!events.some(e=>e.kind+'.'+e.action===key&&e.observedMs<=timeoutMs));
|
||||
while(!errors.length&&missing().length&&performance.now()<deadline)await new Promise(resolve=>setTimeout(resolve,10));
|
||||
result.missing=missing();if(result.missing.length)errors.push('missing probe events: '+result.missing.join(', '));
|
||||
await watcher.stop();result.wallMs=performance.now()-start;result.ok=!errors.length&&result.cleanup?.clean===true;
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function runProof(){
|
||||
const proofStart=performance.now();
|
||||
let interrupted=false,abortOnSignal=()=>{};
|
||||
const signal=()=>{interrupted=true;abortOnSignal();};
|
||||
@@ -52,12 +113,8 @@ function logObject(project,kind,id,name,action){
|
||||
function ensureWatchers(final=false){
|
||||
if(final||[...active].some(j=>!knownRuns.has(j.run.checkout)))for(const run of ownedRuns())knownRuns.set(run.checkout,run);
|
||||
for(const run of knownRuns.values())if(!watchers.has(run.project)){
|
||||
// Replay the few seconds before the immutable start was noticed; the random
|
||||
// exact project label excludes all foreign resources. No raw events are saved.
|
||||
const child=spawn(docker,['events','--since',String(Math.floor(Date.now()/1000)-5),'--filter','label=com.docker.compose.project='+run.project,'--filter','type=container','--filter','type=network','--filter','event=create','--filter','event=destroy','--format','{{json .}}'],{stdio:['ignore','pipe','pipe']});
|
||||
const watcher={child,stopping:false,buffer:''};watchers.set(run.project,watcher);
|
||||
child.stdout.on('data',bytes=>{watcher.buffer+=bytes;let end;while((end=watcher.buffer.indexOf('\n'))>=0){const line=watcher.buffer.slice(0,end);watcher.buffer=watcher.buffer.slice(end+1);if(!line)continue;try{const event=JSON.parse(line),kind=event.Type,action=event.Action;const attrs=event.Actor?.Attributes??{};if(attrs['com.docker.compose.project']&&attrs['com.docker.compose.project']!==run.project)throw Error('foreign project event');if(['container','network'].includes(kind)&&['create','destroy'].includes(action))logObject(run.project,kind,event.Actor.ID,attrs.name,action);}catch(error){eventError.push(error.message);}}});
|
||||
child.stderr.resume();watcher.done=new Promise(resolve=>{child.once('error',error=>{eventError.push(error.message);resolve();});child.once('close',code=>{if(!watcher.stopping)eventError.push('Docker event stream ended early: '+code);resolve();});});
|
||||
const watcher=watchDockerProject(run.project,{docker,onEvent:event=>logObject(event.project,event.kind,event.id,event.name,event.action),onError:message=>eventError.push(message)});
|
||||
watchers.set(run.project,watcher);
|
||||
}
|
||||
}
|
||||
function inventory(project){
|
||||
@@ -135,7 +192,7 @@ async function stop(){
|
||||
}
|
||||
const measured=[],start=proofStart;
|
||||
const monitorTimer=setInterval(monitor,100);
|
||||
let summary;
|
||||
let summary,watcherProbe=null;
|
||||
try{
|
||||
// Preparation is inside the cleanup path. Yield between synchronous clones
|
||||
// so a pending signal stops preparation before another invocation is made.
|
||||
@@ -148,6 +205,10 @@ try{
|
||||
symlinkSync('/mnt/storage/src/mosaic-stack/node_modules',join(run.checkout,'node_modules'));
|
||||
validateTooling(run.checkout);verifyImage(image,run.checkout);
|
||||
}
|
||||
const probeProject='mg197-a-'+randomBytes(6).toString('hex');
|
||||
put('watcher-probe-plan.json',{project:probeProject,container:probeProject+'-watcher-probe',network:probeProject+'_default',imageId:image.imageId});
|
||||
watcherProbe=await probeDockerWatchers({docker,imageId:image.imageId,cleanupProject,project:probeProject});
|
||||
if(!watcherProbe.ok)fail('Docker watcher preflight failed: '+JSON.stringify(watcherProbe));
|
||||
for(const wave of plan){
|
||||
if(interrupted)fail('proof interrupted');
|
||||
const observed=status(state);
|
||||
@@ -161,15 +222,14 @@ try{
|
||||
const end=status(state);
|
||||
if(end.legacy.length||end.a.state!=='CLEAR'||end.b.state!=='CLEAR')fail('unclean wave status');
|
||||
}
|
||||
const solos=measured.slice(0,2).flat(),pairs=measured.slice(2);summary={grant,solos,pairs,summary:summarizePairs(solos,pairs),wallMs:performance.now()-start,finished:new Date().toISOString()};
|
||||
const solos=measured.slice(0,2).flat(),pairs=measured.slice(2);summary={grant,watcherProbe,solos,pairs,summary:summarizePairs(solos,pairs),wallMs:performance.now()-start,finished:new Date().toISOString()};
|
||||
}catch(error){
|
||||
await stop();put('failed.json',{grant,message:error.message,firstFailure,measured,at:new Date().toISOString()});process.stderr.write(error.message+'\n');process.exitCode=1;
|
||||
}finally{
|
||||
clearInterval(monitorTimer);clearTimeout(abortTimer);await stop();const cleanupErrors=[];
|
||||
try{ensureWatchers(true);}catch(error){cleanupErrors.push({message:error.message});}
|
||||
for(const run of knownRuns.values())try{inventory(run.project);cleanupProject({project:run.project},docker);if(inventory(run.project).length)fail('Docker resources remain');}catch(error){cleanupErrors.push({project:run.project,message:error.message});}
|
||||
for(const watcher of watchers.values()){watcher.stopping=true;watcher.child.kill('SIGTERM');}
|
||||
await Promise.all([...watchers.values()].map(w=>w.done));
|
||||
await Promise.all([...watchers.values()].map(w=>w.stop()));
|
||||
let finalStatus;try{finalStatus=status(state);if(finalStatus.legacy.length||finalStatus.a.state!=='CLEAR'||finalStatus.b.state!=='CLEAR')cleanupErrors.push({message:'lane status not clear',status:finalStatus});}catch(error){cleanupErrors.push({message:error.message});}
|
||||
// Start receipts establish which short roots belong to this proof. Never
|
||||
// discover roots by scanning scratch or follow a substituted root symlink.
|
||||
@@ -191,7 +251,7 @@ try{
|
||||
}catch(error){record.action='error';record.message=error.message;cleanupErrors.push({...record});}
|
||||
tempRoots.push(record);
|
||||
}
|
||||
put('docker-summary.json',{objects:[...objects.values()],eventError,cleanupErrors,finalStatus,tempRoots});
|
||||
put('docker-summary.json',{watcherProbe,objects:[...objects.values()],eventError,cleanupErrors,finalStatus,tempRoots});
|
||||
if(cleanupErrors.length||eventError.length||[...objects.values()].some(o=>!o.name))process.exitCode=1;
|
||||
if(summary&&!process.exitCode){
|
||||
for(const run of knownRuns.values())for(const kind of ['container','network'])if(![...objects.values()].some(o=>o.project===run.project&&o.kind===kind&&o.created)){process.exitCode=1;process.stderr.write('missing Docker creation evidence: '+run.project+' '+kind+'\n');}
|
||||
@@ -199,3 +259,6 @@ try{
|
||||
}
|
||||
for(const name of ['SIGINT','SIGTERM','SIGHUP'])process.off(name,signal);
|
||||
}
|
||||
|
||||
}
|
||||
if(process.argv[1]&&import.meta.url===pathToFileURL(process.argv[1]).href)await runProof();
|
||||
|
||||
Reference in New Issue
Block a user