Files
rea/experiments/analysis-efficiency/wasm-string-framing.mjs
T

185 lines
6.4 KiB
JavaScript

/** Compare unchanged public WASM tasks; raw inputs, exports and transcripts stay outside Git. */
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 { join, resolve } from "node:path";
import { tmpdir } from "node:os";
import { finished } from "node:stream/promises";
import { parseArgs } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { createRetainedQueryCaller } from "./retained-query-calls.mjs";
import { finalizeRetainedQueryReceipt } from "./retained-query-finalization.mjs";
const { values } = parseArgs({
options: Object.fromEntries(
["runtime", "artifact", "wat", "tools", "output"].map((name) => [
name,
{ type: "string" },
]),
),
});
for (const name of ["runtime", "artifact", "wat", "tools", "output"])
assert.ok(
values[name] && values[name] === resolve(values[name]),
`${name} must be absolute`,
);
const hash = (value) => createHash("sha256").update(value).digest("hex");
const bytes = await readFile(values.artifact);
const nativeWat = await readFile(values.wat, "utf8");
// Native compilation validates the independent name inventory; no instance or target code runs.
const module = new WebAssembly.Module(bytes);
const nativeImports = WebAssembly.Module.imports(module);
const nativeExports = WebAssembly.Module.exports(module);
await mkdir(values.output, { mode: 0o700 });
const account = await mkdtemp(join(tmpdir(), "rea-wat-framing-"));
const client = new Client({ name: "wasm-string-framing", 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",
REA_WABT_BIN_DIRECTORY: values.tools,
TMPDIR: account,
},
stderr: "pipe",
});
const serverLog = createWriteStream(join(values.output, "server.log"));
const serverLogFinished = finished(serverLog);
serverLogFinished.catch(() => {});
transport.stderr?.pipe(serverLog);
const report = {
artifact_sha256: hash(bytes),
artifact_bytes: bytes.length,
calls: [],
answers: [],
};
const transcript = [];
const call = createRetainedQueryCaller(client, (receipt, event) => {
report.calls.push(receipt);
transcript.push(event);
});
const observe = async (name, input, phase) => {
const reply = await call(name, input, phase);
assert.notEqual(reply.isError, true, JSON.stringify(reply));
assert.ok(reply.structuredContent);
return reply.structuredContent;
};
const started = performance.now();
try {
await client.connect(transport);
const catalog = await client.listTools();
assert.equal(catalog.nextCursor, undefined);
await writeFile(join(values.output, "catalog.json"), JSON.stringify(catalog));
report.catalog_sha256 = hash(JSON.stringify(catalog));
report.catalog_tools = catalog.tools.length;
const definitions = new Map(
catalog.tools.map((definition) => [definition.name, definition]),
);
const originalCall = client.callTool.bind(client);
client.callTool = (input, options) =>
originalCall(input, {
...options,
toolDefinition: definitions.get(input.name),
});
const observations = [];
for (const phase of ["fresh", "repeat"]) {
const summary = await observe(
"inspect_wasm_artifact",
{ path: values.artifact, detail: "summary" },
phase,
);
const parentId = summary.normalized_result.parent_evidence_id;
const exports = await observe(
"inspect_analysis_view",
{
source: { kind: "retained-evidence", evidence_id: parentId },
view: { kind: "wasm", facet: "exports", offset: 0, limit: 4 },
},
`${phase}:exports`,
);
assert.equal(exports.normalized_result.parent_evidence_id, parentId);
observations.push({ summary, exports, parentId });
}
await observe(
"export_evidence_bundle",
{ path: join(values.output, "evidence.json") },
"export",
);
const bundle = JSON.parse(
await readFile(join(values.output, "evidence.json"), "utf8"),
);
const kind = (value) => (value === "func" ? "function" : value);
for (const { summary, exports, parentId } of observations) {
const parent = bundle.records.find(
(record) => record.evidence_id === parentId,
);
assert.ok(parent);
assert.equal(parent.subject.digest.sha256, report.artifact_sha256);
const facts = parent.normalized_result;
assert.equal(facts.validation, "valid");
assert.equal(facts.wat.text, nativeWat);
assert.equal(facts.wat.sha256, hash(nativeWat));
assert.equal(facts.interface.state, "complete");
assert.deepEqual(
facts.interface.entries
.filter((entry) => entry.direction === "import")
.map((entry) => ({
module: entry.module,
name: entry.name,
kind: kind(entry.kind),
})),
nativeImports,
);
assert.deepEqual(
facts.interface.entries
.filter((entry) => entry.direction === "export")
.map((entry) => ({ name: entry.name, kind: kind(entry.kind) })),
nativeExports,
);
assert.deepEqual(exports.normalized_result.item, facts.exports.slice(0, 4));
assert.equal(
summary.normalized_result.summary.available_counts.imports,
nativeImports.length,
);
assert.equal(
summary.normalized_result.summary.available_counts.exports,
nativeExports.length,
);
assert.equal(facts.runtime_execution, "not-performed");
assert.equal(facts.network_fetch, "not-performed");
report.answers.push({
parent_evidence_id: parentId,
normalized_sha256: hash(JSON.stringify(facts)),
exports: exports.normalized_result.item,
});
}
assert.deepEqual(report.answers[0], report.answers[1]);
assert.equal(hash(await readFile(values.artifact)), report.artifact_sha256);
report.status = "passed";
} catch (cause) {
report.status = "failed";
report.failure = String(cause?.stack ?? cause);
process.exitCode = 1;
} finally {
report.elapsed_ms_before_cleanup = performance.now() - started;
if (
!(await finalizeRetainedQueryReceipt({
report,
client,
transport,
account,
serverLog,
serverLogFinished,
output: values.output,
transcript,
}))
)
process.exitCode = 1;
}