fix(evals): consume search probe response bodies once

This commit is contained in:
Dotta committed 2026-10-27 07:44:09 -05:00
1 parent 8565913082
commit 643447b54d
4 files changed
+36 -2

No files matched your search

@@ -7,6 +7,7 @@ import type { CompanyPortabilityFileEntry } from "../../../packages/shared/src/t
import type { ChatCatalogCase } from "../chat-catalog-contract.js";
import { makeDefinition } from "./internal-definition.js";
import { mainPerson } from "./internal-privacy.js";
import { readSearchProbeResponse } from "./internal-search-response.js";
import { checks, entity, hasLink, observation, observeInternal, project, sameTask, type Entity, type InternalObservation } from "./internal-support.js";
const sha = (body: string | Buffer) => createHash("sha256").update(body).digest("hex");
interface HistoricalTask extends Entity { completedAt?: string | null }
@@ -118,7 +119,7 @@ function findArtifact(): ChatCatalogCase {
async observe(ctx, state): Promise<SearchObservation> {
if (!key) throw new Error("Historical search probe was not created during setup"); const o = await observeInternal(ctx, state); const file = state.finalAttachment as IssueAttachment;
const read = async (path: string) => { const url = new URL(path, ctx.input.api.baseURL); if (url.origin !== ctx.input.api.baseURL || !url.pathname.startsWith("/api/")) throw new Error("Historical probe escaped fixture API");
const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(15000), headers: { Authorization: `Bearer ${key!.token}` } }); const body: unknown = response.ok ? await response.json() : undefined; await response.body?.cancel(); return { status: response.status, body }; };
const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(15000), headers: { Authorization: `Bearer ${key!.token}` } }); return readSearchProbeResponse(response); };
const [currentSource, attachments, deliveryComments, bytes, search] = await Promise.all([
ctx.input.api.get<HistoricalTask>(`/api/issues/${entity(state, "task").id}`), ctx.input.api.get<IssueAttachment[]>(`/api/issues/${entity(state, "task").id}/attachments`),
ctx.input.api.get<Entity[]>(`/api/issues/${entity(state, "task").id}/comments?order=asc`), ctx.input.api.getAttachmentContent(file.id), read(`/api/companies/${o.companyId}/search?q=website`),
@@ -1,6 +1,7 @@
import type { ChatCatalogCase } from "../chat-catalog-contract.js";
import { makeDefinition } from "./internal-definition.js";
import { mainPerson } from "./internal-privacy.js";
import { readSearchProbeResponse } from "./internal-search-response.js";
import { checks, entity, hasLink, observation, observeInternal, project, putDocument, sameTask, task, type Entity, type InternalObservation } from "./internal-support.js";
interface DesignObservation extends InternalObservation {
@@ -66,7 +67,7 @@ function findDesign(): ChatCatalogCase {
const read = async (path: string) => {
const url = new URL(path, ctx.input.api.baseURL); if (url.origin !== ctx.input.api.baseURL || !url.pathname.startsWith("/api/")) throw new Error("Design probe escaped fixture API");
const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(15_000), headers: { Authorization: `Bearer ${key!.token}` } });
const body: unknown = response.ok ? await response.json() : null; await response.body?.cancel(); return { status: response.status, body };
return readSearchProbeResponse(response);
};
const o = await observeInternal(ctx, state); const hidden = entity(state, "privateTask"); const target = entity(state, "task");
const [privateRead, search] = await Promise.all([read(`/api/issues/${hidden.id}`), read(`/api/companies/${o.companyId}/search?q=launch%20website%20design`)]);
@@ -0,0 +1,24 @@
import { describe, expect, it, vi } from "vitest";
import { readSearchProbeResponse } from "./internal-search-response.js";
describe("actual search response body ownership", () => {
it("reproduces the retained locked-stream observer failure with a real Response", async () => {
const response = new Response(JSON.stringify({ results: [{ id: "source" }] }), { status: 200 });
await response.json(); expect(response.bodyUsed).toBe(true); expect(response.body?.locked).toBe(true);
await expect(response.body!.cancel()).rejects.toThrow("ReadableStream is locked");
});
it("returns actual parsed search bytes without cancelling an already consumed successful body", async () => {
const response = new Response(JSON.stringify({ results: [{ id: "source" }] }), { status: 200 });
const cancel = vi.spyOn(response.body!, "cancel");
expect(await readSearchProbeResponse(response)).toEqual({ status: 200, body: { results: [{ id: "source" }] } });
expect(cancel).not.toHaveBeenCalled(); expect(response.bodyUsed).toBe(true);
});
it.each([403, 404])("cancels an unread denied body, retaining actual %s and exposing no restricted bytes", async status => {
const response = new Response("Restricted source body", { status });
const cancel = vi.spyOn(response.body!, "cancel");
expect(await readSearchProbeResponse(response)).toEqual({ status, body: null });
expect(cancel).toHaveBeenCalledOnce(); expect(response.bodyUsed).toBe(true);
});
it("propagates invalid successful JSON instead of inventing empty search results", async () => {
await expect(readSearchProbeResponse(new Response("not JSON", { status: 200 }))).rejects.toThrow();
});
});
@@ -0,0 +1,8 @@
/** These two independently authenticated search probes consume a successful
* JSON body once. json() owns that stream reader; cancel() afterward throws
* on its locked stream. An unread denied body still needs cancellation. */
export async function readSearchProbeResponse(response: Response): Promise<{ status: number; body: unknown }> {
if (response.ok) return { status: response.status, body: await response.json() };
await response.body?.cancel();
return { status: response.status, body: null };
}