fix(domain): preserve retryable capability failures through redaction (#2494)

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: 吴佳浩 <wujiahao@supremind.com>
This commit is contained in:
吴佳浩Alben 2026-10-13 14:43:10 +08:00 committed by GitHub
parent 60f771a11e
commit d73ed6fce2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 19 additions and 0 deletions

View File

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

View File

@ -154,6 +154,7 @@ export const redactExplicitFailure = (
...(error.userMessage === undefined
? {}
: { userMessage: text(error.userMessage) }),
...(error.retryable ? { retryable: true } : {}),
},
);
if (error instanceof AnalysisResourceConstraintError)