From 14035cc0de70c05edc7c7e9baf0214bfa77a3044 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 2 Nov 2026 16:30:49 -0800 Subject: [PATCH] v1.93.28.0 fix(flaky): cso-snapshot-state resumes a paused contender only once it has stopped (GSTA-199) (#3716) * fix(flaky): cso-snapshot-state resumes a paused contender only once ps shows it stopped (GSTA-199) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes resumeStopped checks for an exited child before reading ps and names that exit in its error, separately from the 2 s never-stopped timeout. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: resumeStopped reads /proc on Linux, keeps ps for macOS The stop check no longer needs a ps binary on Linux. A missing /proc entry (the child was already reaped) loops to the exit check instead of failing. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: changelog and TODOS gstack: ok 1.93.22.0 for=ship Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- package.json | 2 +- test/cso-snapshot-state.test.ts | 13 +++++++------ 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec57837bc..49de3e576 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.93.28.0] - 2026-11-03 + +**`test/cso-snapshot-state.test.ts` no longer hangs when a paused contender is resumed too early.** + +### Fixed +- The five paused-contender tests resume a child only once the process table shows it stopped (`/proc//stat` on Linux, `ps` on macOS). Each child marks its pause and then stops itself in two steps, and a SIGCONT that arrived between the two did nothing, so the child stayed stopped and the test timed out (Free Tests 41117003351). With a 200 ms delay injected between the two steps, main failed 3 and then 5 of the 5 tests across two runs, and this change passed 5 of 5 both times. A child that exits, or never stops within 2 s, now fails with a named error instead of hanging. Timing bounds are unchanged. + ## [1.93.27.0] - 2026-11-02 **A test can keep an old value on purpose without turning off `literals` (#3490).** When a minimum version goes up, the test that checks the old version is refused has to keep the old value, and pregate failed it with `PREGATE_LITERALS`. The only way out was `warn: ["literals"]`, which turns the check off for every constant. Now that one line can carry `// pregate-literal: NAME= `. diff --git a/VERSION b/VERSION index 000a12e4a..6e33e7843 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.93.27.0 +1.93.28.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index e9707255b..b1b25db51 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.93.27.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.93.28.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/package.json b/package.json index 048a570d4..402ae4f9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.93.27", + "version": "1.93.28", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/test/cso-snapshot-state.test.ts b/test/cso-snapshot-state.test.ts index 417d3d921..a18799a73 100644 --- a/test/cso-snapshot-state.test.ts +++ b/test/cso-snapshot-state.test.ts @@ -8,6 +8,7 @@ function repo(){const p=tmp();git(p,'init','-q');git(p,'config','user.email','fi async function waitForPath(file:string,timeout=5000):Promise{const deadline=Date.now()+timeout;while(!fs.existsSync(file)){if(Date.now()>=deadline)throw new Error(`Timed out waiting for ${path.basename(file)}`);await Bun.sleep(5);}} function childExit(child:ReturnType,timeout=5000):Promise{return new Promise((resolve,reject)=>{if(child.exitCode!==null){resolve(child.exitCode);return;}const timer=setTimeout(()=>reject(new Error(`Child ${child.pid} did not exit`)),timeout);child.once('error',error=>{clearTimeout(timer);reject(error);});child.once('close',code=>{clearTimeout(timer);resolve(code);});});} function stopChildren(children:Array>):void{for(const child of children){if(!child.pid||child.exitCode!==null)continue;try{process.kill(child.pid,'SIGCONT');}catch{}try{process.kill(child.pid,'SIGKILL');}catch{}}} +async function resumeStopped(child:ReturnType,timeout=2000):Promise{const deadline=Date.now()+timeout;for(;;){if(child.exitCode!==null||child.signalCode!==null)throw new Error(`Child ${child.pid} exited (${child.exitCode??child.signalCode}) after marking its pause, before it stopped`);let state='';if(process.platform==='linux'){try{const stat=fs.readFileSync(`/proc/${child.pid}/stat`,'utf8');state=stat.slice(stat.lastIndexOf(')')+2);}catch(error){if((error as NodeJS.ErrnoException).code!=='ENOENT')throw new Error(`Child ${child.pid} stop check failed: ${(error as Error).message}`);}}else{const ps=spawnSync('ps',['-o','stat=','-p',String(child.pid)],{encoding:'utf8',timeout:2000});if(ps.error)throw new Error(`Child ${child.pid} stop check failed: ${ps.error.message}`);state=(ps.stdout??'').trim();}if(state[0]==='T')break;if(Date.now()>=deadline)throw new Error(`Child ${child.pid} marked its pause but never stopped`);await Bun.sleep(5);}process.kill(child.pid!,'SIGCONT');} function leaseContenderSource():string{return `import fs from 'node:fs';import * as fsModule from 'node:fs';import * as cryptoModule from 'node:crypto';import path from 'node:path';import {spyOn} from 'bun:test'; const dir=process.env.RACE_DIR!,barrier=process.env.BARRIER!,id=process.env.RACE_ID!,mode=process.env.RACE_MODE??'normal',token=process.env.RACE_TOKEN,leases=path.join(dir,'.mutation-lock-leases'),wait=new Int32Array(new SharedArrayBuffer(4));let triggered=false; const write=fs.writeFileSync.bind(fs),mark=(name,value='ready')=>write(path.join(barrier,name),value,{flag:'wx'}),waitFor=(name)=>{const deadline=Date.now()+5000;while(!fs.existsSync(path.join(barrier,name))){if(Date.now()>=deadline)throw new Error('barrier timeout: '+name);Atomics.wait(wait,0,0,1);}}; @@ -240,15 +241,15 @@ let outcome='success';try{withLock(dir,()=>{const active=path.join(barrier,'acti try{ const low=spawn(process.execPath,[script],{env:{...env,RACE_ID:'L',RACE_MODE:'pause-before-decision',RACE_TOKEN:lowToken,RACE_HOLD:'1'},stdio:'ignore'});children.push(low);const lowDone=childExit(low);await waitForPath(path.join(barrier,'paused-L'));expect(fs.readFileSync(path.join(barrier,'choice-L'),'utf8')).toBe('0000000000000001'); const high=spawn(process.execPath,[script],{env:{...env,RACE_ID:'H',RACE_MODE:'pause-before-withdraw',RACE_TOKEN:highToken},stdio:'ignore'});children.push(high);const highDone=childExit(high);await waitForPath(path.join(barrier,'paused-H'),2500);expect(fs.readFileSync(path.join(barrier,'choice-H'),'utf8')).toBe('0000000000000001'); - process.kill(low.pid!,'SIGCONT');await waitForPath(path.join(barrier,'entered-L'));const decision=JSON.parse(fs.readFileSync(path.join(dir,'.mutation-lock-leases',lowToken+'.decision'),'utf8'));expect(decision.kind).toBe('ticket');expect(decision.ticket).toBe('0000000000000001'); - process.kill(high.pid!,'SIGCONT');await waitForPath(path.join(barrier,'result-H'));expect(fs.readFileSync(path.join(barrier,'result-H'),'utf8')).toBe('INSUFFICIENT_CAPACITY');expect(await highDone).toBe(0); + await resumeStopped(low);await waitForPath(path.join(barrier,'entered-L'));const decision=JSON.parse(fs.readFileSync(path.join(dir,'.mutation-lock-leases',lowToken+'.decision'),'utf8'));expect(decision.kind).toBe('ticket');expect(decision.ticket).toBe('0000000000000001'); + await resumeStopped(high);await waitForPath(path.join(barrier,'result-H'));expect(fs.readFileSync(path.join(barrier,'result-H'),'utf8')).toBe('INSUFFICIENT_CAPACITY');expect(await highDone).toBe(0); fs.writeFileSync(path.join(barrier,'release-L'),'release');await waitForPath(path.join(barrier,'result-L'));expect(fs.readFileSync(path.join(barrier,'result-L'),'utf8')).toBe('success');expect(await lowDone).toBe(0);expect(fs.existsSync(path.join(barrier,'overlap'))).toBe(false);expect(fs.readdirSync(path.join(dir,'.mutation-lock-leases'))).toEqual([]); }finally{stopChildren(children);} }); - test('a paused candidate is fenced so a progressing ticket wins within the bound',async()=>{if(process.platform==='win32')return;const dir=tmp(),barrier=path.join(dir,'barrier'),script=path.join(dir,'contender.ts');fs.mkdirSync(barrier);expect(withLock(dir,()=>1)).toBe(1);fs.writeFileSync(script,leaseContenderSource());const env={...process.env,RACE_DIR:dir,BARRIER:barrier},children:Array>=[];try{const earlier=spawn(process.execPath,[script],{env:{...env,RACE_ID:'L',RACE_MODE:'pause-candidate'},stdio:'ignore'});children.push(earlier);const earlierDone=childExit(earlier);await waitForPath(path.join(barrier,'paused-L'));const started=Date.now(),later=spawn(process.execPath,[script],{env:{...env,RACE_ID:'H',RACE_MODE:'normal'},stdio:'ignore'});children.push(later);const laterDone=childExit(later);await waitForPath(path.join(barrier,'result-H'),2500);expect(Date.now()-started).toBeLessThan(2000);expect(fs.readFileSync(path.join(barrier,'result-H'),'utf8')).toBe('success');expect(await laterDone).toBe(0);process.kill(earlier.pid!,'SIGCONT');await waitForPath(path.join(barrier,'result-L'));expect(fs.readFileSync(path.join(barrier,'result-L'),'utf8')).toBe('INSUFFICIENT_CAPACITY');expect(await earlierDone).toBe(0);expect(fs.existsSync(path.join(barrier,'overlap'))).toBe(false);expect(fs.readdirSync(path.join(dir,'.mutation-lock-leases'))).toEqual([]);}finally{stopChildren(children);}}); - test('a paused earlier ticket bounds later contenders without withdrawing the winner',async()=>{if(process.platform==='win32')return;const dir=tmp(),barrier=path.join(dir,'barrier'),script=path.join(dir,'contender.ts');fs.mkdirSync(barrier);expect(withLock(dir,()=>1)).toBe(1);fs.writeFileSync(script,leaseContenderSource());const env={...process.env,RACE_DIR:dir,BARRIER:barrier},children:Array>=[];try{const earlier=spawn(process.execPath,[script],{env:{...env,RACE_ID:'L',RACE_MODE:'pause-ticket'},stdio:'ignore'});children.push(earlier);const earlierDone=childExit(earlier);await waitForPath(path.join(barrier,'paused-L'));const started=Date.now(),later=spawn(process.execPath,[script],{env:{...env,RACE_ID:'H',RACE_MODE:'normal'},stdio:'ignore'});children.push(later);const laterDone=childExit(later);await waitForPath(path.join(barrier,'result-H'),2500);expect(Date.now()-started).toBeLessThan(2000);expect(fs.readFileSync(path.join(barrier,'result-H'),'utf8')).toBe('INSUFFICIENT_CAPACITY');expect(await laterDone).toBe(0);process.kill(earlier.pid!,'SIGCONT');await waitForPath(path.join(barrier,'result-L'));expect(fs.readFileSync(path.join(barrier,'result-L'),'utf8')).toBe('success');expect(await earlierDone).toBe(0);expect(fs.existsSync(path.join(barrier,'overlap'))).toBe(false);expect(fs.readdirSync(path.join(dir,'.mutation-lock-leases'))).toEqual([]);}finally{stopChildren(children);}}); - test('a paused loser cleanup leaves only an ineligible decision and cannot block the winner',async()=>{if(process.platform==='win32')return;const dir=tmp(),barrier=path.join(dir,'barrier'),script=path.join(dir,'contender.ts');fs.mkdirSync(barrier);expect(withLock(dir,()=>1)).toBe(1);fs.writeFileSync(script,leaseContenderSource());const env={...process.env,RACE_DIR:dir,BARRIER:barrier},children:Array>=[];try{const winner=spawn(process.execPath,[script],{env:{...env,RACE_ID:'L',RACE_MODE:'pause-ticket'},stdio:'ignore'});children.push(winner);const winnerDone=childExit(winner);await waitForPath(path.join(barrier,'paused-L'));const loser=spawn(process.execPath,[script],{env:{...env,RACE_ID:'H',RACE_MODE:'pause-withdraw-cleanup'},stdio:'ignore'});children.push(loser);const loserDone=childExit(loser);await waitForPath(path.join(barrier,'paused-H'));const phases=fs.readdirSync(path.join(dir,'.mutation-lock-leases'));expect(phases.filter(name=>name.endsWith('.decision')).length).toBe(2);expect(phases.filter(name=>name.endsWith('.json')).length).toBe(1);process.kill(winner.pid!,'SIGCONT');await waitForPath(path.join(barrier,'result-L'));expect(fs.readFileSync(path.join(barrier,'result-L'),'utf8')).toBe('success');expect(await winnerDone).toBe(0);process.kill(loser.pid!,'SIGCONT');await waitForPath(path.join(barrier,'result-H'));expect(fs.readFileSync(path.join(barrier,'result-H'),'utf8')).toBe('INSUFFICIENT_CAPACITY');expect(await loserDone).toBe(0);expect(fs.existsSync(path.join(barrier,'overlap'))).toBe(false);expect(fs.readdirSync(path.join(dir,'.mutation-lock-leases'))).toEqual([]);}finally{stopChildren(children);}}); - test('a paused later contender cannot turn a cached lower ticket into an all-loser outcome',async()=>{if(process.platform==='win32')return;const dir=tmp(),barrier=path.join(dir,'barrier'),script=path.join(dir,'contender.ts');fs.mkdirSync(barrier);expect(withLock(dir,()=>1)).toBe(1);fs.writeFileSync(script,leaseContenderSource());const env={...process.env,RACE_DIR:dir,BARRIER:barrier},children:Array>=[];try{const earlier=spawn(process.execPath,[script],{env:{...env,RACE_ID:'L',RACE_MODE:'wait-active',RACE_HOLD:'1'},stdio:'ignore'});children.push(earlier);const earlierDone=childExit(earlier);await waitForPath(path.join(barrier,'doorway-L'));const later=spawn(process.execPath,[script],{env:{...env,RACE_ID:'H',RACE_MODE:'pause-later-scan'},stdio:'ignore'});children.push(later);const laterDone=childExit(later);await waitForPath(path.join(barrier,'paused-H'));await waitForPath(path.join(barrier,'entered-L'));process.kill(later.pid!,'SIGCONT');await waitForPath(path.join(barrier,'result-H'),2500);expect(fs.readFileSync(path.join(barrier,'result-H'),'utf8')).toBe('INSUFFICIENT_CAPACITY');expect(await laterDone).toBe(0);fs.writeFileSync(path.join(barrier,'release-L'),'release');await waitForPath(path.join(barrier,'result-L'));expect(fs.readFileSync(path.join(barrier,'result-L'),'utf8')).toBe('success');expect(await earlierDone).toBe(0);expect(fs.existsSync(path.join(barrier,'overlap'))).toBe(false);expect(fs.readdirSync(path.join(dir,'.mutation-lock-leases'))).toEqual([]);}finally{stopChildren(children);}}); + test('a paused candidate is fenced so a progressing ticket wins within the bound',async()=>{if(process.platform==='win32')return;const dir=tmp(),barrier=path.join(dir,'barrier'),script=path.join(dir,'contender.ts');fs.mkdirSync(barrier);expect(withLock(dir,()=>1)).toBe(1);fs.writeFileSync(script,leaseContenderSource());const env={...process.env,RACE_DIR:dir,BARRIER:barrier},children:Array>=[];try{const earlier=spawn(process.execPath,[script],{env:{...env,RACE_ID:'L',RACE_MODE:'pause-candidate'},stdio:'ignore'});children.push(earlier);const earlierDone=childExit(earlier);await waitForPath(path.join(barrier,'paused-L'));const started=Date.now(),later=spawn(process.execPath,[script],{env:{...env,RACE_ID:'H',RACE_MODE:'normal'},stdio:'ignore'});children.push(later);const laterDone=childExit(later);await waitForPath(path.join(barrier,'result-H'),2500);expect(Date.now()-started).toBeLessThan(2000);expect(fs.readFileSync(path.join(barrier,'result-H'),'utf8')).toBe('success');expect(await laterDone).toBe(0);await resumeStopped(earlier);await waitForPath(path.join(barrier,'result-L'));expect(fs.readFileSync(path.join(barrier,'result-L'),'utf8')).toBe('INSUFFICIENT_CAPACITY');expect(await earlierDone).toBe(0);expect(fs.existsSync(path.join(barrier,'overlap'))).toBe(false);expect(fs.readdirSync(path.join(dir,'.mutation-lock-leases'))).toEqual([]);}finally{stopChildren(children);}}); + test('a paused earlier ticket bounds later contenders without withdrawing the winner',async()=>{if(process.platform==='win32')return;const dir=tmp(),barrier=path.join(dir,'barrier'),script=path.join(dir,'contender.ts');fs.mkdirSync(barrier);expect(withLock(dir,()=>1)).toBe(1);fs.writeFileSync(script,leaseContenderSource());const env={...process.env,RACE_DIR:dir,BARRIER:barrier},children:Array>=[];try{const earlier=spawn(process.execPath,[script],{env:{...env,RACE_ID:'L',RACE_MODE:'pause-ticket'},stdio:'ignore'});children.push(earlier);const earlierDone=childExit(earlier);await waitForPath(path.join(barrier,'paused-L'));const started=Date.now(),later=spawn(process.execPath,[script],{env:{...env,RACE_ID:'H',RACE_MODE:'normal'},stdio:'ignore'});children.push(later);const laterDone=childExit(later);await waitForPath(path.join(barrier,'result-H'),2500);expect(Date.now()-started).toBeLessThan(2000);expect(fs.readFileSync(path.join(barrier,'result-H'),'utf8')).toBe('INSUFFICIENT_CAPACITY');expect(await laterDone).toBe(0);await resumeStopped(earlier);await waitForPath(path.join(barrier,'result-L'));expect(fs.readFileSync(path.join(barrier,'result-L'),'utf8')).toBe('success');expect(await earlierDone).toBe(0);expect(fs.existsSync(path.join(barrier,'overlap'))).toBe(false);expect(fs.readdirSync(path.join(dir,'.mutation-lock-leases'))).toEqual([]);}finally{stopChildren(children);}}); + test('a paused loser cleanup leaves only an ineligible decision and cannot block the winner',async()=>{if(process.platform==='win32')return;const dir=tmp(),barrier=path.join(dir,'barrier'),script=path.join(dir,'contender.ts');fs.mkdirSync(barrier);expect(withLock(dir,()=>1)).toBe(1);fs.writeFileSync(script,leaseContenderSource());const env={...process.env,RACE_DIR:dir,BARRIER:barrier},children:Array>=[];try{const winner=spawn(process.execPath,[script],{env:{...env,RACE_ID:'L',RACE_MODE:'pause-ticket'},stdio:'ignore'});children.push(winner);const winnerDone=childExit(winner);await waitForPath(path.join(barrier,'paused-L'));const loser=spawn(process.execPath,[script],{env:{...env,RACE_ID:'H',RACE_MODE:'pause-withdraw-cleanup'},stdio:'ignore'});children.push(loser);const loserDone=childExit(loser);await waitForPath(path.join(barrier,'paused-H'));const phases=fs.readdirSync(path.join(dir,'.mutation-lock-leases'));expect(phases.filter(name=>name.endsWith('.decision')).length).toBe(2);expect(phases.filter(name=>name.endsWith('.json')).length).toBe(1);await resumeStopped(winner);await waitForPath(path.join(barrier,'result-L'));expect(fs.readFileSync(path.join(barrier,'result-L'),'utf8')).toBe('success');expect(await winnerDone).toBe(0);await resumeStopped(loser);await waitForPath(path.join(barrier,'result-H'));expect(fs.readFileSync(path.join(barrier,'result-H'),'utf8')).toBe('INSUFFICIENT_CAPACITY');expect(await loserDone).toBe(0);expect(fs.existsSync(path.join(barrier,'overlap'))).toBe(false);expect(fs.readdirSync(path.join(dir,'.mutation-lock-leases'))).toEqual([]);}finally{stopChildren(children);}}); + test('a paused later contender cannot turn a cached lower ticket into an all-loser outcome',async()=>{if(process.platform==='win32')return;const dir=tmp(),barrier=path.join(dir,'barrier'),script=path.join(dir,'contender.ts');fs.mkdirSync(barrier);expect(withLock(dir,()=>1)).toBe(1);fs.writeFileSync(script,leaseContenderSource());const env={...process.env,RACE_DIR:dir,BARRIER:barrier},children:Array>=[];try{const earlier=spawn(process.execPath,[script],{env:{...env,RACE_ID:'L',RACE_MODE:'wait-active',RACE_HOLD:'1'},stdio:'ignore'});children.push(earlier);const earlierDone=childExit(earlier);await waitForPath(path.join(barrier,'doorway-L'));const later=spawn(process.execPath,[script],{env:{...env,RACE_ID:'H',RACE_MODE:'pause-later-scan'},stdio:'ignore'});children.push(later);const laterDone=childExit(later);await waitForPath(path.join(barrier,'paused-H'));await waitForPath(path.join(barrier,'entered-L'));await resumeStopped(later);await waitForPath(path.join(barrier,'result-H'),2500);expect(fs.readFileSync(path.join(barrier,'result-H'),'utf8')).toBe('INSUFFICIENT_CAPACITY');expect(await laterDone).toBe(0);fs.writeFileSync(path.join(barrier,'release-L'),'release');await waitForPath(path.join(barrier,'result-L'));expect(fs.readFileSync(path.join(barrier,'result-L'),'utf8')).toBe('success');expect(await earlierDone).toBe(0);expect(fs.existsSync(path.join(barrier,'overlap'))).toBe(false);expect(fs.readdirSync(path.join(dir,'.mutation-lock-leases'))).toEqual([]);}finally{stopChildren(children);}}); test('legacy report imports expire after thirty days without following links',()=>{const old=process.env.GSTACK_HOME,base=tmp();process.env.GSTACK_HOME=base;try{const archive=secureDirectory(path.join(base,'security','cso','legacy-imports')),expired=path.join(archive,'a'.repeat(64)+'.json'),retained=path.join(archive,'b'.repeat(64)+'.json');fs.writeFileSync(expired,'{}');fs.writeFileSync(retained,'{}');const now=Date.now();fs.utimesSync(expired,new Date(now-31*86400_000),new Date(now-31*86400_000));retention(now);expect(fs.existsSync(expired)).toBe(false);expect(fs.existsSync(retained)).toBe(true);const unsafe=path.join(archive,'c'.repeat(64)+'.json');fs.symlinkSync(retained,unsafe);expect(()=>retention(now)).toThrow('unsafe artifact');expect(fs.readFileSync(retained,'utf8')).toBe('{}');}finally{if(old===undefined)delete process.env.GSTACK_HOME;else process.env.GSTACK_HOME=old;}}); test('an active recheck pins only its expired parent report, never expired repair material',()=>{const now=Date.now(),repo='f'.repeat(24),oldRun=`${now-31*86400_000}-${'a'.repeat(16)}`,childRun=`${now}-${'b'.repeat(16)}`,findingId='c'.repeat(32),repoDir=secureDirectory(path.join(privateRoot(),repo)),parent=secureDirectory(path.join(repoDir,oldRun)),child=secureDirectory(path.join(repoDir,childRun)),bundles=secureDirectory(path.join(parent,'bundles')),reviews=secureDirectory(path.join(parent,'reviews')),attempts=secureDirectory(path.join(parent,'verification-attempts'));writeHelperJson(path.join(parent,'report.json'),{schemaVersion:3,runId:oldRun,repoId:repo,status:'finished',coverage:[],findings:[{id:findingId}]});writeJson(path.join(bundles,'bundle.json'),{schemaVersion:3});writeJson(path.join(reviews,'review.json'),{schemaVersion:3});writeJson(path.join(attempts,'attempt.json'),{schemaVersion:3});writeHelperJson(path.join(child,'report.json'),{schemaVersion:3,runId:childRun,repoId:repo,status:'running',deadline:new Date(now+60_000).toISOString(),coverage:[],findings:[],parent:{runId:oldRun,findingId,kind:'recheck'}});retention(now);expect(fs.existsSync(path.join(parent,'report.json'))).toBe(true);expect(fs.existsSync(bundles)).toBe(false);expect(fs.existsSync(reviews)).toBe(false);expect(fs.existsSync(attempts)).toBe(false);fs.rmSync(child,{recursive:true});retention(now);expect(fs.existsSync(parent)).toBe(false);}); test('finished, expired, and malformed child reports cannot extend parent retention',()=>{const now=Date.now(),repo='e'.repeat(24),repoDir=secureDirectory(path.join(privateRoot(),repo)),findingId='d'.repeat(32);for(const [index,childValue] of [[0,{status:'finished',repoId:repo,deadline:new Date(now+60_000).toISOString()}],[1,{status:'running',repoId:repo,deadline:new Date(now-1).toISOString()}],[2,{status:'running',repoId:'0'.repeat(24),deadline:new Date(now+60_000).toISOString()}]] as const){const parentRun=`${now-(31+index)*86400_000}-${String(index+1).repeat(16)}`,childRun=`${now-index}-${String(index+4).repeat(16)}`,parent=secureDirectory(path.join(repoDir,parentRun)),child=secureDirectory(path.join(repoDir,childRun));writeHelperJson(path.join(parent,'report.json'),{schemaVersion:3,runId:parentRun,repoId:repo,status:'finished',coverage:[],findings:[{id:findingId}]});writeHelperJson(path.join(child,'report.json'),{schemaVersion:3,runId:childRun,repoId:childValue.repoId,status:childValue.status,deadline:childValue.deadline,coverage:[],findings:[],parent:{runId:parentRun,findingId,kind:'recheck'}});}retention(now);for(const name of fs.readdirSync(repoDir).filter(name=>Number(name.split('-')[0])