From db177c91b83adb0c3edc4f9f6f06e31ac7523d53 Mon Sep 17 00:00:00 2001 From: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Date: Fri, 9 Oct 2026 09:23:00 +0000 Subject: [PATCH] fix(errors): report caller-selected evidence path failures as invalid input An EvidenceFileError for a missing evidence file, a selected path that is not a regular file, or an output that already exists projected code and category execution_failure. The message already told the caller to correct the path, but agents branching on code or category saw a tool failure. Project those three reasons as invalid_request with category invalid_input. An unreadable file (io) and invalid JSON keep their codes. Co-Authored-By: Claude Opus 5.5 --- src/domain/analysisErrorPresentation.ts | 7 +++++ src/domain/analysisErrorProjection.test.ts | 31 +++++++++++++++++++++- src/domain/analysisErrorProjection.ts | 10 ++++--- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/domain/analysisErrorPresentation.ts b/src/domain/analysisErrorPresentation.ts index bf2382058..93ffeb4fb 100644 --- a/src/domain/analysisErrorPresentation.ts +++ b/src/domain/analysisErrorPresentation.ts @@ -119,6 +119,13 @@ export const analysisErrorCategory = ( return "unavailable"; if (error instanceof ArtifactOperationError) return artifactErrorCategory(error.reason); + if ( + error instanceof EvidenceFileError && + (error.reason === "missing" || + error.reason === "not-file" || + error.reason === "exists") + ) + return "invalid_input"; return STATIC_ERROR_CATEGORIES[error._tag] ?? "execution_failure"; }; diff --git a/src/domain/analysisErrorProjection.test.ts b/src/domain/analysisErrorProjection.test.ts index 96525d518..66fa06914 100644 --- a/src/domain/analysisErrorProjection.test.ts +++ b/src/domain/analysisErrorProjection.test.ts @@ -11,7 +11,7 @@ import { import { ArtifactOperationError } from "./artifactOperationError.js"; import { BinaryTargetError } from "./configurationErrors.js"; import { BrowserObservationError } from "./browserObservationError.js"; -import { EvidenceIntegrityError } from "./evidenceErrors.js"; +import { EvidenceFileError, EvidenceIntegrityError } from "./evidenceErrors.js"; import { HopperProcessError, HopperRemoteError, @@ -573,3 +573,32 @@ describe("analysis error projection: operational diagnostics", () => { expect(unknown.remediation.action).toContain("provider_operation_health"); }); }); + +describe("analysis error projection: evidence files", () => { + it.each([ + ["read", "missing", "invalid_request", "invalid_input"], + ["read", "not-file", "invalid_request", "invalid_input"], + ["write", "missing", "invalid_request", "invalid_input"], + ["write", "exists", "invalid_request", "invalid_input"], + [ + "read", + "invalid-json", + "evidence_integrity_mismatch", + "execution_failure", + ], + ["read", "io", "execution_failure", "execution_failure"], + ] as const)( + "classifies a %s %s evidence file by who can correct it", + (operation, reason, code, category) => { + const projected = projectAnalysisError( + new EvidenceFileError(operation, reason, { path: "/selected.json" }), + ); + expect(projected).toMatchObject({ + code, + category, + details: { operation, reason, path: "/selected.json" }, + }); + expect(analysisErrorProjectionSchema.parse(projected)).toEqual(projected); + }, + ); +}); diff --git a/src/domain/analysisErrorProjection.ts b/src/domain/analysisErrorProjection.ts index 5048a88eb..4eaac5176 100644 --- a/src/domain/analysisErrorProjection.ts +++ b/src/domain/analysisErrorProjection.ts @@ -144,10 +144,12 @@ const artifactOperationCode = ( const evidenceFileCode = ( reason: EvidenceFileError["reason"], -): AnalysisErrorProjection["code"] => - reason === "invalid-json" - ? "evidence_integrity_mismatch" - : "execution_failure"; +): AnalysisErrorProjection["code"] => { + if (reason === "invalid-json") return "evidence_integrity_mismatch"; + // A missing, non-file or already-existing path is the caller's selection. + if (reason === "io") return "execution_failure"; + return "invalid_request"; +}; const unknownRegistryCode = ( reason: UnknownRegistryError["reason"],