Files
rea/scripts/lib/verify-wabt-cleanup.mjs
morluto 22a0746f90 fix(wasm): retain cleanup ownership through WABT recovery
Bind runtime and descriptor cleanup to exact retry leases and retain command supervisors until stop is confirmed. Preserve primary errors, partial Evidence and finalization cancellation reasons.

Validate with 88 focused tests, official WABT 1.0.42 CLI/MCP verifier (51 cases), build, static and documentation checks. Command stop failures use controlled source seams; real permission failures and target execution are unverified.

Fixes #4787

Fixes #4798
2026-10-17 21:02:50 +08:00

227 lines
7.5 KiB
JavaScript

import assert from "node:assert/strict";
import {
mkdtemp,
mkdir,
readFile,
writeFile,
readdir,
rm,
} from "node:fs/promises";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { execFile } from "node:child_process";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { requireMcpToolError } from "./mcp-verifier-results.mjs";
import { closeVerifierResources } from "./verifier-cleanup.mjs";
import { WABT_OPERATION_TIMEOUT_MS } from "./wabt-verifier-timeouts.mjs";
const execute = promisify(execFile);
/** Real WABT inspection with authored cleanup denials and same-connection recovery. */
export async function verifyWabtCleanup(entrypoint, environment, parent) {
const root = await mkdtemp(join(parent, "wabt-cleanup-"));
let primaryFailure;
try {
const valid = join(root, "valid.wasm"),
invalid = join(root, "invalid.wasm");
await writeFile(
valid,
Buffer.from("0061736d01000000000a066f7061717565616263", "hex"),
);
await writeFile(invalid, Buffer.from("0061736d01000000ff", "hex"));
const preload = fileURLToPath(
new URL("../../tests/fixtures/wasm/cleanupPreload.mjs", import.meta.url),
);
const records = [];
async function setup(name, mode, input) {
const temporary = join(root, name);
await mkdir(temporary);
const trace = join(root, name + ".jsonl");
return {
temporary,
trace,
env: {
...environment,
HOME: root,
USERPROFILE: root,
REA_LOG_LEVEL: "silent",
TMPDIR: temporary,
TMP: temporary,
TEMP: temporary,
REA_CLEANUP_PROBE_ROOT: temporary,
REA_CLEANUP_PROBE_TRACE: trace,
REA_CLEANUP_PROBE_MODE: mode,
REA_CLEANUP_PROBE_INPUT: input,
},
};
}
async function observations(configuration) {
const trace = await readFile(configuration.trace, "utf8").catch((e) => {
if (e.code === "ENOENT") return "";
throw e;
});
return {
trace:
trace.trim() === "" ? [] : trace.trim().split("\n").map(JSON.parse),
remaining: await readdir(configuration.temporary),
};
}
for (const [name, mode, input] of [
["normal", "normal", valid],
["valid-recovered", "runtime-once", valid],
["invalid-recovered", "runtime-once", invalid],
["descriptor-recovered", "input-once", valid],
]) {
const c = await setup(name, mode, input);
let response,
status = 0;
try {
response = await execute(
process.execPath,
[
"--import",
preload,
entrypoint,
"inspect-wasm-artifact",
input,
"--json",
],
{
env: c.env,
timeout: WABT_OPERATION_TIMEOUT_MS,
maxBuffer: 64 * 1024 * 1024,
},
);
} catch (e) {
if (typeof e.code !== "number" || typeof e.stdout !== "string") throw e;
response = e;
status = e.code;
}
const output = JSON.parse(response.stdout);
const facts = await observations(c);
assert.deepEqual(facts.remaining, []);
if (mode === "normal") {
assert.equal(status, 0);
assert.equal(output.normalized_result.validation, "valid");
} else {
assert.equal(status, 1);
assert.equal(
output.code,
name === "valid-recovered" ? "execution_failure" : "invalid_request",
);
if (name === "valid-recovered") {
assert.equal(
output.details?.partial_observation?.normalized_result?.validation,
"valid",
);
assert.equal(
output.details?.partial_observation?.evidence_id,
records[0].output.evidence_id,
);
}
if (mode === "input-once") {
assert.equal(
output.details?.issues?.[0]?.message,
"authored descriptor cleanup denial",
);
const closes = facts.trace.filter(
(fact) => fact.kind === "input-close",
);
assert.ok(closes.length >= 2);
assert.equal(closes[0].path, input);
assert.equal(closes[0].denied, true);
assert.ok(Number.isInteger(closes[0].descriptor));
for (const retry of closes.slice(1)) {
assert.equal(retry.path, input);
assert.equal(retry.descriptor, closes[0].descriptor);
assert.equal(retry.denied, false);
}
}
assert.notEqual(output.details?.cleanup, "incomplete");
assert.equal(output.details?.resources, undefined);
if (input === invalid) {
assert.equal(output.code, "invalid_request");
assert.ok(output.details?.captured_output?.stderr);
}
}
if (mode === "runtime-once") {
assert.equal(facts.trace.length, 2);
assert.equal(facts.trace[0].path, facts.trace[1].path);
assert.equal(facts.trace[0].denied, true);
assert.equal(facts.trace[1].exists, false);
}
records.push({ kind: "cli", name, status, output, ...facts });
}
for (const mode of ["runtime-once", "runtime-until-allowed"]) {
const c = await setup("mcp-" + mode, mode, valid);
const client = new Client({ name: "wabt-cleanup-probe", version: "1" });
const transport = new StdioClientTransport({
command: process.execPath,
args: ["--import", preload, entrypoint, "mcp"],
env: c.env,
stderr: "pipe",
});
const calls = [];
let callFailure;
try {
await client.connect(transport);
const request = {
name: "inspect_wasm_artifact",
arguments: { path: valid },
};
const call = () =>
client.callTool(request, { timeout: WABT_OPERATION_TIMEOUT_MS });
const first = await call();
calls.push(first);
assert.equal(requireMcpToolError(first).code, "cleanup_incomplete");
if (mode === "runtime-until-allowed") {
const second = await call();
calls.push(second);
assert.equal(requireMcpToolError(second).code, "cleanup_incomplete");
assert.equal(
(await observations(c)).remaining.filter((name) =>
name.startsWith("rea-wabt-"),
).length,
1,
);
await writeFile(join(c.temporary, "allow"), "restore");
}
const last = await call();
calls.push(last);
assert.notEqual(last.isError, true, JSON.stringify(last));
assert.equal(
last.structuredContent.normalized_result.validation,
"valid",
);
} catch (cause) {
callFailure = cause;
throw cause;
} finally {
await closeVerifierResources(
[() => client.close(), () => transport.close()],
callFailure,
);
}
const facts = await observations(c);
assert.deepEqual(
facts.remaining,
mode === "runtime-until-allowed" ? ["allow"] : [],
);
records.push({ kind: "mcp", mode, calls, ...facts });
}
return records.reduce(
(count, record) =>
count + (record.kind === "cli" ? 1 : record.calls.length),
0,
);
} catch (cause) {
primaryFailure = cause;
throw cause;
} finally {
await closeVerifierResources(
[() => rm(root, { recursive: true, force: true })],
primaryFailure,
);
}
}