[GH-ISSUE #2564] fix(native): accept declared symbol facts in dispatch name inventory #881

Closed
opened 2026-10-15 18:29:15 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 13, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2564

Summary

When a selected analysis provider lacks binary dispatch metadata but returns names with declared symbol facts, native dispatch inspection rejects the valid name inventory and returns an output error. This blocks the documented symbol-based result for nonempty Ghidra name inventories; UI action tracing also depends on the same inspection result.

Runtime or environment

Failure verified at a2d8b94dd5 and still present at 8b181716ac. The local verification used the focused service test lane with a simulated analysis provider; a live Ghidra session was not run.

Steps to reproduce

  1. At the stated baseline, create src/application/native/native_dispatch_symbol_facts.test.ts with the following focused fixture. It makes binary metadata unavailable and returns one valid Ghidra-shaped name record; the control omits only the optional symbol field.
  2. Run npm run test:focused -- src/application/native/native_dispatch_symbol_facts.test.ts.
  3. Observe that the symbol-bearing case returns an output error for list_names, while the control succeeds. A live Ghidra session is not needed for this focused reproduction; its emitted record shape is established by the provider schema and bridge source.
import { describe, expect, it } from "vitest";
import { createAnalysisExecution } from "../AnalysisProvider.js";
import type { AnalysisOperationPort } from "../AnalysisProvider.js";
import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js";
import { err, ok } from "../../domain/result.js";
import { inspectNativeDispatch } from "./NativeDispatchMetadataInspection.js";

const subject = { path: "fixture.bin", sha256: "a".repeat(64), format: "mach-o" as const, architecture: "arm64" as const };
const provider = { id: "ghidra", name: "Ghidra", version: null };
const item = { address: "0x401000", value: "-[Fixture buildTapped:]", symbol: { primary: true, dynamic: false, external: false, type: "function", source: "user_defined" } };
const port = (record: Record<string, unknown>): AnalysisOperationPort => ({
  execute: async (operation) => {
    if (operation === "inspect_native_dispatch_metadata") return err(new AnalysisCapabilityUnavailableError("ghidra", operation, "unavailable"));
    if (operation === "list_names") return ok(createAnalysisExecution([record], provider, { subject }));
    throw new Error(`unexpected operation: ${operation}`);
  },
});

describe("dispatch name inventory", () => {
  it("rejects a declared symbol-fact record", async () => {
    expect(await inspectNativeDispatch(port(item), 100)).toMatchObject({ ok: false, error: { _tag: "AnalysisOutputError", operation: "list_names" } });
  });
  it("accepts the same record without optional symbol facts", async () => {
    const { symbol: _symbol, ...withoutSymbol } = item;
    expect((await inspectNativeDispatch(port(withoutSymbol), 100)).ok).toBe(true);
  });
});

Expected behavior

Native dispatch inspection should use the selected provider's name inventory when binary metadata is unavailable and return its narrower symbol-based coverage. The declared list_names output accepts an address, a value, and optional symbol facts, so the Ghidra-shaped record in the fixture is valid input for this fallback.

Actual behavior

The focused test reproduced AnalysisOutputError for operation list_names with the message provider returned an invalid inventory. Removing only symbol made inspection succeed. The UI action tracing path propagates a metadata inspection error according to source; that downstream result was not separately executed.

Evidence

  • Expected source: docs/native-investigation.md:347-380 promises symbol-based coverage from other providers; src/contracts/toolOutputSchemaGroups.ts:124 and src/contracts/toolOutputSchemaPrimitives.ts:574-585 declare list_names records with optional symbol facts; src/ghidra/GhidraInventoryValues.ts:204,300 and bridge/ghidra/ReaGhidraBridge.java:1589-1600,1704-1718,3324-3330 establish Ghidra's record shape.
  • Failure source: src/ghidra/GhidraProviderClient.ts:711-724 returns capability unavailability for the undeclared binary operation; src/application/native/NativeDispatchMetadataInspection.ts:28-50 then reaches the fallback and strictly rejects the extra symbol key; src/application/native/NativeUiActionTrace.ts:28-43 propagates its error.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete
  • The focused probe passed both observations at the stated baseline. The closed dispatch metadata feature issue describes the intended workflow but does not report this record-shape failure.

Restoration check

Using the same unavailable-binary-metadata condition and symbol-bearing name record, inspection should return a successful symbol-based result containing the named dispatch entry rather than AnalysisOutputError. The control without optional facts should continue to succeed; an empty inventory alone is insufficient because it does not exercise the rejected record shape.

Triage assessment

  • Impact level: P1
  • Assessment status: supported
  • Impact basis: A nonempty Ghidra name inventory carries symbol facts on every record, so this valid input blocks the documented dispatch inspection path for that provider; UI action tracing depends on the failed result. The focused probe establishes the failure at the application boundary, while a live provider run remains unverified.
  • Workaround status: unverified
  • Workaround basis: Another provider or the macOS binary reader may answer some targets, but neither is established as available or equivalent in every supported Ghidra environment; no practical workaround was verified for the affected path.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2564 ## Summary When a selected analysis provider lacks binary dispatch metadata but returns names with declared `symbol` facts, native dispatch inspection rejects the valid name inventory and returns an output error. This blocks the documented symbol-based result for nonempty Ghidra name inventories; UI action tracing also depends on the same inspection result. ## Runtime or environment Failure verified at a2d8b94dd50dab8d966239dd63ab84718aa8b4b4 and still present at 8b181716aca2cb467be499cba226abfb5ae19141. The local verification used the focused service test lane with a simulated analysis provider; a live Ghidra session was not run. ## Steps to reproduce 1. At the stated baseline, create `src/application/native/native_dispatch_symbol_facts.test.ts` with the following focused fixture. It makes binary metadata unavailable and returns one valid Ghidra-shaped name record; the control omits only the optional `symbol` field. 2. Run `npm run test:focused -- src/application/native/native_dispatch_symbol_facts.test.ts`. 3. Observe that the symbol-bearing case returns an output error for `list_names`, while the control succeeds. A live Ghidra session is not needed for this focused reproduction; its emitted record shape is established by the provider schema and bridge source. ```ts import { describe, expect, it } from "vitest"; import { createAnalysisExecution } from "../AnalysisProvider.js"; import type { AnalysisOperationPort } from "../AnalysisProvider.js"; import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js"; import { err, ok } from "../../domain/result.js"; import { inspectNativeDispatch } from "./NativeDispatchMetadataInspection.js"; const subject = { path: "fixture.bin", sha256: "a".repeat(64), format: "mach-o" as const, architecture: "arm64" as const }; const provider = { id: "ghidra", name: "Ghidra", version: null }; const item = { address: "0x401000", value: "-[Fixture buildTapped:]", symbol: { primary: true, dynamic: false, external: false, type: "function", source: "user_defined" } }; const port = (record: Record<string, unknown>): AnalysisOperationPort => ({ execute: async (operation) => { if (operation === "inspect_native_dispatch_metadata") return err(new AnalysisCapabilityUnavailableError("ghidra", operation, "unavailable")); if (operation === "list_names") return ok(createAnalysisExecution([record], provider, { subject })); throw new Error(`unexpected operation: ${operation}`); }, }); describe("dispatch name inventory", () => { it("rejects a declared symbol-fact record", async () => { expect(await inspectNativeDispatch(port(item), 100)).toMatchObject({ ok: false, error: { _tag: "AnalysisOutputError", operation: "list_names" } }); }); it("accepts the same record without optional symbol facts", async () => { const { symbol: _symbol, ...withoutSymbol } = item; expect((await inspectNativeDispatch(port(withoutSymbol), 100)).ok).toBe(true); }); }); ``` ## Expected behavior Native dispatch inspection should use the selected provider's name inventory when binary metadata is unavailable and return its narrower symbol-based coverage. The declared `list_names` output accepts an `address`, a `value`, and optional `symbol` facts, so the Ghidra-shaped record in the fixture is valid input for this fallback. ## Actual behavior The focused test reproduced `AnalysisOutputError` for operation `list_names` with the message `provider returned an invalid inventory`. Removing only `symbol` made inspection succeed. The UI action tracing path propagates a metadata inspection error according to source; that downstream result was not separately executed. ## Evidence - Expected source: `docs/native-investigation.md:347-380` promises symbol-based coverage from other providers; `src/contracts/toolOutputSchemaGroups.ts:124` and `src/contracts/toolOutputSchemaPrimitives.ts:574-585` declare `list_names` records with optional `symbol` facts; `src/ghidra/GhidraInventoryValues.ts:204,300` and `bridge/ghidra/ReaGhidraBridge.java:1589-1600,1704-1718,3324-3330` establish Ghidra's record shape. - Failure source: `src/ghidra/GhidraProviderClient.ts:711-724` returns capability unavailability for the undeclared binary operation; `src/application/native/NativeDispatchMetadataInspection.ts:28-50` then reaches the fallback and strictly rejects the extra `symbol` key; `src/application/native/NativeUiActionTrace.ts:28-43` propagates its error. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete - The focused probe passed both observations at the stated baseline. The closed [dispatch metadata feature issue](https://github.com/morluto/rea/issues/484) describes the intended workflow but does not report this record-shape failure. ## Restoration check Using the same unavailable-binary-metadata condition and symbol-bearing name record, inspection should return a successful symbol-based result containing the named dispatch entry rather than `AnalysisOutputError`. The control without optional facts should continue to succeed; an empty inventory alone is insufficient because it does not exercise the rejected record shape. ## Triage assessment - Impact level: P1 - Assessment status: supported - Impact basis: A nonempty Ghidra name inventory carries `symbol` facts on every record, so this valid input blocks the documented dispatch inspection path for that provider; UI action tracing depends on the failed result. The focused probe establishes the failure at the application boundary, while a live provider run remains unverified. - Workaround status: unverified - Workaround basis: Another provider or the macOS binary reader may answer some targets, but neither is established as available or equivalent in every supported Ghidra environment; no practical workaround was verified for the affected path.
bsakaguchi 2026-10-15 18:29:15 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@matheusmacedoms085 commented on GitHub (Oct 13, 2026):

Confirming this with a live Ghidra session, since the report notes that one was not run.

Environment: REA 6.4.0 at 865c35e25, Ghidra 12.1.4, Windows 10 x64, MCP server over stdio.
Target: C:\Windows\System32\whoami.exe (PE, x86_64), opened with provider_id: "ghidra".

Observed

  • list_names succeeds and returns 2213 records. Every one of them has the shape { address, value, symbol: { primary, dynamic, external, type, source } }.
  • inspect_native_dispatch_metadata (max_records: 100) fails with unreadable_output, details {"operation":"list_names","reason":"provider returned an invalid inventory"}.
  • trace_native_ui_action fails with the same error, as it goes through the same inspection.

Parsing the live list_names result with the two z.strictObject branches in src/application/native/NativeDispatchMetadataInspection.ts (lines 39-40) reports Unrecognized key: "symbol".

Local check of a fix

Replacing both z.strictObject with z.object on those two lines and rebuilding makes both tools succeed on the same target: inspect_native_dispatch_metadata returns the symbol-based result with its coverage entries (empty lists, as expected for a Windows PE), and trace_native_ui_action returns reason: "ui_action_or_object_not_found" instead of an output error. I did not check whether a stricter schema that names the symbol field would be preferable.

<!-- gh-comment-id:6148627105 --> @matheusmacedoms085 commented on GitHub (Oct 13, 2026): Confirming this with a live Ghidra session, since the report notes that one was not run. **Environment:** REA 6.4.0 at 865c35e25, Ghidra 12.1.4, Windows 10 x64, MCP server over stdio. **Target:** `C:\Windows\System32\whoami.exe` (PE, x86_64), opened with `provider_id: "ghidra"`. **Observed** - `list_names` succeeds and returns 2213 records. Every one of them has the shape `{ address, value, symbol: { primary, dynamic, external, type, source } }`. - `inspect_native_dispatch_metadata` (`max_records: 100`) fails with `unreadable_output`, details `{"operation":"list_names","reason":"provider returned an invalid inventory"}`. - `trace_native_ui_action` fails with the same error, as it goes through the same inspection. Parsing the live `list_names` result with the two `z.strictObject` branches in `src/application/native/NativeDispatchMetadataInspection.ts` (lines 39-40) reports `Unrecognized key: "symbol"`. **Local check of a fix** Replacing both `z.strictObject` with `z.object` on those two lines and rebuilding makes both tools succeed on the same target: `inspect_native_dispatch_metadata` returns the symbol-based result with its coverage entries (empty lists, as expected for a Windows PE), and `trace_native_ui_action` returns `reason: "ui_action_or_object_not_found"` instead of an output error. I did not check whether a stricter schema that names the `symbol` field would be preferable.
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#881
No description provided.