[GH-ISSUE #6189] [Bug] trace-javascript-semantics: property seeds omit unresolved accesses and their unknowns #3770

Closed
opened 2026-10-31 00:43:51 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @jacobmalibiran on GitHub (Oct 22, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6189

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

trace-javascript-semantics with a property seed returns total_seed_matches: 0
and relevant_unknowns: 0 when every access to that property goes through a
receiver REA doesn't resolve, such as this in an ES5 constructor/prototype or an
ES2015 class.

The parent semantic graph does record those accesses. Each is an expression
node (for example read:/config/ACCEL, write:/speed) whose
properties.property_path names the property, with its own object-flow /
ambiguous-target unknown ("receiver is unresolved or not a retained container").
The trace surfaces none of them, so the result can't be told apart from a property
that never occurs.

First seen through MCP on a public target, wayou's T-Rex runner index.js
(https://wayou.github.io/t-rex-runner/, SHA-256
e7a50d337bdbe4299068de034e4564cfe5fd45ca9257ded37b6ada9330cedf0f). Seeds
ACCELERATION and currentSpeed returned no seeds and no unknowns, although the
graph held read:/config/ACCELERATION and write:/currentSpeed access nodes with
unknowns.

Reproduction

Reproduced on every run (3 runs, including one on main at 2451e187b). Run from
the repository root after npm run build:cached:

R=$(mktemp -d)
mkdir -p "$R/es5" "$R/class" "$R/module"

cat > "$R/es5/game.js" <<'EOF'
function Game() {
  this.config = Game.config;
  this.speed = this.config.START;
}
Game.config = { START: 6, ACCEL: 0.001, MAX: 13 };
Game.prototype.update = function () {
  if (this.speed < this.config.MAX) {
    this.speed += this.config.ACCEL;
  }
};
EOF

cat > "$R/class/game.js" <<'EOF'
export class Game {
  constructor() {
    this.config = { START: 6, ACCEL: 0.001, MAX: 13 };
    this.speed = this.config.START;
  }
  update() {
    if (this.speed < this.config.MAX) {
      this.speed += this.config.ACCEL;
    }
  }
}
EOF

cat > "$R/module/game.js" <<'EOF'
const config = { START: 6, ACCEL: 0.001, MAX: 13 };
const state = { speed: config.START };
export function update() {
  if (state.speed < config.MAX) {
    state.speed += config.ACCEL;
  }
}
EOF

for v in es5 class module; do
  node scripts/rea.mjs analyze-javascript-application "$R/$v" \
    --artifact-format directory --format json > "$R/$v.json" 2>/dev/null
  for seed in '{"kind":"property","name":"ACCEL"}' \
              '{"kind":"property","name":"speed"}' \
              '{"kind":"literal","value":0.001}'; do
    jq -c --argjson s "$seed" \
      '{application: ., query: {seed: $s, direction: "forward-influence"}}' \
      "$R/$v.json" > "$R/query.json"
    printf '%-7s %-36s ' "$v" "$seed"
    node scripts/rea.mjs trace-javascript-semantics "$R/query.json" --format json |
      jq -c '.normalized_result | {status, seeds: .summary.total_seed_matches,
        relations: .summary.traversed_relations, unknowns: .summary.relevant_unknowns}'
  done
  printf '%-7s unresolved accesses in graph: ' "$v"
  jq -c '[.normalized_result.semantic_graph.nodes[]
    | select(.kind == "expression" and (.properties.property_path | type) == "array")
    | .label] | unique' "$R/$v.json"
done

Output on main at 2451e187b:

es5     {"kind":"property","name":"ACCEL"}   {"status":"partial","seeds":0,"relations":0,"unknowns":0}
es5     {"kind":"property","name":"speed"}   {"status":"partial","seeds":0,"relations":0,"unknowns":0}
es5     {"kind":"literal","value":0.001}     {"status":"partial","seeds":0,"relations":0,"unknowns":0}
es5     unresolved accesses in graph: ["read:/config","read:/config/ACCEL","read:/config/MAX","read:/config/START","read:/speed","write:/config","write:/prototype/update","write:/speed"]
class   {"kind":"property","name":"ACCEL"}   {"status":"partial","seeds":0,"relations":0,"unknowns":0}
class   {"kind":"property","name":"speed"}   {"status":"partial","seeds":0,"relations":0,"unknowns":0}
class   {"kind":"literal","value":0.001}     {"status":"partial","seeds":0,"relations":0,"unknowns":0}
class   unresolved accesses in graph: ["read:/config/ACCEL","read:/config/MAX","read:/config/START","read:/speed","write:/config","write:/speed"]
module  {"kind":"property","name":"ACCEL"}   {"status":"found","seeds":1,"relations":1,"unknowns":0}
module  {"kind":"property","name":"speed"}   {"status":"ambiguous","seeds":1,"relations":0,"unknowns":2}
module  {"kind":"literal","value":0.001}     {"status":"found","seeds":1,"relations":2,"unknowns":0}
module  unresolved accesses in graph: ["read:/ACCEL","read:/MAX","read:/START","read:/speed","write:/speed"]

module is a control: the same rule with module-level objects works. The literal
rows are a separate producer gap (see Additional context); this report is about
the property rows.

Expected behavior

The tool contract says "Dynamic or unsupported semantics remain explicit unknowns",
and AGENTS.md
says "Unresolved identity or applicability must remain unknown rather than be
treated as absent". The docs say the trace "returns every matching seed".

Proposed: a property seed also matches access nodes that name the property but
have no property slot, so their existing unknowns appear in the result. For es5
and class that includes read:/config/ACCEL and write:/speed. Results for
properties that already have slots stay the same. This mirrors #4213, where
endpoint seeds were extended to the response carrier of the same endpoint.

Impact and scope

  • An agent asking "where is X updated?" gets an empty result for code that reaches
    its state through this, reads that as "not present", and falls back to raw
    source. No incorrect fact is emitted; matching unknowns are left out.
  • Tested: ES5 constructor/prototype and ES2015 class fail; module-level objects
    work. Object-literal methods and .call/.bind are untested.
  • Frequency on real-world code: NOT_ENOUGH_INFO beyond the one public target above.

REA version

6.7.0 at main 2451e187beab43a811bb6149dfd3af3ad8178327

Runtime and platform

Node v24.22.0; macOS 26.5.1 arm64

Target and artifact context

Directory with one inert JavaScript file (reduced fixture above).

Interface

CLI (reduced reproduction); first observed through MCP.

Earliest failure stage

Transport or result projection: the graph records the accesses and their unknowns;
the trace's seed resolution leaves them out.

Relevant output or evidence

Commands and output: see Reproduction. Source, read rather than executed:

Typed outcome or recovery expected

The unresolved access nodes as seeds, with their unknowns, so the caller can
inspect those locations instead of concluding absence.

Analysis or suspected cause

Source-derived: the graph owner already records these accesses with
property_path, but seed resolution only consults property-slot nodes.
Hypothesis: the fix belongs in seed resolution, as in #4213.

Additional context

Related gaps found while investigating, each probably its own feature request:

  1. this receivers in constructors, prototype methods and class methods aren't
    resolved (related: #4437 for class-instance method calls).
  2. An object literal assigned onto a function (Game.config = {…}) isn't modelled
    as a container, so its values get no literal nodes; hence the literal rows above.
  3. Compound assignment isn't followed
    (javascriptSymbolicBindings.ts:620),
    so even in module, ACCEL traces to read:/ACCEL and stops before
    write:/speed.
Originally created by @jacobmalibiran on GitHub (Oct 22, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6189 ## 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 `trace-javascript-semantics` with a `property` seed returns `total_seed_matches: 0` and `relevant_unknowns: 0` when every access to that property goes through a receiver REA doesn't resolve, such as `this` in an ES5 constructor/prototype or an ES2015 class. The parent semantic graph does record those accesses. Each is an `expression` node (for example `read:/config/ACCEL`, `write:/speed`) whose `properties.property_path` names the property, with its own `object-flow` / `ambiguous-target` unknown ("receiver is unresolved or not a retained container"). The trace surfaces none of them, so the result can't be told apart from a property that never occurs. First seen through MCP on a public target, wayou's T-Rex runner `index.js` (<https://wayou.github.io/t-rex-runner/>, SHA-256 `e7a50d337bdbe4299068de034e4564cfe5fd45ca9257ded37b6ada9330cedf0f`). Seeds `ACCELERATION` and `currentSpeed` returned no seeds and no unknowns, although the graph held `read:/config/ACCELERATION` and `write:/currentSpeed` access nodes with unknowns. ## Reproduction Reproduced on every run (3 runs, including one on `main` at `2451e187b`). Run from the repository root after `npm run build:cached`: ```bash R=$(mktemp -d) mkdir -p "$R/es5" "$R/class" "$R/module" cat > "$R/es5/game.js" <<'EOF' function Game() { this.config = Game.config; this.speed = this.config.START; } Game.config = { START: 6, ACCEL: 0.001, MAX: 13 }; Game.prototype.update = function () { if (this.speed < this.config.MAX) { this.speed += this.config.ACCEL; } }; EOF cat > "$R/class/game.js" <<'EOF' export class Game { constructor() { this.config = { START: 6, ACCEL: 0.001, MAX: 13 }; this.speed = this.config.START; } update() { if (this.speed < this.config.MAX) { this.speed += this.config.ACCEL; } } } EOF cat > "$R/module/game.js" <<'EOF' const config = { START: 6, ACCEL: 0.001, MAX: 13 }; const state = { speed: config.START }; export function update() { if (state.speed < config.MAX) { state.speed += config.ACCEL; } } EOF for v in es5 class module; do node scripts/rea.mjs analyze-javascript-application "$R/$v" \ --artifact-format directory --format json > "$R/$v.json" 2>/dev/null for seed in '{"kind":"property","name":"ACCEL"}' \ '{"kind":"property","name":"speed"}' \ '{"kind":"literal","value":0.001}'; do jq -c --argjson s "$seed" \ '{application: ., query: {seed: $s, direction: "forward-influence"}}' \ "$R/$v.json" > "$R/query.json" printf '%-7s %-36s ' "$v" "$seed" node scripts/rea.mjs trace-javascript-semantics "$R/query.json" --format json | jq -c '.normalized_result | {status, seeds: .summary.total_seed_matches, relations: .summary.traversed_relations, unknowns: .summary.relevant_unknowns}' done printf '%-7s unresolved accesses in graph: ' "$v" jq -c '[.normalized_result.semantic_graph.nodes[] | select(.kind == "expression" and (.properties.property_path | type) == "array") | .label] | unique' "$R/$v.json" done ``` Output on `main` at `2451e187b`: ```text es5 {"kind":"property","name":"ACCEL"} {"status":"partial","seeds":0,"relations":0,"unknowns":0} es5 {"kind":"property","name":"speed"} {"status":"partial","seeds":0,"relations":0,"unknowns":0} es5 {"kind":"literal","value":0.001} {"status":"partial","seeds":0,"relations":0,"unknowns":0} es5 unresolved accesses in graph: ["read:/config","read:/config/ACCEL","read:/config/MAX","read:/config/START","read:/speed","write:/config","write:/prototype/update","write:/speed"] class {"kind":"property","name":"ACCEL"} {"status":"partial","seeds":0,"relations":0,"unknowns":0} class {"kind":"property","name":"speed"} {"status":"partial","seeds":0,"relations":0,"unknowns":0} class {"kind":"literal","value":0.001} {"status":"partial","seeds":0,"relations":0,"unknowns":0} class unresolved accesses in graph: ["read:/config/ACCEL","read:/config/MAX","read:/config/START","read:/speed","write:/config","write:/speed"] module {"kind":"property","name":"ACCEL"} {"status":"found","seeds":1,"relations":1,"unknowns":0} module {"kind":"property","name":"speed"} {"status":"ambiguous","seeds":1,"relations":0,"unknowns":2} module {"kind":"literal","value":0.001} {"status":"found","seeds":1,"relations":2,"unknowns":0} module unresolved accesses in graph: ["read:/ACCEL","read:/MAX","read:/START","read:/speed","write:/speed"] ``` `module` is a control: the same rule with module-level objects works. The `literal` rows are a separate producer gap (see Additional context); this report is about the `property` rows. ## Expected behavior The tool contract says "Dynamic or unsupported semantics remain explicit unknowns", and [AGENTS.md](https://github.com/morluto/rea/blob/2451e187beab43a811bb6149dfd3af3ad8178327/AGENTS.md?plain=1#L21) says "Unresolved identity or applicability must remain unknown rather than be treated as absent". The docs say the trace "returns every matching seed". Proposed: a `property` seed also matches access nodes that name the property but have no property slot, so their existing unknowns appear in the result. For `es5` and `class` that includes `read:/config/ACCEL` and `write:/speed`. Results for properties that already have slots stay the same. This mirrors #4213, where endpoint seeds were extended to the response carrier of the same endpoint. ## Impact and scope - An agent asking "where is X updated?" gets an empty result for code that reaches its state through `this`, reads that as "not present", and falls back to raw source. No incorrect fact is emitted; matching unknowns are left out. - Tested: ES5 constructor/prototype and ES2015 `class` fail; module-level objects work. Object-literal methods and `.call`/`.bind` are untested. - Frequency on real-world code: NOT_ENOUGH_INFO beyond the one public target above. ## REA version 6.7.0 at `main` `2451e187beab43a811bb6149dfd3af3ad8178327` ## Runtime and platform Node v24.22.0; macOS 26.5.1 arm64 ## Target and artifact context Directory with one inert JavaScript file (reduced fixture above). ## Interface CLI (reduced reproduction); first observed through MCP. ## Earliest failure stage Transport or result projection: the graph records the accesses and their unknowns; the trace's seed resolution leaves them out. ## Relevant output or evidence Commands and output: see Reproduction. Source, read rather than executed: - [`javascriptSemanticQueryIdentity.ts:85-86`](https://github.com/morluto/rea/blob/2451e187beab43a811bb6149dfd3af3ad8178327/src/domain/javascript/javascriptSemanticQueryIdentity.ts#L85-L86): `property` seeds match only `property-slot` nodes. - [`JavaScriptSemanticGraphObjectProjection.ts:62-85`](https://github.com/morluto/rea/blob/2451e187beab43a811bb6149dfd3af3ad8178327/src/application/javascript/JavaScriptSemanticGraphObjectProjection.ts#L62-L85): with no receiver binding, no slot is created and the unknown is attached to the access node (`:43`). - [`javascriptSemanticQuery.ts:241-255`](https://github.com/morluto/rea/blob/2451e187beab43a811bb6149dfd3af3ad8178327/src/domain/javascript/javascriptSemanticQuery.ts#L241-L255): only unknowns on traversed nodes are reported, so zero seeds means zero node-bound unknowns. ## Typed outcome or recovery expected The unresolved access nodes as seeds, with their unknowns, so the caller can inspect those locations instead of concluding absence. ## Analysis or suspected cause Source-derived: the graph owner already records these accesses with `property_path`, but seed resolution only consults `property-slot` nodes. Hypothesis: the fix belongs in seed resolution, as in #4213. ## Additional context Related gaps found while investigating, each probably its own feature request: 1. `this` receivers in constructors, prototype methods and class methods aren't resolved (related: #4437 for class-instance method calls). 2. An object literal assigned onto a function (`Game.config = {…}`) isn't modelled as a container, so its values get no literal nodes; hence the `literal` rows above. 3. Compound assignment isn't followed ([`javascriptSymbolicBindings.ts:620`](https://github.com/morluto/rea/blob/2451e187beab43a811bb6149dfd3af3ad8178327/src/domain/javascript/javascriptSymbolicBindings.ts#L620)), so even in `module`, `ACCEL` traces to `read:/ACCEL` and stops before `write:/speed`.
Author
Owner

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

确认这个 bug 复现路径很清晰:seed 匹配 0 且 unknowns 0 会把"接收者未解析"和"属性从未出现"混为一谈,审计时极易误判成"代码里根本没有这个属性"。同意 #6190 把 slotless accesses 纳入 trace 的方向——修复后用户至少能看到相关 unknown,知道图里存在这些访问只是没被解析。建议验收时顺带断言 relevant_unknowns 数量与图里的 access 节点一致,避免只修了 matches 没修 unknowns;同时这个修复是 #6191 的上游,先让访问可见、再解析接收者,链路顺序是对的。

<!-- gh-comment-id:6330346187 --> @XinwangTao commented on GitHub (Oct 22, 2026): 确认这个 bug 复现路径很清晰:seed 匹配 0 且 unknowns 0 会把"接收者未解析"和"属性从未出现"混为一谈,审计时极易误判成"代码里根本没有这个属性"。同意 #6190 把 slotless accesses 纳入 trace 的方向——修复后用户至少能看到相关 unknown,知道图里存在这些访问只是没被解析。建议验收时顺带断言 relevant_unknowns 数量与图里的 access 节点一致,避免只修了 matches 没修 unknowns;同时这个修复是 #6191 的上游,先让访问可见、再解析接收者,链路顺序是对的。
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#3770
No description provided.