From 805812d67ebe65fc0dd57be64dac10eea4f78885 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 11 Sep 2026 14:31:56 -0500 Subject: [PATCH] Accept persisted descriptive output documents in repository handoff E2E --- tests/runner-e2e/chat-flow.test.ts | 29 +++++++++++++++++ tests/runner-e2e/chat-flow.ts | 50 ++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/tests/runner-e2e/chat-flow.test.ts b/tests/runner-e2e/chat-flow.test.ts index 8f290cac4a..cb4ac2ca0d 100644 --- a/tests/runner-e2e/chat-flow.test.ts +++ b/tests/runner-e2e/chat-flow.test.ts @@ -8,6 +8,7 @@ import { chatTaskCompletionFailure, collectChatRunEvidence, readRunningChatLog, + readChatOutputDocument, isResetRun, type ChatIssue, type ChatRun, @@ -105,6 +106,34 @@ describe("chat acceptance contracts", () => { assertChatTaskHandoff({ ...task, projectId: null }, [run], source), ).toThrow(); }); + it("finds a committed descriptive output document without accepting a copied plan or a claim", async () => { + const output = { + ...plan, + id: "description-doc", + issueId: "work", + key: "project-description", + body: "A completed description with CHAT123.", + createdByAgentId: "agent", + }; + const get = vi.fn(async (path: string) => { + if (path === "/api/issues/work/documents") + return [{ key: "plan" }, { key: "project-description" }]; + if (path === "/api/issues/work/documents/project-description") + return output; + throw new Error(`Unexpected document read: ${path}`); + }); + const api = { get } as Pick; + await expect(readChatOutputDocument(api, "work", "CHAT123")).resolves.toBe( + output, + ); + await expect(readChatOutputDocument(api, "work", "WRONG123")).rejects.toThrow( + "no non-plan output document", + ); + get.mockImplementation(async () => [{ key: "plan" }]); + await expect(readChatOutputDocument(api, "work", "CHAT123")).rejects.toThrow( + "document keys: plan", + ); + }); it("uses durable free-text labels, multi-selection, and the supplied submit label", () => { const payload: AskUserQuestionsPayload = { version: 1, diff --git a/tests/runner-e2e/chat-flow.ts b/tests/runner-e2e/chat-flow.ts index f1f28dbe97..d13dc0132b 100644 --- a/tests/runner-e2e/chat-flow.ts +++ b/tests/runner-e2e/chat-flow.ts @@ -46,6 +46,38 @@ type Comment = { conversationSessionGeneration?: number; }; type Plan = { body: string; latestRevisionId: string; updatedAt: string }; +type ChatOutputDocument = Plan & { id: string; issueId: string; key: string }; + +/** A requested output document may have a descriptive key; a copied plan is not output. */ +export async function readChatOutputDocument( + api: Pick, + issueId: string, + marker: string, +): Promise { + const summaries = await api.get>( + `/api/issues/${issueId}/documents`, + ); + const documents = await Promise.all( + summaries + .filter((document) => document.key !== "plan") + .map((document) => + api.get( + `/api/issues/${issueId}/documents/${encodeURIComponent(document.key)}`, + ), + ), + ); + const output = documents.find( + (document) => document.issueId === issueId && document.body.includes(marker), + ); + if (!output) + throw new Error( + `Execution task ${issueId} has no non-plan output document containing ${marker}; document keys: ${summaries.map((document) => document.key).join(", ") || "none"}`, + ); + expect(output.id).toBeTruthy(); + expect(output.latestRevisionId).toBeTruthy(); + return output; +} + export const isResetRun = (run: ChatRun) => run.contextSnapshot?.conversationReset === true || run.resultJson?.conversationReset === true; @@ -610,10 +642,22 @@ export async function runChatFlow(input: { acceptedPlan!.latestRevisionId, ); } else assertChatTaskHandoff(child, taskRuns, issue!); - const output = await api.get( - `/api/issues/${child.id}/documents/output`, - ); + const output = + caseId === "multi-repository" + ? await readChatOutputDocument(api, child.id, marker) + : await api.get(`/api/issues/${child.id}/documents/output`); expect(output.body).toContain(marker); + if (caseId === "multi-repository") { + const outputKey = (output as ChatOutputDocument).key; + await input.evidence("chat-execution-output.json", { + taskId: child.id, + document: output, + revisions: await api.get( + `/api/issues/${child.id}/documents/${encodeURIComponent(outputKey)}/revisions`, + ), + executionRunIds: taskRuns.map((run) => run.id), + }); + } expect( (await comments()) .filter((c) => c.authorAgentId)