fix(evm): report the carrier input allowance as a file-size constraint (#6414)

The EVM interface failure translator grouped the stable reader's input
limit with path and format refusals, so a carrier over the fixed 4 MiB
input allowance surfaced as a non-retryable invalid_request whose only
record of the allowance was issue text pointing at the input schema.
The same refusal now returns a non-retryable resource_constraint with
resource file-size and reported_limits.maximum_input_bytes, following
the WABT input translator, while path and format reader refusals stay
input errors. The reply-size write failure keeps its own branch.

Fixes #6053
This commit is contained in:
MeskineMorty authored and GitHub committed 2026-10-23 14:46:38 +08:00
1 parent 37e92e430b
commit 5d81363391
3 files changed
+76 -11

No files matched your search

+3 -1
View File
@@ -110,7 +110,9 @@ address-space limit and 30 CPU seconds. The worker command deadline is
`timeout_ms` (default 30000 ms, integer 1 through 2147483647) on the next call,
or `--timeout-ms` on `rea inspect-evm-interface`. Omission keeps 30000 ms.
Those byte, address-space, and CPU ceilings have no runtime override, and
`timeout_ms` does not raise them. Virtual
`timeout_ms` does not raise them. An oversized selected carrier returns a
non-retryable `resource_constraint` with resource `file-size` and its fixed
input allowance in `reported_limits.maximum_input_bytes`. Virtual
address space differs from resident memory. The worker retains inherited tighter
soft/hard limits, reports its configured soft limits inline and leaves hard limits
unchanged. WASM trap reservations are disabled
+55
View File
@@ -2,6 +2,7 @@ import { expect, it } from "vitest";
import { evmInterfaceFailure } from "./EvmoleFailures.js";
import { OwnedCommandFailure } from "../process/OwnedCommand.js";
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import {
DEFAULT_EVM_TIMEOUT_MS,
@@ -106,6 +107,60 @@ it("says a CPU signal has no runtime override and timeout_ms does not raise it",
);
});
it("reports the fixed carrier input allowance as a resource constraint", () => {
const source = new ArtifactReaderFailure(
"limit",
"Selected artifact exceeds 4194304 bytes: /selected.bin",
{ boundary: "file-size" },
);
const projected = projectAnalysisError(
evmInterfaceFailure(source, "artifact-read", "/selected.bin"),
);
expect(projected).toMatchObject({
code: "resource_constraint",
category: "resource_constraint",
retryable: false,
details: {
operation: "inspect_evm_interface",
resource: "file-size",
reported_limits: { maximum_input_bytes: 4194304 },
},
});
expect(projected.message).toContain("4194304");
expect(projected.message).toContain("no runtime override");
expect(projected.message).toContain("timeout_ms does not raise it");
expect(projected.remediation.action).toContain("4194304");
expect(projected.remediation.action).toContain("no runtime override");
expect(projected.remediation.action).toContain("Do not truncate");
expect(projected.remediation.action).toContain(
"repeating the unchanged request fails the same way",
);
expect(projected.recovery).toBeUndefined();
});
it.each([
{
reason: "path" as const,
message: "Expected a regular file without a symlink: /selected.bin",
},
{ reason: "format" as const, message: "Unsupported carrier format." },
])("keeps reader $reason refusals as invalid input", ({ reason, message }) => {
const projected = projectAnalysisError(
evmInterfaceFailure(
new ArtifactReaderFailure(reason, message),
"artifact-read",
"/selected.bin",
),
);
expect(projected).toMatchObject({
code: "invalid_request",
category: "invalid_input",
details: {
issues: [{ path: ["path"], reason: "invalid_format", message }],
},
});
});
it.each([
{ failure: "process" as const, cleanup: null, tag: "ProviderAdapterError" },
{ failure: "timeout" as const, cleanup: null, tag: "AnalysisTimeoutError" },
+18 -10
View File
@@ -173,28 +173,36 @@ export const evmInterfaceFailure = (
);
if (
phase === "artifact-read" &&
["limit", "path", "format"].includes(cause.reason)
) {
const inputLimited = cause.reason === "limit";
const message = inputLimited
? `${cause.message} The input allowance is ${String(EVM_INTERFACE_LIMITS.inputBytes)} bytes. This release has no runtime override. timeout_ms does not raise it.`
: cause.message;
cause.reason === "limit" &&
cause.boundary === "file-size"
)
return new AnalysisResourceConstraintError(
"inspect_evm_interface",
"file-size",
`${cause.message} The input allowance is ${String(EVM_INTERFACE_LIMITS.inputBytes)} bytes. This release has no runtime override. timeout_ms does not raise it.`,
{ maximum_input_bytes: EVM_INTERFACE_LIMITS.inputBytes },
{
cause,
...cleanupOptions,
retryable: false,
remediationAction: `Select a suitable complete carrier within the ${String(EVM_INTERFACE_LIMITS.inputBytes)}-byte input allowance. This release has no runtime override. timeout_ms does not raise it, and reply or diagnostic allowances do not admit a larger carrier. Do not truncate required bytes; repeating the unchanged request fails the same way.`,
},
);
if (phase === "artifact-read" && ["path", "format"].includes(cause.reason))
return new AnalysisInputError(
"inspect_evm_interface",
{
cause,
...cleanupOptions,
...(inputLimited ? { userMessage: message } : {}),
},
[
{
path: ["path"],
reason: inputLimited ? "out_of_range" : "invalid_format",
message,
reason: "invalid_format",
message: cause.message,
},
],
);
}
}
if (phase === "artifact-read" && cause instanceof Error && "code" in cause) {
if (cause.code === "EACCES" || cause.code === "EPERM")