diff --git a/tests/runner-e2e/chat-catalog-domains/external-assistant-revoke.test.ts b/tests/runner-e2e/chat-catalog-domains/external-assistant-revoke.test.ts index 6d34a5d76fe..4d5ca87beab 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-assistant-revoke.test.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-assistant-revoke.test.ts @@ -29,6 +29,10 @@ it("accepts any wording once the saved outcome is right, since reply text is not for(const reply of ["Only you can do this from Assistant connections. Revoke Old test assistant there; the website task keeps running.","I can't disconnect assistants for you. Use the page linked here; Morgan's website work stays assigned."]){const o=valid();o.reply=reply;o.phaseReplies=[{runId:"ceo-run",body:reply}];expect(gradePersonalRevoke(o).passed).toBe(true);} expect(gradePersonalRevoke(undefined).passed).toBe(false); }); +it("accepts the native status committer's done transition from the same worker run",()=>{ + const o=valid();o.activity[0]={entityId:"website",action:"issue.updated",actorType:"system",agentId:null,runId:"worker-run",details:{source:"native_status_decision",toStatus:"done"}} as never;expect(gradePersonalRevoke(o).passed).toBe(true); + const p=valid();p.activity[0]={entityId:"website",action:"issue.updated",actorType:"system",agentId:null,runId:"replacement-run",details:{source:"native_status_decision",toStatus:"done"}} as never;expect(gradePersonalRevoke(p).passed).toBe(false); +}); it("rejects unsafe optional client labels before any OAuth/provider operation",async()=>{ await expect(beginConnection(true,undefined,false,"Old test assistant\nInjected")).rejects.toThrow("label"); await expect(beginConnection(true,undefined,false,"")).rejects.toThrow("label"); diff --git a/tests/runner-e2e/chat-catalog-domains/external-assistant-revoke.ts b/tests/runner-e2e/chat-catalog-domains/external-assistant-revoke.ts index d836de2cbea..88cf06bf7c7 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-assistant-revoke.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-assistant-revoke.ts @@ -36,7 +36,7 @@ export function gradePersonalRevoke(value:unknown){ c.add("correct-human-directed-revocation",o.state.modelDirectedPersonalUI===true&&typeof o.state.personalUiLink==="string"&&o.links.includes(o.state.personalUiLink)&&o.state.exactOldButtonClicked===true&&o.state.agentManagementStatus===401&&Array.isArray(after)&&after.some(row=>row.id===o.state.oldGrantId&&!!row.revokedAt)&&after.length===2&&after.some(row=>{const prior=before?.find(item=>item.id===o.state.otherGrantId);return row.id===o.state.otherGrantId&&!row.revokedAt&&!!prior&&row.companyId===prior.companyId&&row.clientName===prior.clientName&&JSON.stringify([...row.scopes].sort())===JSON.stringify([...prior.scopes].sort());})&&o.activity.some(row=>row.action==="mcp.connection_revoked"&&row.entityId===o.state.oldGrantId&&row.actorType==="user"&&row.actorId===o.state.personId),"The actual reply identifies the old client and preserves its delegated work; the signed-in person uses that exact public revoke control, while agent management stays denied."); c.add("revoked-client-denied-other-client-usable",o.state.oldCallAfterStatus===401&&o.state.oldRefreshRejected===true&&o.state.otherUsableAfter===true,"Fresh revoked access/refresh calls fail and the unrelated grant independently remains usable."); const final=o.state.taskAfter as Entity|undefined; - c.add("ordinary-task-lifecycle-survives-disconnect",!!task&&!!final&&final.id===task.id&&final.assigneeAgentId===task.assigneeAgentId&&final.status==="done"&&o.state.workerActiveAtRevocation===true&&o.state.humanAtRevocationId===o.state.personId&&o.state.sameWorkerStillRunningAfterRevocation===true&&o.state.activeAfterWindowMs===5000&&o.state.neutralGateReleased===true&&o.runs.filter(run=>run.contextSnapshot?.issueId===task.id).length===1&&o.runs.some(run=>run.id===o.state.workerRunId&&run.contextSnapshot?.issueId===task.id&&run.agentId===task.assigneeAgentId&&run.status==="succeeded")&&o.activity.some(row=>row.entityId===task.id&&row.action==="issue.updated"&&row.agentId===task.assigneeAgentId&&row.details?.status==="done")&&!o.activity.some(row=>row.entityId===task.id&&row.action==="issue.updated"&&row.agentId===o.leadId&&row.details?.status==="done"),"The same actual native worker remains running across revocation, then completes normally after a neutral fixture release; no cancellation, replacement or unrelated task substitutes."); + c.add("ordinary-task-lifecycle-survives-disconnect",!!task&&!!final&&final.id===task.id&&final.assigneeAgentId===task.assigneeAgentId&&final.status==="done"&&o.state.workerActiveAtRevocation===true&&o.state.humanAtRevocationId===o.state.personId&&o.state.sameWorkerStillRunningAfterRevocation===true&&o.state.activeAfterWindowMs===5000&&o.state.neutralGateReleased===true&&o.runs.filter(run=>run.contextSnapshot?.issueId===task.id).length===1&&o.runs.some(run=>run.id===o.state.workerRunId&&run.contextSnapshot?.issueId===task.id&&run.agentId===task.assigneeAgentId&&run.status==="succeeded")&&o.activity.some(row=>row.entityId===task.id&&row.action==="issue.updated"&&((row.agentId===task.assigneeAgentId&&row.details?.status==="done")||((row as Entity&{runId?:string|null}).runId===o.state.workerRunId&&row.details?.toStatus==="done")))&&!o.activity.some(row=>row.entityId===task.id&&row.action==="issue.updated"&&row.agentId===o.leadId&&row.details?.status==="done"),"The same actual native worker remains running across revocation, then completes normally after a neutral fixture release; no cancellation, replacement or unrelated task substitutes."); c.add("private-credentials-not-public-output",o.state.secretScanComplete===true&&o.state.secretLeaked===false,"Actual source/task documents, historical revisions, attachments, artifacts and raw result observations are scanned against private grant credentials before redaction.");return c.result(); } export const personalRevokeExternalCases=[definition("assistant-connections.revoke-personal-grant",async ctx=>{