[GH-ISSUE #4213] [Bug] Endpoint semantic traces omit matching response consumers #1656

Closed
opened 2026-10-16 21:08:32 +00:00 by bsakaguchi · 1 comment
Owner

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

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

An endpoint seed in trace-javascript-semantics selects the request but omits the response node carrying the same exact endpoint. For the local fetch and response.json() fixture below, the parent analysis retains both nodes and a resolved response-consumer edge. The forward trace returns only the request node, so the response and its consumer disappear from the requested subgraph.

The independently rebuilt public CLI reproduced the omission. The endpoint query returned status: "found", one seed, one node of kind request, zero relations, and zero relevant unknowns. Its coverage.status was partial, consistent with the parent graph's existing static-analysis limitations. There was no endpoint-specific diagnostic for the omitted response carrier; this report does not claim complete coverage was reported.

The response exact-node control returned two nodes of kinds response and call-site and one resolved consumed-by edge. The request exact-node control returned the request alone. Their union exactly matched independent forward reachability from both endpoint carriers, with three nodes and one relation. The parent request had no outgoing relation, and the parent response had no incoming relation, so request anchoring cannot recover this response through traversal. The absent-endpoint control returned zero seeds, nodes, and relations with status: "partial" and partial coverage.

The post-build standalone reproduction below completed all five public commands successfully. No MCP transport run, installed-package run, other platform run, or implementation restoration run was performed.

Reproduction

  1. Use the stated source revision with its locked dependencies. Run npm ci and npm run build:cached to build the runtime. Run the following standalone block from the repository root with the pinned Node on PATH. It creates an inert source fixture and isolated home/config directories, invokes the public CLI, compares the endpoint query with exact node queries, and removes its temporary fixture afterward. It never imports or executes the fixture and never sends a request to its endpoint string.
python3 - <<'PYREPRO'
import json
import os
from pathlib import Path
import subprocess
import tempfile

with tempfile.TemporaryDirectory(prefix="rea-endpoint-") as directory:
    root = Path(directory)
    (root / "app" / "src").mkdir(parents=True)
    (root / "home").mkdir()
    (root / "config").mkdir()
    source = root / "app" / "src" / "main.js"
    source.write_text('''async function load() {
  const response = await fetch("https://example.invalid/v1/data");
  return await response.json();
}
export { load };
''')
    original = source.read_bytes()
    environment = dict(os.environ, HOME=str(root / "home"),
                       XDG_CONFIG_HOME=str(root / "config"))
    def cli(command, argument):
        completed = subprocess.run(
            ["node", "scripts/rea.mjs", command, str(argument), "--json"],
            env=environment, capture_output=True, text=True, timeout=60,
            check=True)
        evidence = json.loads(completed.stdout)
        assert evidence.get("operation")
        return evidence
    application = cli("analyze-javascript-application", root / "app")
    graph = application["normalized_result"]["semantic_graph"]
    endpoint = "https://example.invalid/v1/data"
    carriers = [node for node in graph["nodes"]
                if node["properties"].get("endpoint") == endpoint]
    assert sorted(node["kind"] for node in carriers) == ["request", "response"]
    by_id = {node["node_id"]: node for node in graph["nodes"]}
    def trace(name, seed):
        input_file = root / (name + "-input.json")
        input_file.write_text(json.dumps({"application": application,
            "query": {"seed": seed, "direction": "forward-influence"}}))
        return cli("trace-javascript-semantics", input_file)["normalized_result"]
    selected = trace("endpoint", {"kind": "endpoint", "value": endpoint})
    exact = [trace(node["kind"], {"kind": "semantic-node",
             "node_id": node["node_id"]}) for node in carriers]
    absent = trace("absent", {"kind": "endpoint",
                   "value": "https://example.invalid/v1/missing"})
    expected_nodes = {node["node_id"] for node in carriers}
    expected_relations = set()
    pending = list(expected_nodes)
    while pending:
        current = pending.pop()
        for edge in graph["relations"]:
            if edge["source_node_id"] != current or edge["resolution"] != "resolved":
                continue
            expected_relations.add(edge["relation_id"])
            target = edge["target_node_id"]
            if target not in expected_nodes:
                expected_nodes.add(target)
                pending.append(target)
    recovered_nodes = {node["node_id"] for result in exact for node in result["nodes"]}
    recovered_relations = {edge["relation_id"] for result in exact for edge in result["relations"]}
    assert recovered_nodes == expected_nodes
    assert recovered_relations == expected_relations
    assert len(expected_nodes) == 3 and len(expected_relations) == 1
    assert selected["seed_node_ids"] == [node["node_id"] for node in carriers if node["kind"] == "request"]
    assert [node["kind"] for node in selected["nodes"]] == ["request"]
    assert selected["relations"] == []
    assert absent["seed_node_ids"] == [] and absent["nodes"] == [] and absent["relations"] == []
    response = next(node for node in carriers if node["kind"] == "response")
    consumer = next(edge for edge in graph["relations"] if edge["source_node_id"] == response["node_id"])
    assert consumer["relation"] == "consumed-by" and consumer["resolution"] == "resolved"
    assert by_id[consumer["target_node_id"]]["kind"] == "call-site"
    assert not any(edge["source_node_id"] == next(node["node_id"] for node in carriers if node["kind"] == "request") for edge in graph["relations"])
    assert not any(edge["target_node_id"] == response["node_id"] for edge in graph["relations"])
    assert source.read_bytes() == original
    def compact(result):
        return {"status": result["status"], "seed_count": len(result["seed_node_ids"]),
                "node_kinds": sorted(node["kind"] for node in result["nodes"]),
                "relations": [edge["relation"] for edge in result["relations"]],
                "coverage": result["coverage"]["status"],
                "relevant_unknowns": result["summary"]["relevant_unknowns"]}
    print(json.dumps({"parent_carriers": sorted(node["kind"] for node in carriers),
          "endpoint": compact(selected),
          "exact_nodes": [compact(result) for result in exact],
          "absent": compact(absent),
          "workaround_union": {"nodes": len(recovered_nodes), "relations": len(recovered_relations),
                               "equals_all_carrier_reachability": True},
          "public_commands_exited_zero": 5, "source_unchanged": True}, indent=2))
PYREPRO
  1. Compare the two endpoint carriers with the endpoint trace and the exact-node controls. The five subprocess commands must return valid Evidence and exit zero. The inline assertions also check the preserved consumer edge, lack of a forward request-to-response path, unchanged source bytes, zero matches for an absent endpoint, and restoration of the complete carrier-reachable subgraph by the union of exact-node traces.

Expected behavior

The endpoint query should select both the request and the response whose preserved properties.endpoint equals the query value. For this fixture, forward influence should retain three nodes, the request, response, and response consumer call site, and the resolved consumed-by edge. Static-analysis limitations and partial source coverage must remain visible; matching known endpoint carriers does not establish runtime execution or complete application semantics.

The semantic relation tracing contract promises every matching seed and the complete reachable subgraph. Its endpoint identity documentation explicitly names request and response nodes as carriers of exact endpoint values and distinguishes those values from display labels. This case uses a nonempty exact endpoint and default resolved-edge traversal, with no relation filter or candidate-edge opt-in.

Impact and scope

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A supported endpoint query omits a known matching response and its resolved consumer relationship from an analyst's forward-influence result. The observed scope is this local request/response fixture; broader request forms and platforms were not tested. Parent Evidence retains the omitted facts, and overall trace coverage remains partial.
  • Workaround status: available
  • Workaround basis: In the executed public CLI controls, selecting each matching request/response by its semantic-node ID and combining results by node/relation ID recovered exactly the complete carrier-reachable subgraph. This preserves the response consumer and the original Evidence without changing source or executing the application.

REA version

Source checkout at 62bf09809ef7bb224028916424f8f07652a20f4f, freshly rebuilt with locked dependencies. Current upstream main 08a897b2cd7b359fcb0674c82baec3f1a74af632 was inspected separately. The relevant implementation files are unchanged; the endpoint workflow documentation gained an unrelated native-handoff paragraph while retaining the cited endpoint contract. Current-main execution was not performed. Published-package applicability is NOT_ENOUGH_INFO because no installed release reproduction or complete release-applicability proof was run; the source package version string is not release proof.

Runtime and platform

Node v24.18.0 and npm 11.16.0 on macOS arm64, using the repository-pinned toolchain. The built-in static analyzer needs no browser, native analysis engine, or external service for this fixture.

Target and artifact context

A local application directory containing the complete inert JavaScript fixture above. Query inputs contain the complete unmodified application Evidence returned by the public analysis command. The endpoint text is a static identity; the fixture is never imported or executed, and no request is sent to it.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The standalone block was replayed after a fresh publication-checkout build. All five public CLI commands exited zero. Independent reachability and exact-node union assertions passed, including response consumer identity, absent-endpoint emptiness and unchanged source bytes. The three existing response-endpoint, semantic graph/query and semantic resource projection test files passed all 33 tests; they do not cover this seed omission.

Typed outcome or recovery expected

Repeat the same fixture and public endpoint query. Its seed_node_ids must contain both matching carrier IDs, and its returned node/relation identities must equal the union of the two exact-node controls, with three nodes and one resolved consumer edge. Preserve the response endpoint and edge identity from the selected parent graph. The absent-endpoint query must continue to return zero matches without upgrading the parent graph's partial coverage. No source change or passing post-fix run is claimed.

Analysis or suspected cause

Source-derived reasoning: the endpoint matcher checks only request nodes even though the producer retains the same exact endpoint on response nodes. Seed selection happens before traversal. In the observed graph, the request has no outgoing edge and the response has no incoming edge, so traversal from the request cannot recover the omitted response or consumer. No budget, relation filter or candidate exclusion explains this case.

Additional context

Closed #3328 reports missing response endpoint metadata. That metadata is present here, its existing extraction tests pass, and the response-node control reaches its consumer. The remaining failure is query selection of an already matching carrier, with an independent closure signal. Closed #3190 concerns absent route seeds matching display labels; this query uses a present exact endpoint value. Closed #3170 concerns boundary-field identity, a different seed kind.

No MCP transport, installed-package, real-provider, browser, other-platform or implementation-restoration run was performed. Broader request forms and occurrence frequency are NOT_ENOUGH_INFO because only this reduced fixture and its controls were executed. The last known good revision is NOT_ENOUGH_INFO because no regression boundary was established.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4213 ## 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 An endpoint seed in `trace-javascript-semantics` selects the request but omits the response node carrying the same exact endpoint. For the local `fetch` and `response.json()` fixture below, the parent analysis retains both nodes and a resolved response-consumer edge. The forward trace returns only the request node, so the response and its consumer disappear from the requested subgraph. The independently rebuilt public CLI reproduced the omission. The endpoint query returned `status: "found"`, one seed, one node of kind `request`, zero relations, and zero relevant unknowns. Its `coverage.status` was `partial`, consistent with the parent graph's existing static-analysis limitations. There was no endpoint-specific diagnostic for the omitted response carrier; this report does not claim complete coverage was reported. The response exact-node control returned two nodes of kinds `response` and `call-site` and one resolved `consumed-by` edge. The request exact-node control returned the request alone. Their union exactly matched independent forward reachability from both endpoint carriers, with three nodes and one relation. The parent request had no outgoing relation, and the parent response had no incoming relation, so request anchoring cannot recover this response through traversal. The absent-endpoint control returned zero seeds, nodes, and relations with `status: "partial"` and partial coverage. The post-build standalone reproduction below completed all five public commands successfully. No MCP transport run, installed-package run, other platform run, or implementation restoration run was performed. ## Reproduction 1. Use the stated source revision with its locked dependencies. Run `npm ci` and `npm run build:cached` to build the runtime. Run the following standalone block from the repository root with the pinned Node on `PATH`. It creates an inert source fixture and isolated home/config directories, invokes the public CLI, compares the endpoint query with exact node queries, and removes its temporary fixture afterward. It never imports or executes the fixture and never sends a request to its endpoint string. ```sh python3 - <<'PYREPRO' import json import os from pathlib import Path import subprocess import tempfile with tempfile.TemporaryDirectory(prefix="rea-endpoint-") as directory: root = Path(directory) (root / "app" / "src").mkdir(parents=True) (root / "home").mkdir() (root / "config").mkdir() source = root / "app" / "src" / "main.js" source.write_text('''async function load() { const response = await fetch("https://example.invalid/v1/data"); return await response.json(); } export { load }; ''') original = source.read_bytes() environment = dict(os.environ, HOME=str(root / "home"), XDG_CONFIG_HOME=str(root / "config")) def cli(command, argument): completed = subprocess.run( ["node", "scripts/rea.mjs", command, str(argument), "--json"], env=environment, capture_output=True, text=True, timeout=60, check=True) evidence = json.loads(completed.stdout) assert evidence.get("operation") return evidence application = cli("analyze-javascript-application", root / "app") graph = application["normalized_result"]["semantic_graph"] endpoint = "https://example.invalid/v1/data" carriers = [node for node in graph["nodes"] if node["properties"].get("endpoint") == endpoint] assert sorted(node["kind"] for node in carriers) == ["request", "response"] by_id = {node["node_id"]: node for node in graph["nodes"]} def trace(name, seed): input_file = root / (name + "-input.json") input_file.write_text(json.dumps({"application": application, "query": {"seed": seed, "direction": "forward-influence"}})) return cli("trace-javascript-semantics", input_file)["normalized_result"] selected = trace("endpoint", {"kind": "endpoint", "value": endpoint}) exact = [trace(node["kind"], {"kind": "semantic-node", "node_id": node["node_id"]}) for node in carriers] absent = trace("absent", {"kind": "endpoint", "value": "https://example.invalid/v1/missing"}) expected_nodes = {node["node_id"] for node in carriers} expected_relations = set() pending = list(expected_nodes) while pending: current = pending.pop() for edge in graph["relations"]: if edge["source_node_id"] != current or edge["resolution"] != "resolved": continue expected_relations.add(edge["relation_id"]) target = edge["target_node_id"] if target not in expected_nodes: expected_nodes.add(target) pending.append(target) recovered_nodes = {node["node_id"] for result in exact for node in result["nodes"]} recovered_relations = {edge["relation_id"] for result in exact for edge in result["relations"]} assert recovered_nodes == expected_nodes assert recovered_relations == expected_relations assert len(expected_nodes) == 3 and len(expected_relations) == 1 assert selected["seed_node_ids"] == [node["node_id"] for node in carriers if node["kind"] == "request"] assert [node["kind"] for node in selected["nodes"]] == ["request"] assert selected["relations"] == [] assert absent["seed_node_ids"] == [] and absent["nodes"] == [] and absent["relations"] == [] response = next(node for node in carriers if node["kind"] == "response") consumer = next(edge for edge in graph["relations"] if edge["source_node_id"] == response["node_id"]) assert consumer["relation"] == "consumed-by" and consumer["resolution"] == "resolved" assert by_id[consumer["target_node_id"]]["kind"] == "call-site" assert not any(edge["source_node_id"] == next(node["node_id"] for node in carriers if node["kind"] == "request") for edge in graph["relations"]) assert not any(edge["target_node_id"] == response["node_id"] for edge in graph["relations"]) assert source.read_bytes() == original def compact(result): return {"status": result["status"], "seed_count": len(result["seed_node_ids"]), "node_kinds": sorted(node["kind"] for node in result["nodes"]), "relations": [edge["relation"] for edge in result["relations"]], "coverage": result["coverage"]["status"], "relevant_unknowns": result["summary"]["relevant_unknowns"]} print(json.dumps({"parent_carriers": sorted(node["kind"] for node in carriers), "endpoint": compact(selected), "exact_nodes": [compact(result) for result in exact], "absent": compact(absent), "workaround_union": {"nodes": len(recovered_nodes), "relations": len(recovered_relations), "equals_all_carrier_reachability": True}, "public_commands_exited_zero": 5, "source_unchanged": True}, indent=2)) PYREPRO ``` 2. Compare the two endpoint carriers with the endpoint trace and the exact-node controls. The five subprocess commands must return valid Evidence and exit zero. The inline assertions also check the preserved consumer edge, lack of a forward request-to-response path, unchanged source bytes, zero matches for an absent endpoint, and restoration of the complete carrier-reachable subgraph by the union of exact-node traces. ## Expected behavior The endpoint query should select both the request and the response whose preserved `properties.endpoint` equals the query value. For this fixture, forward influence should retain three nodes, the request, response, and response consumer call site, and the resolved `consumed-by` edge. Static-analysis limitations and partial source coverage must remain visible; matching known endpoint carriers does not establish runtime execution or complete application semantics. The semantic relation tracing contract promises every matching seed and the complete reachable subgraph. Its endpoint identity documentation explicitly names request and response nodes as carriers of exact endpoint values and distinguishes those values from display labels. This case uses a nonempty exact endpoint and default resolved-edge traversal, with no relation filter or candidate-edge opt-in. ## Impact and scope - Impact level: P2 - Assessment status: supported - Impact basis: A supported endpoint query omits a known matching response and its resolved consumer relationship from an analyst's forward-influence result. The observed scope is this local request/response fixture; broader request forms and platforms were not tested. Parent Evidence retains the omitted facts, and overall trace coverage remains partial. - Workaround status: available - Workaround basis: In the executed public CLI controls, selecting each matching request/response by its semantic-node ID and combining results by node/relation ID recovered exactly the complete carrier-reachable subgraph. This preserves the response consumer and the original Evidence without changing source or executing the application. ## REA version Source checkout at [62bf09809ef7bb224028916424f8f07652a20f4f](https://github.com/morluto/rea/tree/62bf09809ef7bb224028916424f8f07652a20f4f), freshly rebuilt with locked dependencies. Current upstream main [08a897b2cd7b359fcb0674c82baec3f1a74af632](https://github.com/morluto/rea/tree/08a897b2cd7b359fcb0674c82baec3f1a74af632) was inspected separately. The relevant implementation files are unchanged; the endpoint workflow documentation gained an unrelated native-handoff paragraph while retaining the cited endpoint contract. Current-main execution was not performed. Published-package applicability is NOT_ENOUGH_INFO because no installed release reproduction or complete release-applicability proof was run; the source package version string is not release proof. ## Runtime and platform Node v24.18.0 and npm 11.16.0 on macOS arm64, using the repository-pinned toolchain. The built-in static analyzer needs no browser, native analysis engine, or external service for this fixture. ## Target and artifact context A local application directory containing the complete inert JavaScript fixture above. Query inputs contain the complete unmodified application Evidence returned by the public analysis command. The endpoint text is a static identity; the fixture is never imported or executed, and no request is sent to it. ## Interface CLI ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence - Expected source: [docs/javascript-application-workflows.md:99-108](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/docs/javascript-application-workflows.md#L99-L108) promises every matching seed and the complete reachable subgraph. The same document at lines 169-184 preserves exact request/response `properties.endpoint` values for tracing, independently of display labels. [src/domain/javascript/javascriptSemanticAnalysis.response_endpoints.test.ts:14-34](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/domain/javascript/javascriptSemanticAnalysis.response_endpoints.test.ts#L14-L34) preserves the endpoint on uniquely consumed responses. - Failure source: the executed endpoint query returns the request alone although the parent Evidence retains both matching carriers. [src/domain/javascript/javascriptSemanticQueryIdentity.ts:72-94](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/domain/javascript/javascriptSemanticQueryIdentity.ts#L72-L94) restricts endpoint seeds to request nodes. [src/application/javascript/JavaScriptSemanticGraphDataProjection.ts:146-219](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/application/javascript/JavaScriptSemanticGraphDataProjection.ts#L146-L219) preserves both carriers and creates the resolved response-consumer edge. - Public entry and guards: [src/cli/applicationCommands.ts:41-53](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/cli/applicationCommands.ts#L41-L53) registers the trace JSON input. [src/application/javascript/JavaScriptSemanticTraceService.ts:25-82](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/application/javascript/JavaScriptSemanticTraceService.ts#L25-L82) parses the parent Evidence before querying. [src/application/javascript/JavaScriptApplicationEvidenceGraph.ts:85-168](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/application/javascript/JavaScriptApplicationEvidenceGraph.ts#L85-L168) checks provider/predicate, authority/confidence, parameters, subject and result shape. [src/domain/javascript/javascriptSemanticQuery.ts:38-108](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/domain/javascript/javascriptSemanticQuery.ts#L38-L108) selects seeds before directed traversal and result projection. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The standalone block was replayed after a fresh publication-checkout build. All five public CLI commands exited zero. Independent reachability and exact-node union assertions passed, including response consumer identity, absent-endpoint emptiness and unchanged source bytes. The three existing response-endpoint, semantic graph/query and semantic resource projection test files passed all 33 tests; they do not cover this seed omission. ## Typed outcome or recovery expected Repeat the same fixture and public endpoint query. Its `seed_node_ids` must contain both matching carrier IDs, and its returned node/relation identities must equal the union of the two exact-node controls, with three nodes and one resolved consumer edge. Preserve the response endpoint and edge identity from the selected parent graph. The absent-endpoint query must continue to return zero matches without upgrading the parent graph's partial coverage. No source change or passing post-fix run is claimed. ## Analysis or suspected cause Source-derived reasoning: the endpoint matcher checks only request nodes even though the producer retains the same exact endpoint on response nodes. Seed selection happens before traversal. In the observed graph, the request has no outgoing edge and the response has no incoming edge, so traversal from the request cannot recover the omitted response or consumer. No budget, relation filter or candidate exclusion explains this case. ## Additional context Closed [#3328](https://github.com/morluto/rea/issues/3328) reports missing response endpoint metadata. That metadata is present here, its existing extraction tests pass, and the response-node control reaches its consumer. The remaining failure is query selection of an already matching carrier, with an independent closure signal. Closed [#3190](https://github.com/morluto/rea/issues/3190) concerns absent route seeds matching display labels; this query uses a present exact endpoint value. Closed [#3170](https://github.com/morluto/rea/issues/3170) concerns boundary-field identity, a different seed kind. No MCP transport, installed-package, real-provider, browser, other-platform or implementation-restoration run was performed. Broader request forms and occurrence frequency are NOT_ENOUGH_INFO because only this reduced fixture and its controls were executed. The last known good revision is NOT_ENOUGH_INFO because no regression boundary was established.
Author
Owner

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

on me

<!-- gh-comment-id:6200318231 --> @JNTMTMTM commented on GitHub (Oct 15, 2026): on me
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#1656
No description provided.