Files
gstack/test/plan-create-prepublication.test.ts
T
Garry Tanandcapy-ai[bot] 3766edac8d v1.91.144.0 feat(evals): Opus 5.5 is the paid gate's model under test; judge stays Fable (GSTA-49 TM1) (#3275)
* feat(evals): Opus 5.5 is the paid gate's model under test; judge stays Fable (GSTA-49 TM1)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.140.0 chore: stamp TM1 (Opus 5.5 model under test) and CHANGELOG

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): repair the five Opus gate reds from run 39161502626

- coverage audit: owned cd; followed by one && chain, wc -l on in-cwd files
- shared-libs: Claude Code 2.1.292 git --version probe, exact argv only
- ship hook: accept 'integrate the hook yourself' / 'combine ... by hand'
- qa-only: deadline starts only after the charter Write completes
- plan floor: tool-only parent session identifies the owned Create grant;
  floor runner fails fast with PERMISSION_DIALOG_UNANSWERED

Each repair replays its captured run as a free regression with negative
controls. HARNESS_VERSION stays 6 (re-recorded from main's 5).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): ship hook manual-integration sentence rule; qa-only inspection uses Read/Glob; tool-only permission tests join their owner file

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): tpa-absent-linux accepts instructions the user follows themselves as the manual option

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): owned plan Edit menu binds to exactly one pending Edit, including unpublished ones

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): shared-libs read evidence counts a literal path loop whose output names each file

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: restamp to v1.91.142.0 (queue slot moved)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): investigate boundary fixture identifies the owner question by its continue/abort options

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): plan-design-with-ui focus check accepts the skill's design-completeness rating in the question body

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): phrasing sweep across the 108 gate rule cases; replay captured Opus paraphrases with negative controls

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(evals): command-detector sweep — exact-count anchor, label slot, 2.1.292 worktree probe

- coverage audit (gate 39207508651 plan-eng-coverage-audit): output of exactly
  known length (wc -l over literal in-cwd files) anchors the next owned read
  at that line; a label's slot counts from the end and a later complete copy
  voids the credit (review/specialists/testing.md prints '---')
- shared-libs-unsupported-git (39161502626): Claude Code 2.1.292's askPass-first
  'worktree list --porcelain -z' cwd probe is Claude's own; the read-only Git
  check is shared (repoGitRequests, isReadOnlyRepoGitRequest) and the free
  regression replays every Git request of that capture

Each fix replays its captured command with negative controls. No threshold,
trial count, case kind or judge changed. HARNESS_VERSION stays 6.

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>
2026-10-19 07:26:49 +00:00

222 lines
16 KiB
TypeScript

import {expect,test} from 'bun:test';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import {createHash} from 'node:crypto';
import {createFilePermissionRecorder,recordFilePermission,currentFilePermissionEpoch,readPendingWriteInput} from './helpers/plan-count-file-permission';
import {readPlanCountTranscript} from './helpers/plan-count-transcript';
import {createPlanCountPermissionGuard,countingCapture,type CountingRun} from './helpers/claude-pty-runner';
import {createPlanCountSnapshotWriter} from './helpers/plan-count-artifacts';
import captured from './fixtures/plan-create-prepublication-491.json';
import toolOnlyCreate from './fixtures/plan-ceo-floor-toolonly-create.json';
function fixture(){
const root=fs.mkdtempSync(path.join(os.tmpdir(),'gstack-prewrite-'));const cwd=path.join(root,'owned'),config=path.join(root,'config');fs.mkdirSync(cwd);
const expected=path.join(cwd,path.basename(captured.originalHook.expected)),sid=captured.originalHook.sessionId;
const id=captured.originalHook.pendingId.slice(sid.length+1),journal=path.join(config,'projects','owned',sid+'.jsonl');fs.mkdirSync(path.dirname(journal),{recursive:true});
const startedAt=Date.now()-1000;const recorder=createFilePermissionRecorder(cwd,config,expected)!;
const input={file_path:expected,content:captured.controlledWriteContent};
const native=(role:string,content:any[])=>({cwd,sessionId:sid,isSidechain:false,timestamp:new Date().toISOString(),message:{role,content}});
const base=[native('assistant',[{type:'text',text:'Preparing the requested review.'}])];
const write=(rows:any[])=>fs.writeFileSync(journal,rows.map(r=>JSON.stringify(r)).join('\n')+'\n');write(base);
const hook=(kind='PreToolUse',toolId=id,toolInput:any=input,extra={})=>recordFilePermission(JSON.stringify({hook_event_name:kind,tool_name:'Write',session_id:sid,tool_use_id:toolId,cwd,transcript_path:journal,tool_input:toolInput,...extra}),recorder.file,cwd,config,expected);
const screen=captured.originalScreen.replaceAll(path.dirname(captured.originalHook.expected),cwd);
const read=()=>currentFilePermissionEpoch(recorder.file,expected,cwd,config,startedAt,readPlanCountTranscript(config,cwd),screen);
const block=(toolId=id,toolInput:any=input,name='Write')=>({type:'tool_use',id:toolId,name,input:toolInput});
const sidecar=recorder.file+'.write.json';
return {root,cwd,config,expected,sid,id,journal,recorder,input,native,base,write,hook,screen,read,block,sidecar,startedAt,close(){recorder.dispose();fs.rmSync(root,{recursive:true,force:true});}};
}
test('owned current Write prepublication input authenticates the captured cropped Create once',()=>{
const f=fixture();try{
f.hook();expect(f.read()?.pendingId).toBe(`${f.sid}:${f.id}`);
const guard=createPlanCountPermissionGuard();expect(guard(f.screen,'',f.read())).toBe('grant');expect(guard(f.screen,'',f.read())).toBe('handled');
const state=JSON.parse(fs.readFileSync(f.recorder.file,'utf8')),bytes=fs.readFileSync(f.sidecar);
expect(state.writeInputSha256).toBe(createHash('sha256').update(bytes).digest('hex'));
expect(JSON.parse(bytes.toString()).input).toEqual(f.input);
expect(fs.readFileSync(f.recorder.file,'utf8')).not.toContain(f.input.content);
expect(fs.statSync(f.sidecar).mode&0o777).toBe(0o600);
}finally{f.close();}
});
test('original content-free capture alone supplies no missing argument or permission credit',()=>{
const f=fixture();try{f.hook();const state=JSON.parse(fs.readFileSync(f.recorder.file,'utf8'));delete state.writeInputSha256;fs.writeFileSync(f.recorder.file,JSON.stringify(state));fs.rmSync(f.sidecar,{force:true});expect(f.read()).toBeNull();}finally{f.close();}
});
test.each(['other-pending','completed','changed-hidden-input','different-tool','conflicting-duplicate'])(`prepublication cannot override %s public native evidence`,mode=>{
const f=fixture();try{f.hook();let rows:any[]=[...f.base];
if(mode==='other-pending')rows.push(f.native('assistant',[f.block('different')]));
if(mode==='completed')rows.push(f.native('user',[{type:'tool_result',tool_use_id:f.id,content:'done'}]));
if(mode==='changed-hidden-input')rows.push(f.native('assistant',[f.block(f.id,{...f.input,content:f.input.content.replace(/^.*$/m,'Changed invisible first line')})]));
if(mode==='different-tool')rows.push(f.native('assistant',[f.block(f.id,f.input,'Read')]));
if(mode==='conflicting-duplicate')rows.push(f.native('assistant',[f.block(),f.block(f.id,{...f.input,content:'different'})]));
f.write(rows);expect(f.read()).toBeNull();
}finally{f.close();}
});
test('matching published Write retains the existing route and exact input agreement',()=>{
const f=fixture();try{f.hook();f.write([...f.base,f.native('assistant',[f.block()])]);expect(f.read()?.pendingId).toBe(`${f.sid}:${f.id}`);}finally{f.close();}
});
test.each(['missing','changed-bytes','linked','wrong-current-id','foreign-target'])(`invalid %s witness fails closed`,mode=>{
const f=fixture();try{f.hook();
if(mode==='missing')fs.unlinkSync(f.sidecar);
if(mode==='changed-bytes')fs.appendFileSync(f.sidecar,' ');
if(mode==='linked'){const copy=f.sidecar+'.copy';fs.copyFileSync(f.sidecar,copy);fs.unlinkSync(f.sidecar);fs.symlinkSync(copy,f.sidecar);}
if(mode==='wrong-current-id'||mode==='foreign-target'){
const witness=JSON.parse(fs.readFileSync(f.sidecar,'utf8'));
if(mode==='wrong-current-id')witness.pendingId=`${f.sid}:different`;else witness.input.file_path=f.expected+'-foreign';
const bytes=JSON.stringify(witness);fs.writeFileSync(f.sidecar,bytes);const state=JSON.parse(fs.readFileSync(f.recorder.file,'utf8'));state.writeInputSha256=createHash('sha256').update(bytes).digest('hex');fs.writeFileSync(f.recorder.file,JSON.stringify(state));
}expect(f.read()).toBeNull();
}finally{f.close();}
});
test('completion and replay cannot retain or reopen a Write witness; a later failed-save recovery is fresh',()=>{
const f=fixture();try{f.hook();f.hook('PostToolUseFailure');expect(f.read()).toBeNull();expect(fs.existsSync(f.sidecar)).toBe(false);f.hook();expect(f.read()).toBeNull();f.hook('PreToolUse','recovery');expect(f.read()?.pendingId).toBe(`${f.sid}:recovery`);f.hook('PostToolUse','recovery');expect(fs.existsSync(f.sidecar)).toBe(false);f.hook('PreToolUse','recovery');expect(f.read()).toBeNull();}finally{f.close();}
});
test('overlapping uncompleted hook epochs never borrow the most recently captured Write',()=>{
const f=fixture();try{f.hook();f.hook('PreToolUse','second');expect(f.read()).toBeNull();f.hook('PostToolUse','second');f.hook('PreToolUse','third');expect(f.read()).toBeNull();}finally{f.close();}
});
test('conflicting repeated current hook arguments invalidate the witness without reopening its ID',()=>{
const f=fixture();try{
f.hook();f.hook();expect(f.read()?.pendingId).toBe(`${f.sid}:${f.id}`);
f.hook('PreToolUse',f.id,{...f.input,content:f.input.content+'\nchanged hidden tail'});
expect(f.read()).toBeNull();
f.hook();expect(f.read()).toBeNull();
f.hook('PostToolUseFailure');f.hook();expect(f.read()).toBeNull();
f.hook('PreToolUse','fresh');expect(f.read()?.pendingId).toBe(`${f.sid}:fresh`);
}finally{f.close();}
});
test.each(['cwd','target','config','stale','future','linked-state','oversized-state','oversized-witness'])(`standalone witness reader rejects %s ownership or bounds`,mode=>{
const f=fixture();try{
f.hook();let cwd=f.cwd,expected=f.expected,config:string|null=f.config,startedAt=f.startedAt;
if(mode==='cwd')cwd+='-foreign';
if(mode==='target')expected+='-foreign';
if(mode==='config')config+='-foreign';
if(mode==='stale')startedAt=Date.now()+1000;
if(mode==='future'){
const state=JSON.parse(fs.readFileSync(f.recorder.file,'utf8'));
state.timestamp=new Date(Date.now()+60_000).toISOString();fs.writeFileSync(f.recorder.file,JSON.stringify(state));
}
if(mode==='linked-state'){
fs.renameSync(f.recorder.file,f.recorder.file+'.copy');fs.symlinkSync(f.recorder.file+'.copy',f.recorder.file);
}
if(mode==='oversized-state')fs.truncateSync(f.recorder.file,64*1024+1);
if(mode==='oversized-witness')fs.truncateSync(f.sidecar,4*1024*1024+1);
expect(readPendingWriteInput(f.recorder.file,expected,cwd,config,startedAt)).toBeUndefined();
}finally{f.close();}
});
test.each(['foreign-session','sidechain','foreign-target','foreign-journal'])(`foreign %s hook cannot supply the cropped Create input`,mode=>{
const f=fixture();try{
const extra:any={};let input:any=f.input;
if(mode==='foreign-session')extra.session_id='foreign';
if(mode==='sidechain')extra.agent_id='foreign';
if(mode==='foreign-target')input={...input,file_path:f.expected+'-foreign'};
if(mode==='foreign-journal')extra.transcript_path=path.join(f.root,'foreign.jsonl');
f.hook('PreToolUse',f.id,input,extra);expect(f.read()).toBeNull();expect(fs.existsSync(f.sidecar)).toBe(false);
}finally{f.close();}
});
test('oversized actual hook input fails closed without a witness',()=>{
const f=fixture();try{
f.hook('PreToolUse',f.id,{...f.input,content:'x'.repeat(4*1024*1024)});
expect(f.read()).toBeNull();expect(fs.existsSync(f.sidecar)).toBe(false);
}finally{f.close();}
});
test('legacy published Write metadata remains sufficient without a sidecar',()=>{
const f=fixture();try{
f.hook();const state=JSON.parse(fs.readFileSync(f.recorder.file,'utf8'));
delete state.writeInputSha256;fs.writeFileSync(f.recorder.file,JSON.stringify(state));fs.unlinkSync(f.sidecar);
f.write([...f.base,f.native('assistant',[f.block()])]);expect(f.read()?.pendingId).toBe(`${f.sid}:${f.id}`);
}finally{f.close();}
});
// Execute only the actual count capture assembly with its real reader/writer.
// This calls the counting runner's capture step directly, not a full loop; existing count throw/timeout tests
// separately prove that the lifecycle invokes capture before fixture disposal.
test('count capture assembly retains exact prepublication input through refresh, throw and fixture cleanup',()=>{
const f=fixture(),evalDir=fs.mkdtempSync(path.join(os.tmpdir(),'count-write-retention-'));
try {
f.hook();const expected=readPendingWriteInput(f.recorder.file,f.expected,f.cwd,f.config,f.startedAt);
expect(expected).toBeDefined();
// The runner's own capture step, called with the state it builds for an attempt.
const run={opts:{skillName:'plan-eng-review'},fixture:{cwd:f.cwd},startedAt:f.startedAt,viewport:f.screen,
ownedFilePermissions:[{file:f.recorder.file,expected:f.expected}],
saveSnapshot:createPlanCountSnapshotWriter({EVALS_RUN_ID:'count-prepublication-free',GSTACK_EVAL_DIR:evalDir})} as unknown as CountingRun;
const capture=(observation:object)=>countingCapture(run,{hermeticConfigDir:f.config,rawOutput:()=>f.screen,visibleText:()=>f.screen},observation);
const progress=capture({state:'in_progress'});
expect(progress.artifactError).toBeUndefined();expect(progress.artifactDir).toBeDefined();
const initial=JSON.parse(fs.readFileSync(path.join(progress.artifactDir,'observation.json'),'utf8'));
expect(initial.state).toBe('in_progress');expect(initial.pendingWriteInputs).toEqual([expected]);expect(initial.publicTools).toEqual([]);
const published=f.native('assistant',[f.block()]);f.write([...f.base,published]);
const thrown=capture({state:'threw',error:'controlled caller interruption'});
expect(thrown.artifactDir).toBe(progress.artifactDir);expect(thrown.artifactError).toBeUndefined();
f.close();
const saved=JSON.parse(fs.readFileSync(path.join(thrown.artifactDir,'observation.json'),'utf8'));
expect(saved.state).toBe('threw');expect(saved.error).toBe('controlled caller interruption');
expect(saved.pendingWriteInputs).toEqual([expected]);expect(fs.existsSync(f.recorder.file)).toBe(false);
expect(saved.publicTools).toEqual([{sessionId:f.sid,timestamp:published.timestamp,toolUseId:f.id,kind:'use',name:'Write',input:f.input}]);
expect(fs.existsSync(f.sidecar)).toBe(false);expect(fs.existsSync(f.journal)).toBe(false);
expect(fs.readFileSync(path.join(thrown.artifactDir,'terminal.screen.log'),'utf8')).toBe(f.screen);
expect(fs.statSync(path.join(thrown.artifactDir,'observation.json')).mode&0o777).toBe(0o600);
}finally{f.close();fs.rmSync(evalDir,{recursive:true,force:true});}
});
// CI 39161502626 slice 6: the parent ran four tools, then its first Write asked to
// create the owned working plan before any assistant text or question existed.
function toolOnlyFixture(){
const root=fs.mkdtempSync(path.join(os.tmpdir(),'gstack-toolonly-'));const cwd=path.join(root,'owned'),config=path.join(root,'config');fs.mkdirSync(cwd);
const expected=path.join(cwd,toolOnlyCreate.basename),sid=toolOnlyCreate.sessionId,id=toolOnlyCreate.pendingId.slice(sid.length+1);
const journal=path.join(config,'projects','owned',sid+'.jsonl');fs.mkdirSync(path.dirname(journal),{recursive:true});
const startedAt=Date.now()-1000;const recorder=createFilePermissionRecorder(cwd,config,expected)!;
const native=(role:string,content:any[],session=sid)=>({cwd,sessionId:session,isSidechain:false,timestamp:new Date().toISOString(),message:{role,content}});
const tools=(session=sid)=>toolOnlyCreate.publicTools.map(event=>event.kind==='use'
? native('assistant',[{type:'tool_use',id:event.toolUseId,name:event.name,input:event.name==='Read'?{file_path:path.join(cwd,'PLAN.md')}:{command:'true'}}],session)
: native('user',[{type:'tool_result',tool_use_id:event.toolUseId,content:'ok'}],session));
const write=(rows:any[],file=journal)=>{fs.mkdirSync(path.dirname(file),{recursive:true});fs.writeFileSync(file,rows.map(r=>JSON.stringify(r)).join('\n')+'\n');};
write(tools());
recordFilePermission(JSON.stringify({hook_event_name:'PreToolUse',tool_name:'Write',session_id:sid,tool_use_id:id,cwd,transcript_path:journal,
tool_input:{file_path:expected,content:toolOnlyCreate.writeContent}}),recorder.file,cwd,config,expected);
const read=(screen=toolOnlyCreate.screen)=>currentFilePermissionEpoch(recorder.file,expected,cwd,config,startedAt,readPlanCountTranscript(config,cwd),screen);
return {root,cwd,config,sid,id,journal,native,tools,write,read,close(){recorder.dispose();fs.rmSync(root,{recursive:true,force:true});}};
}
test('a tool-only parent session authenticates its captured owned Create once',()=>{
const f=toolOnlyFixture();try{
expect(toolOnlyCreate.recorded).toMatchObject({outcome:'timeout',transcriptCalls:0,transcriptAssistantMessages:0});
const transcript=readPlanCountTranscript(f.config,f.cwd);
expect(transcript).toMatchObject({status:'ready',calls:[],assistantMessages:[]});
expect(f.read()?.pendingId).toBe(toolOnlyCreate.pendingId);
const guard=createPlanCountPermissionGuard();
expect(guard(toolOnlyCreate.screen,'',f.read())).toBe('grant');
expect(guard(toolOnlyCreate.screen,'',f.read())).toBe('handled');
}finally{f.close();}
});
test('tool-only identity still requires exactly one parent session that owns the pending Write',()=>{
const f=toolOnlyFixture();try{
const foreign='11111111-2222-4333-8444-555555555555',foreignJournal=path.join(f.config,'projects','owned',foreign+'.jsonl');
f.write(f.tools(foreign),foreignJournal);
expect(f.read()).toBeNull();
f.write([]);
expect(f.read()).toBeNull();
fs.rmSync(foreignJournal);
expect(f.read()).toBeNull();
f.write([...f.tools(),f.native('assistant',[{type:'tool_use',id:'other-write',name:'Write',input:{file_path:path.join(f.cwd,'other.md'),content:'x'}}])]);
expect(f.read()).toBeNull();
}finally{f.close();}
});
test('a tool-only session gets no credit for a changed preview or a foreign create prompt',()=>{
const f=toolOnlyFixture();try{
expect(f.read(toolOnlyCreate.screen.replace('Validate first (recommended)','Validate later (recommended)'))).toBeNull();
expect(f.read(toolOnlyCreate.screen.replaceAll(toolOnlyCreate.basename,'foreign-plan.md'))).toBeUndefined();
}finally{f.close();}
});