Files
reverse-engineering-anything/src/providers/AnalysisClientFailure.ts
T
Kaoru0822andClaude Opus 5.5 5df59ee1fd fix(errors): name fixed plist decode allowances instead of host memory in recovery (#5860)
fix(errors): derive fixed-allowance recovery from a typed refusal field

inspect_plist refused a binary plist above its fixed 64 MiB decode
estimate, or nested deeper than 128 levels, with resource "memory" and no
recovery, so the shared presentation told the caller to add worker or
host memory. Neither raises that allowance. The 64 MiB source-size refusal
likewise got the worker file-size advice for evidence replies.

AnalysisResourceConstraintError now carries a typed fixedAllowance
{ name, kind }. fixedAllowanceOptions sets it instead of a prebuilt
string, and the presentation derives the recovery from it, so every
existing fixed-allowance producer moves to the same field. Operation
re-attribution, IDA startup re-attribution and explicit redaction copy the
field through one resourceConstraintRecovery helper; redaction still
applies to the allowance name.

The plist decode, nesting and input refusals use the field. A nesting
refusal now reports maximum_nesting_depth instead of the decode byte
limit it did not exceed.

Fixes #5847

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:51:46 -07:00

257 lines
7.8 KiB
TypeScript

import {
HopperRemoteError,
HopperTimeoutError,
HopperProcessError,
} from "../domain/hopperErrors.js";
import {
AnalysisError,
type AnalysisErrorOptions,
} from "../domain/analysisErrorBase.js";
import {
AnalysisCancelledError,
AnalysisCapabilityUnavailableError,
AnalysisAccessDeniedError,
AnalysisArtifactChangedError,
AnalysisResourceConstraintError,
resourceConstraintRecovery,
AnalysisOutputError,
AnalysisTimeoutError,
} from "../domain/analysisErrorCore.js";
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
import type { JsonValue } from "../domain/jsonValue.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import { bindArtifactFileCleanup } from "../artifacts/ArtifactFileOperation.js";
/** Copy authored outcome facts; semantic attribution never inspects a later signal. */
export const analysisClientFailureContext = (
cause: AnalysisError,
providerId: string,
operation: string,
): AnalysisErrorOptions => {
const providerRequest =
cause instanceof HopperRemoteError ||
cause instanceof HopperTimeoutError ||
cause instanceof HopperProcessError
? {
...(cause.operation === undefined
? {}
: { operation: cause.operation }),
...(cause.requestId === undefined
? {}
: { request_id: cause.requestId }),
...(cause instanceof HopperRemoteError
? {
provider_code: cause.code,
diagnostic_type: cause.diagnosticType,
}
: {}),
}
: undefined;
const diagnostics =
providerRequest === undefined
? cause.diagnostics
: {
provider_request: providerRequest,
...(cause.diagnostics === undefined
? {}
: { provider_diagnostics: cause.diagnostics }),
};
return {
cause,
...(cause.cleanupPrimary === undefined
? {}
: {
cleanupPrimary: analysisClientFailureForOperation(
cause.cleanupPrimary,
providerId,
operation,
),
}),
...(cause.partialObservation === undefined
? {}
: { partialObservation: cause.partialObservation }),
...(cause.capturedOutput === undefined
? {}
: { capturedOutput: cause.capturedOutput }),
...(diagnostics === undefined ? {} : { diagnostics }),
...(cause.cleanupIncomplete
? {
cleanup: {
reason: cause.cleanup?.reason ?? cause.message,
resources: cause.cleanupResources,
},
}
: {}),
};
};
/** Attribute producer-owned failure facts without inferring interruption. */
export const analysisClientFailureForOperation = (
cause: unknown,
providerId: string,
operation: string,
callerDiagnostics?: Readonly<Record<string, JsonValue>>,
): AnalysisError => {
if (!(cause instanceof AnalysisError)) {
const failure = new ProviderAdapterError(providerId, operation, {
cause,
...(callerDiagnostics === undefined
? {}
: { diagnostics: callerDiagnostics }),
...(cause instanceof ArtifactReaderFailure && cause.cleanup !== undefined
? { cleanup: cause.cleanup }
: {}),
});
bindArtifactFileCleanup(cause, failure);
return failure;
}
if (
!(cause instanceof HopperRemoteError) &&
!(cause instanceof HopperTimeoutError) &&
!(cause instanceof HopperProcessError) &&
!(cause instanceof AnalysisCancelledError) &&
!(cause instanceof ProviderAdapterError) &&
!(cause instanceof AnalysisCapabilityUnavailableError) &&
!(cause instanceof AnalysisAccessDeniedError) &&
!(cause instanceof AnalysisArtifactChangedError) &&
!(cause instanceof AnalysisTimeoutError) &&
!(cause instanceof AnalysisOutputError) &&
!(cause instanceof AnalysisResourceConstraintError)
)
return cause;
const sourceContext = analysisClientFailureContext(
cause,
providerId,
operation,
);
const context = {
...sourceContext,
...(callerDiagnostics === undefined
? {}
: {
diagnostics: { ...sourceContext.diagnostics, ...callerDiagnostics },
}),
};
let result: AnalysisError;
if (cause instanceof HopperRemoteError)
result = new HopperRemoteError(cause.code, cause.safeMessage, {
...context,
operation,
requestId: cause.requestId,
diagnosticType: cause.diagnosticType,
});
else if (cause instanceof HopperTimeoutError)
result = new HopperTimeoutError(
cause.timeoutMs,
operation,
cause.requestId,
cause.providerState,
cause.stage,
cause.launcherOutcome,
context,
);
else if (cause instanceof HopperProcessError)
result = new HopperProcessError(cause.exitCode, {
...context,
operation,
requestId: cause.requestId,
providerState: cause.providerState,
stage: cause.stage,
...(cause.diagnostic === undefined
? {}
: { diagnostic: cause.diagnostic }),
...(cause.launcherFailure === undefined
? {}
: { launcherFailure: cause.launcherFailure }),
});
else if (cause instanceof ProviderCleanupError) {
const { diagnostics, ...outcomeContext } = context;
result = new ProviderCleanupError(
cause.providerId,
cause.cleanupResources,
diagnostics ?? {},
{
...outcomeContext,
operation,
...(cause.remediationAction === undefined
? {}
: { remediationAction: cause.remediationAction }),
},
);
} else if (cause instanceof AnalysisTimeoutError)
result = new AnalysisTimeoutError(operation, cause.timeoutMs, {
...context,
...(cause.userMessage === undefined
? {}
: { userMessage: cause.userMessage }),
...(cause.remediationAction === undefined
? {}
: { remediationAction: cause.remediationAction }),
});
else if (cause instanceof AnalysisOutputError)
result = new AnalysisOutputError(operation, cause.reason, {
...context,
cleanupOnly: cause.cleanupOnly,
...(cause.userMessage === undefined
? {}
: { userMessage: cause.userMessage }),
...(cause.remediationAction === undefined
? {}
: { remediationAction: cause.remediationAction }),
});
else if (cause instanceof AnalysisCancelledError)
result = new AnalysisCancelledError(operation, {
...context,
cause: cause.cause,
});
else if (cause instanceof AnalysisCapabilityUnavailableError)
result = new AnalysisCapabilityUnavailableError(
cause.providerId,
operation,
cause.reason,
{
...context,
...(cause.userMessage === undefined
? {}
: { userMessage: cause.userMessage }),
retryable: cause.retryable,
},
);
else if (cause instanceof AnalysisAccessDeniedError)
result = new AnalysisAccessDeniedError(
operation,
cause.path,
cause.systemCode,
context,
);
else if (cause instanceof AnalysisArtifactChangedError)
result = new AnalysisArtifactChangedError(
operation,
cause.path,
cause.reason,
context,
);
else if (cause instanceof AnalysisResourceConstraintError)
result = new AnalysisResourceConstraintError(
operation,
cause.resource,
cause.reason,
cause.reportedLimits,
{ ...context, ...resourceConstraintRecovery(cause) },
);
else
result = new ProviderAdapterError(providerId, operation, {
...context,
...(cause.userMessage === undefined
? {}
: { userMessage: cause.userMessage }),
...(cause.remediationAction === undefined
? {}
: { remediationAction: cause.remediationAction }),
});
return cause.cleanupIncomplete
? result.bindCleanupDependencies([cause])
: result.resolveCleanup();
};