From 9dd6cef90abf89f9191bce942e31900870231968 Mon Sep 17 00:00:00 2001 From: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Date: Tue, 6 Oct 2026 19:16:49 +0800 Subject: [PATCH] fix(process): scope comparison unknowns to their evidence (#732) --- src/server/registerProcessComparisonTool.ts | 6 ++- tests/boundary/mcp/serverUnknownsMcp.test.ts | 54 +++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/server/registerProcessComparisonTool.ts b/src/server/registerProcessComparisonTool.ts index 6fe33e8d6..6cbce0e8e 100644 --- a/src/server/registerProcessComparisonTool.ts +++ b/src/server/registerProcessComparisonTool.ts @@ -130,6 +130,7 @@ export const registerProcessComparisonTool = ( { left_evidence_id: leftRecord.evidence_id, right_evidence_id: rightRecord.evidence_id, + comparison_evidence_id: evidence.evidence_id, }, comparison, ), @@ -144,6 +145,7 @@ const comparisonUnknownInput = ( parsed: { readonly left_evidence_id: string; readonly right_evidence_id: string; + readonly comparison_evidence_id: string; }, comparison: ReturnType, ): RecordUnknownInput | undefined => { @@ -158,8 +160,10 @@ const comparisonUnknownInput = ( .filter(([, status]) => status !== "unchanged") .map(([scope]) => scope) .join(", "); + // Unknown identity uses the question, not its supporting records. Bind the + // question to this comparison so distinct capture pairs and policies coexist. return { - question: `Process captures disagree across: ${differingScopes}`, + question: `Process captures disagree across: ${differingScopes} (comparison ${parsed.comparison_evidence_id})`, severity: "high", domain: "process-comparison", supporting_evidence_ids: [parsed.left_evidence_id], diff --git a/tests/boundary/mcp/serverUnknownsMcp.test.ts b/tests/boundary/mcp/serverUnknownsMcp.test.ts index 88286dd86..57000324c 100644 --- a/tests/boundary/mcp/serverUnknownsMcp.test.ts +++ b/tests/boundary/mcp/serverUnknownsMcp.test.ts @@ -13,7 +13,8 @@ import { AnalysisCapabilityUnavailableError } from "../../../src/domain/analysis import { err } from "../../../src/domain/result.js"; import { observed as ok } from "../../fixtures/analysisExecution.js"; import { createServer } from "../../../src/server/createServer.js"; -import { createEvidence } from "../../../src/domain/evidence.js"; +import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js"; +import { parseEvidenceBundle } from "../../../src/domain/evidenceBundle.js"; import { processCaptureSchema } from "../../../src/domain/processCapture.js"; import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/contracts/processCaptureExample.js"; import { jsonValueSchema } from "../../../src/domain/jsonValue.js"; @@ -194,6 +195,7 @@ it("records capture disagreement as a contradicted unknown", async () => { }, }); expect(compared.isError).not.toBe(true); + const firstEvidence = parseEvidence(structured(compared).evidence); expect( structured(await client.callTool({ name: "list_unknowns", arguments: {} })), ).toMatchObject({ @@ -202,10 +204,58 @@ it("records capture disagreement as a contradicted unknown", async () => { { status: "contradicted", domain: "process-comparison", - question: "Process captures disagree across: interaction", + question: `Process captures disagree across: interaction (comparison ${firstEvidence.evidence_id})`, contradicting_evidence_ids: [rightEvidence.evidence_id], }, ], }, }); + const repeatedCapture = captureEvidence( + processCaptureSchema.parse({ + ...right, + interaction_events: right.interaction_events.map((event) => ({ + ...event, + data: "second observation", + })), + }), + ); + expect(session.recordEvidence(repeatedCapture).ok).toBe(true); + const secondComparison = await client.callTool({ + name: "compare_process_captures", + arguments: { left: leftEvidence, right: repeatedCapture }, + }); + expect(secondComparison.isError, JSON.stringify(secondComparison)).not.toBe( + true, + ); + const secondEvidence = parseEvidence(structured(secondComparison).evidence); + expect(secondEvidence.normalized_result).toMatchObject({ + status: "changed", + interaction: "added", + }); + expect(secondEvidence.evidence_id).not.toBe(firstEvidence.evidence_id); + + const unknowns = session.listUnknowns({ domain: "process-comparison" }); + expect(unknowns).toHaveLength(2); + expect(unknowns.map((unknown) => unknown.contradicting_evidence_ids)).toEqual( + expect.arrayContaining([ + [rightEvidence.evidence_id], + [repeatedCapture.evidence_id], + ]), + ); + const beforeRepeat = parseEvidenceBundle( + structured(await client.callTool({ name: "get_evidence_bundle" })).result, + ); + const repeatedComparison = await client.callTool({ + name: "compare_process_captures", + arguments: { left: leftEvidence, right: repeatedCapture }, + }); + expect(repeatedComparison.isError).not.toBe(true); + expect(parseEvidence(structured(repeatedComparison).evidence)).toEqual( + secondEvidence, + ); + expect( + parseEvidenceBundle( + structured(await client.callTool({ name: "get_evidence_bundle" })).result, + ), + ).toEqual(beforeRepeat); });