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"],