fix(analysis-view): preserve inline Evidence error paths

Project authenticated inline Evidence failures at source.evidence while retaining their reasons and messages. Supply the matching request root to input issue projection so nested values remain identifiable.

Validation: both service entrypoints and SDK MCP integrity regressions, advertised-schema consumer suite, check:fast and architecture guards. No real-provider verification.
This commit is contained in:
morluto committed 2026-10-11 03:36:35 +08:00
1 parent 8fb79c67ee
commit 514df0918c
3 files changed
+65 -2

No files matched your search

@@ -11,6 +11,7 @@ import {
} from "../../../tests/fixtures/analysisView.js";
import {
inspectAnalysisView,
inspectAnalysisViewValidated,
summarizeRetainedAnalysis,
} from "./AnalysisViewService.js";
import type { Evidence } from "../../domain/evidence.js";
@@ -58,6 +59,35 @@ it.each(["/fixtures/unknown.exe", ""])(
},
);
it("reports inline Evidence identity failures at the complete source path", () => {
const parent = analysisViewLayoutEvidence();
const input = {
source: {
kind: "inline",
evidence: { ...parent, evidence_id: `ev_${"0".repeat(64)}` },
},
view: { kind: "summary" },
} as const;
for (const result of [
inspectAnalysisView(input),
inspectAnalysisViewValidated(input),
])
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisInputError",
issues: [
{
path: ["source", "evidence", "evidence_id"],
reason: "invalid_value",
message: "Evidence semantic identifier does not match its record",
},
],
},
});
});
it("projects a small native view from an authenticated >10 MiB retained dossier", () => {
const original = functionDossierSchema.parse(ghidraFunctionDossier());
const parent = createEvidence(
@@ -132,8 +132,11 @@ const resolveAnalysisViewSource = (
return err(
analysisInputErrorFromIssues(
INSPECT_ANALYSIS_VIEW_OPERATION,
cause.issues,
source.evidence,
cause.issues.map((issue) => ({
...issue,
path: ["source", "evidence", ...issue.path],
})),
{ source },
{ cause },
),
);
@@ -19,6 +19,7 @@ import {
analysisViewJavaScriptEvidence,
analysisViewJavaScriptAnalysisWithSource,
analysisViewBindJavaScriptGraphs,
analysisViewLayoutEvidence,
analysisViewLayoutFixture,
} from "../../fixtures/analysisView.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
@@ -111,6 +112,35 @@ it("advertises exact schemas and projects one layout section from retained Evide
);
});
it("reports a tampered inline Evidence ID at its full MCP source path", async () => {
const { client } = await connect();
const parent = analysisViewLayoutEvidence();
const response = await client.callTool({
name: "inspect_analysis_view",
arguments: {
source: {
kind: "inline",
evidence: { ...parent, evidence_id: `ev_${"0".repeat(64)}` },
},
view: { kind: "summary" },
},
});
expect(parseMcpToolError(response).error).toMatchObject({
code: "invalid_request",
category: "invalid_input",
details: {
issues: [
{
path: ["source", "evidence", "evidence_id"],
reason: "invalid_value",
message: "Evidence semantic identifier does not match its record",
},
],
},
});
});
it("delivers a bounded native dossier view over MCP without a Ghidra provider or lost connection", async () => {
const { client, session } = await connect();
const original = functionDossierSchema.parse(ghidraFunctionDossier());