"""Run one row 69 verification stage after the reserved test slot clears.""" import os, subprocess, signal, pathlib, json, hashlib, sys root=pathlib.Path.home()/'filbert-scratch/r69-review/wt' out=root.parent/'verification';out.mkdir(exist_ok=True) env=dict(os.environ,TMPDIR=str(pathlib.Path.home()/'filbert-scratch/tmp'),DOCKER_HOST='unix:///nonexistent.sock') seats=['darkwing','dewey','rocko','researcher','sage'] other=[str(pathlib.Path.home()/f'{seat}-scratch') for seat in seats]+[str(pathlib.Path('/mnt/storage/scratch')/seat) for seat in seats] def quiet(): busy=[] for p in pathlib.Path('/proc').iterdir(): if not p.name.isdigit():continue try: cwd=str((p/'cwd').resolve(strict=True)) if any(cwd==x or cwd.startswith(x+'/') for x in other):busy.append((p.name,cwd)) except (OSError,RuntimeError):pass if busy:raise SystemExit('Other scratch processes active: '+repr(busy)) def matches(): m=pathlib.Path('/mnt/storage/src/mosaic-stack/agents/darkwing/work/queue-69/candidate-manifest.sha256') for line in m.read_text().splitlines(): sha,path=line.split();assert hashlib.sha256((root/path).read_bytes()).hexdigest()==sha,path quiet();matches() def run(cmd,cwd,log): quiet() with log.open('w') as f: p=subprocess.Popen(cmd,cwd=cwd,env=env,stdout=f,stderr=subprocess.STDOUT,start_new_session=True) try:p.wait(timeout=180) except subprocess.TimeoutExpired: os.killpg(p.pid,signal.SIGKILL);p.wait();return 'timeout' return p.returncode stage=sys.argv[1] if stage in ['cli','queue']: cwd=root/'packages'/stage files=[str(p.relative_to(cwd)) for p in sorted((cwd/'tests').glob('*.test.mjs'))] rc=run(['node','--test',*files],cwd,out/f'{stage}.log') stats=[s for s in (out/f'{stage}.log').read_text().splitlines() if s.startswith(('ℹ tests ','ℹ pass ','ℹ fail ','# tests ','# pass ','# fail '))] row={'suite':stage,'rc':rc,'stats':stats};print(json.dumps(row),flush=True) (out/f'{stage}-summary.json').write_text(json.dumps(row,indent=2)+'\n') raise SystemExit(rc!=0) if stage!='mutants':raise SystemExit('usage: verify.py cli|queue|mutants') S='packages/cli/src/screens.mjs';A='packages/cli/src/app.mjs' M=[ ('shown-no-min-cols',S,'return cols >= MIN_COLS && rows >= MIN_ROWS && o?.kind','return rows >= MIN_ROWS && o?.kind'), ('r3-shown-no-min',S,'return cols >= MIN_COLS && rows >= MIN_ROWS && o?.kind','return o?.kind'), ('n9-17000-end-off',S,'(n >= 0x17000 && n <= 0x18d08)','(n >= 0x17000 && n <= 0x18cff)'), ('cut-drops-choice',S,'const tail = b.slice(-2);','const tail = b.slice(-1);'), ('hidden-no-way-out',S,', "Resize, or use mosaic decide."]',']'), ('hidden-status-promises-y',A,'const state = hidden ? {','const state = false ? {'), ] rows=[] for name,path,old,new in M: quiet();p=root/path;src=p.read_text();assert src.count(old)==1,(name,src.count(old)) try: p.write_text(src.replace(old,new));log=out/f'mutant-{name}.log' rc=run(['node','--test','packages/cli/tests/app.test.mjs'],root,log) failures=[s for s in log.read_text().splitlines() if s.startswith(('✖','not ok'))] row={'mutant':name,'rc':rc,'result':'survived' if rc==0 else 'timeout' if rc=='timeout' else 'killed','failures':failures[:4]} rows.append(row);print(json.dumps(row),flush=True) finally:p.write_text(src) matches() rc=run(['node','--test','packages/cli/tests/app.test.mjs'],root,out/'restored-app.log') rows.append({'restored_app_rc':rc,'manifest_matches':True}) (out/'mutant-summary.json').write_text(json.dumps(rows,indent=2)+'\n') print(json.dumps(rows[-1]),flush=True) raise SystemExit(rc!=0 or any(r.get('result') in ['survived','timeout'] for r in rows))