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>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-03 00:30:49 +00:00
1 parent e85005482a
commit 14035cc0de
5 files changed
+17 -9

No files matched your search

+7
View File
@@ -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/<pid>/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=<old value> <reason>`.
+1 -1
View File
@@ -1 +1 @@
1.93.27.0
1.93.28.0
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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",
+7 -6
View File
@@ -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<void>{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<typeof spawn>,timeout=5000):Promise<number|null>{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<ReturnType<typeof spawn>>):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<typeof spawn>,timeout=2000):Promise<void>{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<ReturnType<typeof spawn>>=[];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<ReturnType<typeof spawn>>=[];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<ReturnType<typeof spawn>>=[];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<ReturnType<typeof spawn>>=[];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<ReturnType<typeof spawn>>=[];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<ReturnType<typeof spawn>>=[];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<ReturnType<typeof spawn>>=[];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<ReturnType<typeof spawn>>=[];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])<now-30*86400_000))expect(fs.existsSync(path.join(repoDir,name))).toBe(false);});