[GH-ISSUE #2568] fix(javascript): exclude read unknowns from callers trace results #885

Closed
opened 2026-10-15 18:29:17 +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/2568

Summary

A callers semantic trace through a resolved call reports an unresolved property read at the reached caller as a relevant unknown. The trace follows only calls relations, so this unrelated reads frontier inflates summary.relevant_unknowns and adds a query-specific unknown limitation. The traversed nodes and relations remain correct.

Steps to reproduce

  1. On the baseline in Runtime or environment, add the following test at src/domain/javascript/semanticUnknownDirection.test.ts.
  2. Run npm run test:focused -- src/domain/javascript/semanticUnknownDirection.test.ts from the repository root. The final assertion fails; the backward-provenance and explicit-relation controls pass.
import { expect, it } from "vitest";
import {
  JAVASCRIPT_SEMANTIC_RELATION_FAMILIES,
  JAVASCRIPT_SEMANTIC_RELATION_FAMILY,
} from "./javascriptSemanticGraphSchemas.js";
import {
  createJavaScriptSemanticGraph,
  createJavaScriptSemanticGraphNode,
  createJavaScriptSemanticGraphRelation,
  createJavaScriptSemanticGraphUnknown,
  JavaScriptSemanticEvidenceContextRegistry,
} from "./javascriptSemanticGraph.js";
import { queryJavaScriptSemanticGraph } from "./javascriptSemanticQuery.js";
import {
  semanticGraphTestEvidence,
  unknownSemanticGraphTestEvidence,
} from "../../../tests/support/javascriptSemanticGraphFixture.js";

it("limits callers unknowns to traversable relation kinds", () => {
  const digest = "a".repeat(64);
  const contexts = new JavaScriptSemanticEvidenceContextRegistry();
  const node = (role: string) =>
    createJavaScriptSemanticGraphNode({
      kind: "function",
      identity: {
        artifact_sha256: digest,
        module_path: "sample.js",
        source_range: {
          start: { line: 1, column: role.length },
          end: { line: 1, column: role.length + 1 },
        },
        role_key: role,
      },
      function_node_id: null,
      application_node_ids: [],
      label: role,
      properties: {},
      evidence: semanticGraphTestEvidence(),
    }, contexts);
  const caller = node("caller");
  const seed = node("seed");
  const call = createJavaScriptSemanticGraphRelation({
    source_node_id: caller.node_id,
    target_node_id: seed.node_id,
    relation: "calls",
    resolution: "resolved",
    properties: {},
    evidence: semanticGraphTestEvidence("inferred"),
  }, contexts);
  const unknown = createJavaScriptSemanticGraphUnknown({
    node_id: caller.node_id,
    family: "data-flow",
    relation_kinds: ["reads"],
    reason: "dynamic-property",
    detail: "Unresolved property read at the caller.",
    candidate_node_ids: [],
    evidence: unknownSemanticGraphTestEvidence(),
  }, contexts);
  const graph = createJavaScriptSemanticGraph({
    schema: "JavaScriptSemanticRelationGraph",
    root_artifact_sha256: digest,
    application_graph_id: `jag_${"b".repeat(64)}`,
    root_node_ids: [seed.node_id],
    evidence_contexts: contexts.contexts,
    nodes: [caller, seed],
    relations: [call],
    fingerprints: [],
    unknowns: [unknown],
    coverage: {
      status: "partial",
      truncated: false,
      omitted_nodes: null,
      omitted_relations: null,
      limits: [],
      families: JAVASCRIPT_SEMANTIC_RELATION_FAMILIES.map((family) => ({
        family,
        status: family === "data-flow" ? ("unknown" as const) : ("complete" as const),
        retained_relations:
          JAVASCRIPT_SEMANTIC_RELATION_FAMILY[call.relation] === family ? 1 : 0,
        omitted_relations: family === "data-flow" ? null : 0,
        unknown_ids: family === "data-flow" ? [unknown.unknown_id] : [],
      })),
    },
    limitations: ["One property read remains unresolved."],
  });
  const query = (direction: "callers" | "backward-provenance", allowed_relations?: ["calls"]) =>
    queryJavaScriptSemanticGraph(graph, {
      seed: { kind: "semantic-node", node_id: seed.node_id },
      direction,
      ...(allowed_relations === undefined ? {} : { allowed_relations }),
    });
  const callers = query("callers");
  const filtered = query("callers", ["calls"]);
  const backward = query("backward-provenance");
  expect(backward.unknowns.map(({ unknown_id }) => unknown_id)).toEqual([unknown.unknown_id]);
  expect(filtered.unknowns).toEqual([]);
  expect(filtered.coverage.status).toBe("partial");
  expect(callers.summary.traversed_nodes).toBe(2);
  expect(callers.unknowns).toEqual([]);
});

Expected behavior

For this valid graph, callers reaches the caller through the resolved calls edge but does not admit reads as a traversable kind. Its result should contain no relevant unknown, report summary.relevant_unknowns: 0, and omit the query-specific message that relevant semantics remain unknown. Backward provenance may retain the reads unknown; the graph-wide coverage remains partial in either query because the source graph itself records an unresolved read.

Actual behavior

The focused test returned one reads unknown for callers, so the final expect(callers.unknowns).toEqual([]) failed. The same query reports summary.relevant_unknowns: 1 and adds the relevant-semantics-unknown limitation. The backward-provenance control retained the unknown, and adding allowed_relations: ["calls"] removed it from callers while leaving graph-wide coverage partial.

Evidence

  • Expected source: docs/javascript-application-workflows.md:53-58 defines direction-scoped semantic tracing; src/domain/javascript/javascriptSemanticQuery.ts:143-181 limits callers traversal to calls, and src/domain/javascript/javascriptSemanticGraphTraversal.test.ts:380-455 treats reads candidate relations as irrelevant to callers.
  • Failure source: src/domain/javascript/javascriptSemanticQuery.ts:241-256 selects reached-node unknowns without checking query direction; the focused test above constructed a valid graph and observed the contrary result.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Restoration check

With the same graph and unfiltered callers input, return no unknown, summary.relevant_unknowns: 0, and no query-specific relevant-unknown limitation while still returning both reached nodes. Keep the reads unknown in backward provenance. The explicit allowed_relations: ["calls"] control should agree with the unfiltered callers result; graph-wide partial coverage is expected to remain partial.

Runtime or environment

Additional context

The broader semantic tracing capability was tracked in https://github.com/morluto/rea/issues/404; that closed feature issue does not describe this direction-scoping failure.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A bounded callers trace returns an unrelated unknown, count, and limitation, which can mislead consumers that inspect trace uncertainty; the trace topology remains correct.
  • Workaround status: available
  • Workaround basis: For callers, setting the supported allowed_relations: ["calls"] removes the unrelated reads unknown without excluding any relation kind that callers can traverse.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2568 ## Summary A `callers` semantic trace through a resolved call reports an unresolved property read at the reached caller as a relevant unknown. The trace follows only `calls` relations, so this unrelated `reads` frontier inflates `summary.relevant_unknowns` and adds a query-specific unknown limitation. The traversed nodes and relations remain correct. ## Steps to reproduce 1. On the baseline in Runtime or environment, add the following test at `src/domain/javascript/semanticUnknownDirection.test.ts`. 2. Run `npm run test:focused -- src/domain/javascript/semanticUnknownDirection.test.ts` from the repository root. The final assertion fails; the backward-provenance and explicit-relation controls pass. ```ts import { expect, it } from "vitest"; import { JAVASCRIPT_SEMANTIC_RELATION_FAMILIES, JAVASCRIPT_SEMANTIC_RELATION_FAMILY, } from "./javascriptSemanticGraphSchemas.js"; import { createJavaScriptSemanticGraph, createJavaScriptSemanticGraphNode, createJavaScriptSemanticGraphRelation, createJavaScriptSemanticGraphUnknown, JavaScriptSemanticEvidenceContextRegistry, } from "./javascriptSemanticGraph.js"; import { queryJavaScriptSemanticGraph } from "./javascriptSemanticQuery.js"; import { semanticGraphTestEvidence, unknownSemanticGraphTestEvidence, } from "../../../tests/support/javascriptSemanticGraphFixture.js"; it("limits callers unknowns to traversable relation kinds", () => { const digest = "a".repeat(64); const contexts = new JavaScriptSemanticEvidenceContextRegistry(); const node = (role: string) => createJavaScriptSemanticGraphNode({ kind: "function", identity: { artifact_sha256: digest, module_path: "sample.js", source_range: { start: { line: 1, column: role.length }, end: { line: 1, column: role.length + 1 }, }, role_key: role, }, function_node_id: null, application_node_ids: [], label: role, properties: {}, evidence: semanticGraphTestEvidence(), }, contexts); const caller = node("caller"); const seed = node("seed"); const call = createJavaScriptSemanticGraphRelation({ source_node_id: caller.node_id, target_node_id: seed.node_id, relation: "calls", resolution: "resolved", properties: {}, evidence: semanticGraphTestEvidence("inferred"), }, contexts); const unknown = createJavaScriptSemanticGraphUnknown({ node_id: caller.node_id, family: "data-flow", relation_kinds: ["reads"], reason: "dynamic-property", detail: "Unresolved property read at the caller.", candidate_node_ids: [], evidence: unknownSemanticGraphTestEvidence(), }, contexts); const graph = createJavaScriptSemanticGraph({ schema: "JavaScriptSemanticRelationGraph", root_artifact_sha256: digest, application_graph_id: `jag_${"b".repeat(64)}`, root_node_ids: [seed.node_id], evidence_contexts: contexts.contexts, nodes: [caller, seed], relations: [call], fingerprints: [], unknowns: [unknown], coverage: { status: "partial", truncated: false, omitted_nodes: null, omitted_relations: null, limits: [], families: JAVASCRIPT_SEMANTIC_RELATION_FAMILIES.map((family) => ({ family, status: family === "data-flow" ? ("unknown" as const) : ("complete" as const), retained_relations: JAVASCRIPT_SEMANTIC_RELATION_FAMILY[call.relation] === family ? 1 : 0, omitted_relations: family === "data-flow" ? null : 0, unknown_ids: family === "data-flow" ? [unknown.unknown_id] : [], })), }, limitations: ["One property read remains unresolved."], }); const query = (direction: "callers" | "backward-provenance", allowed_relations?: ["calls"]) => queryJavaScriptSemanticGraph(graph, { seed: { kind: "semantic-node", node_id: seed.node_id }, direction, ...(allowed_relations === undefined ? {} : { allowed_relations }), }); const callers = query("callers"); const filtered = query("callers", ["calls"]); const backward = query("backward-provenance"); expect(backward.unknowns.map(({ unknown_id }) => unknown_id)).toEqual([unknown.unknown_id]); expect(filtered.unknowns).toEqual([]); expect(filtered.coverage.status).toBe("partial"); expect(callers.summary.traversed_nodes).toBe(2); expect(callers.unknowns).toEqual([]); }); ``` ## Expected behavior For this valid graph, `callers` reaches the caller through the resolved `calls` edge but does not admit `reads` as a traversable kind. Its result should contain no relevant unknown, report `summary.relevant_unknowns: 0`, and omit the query-specific message that relevant semantics remain unknown. Backward provenance may retain the `reads` unknown; the graph-wide coverage remains partial in either query because the source graph itself records an unresolved read. ## Actual behavior The focused test returned one `reads` unknown for `callers`, so the final `expect(callers.unknowns).toEqual([])` failed. The same query reports `summary.relevant_unknowns: 1` and adds the relevant-semantics-unknown limitation. The backward-provenance control retained the unknown, and adding `allowed_relations: ["calls"]` removed it from `callers` while leaving graph-wide coverage partial. ## Evidence - Expected source: `docs/javascript-application-workflows.md:53-58` defines direction-scoped semantic tracing; `src/domain/javascript/javascriptSemanticQuery.ts:143-181` limits `callers` traversal to `calls`, and `src/domain/javascript/javascriptSemanticGraphTraversal.test.ts:380-455` treats `reads` candidate relations as irrelevant to `callers`. - Failure source: `src/domain/javascript/javascriptSemanticQuery.ts:241-256` selects reached-node unknowns without checking query direction; the focused test above constructed a valid graph and observed the contrary result. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete ## Restoration check With the same graph and unfiltered `callers` input, return no unknown, `summary.relevant_unknowns: 0`, and no query-specific relevant-unknown limitation while still returning both reached nodes. Keep the `reads` unknown in backward provenance. The explicit `allowed_relations: ["calls"]` control should agree with the unfiltered `callers` result; graph-wide partial coverage is expected to remain partial. ## Runtime or environment - Failure verified at a2d8b94dd50dab8d966239dd63ab84718aa8b4b4 and still present at 8b181716aca2cb467be499cba226abfb5ae19141 ## Additional context The broader semantic tracing capability was tracked in https://github.com/morluto/rea/issues/404; that closed feature issue does not describe this direction-scoping failure. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A bounded `callers` trace returns an unrelated unknown, count, and limitation, which can mislead consumers that inspect trace uncertainty; the trace topology remains correct. - Workaround status: available - Workaround basis: For `callers`, setting the supported `allowed_relations: ["calls"]` removes the unrelated `reads` unknown without excluding any relation kind that `callers` can traverse.
bsakaguchi 2026-10-15 18:29:17 +00:00
Author
Owner

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

Addressed on main in 896696b2e761e34c267977bb5c6982c607a622d0.

Unknown frontier selection respects query relation and direction; callers traces no longer inherit unrelated read unknowns.

Validation: Affected suites passed in the merged 65-file run (1169 passed, 2 explicit skips), with full static/architecture checks and documentation checks passing. Final installed-package verification is recorded separately in the audit.

Verification scope: Controlled source and production-boundary verification on macOS with pinned Node 24.18.0; real external engines and Windows hosts were not independently rerun. Closure covers the described source defect, not broader provider conformance or performance.

<!-- gh-comment-id:6159192934 --> @morluto commented on GitHub (Oct 14, 2026): Addressed on main in [896696b2e761e34c267977bb5c6982c607a622d0](https://github.com/morluto/rea/commit/896696b2e761e34c267977bb5c6982c607a622d0). Unknown frontier selection respects query relation and direction; callers traces no longer inherit unrelated read unknowns. Validation: Affected suites passed in the merged 65-file run (1169 passed, 2 explicit skips), with full static/architecture checks and documentation checks passing. Final installed-package verification is recorded separately in the audit. Verification scope: Controlled source and production-boundary verification on macOS with pinned Node 24.18.0; real external engines and Windows hosts were not independently rerun. Closure covers the described source defect, not broader provider conformance or performance.
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#885
No description provided.