From 4ad1eb5beb1520274deb8ca1fe894359c7d4cb11 Mon Sep 17 00:00:00 2001 From: gensokyo-dev Date: Sat, 14 Nov 2026 11:13:01 +0000 Subject: [PATCH] fix(snapshot): locate entry diagnostics by collection and index Each snapshot entry is validated alone, so its schema issue path started at the entry. Prefix it with entries or workflow_entries and the entry index so the reason names the entry to repair, and match the header case's issue count without freezing the whole sentence. Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> --- .../binary/AnalysisSnapshotFiles.ts | 26 ++++++++++++++++--- .../analysisSnapshotDiagnostics.test.ts | 8 +++--- .../filesystem/analysisSnapshotInput.test.ts | 8 +++--- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/application/binary/AnalysisSnapshotFiles.ts b/src/application/binary/AnalysisSnapshotFiles.ts index c3144be396..4beb0b4685 100644 --- a/src/application/binary/AnalysisSnapshotFiles.ts +++ b/src/application/binary/AnalysisSnapshotFiles.ts @@ -90,7 +90,23 @@ export const readAnalysisSnapshot = async ( } }; +/** Locate an entry's schema issues within its snapshot collection. */ +const locateEntryFailure = ( + cause: unknown, + collection: string, + index: number, +): unknown => + cause instanceof z.ZodError + ? new z.ZodError( + cause.issues.map((issue) => ({ + ...issue, + path: [collection, index, ...issue.path], + })), + ) + : cause; + const snapshotEntryReader = (kind: "provider" | "workflow") => { + const collection = kind === "provider" ? "entries" : "workflow_entries"; let seen = false; let entries: PreparedSnapshotEntry[] = []; let failure: { readonly cause: unknown } | undefined; @@ -102,8 +118,12 @@ const snapshotEntryReader = (kind: "provider" | "workflow") => { failure = undefined; }, accept: (value) => { - if (failure === undefined) + if (failure !== undefined) return; + try { entries.push(prepareSnapshotEntry(value, kind)); + } catch (cause: unknown) { + throw locateEntryFailure(cause, collection, entries.length); + } }, reject: (cause) => { failure ??= { cause }; @@ -113,9 +133,7 @@ const snapshotEntryReader = (kind: "provider" | "workflow") => { reader, finish: (): PreparedSnapshotEntry[] => { if (!seen) - throw new TypeError( - `Analysis snapshot ${kind === "provider" ? "entries" : "workflow_entries"} is missing`, - ); + throw new TypeError(`Analysis snapshot ${collection} is missing`); if (failure !== undefined) throw failure.cause; return entries; }, diff --git a/tests/acceptance/analysisSnapshotDiagnostics.test.ts b/tests/acceptance/analysisSnapshotDiagnostics.test.ts index b9c8496a72..3b993bbb2e 100644 --- a/tests/acceptance/analysisSnapshotDiagnostics.test.ts +++ b/tests/acceptance/analysisSnapshotDiagnostics.test.ts @@ -24,8 +24,8 @@ cliTest( const call = await cli.sharedMcpCall!(); for (const [document, field] of [ [{ entries: [], workflow_entries: [] }, "target"], - [{ entries: [{}], workflow_entries: [] }, "query_id"], - [{ entries: [], workflow_entries: [{}] }, "query_id"], + [{ entries: [{}], workflow_entries: [] }, "entries.0.query_id"], + [{ entries: [], workflow_entries: [{}] }, "workflow_entries.0.query_id"], ] as const) { const encoded = JSON.stringify(document); await writeFile(snapshot, encoded); @@ -60,8 +60,8 @@ cliTest( expect(error).not.toHaveProperty("cause"); expect(error).not.toHaveProperty("stack"); if (field === "target") - expect(error.details?.reason).toBe( - "Analysis snapshot validation failed at target: Invalid input: expected object, received undefined (2 more issues)", + expect(error.details?.reason).toMatch( + / at target: .+ \(2 more issues\)$/, ); } // MCP adds open-session facts; the snapshot reason must match exactly. diff --git a/tests/boundary/filesystem/analysisSnapshotInput.test.ts b/tests/boundary/filesystem/analysisSnapshotInput.test.ts index 6f67fbf01d..8af4b209a1 100644 --- a/tests/boundary/filesystem/analysisSnapshotInput.test.ts +++ b/tests/boundary/filesystem/analysisSnapshotInput.test.ts @@ -33,12 +33,12 @@ describe.each([ { label: "provider entry", document: { entries: [{}], workflow_entries: [] }, - field: "query_id", + field: "entries.0.query_id", }, { label: "workflow entry", document: { entries: [], workflow_entries: [{}] }, - field: "query_id", + field: "workflow_entries.0.query_id", }, ])( "retains the first $label schema diagnostic through the file wrapper", @@ -60,8 +60,8 @@ describe.each([ details: { reason: expect.stringContaining(`at ${field}:`) }, }); if (field === "target") - expect(projected.details?.reason).toBe( - "Analysis snapshot validation failed at target: Invalid input: expected object, received undefined (2 more issues)", + expect(projected.details?.reason).toMatch( + / at target: .+ \(2 more issues\)$/, ); expect(projected).not.toHaveProperty("cause"); expect(projected).not.toHaveProperty("stack");