mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 21:36:13 +00:00
fix(evm): explain limiter configuration and access failures (#8669)
This commit is contained in:
1 parent
6442ea02cb
commit
b7911b2d30
4 files changed
+121
-31
No files matched your search
@@ -28,6 +28,15 @@ The host must already provide util-linux `prlimit`, normally `/usr/bin/prlimit`;
|
||||
no system tools and changes no user configuration. [Upstream provenance and
|
||||
license](https://github.com/morluto/rea/blob/main/third_party/evmole/README.md) are packaged.
|
||||
|
||||
Limiter discovery validates the selected path without launching it. A relative
|
||||
path or directory is reported as `not_configured`, a missing file as
|
||||
`executable_missing`, and denied access or another filesystem-probe failure as
|
||||
`runtime_unavailable`. Recovery names the selected path and
|
||||
`REA_EVM_PRLIMIT_COMMAND`; denied access asks for execute permission and
|
||||
filesystem access. The original admission or system code stays in diagnostics.
|
||||
An available filesystem probe does not verify the selected tool's version or
|
||||
util-linux compatibility, and REA does not substitute another command.
|
||||
|
||||
## Evidence semantics
|
||||
|
||||
- `encoding` is required: raw bytes or UTF-8 hex, with optional `0x` prefix and
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { EvmAdmissionError } from "./EvmAdmissionError.js";
|
||||
import type { ProviderRejectionCode } from "../domain/providerSelection.js";
|
||||
import { snapshotEnvironment } from "../process/snapshotEnvironment.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import { safeParseUtf8Json } from "../domain/safeJson.js";
|
||||
@@ -115,6 +116,7 @@ type EvmLimiterSelection = {
|
||||
readonly diagnostics: Readonly<Record<string, JsonValue>>;
|
||||
};
|
||||
type EvmLimiterUnavailable = EvmLimiterSelection & {
|
||||
readonly code: ProviderRejectionCode;
|
||||
readonly reason: string;
|
||||
readonly cause: unknown;
|
||||
};
|
||||
@@ -159,16 +161,39 @@ const selectEvmLimiter = async (
|
||||
if (cause instanceof EvmAdmissionError)
|
||||
return err({
|
||||
path,
|
||||
code: "not_configured",
|
||||
diagnostics: {
|
||||
...diagnostics,
|
||||
code: cause.code,
|
||||
...cause.detail,
|
||||
},
|
||||
reason: cause.code,
|
||||
reason: `Set REA_EVM_PRLIMIT_COMMAND to an absolute executable regular file for caller-supplied util-linux prlimit. The selected path ${path} ${cause.code === "prlimit_not_absolute" ? "is not absolute" : "is not a regular file"}.`,
|
||||
cause,
|
||||
});
|
||||
const reason = `Offline EVM interface inspection requires caller-supplied util-linux prlimit: ${path}; ${cause instanceof Error ? cause.message : String(cause)}`;
|
||||
return err({ path, diagnostics, reason, cause });
|
||||
const systemCode =
|
||||
cause instanceof Error &&
|
||||
"code" in cause &&
|
||||
typeof cause.code === "string"
|
||||
? cause.code
|
||||
: undefined;
|
||||
const permissionDenied = systemCode === "EACCES" || systemCode === "EPERM";
|
||||
const missing = systemCode === "ENOENT" || systemCode === "ENOTDIR";
|
||||
const recovery = permissionDenied
|
||||
? "Grant execute permission and filesystem access to the selected file, or set REA_EVM_PRLIMIT_COMMAND to an accessible absolute executable regular file for util-linux prlimit."
|
||||
: missing
|
||||
? "Set REA_EVM_PRLIMIT_COMMAND to an existing absolute executable regular file for util-linux prlimit."
|
||||
: "Check the selected file and filesystem access, or set REA_EVM_PRLIMIT_COMMAND to an accessible absolute executable regular file for util-linux prlimit.";
|
||||
const reason = `Offline EVM interface inspection requires caller-supplied util-linux prlimit: ${path}; ${cause instanceof Error ? cause.message : String(cause)}. ${recovery}`;
|
||||
return err({
|
||||
path,
|
||||
code: missing ? "executable_missing" : "runtime_unavailable",
|
||||
diagnostics: {
|
||||
...diagnostics,
|
||||
...(systemCode === undefined ? {} : { system_code: systemCode }),
|
||||
},
|
||||
reason,
|
||||
cause,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -193,7 +218,7 @@ export const evmLimiterAvailability = async (
|
||||
}
|
||||
: {
|
||||
status: "unavailable",
|
||||
code: "executable_missing",
|
||||
code: selected.error.code,
|
||||
reason: selected.error.reason,
|
||||
// The limiter owner does not yet author a separate repair (#8508).
|
||||
remediation: selected.error.reason,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
import { withVerifierCleanup } from "../../../scripts/lib/verifier-cleanup.mjs";
|
||||
import { buildCapabilityInventory } from "../../../src/application/CapabilityInventory.js";
|
||||
@@ -41,33 +44,43 @@ it("probes the default limiter with executable and regular-file checks", async (
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses the exact absolute override and reports a failed executable check", async () => {
|
||||
const limiter = "/fixture/util-linux/prlimit";
|
||||
const availability = await evmLimiterAvailability(
|
||||
{ REA_EVM_PRLIMIT_COMMAND: limiter },
|
||||
{
|
||||
...linux,
|
||||
fs: {
|
||||
access: async (path) => {
|
||||
expect(path).toBe(limiter);
|
||||
throw Object.assign(new Error("permission denied"), {
|
||||
code: "EACCES",
|
||||
});
|
||||
},
|
||||
stat: async () => {
|
||||
throw new Error("stat must not run after access failure");
|
||||
it.each(["EACCES", "EPERM"])(
|
||||
"uses the exact absolute override and reports denied access: %s",
|
||||
async (code) => {
|
||||
const limiter = "/fixture/util-linux/prlimit";
|
||||
const availability = await evmLimiterAvailability(
|
||||
{ REA_EVM_PRLIMIT_COMMAND: limiter },
|
||||
{
|
||||
...linux,
|
||||
fs: {
|
||||
access: async (path) => {
|
||||
expect(path).toBe(limiter);
|
||||
throw Object.assign(new Error("permission denied"), {
|
||||
code,
|
||||
});
|
||||
},
|
||||
stat: async () => {
|
||||
throw new Error("stat must not run after access failure");
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
expect(availability).toMatchObject({
|
||||
status: "unavailable",
|
||||
code: "executable_missing",
|
||||
reason: expect.stringContaining(limiter),
|
||||
diagnostics: { limiter_path: limiter },
|
||||
});
|
||||
});
|
||||
expect(availability).toMatchObject({
|
||||
status: "unavailable",
|
||||
code: "runtime_unavailable",
|
||||
reason: expect.stringContaining(limiter),
|
||||
diagnostics: { limiter_path: limiter, system_code: code },
|
||||
});
|
||||
if (availability === undefined) throw new Error("Expected limiter probe");
|
||||
const entry = buildCapabilityInventory(status, {
|
||||
...basePolicy,
|
||||
evmLimiterAvailability: availability,
|
||||
}).find(({ name }) => name === "inspect_evm_interface");
|
||||
expect(entry?.remediation).toContain("REA_EVM_PRLIMIT_COMMAND");
|
||||
expect(entry?.remediation).toContain("execute permission");
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects a relative override and non-file without probing an alternate command", async () => {
|
||||
const relative = await evmLimiterAvailability(
|
||||
@@ -84,7 +97,8 @@ it("rejects a relative override and non-file without probing an alternate comman
|
||||
);
|
||||
expect(relative).toMatchObject({
|
||||
status: "unavailable",
|
||||
reason: "prlimit_not_absolute",
|
||||
code: "not_configured",
|
||||
reason: expect.stringContaining("absolute executable regular file"),
|
||||
diagnostics: {
|
||||
limiter_path: "prlimit",
|
||||
code: "prlimit_not_absolute",
|
||||
@@ -104,11 +118,53 @@ it("rejects a relative override and non-file without probing an alternate comman
|
||||
);
|
||||
expect(directory).toMatchObject({
|
||||
status: "unavailable",
|
||||
reason: "prlimit_not_file",
|
||||
code: "not_configured",
|
||||
reason: expect.stringContaining("absolute executable regular file"),
|
||||
diagnostics: expect.objectContaining({ code: "prlimit_not_file" }),
|
||||
});
|
||||
});
|
||||
|
||||
it("delivers selected-path recovery from real filesystem probes to capability output", async () => {
|
||||
const root = await createTestTempDirectory("rea-evm-limiter-recovery-");
|
||||
const executable = join(root, "caller-selected-limiter");
|
||||
await writeFile(executable, "not executed by the readiness probe", {
|
||||
mode: 0o700,
|
||||
});
|
||||
for (const [path, expectedCode, expectedReason] of [
|
||||
["prlimit", "not_configured", "absolute executable regular file"],
|
||||
[root, "not_configured", "absolute executable regular file"],
|
||||
[
|
||||
join(root, "missing-limiter"),
|
||||
"executable_missing",
|
||||
"REA_EVM_PRLIMIT_COMMAND",
|
||||
],
|
||||
[executable, null, null],
|
||||
] as const) {
|
||||
const availability = await evmLimiterAvailability(
|
||||
{ REA_EVM_PRLIMIT_COMMAND: path },
|
||||
linux,
|
||||
);
|
||||
expect(availability?.code).toBe(expectedCode);
|
||||
expect(availability?.diagnostics.limiter_path).toBe(path);
|
||||
if (availability === undefined) throw new Error("Expected limiter probe");
|
||||
const entry = buildCapabilityInventory(status, {
|
||||
...basePolicy,
|
||||
evmLimiterAvailability: availability,
|
||||
}).find(({ name }) => name === "inspect_evm_interface");
|
||||
if (expectedReason === null) {
|
||||
expect(entry).toMatchObject({ available: true, remediation: null });
|
||||
} else {
|
||||
expect(entry).toMatchObject({
|
||||
available: false,
|
||||
reason: "provider_missing",
|
||||
});
|
||||
expect(entry?.remediation).toContain(expectedReason);
|
||||
expect(entry?.remediation).toContain(path);
|
||||
expect(entry?.remediation).toContain("util-linux prlimit");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("rechecks on each observation so inventory follows the selected executable's current state", async () => {
|
||||
let accessible = true;
|
||||
const fs = {
|
||||
|
||||
@@ -970,7 +970,7 @@ it.runIf(!unsupportedHost).each(["directory", "missing-interpreter"])(
|
||||
});
|
||||
if (scenario === "directory")
|
||||
expect(result.error).toMatchObject({
|
||||
reason: "prlimit_not_file",
|
||||
reason: expect.stringContaining("absolute executable regular file"),
|
||||
diagnostics: {
|
||||
limiter_path: limiter,
|
||||
code: "prlimit_not_file",
|
||||
|
||||
Reference in new issue
Block a user