--- /tmp/lead-33554-r1/evidence-capture-3/driver-original.mjs 2026-11-10 00:40:23.767358862 +0000 +++ /tmp/lead-33554-r1/evidence-capture-3/driver.mjs 2026-11-10 00:40:23.935358862 +0000 @@ -11,7 +11,14 @@ const [Effect,Layer,Stream,Result,NodeServices,NodeHttpClient,Net,SshAuth,SshTunnel,{ChildProcessSpawner},{HttpClient}]=await Promise.all([ mod('effect/Effect'),mod('effect/Layer'),mod('effect/Stream'),mod('effect/Result'),mod('@effect/platform-node/NodeServices'),mod('@effect/platform-node/NodeHttpClient'),import(pathToFileURL(checkout+'/packages/shared/src/Net.ts').href),mod('./src/auth.ts'),mod('./src/tunnel.ts'),mod('effect/process'),mod('effect/http')]); const start=performance.now(); -function event(name,data={}){const e={utc:new Date().toISOString(),ms:Math.round((performance.now()-start)*10)/10,event:name,...data};fs.appendFileSync(r+'/events.jsonl',JSON.stringify(e)+'\n');console.log(JSON.stringify(e));} +let activeCall=null; +const tunnelSpawns=[]; +function pidState(pid){ + let signalExists=false;try{process.kill(Number(pid),0);signalExists=true}catch{} + let state=null;try{state=fs.readFileSync('/proc/'+pid+'/stat','utf8').split(') ')[1].split(' ')[0]}catch{} + return {pid:Number(pid),signalExists,procState:state,dead:!signalExists||state==='Z'||state==='X'}; +} +function event(name,data={}){const e={utc:new Date().toISOString(),ms:Math.round((performance.now()-start)*10)/10,event:name,call:activeCall,...data};fs.appendFileSync(r+'/events.jsonl',JSON.stringify(e)+'\n');console.log(JSON.stringify(e));} let listenerTime=null,localPort=null,tunnel=null,tunnelCount=0,httpCount=0,monitorBusy=false,exited=false; const sockets=new Set(),timers=[]; let proxy=null; @@ -38,33 +45,53 @@ },25); const spawnerLayer=Layer.effect(ChildProcessSpawner.ChildProcessSpawner,Effect.gen(function*(){ const actual=yield* ChildProcessSpawner.ChildProcessSpawner; - return ChildProcessSpawner.make(command=>actual.spawn(command).pipe(Effect.map(child=>{ - const args=command.args??[];event('process.spawn',{pid:child.pid,args:args.includes('-N')?args:args.slice(0,8),kind:args.includes('-N')?'real-tunnel':args.includes('-G')?'real-config-resolve':'stub-remote-command'}); - if(args.includes('-N')){tunnel=child;tunnelCount++;event('tunnel.spawn',{pid:child.pid,count:tunnelCount}); - return {...child,stderr:child.stderr.pipe(Stream.tap(chunk=>Effect.sync(()=>fs.appendFileSync(r+'/ssh.stderr.jsonl',JSON.stringify({utc:new Date().toISOString(),ms:performance.now()-start,text:new TextDecoder().decode(chunk)})+'\n')))),exitCode:child.exitCode.pipe(Effect.tap(code=>Effect.sync(()=>{exited=true;event('tunnel.exit',{pid:child.pid,code:Number(code)})}))),kill:options=>{event('tunnel.kill.requested',{pid:child.pid,options});return child.kill(options)}}; + return ChildProcessSpawner.make(command=>Effect.gen(function*(){ + const args=command.args??[]; + const isTunnel=args.includes('-N'); + const previousTunnel=isTunnel&&tunnel?pidState(tunnel.pid):null; + if(isTunnel)event('tunnel.before.spawn',{port:localPort,previousTunnel}); + const child=yield* actual.spawn(command); + event('process.spawn',{pid:child.pid,args:isTunnel?args:args.slice(0,8),kind:isTunnel?'real-tunnel':args.includes('-G')?'real-config-resolve':'stub-remote-command'}); + if(isTunnel){tunnel=child;tunnelCount++; + const spawn={call:activeCall,pid:Number(child.pid),port:localPort,count:tunnelCount,previousTunnel}; + tunnelSpawns.push(spawn);event('tunnel.spawn',spawn); + return {...child,stderr:child.stderr.pipe(Stream.tap(chunk=>Effect.sync(()=>fs.appendFileSync(r+'/ssh.stderr.jsonl',JSON.stringify({utc:new Date().toISOString(),ms:performance.now()-start,call:activeCall,pid:Number(child.pid),text:new TextDecoder().decode(chunk)})+'\n')))),exitCode:child.exitCode.pipe(Effect.tap(code=>Effect.sync(()=>{exited=true;event('tunnel.exit',{pid:child.pid,code:Number(code)})}))),kill:options=>{event('tunnel.kill.requested',{pid:child.pid,options});return child.kill(options)}}; }return child; - }))); + })); })).pipe(Layer.provide(NodeServices.layer)); const httpLayer=Layer.effect(HttpClient.HttpClient,Effect.gen(function*(){const actual=yield* HttpClient.HttpClient;return HttpClient.make(request=>{httpCount++;event('http.probe',{number:httpCount,url:request.url});return actual.execute(request)});})).pipe(Layer.provide(NodeHttpClient.layerUndici)); const NodeCrypto=await mod('@effect/platform-node/NodeCrypto'); const cryptoLayer=NodeCrypto.layer; const layer=Layer.mergeAll(NodeServices.layer,cryptoLayer,spawnerLayer,httpLayer,Layer.succeed(Net.NetService,netService),SshAuth.SshPasswordPrompt.disabledLayer,SshTunnel.SshEnvironmentManager.layer({resolveCliRunner:Effect.succeed({nodeScriptPath:'/fixture/remote-t3.cjs'})})); -const alias=row==='F06'?'fixture-never':'fixture'; +const alias=['F06','F06b'].includes(row)?'fixture-never':'fixture'; const target={alias,hostname:'fixture.invalid',username:'ubuntu',port:42222}; let results=[]; try{ await Effect.runPromise(Effect.gen(function*(){ const manager=yield* SshTunnel.SshEnvironmentManager; - const ensure=function*(label){const t=performance.now();event('ensure.start',{label,target,issuePairingToken:false});const result=yield* Effect.result(manager.ensureEnvironment(target,{issuePairingToken:false}));const summary=Result.isSuccess(result)?{outcome:'success',value:result.success}:{outcome:'failure',tag:result.failure._tag,error:result.failure.message,exitCode:result.failure.exitCode??null};const saved={row,label,elapsedMs:Math.round(performance.now()-t),httpCount,tunnelCount,listenerObserved:listenerTime!==null,...summary};results.push(saved);event('ensure.result',saved);fs.writeFileSync(r+'/result.json',JSON.stringify(results,null,2));return result;}; + event('manager.scope.ready',{sameManagerForBothCalls:true}); + const ensure=function*(label){ + activeCall=label;const t=performance.now(),startUtc=new Date().toISOString(),startMs=t-start; + const probesAtStart=httpCount,spawnsAtStart=tunnelCount; + event('ensure.start',{label,startUtc,startMs,target,issuePairingToken:false,previousTunnel:tunnel?pidState(tunnel.pid):null}); + const result=yield* Effect.result(manager.ensureEnvironment(target,{issuePairingToken:false})); + const endUtc=new Date().toISOString(),endMs=performance.now()-start; + const summary=Result.isSuccess(result)?{outcome:'success',value:result.success}:{outcome:'failure',errorClass:result.failure.constructor.name,tag:result.failure._tag,error:result.failure.message,exitCode:result.failure.exitCode??null}; + const saved={row,label,startUtc,endUtc,startMs,endMs,elapsedMs:Math.round(endMs-startMs),httpProbesThisCall:httpCount-probesAtStart,tunnelSpawnsThisCall:tunnelCount-spawnsAtStart,tunnelSpawns:tunnelSpawns.filter(s=>s.call===label),tunnelStateAtEnd:tunnel?pidState(tunnel.pid):null,httpCount,tunnelCount,listenerObserved:listenerTime!==null,...summary}; + results.push(saved);event('ensure.result',saved);fs.writeFileSync(r+'/result.json',JSON.stringify(results,null,2));activeCall=null;return result; + }; const first=yield* Effect.gen(()=>ensure('initial')); + if(row==='F06b')yield* Effect.gen(()=>ensure('retry-same-target')); if(row==='F16'&&Result.isSuccess(first)){ event('fixture.stale.kill',{pid:tunnel.pid});yield* tunnel.kill({killSignal:'SIGTERM',forceKillAfter:2000});yield* Effect.gen(()=>ensure('stale-replacement')); } if(['F15','F23'].includes(row)&&Result.isSuccess(first)){yield* Effect.gen(()=>ensure('healthy-reuse'));event('disconnect.start');yield* manager.disconnectEnvironment(target);event('disconnect.complete');} + event('manager.scope.closing'); }).pipe(Effect.provide(layer),Effect.scoped)); + event('manager.scope.closed'); }catch(error){event('driver.failure',{message:String(error),stack:error.stack});process.exitCode=1;} finally{ clearInterval(monitor);for(const timer of timers)clearTimeout(timer);for(const socket of sockets)socket.destroy();backend.closeAllConnections?.();await new Promise(resolve=>backend.close(resolve));if(proxy)await new Promise(resolve=>proxy.close(resolve)); let running=false;if(tunnel){try{process.kill(Number(tunnel.pid),0);running=true}catch{}} - event('cleanup',{tunnelRunning:running,tunnelExitObserved:exited,httpCount,tunnelCount}); + event('cleanup',{tunnelRunning:running,tunnelExitObserved:exited,httpCount,tunnelCount,tunnelStates:tunnelSpawns.map(s=>pidState(s.pid))}); }