* 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>
200 lines
7.6 KiB
TypeScript
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),
|
|
]),
|
|
);
|
|
},
|
|
);
|