mirror of
https://github.com/morluto/rea
synced 2026-10-26 22:28:27 -04:00
8f65d7f256
Attribute shared startup failures separately for each accepted operation. Preserve the original stage as a compact diagnostic, with typed timeout budgets, limits, output, recovery and exact cleanup dependencies. Link cancellation wrappers to the originating live cleanup owner, including release before and after wrapping. Remove the redundant typed-error branch. Validated 130 focused tests, static checks, docs and five conformance controls; four source controls rerun after fixture type correction. Public CLI uses a loopback supervisor; no native IDA engine verified. Refs: #4614, #4633
102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
import { describe, expect } from "vitest";
|
|
|
|
import { workspaceCliTest } from "../../support/cli/workspaceCliFixture.js";
|
|
|
|
import { elf } from "../../../src/domain/binaryTarget.fixture.js";
|
|
|
|
/**
|
|
* A headless IDA supervisor that completes the MCP handshake and then leaves
|
|
* every tool call unanswered, as an `idb_open` still running auto-analysis
|
|
* would. It exits when REA closes its input.
|
|
*/
|
|
const UNRESPONSIVE_SUPERVISOR = `import { createInterface } from "node:readline";
|
|
const tools = ["idb_open", "idb_list", "idb_close", "server_health", "list_funcs",
|
|
"lookup_funcs", "find_regex", "decompile", "disasm", "callees", "xrefs_to"];
|
|
const send = (message) =>
|
|
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", ...message }) + "\\n");
|
|
for await (const line of createInterface({ input: process.stdin })) {
|
|
const request = JSON.parse(line);
|
|
if (request.method === "initialize")
|
|
send({ id: request.id, result: {
|
|
protocolVersion: request.params.protocolVersion,
|
|
capabilities: { tools: {} },
|
|
serverInfo: { name: "unresponsive-ida-fixture", version: "1" },
|
|
} });
|
|
else if (request.method === "tools/list")
|
|
send({ id: request.id, result: {
|
|
tools: tools.map((name) => ({ name, inputSchema: { type: "object" } })),
|
|
} });
|
|
}
|
|
`;
|
|
|
|
describe("headless IDA timeout through the one-shot CLI", () => {
|
|
workspaceCliTest(
|
|
"exits after a retained-cleanup failure instead of waiting on the supervisor",
|
|
async ({ cli, workspace }) => {
|
|
const supervisor = await workspace.write(
|
|
"supervisor.mjs",
|
|
UNRESPONSIVE_SUPERVISOR,
|
|
);
|
|
const workspaceRoot = await workspace.mkdir("ida-workspaces");
|
|
const registration = await workspace.write(
|
|
"ida-mcp.json",
|
|
JSON.stringify({
|
|
command: process.execPath,
|
|
args: [supervisor],
|
|
mode: "headless",
|
|
timeoutMs: 500,
|
|
workspaceRoot,
|
|
}),
|
|
);
|
|
const target = await workspace.write("target.elf", elf(2, 1, 62));
|
|
|
|
const result = await cli.run({
|
|
arguments: ["decompile", target, "main", "--json"],
|
|
environment: {
|
|
...workspace.environment,
|
|
REA_ANALYSIS_PROVIDER: "ida",
|
|
REA_IDA_MCP_CONFIG: registration,
|
|
REA_LOG_LEVEL: "silent",
|
|
},
|
|
timeoutMs: 30_000,
|
|
});
|
|
|
|
expect(result.exitCode).toBe(1);
|
|
expect(result.json).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
details: {
|
|
cleanup: "incomplete",
|
|
execution_failure: "provider_timeout",
|
|
operation: "procedure_pseudo_code",
|
|
diagnostics: {
|
|
startup_operation: "idb_open",
|
|
},
|
|
timeout_ms: 500,
|
|
resources: [expect.stringContaining(workspaceRoot)],
|
|
cleanup_error: {
|
|
code: "cleanup_incomplete",
|
|
details: {
|
|
provider_id: "ida",
|
|
operation: "close",
|
|
diagnostics: {
|
|
upstream_sessions: {
|
|
state: "unverified",
|
|
sessions: [
|
|
{
|
|
session_id: expect.stringMatching(/^rea-/),
|
|
backend: "worker",
|
|
owned: null,
|
|
input_path: null,
|
|
is_active: null,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
},
|
|
);
|
|
});
|