From cc58263ee06ccf5f63eba5ee49b865138f2c4b58 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sun, 8 Nov 2026 04:29:36 +0800 Subject: [PATCH] fix(trace): disclose address-only reference scope (#8575) * fix(trace): disclose address-only reference scope * test(trace): assert qualified reference facts * test(contracts): distinguish address and process starts * fix(trace): clarify procedure interior reference scope --- src/application/WorkflowAnalysisProfile.ts | 2 +- src/application/WorkflowEvidence.ts | 7 +++++++ .../binary/AnalysisSnapshotCache.test.ts | 2 +- src/contracts/enhancedToolContracts.ts | 2 +- src/domain/native/literalFeatureTrace.ts | 12 ++++++++++-- .../enhancedTools.traceFeatureCoverage.test.ts | 18 +++++++++++++++++- 6 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/application/WorkflowAnalysisProfile.ts b/src/application/WorkflowAnalysisProfile.ts index 4219b0d2f6..49ebc54306 100644 --- a/src/application/WorkflowAnalysisProfile.ts +++ b/src/application/WorkflowAnalysisProfile.ts @@ -21,7 +21,7 @@ export const workflowAnalysisProfile = ( : operation === "batch_decompile" ? { workflow_revision: 4 } : operation === "trace_feature" - ? { workflow_revision: 8 } + ? { workflow_revision: 9 } : operation === "get_call_graph" ? { workflow_revision: 3 } : operation === "trace_call_path" || diff --git a/src/application/WorkflowEvidence.ts b/src/application/WorkflowEvidence.ts index fb0a22e425..39b44a096b 100644 --- a/src/application/WorkflowEvidence.ts +++ b/src/application/WorkflowEvidence.ts @@ -204,6 +204,10 @@ export const executeComposedWorkflow = async ( ); }; +/** Scope of the exact-address reference lookups composed by trace_feature. */ +export const TRACE_FEATURE_REFERENCE_LIMITATION = + "Reference queries cover only each matched start address, not interior bytes of strings and procedures. Empty references and truncated: false do not establish complete reference coverage."; + /** Build the shared Evidence record for a successful composed binary workflow. */ export const createWorkflowEvidence = (input: { readonly target: BinaryTarget | undefined; @@ -232,6 +236,9 @@ export const createWorkflowEvidence = (input: { : { evidenceLinks: input.upstreamEvidenceLinks }), limitations: [ "Derived by an REA composed workflow.", + ...(input.operation === "trace_feature" + ? [TRACE_FEATURE_REFERENCE_LIMITATION] + : []), ...input.upstreamLimitations, ], ...(input.operation === "batch_decompile" diff --git a/src/application/binary/AnalysisSnapshotCache.test.ts b/src/application/binary/AnalysisSnapshotCache.test.ts index c593fb3389..f54118f1ec 100644 --- a/src/application/binary/AnalysisSnapshotCache.test.ts +++ b/src/application/binary/AnalysisSnapshotCache.test.ts @@ -1595,7 +1595,7 @@ it("revises trace workflow profiles so earlier results are not replayed", () => expect( workflowAnalysisProfile(profile, "trace_feature").parameters .workflow_revision, - ).toBe(8); + ).toBe(9); expect(workflowAnalysisProfile(profile, "trace_feature")).not.toEqual( workflowAnalysisProfile(profile), diff --git a/src/contracts/enhancedToolContracts.ts b/src/contracts/enhancedToolContracts.ts index 6c4063ac14..bb3d558e0a 100644 --- a/src/contracts/enhancedToolContracts.ts +++ b/src/contracts/enhancedToolContracts.ts @@ -82,7 +82,7 @@ export const ENHANCED_TOOL_CONTRACTS = [ ...FUNCTION_WORKFLOW_TOOL_CONTRACTS, enhanced( "trace_feature", - "Trace the complete literal query, preserving spaces, through every matching string and procedure and their xrefs. Preserve an explicit unavailable owner for a non-retryable containment capability refusal. Returns observations and residual unknowns without inferring reference kinds; truncated only reports an observed reference cap. For a procedure match, use procedure_references with direction incoming for provider-reported kinds when supported; it covers the procedure body, so compare target addresses when selecting exact-entry references. Select detail summary to retain complete Evidence and page matches, references and unknowns with inspect_analysis_view kind native-feature-trace.", + "Search provider string and procedure inventories for the complete literal query, preserving spaces, then query xrefs at each matched start address. References to interior bytes of strings and procedures are not searched. An empty result or truncated: false does not prove there are no references. Preserve an explicit unavailable owner for a non-retryable containment capability refusal; reference kinds remain unknown. For a procedure match, use procedure_references with direction incoming for provider-reported kinds when supported; it covers the procedure body, so compare target addresses when selecting exact-entry references. Select detail summary to retain complete Evidence and page matches, references and unknowns with inspect_analysis_view kind native-feature-trace.", enhancedInputSchemas.trace_feature.extend({ detail: analysisDetailSchema }), ), enhanced( diff --git a/src/domain/native/literalFeatureTrace.ts b/src/domain/native/literalFeatureTrace.ts index e1aed4d2b3..3e89c83320 100644 --- a/src/domain/native/literalFeatureTrace.ts +++ b/src/domain/native/literalFeatureTrace.ts @@ -26,8 +26,16 @@ export const literalFeatureTraceSchema = z.object({ query: z.string(), search_mode: z.literal("literal"), matches: z.array(literalFeatureMatchSchema), - references: z.array(literalFeatureReferenceSchema), - truncated: z.boolean(), + references: z + .array(literalFeatureReferenceSchema) + .describe( + "References to matched start addresses only; references to interior bytes of strings and procedures are not searched. Empty does not establish absence.", + ), + truncated: z + .boolean() + .describe( + "True only when a provider reports a reference cap; false does not establish complete reference coverage.", + ), residual_unknowns: z.array(z.string()), }); diff --git a/tests/boundary/mcp/enhancedTools.traceFeatureCoverage.test.ts b/tests/boundary/mcp/enhancedTools.traceFeatureCoverage.test.ts index b199ef8654..99fa648ef7 100644 --- a/tests/boundary/mcp/enhancedTools.traceFeatureCoverage.test.ts +++ b/tests/boundary/mcp/enhancedTools.traceFeatureCoverage.test.ts @@ -3,6 +3,7 @@ import { afterEach, describe, expect, it } from "vitest"; import type { AnalysisOperationPort } from "../../../src/application/AnalysisProvider.js"; import { createAnalysisExecution } from "../../../src/application/AnalysisProvider.js"; import { EnhancedTools } from "../../../src/application/EnhancedTools.js"; +import { TRACE_FEATURE_REFERENCE_LIMITATION } from "../../../src/application/WorkflowEvidence.js"; import { AnalysisCapabilityUnavailableError, AnalysisOutputError, @@ -137,7 +138,10 @@ const trace = async (analysis: AnalysisOperationPort) => { arguments: { query: "hello" }, }); expect(result.isError).not.toBe(true); - return result.structuredContent as { normalized_result: unknown }; + return result.structuredContent as { + normalized_result: unknown; + limitations: string[]; + }; }; it("reports known reference-discovery gaps without claiming truncation or a missing edge", async () => { @@ -430,6 +434,18 @@ it("retains incomplete cleanup when cancellation arrives during owner lookup", a }); describe("trace_feature reference coverage", () => { + it("reports the address-only scope when a match has no references", async () => { + const output = await trace(provider({ emptyXrefsOf: "0x10" })); + + expect(output.normalized_result).toEqual(expectedTrace([], [], ["0x10"])); + expect(output.limitations).toContain(TRACE_FEATURE_REFERENCE_LIMITATION); + expect(TRACE_FEATURE_REFERENCE_LIMITATION).toMatch(/start address/); + expect(TRACE_FEATURE_REFERENCE_LIMITATION).toMatch( + /interior bytes of strings and procedures/, + ); + expect(TRACE_FEATURE_REFERENCE_LIMITATION).toMatch(/truncated: false/); + }); + it("marks the trace truncated for the target whose references the provider capped", async () => { const output = await trace( provider({ coverage: { "0x10": true, "0x11": false } }),