diff --git a/src/domain/explicitSensitiveFailure.test.ts b/src/domain/explicitSensitiveFailure.test.ts index 2a882c7500..82dc095870 100644 --- a/src/domain/explicitSensitiveFailure.test.ts +++ b/src/domain/explicitSensitiveFailure.test.ts @@ -198,6 +198,49 @@ it("redacts cleanup primaries and mirrors the source error's live status", () => }); }); +it("retains sanitized finalization failures and their independent cleanup status", () => { + const primary = new AnalysisInputError("inspect_web_network_capture", { + userMessage: "declared-marker capture is invalid", + }); + const reason = new Error("cancelled during reply finalization"); + const cancellation = new AnalysisCancelledError( + "inspect_web_network_capture", + { cause: reason, diagnostics: { note: "declared-marker finalization" } }, + ); + const cleanup = new ProviderCleanupError( + "har", + ["/tmp/declared-marker-runtime"], + { reason: "declared-marker runtime close failed" }, + { operation: "inspect_web_network_capture" }, + ); + primary.retainSecondaryFailure(cancellation); + primary.retainSecondaryFailure(cleanup); + + const redacted = redactExplicitFailure(primary, ["declared-marker"]); + const [retainedCancellation, retainedCleanup] = redacted.secondaryFailures; + expect(retainedCancellation).toBeInstanceOf(AnalysisCancelledError); + expect(retainedCancellation?.cause).toBe(reason); + expect(retainedCleanup).toBeInstanceOf(ProviderCleanupError); + expect(retainedCleanup?.cleanupIncomplete).toBe(true); + expect(redacted.cleanupIncomplete).toBe(false); + const projected = projectAnalysisError(redacted); + expect(projected.code).toBe("invalid_request"); + expect(projected.details?.secondary_failures).toMatchObject([ + { + code: "cancelled", + details: { diagnostics: { note: "[REDACTED] finalization" } }, + }, + { + code: "cleanup_incomplete", + details: { resources: ["/tmp/[REDACTED]-runtime"] }, + }, + ]); + expect(JSON.stringify(projected)).not.toContain("declared-marker"); + cleanup.resolveCleanup(); + expect(retainedCleanup?.cleanupIncomplete).toBe(false); + expect(redacted.cleanupIncomplete).toBe(false); +}); + it("keeps a named timeout control through explicit-value redaction", () => { const error = new AnalysisTimeoutError( "analyze_javascript_application", diff --git a/src/domain/explicitSensitiveFailure.ts b/src/domain/explicitSensitiveFailure.ts index 4c7cd25114..fe626b462c 100644 --- a/src/domain/explicitSensitiveFailure.ts +++ b/src/domain/explicitSensitiveFailure.ts @@ -84,6 +84,9 @@ export const redactSensitiveFailure = ( // Other observation owners already apply their format-aware exclusions. if (redacted !== error && error.partialObservation !== undefined) redacted.retainPartialObservation(error.partialObservation); + if (redacted !== error) + for (const secondary of error.secondaryFailures) + redacted.retainSecondaryFailure(redactSensitiveFailure(secondary, text)); return redacted === error ? error : mirrorCleanup(error, redacted, text); };