[PR #2348] [MERGED] fix(mcp): report references a provider truncated in trace_feature #5183

Closed
opened 2026-10-31 01:04:37 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/2348
Author: @nikolay-dementiev
Created: 10/12/2026
Status: ✅ Merged
Merged: 10/13/2026
Merged by: @morluto

Base: main ← Head: feat/trace-feature-xref-coverage


📝 Commits (10+)

  • d8b802b test(mcp): expect trace_feature to report references the provider truncated
  • 41708b0 fix(mcp): report references a provider truncated in trace_feature
  • 5c01da3 refactor(ida): report reference truncation whenever the runner observed an xrefs cap
  • 6160b6e test(mcp): cover session forwarding, per-target state, native reports and failures of the trace coverage
  • 142f8ad test(mcp): format the session forwarding test
  • 56a1f6f Merge remote-tracking branch 'upstream/main' into feat/trace-feature-xref-coverage
  • 86bec61 test(process): format processCaptureLifecycle.test.ts after the merge
  • b39c768 test(ida): declare the alignment each synthetic universal slice offset satisfies
  • 2808af0 Merge remote-tracking branch 'upstream/main' into feat/trace-feature-xref-coverage
  • a7948f3 Merge remote-tracking branch 'upstream/main' into feat/trace-feature-xref-coverage

📊 Changes

1 file changed (+5 additions, -0 deletions)

View changed files

📝 src/ida/IdaNativeProtocol.test.ts (+5 -0)

📄 Description

Summary

trace_feature always reported truncated: false with no residual unknowns, even when a provider capped the references it returned for a match. A provider can now report that an xrefs read was truncated, and the trace marks that target as a residual unknown. A provider that reports nothing produces the same trace as before.

Prerequisite for #2335 (headless IDA containing procedures); this change alone does not close it.

Problem and expected behavior

The IDA adapter reads references with upstream xrefs_to (limit 1000) and records more: true only as a diagnostic limitation string. EnhancedTools.#call keeps just the result, and traceLiteralFeature returns truncated: false, residual_unknowns: [] unconditionally, so a trace over a capped read looks complete.

Expected: when the provider explicitly reports that the references to a matched address were truncated, the trace keeps the references it received, sets truncated: true, and adds a residual unknown naming that address. An explicit "not capped" report and a provider that reports nothing leave the trace unchanged. No completeness is inferred from limitation text.

Change and scope

  • src/application/AnalysisProvider.ts: AnalysisExecution and createAnalysisExecution gain an optional referencesTruncated (true: the provider capped the xrefs read; false: it explicitly reported no cap; absent: it reports nothing). It is internal: it is not part of Evidence, tool output or the snapshot format.
  • src/ida/IdaOperationRunner.ts, src/ida/IdaSessionClient.ts: the IDA adapter sets it from the upstream more flag of xrefs_to; the existing limitation text is unchanged.
  • src/application/EnhancedLiteralTracing.ts, src/application/EnhancedTools.ts: trace_feature reads xrefs through #observe and, only when referencesTruncated is true, sets truncated: true and adds "References to were truncated by the provider; additional references may exist." once per address.
  • src/application/InvestigationProviders.ts: the trace_feature workflow profile revision is bumped, so earlier results are not replayed under the new semantics (same pattern as the graph and batch workflows).
  • src/application/binary/AnalysisSnapshotCache.ts: record does not store an execution whose referencesTruncated is true, because the snapshot format cannot carry the field and a replay would present a capped read as complete. Only IDA sets the field today and its operations are never cached.
  • Hopper, Ghidra and every other provider are unchanged and do not set the field, so their traces are identical to before (asserted).

Intentionally not included:

  • Supporting Evidence and evidence_links for the primitive reads behind a composed workflow.
  • Containing-procedure support for headless IDA, which makes trace_feature usable there (separate change).
  • No tool schema, CLI output or error code changes; truncated and residual_unknowns already exist in the output.

Contract and boundary impact

  • Semantic owner and earliest changed stage: the provider execution contract in src/application, set by the IDA adapter; consumed by the trace_feature workflow.
  • CLI and MCP/tool-catalog contract: none. Existing output fields carry the new values; the new execution field never appears in output (asserted).
  • Provider, bridge, target-format, or platform compatibility: IDA headless, attached and native profiles that report more; legacy get_xrefs_to reports no flag and stays silent.
  • Evidence, artifact, provenance, or reconstruction contract: unchanged for primitive Evidence. The composed workflow result differs only when a provider reports truncation.
  • Process execution, authorization, cleanup, or containment impact: none.
  • Generated metadata/package/installation impact: none.

Evidence and regression coverage

  • Tests added or updated: tests/boundary/mcp/enhancedTools.traceFeatureCoverage.test.ts (production MCP server over an in-memory transport, exact toEqual on the whole trace): a capped target is a residual unknown while an uncapped sibling is not and all references are kept; a truncation is not carried over to the next target that reports nothing; a target matched twice is reported once; an explicit "not capped" report leaves the trace complete; a provider that reports nothing, even with limitation text mentioning a cap, returns exactly the trace it returned before; neither spelling of the field appears in the output; a failed references read stops the trace without reading further targets; a cancelled trace reads no references. tests/composition/analysis-sessions/binarySession.test.ts: the session forwards the field on both execution paths and a truncated read is never replayed, while an explicit complete report is. src/ida/IdaReferenceCoverage.test.ts (real headless session client over the recording fake): more: true gives true and keeps the limitation, more: false gives false, no flag gives nothing, the legacy profile and other operations report nothing; src/ida/IdaNativeProtocol.test.ts asserts the native profile forwards the explicit flag. src/application/binary/AnalysisSnapshotCache.test.ts: a truncated execution is not replayed while a complete or silent one is, and the trace_feature profile is revised while other workflows keep theirs.
  • Base reproduction or other evidence: the new tests fail on a97b713f7 (RED commit d8b802b3a: 5 failed in the three files; the unchanged-trace test passes there by design).
  • Real IDA Professional 9.1 (headless idalib, ida-pro-mcp c133c38, macOS arm64): a fixed 37-step sweep over five fixtures (callees, call graphs, traces, build_call_path, error cases, universal and stripped binaries) gives results identical to a97b713f7, and npm run verify:ida passes.
  • Remaining proof gaps: Hopper and Ghidra were not run (no engines on this machine); real IDA cannot run trace_feature until containing procedures are supported for headless IDA.
  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run check:fast, npm run check, npm run docs:check, npm run verify:test-discovery (966 files) - green
  • npm run test:focused over the five affected test files - 87 passed
  • npm run test:fast - 408 files, 5310 passed, 7 skipped, 0 failed; npm run test:mcp - 103 files passed, 2 skipped, 447 passed, 3 skipped; npm run test:boundary -- tests/boundary/providers/ida after npm run build:cached - 14 passed
  • Mutation checks, each reverted: the IDA adapter never reporting fails 2; counting an absent field as truncated fails 1; removing the revision fails 1; removing the snapshot guard fails 1; deriving truncation from limitation text fails 2; a session that drops the field fails 2; reporting a capped target twice fails 1; leaking a differently spelled field into the output fails 6

macOS arm64 only; Linux and Windows were not executed.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. A trace_feature result for a provider that reports a truncated read now has truncated: true and a residual unknown.
  • Real Hopper/Ghidra, browser, or OS coverage: none; not applicable to the reported flag, which only the IDA adapter sets.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: no new capability, input or write path; no limitation text is parsed.

Review checklist

  • The change addresses a concrete problem, not an unscoped refactor.
  • The PR has one focused outcome, and the title follows type(scope): outcome.
  • Related issue is linked.
  • Tests cover changed behavior and failure paths.
  • Owning docs, contracts, and generated metadata are updated.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/2348 **Author:** [@nikolay-dementiev](https://github.com/nikolay-dementiev) **Created:** 10/12/2026 **Status:** ✅ Merged **Merged:** 10/13/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `feat/trace-feature-xref-coverage` --- ### 📝 Commits (10+) - [`d8b802b`](https://github.com/morluto/rea/commit/d8b802b3a035a762d49793cec03dbd9d627509cf) test(mcp): expect trace_feature to report references the provider truncated - [`41708b0`](https://github.com/morluto/rea/commit/41708b0928055da57e4595532eb36ad273e914b3) fix(mcp): report references a provider truncated in trace_feature - [`5c01da3`](https://github.com/morluto/rea/commit/5c01da3dbf80212e557ef9335c3237c76a6716af) refactor(ida): report reference truncation whenever the runner observed an xrefs cap - [`6160b6e`](https://github.com/morluto/rea/commit/6160b6eb8ee9f51a44ba534712cec0640a06e3c9) test(mcp): cover session forwarding, per-target state, native reports and failures of the trace coverage - [`142f8ad`](https://github.com/morluto/rea/commit/142f8ad693b4a32e80a2f15e14892802973bff72) test(mcp): format the session forwarding test - [`56a1f6f`](https://github.com/morluto/rea/commit/56a1f6fd6f3fc536218c7b91d271efebf26fae26) Merge remote-tracking branch 'upstream/main' into feat/trace-feature-xref-coverage - [`86bec61`](https://github.com/morluto/rea/commit/86bec61b8908e399d46f7c04b099a95d08fcad54) test(process): format processCaptureLifecycle.test.ts after the merge - [`b39c768`](https://github.com/morluto/rea/commit/b39c7683bacc6f8e00d33147171a3b7c7dc6b26b) test(ida): declare the alignment each synthetic universal slice offset satisfies - [`2808af0`](https://github.com/morluto/rea/commit/2808af0109d8bd9b25e8947c74ca440f4867d519) Merge remote-tracking branch 'upstream/main' into feat/trace-feature-xref-coverage - [`a7948f3`](https://github.com/morluto/rea/commit/a7948f36e2075f4ce7c6174bad4ce765563d74b1) Merge remote-tracking branch 'upstream/main' into feat/trace-feature-xref-coverage ### 📊 Changes **1 file changed** (+5 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `src/ida/IdaNativeProtocol.test.ts` (+5 -0) </details> ### 📄 Description ## Summary `trace_feature` always reported `truncated: false` with no residual unknowns, even when a provider capped the references it returned for a match. A provider can now report that an `xrefs` read was truncated, and the trace marks that target as a residual unknown. A provider that reports nothing produces the same trace as before. Prerequisite for #2335 (headless IDA containing procedures); this change alone does not close it. ## Problem and expected behavior The IDA adapter reads references with upstream `xrefs_to` (limit 1000) and records `more: true` only as a diagnostic limitation string. `EnhancedTools.#call` keeps just the result, and `traceLiteralFeature` returns `truncated: false, residual_unknowns: []` unconditionally, so a trace over a capped read looks complete. Expected: when the provider explicitly reports that the references to a matched address were truncated, the trace keeps the references it received, sets `truncated: true`, and adds a residual unknown naming that address. An explicit "not capped" report and a provider that reports nothing leave the trace unchanged. No completeness is inferred from limitation text. ## Change and scope - `src/application/AnalysisProvider.ts`: `AnalysisExecution` and `createAnalysisExecution` gain an optional `referencesTruncated` (true: the provider capped the `xrefs` read; false: it explicitly reported no cap; absent: it reports nothing). It is internal: it is not part of Evidence, tool output or the snapshot format. - `src/ida/IdaOperationRunner.ts`, `src/ida/IdaSessionClient.ts`: the IDA adapter sets it from the upstream `more` flag of `xrefs_to`; the existing limitation text is unchanged. - `src/application/EnhancedLiteralTracing.ts`, `src/application/EnhancedTools.ts`: `trace_feature` reads `xrefs` through `#observe` and, only when `referencesTruncated` is true, sets `truncated: true` and adds "References to <address> were truncated by the provider; additional references may exist." once per address. - `src/application/InvestigationProviders.ts`: the `trace_feature` workflow profile revision is bumped, so earlier results are not replayed under the new semantics (same pattern as the graph and batch workflows). - `src/application/binary/AnalysisSnapshotCache.ts`: `record` does not store an execution whose `referencesTruncated` is true, because the snapshot format cannot carry the field and a replay would present a capped read as complete. Only IDA sets the field today and its operations are never cached. - Hopper, Ghidra and every other provider are unchanged and do not set the field, so their traces are identical to before (asserted). Intentionally not included: - Supporting Evidence and `evidence_links` for the primitive reads behind a composed workflow. - Containing-procedure support for headless IDA, which makes `trace_feature` usable there (separate change). - No tool schema, CLI output or error code changes; `truncated` and `residual_unknowns` already exist in the output. ## Contract and boundary impact - Semantic owner and earliest changed stage: the provider execution contract in `src/application`, set by the IDA adapter; consumed by the `trace_feature` workflow. - CLI and MCP/tool-catalog contract: none. Existing output fields carry the new values; the new execution field never appears in output (asserted). - Provider, bridge, target-format, or platform compatibility: IDA headless, attached and native profiles that report `more`; legacy `get_xrefs_to` reports no flag and stays silent. - Evidence, artifact, provenance, or reconstruction contract: unchanged for primitive Evidence. The composed workflow result differs only when a provider reports truncation. - Process execution, authorization, cleanup, or containment impact: none. - Generated metadata/package/installation impact: none. ## Evidence and regression coverage - Tests added or updated: `tests/boundary/mcp/enhancedTools.traceFeatureCoverage.test.ts` (production MCP server over an in-memory transport, exact `toEqual` on the whole trace): a capped target is a residual unknown while an uncapped sibling is not and all references are kept; a truncation is not carried over to the next target that reports nothing; a target matched twice is reported once; an explicit "not capped" report leaves the trace complete; a provider that reports nothing, even with limitation text mentioning a cap, returns exactly the trace it returned before; neither spelling of the field appears in the output; a failed references read stops the trace without reading further targets; a cancelled trace reads no references. `tests/composition/analysis-sessions/binarySession.test.ts`: the session forwards the field on both execution paths and a truncated read is never replayed, while an explicit complete report is. `src/ida/IdaReferenceCoverage.test.ts` (real headless session client over the recording fake): `more: true` gives `true` and keeps the limitation, `more: false` gives `false`, no flag gives nothing, the legacy profile and other operations report nothing; `src/ida/IdaNativeProtocol.test.ts` asserts the native profile forwards the explicit flag. `src/application/binary/AnalysisSnapshotCache.test.ts`: a truncated execution is not replayed while a complete or silent one is, and the `trace_feature` profile is revised while other workflows keep theirs. - Base reproduction or other evidence: the new tests fail on `a97b713f7` (RED commit `d8b802b3a`: 5 failed in the three files; the unchanged-trace test passes there by design). - Real IDA Professional 9.1 (headless idalib, ida-pro-mcp c133c38, macOS arm64): a fixed 37-step sweep over five fixtures (callees, call graphs, traces, `build_call_path`, error cases, universal and stripped binaries) gives results identical to `a97b713f7`, and `npm run verify:ida` passes. - Remaining proof gaps: Hopper and Ghidra were not run (no engines on this machine); real IDA cannot run `trace_feature` until containing procedures are supported for headless IDA. - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run check:fast`, `npm run check`, `npm run docs:check`, `npm run verify:test-discovery` (966 files) - green - `npm run test:focused` over the five affected test files - 87 passed - `npm run test:fast` - 408 files, 5310 passed, 7 skipped, 0 failed; `npm run test:mcp` - 103 files passed, 2 skipped, 447 passed, 3 skipped; `npm run test:boundary -- tests/boundary/providers/ida` after `npm run build:cached` - 14 passed - Mutation checks, each reverted: the IDA adapter never reporting fails 2; counting an absent field as truncated fails 1; removing the revision fails 1; removing the snapshot guard fails 1; deriving truncation from limitation text fails 2; a session that drops the field fails 2; reporting a capped target twice fails 1; leaking a differently spelled field into the output fails 6 macOS arm64 only; Linux and Windows were not executed. ## Compatibility, safety, and release - Breaking changes or migration steps: none. A `trace_feature` result for a provider that reports a truncated read now has `truncated: true` and a residual unknown. - Real Hopper/Ghidra, browser, or OS coverage: none; not applicable to the reported flag, which only the IDA adapter sets. - Package or release metadata impact: none. - Security, privacy, process, or containment review: no new capability, input or write path; no limitation text is parsed. ## Review checklist - [x] The change addresses a concrete problem, not an unscoped refactor. - [x] The PR has one focused outcome, and the title follows `type(scope): outcome`. - [x] Related issue is linked. - [x] Tests cover changed behavior and failure paths. - [x] Owning docs, contracts, and generated metadata are updated. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:04:37 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#5183
No description provided.