mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
* refactor(verify): give the android, electron, firmware, ghidra-seed, harmony, inspector and managed verifiers' roots to VerifierFixture Each root is removed only after the MCP clients, transports, owned processes and file handles that may still use it confirm cleanup; a failed cleanup retains and names the root. The verifier-fixture-root baseline shrinks from 90 files / 95 sites to 80 / 85. * fix(verify): keep nested Electron and inspector children owned by the fixture, and plain roots where Windows creates directories The Electron cancellation CLI and application, and each inspector target, now register their stop with the fixture (onceConfirmed keeps the early stop), so the root is removed only after they confirm. The android and inspector verifiers keep a plain directory as before: tools create directories beneath the root, which a private root blocks on Windows.
231 lines
7.8 KiB
JavaScript
231 lines
7.8 KiB
JavaScript
import { requireMcpOutput } from "../../lib/mcp-verifier-results.mjs";
|
|
import assert from "node:assert/strict";
|
|
import { execFile } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import { createReadStream } from "node:fs";
|
|
import { access, writeFile } from "node:fs/promises";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { promisify } from "node:util";
|
|
|
|
import { Client } from "@modelcontextprotocol/client";
|
|
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
|
|
import { parseEvidence } from "../../../dist/domain/evidence.js";
|
|
import { harmonyApplicationProjectionResultSchema } from "../../../dist/domain/harmony/harmonyApplication.js";
|
|
import fixture from "../../fixtures/harmony/pipeline.json" with { type: "json" };
|
|
import {
|
|
VerifierFixture,
|
|
withVerifierFixture,
|
|
} from "../../lib/verifier-fixture.mjs";
|
|
|
|
const repository = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
|
|
const hap = process.env.REA_HARMONY_TEST_HAP;
|
|
if (hap === undefined)
|
|
throw new Error(
|
|
"verify:harmony requires REA_HARMONY_TEST_HAP. Run npm run fixtures:harmony and set it to the downloaded file; see docs/guides/harmony/harmony-analysis.md.",
|
|
);
|
|
await access(hap);
|
|
const hashFile = async (path) => {
|
|
const hash = createHash("sha256");
|
|
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
return hash.digest("hex");
|
|
};
|
|
assert.equal(
|
|
await hashFile(hap),
|
|
fixture.sha256,
|
|
"Real HarmonyOS lane requires the pinned Harmony pipeline release fixture",
|
|
);
|
|
const entrypoint =
|
|
process.env.REA_HARMONY_ENTRYPOINT ?? resolve(repository, "scripts/rea.mjs");
|
|
const execute = promisify(execFile);
|
|
const path = resolve(hap);
|
|
const workspaceFixture = await VerifierFixture.open({
|
|
prefix: "rea-harmony-verify-",
|
|
});
|
|
const workspace = workspaceFixture.path;
|
|
|
|
await withVerifierFixture(workspaceFixture, async () => {
|
|
const inspectResponse = await execute(
|
|
process.execPath,
|
|
[entrypoint, "inspect-artifact", path, "--format", "json"],
|
|
{ cwd: repository, timeout: 150_000, maxBuffer: 64 * 1024 * 1024 },
|
|
);
|
|
const inspection = parseEvidence(JSON.parse(inspectResponse.stdout));
|
|
assert.equal(inspection.operation, "inspect_artifact");
|
|
assert.equal(inspection.subject?.format, "hap");
|
|
assert.equal(inspection.subject?.digest.sha256, fixture.sha256);
|
|
console.log("PASS CLI inspect_artifact");
|
|
|
|
const projectionInput = JSON.stringify({
|
|
inventory_evidence: [inspection],
|
|
});
|
|
const projectionFile = resolve(workspace, "projection-input.json");
|
|
await writeFile(projectionFile, projectionInput);
|
|
const projectionResponse = await execute(
|
|
process.execPath,
|
|
[
|
|
entrypoint,
|
|
"project-harmony-application-graph",
|
|
projectionFile,
|
|
"--format",
|
|
"json",
|
|
],
|
|
{ cwd: repository, timeout: 150_000, maxBuffer: 64 * 1024 * 1024 },
|
|
);
|
|
const projectionEvidence = parseEvidence(
|
|
JSON.parse(projectionResponse.stdout),
|
|
);
|
|
assert.equal(
|
|
projectionEvidence.operation,
|
|
"project_harmony_application_graph",
|
|
);
|
|
const projection = harmonyApplicationProjectionResultSchema.parse(
|
|
projectionEvidence.normalized_result,
|
|
);
|
|
const repeatResponse = await execute(
|
|
process.execPath,
|
|
[
|
|
entrypoint,
|
|
"project-harmony-application-graph",
|
|
projectionFile,
|
|
"--format",
|
|
"json",
|
|
],
|
|
{ cwd: repository, timeout: 150_000, maxBuffer: 64 * 1024 * 1024 },
|
|
);
|
|
assert.equal(
|
|
JSON.parse(repeatResponse.stdout).normalized_result.projection_id,
|
|
projection.projection_id,
|
|
"projection must be deterministic for the same inventory Evidence",
|
|
);
|
|
|
|
assert.equal(projection.root_format, "hap");
|
|
assert.equal(projection.packaging_model, "stage");
|
|
assert.deepEqual(
|
|
projection.components.manifests.map(({ path }) => path).sort(),
|
|
["module.json", "pack.info"],
|
|
);
|
|
assert.deepEqual(
|
|
projection.components.bytecode.map(({ path }) => path),
|
|
["ets/modules.abc"],
|
|
);
|
|
assert.deepEqual(projection.components.native_libraries, []);
|
|
assert.equal(
|
|
projection.components.resources.length,
|
|
8,
|
|
"resources.index plus every resources/ entry",
|
|
);
|
|
assert.ok(
|
|
projection.components.resources.some(
|
|
({ path }) => path === "resources.index",
|
|
),
|
|
);
|
|
assert.equal(projection.components.javascript.length, 0);
|
|
assert.equal(
|
|
projection.components.signing.length,
|
|
0,
|
|
"this fixture has no META-INF certificate entries; signing status is unverified",
|
|
);
|
|
assert.deepEqual(projection.runtime_families, ["ark"]);
|
|
assert.deepEqual(projection.bridge_candidates, []);
|
|
assert.deepEqual(projection.unavailable_components, []);
|
|
assert.deepEqual(projection.coverage, {
|
|
status: "complete-within-inventory",
|
|
inventory_complete: true,
|
|
});
|
|
assert.ok(
|
|
projection.limitations.some((limitation) =>
|
|
limitation.startsWith(
|
|
"Manifest, module, resource, and signing semantics require a dedicated HarmonyOS provider",
|
|
),
|
|
),
|
|
);
|
|
console.log("PASS CLI project_harmony_application_graph");
|
|
|
|
const transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [entrypoint, "mcp"],
|
|
cwd: repository,
|
|
env: process.env,
|
|
stderr: "pipe",
|
|
});
|
|
let stderr = "";
|
|
transport.stderr?.on("data", (chunk) => {
|
|
stderr += chunk.toString();
|
|
});
|
|
const client = new Client({
|
|
name: "rea-real-harmony-verifier",
|
|
version: "1",
|
|
});
|
|
try {
|
|
await client.connect(transport, { timeout: 30_000 });
|
|
await client.listTools();
|
|
const opened = await client.callTool(
|
|
{ name: "open_binary", arguments: { path } },
|
|
{ timeout: 150_000 },
|
|
);
|
|
assert.notEqual(opened.isError, true, JSON.stringify(opened));
|
|
assert.equal(opened.structuredContent?.result?.format, "hap");
|
|
assert.equal(opened.structuredContent?.result?.sha256, fixture.sha256);
|
|
const status = await client.callTool(
|
|
{ name: "binary_session", arguments: {} },
|
|
{ timeout: 150_000 },
|
|
);
|
|
assert.notEqual(status.isError, true, JSON.stringify(status));
|
|
assert.equal(status.structuredContent?.result?.open, true);
|
|
assert.equal(status.structuredContent?.result?.format, "hap");
|
|
const inspected = await client.callTool(
|
|
{ name: "inspect_artifact", arguments: {} },
|
|
{ timeout: 150_000 },
|
|
);
|
|
assert.notEqual(inspected.structuredContent, undefined);
|
|
const mcpInspection = parseEvidence(
|
|
requireMcpOutput(inspected, "inspect_artifact"),
|
|
);
|
|
assert.deepEqual(
|
|
mcpInspection.normalized_result,
|
|
inspection.normalized_result,
|
|
);
|
|
console.log("PASS MCP open/status/inspect_artifact parity");
|
|
const response = await client.callTool(
|
|
{
|
|
name: "project_harmony_application_graph",
|
|
arguments: { inventory_evidence: [mcpInspection] },
|
|
},
|
|
{ timeout: 150_000 },
|
|
);
|
|
assert.notEqual(response.structuredContent, undefined);
|
|
const mcpEvidence = parseEvidence(
|
|
requireMcpOutput(response, "project_harmony_application_graph"),
|
|
);
|
|
assert.deepEqual(
|
|
mcpEvidence.normalized_result,
|
|
projectionEvidence.normalized_result,
|
|
);
|
|
console.log("PASS MCP/CLI parity project_harmony_application_graph");
|
|
const closed = await client.callTool(
|
|
{ name: "close_binary", arguments: {} },
|
|
{ timeout: 150_000 },
|
|
);
|
|
assert.notEqual(closed.isError, true, JSON.stringify(closed));
|
|
const empty = await client.callTool(
|
|
{ name: "binary_session", arguments: {} },
|
|
{ timeout: 150_000 },
|
|
);
|
|
assert.notEqual(empty.isError, true, JSON.stringify(empty));
|
|
assert.equal(empty.structuredContent?.result?.open, false);
|
|
assert.equal(
|
|
await hashFile(path),
|
|
fixture.sha256,
|
|
"MCP workflow must preserve source bytes",
|
|
);
|
|
console.log("PASS MCP close and unchanged source bytes");
|
|
} catch (cause) {
|
|
process.stderr.write(stderr);
|
|
throw cause;
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
});
|