fix: report provider admission failures as typed codes (#8066)

* fix: report provider admission failures as typed codes

* fix(evm): keep limiter path on admission diagnostics

* fix: assert WABT and CBOR admission codes

* fix: keep admission details on returned failures

* fix(evm): detect repeated CBOR keys in the decoder

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
morlutoandCursor Agent authored and GitHub committed 2026-10-30 06:48:01 +08:00
1 parent 3b647132c6
commit 317bf3fe23
63 files changed
+1021 -432

No files matched your search

@@ -82,8 +82,12 @@ it("rejects a relative override and non-file without probing an alternate comman
);
expect(relative).toMatchObject({
status: "unavailable",
diagnostics: { limiter_path: "prlimit" },
reason: expect.stringContaining("absolute executable path"),
reason: "prlimit_not_absolute",
diagnostics: {
limiter_path: "prlimit",
code: "prlimit_not_absolute",
setting: "REA_EVM_PRLIMIT_COMMAND",
},
});
const directory = await evmLimiterAvailability(
@@ -98,7 +102,8 @@ it("rejects a relative override and non-file without probing an alternate comman
);
expect(directory).toMatchObject({
status: "unavailable",
reason: expect.stringContaining("regular executable file"),
reason: "prlimit_not_file",
diagnostics: expect.objectContaining({ code: "prlimit_not_file" }),
});
});
@@ -982,7 +982,15 @@ it.runIf(!unsupportedHost).each(["directory", "missing-interpreter"])(
category: "unsupported_provider",
details: { provider_id: "evmole-interface" },
});
expect(result.error.message).toContain(limiter);
if (scenario === "directory")
expect(result.error).toMatchObject({
reason: "prlimit_not_file",
diagnostics: {
limiter_path: limiter,
code: "prlimit_not_file",
},
});
else expect(result.error.message).toContain(limiter);
if (ownedRoot !== undefined)
await expect(access(ownedRoot)).rejects.toMatchObject({ code: "ENOENT" });
expect(await readFile(path, "utf8")).toBe("0x60006000f3\n");
@@ -94,7 +94,17 @@ it.each([
if (kind.startsWith("duplicate nested")) {
expect(metadata.entries).toEqual([]);
expect(metadata.compiler_version).toBeNull();
expect(metadata.diagnostic).toContain("repeat map key");
expect(metadata.diagnostic).toBe("repeated_map_key");
expect(metadata.admission).toMatchObject({ code: "repeated_map_key" });
if (kind === "duplicate nested text keys")
expect(metadata.admission?.detail).toEqual({
key: JSON.stringify({ kind: "text", value: "a" }),
});
else
expect(
metadata.admission?.detail?.key ??
metadata.admission?.detail?.key_length,
).toBeDefined();
expect(metadata.bytecode_offset).toBe(1);
expect(metadata.cbor_bytes).toBe(19);
}
@@ -102,7 +102,7 @@ const admissionCases: readonly AdmissionCase[] = [
{
name: "malformed Unicode escape",
properties: "application.version=12.1.4\nother=\\u00xx\n",
error: "Malformed Unicode escape in Java properties file",
error: "malformed_unicode_escape",
},
];
@@ -603,7 +603,12 @@ it.each(["seed", "extension"] as const)(
],
},
});
else expect(result.error.diagnostics?.reason).toContain("not a JAR");
else
expect(result.error.diagnostics).toMatchObject({
code: "not_jar",
reason: "not_jar",
path,
});
allowClose = true;
await scope.retryCleanup();
expect(result.error.cleanupIncomplete).toBe(false);
@@ -241,14 +241,27 @@ describe.skipIf(process.platform === "win32")(
if (missing.ok) throw new Error("Missing installation was accepted");
expect(missing.error.message).toContain("PSP_EXTENSION_UNAVAILABLE");
});
},
);
describe.skipIf(process.platform === "win32")(
"PSP extension admission codes",
() => {
it.each([
["name=other\nversion=12.1.3\n", "does not match"],
["name=ghidra-allegrex\nversion=12.1.4\n", "does not match"],
["name=ghidra-allegrex\nversion=12.1.\\uZZZZ\n", "Unicode escape"],
])(
["name=other\nversion=12.1.3\n", "metadata_mismatch", undefined],
[
"name=ghidra-allegrex\nversion=12.1.4\n",
"metadata_mismatch",
undefined,
],
[
"name=ghidra-allegrex\nversion=12.1.\\uZZZZ\n",
"properties_invalid",
"malformed_unicode_escape",
],
] as const)(
"rejects mismatched or malformed extension metadata %s",
async (properties, reason) => {
async (properties, code, parserCode) => {
const { root } = await installation();
await writeFile(join(root, "extension.properties"), properties);
await expect(
@@ -257,7 +270,14 @@ describe.skipIf(process.platform === "win32")(
"12.1.3",
new ArtifactResourceScope(),
),
).rejects.toThrow(reason);
).rejects.toMatchObject({
diagnostics: {
code,
...(parserCode === undefined
? { ghidra_version: "12.1.3" }
: { parser_code: parserCode }),
},
});
},
);
@@ -269,7 +289,12 @@ describe.skipIf(process.platform === "win32")(
);
await expect(
inspectGhidraPspExtension(root, "12.1.3", new ArtifactResourceScope()),
).rejects.toThrow("Module.manifest");
).rejects.toMatchObject({
diagnostics: {
code: "component_missing",
component: "Module.manifest",
},
});
});
it("rejects symlinked code instead of hashing an untracked external producer", async () => {
@@ -279,7 +304,9 @@ describe.skipIf(process.platform === "win32")(
await symlink(join(directory, "external.jar"), path);
await expect(
inspectGhidraPspExtension(root, "12.1.3", new ArtifactResourceScope()),
).rejects.toThrow("regular files");
).rejects.toMatchObject({
diagnostics: { code: "entry_kind", relative: "lib/extension.jar" },
});
});
it("bounds sparse extension input before reading or allocating its declared extent", async () => {
@@ -287,7 +314,12 @@ describe.skipIf(process.platform === "win32")(
await truncate(join(root, "lib/extension.jar"), 64 * 1024 * 1024 + 1);
await expect(
inspectGhidraPspExtension(root, "12.1.3", new ArtifactResourceScope()),
).rejects.toThrow("inspection limit");
).rejects.toMatchObject({
diagnostics: {
code: "bytes_exceed_limit",
max_bytes: 64 * 1024 * 1024,
},
});
});
},
);
@@ -314,7 +346,9 @@ describe.skipIf(process.platform === "win32")(
"12.1.3",
new ArtifactResourceScope(),
),
).rejects.toThrow("inventory exceeds the inspection limit");
).rejects.toMatchObject({
diagnostics: { code: "inventory_exceeds_limit", max_entries: 1024 },
});
},
);
},
@@ -571,7 +605,12 @@ describe.skipIf(process.platform === "win32")(
const languageOverride = { languageId: "x86:LE:64:default" };
expect(() =>
ghidraHeadlessArguments({ ...paths, psp: true, languageOverride }),
).toThrow("PSP ELF import cannot be combined");
).toThrow(
expect.objectContaining({
_tag: "GhidraAdmissionError",
code: "psp_combined",
}),
);
expect(ghidraHeadlessArguments({ ...paths, languageOverride })).toEqual(
expect.arrayContaining(["-processor", "x86:LE:64:default"]),
);
@@ -903,11 +942,14 @@ describe.skipIf(process.platform === "win32")(
const result = await provider.resolveAnalysisProfile(target);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Changed file admitted");
expect(result.error.message).toContain(
change === "growth"
? "changed during inspection"
: "Truncated Allegrex component",
);
expect(result.error).toMatchObject({
diagnostics: {
code:
change === "growth" ? "component_changed" : "component_truncated",
relative:
change === "growth" ? "Module.manifest" : "extension.properties",
},
});
expect(closes).toBe(1);
expect(result.error.cleanupIncomplete).toBe(false);
expect(provider.hasPendingOwnedResources()).toBe(false);
@@ -12,6 +12,7 @@ import { OwnedFileHandle } from "../../../src/filesystem/OwnedFileHandle.js";
import { OwnedStagedOutput } from "../../../src/filesystem/OwnedStagedOutput.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import { writeTextParts } from "../../../src/application/JsonFiles.js";
import { StagedOutputError } from "../../../src/filesystem/StagedOutputError.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const allocation = vi.hoisted(() => ({
@@ -70,7 +71,10 @@ it("closes the descriptor and retains staged content when its final identity is
expect(result.error.cause).toMatchObject({
outcome: {
ok: false,
error: { message: "Cannot verify staged file identity" },
error: {
_tag: "StagedOutputError",
code: "identity_unverified",
},
},
});
const stage = result.error.cleanupResources[0];
@@ -457,7 +461,11 @@ it("refuses to remove unexpected files from an owned staging directory", async (
const unexpected = join(directory, "unexpected");
writeFileSync(unexpected, "unowned");
try {
await expect(owner.close()).rejects.toThrow(/Unexpected staging entries/u);
await expect(owner.close()).rejects.toMatchObject({
_tag: "StagedOutputError",
code: "unexpected_entries",
detail: { count: 1, entries: "unexpected" },
});
expect(await fs.readFile(unexpected, "utf8")).toBe("unowned");
} finally {
await fs.rm(unexpected, { force: true });
@@ -466,6 +474,37 @@ it("refuses to remove unexpected files from an owned staging directory", async (
}
});
it("keeps unexpected staging names on the publication error", async () => {
const root = await createTestTempDirectory("rea-staged-public-entries-");
const destination = join(root, "out.json");
const resources = new ArtifactResourceScope();
const close = vi
.spyOn(OwnedStagedOutput.prototype, "close")
.mockImplementation(async () => {
throw new StagedOutputError("unexpected_entries", {
count: 1,
entries: "unexpected",
});
});
try {
const result = await writeTextParts(["{}"], destination, false, resources);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error).toMatchObject({
_tag: "EvidenceFileError",
diagnostics: {
code: "unexpected_entries",
count: 1,
entries: "unexpected",
},
});
} finally {
close.mockRestore();
await resources.close().catch(() => undefined);
await rm(root, { recursive: true, force: true });
}
});
it("retains cleanup when its staging directory was renamed", async () => {
const root = await createTestTempDirectory("rea-staged-renamed-dir-");
const directory = join(root, ".rea-write-owned");
+12 -2
View File
@@ -141,7 +141,11 @@ it.each(["bin", "hop"])(
code: "capability_unavailable",
details: {
operation: "inspect_wasm_artifact",
reason: expect.stringContaining("REA_WABT_BIN_DIRECTORY"),
reason: "directory_not_absolute",
diagnostics: {
code: "directory_not_absolute",
setting: "REA_WABT_BIN_DIRECTORY",
},
},
},
});
@@ -152,7 +156,13 @@ it.each(["bin", "hop"])(
expect(parseMcpToolError(summary)).toMatchObject({
error: {
code: "capability_unavailable",
details: { reason: expect.stringContaining("REA_WABT_BIN_DIRECTORY") },
details: {
reason: "directory_not_absolute",
diagnostics: {
code: "directory_not_absolute",
setting: "REA_WABT_BIN_DIRECTORY",
},
},
},
});
await client.ping();
@@ -1040,7 +1040,10 @@ it.skipIf(process.platform === "win32")(
throw new Error("closed reader compilation unexpectedly succeeded");
expect(outcome.cause).toMatchObject({ name: "AbortError" });
await expect(access(state.root)).rejects.toThrow();
await expect(reader.prepare()).rejects.toThrow(/reader is closed/u);
await expect(reader.prepare()).rejects.toMatchObject({
_tag: "ProcessAdmissionError",
code: "reader_closed",
});
await expect(reader.close()).resolves.toBeUndefined();
} finally {
await Promise.allSettled([preparation]);
@@ -506,7 +506,10 @@ describe("provider process memory limits", () => {
platform: "linux",
memoryLimitBytes: 704 * 1024 * 1024,
}),
).rejects.toThrow("require the Windows Job Object launcher");
).rejects.toMatchObject({
_tag: "ProcessAdmissionError",
code: "memory_limit_unsupported",
});
});
});
@@ -452,6 +452,13 @@ describe.skipIf(process.platform === "win32")(
cause: { code: 3 },
},
});
if (mode === "env-malformed")
expect(cause).toMatchObject({
cause: {
code: "invalid_environment_entry",
detail: { check: "separator", line: "invalid environment" },
},
});
if (mode === "vm-malformed")
expect(cause).toMatchObject({
launch_support: expect.arrayContaining([
@@ -474,9 +481,8 @@ describe.skipIf(process.platform === "win32")(
if (launched.ok)
throw new Error("Expected a JVM platform override failure");
expect(launched.error.cause).toMatchObject({
message: expect.stringContaining(
"may not override os.arch or os.name",
),
_tag: "GhidraAdmissionError",
code: "platform_override",
});
},
);
@@ -499,9 +505,8 @@ describe.skipIf(process.platform === "win32")(
if (launched.ok)
throw new Error("Expected a JVM platform override failure");
expect(launched.error.cause).toMatchObject({
message: expect.stringContaining(
"may not override os.arch or os.name",
),
_tag: "GhidraAdmissionError",
code: "platform_override",
});
},
);
@@ -578,9 +583,8 @@ describe.skipIf(process.platform === "win32")(
if (launched.ok)
throw new Error("Expected an effective platform override refusal");
expect(launched.error.cause).toMatchObject({
message: expect.stringContaining(
"may not override os.arch or os.name",
),
_tag: "GhidraAdmissionError",
code: "platform_override",
});
},
);
@@ -600,7 +604,8 @@ describe.skipIf(process.platform === "win32")(
if (launched.ok)
throw new Error("Expected a JVM platform override failure");
expect(launched.error.cause).toMatchObject({
message: expect.stringContaining("may not override os.arch or os.name"),
_tag: "GhidraAdmissionError",
code: "platform_override",
});
});
@@ -634,7 +639,9 @@ describe.skipIf(process.platform === "win32")(
if (launched.ok)
throw new Error("Expected an unreadable argument-file failure");
expect(launched.error.cause).toMatchObject({
message: expect.stringContaining("argument file is unreadable"),
_tag: "GhidraAdmissionError",
code: "argument_file_unreadable",
detail: { path: optionsFile },
});
});
@@ -657,7 +664,8 @@ describe.skipIf(process.platform === "win32")(
if (launched.ok)
throw new Error("Expected a nested argument-file failure");
expect(launched.error.cause).toMatchObject({
message: expect.stringContaining("may not nest @ references"),
_tag: "GhidraAdmissionError",
code: "nested_argument_file",
});
});
},
@@ -823,9 +831,8 @@ describe.skipIf(process.platform === "win32")(
);
if (cleanup.fileCleanupFailure.outcome.kind === "failed")
expect(cleanup.fileCleanupFailure.outcome.cause).toMatchObject({
message: expect.stringContaining(
"may not override os.arch or os.name",
),
_tag: "GhidraAdmissionError",
code: "platform_override",
});
await resources.retryCleanup();
expect(resources.hasPending).toBe(false);
@@ -260,7 +260,12 @@ describe("Ghidra GameCube DOL loader", () => {
sectionFilePath: "/tmp/gamecube-dol-sections.txt",
},
}),
).toThrow(/cannot be combined with another loader, language override/u);
).toThrow(
expect.objectContaining({
_tag: "GhidraAdmissionError",
code: "gamecube_dol_combined",
}),
);
});
});
@@ -346,7 +351,12 @@ describe("Ghidra headless launcher", () => {
])
expect(() =>
ghidraHeadlessArguments({ ...base, mipsSoftFloat: true, ...conflict }),
).toThrow(/generic MIPS ELF/u);
).toThrow(
expect.objectContaining({
_tag: "GhidraAdmissionError",
code: "mips_soft_float_scope",
}),
);
});
});
@@ -395,7 +405,12 @@ describe("Ghidra configured import language and seeds", () => {
dosMz: true,
languageOverride: { languageId: "x86:LE:32:default" },
}),
).toThrow(/cannot be combined with a DOS import/u);
).toThrow(
expect.objectContaining({
_tag: "GhidraAdmissionError",
code: "language_override_dos",
}),
);
});
it("never combines a configured language with a raw-binary import", () => {
@@ -409,7 +424,12 @@ describe("Ghidra configured import language and seeds", () => {
baseAddr: "0x8000000",
},
}),
).toThrow(/cannot be combined with a raw-binary import/u);
).toThrow(
expect.objectContaining({
_tag: "GhidraAdmissionError",
code: "language_override_raw_binary",
}),
);
});
it.each(["darwin", "win32"] as const)(
@@ -579,30 +599,41 @@ describe("Ghidra existing project", () => {
});
it.each([
{ dosMz: true as const },
{ dosCom: true as const },
{
rawBinary: {
languageId: "ARM:LE:32:v7",
compilerSpecId: "default",
baseAddr: "0x8000000",
[{ dosMz: true as const }, "existing_project_dos"],
[{ dosCom: true as const }, "existing_project_dos"],
[
{
rawBinary: {
languageId: "ARM:LE:32:v7",
compilerSpecId: "default",
baseAddr: "0x8000000",
},
},
},
{ languageOverride: { languageId: "x86:LE:32:default" } },
{ psp: true as const },
{ mipsSoftFloat: true as const },
{ seedPath: "/tmp/runtime/seeds/seeds.tsv" },
{ disabledAnalyzers: ["Decompiler Parameter ID"] },
])(
"existing_project_raw_binary",
],
[
{ languageOverride: { languageId: "x86:LE:32:default" } },
"existing_project_language_override",
],
[{ psp: true as const }, "existing_project_psp"],
[{ mipsSoftFloat: true as const }, "existing_project_mips_soft_float"],
[{ seedPath: "/tmp/runtime/seeds/seeds.tsv" }, "existing_project_analysis"],
[
{ disabledAnalyzers: ["Decompiler Parameter ID"] },
"existing_project_analysis",
],
] as const)(
"rejects the import-only option %o with an existing project",
(conflict) => {
(conflict, code) => {
expect(() =>
ghidraHeadlessArguments({
...base,
existingProject: { programName: "game.exe" },
...conflict,
}),
).toThrow(/existing (Ghidra )?project|fresh import/u);
).toThrow(
expect.objectContaining({ _tag: "GhidraAdmissionError", code }),
);
},
);
@@ -618,7 +649,12 @@ describe("Ghidra existing project", () => {
),
existingProject: { programName: "game.exe" },
}),
).toThrow(/Windows/u);
).toThrow(
expect.objectContaining({
_tag: "GhidraAdmissionError",
code: "existing_project_windows",
}),
);
});
it("requires a program name for -process", () => {
@@ -627,7 +663,12 @@ describe("Ghidra existing project", () => {
...base,
existingProject: { programName: " " },
}),
).toThrow(/program name/u);
).toThrow(
expect.objectContaining({
_tag: "GhidraAdmissionError",
code: "existing_project_program_name",
}),
);
});
});