mirror of
https://github.com/morluto/rea.git
synced 2026-10-22 22:02:27 -05:00
Add opt-in retained GDB and objdump summaries using the shared analysis-view layer. Preserve default complete Evidence, ordered records, provenance and completion facts. Validate real Bash disassembly, whole-client context costs, CLI publication and cleanup; retain the original measured cohorts and explicit coverage limitations. Closes #5584.
213 lines
7.0 KiB
JavaScript
213 lines
7.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { createHash } from "node:crypto";
|
|
import { createWriteStream } from "node:fs";
|
|
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
|
import { createRequire } from "node:module";
|
|
import { tmpdir } from "node:os";
|
|
import { join, resolve } from "node:path";
|
|
import { finished } from "node:stream/promises";
|
|
import { pathToFileURL } from "node:url";
|
|
import { parseArgs } from "node:util";
|
|
import { createRetainedQueryCaller } from "./retained-query-calls.mjs";
|
|
import { finalizeRetainedQueryReceipt } from "./retained-query-finalization.mjs";
|
|
|
|
const { values } = parseArgs({
|
|
options: {
|
|
runtime: { type: "string" },
|
|
input: { type: "string" },
|
|
oracle: { type: "string" },
|
|
output: { type: "string" },
|
|
provider: { type: "string" },
|
|
symbol: { type: "string" },
|
|
detail: { type: "string", default: "complete" },
|
|
warm: { type: "boolean", default: false },
|
|
},
|
|
});
|
|
for (const key of ["runtime", "input", "oracle", "output"])
|
|
assert.equal(values[key], resolve(values[key]));
|
|
assert.ok(["gdb", "objdump"].includes(values.provider));
|
|
assert.ok(["complete", "summary"].includes(values.detail));
|
|
const oracle = JSON.parse(await readFile(values.oracle, "utf8"));
|
|
assert.ok(oracle.symbols.some((row) => row.name === values.symbol));
|
|
const inputBytes = await readFile(values.input);
|
|
assert.equal(
|
|
createHash("sha256").update(inputBytes).digest("hex"),
|
|
oracle.artifact_sha256,
|
|
);
|
|
const require = createRequire(join(values.runtime, "package.json"));
|
|
const { Client } = await import(
|
|
pathToFileURL(require.resolve("@modelcontextprotocol/client"))
|
|
);
|
|
const { StdioClientTransport } = await import(
|
|
pathToFileURL(require.resolve("@modelcontextprotocol/client/stdio"))
|
|
);
|
|
await mkdir(values.output, { mode: 0o700 });
|
|
const account = await mkdtemp(join(tmpdir(), "rea-command-views-"));
|
|
const client = new Client({ name: "command-observation-task", version: "1" });
|
|
const transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [join(values.runtime, "scripts/rea.mjs"), "mcp"],
|
|
env: {
|
|
HOME: account,
|
|
PATH: process.env.PATH,
|
|
NODE_OPTIONS: "--max-old-space-size=2048",
|
|
UV_THREADPOOL_SIZE: "1",
|
|
REA_LOG_LEVEL: "silent",
|
|
},
|
|
stderr: "pipe",
|
|
});
|
|
const serverLog = createWriteStream(join(values.output, "server.log"));
|
|
const serverLogFinished = finished(serverLog);
|
|
serverLogFinished.catch(() => {});
|
|
transport.stderr?.pipe(serverLog);
|
|
const transcript = [];
|
|
const report = {
|
|
question:
|
|
"Read architecture and the complete byte-annotated disassembly of one selected native function; export complete Evidence. No inferior execution.",
|
|
provider: values.provider,
|
|
symbol: values.symbol,
|
|
detail: values.detail,
|
|
warm: values.warm,
|
|
calls: [],
|
|
observations: [],
|
|
};
|
|
const call = createRetainedQueryCaller(client, (receipt, event) => {
|
|
report.calls.push(receipt);
|
|
transcript.push(event);
|
|
});
|
|
let sessionId;
|
|
try {
|
|
await client.connect(transport);
|
|
const catalog = await client.listTools();
|
|
await writeFile(join(values.output, "catalog.json"), JSON.stringify(catalog));
|
|
const definitions = new Map(catalog.tools.map((tool) => [tool.name, tool]));
|
|
const originalCall = client.callTool.bind(client);
|
|
client.callTool = (input, options) =>
|
|
originalCall(input, {
|
|
...options,
|
|
timeout: 60000,
|
|
toolDefinition: definitions.get(input.name),
|
|
});
|
|
const observe = async (name, input, phase = name) => {
|
|
const response = await call(name, input, phase);
|
|
assert.ok(!response.isError, JSON.stringify(response));
|
|
report.observations.push({
|
|
name,
|
|
phase,
|
|
evidence: response.structuredContent,
|
|
});
|
|
return response.structuredContent;
|
|
};
|
|
const completeParents = [];
|
|
const disassemble = async (phase) => {
|
|
const selection = values.detail === "summary" ? { detail: "summary" } : {};
|
|
const result =
|
|
values.provider === "gdb"
|
|
? await observe(
|
|
"gdb_console",
|
|
{
|
|
session_id: sessionId,
|
|
command: `disassemble /r ${values.symbol}`,
|
|
...selection,
|
|
},
|
|
phase,
|
|
)
|
|
: await observe(
|
|
"inspect_with_objdump",
|
|
{
|
|
path: values.input,
|
|
operation: "disassemble",
|
|
symbol: values.symbol,
|
|
...selection,
|
|
},
|
|
phase,
|
|
);
|
|
completeParents.push(result);
|
|
};
|
|
if (values.provider === "gdb") {
|
|
const started = await observe("start_gdb_session", {});
|
|
sessionId = started.session_id;
|
|
for (const command of [
|
|
"set auto-load off",
|
|
"set debuginfod enabled off",
|
|
`file ${values.input}`,
|
|
"show architecture",
|
|
])
|
|
await observe("gdb_console", { session_id: sessionId, command });
|
|
} else
|
|
await observe("inspect_with_objdump", {
|
|
path: values.input,
|
|
operation: "file_headers",
|
|
});
|
|
await disassemble("disassembly:fresh");
|
|
if (values.warm) await disassemble("disassembly:repeat");
|
|
if (sessionId) {
|
|
await observe("close_gdb_session", { session_id: sessionId });
|
|
sessionId = undefined;
|
|
}
|
|
await observe("export_evidence_bundle", {
|
|
path: join(values.output, "evidence.json"),
|
|
});
|
|
const bundle = JSON.parse(
|
|
await readFile(join(values.output, "evidence.json"), "utf8"),
|
|
);
|
|
for (const delivered of completeParents) {
|
|
const parent =
|
|
values.detail === "complete"
|
|
? delivered
|
|
: bundle.records.find(
|
|
(record) =>
|
|
record.evidence_id ===
|
|
delivered.normalized_result.parent_evidence_id,
|
|
);
|
|
assert.ok(parent);
|
|
assert.equal(parent.normalized_result.completion_status, "complete");
|
|
assert.equal(parent.normalized_result.output_truncated, false);
|
|
if (values.provider === "gdb") assert.equal(parent.subject, null);
|
|
else assert.equal(parent.subject.digest.sha256, oracle.artifact_sha256);
|
|
if (values.detail === "summary") {
|
|
const selected = delivered.normalized_result.summary;
|
|
const { available_counts: counts, ...facts } = selected;
|
|
const { console, target, log, ...records } = parent.normalized_result;
|
|
assert.deepEqual(
|
|
values.provider === "gdb" ? facts : selected,
|
|
values.provider === "gdb" ? records : parent.normalized_result,
|
|
);
|
|
if (values.provider === "gdb")
|
|
assert.equal(counts.console_utf16_units, console.length);
|
|
assert.equal(delivered.raw_result, null);
|
|
}
|
|
}
|
|
report.status = "passed";
|
|
} catch (error) {
|
|
report.status = "failed";
|
|
report.failure = String(error?.stack ?? error);
|
|
process.exitCode = 1;
|
|
} finally {
|
|
if (sessionId)
|
|
try {
|
|
const closed = await call(
|
|
"close_gdb_session",
|
|
{ session_id: sessionId },
|
|
"cleanup:gdb",
|
|
);
|
|
assert.ok(!closed.isError);
|
|
} catch (error) {
|
|
report.gdb_cleanup_failure = String(error);
|
|
process.exitCode = 1;
|
|
}
|
|
if (
|
|
!(await finalizeRetainedQueryReceipt({
|
|
report,
|
|
client,
|
|
transport,
|
|
account,
|
|
serverLog,
|
|
serverLogFinished,
|
|
output: values.output,
|
|
transcript,
|
|
}))
|
|
)
|
|
process.exitCode = 1;
|
|
}
|