From d73ed6fce24f6d1372c80319cd41d29d66666159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E4=BD=B3=E6=B5=A9Alben?= <35291372+XiaoBinGan@users.noreply.github.com> Date: Tue, 13 Oct 2026 14:43:10 +0800 Subject: [PATCH] fix(domain): preserve retryable capability failures through redaction (#2494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit redactExplicitFailure rebuilds each typed failure field by field so explicit literals are removed while the projected code stays the same. The rebuild for AnalysisCapabilityUnavailableError carried providerId, operation, reason and userMessage but not retryable, and the constructor defaults that field to false. Redacting a transient capability failure therefore made it permanent and changed the remediation shown to the caller. Pass the field through with the same conditional-spread shape used for userMessage. The constructor recomputes retryable from cleanupIncomplete, and cleanup is already part of the reconstructed context, so a value that was true before redaction stays true and a false value stays absent. Verified with 66 error projection, access-denied, redaction and capture-failure tests, check:fast and verify:test-discovery on macOS arm64. The regression test failed with "expected false to be true" before the change. Co-authored-by: 吴佳浩 --- src/domain/explicitSensitiveFailure.test.ts | 18 ++++++++++++++++++ src/domain/explicitSensitiveFailure.ts | 1 + 2 files changed, 19 insertions(+) diff --git a/src/domain/explicitSensitiveFailure.test.ts b/src/domain/explicitSensitiveFailure.test.ts index 68aaf9f8f..757e9887a 100644 --- a/src/domain/explicitSensitiveFailure.test.ts +++ b/src/domain/explicitSensitiveFailure.test.ts @@ -116,6 +116,24 @@ it("keeps unsupported profile reasons distinct from malformed input", () => { expect(JSON.stringify(projectAnalysisError(result))).not.toContain("private"); }); +it("preserves a retryable capability failure through explicit-value redaction", () => { + // Retryability is a semantic field of the error, not presentation: dropping + // it turns a transient admission failure into a permanent one and changes + // the remediation the caller is shown. + const error = new AnalysisCapabilityUnavailableError( + "mitmproxy", + "inspect_web_network_capture", + "Runtime admission for /targets/private/capture failed transiently", + { retryable: true }, + ); + expect(projectAnalysisError(error).retryable).toBe(true); + const result = redactExplicitFailure(error, ["private"]); + expect(result).toBeInstanceOf(AnalysisCapabilityUnavailableError); + expect((result as AnalysisCapabilityUnavailableError).retryable).toBe(true); + expect(projectAnalysisError(result).retryable).toBe(true); + expect(JSON.stringify(projectAnalysisError(result))).not.toContain("private"); +}); + it("coarsens format issue pointers without creating replacement coordinates", () => { const error = new AnalysisInputError( "inspect_web_network_capture", diff --git a/src/domain/explicitSensitiveFailure.ts b/src/domain/explicitSensitiveFailure.ts index 05a4f2f88..8a0fac095 100644 --- a/src/domain/explicitSensitiveFailure.ts +++ b/src/domain/explicitSensitiveFailure.ts @@ -154,6 +154,7 @@ export const redactExplicitFailure = ( ...(error.userMessage === undefined ? {} : { userMessage: text(error.userMessage) }), + ...(error.retryable ? { retryable: true } : {}), }, ); if (error instanceof AnalysisResourceConstraintError)