mirror of
https://github.com/morluto/rea.git
synced 2026-10-28 18:35:53 +00:00
* fix(comparisons)!: preserve unknowns across partial observations Share absence and digest semantics, keep unobserved managed bodies unknown, and match unique managed type/name/signatures before structural fallback. BREAKING CHANGE: Managed comparison metadata adds exact-signature matching and its count, and name_matching now reports exact-signature-fallback. * fix(javascript): preserve projected result binding semantics Share syntax-aware property reads and assigned-result resolution. Retain destructuring paths and downgrade downstream whole-result relationships instead of resolving nested or wrapped calls as aliases. * fix(native)!: admit complete parser values and preserve typed failures Decode Mach headers by named columns, validate complete slice keys and lipo alignment, and preserve codesign runtime flags and tagged parser errors. Rename the Ghidra test dossier fixture and connect fake-backed suites to real verification lanes. BREAKING CHANGE: Native load-command results no longer contain the unused, always-null file_offset field. * fix(process): preserve host identity and actionable boundary failures Thread selected platform and environment through process and Doctor seams, stamp observed producer identity in Evidence, and retain PTY, filesystem, lease and lazy-provider failure causes. Drain cancelled lazy loads and omit exited descendants from verified lineage. * fix(browser): preserve cleanup and document-transition evidence Make V8 and Playwright teardown cancellation-aware, expose primary failures alongside incomplete cleanup, share frame-tree and navigation rules, and clear stale WebMCP registrations without pruning unrelated child frames. * fix(adapters): align managed targets and typed CLI outcomes Inspect managed PE/CLI targets independently of native provider selection, reuse successful managed selections, and keep evidence bound to the actual inspected target. Classify canonical CLI error envelopes and exhaustive setup/update/uninstall outcomes, audit registered CLI coverage, and assert platform-specific runtime results. * docs: refresh boundary contracts and shipped roadmap behavior
208 lines
7.4 KiB
TypeScript
208 lines
7.4 KiB
TypeScript
import { fileURLToPath } from "node:url";
|
|
|
|
import { Client } from "@modelcontextprotocol/client";
|
|
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
import { describe, expect, it } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { CATALOG_IDENTITY } from "../../../src/catalogIdentity.js";
|
|
import { TOOL_CONTRACTS } from "../../../src/contracts/toolContracts.js";
|
|
import { evidenceResultOf } from "../../../src/contracts/toolOutputSchemaPrimitives.js";
|
|
import { parseEvidence } from "../../../src/domain/evidence.js";
|
|
|
|
const mainPath = fileURLToPath(
|
|
new URL("../../../dist/main.js", import.meta.url),
|
|
);
|
|
const fixturePath = fileURLToPath(
|
|
new URL("../../fixtures/fakeLauncher.mjs", import.meta.url),
|
|
);
|
|
|
|
const expectAvailableToolInventory = async (client: Client): Promise<void> => {
|
|
const listed = await client.listTools();
|
|
const status = await client.callTool({
|
|
name: "binary_session",
|
|
arguments: {},
|
|
});
|
|
const availability = z
|
|
.object({
|
|
result: z.object({
|
|
tool_availability: z.array(
|
|
z.object({ name: z.string(), available: z.boolean() }),
|
|
),
|
|
}),
|
|
})
|
|
.parse(status.structuredContent).result.tool_availability;
|
|
// This asserts the compiled entrypoint matches the source catalog. When it
|
|
// fails, check that `dist/` is current (`npm run build:cached`) before
|
|
// suspecting a registration bug -- a stale build is by far the likeliest
|
|
// cause of a pure count mismatch here.
|
|
expect(
|
|
availability.length,
|
|
"compiled tool inventory is stale; run npm run build:cached",
|
|
).toBe(CATALOG_IDENTITY.counts.mcp_tools);
|
|
expect(new Set(listed.tools.map(({ name }) => name))).toEqual(
|
|
new Set(TOOL_CONTRACTS.map(({ name }) => name)),
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Parses complete newline-delimited records, dropping any partial trailing
|
|
* line so a chunk boundary cannot turn into a JSON parse error.
|
|
*/
|
|
const completeStderrRecords = (stderr: string): unknown[] =>
|
|
stderr
|
|
.split("\n")
|
|
.slice(0, -1)
|
|
.map((line) => line.trim())
|
|
.filter((line) => line.length > 0)
|
|
.map((line): unknown => JSON.parse(line));
|
|
|
|
describe("production stdio runtime", () => {
|
|
// The `test:acceptance` lane runs the real entrypoint on the current host;
|
|
// these cases state each supported host's result in the test name and body.
|
|
const callCurrentDocument = async () => {
|
|
const transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [mainPath],
|
|
cwd: process.cwd(),
|
|
env: {
|
|
PATH: process.env.PATH ?? "",
|
|
HOPPER_LAUNCHER_PATH: process.execPath,
|
|
HOPPER_TARGET_PATH: process.execPath,
|
|
HOPPER_LOADER_ARGS_JSON: JSON.stringify([fixturePath]),
|
|
},
|
|
stderr: "pipe",
|
|
});
|
|
let stderr = "";
|
|
transport.stderr?.on("data", (chunk: Buffer | string) => {
|
|
stderr += chunk.toString();
|
|
});
|
|
const client = new Client({ name: "runtime-smoke", version: "1.0.0" });
|
|
|
|
let result: Awaited<ReturnType<Client["callTool"]>>;
|
|
try {
|
|
await client.connect(transport);
|
|
await expectAvailableToolInventory(client);
|
|
result = await client.callTool({
|
|
name: "current_document",
|
|
arguments: {},
|
|
});
|
|
} finally {
|
|
await client.close();
|
|
await transport.close();
|
|
}
|
|
return { result, stderr, records: completeStderrRecords(stderr) };
|
|
};
|
|
|
|
it.runIf(process.platform === "linux")(
|
|
"reports the Linux current-document failure through MCP and structured logs",
|
|
async () => {
|
|
const { result, stderr, records } = await callCurrentDocument();
|
|
expect(result.isError).toBe(true);
|
|
expect(result.content).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ type: "text", text: expect.any(String) }),
|
|
]),
|
|
);
|
|
expect(records).toContainEqual(
|
|
expect.objectContaining({
|
|
application: "rea",
|
|
mode: "mcp",
|
|
layer: "server",
|
|
tool: "current_document",
|
|
status: "error",
|
|
}),
|
|
);
|
|
expect(stderr).not.toContain("HOPPER_LOADER_ARGS_JSON");
|
|
expect(stderr).not.toContain(fixturePath);
|
|
},
|
|
15_000,
|
|
);
|
|
|
|
it.runIf(process.platform === "darwin")(
|
|
"returns the selected document on macOS through MCP and structured logs",
|
|
async () => {
|
|
const { result, stderr, records } = await callCurrentDocument();
|
|
expect(result.isError).not.toBe(true);
|
|
const observation = evidenceResultOf(z.literal("fixture")).parse(
|
|
result.structuredContent,
|
|
);
|
|
const evidence = parseEvidence(observation.evidence);
|
|
expect(evidence.operation).toBe("current_document");
|
|
expect(evidence.provider.id).toBe("hopper");
|
|
expect(evidence.normalized_result).toBe(observation.result);
|
|
expect(records).toContainEqual(
|
|
expect.objectContaining({
|
|
application: "rea",
|
|
mode: "mcp",
|
|
layer: "server",
|
|
tool: "current_document",
|
|
status: "ok",
|
|
}),
|
|
);
|
|
expect(stderr).not.toContain("HOPPER_LOADER_ARGS_JSON");
|
|
expect(stderr).not.toContain(fixturePath);
|
|
},
|
|
15_000,
|
|
);
|
|
|
|
it("starts with a database-kind initial target without a fatal record", async () => {
|
|
const transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [mainPath],
|
|
cwd: process.cwd(),
|
|
env: {
|
|
PATH: process.env.PATH ?? "",
|
|
HOPPER_LAUNCHER_PATH: process.execPath,
|
|
HOPPER_TARGET_PATH: new URL(import.meta.url).pathname,
|
|
HOPPER_TARGET_KIND: "database",
|
|
HOPPER_LOADER_ARGS_JSON: JSON.stringify([fixturePath]),
|
|
},
|
|
stderr: "pipe",
|
|
});
|
|
let stderr = "";
|
|
transport.stderr?.on("data", (chunk: Buffer | string) => {
|
|
stderr += chunk.toString();
|
|
});
|
|
const client = new Client({ name: "database-runtime", version: "1.0.0" });
|
|
|
|
try {
|
|
await client.connect(transport);
|
|
// The catalog itself is already proven against the compiled entrypoint
|
|
// above, so this test asserts the part that is unique to a database-kind
|
|
// initial target: the server comes up and serves tools without a fatal
|
|
// startup record.
|
|
const listed = await client.listTools();
|
|
expect(listed.tools.length).toBeGreaterThan(0);
|
|
// A database-kind initial target must also serve a tool. Merely listing
|
|
// the catalog starts no bridge work, so without this call the child
|
|
// writes no startup records at all and the fatal-record assertion below
|
|
// would pass without observing anything.
|
|
await expect(
|
|
client.callTool({ name: "current_document", arguments: {} }),
|
|
).resolves.toBeDefined();
|
|
} finally {
|
|
await client.close();
|
|
await transport.close();
|
|
}
|
|
// The transport has closed, so the stderr pipe has ended and every record
|
|
// the child wrote has been delivered. Reading while the child is still
|
|
// running races the pipe and can observe an empty or partial buffer, which
|
|
// would silently miss the fatal record this test exists to catch.
|
|
const records = completeStderrRecords(stderr);
|
|
expect(
|
|
records.length,
|
|
"no startup records were observed, so the fatal-record check is vacuous",
|
|
).toBeGreaterThan(0);
|
|
const fatal = records.filter(
|
|
(record): record is { level: number } =>
|
|
typeof record === "object" &&
|
|
record !== null &&
|
|
"level" in record &&
|
|
typeof record.level === "number" &&
|
|
record.level >= 50,
|
|
);
|
|
expect(fatal).toEqual([]);
|
|
}, 15_000);
|
|
});
|