From 3ea1a3374726f5aa9253eaa07f0c4f5dfd064cb0 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Wed, 11 Nov 2026 15:34:35 +0800 Subject: [PATCH] test(canvas): trace AI receipt protocol headers --- tests/headless/foundry-ai.integration.mjs | 2 +- tests/headless/foundry.integration.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/headless/foundry-ai.integration.mjs b/tests/headless/foundry-ai.integration.mjs index 222f780..4373310 100644 --- a/tests/headless/foundry-ai.integration.mjs +++ b/tests/headless/foundry-ai.integration.mjs @@ -24,7 +24,7 @@ export async function verifyFoundryAI({open,transport,rootId,imageId,assets,call await reopen();assert.equal(posts('/ai/runs').length,admissionCount);assert.equal(engine.getSnapshot().runtime.ledger.requests.get(admission.id).body,admission.body); assert.ok(calls.some(call=>call.method==='GET'&&call.path.endsWith('/ai/operations/'+admission.id))); const recovered=engine.getSnapshot().runtime.ledger.requests.get(admission.id); - assert.equal(recovered.phase,'resolved','AI admission recovery: '+JSON.stringify({phase:recovered.phase,error:recovered.error,receipt:engine.getSnapshot().runtime.receipts.get(admission.id)})); + assert.equal(recovered.phase,'resolved','AI admission recovery: '+JSON.stringify({phase:recovered.phase,error:recovered.error,requestId:admission.id,operationId:first.operationIds[0],responses:calls.filter(call=>call.path.endsWith('/ai/runs')||call.path.endsWith('/ai/operations/'+admission.id)).map(call=>({method:call.method,response:call.response}))})); const firstRun=byOperation(first.operationIds[0]);assert.ok(firstRun,'resolved admission must publish a RunRecord');assert.equal(firstRun.status,'queued'); assert.equal(engine.editor.dispatch({type:'element.geometry',target,patch:{x:77}}).status,'applied');await engine.settle(); await control('execute');await poll(); diff --git a/tests/headless/foundry.integration.mjs b/tests/headless/foundry.integration.mjs index 5a2a3eb..aa0515b 100644 --- a/tests/headless/foundry.integration.mjs +++ b/tests/headless/foundry.integration.mjs @@ -16,7 +16,7 @@ assert.ok(url&&project&&accountId&&workspaceId&&rootId&&token,'requires isolated const registry=createRegistry(builtinComponents()),basePath=`${url}/projects/${project}/`,calls=[]; let loseNextAck=false,loseNextAssetAck=false,loseNextAIAdmission=false,loseNextAIAction=false; const rawTransport=createFoundryTransport({basePath,accountId,isCurrentIdentity:()=>true,fetch:async(resource,init)=>{const headers=new Headers(init.headers);headers.set('Authorization',`Bearer ${token}`);return fetch(resource,{...init,headers});}}); -const transport=async input=>{calls.push({method:input.method,path:input.path,requestId:input.requestId,body:input.body});const result=await rawTransport(input);if(loseNextAIAdmission&&input.method==='POST'&&input.path.endsWith('/ai/runs')&&result.status===202){loseNextAIAdmission=false;throw Error('injected response loss after AI admission');}if(loseNextAIAction&&input.method==='POST'&&/\/(cancel|apply)$/.test(input.path)&&result.status===200){loseNextAIAction=false;throw Error('injected response loss after AI action');}if(loseNextAssetAck&&input.method==='POST'&&input.path.endsWith('/assets')&&result.status>=200&&result.status<300){loseNextAssetAck=false;throw Error('injected upload response loss');}if(loseNextAck&&input.method==='POST'&&result.status===200){loseNextAck=false;throw Error('injected response loss after server commit');}return result;}; +const transport=async input=>{const call={method:input.method,path:input.path,requestId:input.requestId,body:input.body};calls.push(call);const result=await rawTransport(input);if(input.path.includes('/canvas/ai/'))call.response={httpStatus:result.status,...Object.fromEntries(['protocol_version','request_id','operation_id','action','admission','status','error'].filter(key=>result.body?.[key]!==undefined).map(key=>[key,result.body[key]]))};if(loseNextAIAdmission&&input.method==='POST'&&input.path.endsWith('/ai/runs')&&result.status===202){loseNextAIAdmission=false;throw Error('injected response loss after AI admission');}if(loseNextAIAction&&input.method==='POST'&&/\/(cancel|apply)$/.test(input.path)&&result.status===200){loseNextAIAction=false;throw Error('injected response loss after AI action');}if(loseNextAssetAck&&input.method==='POST'&&input.path.endsWith('/assets')&&result.status>=200&&result.status<300){loseNextAssetAck=false;throw Error('injected upload response loss');}if(loseNextAck&&input.method==='POST'&&result.status===200){loseNextAck=false;throw Error('injected response loss after server commit');}return result;}; const path=`workspaces/${workspaceId}/materials/${rootId}/canvas`; const migration=await loadFoundryModel(transport,workspaceId,rootId,registry);assert.equal(migration.status,'migration-required'); const migrationId=randomUUID(),migrationBody=JSON.stringify({protocol_version:2,request_id:migrationId,expected_versions:Object.fromEntries(migration.sourceVersions)});