Files
gstack/test/plan-edit-cropped-permission.test.ts
Garry Tanandcapy-ai[bot] 5de607f26c v1.91.170.0 fix(autoplan): guard waits for an unflushed journal; plan floor harness reads cropped menus; office-hours reviewer recovers with Write (GSTA-57) (#3326)
* fix(autoplan,evals): guard waits for an unflushed journal; floor harness reads cropped menus; office-hours reviewer recovers with Write (GSTA-57)

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

* v1.91.170.0 stamp

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-20 19:02:57 +00:00

251 lines
18 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,currentFilePermissionEpoch,currentFilePermissionBinding,recordFilePermission,isCroppedEditPermissionVisible} from './helpers/plan-count-file-permission';
import {readPlanCountTranscript} from './helpers/plan-count-transcript';
import {createPlanCountPermissionGuard,isPermissionDialogVisible,classifyPlanCountFrame} from './helpers/claude-pty-runner';
import captured from './fixtures/plan-edit-cropped-permission-1579.json';
import editPrepublication from './fixtures/plan-ceo-floor-edit-prepublication.json';
function fixture(){
const dir=fs.mkdtempSync(path.join(os.tmpdir(),'cropped-edit-')),cwd=path.join(dir,'owned'),config=path.join(dir,'config');fs.mkdirSync(cwd);
const expected=path.join(cwd,path.basename(captured.hook.expected)),sid=captured.hook.sessionId,id=captured.pendingEdit.toolUseId;
const journal=path.join(config,'projects','owned',sid+'.jsonl');fs.mkdirSync(path.dirname(journal),{recursive:true});
const recorder=createFilePermissionRecorder(cwd,config,expected)!;
const state={...captured.hook,cwd,expected,transcriptPath:journal};
fs.writeFileSync(recorder.file,JSON.stringify(state));fs.writeFileSync(expected,captured.priorWrite.input.content);
const input={...captured.pendingEdit.input,file_path:expected},block={type:'tool_use',id,name:'Edit',input};
const native=(role:string,content:any[])=>({cwd,sessionId:sid,isSidechain:false,timestamp:captured.pendingEdit.timestamp,message:{role,content}});
const rows=[native('assistant',[{type:'text',text:'Updating the owned report.'},block])];
const write=(value:any[]=rows)=>fs.writeFileSync(journal,value.map(r=>JSON.stringify(r)).join('\n')+'\n');write();
const transcript=()=>readPlanCountTranscript(config,cwd);
const read=(screen=captured.screen)=>currentFilePermissionEpoch(recorder.file,expected,cwd,config,captured.commandStartedAt,transcript(),screen);
return {dir,cwd,config,expected,sid,id,journal,recorder,state,input,block,native,rows,write,read,transcript,
close(){recorder.dispose();fs.rmSync(dir,{recursive:true,force:true});}};
}
test('actual cropped ordinary Edit binds its current public request and grants only once',()=>{
const f=fixture();try{
expect(createHash('sha256').update(captured.screen).digest('hex')).toBe(captured.screenSha256);
expect(captured.pendingEdit.input.old_string).toBe('## Onboarding flow (under review, as supplied)');
expect(f.read()?.pendingId).toBe(captured.hook.pendingId);
const owned=currentFilePermissionBinding([{file:f.recorder.file,expected:f.expected}],f.cwd,f.config,captured.commandStartedAt,f.transcript(),captured.screen);
expect(owned?.epoch.pendingId).toBe(captured.hook.pendingId);
const guard=createPlanCountPermissionGuard();expect(guard(captured.screen,'',f.read())).toBe('grant');
expect(guard(captured.screen,'',f.read())).toBe('handled');
}finally{f.close();}
});
test('the new cropped class never becomes an unowned generic permission grant',()=>{
expect(isPermissionDialogVisible(captured.screen)).toBe(false);
expect(classifyPlanCountFrame(captured.screen)).not.toBe('permission');
expect(createPlanCountPermissionGuard()(captured.screen)).not.toBe('grant');
expect(createPlanCountPermissionGuard()(captured.screen,'',null)).not.toBe('grant');
});
test.each(['pending','already-applied'])('a partial added-row crop retains the current Edit location: %s',mode=>{
const f=fixture();try{
const start=captured.screen.indexOf(' 40 +'),end=captured.screen.indexOf(' 81 +');
const footer=captured.screen.slice(captured.screen.lastIndexOf('\n╌'));
const screen=captured.screen.slice(start,end)+footer;
if(mode==='already-applied')fs.writeFileSync(f.expected,captured.priorWrite.input.content.replace(f.input.old_string,f.input.new_string));
if(mode==='already-applied')expect(f.read(screen)).toBeNull();
else expect(f.read(screen)?.pendingId).toBe(f.state.pendingId);
}finally{f.close();}
});
for(const [index,line] of captured.screen.split('\n').entries())if(/^ {4,5}[+\-]/.test(line))
test(`the captured wrapped diff crop at viewport row ${index+1} binds its complete visible suffix`,()=>{
const f=fixture();try{
const screen=captured.screen.split('\n').slice(index).join('\n');
expect(f.read(screen)?.pendingId).toBe(f.state.pendingId);
expect(createPlanCountPermissionGuard()(screen,'',f.read(screen))).toBe('grant');
expect(f.read(screen.replace(line,line+' changed'))).toBeNull();
}finally{f.close();}
});
test.each(['crlf','without-hint','wrapped-menu'])(`owned ordinary Edit supports native %s layout`,mode=>{
const f=fixture();try{let screen=captured.screen;
if(mode==='crlf')screen=screen.replaceAll('\n','\r\n');
if(mode==='without-hint')screen=screen.replace(' (shift+tab)','');
if(mode==='wrapped-menu')screen=screen.replace('file edits and common file commands','file edits and\n common file commands');
expect(f.read(screen)?.pendingId).toBe(f.state.pendingId);
expect(createPlanCountPermissionGuard()(screen,'',f.read(screen))).toBe('grant');
}finally{f.close();}
});
test.each(['missing-journal','wrong-session','foreign-path','foreign-id','wrong-tool','completed','failed','completed-replay','ambiguous','conflicting-use','changed-old','changed-new'])(`crop cannot override %s native evidence`,mode=>{
const f=fixture();try{const block=structuredClone(f.block),rows:any[]=[f.native('assistant',[{type:'text',text:'Updating.'},block])];
if(mode==='wrong-session')rows[0].sessionId='foreign';
if(mode==='foreign-path')block.input.file_path=path.join(f.dir,path.basename(f.expected));
if(mode==='foreign-id')block.id='other';
if(mode==='wrong-tool')block.name='Write';
if(mode==='changed-old')block.input.old_string='Different current source';
if(mode==='changed-new')block.input.new_string=block.input.new_string.replace('empathy narrative','different narrative');
if(['completed','failed','completed-replay'].includes(mode))rows.push(f.native('user',[{type:'tool_result',tool_use_id:f.id,content:'done',is_error:mode==='failed'}]));
if(mode==='completed-replay')rows.push(f.native('assistant',[f.block]));
if(mode==='ambiguous')rows.push(f.native('assistant',[{...f.block,id:'other'}]));
if(mode==='conflicting-use')rows.unshift(f.native('assistant',[{...f.block,input:{...f.input,new_string:'Different input'}}]));
f.write(rows);if(mode==='missing-journal')fs.unlinkSync(f.journal);
expect(f.read()).toBeNull();expect(createPlanCountPermissionGuard()(captured.screen,'',f.read())).not.toBe('grant');
}finally{f.close();}
});
test.each(['foreign-cwd','foreign-config','completed-epoch','stale','future','linked-state','linked-target','linked-parent','different-source','ambiguous-source'])(`cropped Edit rejects %s ownership`,mode=>{
const f=fixture();try{let state:any={...f.state};
if(mode==='foreign-cwd')state.cwd=f.dir;
if(mode==='foreign-config')state.transcriptPath=path.join(f.dir,'foreign','projects','owned',f.sid+'.jsonl');
if(mode==='completed-epoch')state.completedId=state.pendingId;
if(mode==='stale')state.timestamp=new Date(captured.commandStartedAt-1).toISOString();
if(mode==='future')state.timestamp=new Date(Date.now()+60000).toISOString();
fs.writeFileSync(f.recorder.file,JSON.stringify(state));
if(mode==='linked-state'){fs.renameSync(f.recorder.file,f.recorder.file+'.real');fs.symlinkSync(f.recorder.file+'.real',f.recorder.file);}
if(mode==='linked-target'){fs.renameSync(f.expected,f.expected+'.real');fs.symlinkSync(f.expected+'.real',f.expected);}
if(mode==='linked-parent'){fs.renameSync(f.cwd,f.cwd+'.real');fs.symlinkSync(f.cwd+'.real',f.cwd);}
if(mode==='different-source')fs.writeFileSync(f.expected,'Different source.');
if(mode==='ambiguous-source')fs.appendFileSync(f.expected,'\n'+f.input.old_string);
expect(f.read()).toBeNull();
}finally{f.close();}
});
test.each(['foreign-basename','foreign-header','quoted','fenced','auq','old-menu','missing-footer','selected-other','changed-action','added-choice','changed-preview','wrong-number','partial-footer','plain-prose'])(`cropped Edit rejects %s viewport`,mode=>{
const f=fixture();try{let screen=captured.screen;
if(mode==='foreign-basename')screen=screen.replace(path.basename(f.expected),'foreign.md');
if(mode==='foreign-header')screen='/foreign/'+path.basename(f.expected)+'\n'+screen;
if(mode==='quoted')screen=screen.split('\n').map(l=>'> '+l).join('\n');
if(mode==='fenced')screen='```text\n'+screen;
if(mode==='auq')screen='☐ Review\n'+screen;
if(mode==='old-menu')screen='❯ 1. Old\n 2. Other\n'+screen;
if(mode==='missing-footer')screen=screen.replace('Esc to cancel · Tab to amend','');
if(mode==='selected-other')screen=screen.replace('❯ 1. Yes',' 1. Yes').replace(' 2.',' ❯ 2.');
if(mode==='changed-action')screen=screen.replace('1. Yes','1. Yes, approve all');
if(mode==='added-choice')screen=screen.replace(' 3. No',' 3. No\n 4. Other');
if(mode==='changed-preview')screen=screen.replace('empathy narrative','different narrative');
if(mode==='wrong-number')screen=screen.replace(' 30 +',' 31 +');
if(mode==='partial-footer')screen=screen.replace('Tab to amend','Tab to');
if(mode==='plain-prose')screen='Here is an example:\n'+screen;
expect(f.read(screen)).toBeFalsy();
expect(createPlanCountPermissionGuard()(screen,'',f.read(screen))).not.toBe('grant');
}finally{f.close();}
});
// Ship-measure plan-ceo-finding-floor baseline at 6f32737: after the owned Write was
// granted, t04 (three published Edits pending at once) and t07/t08 (Edit menu shown
// before Claude Code published the Edit) stalled on the owned working-plan Edit menu.
function edits(trial:string){
const c=editPrepublication.cases.find(c=>c.trial===trial)!;
const dir=fs.mkdtempSync(path.join(os.tmpdir(),'edit-prepub-')),cwd=path.join(dir,'owned'),config=path.join(dir,'config');fs.mkdirSync(cwd);
const expected=path.join(cwd,c.basename),sid=c.sessionId,journal=path.join(config,'projects','owned',sid+'.jsonl');
fs.mkdirSync(path.dirname(journal),{recursive:true});
const startedAt=Date.now()-1000,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 hook=(kind:string,tool:string,id:string,input:any,extra={})=>recordFilePermission(JSON.stringify({hook_event_name:kind,tool_name:tool,session_id:sid,
tool_use_id:id,cwd,transcript_path:journal,tool_input:input,...extra}),recorder.file,cwd,config,expected);
const writeInput={file_path:expected,content:c.writeContent};
const base=[native('assistant',[{type:'tool_use',id:'owned-write',name:'Write',input:writeInput}]),
native('user',[{type:'tool_result',tool_use_id:'owned-write',content:'ok'}])];
hook('PreToolUse','Write','owned-write',writeInput);fs.writeFileSync(expected,c.writeContent);hook('PostToolUse','Write','owned-write',writeInput);
const write=(rows:any[]=base)=>fs.writeFileSync(journal,rows.map(r=>JSON.stringify(r)).join('\n')+'\n');write();
const pending=c.edits.map(e=>({id:e.toolUseId,input:{file_path:expected,...e.input}}));
const block=(e:{id:string;input:any})=>({type:'tool_use',id:e.id,name:'Edit',input:e.input});
const read=(screen=c.screen)=>currentFilePermissionEpoch(recorder.file,expected,cwd,config,startedAt,readPlanCountTranscript(config,cwd),screen);
return {c,dir,cwd,config,expected,sid,journal,recorder,native,hook,base,write,pending,block,read,startedAt,
close(){recorder.dispose();fs.rmSync(dir,{recursive:true,force:true});}};
}
test.each(['t07','t08'])('%s: an unpublished owned Edit is granted once from its own PreToolUse input',trial=>{
const f=edits(trial);try{
expect(f.c.recorded).toMatchObject({outcome:'permission_unanswered',transcriptStatus:'ready',publishedEdits:0});
expect(createHash('sha256').update(f.c.screen).digest('hex')).toBe(f.c.screenSha256);
expect(isCroppedEditPermissionVisible(f.c.screen)).toBe(true);
const [edit]=f.pending;expect(f.read()).toBeNull();
f.hook('PreToolUse','Edit',edit!.id,edit!.input);
expect(fs.readFileSync(f.recorder.file,'utf8')).not.toContain(edit!.input.new_string);
expect(fs.statSync(f.recorder.file+'.edit.json').mode&0o777).toBe(0o600);
const owned=currentFilePermissionBinding([{file:f.recorder.file,expected:f.expected}],f.cwd,f.config,f.startedAt,readPlanCountTranscript(f.config,f.cwd),f.c.screen);
expect(owned?.epoch.pendingId).toBe(`${f.sid}:${edit!.id}`);
const guard=createPlanCountPermissionGuard();expect(guard(f.c.screen,'',f.read())).toBe('grant');expect(guard(f.c.screen,'',f.read())).toBe('handled');
// Publication later must agree with the witness; completion retires it.
f.write([...f.base,f.native('assistant',[f.block(edit!)])]);expect(f.read()?.pendingId).toBe(`${f.sid}:${edit!.id}`);
f.write([...f.base,f.native('assistant',[f.block({...edit!,input:{...edit!.input,new_string:edit!.input.new_string+'\nhidden'}})])]);expect(f.read()).toBeNull();
f.write();f.hook('PostToolUse','Edit',edit!.id,edit!.input);
expect(fs.existsSync(f.recorder.file+'.edit.json')).toBe(false);expect(f.read()).toBeNull();
}finally{f.close();}
});
// Gate 39293281037 attempt 1: the viewport kept the bare cwd-relative file row above the diff rule.
test('ci-39293281037-a1: a crop headed by the owned cwd-relative file row is granted once from its PreToolUse input',()=>{
const f=edits('ci-39293281037-a1');try{
expect(f.c.recorded).toMatchObject({outcome:'timeout',transcriptStatus:'ready',publishedEdits:0});
expect(createHash('sha256').update(f.c.screen).digest('hex')).toBe(f.c.screenSha256);
expect(f.c.screen.split('\n')[0]).toBe(' gstack-test-plan-ceo-floor.md');
expect(isCroppedEditPermissionVisible(f.c.screen)).toBe(true);
const [edit]=f.pending;expect(f.read()).toBeNull();
f.hook('PreToolUse','Edit',edit!.id,edit!.input);
expect(f.read()?.pendingId).toBe(`${f.sid}:${edit!.id}`);
const guard=createPlanCountPermissionGuard();expect(guard(f.c.screen,'',f.read())).toBe('grant');expect(guard(f.c.screen,'',f.read())).toBe('handled');
expect(isPermissionDialogVisible(f.c.screen)).toBe(false);
}finally{f.close();}
});
test.each(['foreign-directory','foreign-basename','numbered-row','changed-row','unwitnessed'])('ci-39293281037-a1 header negative control: %s is refused',mode=>{
const f=edits('ci-39293281037-a1');try{
const [edit]=f.pending;let screen=f.c.screen;
if(mode!=='unwitnessed')f.hook('PreToolUse','Edit',edit!.id,edit!.input);
if(mode==='foreign-directory')screen=screen.replace(' gstack-test-plan-ceo-floor.md\n',' other/gstack-test-plan-ceo-floor.md\n');
if(mode==='foreign-basename')screen=screen.replace(' gstack-test-plan-ceo-floor.md\n',' notes.md\n');
if(mode==='numbered-row')screen=screen.replace(' gstack-test-plan-ceo-floor.md\n',' 58 gstack-test-plan-ceo-floor.md\n');
if(mode==='changed-row')screen=screen.replace('Header: Premise','Header: Pricing');
expect(f.read(screen)).toBeFalsy();
expect(createPlanCountPermissionGuard()(screen,'',f.read(screen))).not.toBe('grant');
}finally{f.close();}
});
test.each(['before','after'])('t04: the menu binds to the one of three pending owned Edits its diff shows (later hooks %s the menu)',order=>{
const f=edits('t04');try{
expect(f.c.recorded).toMatchObject({outcome:'permission_unanswered',publishedEdits:3});
f.write([...f.base,f.native('assistant',f.pending.map(f.block))]);
for(const e of order==='before'?f.pending:f.pending.slice(0,1))f.hook('PreToolUse','Edit',e.id,e.input);
const [shown,...rest]=f.pending;
expect(f.read()?.pendingId).toBe(`${f.sid}:${shown!.id}`);
const guard=createPlanCountPermissionGuard();expect(guard(f.c.screen,'',f.read())).toBe('grant');expect(guard(f.c.screen,'',f.read())).toBe('handled');
// After the shown Edit lands, its stale render never binds to the other two.
const before=fs.readFileSync(f.expected,'utf8');
fs.writeFileSync(f.expected,before.replace(shown!.input.old_string,shown!.input.new_string));
f.hook('PostToolUse','Edit',shown!.id,shown!.input);
f.write([...f.base,f.native('assistant',f.pending.map(f.block)),f.native('user',[{type:'tool_result',tool_use_id:shown!.id,content:'ok'}])]);
if(order==='after')for(const e of rest)f.hook('PreToolUse','Edit',e.id,e.input);
expect(f.read()).toBeNull();
}finally{f.close();}
});
test.each(['foreign-file-published','foreign-file-hook','no-match','two-matches-witnessed','two-matches-published','unwitnessed','foreign-session','foreign-session-journal','changed-witness','sidechain'])
('owned Edit menu negative control: %s is refused',mode=>{
const f=edits(mode.includes('published')||mode==='unwitnessed'?'t04':'t07');try{
const [edit]=f.pending;let screen=f.c.screen;
if(mode==='foreign-file-published'){
f.write([...f.base,f.native('assistant',[...f.pending.map(f.block),f.block({id:'foreign-edit',input:{...edit!.input,file_path:path.join(f.dir,'foreign.md')}})])]);
for(const e of f.pending)f.hook('PreToolUse','Edit',e.id,e.input);
}
if(mode==='foreign-file-hook')f.hook('PreToolUse','Edit',edit!.id,{...edit!.input,file_path:path.join(f.dir,f.c.basename)});
if(mode==='no-match'){f.hook('PreToolUse','Edit',edit!.id,edit!.input);screen=screen.replace('Recommendation: B','Recommendation: A');}
if(mode==='two-matches-witnessed'){f.hook('PreToolUse','Edit',edit!.id,edit!.input);f.hook('PreToolUse','Edit','twin-edit',edit!.input);}
if(mode==='two-matches-published'){
f.write([...f.base,f.native('assistant',[...f.pending.map(f.block),f.block({id:'twin-edit',input:edit!.input})])]);
for(const e of f.pending)f.hook('PreToolUse','Edit',e.id,e.input);
}
if(mode==='unwitnessed'){f.write([...f.base,f.native('assistant',f.pending.map(f.block))]);for(const e of f.pending.slice(1))f.hook('PreToolUse','Edit',e.id,e.input);}
if(mode==='foreign-session'||mode==='foreign-session-journal'){
const foreign='11111111-2222-4333-8444-555555555555',journal=path.join(f.config,'projects','owned',foreign+'.jsonl');
if(mode==='foreign-session-journal')fs.writeFileSync(journal,JSON.stringify(f.native('assistant',[f.block(edit!)],foreign))+'\n');
f.hook('PreToolUse','Edit',edit!.id,edit!.input,{session_id:foreign,transcript_path:journal});
}
if(mode==='changed-witness'){f.hook('PreToolUse','Edit',edit!.id,edit!.input);fs.appendFileSync(f.recorder.file+'.edit.json',' ');}
if(mode==='sidechain')f.hook('PreToolUse','Edit',edit!.id,edit!.input,{agent_id:'helper'});
expect(f.read(screen)).toBeFalsy();
expect(createPlanCountPermissionGuard()(screen,'',f.read(screen))).not.toBe('grant');
}finally{f.close();}
});