mirror of
https://github.com/morluto/rea
synced 2026-10-27 22:45:19 -04:00
a1b5d51a8d
* feat(go): inspect embedded binary build metadata * fix(go): preserve byte metadata and validate container mappings * fix(go): support moduleless legacy build metadata Recognize optional module string headers in declared zero-filled memory while rejecting ambiguous and unmapped pointers. Preserve the original module-pointer field as the zero-byte source anchor. Classify initial symlink-loop and overlong-path failures as invalid input, with CLI and MCP regressions. * fix(go): reject undersized PE optional headers * fix(native): stabilize ELF memory checks and clean up exec wrappers Use existing parent/group/run-token ownership for caller-selected Python launchers, which can exec through a shell or prlimit. Add cancellation regressions for the valid wrapper and wrong-parent/token cases. Replace the timing-dependent 30,000-section memory workload with a valid 32 MiB padded ELF under a 64 MiB address-space limit. Check the same image successfully through CLI and MCP before requiring a typed memory failure, preserving the production timeout and all error assertions. Validation: npm run check and check:fast passed; 61 focused tests passed including Go CLI/MCP and session snapshots. Real decoder probes with pinned packages passed on Python 3.12.3 and 3.12.14. The full local ELF verifier is blocked by this container's procps/process-ownership boundary; the exact pushed head requires GitHub's real-binary-layout lane. * test: repair Electron fixtures and stabilize catalog startup checks Declare Electron imports in option, preload override, and path-resolution fixtures after upstream began requiring proven API provenance. Retain all option and preload assertions and adjust exact source ranges for the added import line. Use the existing bounded acceptance timeout for the cold catalog lifecycle test instead of a separate 15-second ceiling. This retains the coverage allowance and all catalog, error, and cleanup assertions without retries. Validation: npm run check, final check:fast, formatting and diff checks passed. Source path regression and Go boundary/MCP/service checks passed. All 20 Go CLI cases and both supported-host runtime cases passed on rerun; two macOS-only cases skipped on Linux. A prior contended local run timed out once in a Go CLI diagnostic; that case passed on rerun. Electron process-boundary verification is blocked locally by procps namespace inspection and requires fresh CI. * test(mcp): isolate nested schema references between tool contracts Clear Ajv's non-meta schema registry after each root, including compilation failures. Disabling root registration alone leaves nested IDs available to later tools and can let a dangling reference pass the catalog gate. Add regressions for leakage after successful and failed compilation while retaining shared meta-schema validators. The regression failed before the fix; all 11 schema cases and full npm run check pass. Reviewed independently. * test(ci): align Go effects and source-map verification contracts Audit the Go inspector wording as static metadata inspection. Assert retained source declaration indexes in BOM reconstruction and the compiler-derived nested-map oracle. Keep a 45,000-map admission control below the 32 MiB output budget and the 50,000-map overflow case above it with the expanded representation. Validation: reproduced all four failing assertions, then passed 120 focused tests and the final admission suite. npm run check and real Chrome 154 / esbuild 0.25.10 source-map verification passed, including all four nested-map scenarios and verified process lineage. --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
270 lines
9.2 KiB
TypeScript
270 lines
9.2 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,
|
|
expandToolAvailability,
|
|
toolAvailabilityInventory,
|
|
} 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,
|
|
listed: Awaited<ReturnType<Client["listTools"]>>,
|
|
) => {
|
|
const binarySession = listed.tools.find(
|
|
({ name }) => name === "binary_session",
|
|
);
|
|
if (binarySession === undefined)
|
|
throw new Error("The compiled catalog omitted binary_session");
|
|
const status = await client.callTool(
|
|
{ name: "binary_session", arguments: {} },
|
|
{ toolDefinition: binarySession },
|
|
);
|
|
expect(status.isError).not.toBe(true);
|
|
const inventory = z
|
|
.object({
|
|
result: z.object({
|
|
tool_availability: toolAvailabilityInventory,
|
|
}),
|
|
})
|
|
.parse(status.structuredContent).result.tool_availability;
|
|
const availability = expandToolAvailability(inventory);
|
|
// 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)),
|
|
);
|
|
return status;
|
|
};
|
|
|
|
/**
|
|
* 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));
|
|
|
|
const callCurrentDocument = async (target?: {
|
|
readonly path: string;
|
|
readonly kind?: "database";
|
|
}) => {
|
|
const transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [mainPath],
|
|
cwd: process.cwd(),
|
|
env: {
|
|
PATH: process.env.PATH ?? "",
|
|
HOPPER_LAUNCHER_PATH: process.execPath,
|
|
...(target === undefined
|
|
? {}
|
|
: {
|
|
HOPPER_TARGET_PATH: target.path,
|
|
...(target.kind === undefined
|
|
? {}
|
|
: { HOPPER_TARGET_KIND: target.kind }),
|
|
}),
|
|
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);
|
|
result = await client.callTool({
|
|
name: "current_document",
|
|
arguments: {},
|
|
});
|
|
} finally {
|
|
await client.close();
|
|
await transport.close();
|
|
}
|
|
return { result, stderr, records: completeStderrRecords(stderr) };
|
|
};
|
|
|
|
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.
|
|
|
|
it("reports a missing target 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.stringContaining("No app is open."),
|
|
}),
|
|
]),
|
|
);
|
|
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({
|
|
path: process.execPath,
|
|
});
|
|
expect(result.isError).not.toBe(true);
|
|
const observation = evidenceResultOf(z.literal("fixture")).parse(
|
|
result.structuredContent,
|
|
);
|
|
const evidence = parseEvidence(observation);
|
|
expect(evidence.operation).toBe("current_document");
|
|
expect(evidence.provider.id).toBe("hopper");
|
|
expect(evidence.normalized_result).toBe(observation.normalized_result);
|
|
// Hopper's reply is the result; it is not repeated as raw_result.
|
|
expect(evidence.raw_result).toBeNull();
|
|
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,
|
|
);
|
|
|
|
// Use the suite's bounded acceptance deadline, including its coverage allowance.
|
|
// This cold-start workflow computes the growing catalog's schema digests; it
|
|
// verifies catalog and lifecycle behavior, not a 15-second performance target.
|
|
it("serves the compiled catalog without an initial target or fatal record", async () => {
|
|
const transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [mainPath],
|
|
cwd: process.cwd(),
|
|
env: {
|
|
PATH: process.env.PATH ?? "",
|
|
},
|
|
stderr: "pipe",
|
|
});
|
|
let stderr = "";
|
|
transport.stderr?.on("data", (chunk: Buffer | string) => {
|
|
stderr += chunk.toString();
|
|
});
|
|
const client = new Client({ name: "catalog-runtime", version: "1.0.0" });
|
|
|
|
try {
|
|
await client.connect(transport);
|
|
// Keep catalog parity independent of initial target/provider startup.
|
|
// Validate each called tool against its advertised definition without
|
|
// asking the SDK to compile every unrelated output schema in the catalog.
|
|
const listed = await client.listTools();
|
|
const status = await expectAvailableToolInventory(client, listed);
|
|
expect(
|
|
z
|
|
.object({ result: z.object({ open: z.boolean() }) })
|
|
.parse(status.structuredContent).result,
|
|
).toEqual({ open: false });
|
|
// Closing the empty session produces a structured lifecycle record,
|
|
// making the fatal-record check observable without launching a provider.
|
|
const closeBinary = listed.tools.find(
|
|
({ name }) => name === "close_binary",
|
|
);
|
|
if (closeBinary === undefined)
|
|
throw new Error("The compiled catalog omitted close_binary");
|
|
const closed = await client.callTool(
|
|
{ name: "close_binary", arguments: {} },
|
|
{ toolDefinition: closeBinary },
|
|
);
|
|
expect(closed.isError).not.toBe(true);
|
|
} 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);
|
|
expect(records).toContainEqual(
|
|
expect.objectContaining({ tool: "close_binary", status: "ok" }),
|
|
);
|
|
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([]);
|
|
});
|
|
|
|
it.runIf(process.platform === "darwin")(
|
|
"serves a database-kind initial target without a fatal record on macOS",
|
|
async () => {
|
|
const { result, records } = await callCurrentDocument({
|
|
path: fileURLToPath(import.meta.url),
|
|
kind: "database",
|
|
});
|
|
expect(result.isError).not.toBe(true);
|
|
evidenceResultOf(z.literal("fixture")).parse(result.structuredContent);
|
|
expect(records).toContainEqual(
|
|
expect.objectContaining({ tool: "current_document", status: "ok" }),
|
|
);
|
|
expect(
|
|
records.some(
|
|
(record) =>
|
|
typeof record === "object" &&
|
|
record !== null &&
|
|
"level" in record &&
|
|
typeof record.level === "number" &&
|
|
record.level >= 50,
|
|
),
|
|
).toBe(false);
|
|
},
|
|
15_000,
|
|
);
|
|
});
|