// #650 first actual turn, one initial budget; prepared only, not executed. import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { watchModelOutput } from '../src/model-stall.mjs'; const delay=ms=>new Promise(r=>setTimeout(r,ms)); async function doneWithin(watch){let timer;try{await Promise.race([watch.done,new Promise((_,reject)=>{timer=setTimeout(()=>reject(Error('watcher completion not observed')),5000);})]);}finally{clearTimeout(timer);}} function fixture(t){const runDir=fs.mkdtempSync(join(tmpdir(),'clock650-'));let stops=0;const receipts=[];t.after(()=>fs.rmSync(runDir,{recursive:true,force:true}));const watch=watchModelOutput({runDir,armAtFirstTurn:true,seconds:0.1,keyId:'0123456789ab',stop:async()=>{stops++;},receipt:r=>receipts.push(r)});t.after(watch.cancel);return {runDir,watch,receipts,stops:()=>stops,arm:()=>fs.writeFileSync(join(runDir,'proxy-first-turn'),`turn ${process.hrtime.bigint()}\n`,{mode:0o600})};} test('650 idle longer than initial budget remains unarmed',async t=>{const f=fixture(t);await delay(250);assert.equal(f.stops(),0,'idle time must not spend first-output budget');f.watch.cancel();await f.watch.done;assert.deepEqual(f.receipts,[]);}); test('650 first silent turn spends one initial budget then invokes existing stop callback',async t=>{const f=fixture(t);await delay(250);assert.equal(f.stops(),0,'silent turn clock is unarmed before first turn');f.arm();await doneWithin(f.watch);assert.equal(f.stops(),1);assert.deepEqual(f.receipts,[{action:'proxy-stall',keyIds:['0123456789ab']}]);}); test('650 first output cancels turn watcher without output text in receipt',async t=>{const f=fixture(t);f.arm();fs.writeFileSync(join(f.runDir,'proxy-first-output'),'output\n',{mode:0o600});await doneWithin(f.watch);assert.equal(f.stops(),0);assert.deepEqual(f.receipts,[]);}); test('650 later marker cannot extend already armed initial budget',async t=>{const f=fixture(t);f.arm();await delay(30);fs.writeFileSync(join(f.runDir,'proxy-first-turn'),`turn ${process.hrtime.bigint()+60_000_000_000n}\n`);await doneWithin(f.watch);assert.equal(f.stops(),1);}); test('650 idle run exit cancellation stops no process',async t=>{const f=fixture(t);f.watch.cancel();await f.watch.done;assert.equal(f.stops(),0);assert.deepEqual(f.receipts,[]);});