diff --git a/docs/mcp-contracts.md b/docs/mcp-contracts.md index 66487bd010..5eedd9c14c 100644 --- a/docs/mcp-contracts.md +++ b/docs/mcp-contracts.md @@ -198,7 +198,9 @@ Custom clients upgrading from 6.1 should follow the Evidence-producing tools return the complete canonical Evidence record in both text and structured content. Read `structuredContent.normalized_result` for the -operation result and `structuredContent.evidence_id` for its identity. The same +operation result and `structuredContent.evidence_id` for its identity. +`raw_result` holds a provider representation distinct from that result, such +as an upstream tool's original report, and is `null` when there is none. The same record is retained in the session bundle. `analysis_profile` is always present: a concrete profile object or `null`; either value participates in semantic identity. Records omitting this field are rejected. Pass the returned Evidence diff --git a/src/application/AnalysisProvider.test.ts b/src/application/AnalysisProvider.test.ts new file mode 100644 index 0000000000..01b6afc17a --- /dev/null +++ b/src/application/AnalysisProvider.test.ts @@ -0,0 +1,22 @@ +import { expect, it } from "vitest"; + +import { createAnalysisExecution } from "./AnalysisProvider.js"; + +const PROVIDER = { id: "fixture", name: "Fixture", version: "1" }; + +it("records no raw result unless the provider supplies a distinct one", () => { + const result = { entries: [{ value: "normalized" }] }; + + expect(createAnalysisExecution(result, PROVIDER)).toMatchObject({ + result, + rawResult: null, + }); + expect( + createAnalysisExecution(result, PROVIDER, { + rawResult: { stdout: "raw" }, + }).rawResult, + ).toEqual({ stdout: "raw" }); + expect( + createAnalysisExecution(result, PROVIDER, { rawResult: null }).rawResult, + ).toBeNull(); +}); diff --git a/src/application/AnalysisProvider.ts b/src/application/AnalysisProvider.ts index 99328c1a57..a922cdb400 100644 --- a/src/application/AnalysisProvider.ts +++ b/src/application/AnalysisProvider.ts @@ -41,7 +41,13 @@ export interface AnalysisExecution { readonly subject: EvidenceSubjectTarget | null; } -/** Build a validated atomic provider observation at an adapter boundary. */ +/** + * Build a validated atomic provider observation at an adapter boundary. + * + * Pass `rawResult` only for a provider representation distinct from + * `result`. Omitting it records `null` rather than a second complete copy of + * the normalized result in every Evidence record and tool response. + */ export const createAnalysisExecution = ( result: unknown, provider: ProviderIdentity, @@ -56,7 +62,7 @@ export const createAnalysisExecution = ( result: jsonValueSchema.parse(result), rawResult: options.rawResult === undefined - ? jsonValueSchema.parse(result) + ? null : jsonValueSchema.parse(options.rawResult), provider, ...(options.analysisProfile === undefined diff --git a/tests/boundary/cli/dylibResolutionCli.test.ts b/tests/boundary/cli/dylibResolutionCli.test.ts index 7a6b12a4d6..c92d265b29 100644 --- a/tests/boundary/cli/dylibResolutionCli.test.ts +++ b/tests/boundary/cli/dylibResolutionCli.test.ts @@ -120,6 +120,9 @@ describe("trace-dylib-resolution CLI", () => { "Contents/Helpers/tool", "Contents/MacOS/App", ]); + // The normalized result is the provider's only representation; Evidence + // must not carry it a second time as raw_result. + expect(result.json).toMatchObject({ raw_result: null }); expect( trace.edges.map(({ loader, install_name: name, resolution }) => [ loader, diff --git a/tests/composition/directAnalysisCleanup.test.ts b/tests/composition/directAnalysisCleanup.test.ts index 22fd849428..fcbdbddcba 100644 --- a/tests/composition/directAnalysisCleanup.test.ts +++ b/tests/composition/directAnalysisCleanup.test.ts @@ -84,7 +84,7 @@ describe("one-shot analysis cleanup outcomes", () => { operation: "read_bytes", subject: { local_path: path }, normalized_result: { observation: "retained result" }, - raw_result: { observation: "retained result" }, + raw_result: null, }, }, }); @@ -198,7 +198,7 @@ describe("managed one-shot cleanup outcomes", () => { operation: "inspect_managed_artifact", observation: "retained result", }, - rawResult: { observation: "retained result" }, + rawResult: null, provider: { id: "fixture" }, }, },