mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
Preserve format and Evidence semantics at their owning boundaries, with focused regression coverage and documentation where applicable. Fixes #6788
190 lines
6.2 KiB
TypeScript
190 lines
6.2 KiB
TypeScript
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
import { expect, it } from "vitest";
|
|
|
|
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
|
|
import { callPathResultSchema } from "../../../src/domain/callPathSchemas.js";
|
|
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
|
import { createServer } from "../../../src/server/createServer.js";
|
|
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
|
|
|
const observe = (
|
|
address: string,
|
|
callees: readonly string[],
|
|
unresolvedCalls: readonly { address: string; reason: string }[] = [],
|
|
) => {
|
|
const template = FUNCTION_COMPARISON_EXAMPLE.left;
|
|
if (template.subject === null)
|
|
throw new Error("Expected artifact-bound fixture");
|
|
const dossier = functionDossierSchema.parse(template.normalized_result);
|
|
return createEvidence(
|
|
{
|
|
path: template.subject.local_path,
|
|
sha256: template.subject.digest.sha256,
|
|
format: template.subject.format,
|
|
},
|
|
template.provider,
|
|
{
|
|
operation: "analyze_function",
|
|
parameters: { procedure: address },
|
|
result: {
|
|
...dossier,
|
|
procedure: { ...dossier.procedure, address },
|
|
callees: callees.map((callee) => ({
|
|
address: callee,
|
|
name: `fn_${callee}`,
|
|
classification: null,
|
|
body: dossier.procedure.body,
|
|
})),
|
|
callers: [],
|
|
unresolved_calls: [...unresolvedCalls],
|
|
limitations: [],
|
|
},
|
|
confidence: template.confidence,
|
|
authority: template.authority,
|
|
relationshipCoverage: { status: "complete" },
|
|
},
|
|
);
|
|
};
|
|
|
|
it("publishes explored extent without the terminal goal's callees", async () => {
|
|
const session = createTestBinarySession(() => {
|
|
throw new Error("Offline call paths must not launch a provider");
|
|
});
|
|
const server = createServer({ kind: "session", session });
|
|
const client = new Client({ name: "call-path-search-extent", version: "1" });
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
try {
|
|
await server.connect(serverTransport);
|
|
await client.connect(clientTransport);
|
|
const start = observe("0x1000", ["0x4000"]);
|
|
const goal = observe("0x4000", ["0x5000", "0x1000"]);
|
|
for (const evidence of [start, goal])
|
|
expect(session.recordEvidence(evidence).ok).toBe(true);
|
|
for (const { functions, target, hops, explored } of [
|
|
{
|
|
functions: [start],
|
|
target: "0x4000",
|
|
hops: 1,
|
|
explored: { nodes: 2, edges: 1, depth_reached: 1 },
|
|
},
|
|
{
|
|
functions: [start, goal],
|
|
target: "0x4000",
|
|
hops: 1,
|
|
explored: { nodes: 2, edges: 1, depth_reached: 1 },
|
|
},
|
|
{
|
|
functions: [start],
|
|
target: "0x1000",
|
|
hops: 0,
|
|
explored: { nodes: 1, edges: 0, depth_reached: 0 },
|
|
},
|
|
]) {
|
|
const reply = await client.callTool({
|
|
name: "build_call_path",
|
|
arguments: {
|
|
functions,
|
|
start: { address: "0x1000" },
|
|
goal: { address: target },
|
|
},
|
|
});
|
|
if (reply.isError) throw new Error(JSON.stringify(reply));
|
|
const result = callPathResultSchema.parse(
|
|
parseEvidence(reply.structuredContent).normalized_result,
|
|
);
|
|
expect(result).toMatchObject({
|
|
status: "found",
|
|
shortest_hops: hops,
|
|
search_scope: { exhaustive: true },
|
|
explored,
|
|
limitations: [],
|
|
});
|
|
}
|
|
} finally {
|
|
await Promise.allSettled([session.close(), client.close(), server.close()]);
|
|
}
|
|
});
|
|
|
|
it("preserves unresolved outgoing call caveats and bounds search completeness at expanded nodes", async () => {
|
|
const session = createTestBinarySession(() => {
|
|
throw new Error("Offline call paths must not launch a provider");
|
|
});
|
|
const server = createServer({ kind: "session", session });
|
|
const client = new Client({ name: "unresolved-call-path", version: "1" });
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
try {
|
|
await server.connect(serverTransport);
|
|
await client.connect(clientTransport);
|
|
const unresolved = {
|
|
address: "0x1008",
|
|
reason: "Computed target could not be resolved.",
|
|
};
|
|
const scenarios = [
|
|
{
|
|
start: observe("0x1000", [], [unresolved]),
|
|
terminal: undefined,
|
|
goal: "0x4000",
|
|
expected: {
|
|
status: "unknown",
|
|
search_scope: { exhaustive: false },
|
|
limitations: [
|
|
"Unresolved call at 0x1008: Computed target could not be resolved.",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
start: observe("0x1000", ["0x4000"], [unresolved]),
|
|
terminal: undefined,
|
|
goal: "0x4000",
|
|
expected: {
|
|
status: "found",
|
|
shortest_hops: 1,
|
|
search_scope: { exhaustive: false },
|
|
limitations: [
|
|
"Unresolved call at 0x1008: Computed target could not be resolved.",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
start: observe("0x1000", ["0x4000"]),
|
|
terminal: observe("0x4000", [], [unresolved]),
|
|
goal: "0x4000",
|
|
expected: {
|
|
status: "found",
|
|
shortest_hops: 1,
|
|
search_scope: { exhaustive: true },
|
|
limitations: [
|
|
"Unresolved call at 0x1008: Computed target could not be resolved.",
|
|
],
|
|
},
|
|
},
|
|
];
|
|
for (const scenario of scenarios) {
|
|
const functions = [
|
|
scenario.start,
|
|
...(scenario.terminal ? [scenario.terminal] : []),
|
|
];
|
|
for (const evidence of functions)
|
|
expect(session.recordEvidence(evidence).ok).toBe(true);
|
|
const reply = await client.callTool({
|
|
name: "build_call_path",
|
|
arguments: {
|
|
functions,
|
|
start: { address: "0x1000" },
|
|
goal: { address: scenario.goal },
|
|
},
|
|
});
|
|
if (reply.isError) throw new Error(JSON.stringify(reply));
|
|
const result = callPathResultSchema.parse(
|
|
parseEvidence(reply.structuredContent).normalized_result,
|
|
);
|
|
expect(result).toMatchObject(scenario.expected);
|
|
}
|
|
} finally {
|
|
await Promise.allSettled([session.close(), client.close(), server.close()]);
|
|
}
|
|
});
|