Files
rea/tests/acceptance/retainedComparisonAggregation.test.ts
morlutoandCursor Agent 4afb6b79ae fix(mcp): guide retained evidence input recovery (#8761)
* fix(mcp): guide retained evidence input recovery

* style(mcp): format catalog and server types

* test(mcp): verify HTTP capacity recovery guidance

* test(mcp): model catalog recorder imports in cleanup fixtures

record-mcp-catalog.mjs loads tool contracts and the Evidence-input
contract before it registers cleanup owners. The cleanup fixture still
sentineled only the previous imports, so those loads failed before any
owner wrote cleanup.jsonl. Track the script's read() calls and replace
the new modules with the empty contract list this fault injection uses.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-11-05 03:34:26 +08:00

200 lines
7.6 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it } from "vitest";
import { FUNCTION_COMPARISON_EXAMPLE } from "../../src/contracts/functionComparisonExample.js";
import { FUNCTION_COMPARISON_EVIDENCE } from "../../src/contracts/investigationExamples.js";
import { changedBehaviorResultSchema } from "../../src/domain/changedBehavior.js";
import { createEvidence, parseEvidence } from "../../src/domain/evidence.js";
import { createEvidenceBundle } from "../../src/domain/evidenceBundle.js";
import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js";
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
it("recovers a missing retained closure by importing the originating bundle", async () => {
const comparisons = [FUNCTION_COMPARISON_EVIDENCE];
const { call } = await connectLocalToolsMcp();
const input = { comparisons };
const retainedRequest = {
comparisons: [
{
kind: "retained-evidence",
evidence_id: FUNCTION_COMPARISON_EVIDENCE.evidence_id,
},
],
};
const requests = [
{
input,
// The inline comparison is already supplied; its missing dependencies
// still require their originating retained Evidence closure.
missingIds: FUNCTION_COMPARISON_EVIDENCE.evidence_links,
},
{
input: retainedRequest,
missingIds: [FUNCTION_COMPARISON_EVIDENCE.evidence_id],
},
];
for (const { input: request, missingIds } of requests) {
const missing = parseMcpToolError(
await call("find_changed_behavior", request),
).error;
expect(missing).toMatchObject({
code: "evidence_integrity_mismatch",
retryable: false,
details: {
reason: "missing",
reference_scope: "retained-closure",
actual: null,
},
});
expect(missingIds).toContain(missing.details?.evidence_id);
expect(missing.message).toContain("required retained Evidence closure");
expect(missing.remediation.action).toContain("originating Evidence bundle");
expect(JSON.stringify(missing)).not.toContain("inline Evidence");
}
const directory = await createTestTempDirectory("rea-comparison-recovery-");
const path = join(directory, "originating-bundle.json");
await writeFile(
path,
JSON.stringify(
createEvidenceBundle([
FUNCTION_COMPARISON_EXAMPLE.left,
FUNCTION_COMPARISON_EXAMPLE.right,
...comparisons,
]),
),
);
const imported = await call("import_evidence_bundle", { path });
expect(imported.isError, JSON.stringify(imported)).not.toBe(true);
const response = await call("find_changed_behavior", input);
expect(response.isError, JSON.stringify(response)).not.toBe(true);
const aggregate = parseEvidence(response.structuredContent);
expect(
changedBehaviorResultSchema.parse(aggregate.normalized_result)
.behavior_status,
).toBe("unknown");
expect(aggregate.evidence_links).toEqual(
expect.arrayContaining([
FUNCTION_COMPARISON_EXAMPLE.left.evidence_id,
FUNCTION_COMPARISON_EXAMPLE.right.evidence_id,
FUNCTION_COMPARISON_EVIDENCE.evidence_id,
]),
);
const retained = await call("find_changed_behavior", retainedRequest);
expect(retained.isError, JSON.stringify(retained)).not.toBe(true);
expect(parseEvidence(retained.structuredContent)).toEqual(aggregate);
});
it.each(["compact", "full"] as const)(
"aggregates retained observations within the %s input profile budget",
async (profile) => {
// Distinct authenticated observations may have identical derived contents.
const comparisons = Array.from({ length: 12 }, (_, observation) =>
createEvidence(undefined, FUNCTION_COMPARISON_EVIDENCE.provider, {
operation: "compare_functions",
predicateType: "rea.function-comparison",
parameters: { ...FUNCTION_COMPARISON_EVIDENCE.parameters, observation },
result: FUNCTION_COMPARISON_EVIDENCE.normalized_result,
confidence: "derived",
authority: "analyst-inference",
limitations: FUNCTION_COMPARISON_EVIDENCE.limitations,
evidenceLinks: FUNCTION_COMPARISON_EVIDENCE.evidence_links,
}),
);
const directory = await createTestTempDirectory(
"rea-retained-comparisons-",
);
const path = join(directory, "comparisons.json");
await writeFile(
path,
JSON.stringify(
createEvidenceBundle([
FUNCTION_COMPARISON_EXAMPLE.left,
FUNCTION_COMPARISON_EXAMPLE.right,
...comparisons,
]),
),
);
const { call } = await connectLocalToolsMcp({
REA_MCP_MAX_INPUT_ELEMENTS: "1024",
REA_MCP_INPUT_SCHEMA_PROFILE: profile,
});
// Both refusals happen before any call initializes the deferred runtime.
const inline = await call("find_changed_behavior", { comparisons });
expect(parseMcpToolError(inline).error).toMatchObject({
code: "resource_constraint",
remediation: { action: expect.stringContaining("retained-evidence") },
details: { reported_limits: { max_input_elements: 1024 } },
});
const unrelated = parseMcpToolError(
await call("read_bytes", {
address: "0x1000",
length: 1,
extra: Array.from({ length: 1024 }, () => 0),
}),
).error;
expect(unrelated.code).toBe("resource_constraint");
expect(unrelated.remediation.action).not.toContain("retained-evidence");
const inlineView = parseMcpToolError(
await call("inspect_analysis_view", {
source: {
kind: "inline",
evidence: {
...FUNCTION_COMPARISON_EXAMPLE.left,
limitations: Array.from({ length: 1024 }, () => "limit"),
},
},
view: { kind: "summary" },
}),
).error;
expect(inlineView.code).toBe("resource_constraint");
expect(inlineView.remediation.action).toContain("retained-evidence");
const referenceOnly = parseMcpToolError(
await call("export_environment_bundle", {
source: {
kind: "retained-evidence",
evidence_id: FUNCTION_COMPARISON_EVIDENCE.evidence_id,
},
output_directory: "/unused",
template_rules: Array.from({ length: 1024 }, () => "unused"),
}),
).error;
expect(referenceOnly.code).toBe("resource_constraint");
expect(referenceOnly.remediation.action).not.toContain("retained-evidence");
const imported = await call("import_evidence_bundle", { path });
expect(imported.isError, JSON.stringify(imported)).not.toBe(true);
const response = await call("find_changed_behavior", {
comparisons: comparisons.map(({ evidence_id }) => ({
kind: "retained-evidence",
evidence_id,
})),
});
expect(response.isError, JSON.stringify(response)).not.toBe(true);
const aggregate = parseEvidence(response.structuredContent);
const result = changedBehaviorResultSchema.parse(
aggregate.normalized_result,
);
expect(result.behavior_status).toBe("unknown");
expect(result.summary.observed_changes).toBe(0);
expect(aggregate.parameters.comparison_evidence_ids).toEqual(
comparisons.map(({ evidence_id }) => evidence_id),
);
expect(
new Set(
result.findings.items.map(
({ source_comparison_id }) => source_comparison_id,
),
),
).toEqual(new Set(comparisons.map(({ evidence_id }) => evidence_id)));
expect(aggregate.evidence_links).toEqual(
expect.arrayContaining([
FUNCTION_COMPARISON_EXAMPLE.left.evidence_id,
FUNCTION_COMPARISON_EXAMPLE.right.evidence_id,
...comparisons.map(({ evidence_id }) => evidence_id),
]),
);
},
);