diff --git a/tests/runner-e2e/chat-flow.test.ts b/tests/runner-e2e/chat-flow.test.ts index bbe41379f84..f6ef2e024ed 100644 --- a/tests/runner-e2e/chat-flow.test.ts +++ b/tests/runner-e2e/chat-flow.test.ts @@ -307,6 +307,19 @@ If you only have the club name, audience, and tone, that is enough to begin; I c await expect(collectChatRunEvidence({ get }, { ...suppressed, ...patch })).rejects.toThrow("Run log not found"); } }); + it("retains a wake retired by a terminal issue before admission without requesting a log it never had", async () => { + const get = vi.fn().mockResolvedValue([{ seq: 1, eventType: "lifecycle" }]); + const retired = { ...run, status: "cancelled", errorCode: "issue_terminal_status", startedAt: null, logStore: null, logRef: null }; + expect(await collectChatRunEvidence({ get }, retired)).toMatchObject({ + runId: run.id, log: null, logOmissionReason: "issue_terminal_before_start", events: [{ seq: 1, eventType: "lifecycle" }], + }); + expect(get.mock.calls).toEqual([[`/api/heartbeat-runs/${run.id}/events?afterSeq=0&limit=1000`]]); + get.mockRejectedValue(new Error("Run log not found")); + for (const patch of [{ startedAt: "2026-10-26T10:00:15Z" }, { startedAt: undefined }, { status: "failed" }, + { errorCode: "cancelled" }, { logStore: "local_file" }, { logRef: "recorded-log" }]) { + await expect(collectChatRunEvidence({ get }, { ...retired, ...patch })).rejects.toThrow("Run log not found"); + } + }); it("records legitimate pre-provider log absence only with explicit bootstrap and storage evidence", async () => { const stopped = { ...run, status: "interrupted", runtimeMode: "legacy", runtimeModeResolvedAt: null, logStore: null, logRef: null, resultJson: { executionRecovery: { kind: "bootstrap", providerWorkStarted: false } } }; diff --git a/tests/runner-e2e/chat-flow.ts b/tests/runner-e2e/chat-flow.ts index fb521b002e7..2e2c7daf7a2 100644 --- a/tests/runner-e2e/chat-flow.ts +++ b/tests/runner-e2e/chat-flow.ts @@ -319,12 +319,16 @@ export async function collectChatRunEvidence( run.logStore === null && run.logRef === null && ["failed", "interrupted"].includes(run.status) && recovery?.kind === "bootstrap" && recovery.providerWorkStarted === false; const reassignedBeforeStart = isReassignedUnstartedWake(run); + const terminalBeforeStart = run.status === "cancelled" && run.errorCode === "issue_terminal_status" && + run.startedAt === null && run.logStore === null && run.logRef === null; return { runId: run.id, ...(providerNeverStarted ? { logOmissionReason: "provider_not_started" } : {}), ...(admissionWait ? { logOmissionReason: "pre_dispatch_execution_wait" } : {}), ...(reassignedBeforeStart ? { logOmissionReason: "assignee_changed_before_start" } : {}), + ...(terminalBeforeStart ? { logOmissionReason: "issue_terminal_before_start" } : {}), log: isResetRun(run) || isBlockedUnstartedWake({ ...run }) || providerNeverStarted || admissionWait || reassignedBeforeStart + || terminalBeforeStart ? null : await api.get(`/api/heartbeat-runs/${run.id}/log?limitBytes=1048576`), events: admissionEvents ?? await collectRunEvents((afterSeq, limit) =>