mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 00:36:35 +00:00
* fix(native)!: recognize identical function evidence * test(native): verify same-evidence dimension citations * fix(compare): reject impossible same-evidence changes A comparison of one Evidence record cannot report a changed dimension, unequal subject hashes, or unequal digests on an unchanged dimension. * test(compare): admit the partial same-evidence fixture explicitly --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
212 lines
7.5 KiB
TypeScript
212 lines
7.5 KiB
TypeScript
import { afterAll, expect, it } from "vitest";
|
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
import type { AnySchema } from "ajv";
|
|
import type { Client } from "@modelcontextprotocol/client";
|
|
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
|
|
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
|
import { createMcpSchemaValidation } from "../../fixtures/mcpSchemaValidation.js";
|
|
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
|
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
|
|
import {
|
|
createEvidence,
|
|
parseEvidence,
|
|
type Evidence,
|
|
} from "../../../src/domain/evidence.js";
|
|
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
|
import { functionComparisonResultSchema } from "../../../src/domain/functionComparisonSchemas.js";
|
|
|
|
const example = FUNCTION_COMPARISON_EXAMPLE.left;
|
|
const dossier = functionDossierSchema.parse(example.normalized_result);
|
|
const { symbol: _symbol, ...procedure } = dossier.procedure;
|
|
const observation = (request: string) =>
|
|
createEvidence(
|
|
{ path: "/tmp/fixture", sha256: "0".repeat(64), format: "mach-o" },
|
|
example.provider,
|
|
{
|
|
operation: "analyze_function",
|
|
parameters: { procedure: request },
|
|
result: {
|
|
...dossier,
|
|
procedure,
|
|
comments: [],
|
|
facet_availability: {
|
|
...dossier.facet_availability,
|
|
comments: { available: false, reason: "Fixture has no comments." },
|
|
},
|
|
},
|
|
confidence: "derived",
|
|
authority: "shipped-artifact",
|
|
relationshipCoverage: { status: "complete" },
|
|
},
|
|
);
|
|
const main = observation("main");
|
|
const another = observation("another-selector");
|
|
const schemaValidation = createMcpSchemaValidation();
|
|
afterAll(() => schemaValidation.clear());
|
|
const ref = (evidence_id: string) => ({
|
|
kind: "retained-evidence",
|
|
evidence_id,
|
|
});
|
|
|
|
const expectRejectedDimensionCitation = async (
|
|
client: Client,
|
|
comparison: Evidence,
|
|
) => {
|
|
const original = functionComparisonResultSchema.parse(
|
|
comparison.normalized_result,
|
|
);
|
|
const altered = (dimension: (typeof original.dimensions)[number]) =>
|
|
dimension.dimension === "comments"
|
|
? {
|
|
...dimension,
|
|
evidence_links: [main.evidence_id, another.evidence_id],
|
|
}
|
|
: dimension;
|
|
const result = functionComparisonResultSchema.parse({
|
|
...original,
|
|
dimensions: original.dimensions.map(altered),
|
|
changes: original.changes.map(altered),
|
|
});
|
|
const invalid = createEvidence(undefined, comparison.provider, {
|
|
predicateType: comparison.predicate_type,
|
|
operation: comparison.operation,
|
|
parameters: comparison.parameters,
|
|
result,
|
|
confidence: comparison.confidence,
|
|
authority: comparison.authority,
|
|
limitations: comparison.limitations,
|
|
evidenceLinks: [main.evidence_id, another.evidence_id],
|
|
});
|
|
const response = await client.callTool({
|
|
name: "find_changed_behavior",
|
|
arguments: { comparisons: [invalid] },
|
|
});
|
|
expect(response.isError).toBe(true);
|
|
expect(parseMcpToolError(response).error.code).toBe(
|
|
"evidence_integrity_mismatch",
|
|
);
|
|
};
|
|
|
|
it.each(["full", "compact"] as const)(
|
|
"compares the same authenticated observation while preserving incomplete facets (%s)",
|
|
async (profile) => {
|
|
const session = createTestBinarySession(() => {
|
|
throw new Error("offline comparison must not launch a provider");
|
|
});
|
|
expect(session.recordEvidence(main).ok).toBe(true);
|
|
expect(session.recordEvidence(another).ok).toBe(true);
|
|
const { client } = await createApplicationMcpHarness({
|
|
session,
|
|
schemaValidator: schemaValidation,
|
|
serverOptions: {
|
|
environment: { REA_MCP_OUTPUT_SCHEMA_PROFILE: profile },
|
|
},
|
|
});
|
|
const schema = (await client.listTools()).tools.find(
|
|
({ name }) => name === "compare_functions",
|
|
)?.outputSchema;
|
|
if (schema === undefined) throw new Error("Missing output schema");
|
|
const reply = await client.callTool({
|
|
name: "compare_functions",
|
|
arguments: { left: ref(main.evidence_id), right: ref(main.evidence_id) },
|
|
});
|
|
expect(reply.isError, JSON.stringify(reply.content)).not.toBe(true);
|
|
expect(
|
|
new Ajv2020({ strict: false }).validate(
|
|
schema as AnySchema,
|
|
reply.structuredContent,
|
|
),
|
|
).toBe(true);
|
|
const result = parseEvidence(reply.structuredContent).normalized_result;
|
|
expect(result).toMatchObject({
|
|
status: "unknown",
|
|
function_match: { status: "matched", method: "same-evidence" },
|
|
dimensions: expect.arrayContaining([
|
|
expect.objectContaining({ dimension: "identity", status: "unchanged" }),
|
|
expect.objectContaining({ dimension: "comments", status: "unknown" }),
|
|
]),
|
|
});
|
|
const comparison = parseEvidence(reply.structuredContent);
|
|
const behavior = await client.callTool({
|
|
name: "find_changed_behavior",
|
|
arguments: { comparisons: [ref(comparison.evidence_id)] },
|
|
});
|
|
expect(behavior.isError, JSON.stringify(behavior.content)).not.toBe(true);
|
|
const behaviorResult = parseEvidence(
|
|
behavior.structuredContent,
|
|
).normalized_result;
|
|
expect(behaviorResult).toMatchObject({
|
|
behavior_status: "unknown",
|
|
findings: {
|
|
items: expect.arrayContaining([
|
|
expect.objectContaining({ dimension: "comments", status: "unknown" }),
|
|
]),
|
|
},
|
|
});
|
|
expect(behaviorResult).toMatchObject({
|
|
findings: {
|
|
items: expect.not.arrayContaining([
|
|
expect.objectContaining({ dimension: "identity" }),
|
|
]),
|
|
},
|
|
});
|
|
const contradictory = createEvidence(undefined, comparison.provider, {
|
|
predicateType: comparison.predicate_type,
|
|
operation: comparison.operation,
|
|
parameters: {
|
|
left_evidence_id: main.evidence_id,
|
|
right_evidence_id: another.evidence_id,
|
|
},
|
|
result: comparison.normalized_result,
|
|
confidence: comparison.confidence,
|
|
authority: comparison.authority,
|
|
limitations: comparison.limitations,
|
|
evidenceLinks: [main.evidence_id, another.evidence_id],
|
|
});
|
|
const contradictoryBehavior = await client.callTool({
|
|
name: "find_changed_behavior",
|
|
arguments: { comparisons: [contradictory] },
|
|
});
|
|
expect(contradictoryBehavior.isError).toBe(true);
|
|
expect(parseMcpToolError(contradictoryBehavior).error.code).toBe(
|
|
"evidence_integrity_mismatch",
|
|
);
|
|
await expectRejectedDimensionCitation(client, comparison);
|
|
const distinct = await client.callTool({
|
|
name: "compare_functions",
|
|
arguments: {
|
|
left: ref(main.evidence_id),
|
|
right: ref(another.evidence_id),
|
|
},
|
|
});
|
|
expect(
|
|
parseEvidence(distinct.structuredContent).normalized_result,
|
|
).toMatchObject({
|
|
function_match: { status: "ambiguous" },
|
|
});
|
|
const rebound = await client.callTool({
|
|
name: "compare_functions",
|
|
arguments: {
|
|
left: main,
|
|
right: { ...main, caller_input_path: "/tmp/alternate-spelling" },
|
|
},
|
|
});
|
|
expect(
|
|
parseEvidence(rebound.structuredContent).normalized_result,
|
|
).toMatchObject({
|
|
function_match: { status: "matched", method: "same-evidence" },
|
|
});
|
|
const tampered = await client.callTool({
|
|
name: "compare_functions",
|
|
arguments: {
|
|
left: ref(main.evidence_id),
|
|
right: { ...main, normalized_result: { ...dossier, procedure } },
|
|
},
|
|
});
|
|
expect(tampered.isError).toBe(true);
|
|
expect(parseMcpToolError(tampered).error.code).toBe(
|
|
"evidence_integrity_mismatch",
|
|
);
|
|
},
|
|
);
|