Files
reverse-engineering-anything/tests/boundary/mcp/managedArtifactSummaryMcp.test.ts
T
Kaoru0822andClaude Opus 5.5 7484401601 perf(managed): add retained member summary and pages (#3061)
* perf(managed): add retained member summary and pages

inspect_managed_members always returned the assembly-wide member_refs, so
one selected or unmatched type produced megabytes, and an unselected
ordinary assembly exceeded the MCP response budget. Add opt-in
detail: "summary" that retains the complete Evidence and returns identity,
selection with unmatched names, coverage, user-string coverage, limitations
and per-collection counts. Page original rows through inspect_analysis_view
with kind managed-members; each page names its collection so no row schema
is chosen by union order. Complete delivery stays the default.

Refs #3010

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(managed): validate a sealed member parent once across pages

Each managed-members page reparsed the whole parent report, about 35 ms
for an 18 MB assembly result regardless of the page size. The analysis
view service now reports whether the parent Evidence is a sealed
snapshot, and the member view reuses the validated report for that
sealed result through a weak reference. Unsealed input is still
validated on every call.

The round-trip test fixture now carries an exported type, an ldstr
literal, a field access and a call edge, and requires every collection
to be nonempty so a row schema that drops facts cannot pass on empty
arrays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(managed): keep delivery detail out of retained Evidence identity

Summary, omitted and complete delivery of the same managed inspection
now retain one parent record, as inspect_macho already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:16:38 -07:00

259 lines
10 KiB
TypeScript

import { writeFile, unlink } from "node:fs/promises";
import { join } from "node:path";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { Ajv2020 } from "ajv/dist/2020.js";
import { expect, it, onTestFinished } from "vitest";
import { composeBinarySession } from "../../../src/application/binary/BinarySessionComposition.js";
import { AnalysisProviderRegistry } from "../../../src/application/binary/AnalysisProviderRegistry.js";
import { inspectAnalysisView } from "../../../src/application/analysisView/AnalysisViewService.js";
import { ManagedStaticProvider } from "../../../src/dotnet/ManagedStaticProvider.js";
import { buildManagedPeFixture } from "../../../src/dotnet/ManagedPe.fixture.js";
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
import {
managedArtifactSummaryViewResultSchema,
analysisViewResultSchema,
} from "../../../src/domain/analysisView/analysisView.js";
import { managedArtifactInspectionSchema } from "../../../src/domain/managed/managedArtifact.js";
import { createServer } from "../../../src/server/createServer.js";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
const createHarness = async () => {
const session = composeBinarySession(new AnalysisProviderRegistry([]), [
new ManagedStaticProvider(),
]);
const harness = await createApplicationMcpHarness({ session });
onTestFinished(harness.close);
return harness;
};
it("returns a first-call overview and pages the unchanged source after its file disappears", async () => {
const directory = await createTestTempDirectory("rea-managed-summary-");
const path = join(directory, "fixture.dll");
await writeFile(
path,
buildManagedPeFixture({
references: ["mscorlib", "Another.Reference"],
customAttributeRowCount: 3,
}),
);
const { client, session } = await createHarness();
const catalog = (await client.listTools()).tools;
const definition = catalog.find((x) => x.name === "inspect_managed_artifact");
if (definition?.outputSchema === undefined)
throw new Error("Managed schemas are absent");
const outputSchema: Record<string, unknown> = definition.outputSchema;
const ajv = new Ajv2020({ strict: false, validateFormats: false });
expect(ajv.validateSchema(definition.inputSchema)).toBe(true);
expect(ajv.validateSchema(outputSchema)).toBe(true);
expect(
ajv.validate(
catalog.find((x) => x.name === "inspect_managed_native_boundaries")!
.inputSchema,
{ path, detail: "summary" },
),
).toBe(false);
const reply = await client.callTool({
name: "inspect_managed_artifact",
arguments: { path, detail: "summary" },
});
expect(reply.isError).not.toBe(true);
expect(
ajv.validate(outputSchema, reply.structuredContent),
JSON.stringify(ajv.errors),
).toBe(true);
const receipt = parseEvidence(reply.structuredContent);
const view = managedArtifactSummaryViewResultSchema.parse(
receipt.normalized_result,
);
const parent = session.evidenceById(view.parent_evidence_id);
if (parent === undefined)
throw new Error("Complete managed source was not retained");
const report = managedArtifactInspectionSchema.parse(
parent.normalized_result,
);
expect(parent.operation).toBe("inspect_managed_artifact");
expect(parent.parameters).toEqual({ path });
expect(receipt.evidence_id).not.toBe(parent.evidence_id);
expect(receipt.evidence_links).toEqual([parent.evidence_id]);
const { references, resources, attributes, ...identity } = report;
expect(view.summary).toMatchObject(identity);
expect(view.summary.reported_counts).toEqual({
references: references.length,
resources: resources.length,
attributes: attributes.length,
});
expect(view.summary).not.toHaveProperty("attributes");
for (const detail of [undefined, "complete"]) {
const complete = parseEvidence(
(
await client.callTool({
name: "inspect_managed_artifact",
arguments: { path, ...(detail === undefined ? {} : { detail }) },
})
).structuredContent,
);
expect(complete.operation).toBe("inspect_managed_artifact");
expect(complete.evidence_id).toBe(parent.evidence_id);
}
const reused = await client.callTool({
name: "inspect_managed_native_boundaries",
arguments: {},
});
expect(reused.isError).not.toBe(true);
expect(parseEvidence(reused.structuredContent).subject?.local_path).toBe(
path,
);
const original = JSON.stringify(parent);
await unlink(path);
for (const collection of ["references", "resources", "attributes"] as const) {
const result = await client.callTool({
name: "inspect_analysis_view",
arguments: {
source: { kind: "retained-evidence", evidence_id: parent.evidence_id },
view: { kind: "managed", collection, offset: 0, limit: 1 },
},
});
expect(result.isError).not.toBe(true);
const page = analysisViewResultSchema.parse(
parseEvidence(result.structuredContent).normalized_result,
);
if (page.kind !== "managed") throw new Error("Unexpected page kind");
expect(page.item).toEqual(report[collection].slice(0, 1));
expect(page.identity_scope.requires_mvid).toBe(report.module?.mvid ?? null);
expect(page.source_coverage).toEqual(report.coverage);
expect(page.coverage.total).toBe(report[collection].length);
}
expect(JSON.stringify(session.evidenceById(parent.evidence_id))).toBe(
original,
);
expect(
session
.exportEvidenceBundle()
.records.find((x) => x.evidence_id === parent.evidence_id),
).toEqual(parent);
const portable = inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: { kind: "summary" },
});
if (!portable.ok) throw portable.error;
expect(portable.value.normalized_result).toEqual(view);
}, 30000);
it("keeps malformed metadata, empty returned rows and historical null subjects explicit", async () => {
const directory = await createTestTempDirectory(
"rea-managed-unavailable-view-",
);
const path = join(directory, "malformed.dll");
await writeFile(
path,
buildManagedPeFixture({ corruptMetadataSignature: true }),
);
const { client } = await createHarness();
const completeReply = await client.callTool({
name: "inspect_managed_artifact",
arguments: { path },
});
expect(completeReply.isError).not.toBe(true);
const parent = parseEvidence(completeReply.structuredContent);
const report = managedArtifactInspectionSchema.parse(
parent.normalized_result,
);
expect(report.metadata.status).toBe("malformed");
const historical = createEvidence(undefined, parent.provider, {
operation: parent.operation,
parameters: parent.parameters,
result: parent.normalized_result,
limitations: parent.limitations,
authority: parent.authority,
});
const original = JSON.stringify(historical);
for (const view of [
{ kind: "summary" },
{ kind: "managed", collection: "attributes", offset: 0, limit: 3 },
]) {
const result = inspectAnalysisView({
source: { kind: "inline", evidence: historical },
view,
});
if (!result.ok) throw result.error;
expect(result.value.subject).toBeNull();
const projected = analysisViewResultSchema.parse(
result.value.normalized_result,
);
expect(projected.unknowns.join(" ")).toContain("no recorded subject");
if (projected.kind === "managed") {
expect(projected.item).toEqual(report.attributes);
expect(projected.source_coverage).toEqual(report.coverage);
expect(projected.source_metadata_status).toBe("malformed");
expect(projected.identity_scope.requires_mvid).toBeNull();
} else if (projected.kind === "summary") {
const summary =
managedArtifactSummaryViewResultSchema.parse(projected).summary;
expect(summary.classification).toEqual(report.classification);
expect(summary.coverage).toEqual(report.coverage);
expect(summary.metadata).toEqual(report.metadata);
} else throw new Error("Unexpected view");
}
expect(JSON.stringify(historical)).toBe(original);
const incompatible = createEvidence(
{ path, format: "pe", sha256: "b".repeat(64) },
parent.provider,
{
operation: parent.operation,
parameters: parent.parameters,
result: parent.normalized_result,
},
);
expect(
inspectAnalysisView({
source: { kind: "inline", evidence: incompatible },
view: { kind: "summary" },
}),
).toMatchObject({
ok: false,
error: { _tag: "EvidenceIntegrityError" },
});
}, 30000);
it("refuses selected delivery before file access when the server cannot retain Evidence", async () => {
const directory = await createTestTempDirectory("rea-managed-fixed-view-");
const path = join(directory, "fixture.dll");
await writeFile(path, buildManagedPeFixture());
const analysis = createTestBinarySession(() => {
throw new Error("Deep provider must not start");
});
const server = createServer({ kind: "fixed", analysis });
const client = new Client({ name: "managed-fixed-delivery", version: "1" });
onTestFinished(async () => {
await client.close();
await server.close();
await analysis.close();
});
const [a, b] = InMemoryTransport.createLinkedPair();
await server.connect(b);
await client.connect(a);
for (const name of ["inspect_managed_artifact", "inspect_managed_members"]) {
const rejected = await client.callTool({
name,
arguments: { path: join(directory, "absent.dll"), detail: "summary" },
});
expect(parseMcpToolError(rejected).error.code).toBe(
"capability_unavailable",
);
}
const complete = await client.callTool({
name: "inspect_managed_artifact",
arguments: { path },
});
expect(complete.isError).not.toBe(true);
expect(
toolContract("inspect_managed_artifact").outputSchema.safeParse(
complete.structuredContent,
).success,
).toBe(true);
}, 30000);