fix(evm): preserve reply allowance failures and selected limits

This commit is contained in:
morluto
2026-10-24 02:39:42 +08:00
parent 1ddb32956a
commit 4b2a4e8eae
5 changed files with 87 additions and 21 deletions
+28 -1
View File
@@ -2,7 +2,10 @@ import { readFile, symlink } from "node:fs/promises";
import { join } from "node:path";
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
import { expect, it } from "vitest";
import { evmInterfaceFailure } from "./EvmoleFailures.js";
import {
evmInterfaceFailure,
evmReplyBudgetFailure,
} from "./EvmoleFailures.js";
import { OwnedCommandFailure } from "../process/OwnedCommand.js";
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
@@ -20,6 +23,30 @@ const snapshot: ProviderProcessSnapshot = {
signal: null,
};
it("projects a complete EVM reply allowance refusal as a deterministic resource constraint", () => {
const capturedOutput = {
stdout: "kept",
stderr: "warning",
truncated: false,
};
const projected = projectAnalysisError(
evmReplyBudgetFailure(
"Complete interface evidence exceeds the 16 MiB reply budget.",
{ capturedOutput },
),
);
expect(projected).toMatchObject({
code: "resource_constraint",
retryable: false,
details: {
reported_limits: { maximum_reply_bytes: 16777216 },
captured_output: capturedOutput,
},
remediation: { action: expect.stringContaining("fixed report allowance") },
});
expect(projected.remediation.action).not.toContain("doctor");
});
it.each([
undefined,
null,
+21
View File
@@ -7,6 +7,7 @@ import {
import {
AnalysisError,
type AnalysisCapturedOutput,
type AnalysisErrorOptions,
} from "../domain/analysisErrorBase.js";
import type { ProviderProcessSnapshot } from "../process/ProviderProcess.js";
import {
@@ -32,6 +33,26 @@ import {
EVM_INTERFACE_LIMITS,
EVM_FILE_SIZE_FAILURE_EXIT,
} from "./EvmoleRelease.js";
import { fixedAllowanceOptions } from "../domain/fixedAllowanceRemediation.js";
/** Both a structured worker refusal and reply-file admission name this allowance. */
export const evmReplyBudgetFailure = (
reason: string,
options: AnalysisErrorOptions,
) =>
new AnalysisResourceConstraintError(
"inspect_evm_interface",
"file-size",
reason,
{ maximum_reply_bytes: EVM_INTERFACE_LIMITS.outputBytes },
{
...options,
...fixedAllowanceOptions(
`The complete EVMole worker reply allowance of ${String(EVM_INTERFACE_LIMITS.outputBytes)} bytes (this release has no runtime override)`,
"report",
),
},
);
const fixedEvmRecovery = (allowance: string): string =>
`${allowance}. This release has no runtime override. timeout_ms does not raise it. Repeating the unchanged request fails the same way.`;
+21 -13
View File
@@ -53,6 +53,7 @@ import type { ProviderProcessSupervisor } from "../process/ProviderProcess.js";
import {
evmInterfaceFailure,
capturedEvmOutput,
evmReplyBudgetFailure,
type EvmFileSizeFailureEvidence,
} from "./EvmoleFailures.js";
import {
@@ -582,15 +583,24 @@ export class EvmoleInterfaceProvider implements EvmInterfacePort {
bindArtifactFileCleanup(cause, failure);
throw failure;
}
const failure = new AnalysisOutputError(
OPERATION,
`Owned interface worker reply failed for ${input.path}: ${cause instanceof Error ? cause.message : String(cause)}`,
{
cause,
capturedOutput,
...(cleanup === undefined ? {} : { cleanup }),
},
);
const failure =
cause instanceof ArtifactReaderFailure &&
cause.reason === "limit" &&
cause.boundary === "file-size"
? evmReplyBudgetFailure(cause.message, {
cause,
capturedOutput,
...(cleanup === undefined ? {} : { cleanup }),
})
: new AnalysisOutputError(
OPERATION,
`Owned interface worker reply failed for ${input.path}: ${cause instanceof Error ? cause.message : String(cause)}`,
{
cause,
capturedOutput,
...(cleanup === undefined ? {} : { cleanup }),
},
);
if (cause instanceof ArtifactReaderFailure)
bindArtifactFileCleanup(cause, failure);
throw failure;
@@ -612,10 +622,8 @@ export class EvmoleInterfaceProvider implements EvmInterfacePort {
},
]);
if (reply.reason === "output-limit")
throw new AnalysisOutputError(OPERATION, reply.message, {
throw evmReplyBudgetFailure(reply.message, {
capturedOutput,
userMessage: `${reply.message} The reply allowance is ${String(EVM_INTERFACE_LIMITS.outputBytes)} bytes. This release has no runtime override.`,
remediationAction: `The EVMole reply allowance is ${String(EVM_INTERFACE_LIMITS.outputBytes)} bytes. This release has no runtime override. timeout_ms does not raise it. Repeating the unchanged request fails the same way.`,
});
throw new ProviderAdapterError(EVMOLE_PROVIDER_IDENTITY.id, OPERATION, {
diagnostics: {
@@ -653,7 +661,7 @@ export class EvmoleInterfaceProvider implements EvmInterfacePort {
"Bytecode kind, hardfork and deployed-contract authenticity are unknown. This inspection performs no target runtime execution, chain/RPC lookup or network request.",
"Original carrier and decoded bytecode SHA-256 are distinct identities; SHA-256 is not the Ethereum Keccak code hash.",
"Terminal CBOR metadata is an independently decoded candidate, not proof of Solidity origin, source identity or deployment. Nested or unsupported values retain explicit unavailable interpretation and original byte ranges.",
`The owned worker has a 256 MiB JavaScript heap, an address-space ceiling of ${String(EVM_INTERFACE_LIMITS.addressSpaceBytes)} bytes, and a CPU ceiling of ${String(EVM_CPU_SECONDS)} seconds. The command deadline is timeout_ms at ${String(timeoutMs)} ms (default ${String(DEFAULT_EVM_TIMEOUT_MS)}). Those byte, address-space, and CPU ceilings have no runtime override, and timeout_ms does not raise them. Virtual address space is distinct from resident memory; limits fail without partial success.`,
`The owned worker has a 256 MiB JavaScript heap. The profile maxima are ${String(EVM_INTERFACE_LIMITS.addressSpaceBytes)} address-space bytes and ${String(EVM_CPU_SECONDS)} CPU seconds; the configured soft limits above retain tighter inherited limits. The command deadline is timeout_ms at ${String(timeoutMs)} ms (default ${String(DEFAULT_EVM_TIMEOUT_MS)}). Those byte, address-space, and CPU maxima have no runtime override, and timeout_ms does not raise them. Virtual address space is distinct from resident memory; limits fail without partial success.`,
];
const validated = evmInterfaceSchema.safeParse({
artifact: {