test(evals): preserve distinct same-title task fixtures

This commit is contained in:
Dotta committed 2026-10-27 04:52:37 -05:00
1 parent f6d1e0dec1
commit 151488bf8c
4 files changed
+31 -3

No files matched your search

@@ -0,0 +1,9 @@
import { expect, it } from "vitest";
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
import { verifyDistinctTaskFixtures } from "./internal-distinct-fixtures.js";
const originals = () => [{ id: "launch-task", companyId: "company", title: "Landing page", projectId: "launch", status: "backlog" },
{ id: "experiment-task", companyId: "company", title: "Landing page", projectId: "experiments", status: "backlog" }];
function context(rows = originals()) { return { input: { api: { get: async (path: string) => rows.find(row => path.endsWith(`/${row.id}`)) }, fixtures: { company: { id: "company" } }, evidence: async () => undefined } } as unknown as ChatCatalogContext; }
it("accepts genuinely distinct saved same-title tasks in the declared projects", async () => expect(await verifyDistinctTaskFixtures(context(), originals())).toEqual(originals()));
it("rejects public-create title reuse before sending the canonical query", async () => { const rows = originals(); rows[1] = rows[0]!; await expect(verifyDistinctTaskFixtures(context(rows), rows)).rejects.toThrow(/aliased/); });
it("rejects a saved task in the wrong project or company", async () => { const rows = originals(); rows[1]!.projectId = "launch"; await expect(verifyDistinctTaskFixtures(context(rows), originals())).rejects.toThrow(/project/); const foreign = originals(); foreign[1]!.companyId = "foreign"; await expect(verifyDistinctTaskFixtures(context(foreign), originals())).rejects.toThrow(/identity/); });
@@ -0,0 +1,13 @@
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
import { type Entity, sameTask } from "./internal-support.js";
/** Same-title tasks are intentional context here, not repeated creation requests. */
export async function verifyDistinctTaskFixtures(ctx: ChatCatalogContext, expected: readonly Entity[]) {
if (new Set(expected.map(row => row.id)).size !== expected.length) throw new Error("Distinct prerequisite tasks aliased to one public-create result");
const saved = await Promise.all(expected.map(row => ctx.input.api.get<Entity>(`/api/issues/${row.id}`)));
if (!saved.every((row, index) => row.companyId === ctx.input.fixtures.company.id && sameTask(expected[index]!, row)))
throw new Error("Saved prerequisite task identity, title or project differs from the declared context");
await ctx.input.evidence("chat-catalog-distinct-prerequisite-tasks.json", { companyId: ctx.input.fixtures.company.id,
tasks: saved.map(row => ({ id: row.id, title: row.title, projectId: row.projectId, status: row.status, priority: row.priority })) });
return saved;
}
@@ -1,4 +1,5 @@
import { createHash } from "node:crypto";
import { verifyDistinctTaskFixtures } from "./internal-distinct-fixtures.js";
import type { ChatCatalogCase, ChatCatalogContext } from "../chat-catalog-contract.js";
import type { ChatRun, ChatIssue } from "../chat-flow.js";
import { dropChatSendAcknowledgement } from "../lost-send.js";
@@ -104,7 +105,8 @@ function creationDefinition(scenarioId: string): ChatCatalogCase {
if (scenarioId === "projects.project-work") {
priorTasks.push(await task(ctx, "Signup copy", p.id));
priorTasks.push(await task(ctx, "Product brief", p.id, { status: "done" }));
const other = await project(ctx, "Experiments"); priorTasks.push(await task(ctx, "Signup copy", other.id));
const other = await project(ctx, "Experiments"); priorTasks.push(await task(ctx, "Signup copy", other.id, { allowDuplicate: true }));
await verifyDistinctTaskFixtures(ctx, priorTasks);
}
}
if (scenarioId === "tasks.website-without-project") interception = await dropChatSendAcknowledgement(ctx.input.page, ctx.scenario.query);
@@ -1,4 +1,5 @@
import { withEditOwnership } from "./internal-edit-ownership.js";
import { verifyDistinctTaskFixtures } from "./internal-distinct-fixtures.js";
import { delegatedTaskCases } from "./internal-task-creation.js";
import { taskPrivacyCases } from "./internal-privacy.js";
import { taskInventoryCases } from "./internal-task-inventory.js";
@@ -102,10 +103,13 @@ export const tasksCases: readonly ChatCatalogCase[] = [
function ambiguousTask(): ChatCatalogCase {
const base = define("tasks.ambiguous-task", async ctx => {
const p = await project(ctx); const otherProject = await project(ctx, "Experiments");
const t = await task(ctx, "Landing page", p.id); const otherTask = await task(ctx, "Landing page", otherProject.id);
const t = await task(ctx, "Landing page", p.id);
// Public creation defaults to company-wide recent-title reuse, regardless of project.
const otherTask = await task(ctx, "Landing page", otherProject.id, { allowDuplicate: true });
await verifyDistinctTaskFixtures(ctx, [t, otherTask]);
return { project: p, task: t, otherProject, otherTask };
});
return { ...base, expectedRunCount: 2, expectedHumanMessageCount: 2, attemptTimeoutMs: 420_000,
return { ...base, sourceFiles: [...base.sourceFiles, "chat-catalog-domains/internal-distinct-fixtures.ts"], expectedRunCount: 2, expectedHumanMessageCount: 2, attemptTimeoutMs: 420_000,
async act(ctx, state) {
const first = await pollUntil({ label: "Task ambiguity clarification", deadlineAt: Date.now() + 150_000,
load: () => observeInternal(ctx, state), accept: o => o.runs.length === 1 && o.runs.every(row => row.status === "succeeded")