From 9bbdfdcb60c55440d02efb7232402440bce58fd1 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 11 Sep 2026 15:00:56 -0500 Subject: [PATCH] Verify revised plan outcomes and navigate cleanly after server restart --- tests/runner-e2e/chat-flow.test.ts | 8 ++++++++ tests/runner-e2e/chat-flow.ts | 26 +++++++++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/tests/runner-e2e/chat-flow.test.ts b/tests/runner-e2e/chat-flow.test.ts index 734cf771f2..1e7a715f8b 100644 --- a/tests/runner-e2e/chat-flow.test.ts +++ b/tests/runner-e2e/chat-flow.test.ts @@ -10,6 +10,7 @@ import { readRunningChatLog, readChatOutputDocument, isChatClarificationReply, + assertChatExecutionOutput, isResetRun, type ChatIssue, type ChatRun, @@ -56,6 +57,7 @@ describe("chat acceptance contracts", () => { isChatClarificationReply("Tell me the intended audience and format."), ).toBe(true); expect(isChatClarificationReply("Please share:")).toBe(false); + expect(isChatClarificationReply("Please share.")).toBe(false); expect( isChatClarificationReply("Asked the user clarifying questions about their club."), ).toBe(false); @@ -64,6 +66,12 @@ describe("chat acceptance contracts", () => { ).toBe(false); }); + it("rejects superseded plan requirements in executed output, independently of plan history", () => { + expect(() => assertChatExecutionOutput("Welcome CHAT123.", "CHAT123", "DRAFT123")).not.toThrow(); + expect(() => assertChatExecutionOutput("Welcome DRAFT123 and CHAT123.", "CHAT123", "DRAFT123")).toThrow(); + expect(() => assertChatExecutionOutput("Welcome DRAFT123.", "CHAT123", "DRAFT123")).toThrow(); + }); + it("keeps chat markers literal across rich-text and Markdown boundaries", () => { for (const prefix of ["CHAT", "DRAFT", "OLDCONTEXT"] as const) { expect(chatMarker(prefix, "abc123-1")).toBe(`${prefix}abc1231`); diff --git a/tests/runner-e2e/chat-flow.ts b/tests/runner-e2e/chat-flow.ts index d468c00f2a..69cba685f2 100644 --- a/tests/runner-e2e/chat-flow.ts +++ b/tests/runner-e2e/chat-flow.ts @@ -54,7 +54,16 @@ export function isChatClarificationReply(body: string): boolean { const request = body.match( /\b(?:please\s+(?:share|provide|clarify|confirm)|tell me|let me know)\b([\s\S]*)/i, ); - return Boolean(request?.[1].replace(/[\s:*-]/g, "")); + return Boolean(request && /[\p{L}\p{N}]/u.test(request[1])); +} + +export function assertChatExecutionOutput( + body: string, + marker: string, + supersededMarker?: string, +): void { + expect(body).toContain(marker); + if (supersededMarker) expect(body).not.toContain(supersededMarker); } /** A requested output document may have a descriptive key; a copied plan is not output. */ @@ -315,7 +324,10 @@ export async function runChatFlow(input: { ).toContain(secret); const count = runs.length; await input.restart(); - await page.reload({ waitUntil: "domcontentloaded", timeout: 60_000 }); + // Re-enter the canonical route after the server replaces its browser + // transport; reloading the stale document can target a detached page. + await page.goto(route, { waitUntil: "domcontentloaded", timeout: 60_000 }); + await expect(page.getByTestId("task-chat-composer-input")).toBeVisible(); await idle(2); expect(runs).toHaveLength(count); await turn( @@ -561,7 +573,8 @@ export async function runChatFlow(input: { `/api/issues/${issue!.id}/documents/plan`, ); expect(revised.body).toContain(marker); - expect(revised.body).not.toContain(draftMarker); + // A revision-history section may quote the superseded requirement. + // The executed output below must use only the accepted requirement. expect(revised.latestRevisionId).not.toBe(draft.latestRevisionId); acceptedPlan = revised; await noTasks(); @@ -642,7 +655,6 @@ export async function runChatFlow(input: { if (plan) { assertChatHandoff(child, plan, taskRuns, issue!); expect(plan.body).toContain(marker); - expect(plan.body).not.toContain(draftMarker); const sourcePlan = await api.get( `/api/issues/${issue!.id}/documents/plan`, ); @@ -652,7 +664,11 @@ export async function runChatFlow(input: { ); } else assertChatTaskHandoff(child, taskRuns, issue!); const output = await readChatOutputDocument(api, child.id, marker); - expect(output.body).toContain(marker); + assertChatExecutionOutput( + output.body, + marker, + caseId === "plan-handoff" ? draftMarker : undefined, + ); await input.evidence("chat-execution-output.json", { taskId: child.id, document: output,