[GH-ISSUE #7345] [Bug] build_call_path joins equal addresses from different declared coordinate mappings #4363

Closed
opened 2026-10-31 00:49:14 +00:00 by bsakaguchi · 0 comments
Owner

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

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

The public MCP build_call_path handler accepted two authenticated function dossiers for the same known arm64 artifact and provider, despite different function_coordinate_context.mapping_sha256 commitments. It returned the joined path 0x2000 -> 0x5000 -> 0x6000, status: "found", two hops, search_scope.exhaustive: true, and no limitations. The second edge cites the dossier from the other mapping.

This was a synthetic Evidence replay through the production server and SDK in-memory transport. No native analysis provider or target ran.

Reproduction

Use the source revision below with its locked dependencies and built dist available. From the repository root, run the following with the pinned Node.js. The fixture uses production Evidence constructors and an empty provider registry. Each of the four requests ran once, sequentially, in this reproduction.

review_home="$(mktemp -d)"
env -i HOME="$review_home" TMPDIR="$review_home" PATH="$PATH" "$(command -v node)" --input-type=module <<'JS'
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
import assert from 'node:assert/strict';
import { release } from 'node:os';
const repo=process.cwd();
const load=p=>import(pathToFileURL(`${repo}/dist/${p}.js`).href);
const require=createRequire(`${repo}/package.json`);
const {Client,InMemoryTransport}=await import(pathToFileURL(require.resolve('@modelcontextprotocol/client')).href);
const {BinarySession}=await load('application/binary/BinarySession');
const {SessionProviderRouter}=await load('application/binary/SessionProviderRouter');
const {AnalysisProviderRegistry}=await load('application/binary/AnalysisProviderRegistry');
const {createServer}=await load('server/createServer');
const {createEvidence,parseEvidence}=await load('domain/evidence');
const {createAnalysisProfile}=await load('domain/analysisProfile');
const {createFunctionCoordinateContext,functionCoordinateContextsEqual}=await load('domain/native/functionCoordinateContext');
const {FUNCTION_COMPARISON_EXAMPLE}=await load('contracts/functionComparisonExample');
const template=FUNCTION_COMPARISON_EXAMPLE.left.normalized_result;
const provider={id:'synthetic',name:'Synthetic coordinate provider',version:'1'};
const profile=(base,extra={})=>createAnalysisProfile(provider,{function_coordinate_context:createFunctionCoordinateContext({image_base:base}),...extra});
const a=profile('0x0'),b=profile('0x4000'),aOther=profile('0x0',{analysis_option:'different'});
const observation=(address,callees,p=a,overrides={})=>{
 const d=structuredClone(template);
 d.procedure={...d.procedure,address,name:`fn_${address}`};
 d.callers=[];d.callees=callees.map(address=>({address,name:`fn_${address}`,classification:null,body:d.procedure.body}));d.limitations=[];
 return createEvidence({path:'/synthetic/image',sha256:'1'.repeat(64),format:'mach-o',architecture:'arm64',...overrides.subject},overrides.provider??provider,{
  operation:'analyze_function',parameters:{procedure:address},result:d,confidence:'observed',authority:'shipped-artifact',relationshipCoverage:{status:'complete'},analysisProfile:p,...overrides.observation
 });
};
const left=observation('0x2000',['0x5000']);
const mixed=observation('0x5000',['0x6000'],b);
const same=observation('0x5000',['0x6000']);
const sameMapOtherProfile=observation('0x5000',['0x6000'],aOther);
const session=new BinarySession(SessionProviderRouter.selectable(new AnalysisProviderRegistry([]),[]));
const server=createServer({kind:'session',session},{environment:{}});
const client=new Client({name:'independent-call-path-verifier',version:'1'});
const [ct,st]=InMemoryTransport.createLinkedPair();
await server.connect(st);await client.connect(ct);
const results=[];
const cases=[
 ['mixed-mapping',[left,mixed],'found'],
 ['same-mapping',[left,same],'found'],
 ['same-mapping-different-profile',[left,sameMapOtherProfile],'found'],
 ['missing-middle',[left],'unknown'],
];
try {
 console.log(JSON.stringify({runtime:process.version,platform:process.platform,architecture:process.arch,kernel:release(),providers:0,contexts_equal:functionCoordinateContextsEqual(a,b),same_mapping_other_profile:functionCoordinateContextsEqual(a,aOther),profile_digests_differ:a.digest!==aOther.digest}));
 assert.equal(functionCoordinateContextsEqual(a,b),false);
 assert.equal(functionCoordinateContextsEqual(a,aOther),true);
 for(const [name,functions,expected] of cases){
  for(const e of functions){parseEvidence(e);assert.equal(session.recordEvidence(e).ok,true);}
  const reply=await client.callTool({name:'build_call_path',arguments:{functions,start:{address:'0x2000'},goal:{address:'0x6000'}}});
  assert.notEqual(reply.isError,true,JSON.stringify(reply));
  const evidence=parseEvidence(reply.structuredContent),r=evidence.normalized_result;
  assert.equal(r.status,expected);
  if(expected==='found'){
   assert.equal(r.search_scope.exhaustive,true);assert.deepEqual(r.limitations,[]);
   assert.equal(r.shortest_hops,2);assert.deepEqual(r.paths[0].nodes.map(n=>n.address),['0x2000','0x5000','0x6000']);
   assert.deepEqual(r.paths[0].edges[1].evidence_links,[functions[1].evidence_id]);
  }else{assert.equal(r.search_scope.exhaustive,false);assert.equal(r.paths.length,0);}
  const row={name,status:r.status,shortest_hops:r.shortest_hops,exhaustive:r.search_scope.exhaustive,limitations:r.limitations,paths:r.paths.map(p=>p.nodes.map(n=>n.address))};
  results.push(row);console.log(JSON.stringify(row));
 }
 console.log(JSON.stringify({cases:results.length,assertions:'passed'}));
}finally{await client.close();await server.close();await session.close();}
JS
rm -rf "$review_home"

The two synthetic mappings declare image bases 0x0 and 0x4000. A observes 0x2000 -> 0x5000; B observes 0x5000 -> 0x6000. B's middle address denotes artifact offset 0x1000, while A's middle address denotes offset 0x5000. Expressed in A's coordinates, B observes 0x1000 -> 0x2000; no supplied dossier observes A's middle function. Thus the pair is not contradictory, and there is no observed two-hop path from A's 0x2000 to A's 0x6000.

Expected behavior

Do not join these equal address strings as one endpoint across explicitly different coordinate mappings. Reject incompatible Evidence or retain the uncovered middle function as unknown with a limitation. The supplied mixed pair must not produce an unqualified joined path.

The call-path contract in src/contracts/sessionToolContracts.ts:132-134 promises direct-callee paths using exact canonical addresses and unknown results for missing dossiers. docs/tool-design.md, “Define the contract,” requires identity, uncertainty and the distinction between observed and derived relationships to survive composition. src/domain/native/functionCoordinateContext.ts:13-35 defines a provider-owned mapping of executable coordinates. The comparison-specific discussion in docs/mcp-contracts.md, “Function comparisons,” corroborates that equal addresses in different mappings cannot establish equality; it does not by itself define call-path admission.

Preserve inputs with the same coordinate mapping even when unrelated analysis settings give their overall analysis profiles different digests. The third control demonstrates that distinction.

Impact and scope

The observed effect is a false derived call path and misleading exhaustive coverage when callers compose complete dossiers with unequal coordinate commitments. The same-mapping and different-profile/same-mapping controls returned the expected path. Removing the other-mapping dossier correctly returned unknown for the missing middle function.

P2: this is a bounded Evidence-correctness defect. A caller can inspect the coordinate commitments and compose dossiers from one established coordinate context; missing observations then remain unknown. The replay does not establish how often shipped providers naturally produce such pairs, and does not claim a false negative, a real-program path, or every possible runtime edge.

REA version

6.8.0; source revision dbe2c97af34361b11b9504e37611dd47715b52cb.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1 arm64; locked repository dependencies and supplied prebuilt runtime. No engine version applies.

Target and artifact context

Synthetic complete analyze_function dossiers derived from the repository's FUNCTION_COMPARISON_EXAMPLE; known arm64 Mach-O subject; identical subject digest and synthetic provider identity. No actual executable was opened.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The inline reproduction exited 0 with all assertions passing. Its relevant observations were:

Case Status Hops Exhaustive Limitations
Different coordinate mappings found 2 true []
Same mapping found 2 true []
Same mapping, different overall profile found 2 true []
Missing middle dossier unknown null false No analyze_function Evidence covers reachable function 0x5000

All found cases returned ["0x2000","0x5000","0x6000"]. The mixed pair's coordinate-equality predicate returned false. Assertions also checked that the second edge cites the second dossier. Separately executed production MCP controls rejected different subject digests, different architectures, different providers, duplicate function addresses and a wrong source operation.

Source trace: src/server/registerInvestigationTools.ts:141-158 resolves the session Evidence closure and invokes buildCallPath. src/domain/functionDossierEvidence.ts authenticates the source and requires a complete artifact-bound function dossier. src/domain/callPath.ts:160-225 keys nodes by canonical address and checks subject/provider compatibility without checking mapping commitments. Its search subsequently treats the other-mapping dossier as coverage of the middle address.

Typed outcome or recovery expected

A typed incompatible-input response or an unknown result that preserves the uncovered coordinate context is acceptable. Retain the valid same-mapping controls, including the different overall profile digest, and existing missing-dossier uncertainty.

Analysis or suspected cause

Source-derived explanation: graph identity is the canonical address string. assertCompatible compares artifact digest, format, architecture and provider id/name/version, but does not establish coordinate correspondence. The presence of a dossier at the same string therefore suppresses the missing-frontier rule even when its declared mapping differs.

Additional context

  • Expected source: the call-path contract in src/contracts/sessionToolContracts.ts:132-134, identity/uncertainty requirements in docs/tool-design.md, and provider-owned coordinate definition in src/domain/native/functionCoordinateContext.ts.
  • Failure source: the public MCP mixed-mapping output above, with source trace in src/domain/callPath.ts:160-225 and its inspectSearch coverage lookup.
  • Evidence provenance: observed; source reasoning is explicitly identified above.
  • Local verification: reproduced using the production MCP handler and in-memory SDK transport, with authenticated supplied Evidence and no deep providers.
  • Reproduction completeness: complete for supplied synthetic Evidence. NOT_ENOUGH_INFO for natural acquisition frequency or real-provider behavior; no such run was permitted.
  • Restoration check: the mixed pair must not yield the unqualified joined path; preserve successful same-mapping composition, successful composition with unrelated profile differences, and unknown for the missing middle dossier. No repair or post-fix run was performed.

The complete saved history and final live open/closed issue and pull-request searches contain no matching coordinate-join report. Related closed reports #6788 (unresolved calls), #5906/#6256 (explored-edge count), #6502 (cross-slice reference comparison), and #2322/#2550/#2371 (caller-only uncertainty), plus open PR #4984 (path enumeration bounds), concern distinct failing signals. The related current discussions and recent reports also contained no exact duplicate of this coordinate-join failure.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The accepted synthetic pair produces a path whose middle endpoint crosses explicitly different mappings, with exhaustive true and no limitation; reach is bounded to supplied multi-mapping dossiers.
  • Workaround status: available
  • Workaround basis: Inspect provider-owned coordinate commitments and supply dossiers from one established coordinate context. The same-mapping controls succeed; withholding the incompatible middle dossier preserves the observed unknown frontier instead of manufacturing a hop.
Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7345 ## 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 The public MCP `build_call_path` handler accepted two authenticated function dossiers for the same known arm64 artifact and provider, despite different `function_coordinate_context.mapping_sha256` commitments. It returned the joined path `0x2000 -> 0x5000 -> 0x6000`, `status: "found"`, two hops, `search_scope.exhaustive: true`, and no limitations. The second edge cites the dossier from the other mapping. This was a synthetic Evidence replay through the production server and SDK in-memory transport. No native analysis provider or target ran. ## Reproduction Use the source revision below with its locked dependencies and built `dist` available. From the repository root, run the following with the pinned Node.js. The fixture uses production Evidence constructors and an empty provider registry. Each of the four requests ran once, sequentially, in this reproduction. ```sh review_home="$(mktemp -d)" env -i HOME="$review_home" TMPDIR="$review_home" PATH="$PATH" "$(command -v node)" --input-type=module <<'JS' import { createRequire } from 'node:module'; import { pathToFileURL } from 'node:url'; import assert from 'node:assert/strict'; import { release } from 'node:os'; const repo=process.cwd(); const load=p=>import(pathToFileURL(`${repo}/dist/${p}.js`).href); const require=createRequire(`${repo}/package.json`); const {Client,InMemoryTransport}=await import(pathToFileURL(require.resolve('@modelcontextprotocol/client')).href); const {BinarySession}=await load('application/binary/BinarySession'); const {SessionProviderRouter}=await load('application/binary/SessionProviderRouter'); const {AnalysisProviderRegistry}=await load('application/binary/AnalysisProviderRegistry'); const {createServer}=await load('server/createServer'); const {createEvidence,parseEvidence}=await load('domain/evidence'); const {createAnalysisProfile}=await load('domain/analysisProfile'); const {createFunctionCoordinateContext,functionCoordinateContextsEqual}=await load('domain/native/functionCoordinateContext'); const {FUNCTION_COMPARISON_EXAMPLE}=await load('contracts/functionComparisonExample'); const template=FUNCTION_COMPARISON_EXAMPLE.left.normalized_result; const provider={id:'synthetic',name:'Synthetic coordinate provider',version:'1'}; const profile=(base,extra={})=>createAnalysisProfile(provider,{function_coordinate_context:createFunctionCoordinateContext({image_base:base}),...extra}); const a=profile('0x0'),b=profile('0x4000'),aOther=profile('0x0',{analysis_option:'different'}); const observation=(address,callees,p=a,overrides={})=>{ const d=structuredClone(template); d.procedure={...d.procedure,address,name:`fn_${address}`}; d.callers=[];d.callees=callees.map(address=>({address,name:`fn_${address}`,classification:null,body:d.procedure.body}));d.limitations=[]; return createEvidence({path:'/synthetic/image',sha256:'1'.repeat(64),format:'mach-o',architecture:'arm64',...overrides.subject},overrides.provider??provider,{ operation:'analyze_function',parameters:{procedure:address},result:d,confidence:'observed',authority:'shipped-artifact',relationshipCoverage:{status:'complete'},analysisProfile:p,...overrides.observation }); }; const left=observation('0x2000',['0x5000']); const mixed=observation('0x5000',['0x6000'],b); const same=observation('0x5000',['0x6000']); const sameMapOtherProfile=observation('0x5000',['0x6000'],aOther); const session=new BinarySession(SessionProviderRouter.selectable(new AnalysisProviderRegistry([]),[])); const server=createServer({kind:'session',session},{environment:{}}); const client=new Client({name:'independent-call-path-verifier',version:'1'}); const [ct,st]=InMemoryTransport.createLinkedPair(); await server.connect(st);await client.connect(ct); const results=[]; const cases=[ ['mixed-mapping',[left,mixed],'found'], ['same-mapping',[left,same],'found'], ['same-mapping-different-profile',[left,sameMapOtherProfile],'found'], ['missing-middle',[left],'unknown'], ]; try { console.log(JSON.stringify({runtime:process.version,platform:process.platform,architecture:process.arch,kernel:release(),providers:0,contexts_equal:functionCoordinateContextsEqual(a,b),same_mapping_other_profile:functionCoordinateContextsEqual(a,aOther),profile_digests_differ:a.digest!==aOther.digest})); assert.equal(functionCoordinateContextsEqual(a,b),false); assert.equal(functionCoordinateContextsEqual(a,aOther),true); for(const [name,functions,expected] of cases){ for(const e of functions){parseEvidence(e);assert.equal(session.recordEvidence(e).ok,true);} const reply=await client.callTool({name:'build_call_path',arguments:{functions,start:{address:'0x2000'},goal:{address:'0x6000'}}}); assert.notEqual(reply.isError,true,JSON.stringify(reply)); const evidence=parseEvidence(reply.structuredContent),r=evidence.normalized_result; assert.equal(r.status,expected); if(expected==='found'){ assert.equal(r.search_scope.exhaustive,true);assert.deepEqual(r.limitations,[]); assert.equal(r.shortest_hops,2);assert.deepEqual(r.paths[0].nodes.map(n=>n.address),['0x2000','0x5000','0x6000']); assert.deepEqual(r.paths[0].edges[1].evidence_links,[functions[1].evidence_id]); }else{assert.equal(r.search_scope.exhaustive,false);assert.equal(r.paths.length,0);} const row={name,status:r.status,shortest_hops:r.shortest_hops,exhaustive:r.search_scope.exhaustive,limitations:r.limitations,paths:r.paths.map(p=>p.nodes.map(n=>n.address))}; results.push(row);console.log(JSON.stringify(row)); } console.log(JSON.stringify({cases:results.length,assertions:'passed'})); }finally{await client.close();await server.close();await session.close();} JS rm -rf "$review_home" ``` The two synthetic mappings declare image bases `0x0` and `0x4000`. A observes `0x2000 -> 0x5000`; B observes `0x5000 -> 0x6000`. B's middle address denotes artifact offset `0x1000`, while A's middle address denotes offset `0x5000`. Expressed in A's coordinates, B observes `0x1000 -> 0x2000`; no supplied dossier observes A's middle function. Thus the pair is not contradictory, and there is no observed two-hop path from A's `0x2000` to A's `0x6000`. ## Expected behavior Do not join these equal address strings as one endpoint across explicitly different coordinate mappings. Reject incompatible Evidence or retain the uncovered middle function as unknown with a limitation. The supplied mixed pair must not produce an unqualified joined path. The call-path contract in `src/contracts/sessionToolContracts.ts:132-134` promises direct-callee paths using exact canonical addresses and unknown results for missing dossiers. `docs/tool-design.md`, “Define the contract,” requires identity, uncertainty and the distinction between observed and derived relationships to survive composition. `src/domain/native/functionCoordinateContext.ts:13-35` defines a provider-owned mapping of executable coordinates. The comparison-specific discussion in `docs/mcp-contracts.md`, “Function comparisons,” corroborates that equal addresses in different mappings cannot establish equality; it does not by itself define call-path admission. Preserve inputs with the same coordinate mapping even when unrelated analysis settings give their overall analysis profiles different digests. The third control demonstrates that distinction. ## Impact and scope The observed effect is a false derived call path and misleading exhaustive coverage when callers compose complete dossiers with unequal coordinate commitments. The same-mapping and different-profile/same-mapping controls returned the expected path. Removing the other-mapping dossier correctly returned unknown for the missing middle function. P2: this is a bounded Evidence-correctness defect. A caller can inspect the coordinate commitments and compose dossiers from one established coordinate context; missing observations then remain unknown. The replay does not establish how often shipped providers naturally produce such pairs, and does not claim a false negative, a real-program path, or every possible runtime edge. ## REA version 6.8.0; source revision `dbe2c97af34361b11b9504e37611dd47715b52cb`. ## Runtime and platform Node.js v24.22.1; macOS 27.0.1 arm64; locked repository dependencies and supplied prebuilt runtime. No engine version applies. ## Target and artifact context Synthetic complete `analyze_function` dossiers derived from the repository's `FUNCTION_COMPARISON_EXAMPLE`; known arm64 Mach-O subject; identical subject digest and synthetic provider identity. No actual executable was opened. ## Interface MCP ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence The inline reproduction exited 0 with all assertions passing. Its relevant observations were: | Case | Status | Hops | Exhaustive | Limitations | | --- | --- | --- | --- | --- | | Different coordinate mappings | found | 2 | true | [] | | Same mapping | found | 2 | true | [] | | Same mapping, different overall profile | found | 2 | true | [] | | Missing middle dossier | unknown | null | false | No analyze_function Evidence covers reachable function 0x5000 | All found cases returned `["0x2000","0x5000","0x6000"]`. The mixed pair's coordinate-equality predicate returned false. Assertions also checked that the second edge cites the second dossier. Separately executed production MCP controls rejected different subject digests, different architectures, different providers, duplicate function addresses and a wrong source operation. Source trace: `src/server/registerInvestigationTools.ts:141-158` resolves the session Evidence closure and invokes `buildCallPath`. `src/domain/functionDossierEvidence.ts` authenticates the source and requires a complete artifact-bound function dossier. `src/domain/callPath.ts:160-225` keys nodes by canonical address and checks subject/provider compatibility without checking mapping commitments. Its search subsequently treats the other-mapping dossier as coverage of the middle address. ## Typed outcome or recovery expected A typed incompatible-input response or an unknown result that preserves the uncovered coordinate context is acceptable. Retain the valid same-mapping controls, including the different overall profile digest, and existing missing-dossier uncertainty. ## Analysis or suspected cause Source-derived explanation: graph identity is the canonical address string. `assertCompatible` compares artifact digest, format, architecture and provider id/name/version, but does not establish coordinate correspondence. The presence of a dossier at the same string therefore suppresses the missing-frontier rule even when its declared mapping differs. ## Additional context - Expected source: the call-path contract in `src/contracts/sessionToolContracts.ts:132-134`, identity/uncertainty requirements in `docs/tool-design.md`, and provider-owned coordinate definition in `src/domain/native/functionCoordinateContext.ts`. - Failure source: the public MCP mixed-mapping output above, with source trace in `src/domain/callPath.ts:160-225` and its `inspectSearch` coverage lookup. - Evidence provenance: observed; source reasoning is explicitly identified above. - Local verification: reproduced using the production MCP handler and in-memory SDK transport, with authenticated supplied Evidence and no deep providers. - Reproduction completeness: complete for supplied synthetic Evidence. NOT_ENOUGH_INFO for natural acquisition frequency or real-provider behavior; no such run was permitted. - Restoration check: the mixed pair must not yield the unqualified joined path; preserve successful same-mapping composition, successful composition with unrelated profile differences, and unknown for the missing middle dossier. No repair or post-fix run was performed. The complete saved history and final live open/closed issue and pull-request searches contain no matching coordinate-join report. Related closed reports #6788 (unresolved calls), #5906/#6256 (explored-edge count), #6502 (cross-slice reference comparison), and #2322/#2550/#2371 (caller-only uncertainty), plus open PR #4984 (path enumeration bounds), concern distinct failing signals. The related current discussions and recent reports also contained no exact duplicate of this coordinate-join failure. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The accepted synthetic pair produces a path whose middle endpoint crosses explicitly different mappings, with exhaustive true and no limitation; reach is bounded to supplied multi-mapping dossiers. - Workaround status: available - Workaround basis: Inspect provider-owned coordinate commitments and supply dossiers from one established coordinate context. The same-mapping controls succeed; withholding the incompatible middle dossier preserves the observed unknown frontier instead of manufacturing a hop.
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#4363
No description provided.