mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
Attempt independent resource cleanup without losing primary failures, retain unresolved owners, and publish completion after release. Bound existing producer commands, preserve helper return values, and settle startup and fixture children on failure. Validation: 29 focused boundary tests passed; actual helper-value and import-bound filesystem cleanup controls passed. The real inert-child termination control and targeted JavaScript binding checks passed, alongside typecheck, lint and formatting. Live Ghidra, Cutter, Electron GUI and Windows addon qualification was not run.
230 lines
6.6 KiB
JavaScript
230 lines
6.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import {
|
|
closeVerifierResources,
|
|
withVerifierCleanup,
|
|
} from "./lib/verifier-cleanup.mjs";
|
|
import assert from "node:assert/strict";
|
|
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
import { createPackageVerifierCommands } from "./lib/verify-package-commands.mjs";
|
|
import { createHash, randomUUID } from "node:crypto";
|
|
import {
|
|
access,
|
|
constants,
|
|
mkdtemp,
|
|
readFile,
|
|
rm,
|
|
writeFile,
|
|
} from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join, resolve } from "node:path";
|
|
|
|
import { createServer } from "../dist/server/createServer.js";
|
|
|
|
const cli = resolve("scripts/rea.mjs");
|
|
const runCli = async (...arguments_) => {
|
|
const { stdout } = await commands.exec(
|
|
process.execPath,
|
|
[cli, ...arguments_, "--format", "json"],
|
|
{
|
|
timeout: 150_000,
|
|
maxBuffer: 4 * 1024 * 1024,
|
|
env: process.env,
|
|
},
|
|
);
|
|
return JSON.parse(stdout);
|
|
};
|
|
|
|
const rizinQuote = (value) =>
|
|
`"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
const unavailableAnalysis = {
|
|
execute: async () => {
|
|
throw new Error(
|
|
"The Cutter verification does not call unrelated analysis tools.",
|
|
);
|
|
},
|
|
};
|
|
const expectedPlatform = {
|
|
linux: "linux",
|
|
Linux: "linux",
|
|
macOS: "darwin",
|
|
Windows: "win32",
|
|
}[process.env.EXPECTED_PLATFORM];
|
|
if (process.env.EXPECTED_PLATFORM !== undefined) {
|
|
assert.ok(expectedPlatform, "Unrecognized Cutter runner platform.");
|
|
assert.equal(
|
|
process.platform,
|
|
expectedPlatform,
|
|
"Cutter runner did not match its declared platform.",
|
|
);
|
|
}
|
|
const projectRoot = await mkdtemp(join(tmpdir(), "rea-cutter-smoke-"));
|
|
const commands = await createPackageVerifierCommands({
|
|
workspace: {
|
|
path: projectRoot,
|
|
close: () => rm(projectRoot, { recursive: true, force: true }),
|
|
},
|
|
});
|
|
const projectPath = join(projectRoot, "smoke.rzdb");
|
|
await withVerifierCleanup(async () => {
|
|
const discovery = await runCli("list-cutter-sessions");
|
|
assert.equal(
|
|
discovery.discovery_status,
|
|
"sessions_found",
|
|
"Open upstream Cutter with the REA plugin before running this smoke test.",
|
|
);
|
|
assert.ok(discovery.sessions.length > 0);
|
|
const session = discovery.sessions[0];
|
|
assert.ok(session);
|
|
assert.ok(
|
|
session.cutter_version,
|
|
"The upstream Cutter API did not report its version.",
|
|
);
|
|
if (process.env.REA_CUTTER_EXPECTED_VERSION !== undefined)
|
|
assert.ok(
|
|
session.cutter_version.includes(process.env.REA_CUTTER_EXPECTED_VERSION),
|
|
"The live Cutter version did not match the configured version.",
|
|
);
|
|
assert.ok(
|
|
session.current_file,
|
|
"Open a disposable local binary in Cutter before running this smoke test.",
|
|
);
|
|
const artifactPath = session.current_file;
|
|
const artifactDigest = createHash("sha256")
|
|
.update(await readFile(artifactPath))
|
|
.digest("hex");
|
|
|
|
const inspection = await runCli(
|
|
"cutter-command",
|
|
session.session_id,
|
|
String(session.document_generation),
|
|
"ij",
|
|
"--json-output",
|
|
);
|
|
assert.equal(inspection.predicate_type, "rea.cutter.command-observation");
|
|
assert.ok(
|
|
Object.hasOwn(inspection, "raw_result"),
|
|
"Cutter ij result was not retained as Evidence.",
|
|
);
|
|
assert.equal(typeof inspection.raw_result.bin?.arch, "string");
|
|
|
|
const mcpServer = createServer(
|
|
{ kind: "fixed", analysis: unavailableAnalysis },
|
|
{
|
|
providerEnvironment: process.env,
|
|
},
|
|
);
|
|
const mcpClient = new Client({
|
|
name: "rea-cutter-upstream-smoke",
|
|
version: "1",
|
|
});
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
const mcpOwner = {
|
|
close: () =>
|
|
closeVerifierResources([
|
|
() => mcpClient.close(),
|
|
() => mcpServer.close(),
|
|
]),
|
|
};
|
|
commands.adopt(mcpOwner, "Cutter verifier MCP server");
|
|
await withVerifierCleanup(async () => {
|
|
await mcpServer.connect(serverTransport);
|
|
await mcpClient.connect(clientTransport);
|
|
const mcpDiscovery = await mcpClient.callTool({
|
|
name: "list_cutter_sessions",
|
|
arguments: {},
|
|
});
|
|
assert.notEqual(mcpDiscovery.isError, true);
|
|
assert.equal(
|
|
mcpDiscovery.structuredContent.discovery_status,
|
|
"sessions_found",
|
|
);
|
|
const mcpInspection = await mcpClient.callTool({
|
|
name: "cutter_command",
|
|
arguments: {
|
|
session_id: session.session_id,
|
|
expected_generation: session.document_generation,
|
|
command: "ij",
|
|
json: true,
|
|
},
|
|
});
|
|
assert.notEqual(mcpInspection.isError, true);
|
|
assert.deepEqual(
|
|
mcpInspection.structuredContent.normalized_result,
|
|
inspection.normalized_result,
|
|
"Cutter CLI and MCP returned different analysis results.",
|
|
);
|
|
}, [() => mcpOwner.close()]);
|
|
|
|
const annotation = `rea-smoke-${randomUUID()}`;
|
|
await runCli(
|
|
"cutter-command",
|
|
session.session_id,
|
|
String(session.document_generation),
|
|
`CC ${annotation} @ entry0`,
|
|
);
|
|
await runCli(
|
|
"cutter-command",
|
|
session.session_id,
|
|
String(session.document_generation),
|
|
`Ps ${rizinQuote(projectPath)}`,
|
|
);
|
|
await access(projectPath, constants.R_OK | constants.W_OK);
|
|
await runCli(
|
|
"cutter-command",
|
|
session.session_id,
|
|
String(session.document_generation),
|
|
"CC- @ entry0",
|
|
);
|
|
const removed = await runCli(
|
|
"cutter-command",
|
|
session.session_id,
|
|
String(session.document_generation),
|
|
"CC. @ entry0",
|
|
);
|
|
assert.ok(
|
|
!String(removed.raw_result).includes(annotation),
|
|
"The in-memory annotation was not removed before project reopening.",
|
|
);
|
|
await runCli(
|
|
"cutter-command",
|
|
session.session_id,
|
|
String(session.document_generation),
|
|
`Po ${rizinQuote(projectPath)}`,
|
|
);
|
|
const recovered = await runCli(
|
|
"cutter-command",
|
|
session.session_id,
|
|
String(session.document_generation),
|
|
"CC. @ entry0",
|
|
);
|
|
assert.equal(recovered.predicate_type, "rea.cutter.command-observation");
|
|
assert.ok(
|
|
String(recovered.raw_result).includes(annotation),
|
|
"The saved project did not preserve the analysis comment after reopening.",
|
|
);
|
|
const finalArtifactDigest = createHash("sha256")
|
|
.update(await readFile(artifactPath))
|
|
.digest("hex");
|
|
assert.equal(
|
|
finalArtifactDigest,
|
|
artifactDigest,
|
|
"Cutter project edits changed the executable bytes.",
|
|
);
|
|
|
|
const report = {
|
|
ok: true,
|
|
platform: process.platform,
|
|
cutter_version: session.cutter_version,
|
|
document_generation: session.document_generation,
|
|
cli_mcp_evidence_parity: true,
|
|
annotation_saved_and_reopened: true,
|
|
executable_digest_unchanged: true,
|
|
};
|
|
await writeFile("cutter-smoke.json", `${JSON.stringify(report, null, 2)}\n`, {
|
|
mode: 0o600,
|
|
});
|
|
console.log(JSON.stringify(report));
|
|
}, [() => commands.close()]);
|