[GH-ISSUE #3891] [Bug] Native caller/callee analysis views drop partial relationship coverage and diagnostics #1512

Closed
opened 2026-10-16 21:06:23 +00:00 by bsakaguchi · 4 comments
Owner

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

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

rea inspect-analysis-view drops the parent analyze_function Evidence's relationship_coverage when projecting a native callees page. A parent that declares partial callee coverage, with a read-limit reason and a diagnostic, yields a view whose Evidence has relationship_coverage: null, empty limitations, and coverage.exhausted: true. The page gives no sign that more direct callees exist.

The command succeeds. The view Evidence has relationship_coverage: null; its normalized result has item: [], coverage.total: 0, coverage.exhausted: true, and empty limitations. Its only unknowns concern native API and value-flow availability. Neither the read-limit reason nor the diagnostic appears anywhere in the result.

Reproduction

  1. Create a scratch directory with PROBE_DIR=$(mktemp -d) and save this generator as $PROBE_DIR/probe.mjs. It builds canonical analyze_function Evidence with the shipped createEvidence, using a dossier admitted by functionDossierSchema and partial callee relationship coverage. No artifact file is opened.
import {writeFileSync} from 'node:fs';
import {pathToFileURL} from 'node:url';
const [repo, out] = process.argv.slice(2);
const {createEvidence}=await import(pathToFileURL(repo+'/dist/domain/evidence.js'));
const {functionDossierSchema}=await import(pathToFileURL(repo+'/dist/domain/hopperValues.js'));
const dossier={procedure:{address:'0x1000',name:'main',classification:null,body:{available:false,reason:'Fixture omits body extent'},signature:'int main(void)',locals:[]},pseudocode:'return 0;',assembly:['ret'],comments:[],callers:[],callees:[],incoming_references:[],outgoing_references:[],referenced_strings:[],referenced_names:[],basic_blocks:[],native_api:null,native_value_flow:null,facet_availability:{callers:{available:true},callees:{available:true}},limitations:[]};
functionDossierSchema.parse(dossier);
const coverage={status:'partial',limited_directions:['callees'],limitations:['Callees: fixture read stopped before all direct callees were returned'],diagnostics:['Fixture relationship diagnostic']};
const parent=createEvidence({path:'fixture.bin',sha256:'a'.repeat(64),format:'mach-o'},{id:'fixture',name:'Fixture provider',version:'1'},{operation:'analyze_function',parameters:{procedure:'main'},result:dossier,relationshipCoverage:coverage});
writeFileSync(out,JSON.stringify({source:{kind:'inline',evidence:parent},view:{kind:'native',facet:'callees',offset:0,limit:1}}));
console.log(JSON.stringify({parent_coverage:parent.relationship_coverage,parent_limitations:parent.limitations,dossier_limitations:dossier.limitations}));
  1. From the root of a built source checkout (the generator imports dist/ modules; node scripts/rea.mjs is the rea bin entry), generate the request and project the callee page.
node "$PROBE_DIR/probe.mjs" "$PWD" "$PROBE_DIR/view.json"
node scripts/rea.mjs inspect-analysis-view "$PROBE_DIR/view.json" --format json > "$PROBE_DIR/result.json"
  1. Compare the parent coverage printed by the generator with normalized_result.coverage, normalized_result.limitations, limitations, and relationship_coverage in $PROBE_DIR/result.json.

Expected behavior

The native callee view should carry the source's declared relationship incompleteness: relationship_coverage (or an equivalent limitation) should report status: "partial" for callees, keep the read-limit reason, and keep the diagnostic visible independently of page exhaustion. Page exhaustion itself may still be true for the returned array.

Impact and scope

Consumers of native caller/callee views lose declared provider incompleteness and diagnostics, so an empty or exhausted page can be read as the complete relationship set; the parent Evidence remains intact.

Workaround: available. Callers can read relationship_coverage from the parent analyze_function Evidence alongside the view, which retains the reason and diagnostic, at the cost of the extra inspection the view is meant to avoid.

Reporter's impact estimate: P2 (supported).

REA version

6.5.0, built from source at main commit 034342ba28 (latest npm release is 6.5.0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64; The parent Evidence is synthetic; real Ghidra, Hopper, and IDA providers were not run. The MCP inspect_analysis_view handler shares the affected application service but was not exercised.

Target and artifact context

Synthetic analyze_function Evidence for a Mach-O function

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

  • Expected source: docs/mcp-contracts.md:213-221 ("Unavailable facts and provider limitations remain explicit" for native caller/callee views); docs/tool-design.md:51-64 (views return coverage and limitations inline and distinguish provider relationship completeness from diagnostics); src/application/FunctionAnalysisEvidence.ts:40-51 keeps relationship coverage on the parent separately from its limitations
  • Failure source: public CLI result for the request above; static cause at src/application/analysisView/AnalysisViewService.ts:69-85 and src/domain/analysisView/nativeFunctionView.ts:25-59, 193-199
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Generator output (parent Evidence):

{"parent_coverage":{"status":"partial","limitations":["Callees: fixture read stopped before all direct callees were returned"],"limited_directions":["callees"],"diagnostics":["Fixture relationship diagnostic"]},"parent_limitations":[],"dossier_limitations":[]}

Relevant fields of the successful CLI result:

{
  "normalized_result": {
    "kind": "native",
    "view": {"kind":"native","facet":"callees","offset":0,"limit":1},
    "item": [],
    "coverage": {"status":"empty","examined":0,"total":0,"next_offset":null,"exhausted":true},
    "limitations": [],
    "unknowns": ["native_api was not recorded","native_value_flow was not recorded"]
  },
  "limitations": [],
  "relationship_coverage": null
}

Control: with dossier.limitations: ["Fixture dossier caveat"] and the same partial relationship coverage, the CLI returned normalized_result.limitations: ["Fixture dossier caveat"] while still dropping both relationship messages and returning relationship_coverage: null. Native views keep ordinary dossier caveats but omit the separate coverage carrier.

Source-derived cause: src/application/analysisView/AnalysisViewService.ts:69-85 builds the projection parent without forwarding parent.relationship_coverage. src/domain/analysisView/nativeFunctionView.ts:25-59 derives limitations and unknowns only from parent and dossier limitations and API/value-flow facts, and lines 193-199 page the callee array without source relationship coverage. The view Evidence creator supplies no relationship coverage either.

Typed outcome or recovery expected

The native callees view should report the parent's partial relationship coverage for callees, including its read-limit reason and diagnostic, through relationship_coverage or explicit limitations.

Restoration check:

For the same view.json request, the tested build returns relationship_coverage: null and no relationship limitation; a fixed build should expose the partial callees coverage, the read-limit reason, and the diagnostic in the view result. A control parent with complete relationship coverage should not gain a partial-coverage limitation, and the dossier-caveat control should keep its caveat.

Analysis or suspected cause

Source-derived: src/application/analysisView/AnalysisViewService.ts:69-85 omits parent.relationship_coverage from the projection parent, and src/domain/analysisView/nativeFunctionView.ts:25-59 builds limitations only from parent/dossier limitations and API/value-flow facts.

Additional context

Related closed reports on other consumers of the same coverage data: #3195 (compare_functions and reconstruction verification dropped call-coverage diagnostics), #2951 (Ghidra dossier coverage production), and #1247 (native facet selection). This report concerns the native view consumer discarding coverage that the parent already carries; it was not shown to be a regression of those fixes.

This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3891 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `rea inspect-analysis-view` drops the parent `analyze_function` Evidence's `relationship_coverage` when projecting a native `callees` page. A parent that declares partial callee coverage, with a read-limit reason and a diagnostic, yields a view whose Evidence has `relationship_coverage: null`, empty `limitations`, and `coverage.exhausted: true`. The page gives no sign that more direct callees exist. The command succeeds. The view Evidence has `relationship_coverage: null`; its normalized result has `item: []`, `coverage.total: 0`, `coverage.exhausted: true`, and empty `limitations`. Its only unknowns concern native API and value-flow availability. Neither the read-limit reason nor the diagnostic appears anywhere in the result. ### Reproduction 1. Create a scratch directory with `PROBE_DIR=$(mktemp -d)` and save this generator as `$PROBE_DIR/probe.mjs`. It builds canonical `analyze_function` Evidence with the shipped `createEvidence`, using a dossier admitted by `functionDossierSchema` and partial callee relationship coverage. No artifact file is opened. ```js import {writeFileSync} from 'node:fs'; import {pathToFileURL} from 'node:url'; const [repo, out] = process.argv.slice(2); const {createEvidence}=await import(pathToFileURL(repo+'/dist/domain/evidence.js')); const {functionDossierSchema}=await import(pathToFileURL(repo+'/dist/domain/hopperValues.js')); const dossier={procedure:{address:'0x1000',name:'main',classification:null,body:{available:false,reason:'Fixture omits body extent'},signature:'int main(void)',locals:[]},pseudocode:'return 0;',assembly:['ret'],comments:[],callers:[],callees:[],incoming_references:[],outgoing_references:[],referenced_strings:[],referenced_names:[],basic_blocks:[],native_api:null,native_value_flow:null,facet_availability:{callers:{available:true},callees:{available:true}},limitations:[]}; functionDossierSchema.parse(dossier); const coverage={status:'partial',limited_directions:['callees'],limitations:['Callees: fixture read stopped before all direct callees were returned'],diagnostics:['Fixture relationship diagnostic']}; const parent=createEvidence({path:'fixture.bin',sha256:'a'.repeat(64),format:'mach-o'},{id:'fixture',name:'Fixture provider',version:'1'},{operation:'analyze_function',parameters:{procedure:'main'},result:dossier,relationshipCoverage:coverage}); writeFileSync(out,JSON.stringify({source:{kind:'inline',evidence:parent},view:{kind:'native',facet:'callees',offset:0,limit:1}})); console.log(JSON.stringify({parent_coverage:parent.relationship_coverage,parent_limitations:parent.limitations,dossier_limitations:dossier.limitations})); ``` 2. From the root of a built source checkout (the generator imports `dist/` modules; `node scripts/rea.mjs` is the `rea` bin entry), generate the request and project the callee page. ```sh node "$PROBE_DIR/probe.mjs" "$PWD" "$PROBE_DIR/view.json" node scripts/rea.mjs inspect-analysis-view "$PROBE_DIR/view.json" --format json > "$PROBE_DIR/result.json" ``` 3. Compare the parent coverage printed by the generator with `normalized_result.coverage`, `normalized_result.limitations`, `limitations`, and `relationship_coverage` in `$PROBE_DIR/result.json`. ### Expected behavior The native callee view should carry the source's declared relationship incompleteness: `relationship_coverage` (or an equivalent limitation) should report `status: "partial"` for `callees`, keep the read-limit reason, and keep the diagnostic visible independently of page exhaustion. Page exhaustion itself may still be true for the returned array. ### Impact and scope Consumers of native caller/callee views lose declared provider incompleteness and diagnostics, so an empty or exhausted page can be read as the complete relationship set; the parent Evidence remains intact. Workaround: available. Callers can read `relationship_coverage` from the parent `analyze_function` Evidence alongside the view, which retains the reason and diagnostic, at the cost of the extra inspection the view is meant to avoid. Reporter's impact estimate: P2 (supported). ### REA version 6.5.0, built from source at main commit 034342ba28c8e56c3746be92b78aa7299afa02bd (latest npm release is 6.5.0) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64; The parent Evidence is synthetic; real Ghidra, Hopper, and IDA providers were not run. The MCP `inspect_analysis_view` handler shares the affected application service but was not exercised. ### Target and artifact context Synthetic analyze_function Evidence for a Mach-O function ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence - Expected source: docs/mcp-contracts.md:213-221 ("Unavailable facts and provider limitations remain explicit" for native caller/callee views); docs/tool-design.md:51-64 (views return coverage and limitations inline and distinguish provider relationship completeness from diagnostics); src/application/FunctionAnalysisEvidence.ts:40-51 keeps relationship coverage on the parent separately from its limitations - Failure source: public CLI result for the request above; static cause at src/application/analysisView/AnalysisViewService.ts:69-85 and src/domain/analysisView/nativeFunctionView.ts:25-59, 193-199 - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Generator output (parent Evidence): ```json {"parent_coverage":{"status":"partial","limitations":["Callees: fixture read stopped before all direct callees were returned"],"limited_directions":["callees"],"diagnostics":["Fixture relationship diagnostic"]},"parent_limitations":[],"dossier_limitations":[]} ``` Relevant fields of the successful CLI result: ```json { "normalized_result": { "kind": "native", "view": {"kind":"native","facet":"callees","offset":0,"limit":1}, "item": [], "coverage": {"status":"empty","examined":0,"total":0,"next_offset":null,"exhausted":true}, "limitations": [], "unknowns": ["native_api was not recorded","native_value_flow was not recorded"] }, "limitations": [], "relationship_coverage": null } ``` Control: with `dossier.limitations: ["Fixture dossier caveat"]` and the same partial relationship coverage, the CLI returned `normalized_result.limitations: ["Fixture dossier caveat"]` while still dropping both relationship messages and returning `relationship_coverage: null`. Native views keep ordinary dossier caveats but omit the separate coverage carrier. Source-derived cause: src/application/analysisView/AnalysisViewService.ts:69-85 builds the projection parent without forwarding `parent.relationship_coverage`. src/domain/analysisView/nativeFunctionView.ts:25-59 derives limitations and unknowns only from parent and dossier limitations and API/value-flow facts, and lines 193-199 page the callee array without source relationship coverage. The view Evidence creator supplies no relationship coverage either. ### Typed outcome or recovery expected The native callees view should report the parent's partial relationship coverage for callees, including its read-limit reason and diagnostic, through relationship_coverage or explicit limitations. Restoration check: For the same `view.json` request, the tested build returns `relationship_coverage: null` and no relationship limitation; a fixed build should expose the partial `callees` coverage, the read-limit reason, and the diagnostic in the view result. A control parent with complete relationship coverage should not gain a partial-coverage limitation, and the dossier-caveat control should keep its caveat. ### Analysis or suspected cause Source-derived: src/application/analysisView/AnalysisViewService.ts:69-85 omits parent.relationship_coverage from the projection parent, and src/domain/analysisView/nativeFunctionView.ts:25-59 builds limitations only from parent/dossier limitations and API/value-flow facts. ### Additional context Related closed reports on other consumers of the same coverage data: #3195 (compare_functions and reconstruction verification dropped call-coverage diagnostics), #2951 (Ghidra dossier coverage production), and #1247 (native facet selection). This report concerns the native view consumer discarding coverage that the parent already carries; it was not shown to be a regression of those fixes. This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

This projection bug is subtle but consequential: the parent Evidence clearly carries relationship_coverage (partial, with a read-limit reason and a diagnostic), yet the view silently nulls it out and reports coverage.exhausted: true with empty limitations — which actively misleads anyone reading the page into believing the callee list is complete. For an analysis view whose purpose is to communicate partial results honestly, dropping the read-limit reason and the diagnostic is worse than the missing items themselves. Suggest preserving relationship_coverage and limitations through the projection (or at minimum surfacing the diagnostic + non-exhausted flag), and adding a view-level regression fixture that feeds a partial-coverage parent and asserts the coverage metadata survives.

<!-- gh-comment-id:6185588924 --> @XinwangTao commented on GitHub (Oct 15, 2026): This projection bug is subtle but consequential: the parent Evidence clearly carries relationship_coverage (partial, with a read-limit reason and a diagnostic), yet the view silently nulls it out and reports coverage.exhausted: true with empty limitations — which actively misleads anyone reading the page into believing the callee list is complete. For an analysis view whose purpose is to communicate partial results honestly, dropping the read-limit reason and the diagnostic is worse than the missing items themselves. Suggest preserving relationship_coverage and limitations through the projection (or at minimum surfacing the diagnostic + non-exhausted flag), and adding a view-level regression fixture that feeds a partial-coverage parent and asserts the coverage metadata survives.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

This projection bug is subtle but consequential: the parent Evidence clearly carries relationship_coverage (partial, with a read-limit reason and a diagnostic), yet the view silently nulls it out and reports coverage.exhausted: true with empty limitations — which actively misleads anyone reading the page into believing the callee list is complete. For an analysis view whose purpose is to communicate partial results honestly, dropping the read-limit reason and the diagnostic is worse than the missing items themselves. Suggest preserving relationship_coverage and limitations through the projection (or at minimum surfacing the diagnostic + non-exhausted flag), and adding a view-level regression fixture that feeds a partial-coverage parent and asserts the coverage metadata survives.

<!-- gh-comment-id:6185604882 --> @XinwangTao commented on GitHub (Oct 15, 2026): This projection bug is subtle but consequential: the parent Evidence clearly carries relationship_coverage (partial, with a read-limit reason and a diagnostic), yet the view silently nulls it out and reports coverage.exhausted: true with empty limitations — which actively misleads anyone reading the page into believing the callee list is complete. For an analysis view whose purpose is to communicate partial results honestly, dropping the read-limit reason and the diagnostic is worse than the missing items themselves. Suggest preserving relationship_coverage and limitations through the projection (or at minimum surfacing the diagnostic + non-exhausted flag), and adding a view-level regression fixture that feeds a partial-coverage parent and asserts the coverage metadata survives.
Author
Owner

@morluto commented on GitHub (Oct 16, 2026):

Fixed on main in 89dd71f0f. Native analysis views preserve parent relationship coverage and diagnostics through inline and retained rows independently of page exhaustion.

View projection controls cover partial and complete parent coverage; no additional native-provider execution is claimed.

Hosted main CI is still pending; this closure is based on the implemented fix and the scoped regression evidence.

<!-- gh-comment-id:6206044286 --> @morluto commented on GitHub (Oct 16, 2026): Fixed on `main` in [89dd71f0f](https://github.com/morluto/rea/commit/89dd71f0f0b3e02049a22b2bb02da98d157b60f8). Native analysis views preserve parent relationship coverage and diagnostics through inline and retained rows independently of page exhaustion. View projection controls cover partial and complete parent coverage; no additional native-provider execution is claimed. Hosted main CI is still pending; this closure is based on the implemented fix and the scoped regression evidence.
Author
Owner

@XinwangTao commented on GitHub (Oct 16, 2026):

This is a meaningful fix — the projected view was silently erasing the parent's partial-coverage signal, so a consumer would read exhausted: true and never learn more direct callees exist. Carrying the parent relationship coverage (with its read-limit reason and diagnostics) into the view keeps the incompleteness explicit. Good catch and clean resolution in 89dd71f.

<!-- gh-comment-id:6206260086 --> @XinwangTao commented on GitHub (Oct 16, 2026): This is a meaningful fix — the projected view was silently erasing the parent's partial-coverage signal, so a consumer would read exhausted: true and never learn more direct callees exist. Carrying the parent relationship coverage (with its read-limit reason and diagnostics) into the view keeps the incompleteness explicit. Good catch and clean resolution in 89dd71f.
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#1512
No description provided.