mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 09:52:27 +00:00
* fix(cli)!: retain repeated WASM input paths BREAKING CHANGE: caller_input_paths values may now be ordered arrays of strings as well as strings. Clients must accept both forms. * fix(capture): retain safe caller path selections * fix(evidence): retain auxiliary selections in direct summaries * test(wasm): separate SDK schema and acquired-input coverage
213 lines
6.7 KiB
TypeScript
213 lines
6.7 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { analysisErrorProjectionSchema } from "../../../src/contracts/errorSchemas.js";
|
|
import { toolContract } from "../../../src/contracts/toolContracts.js";
|
|
import {
|
|
createEvidence,
|
|
parseEvidence,
|
|
withEvidenceCallerInputPath,
|
|
} from "../../../src/domain/evidence.js";
|
|
import { wasmArtifactFixture } from "../../fixtures/wasm/artifact.js";
|
|
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
|
import { advertisedMcpToolSchemas } from "../../fixtures/advertisedMcpTool.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
const sha256 = (bytes: Buffer | string): string =>
|
|
createHash("sha256").update(bytes).digest("hex");
|
|
|
|
// These scripts exercise the public adapters with WABT-shaped responses. They
|
|
// do not establish how the real WABT release classifies the selected bytes.
|
|
cliTest.runIf(process.platform !== "win32")(
|
|
"retains selected, candidate, and glue facts after simulated WABT rejection through CLI and stdio MCP",
|
|
async ({ cli }) => {
|
|
const directory = await createTestTempDirectory("rea-wasm-acquired-");
|
|
const tools = join(directory, "tools");
|
|
await mkdir(tools);
|
|
for (const name of ["wasm-validate", "wasm-objdump", "wasm2wat"]) {
|
|
const path = join(tools, name);
|
|
const body =
|
|
name === "wasm-validate"
|
|
? "echo 'fixture validation rejected' >&2\nexit 1"
|
|
: "exit 0";
|
|
await writeFile(
|
|
path,
|
|
`#!/bin/sh
|
|
if [ "$1" = "--version" ]; then echo '1.0.42'; exit 0; fi
|
|
${body}
|
|
`,
|
|
{ mode: 0o700 },
|
|
);
|
|
await chmod(path, 0o700);
|
|
}
|
|
const selected = join(directory, "selected.wasm");
|
|
const candidate = join(directory, "candidate.wasm");
|
|
const glue = join(directory, "glue.js");
|
|
const selectedBytes = Buffer.from("0061736d01000000", "hex");
|
|
const candidateBytes = Buffer.from("independent candidate bytes");
|
|
const glueSource = 'fetch("./selected.wasm");';
|
|
const environment = {
|
|
HOME: directory,
|
|
XDG_CONFIG_HOME: directory,
|
|
XDG_CACHE_HOME: directory,
|
|
REA_WABT_BIN_DIRECTORY: tools,
|
|
};
|
|
await Promise.all([
|
|
writeFile(selected, selectedBytes),
|
|
writeFile(candidate, candidateBytes),
|
|
writeFile(glue, glueSource),
|
|
]);
|
|
const expected = {
|
|
artifact: {
|
|
path: selected,
|
|
sha256: sha256(selectedBytes),
|
|
bytes: selectedBytes.length,
|
|
},
|
|
candidates: [
|
|
{
|
|
path: selected,
|
|
sha256: sha256(selectedBytes),
|
|
bytes: selectedBytes.length,
|
|
},
|
|
{
|
|
path: candidate,
|
|
sha256: sha256(candidateBytes),
|
|
bytes: candidateBytes.length,
|
|
},
|
|
],
|
|
glue: [
|
|
{
|
|
artifact: {
|
|
path: glue,
|
|
sha256: sha256(glueSource),
|
|
bytes: Buffer.byteLength(glueSource),
|
|
},
|
|
parse_status: "complete",
|
|
references: [
|
|
{
|
|
value: "./selected.wasm",
|
|
candidate_paths: [selected],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
validation: "rejected-by-selected-profile",
|
|
tool_profile: { release: "1.0.42" },
|
|
exit_code: 1,
|
|
stderr: "fixture validation rejected\n",
|
|
};
|
|
const invocation = await cli.run({
|
|
arguments: [
|
|
"inspect-wasm-artifact",
|
|
"./selected.wasm",
|
|
"--candidate_paths",
|
|
"./candidate.wasm",
|
|
"--candidate_paths",
|
|
"./candidate.wasm",
|
|
"--glue_paths",
|
|
"./glue.js",
|
|
"--format",
|
|
"json",
|
|
],
|
|
environment,
|
|
cwd: directory,
|
|
});
|
|
expect(invocation.exitCode, invocation.stderr).toBe(1);
|
|
const cliError = analysisErrorProjectionSchema.parse(invocation.json);
|
|
expect(cliError).toMatchObject({
|
|
code: "invalid_request",
|
|
details: {
|
|
partial_observation: {
|
|
caller_input_paths: {
|
|
candidate_paths: ["./candidate.wasm", "./candidate.wasm"],
|
|
glue_paths: ["./glue.js"],
|
|
},
|
|
normalized_result: expected,
|
|
},
|
|
},
|
|
});
|
|
|
|
const { client, call } = await connectLocalToolsMcp(environment);
|
|
try {
|
|
const response = await call("inspect_wasm_artifact", {
|
|
path: selected,
|
|
candidate_paths: [candidate],
|
|
glue_paths: [glue],
|
|
});
|
|
const mcpError = parseMcpToolError(response).error;
|
|
expect(mcpError.code).toBe("invalid_request");
|
|
const partialReference = z
|
|
.object({
|
|
kind: z.literal("retained-evidence"),
|
|
evidence_id: z.string(),
|
|
})
|
|
.parse(
|
|
z
|
|
.object({ partial_observation: z.unknown() })
|
|
.passthrough()
|
|
.parse(mcpError.details).partial_observation,
|
|
);
|
|
const retainedReply = await call("get_evidence_bundle", {
|
|
detail: "complete",
|
|
});
|
|
expect(retainedReply.isError).not.toBe(true);
|
|
const retainedBundle = toolContract(
|
|
"get_evidence_bundle",
|
|
).outputSchema.parse(retainedReply.structuredContent).result;
|
|
if ("kind" in retainedBundle)
|
|
throw new Error("Expected complete retained Evidence");
|
|
const retained = parseEvidence(
|
|
retainedBundle.records.find(
|
|
(record) => record.evidence_id === partialReference.evidence_id,
|
|
),
|
|
);
|
|
expect(retained.evidence_id).toBe(partialReference.evidence_id);
|
|
expect(retained.normalized_result).toMatchObject(expected);
|
|
await client.ping();
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"advertises ordered WASM caller selections through the SDK output schema",
|
|
async () => {
|
|
const { client } = await connectLocalToolsMcp();
|
|
try {
|
|
const schema = await advertisedMcpToolSchemas(
|
|
client,
|
|
"inspect_wasm_artifact",
|
|
);
|
|
const valid = withEvidenceCallerInputPath(
|
|
createEvidence(
|
|
undefined,
|
|
{ id: "fixture-wabt", name: "fixture", version: "1" },
|
|
{
|
|
operation: "inspect_wasm_artifact",
|
|
parameters: {},
|
|
result: wasmArtifactFixture(),
|
|
confidence: "observed",
|
|
},
|
|
),
|
|
"./selected.wasm",
|
|
{
|
|
candidate_paths: ["./candidate.wasm", "./candidate.wasm"],
|
|
glue_paths: ["./glue.js"],
|
|
},
|
|
);
|
|
expect(
|
|
schema.ajv.validate(schema.outputSchema, valid),
|
|
JSON.stringify(schema.ajv.errors),
|
|
).toBe(true);
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
},
|
|
);
|