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 2026-10-11 03:33:34 +08:00
parent 8fb79c67ee
commit 514df0918c
3 changed files with 65 additions and 2 deletions

View File

@ -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(

View File

@ -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 },
),
);

View File

@ -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());