From 7b77db7effa533c6ce038ff2e8ff6daebf1a5eb1 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 02:29:23 +0800 Subject: [PATCH] fix(reconstruction): include constituent unknowns in overall function claims Have the function observation owner select aggregate dimensions before residual-unknown lookup. Retain independent dimension scope, unknown citations and recommended probes through the public MCP verifier. Validation is pending the centrally coordinated lane; single-file formatting and lint completed. --- src/domain/reconstructionUnknowns.test.ts | 2 +- src/domain/reconstructionUnknowns.ts | 8 +- src/domain/reconstructionVerification.ts | 15 ++- .../mcp/investigationAnalysisMcp.test.ts | 108 ++++++++++++++++++ 4 files changed, 121 insertions(+), 12 deletions(-) diff --git a/src/domain/reconstructionUnknowns.test.ts b/src/domain/reconstructionUnknowns.test.ts index 9fa406b199..822dc55470 100644 --- a/src/domain/reconstructionUnknowns.test.ts +++ b/src/domain/reconstructionUnknowns.test.ts @@ -64,7 +64,7 @@ it("keeps explicit dependencies while excluding unrelated scoped unknowns", () = ]), comparisonId, [ev("b")], - "calls", + ["calls"], ); expect(selected.map(({ unknown_id }) => unknown_id)).toEqual([ calls.unknown_id, diff --git a/src/domain/reconstructionUnknowns.ts b/src/domain/reconstructionUnknowns.ts index c09e814eb0..55f05f25ce 100644 --- a/src/domain/reconstructionUnknowns.ts +++ b/src/domain/reconstructionUnknowns.ts @@ -28,17 +28,15 @@ export const reconstructionClaimUnknowns = ( heads: ReadonlyMap, comparisonId: string, sourceIds: readonly string[], - dimension?: string, + dimensions?: readonly string[], ): ResidualUnknown[] => { const appliesToClaim = (item: ResidualUnknown): boolean => { const scope = item.comparison_conclusion_scope; return ( scope === undefined || (scope.comparison_evidence_id === comparisonId && - (dimension === undefined || - scope.dimensions.some( - (scopedDimension) => scopedDimension === dimension, - ))) + (dimensions === undefined || + scope.dimensions.some((dimension) => dimensions.includes(dimension)))) ); }; const evidenceIds = new Set([comparisonId, ...sourceIds]); diff --git a/src/domain/reconstructionVerification.ts b/src/domain/reconstructionVerification.ts index 3224f3604a..f23e83d868 100644 --- a/src/domain/reconstructionVerification.ts +++ b/src/domain/reconstructionVerification.ts @@ -153,15 +153,13 @@ const evaluateClaim = ( ...authorityLimitations(claim, sources, sides), ...callCoverage.blockers, ]; - const relatedUnknowns = reconstructionClaimUnknowns( + const relevantUnknowns = reconstructionClaimUnknowns( heads, comparison.evidence_id, allSourceIds, - claim.dimension, + observation.selectedDimensions ?? [claim.dimension], ); - const relevantUnknowns = relatedUnknowns; const status = classify(observed, blockers, relevantUnknowns); - const displayedUnknowns = relevantUnknowns; return reconstructionClaimResultSchema.parse({ claim_id: claim.claim_id, kind: claim.kind, @@ -172,13 +170,13 @@ const evaluateClaim = ( left_evidence_ids: sides.left, right_evidence_ids: sides.right, evidence_links: uniqueSorted([comparison.evidence_id, ...allSourceIds]), - unknown_ids: displayedUnknowns.map(({ unknown_id: id }) => id), + unknown_ids: relevantUnknowns.map(({ unknown_id: id }) => id), limitations: uniqueSorted([ ...comparison.limitations, ...observation.limitations, ...blockers, ...callCoverage.diagnostics, - ...displayedUnknowns.map( + ...relevantUnknowns.map( ({ question }) => `Residual unknown: ${question}`, ), ]), @@ -279,6 +277,7 @@ const comparisonObservation = ( readonly status: ObservedStatus; readonly limitations: readonly string[]; readonly hasIndependentFunctionChange?: boolean; + readonly selectedDimensions?: readonly string[]; } => { if (claim.kind === "behavioral") { const parsed = readComparisonResult(evidence, "compare_process_captures"); @@ -306,6 +305,10 @@ const comparisonObservation = ( if (claim.dimension === "overall") return { status: result.status, + selectedDimensions: [ + "overall", + ...result.dimensions.map(({ dimension }) => dimension), + ], limitations: result.dimensions.flatMap( ({ limitations }) => limitations, ), diff --git a/tests/boundary/mcp/investigationAnalysisMcp.test.ts b/tests/boundary/mcp/investigationAnalysisMcp.test.ts index 6c2420c232..5144ec1494 100644 --- a/tests/boundary/mcp/investigationAnalysisMcp.test.ts +++ b/tests/boundary/mcp/investigationAnalysisMcp.test.ts @@ -592,3 +592,111 @@ const close = async ( ) => { await Promise.allSettled([client.close(), server.close(), session.close()]); }; + +it("keeps an aggregate function reconstruction unknown while a constituent dimension is unresolved", async () => { + const { session, server, client } = await connected(); + try { + const left = FUNCTION_COMPARISON_EXAMPLE.left; + const right = createEvidence( + { + path: "/tmp/rebuilt-function", + sha256: "2".repeat(64), + format: "mach-o", + }, + left.provider, + { + operation: left.operation, + parameters: left.parameters, + result: left.normalized_result, + confidence: left.confidence, + authority: left.authority, + relationshipCoverage: { status: "complete" }, + }, + ); + const compared = await client.callTool({ + name: "compare_functions", + arguments: { left, right }, + }); + expect(compared.isError).not.toBe(true); + const comparison = inlineEvidence(compared.structuredContent); + const specification = { + name: "Function reconstruction", + claims: ["overall", "pseudocode", "assembly"].map((dimension) => ({ + kind: "structural-function", + claim_id: dimension, + title: dimension, + dimension, + comparison_evidence_id: comparison.evidence_id, + })), + }; + const baseline = await client.callTool({ + name: "verify_reconstruction", + arguments: { specification }, + }); + expect( + inlineEvidence(baseline.structuredContent).normalized_result, + ).toMatchObject({ status: "pass" }); + const recorded = await client.callTool({ + name: "record_unknown", + arguments: { + question: "Does the pseudocode preserve the unresolved edge case?", + severity: "high", + domain: "function-comparison", + supporting_evidence_ids: [comparison.evidence_id], + contradicting_evidence_ids: [], + required_authority: null, + required_confidence: "derived", + required_environment: null, + recommended_probes: [ + { + operation: "analyze_function", + rationale: "Resolve the pseudocode edge case.", + }, + ], + relationships: [], + comparison_conclusion_scope: { + comparison_evidence_id: comparison.evidence_id, + dimensions: ["pseudocode"], + }, + }, + }); + expect(recorded.isError).not.toBe(true); + const result = await client.callTool({ + name: "verify_reconstruction", + arguments: { specification }, + }); + const payload = inlineEvidence(result.structuredContent).normalized_result; + expect(payload).toMatchObject({ + status: "unknown", + claims: { + items: [ + { claim_id: "assembly", status: "pass", unknown_ids: [] }, + { + claim_id: "overall", + status: "unknown", + unknown_ids: [expect.stringMatching(/^unk_/u)], + }, + { + claim_id: "pseudocode", + status: "unknown", + unknown_ids: [expect.stringMatching(/^unk_/u)], + }, + ], + }, + recommended_probes: [ + expect.objectContaining({ claim_ids: ["overall", "pseudocode"] }), + ], + }); + const overallOnly = await client.callTool({ + name: "verify_reconstruction", + arguments: { + specification: { ...specification, claims: [specification.claims[0]] }, + }, + }); + expect( + inlineEvidence(overallOnly.structuredContent).normalized_result, + ).toMatchObject({ status: "unknown", summary: { unknown: 1 } }); + } finally { + await close(session, server, client); + } +});