164 lines
5.8 KiB
JavaScript
164 lines
5.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { createHash } from "node:crypto";
|
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
import { isAbsolute, resolve } from "node:path";
|
|
import { parseArgs } from "node:util";
|
|
import { Client } from "@modelcontextprotocol/client";
|
|
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
|
|
const { values } = parseArgs({
|
|
options: {
|
|
"runtime-root": { type: "string" },
|
|
revision: { type: "string" },
|
|
bundle: { type: "string" },
|
|
"inspection-reply": { type: "string" },
|
|
output: { type: "string" },
|
|
},
|
|
});
|
|
for (const key of ["runtime-root", "bundle", "inspection-reply", "output"])
|
|
assert.ok(values[key] && isAbsolute(values[key]), `Supply --${key} ABS`);
|
|
assert.ok(values.revision);
|
|
await mkdir(values.output, { mode: 0o700 });
|
|
const sha = (value) => createHash("sha256").update(value).digest("hex");
|
|
const bundleBytes = await readFile(values.bundle);
|
|
const oldBundle = JSON.parse(bundleBytes.toString("utf8"));
|
|
const oldWasm = oldBundle.records.find(
|
|
(record) => record.operation === "inspect_wasm_artifact",
|
|
);
|
|
assert.ok(oldWasm);
|
|
const inspectionBytes = await readFile(values["inspection-reply"]);
|
|
const oldInspection = JSON.parse(
|
|
inspectionBytes.toString("utf8"),
|
|
).structuredContent;
|
|
assert.equal(oldInspection.operation, "inspect_artifact");
|
|
const definitions = new Map();
|
|
const client = new Client({
|
|
name: "rea-real-evidence-migration",
|
|
version: "1",
|
|
});
|
|
// An absent explicitly selected WABT directory makes accidental re-analysis
|
|
// fail. Imported/portable views must operate on existing Evidence alone.
|
|
const transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [resolve(values["runtime-root"], "scripts/rea.mjs"), "mcp"],
|
|
env: {
|
|
...process.env,
|
|
REA_LOG_LEVEL: "silent",
|
|
NODE_OPTIONS: "--max-old-space-size=512",
|
|
REA_WABT_BIN_DIRECTORY: resolve(values.output, "absent-wabt"),
|
|
},
|
|
stderr: "pipe",
|
|
});
|
|
const calls = [];
|
|
const call = async (name, arguments_) => {
|
|
const reply = await client.callTool(
|
|
{ name, arguments: arguments_ },
|
|
{ toolDefinition: definitions.get(name), timeout: 120_000 },
|
|
);
|
|
await writeFile(
|
|
resolve(values.output, `call-${calls.length + 1}.json`),
|
|
JSON.stringify(reply),
|
|
);
|
|
assert.notEqual(reply.isError, true, JSON.stringify(reply));
|
|
const result = reply.structuredContent ?? JSON.parse(reply.content[0].text);
|
|
calls.push({
|
|
name,
|
|
arguments:
|
|
name === "inspect_analysis_view" && arguments_.source.kind === "inline"
|
|
? {
|
|
source: {
|
|
kind: "inline",
|
|
original_evidence_id: arguments_.source.evidence.evidence_id,
|
|
},
|
|
view: arguments_.view,
|
|
}
|
|
: arguments_,
|
|
evidence_id: result.evidence_id ?? null,
|
|
});
|
|
return result;
|
|
};
|
|
let report;
|
|
try {
|
|
await client.connect(transport);
|
|
transport.stderr?.resume();
|
|
for (const definition of (await client.listTools()).tools)
|
|
definitions.set(definition.name, definition);
|
|
await call("import_evidence_bundle", { path: values.bundle });
|
|
const source = {
|
|
kind: "retained-evidence",
|
|
evidence_id: oldWasm.evidence_id,
|
|
};
|
|
const summary = await call("inspect_analysis_view", {
|
|
source,
|
|
view: { kind: "summary" },
|
|
});
|
|
const exports = await call("inspect_analysis_view", {
|
|
source,
|
|
view: { kind: "wasm", facet: "exports", offset: 0, limit: 4 },
|
|
});
|
|
assert.equal(
|
|
summary.normalized_result.parent_evidence_id,
|
|
oldWasm.evidence_id,
|
|
);
|
|
assert.equal(summary.subject.digest.sha256, oldWasm.subject.digest.sha256);
|
|
assert.equal(
|
|
summary.normalized_result.summary.wat.sha256,
|
|
oldWasm.normalized_result.wat.sha256,
|
|
);
|
|
assert.deepEqual(
|
|
exports.normalized_result.item,
|
|
oldWasm.normalized_result.exports.slice(0, 4),
|
|
);
|
|
const inventory = await call("inspect_analysis_view", {
|
|
source: { kind: "inline", evidence: oldInspection },
|
|
view: { kind: "summary" },
|
|
});
|
|
assert.equal(
|
|
inventory.normalized_result.parent_evidence_id,
|
|
oldInspection.evidence_id,
|
|
);
|
|
const oldAtomic = oldInspection.normalized_result.substeps[0].evidence;
|
|
assert.deepEqual(
|
|
inventory.normalized_result.summary.manifest,
|
|
oldAtomic.normalized_result.manifest,
|
|
);
|
|
assert.deepEqual(inventory.normalized_result.summary.inventory_evidence_ids, [
|
|
oldAtomic.evidence_id,
|
|
]);
|
|
const exportedPath = resolve(values.output, "exported-after-import.json");
|
|
await call("export_evidence_bundle", { path: exportedPath });
|
|
const exported = JSON.parse(await readFile(exportedPath, "utf8"));
|
|
assert.deepEqual(
|
|
exported.records.find(
|
|
(record) => record.evidence_id === oldWasm.evidence_id,
|
|
),
|
|
oldWasm,
|
|
);
|
|
assert.equal(sha(await readFile(values.bundle)), sha(bundleBytes));
|
|
assert.equal(
|
|
sha(await readFile(values["inspection-reply"])),
|
|
sha(inspectionBytes),
|
|
);
|
|
report = {
|
|
runtime_revision: values.revision,
|
|
original_bundle_sha256: sha(bundleBytes),
|
|
original_inspection_reply_sha256: sha(inspectionBytes),
|
|
source_files_unchanged: true,
|
|
imported_wasm_source_evidence_id: oldWasm.evidence_id,
|
|
original_wasm_record_preserved_exactly: true,
|
|
typed_summary_and_first4_exports_match: true,
|
|
portable_old_inspection_and_atomic_inventory_identity_preserved: true,
|
|
original_inventory_evidence_id: oldAtomic.evidence_id,
|
|
calls,
|
|
scope:
|
|
"Actual old complete Evidence imported or passed inline to the new runtime; original authentication, IDs, records, source bytes and selected facts preserved. WABT is unavailable and no producer analysis is requested. Existing file import/export is used, not an engine database cache.",
|
|
};
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
await writeFile(
|
|
resolve(values.output, "summary.json"),
|
|
JSON.stringify(report, null, 2),
|
|
);
|
|
process.stdout.write(JSON.stringify(report) + "\n");
|