[GH-ISSUE #7418] [Bug] analyze-javascript-application retains a stale literal after a member assignment inside with #4424

Closed
opened 2026-10-31 00:49:40 +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/7418

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

analyze-javascript-application retains the exported read function's return as literal "initial" after an unconditional shared.mode = "updated" inside with (unrelated). Running the same self-contained CommonJS fixture in Node returns "updated". The parent analysis records dynamic-scope uncertainty and marks return-shape coverage partial, but its retained return field and property slot still claim a literal. A forward trace-javascript-semantics query seeded by "initial" reaches the later property read and return through three resolved relations with zero relevant unknowns. Both the stale value and this trace behavior reproduced in three independent CLI analysis-and-trace attempts.

Reproduction

  1. Use the prepared baseline checkout, with its installed dependencies and built runtime, as rea in an otherwise empty working directory. Run the following from that directory with Node 24 on PATH. The reproduction used the supplied checkout through a read-only directory link; no checkout files need changing.
mkdir -p app home cache
cat > app/package.json <<'JSON'
{"name":"with-write-probe","version":"1.0.0","main":"main.js"}
JSON
cat > app/main.js <<'JS'
const shared = { mode: "initial", keep: 1 };
const unrelated = {};
with (unrelated) { shared.mode = "updated"; }
module.exports.read = function read() { return shared.mode; };
module.exports.keep = function keep() { return shared.keep; };
JS
node -e 'const m=require("./app/main.js"); console.log("oracle",m.read(),m.keep());'

Observed oracle output:

oracle updated 1
  1. Analyze the inert source and trace the initializer literal using the complete returned Evidence. REA does not execute the fixture.
env HOME="$PWD/home" XDG_CACHE_HOME="$PWD/cache" node rea/scripts/rea.mjs analyze-javascript-application ./app --json > replay-analysis.json 2> replay-analysis.stderr
python3 - <<'PYQUERY'
import json
json.dump({'application': json.load(open('replay-analysis.json')), 'query': {'seed': {'kind':'literal','value':'initial'}, 'direction':'forward-influence'}},open('replay-query.json','w'))
PYQUERY
env HOME="$PWD/home" XDG_CACHE_HOME="$PWD/cache" node rea/scripts/rea.mjs trace-javascript-semantics ./replay-query.json --json > replay-trace.json 2> replay-trace.stderr
  1. Print the retained return field and relevant trace facts.
python3 - <<'PYRESULT'
import json
for n in json.load(open('replay-analysis.json'))['normalized_result']['graph']['nodes']:
 for o in n.get('observations',[]):
  p=o.get('properties',{})
  if p.get('semantic_role')=='export-return-shapes' and p['exported_name']=='read':
   f=p['static_return_shapes'][0]['fields'][0]
   print('read', f['state'],f['value'],'coverage',p['return_shape_coverage']['status'])
r=json.load(open('replay-trace.json'))['normalized_result'];s=r['summary']
print('trace',r['status'],'seeds',s['total_seed_matches'],'nodes',s['traversed_nodes'],'relations',s['traversed_relations'],'unknowns',s['relevant_unknowns'])
for n in sorted(r['nodes'],key=lambda n:(n['kind'],n['label'])): print('node',n['kind'],n['label'])
for e in sorted(r['relations'],key=lambda e:e['relation']):print('relation',e['relation'],e['resolution'])
PYRESULT

Observed output on the final replay, matching the preceding two attempts:

read literal initial coverage partial
trace found seeds 1 nodes 4 relations 3 unknowns 0
node expression read:/mode
node literal "initial"
node property-slot mode
node return-site return
relation aliases resolved
relation defines resolved
relation reads-property resolved

Expected behavior

Under “Semantic relation tracing,” docs/javascript-application-workflows.md:236-243 says “Mutation tracking covers explicit member assignments, updates, deletion, and loop assignment targets,” and lines 259-262 say dynamic names and unsupported API shapes “stay explicit unknowns.” The guide does not document with statements as excluded inputs. The analyzer already recognizes this statement as a dynamic binding environment. A write whose receiver may be the outer shared object must prevent the later read from retaining the pre-write literal as its known value. Conservative unknown state is sufficient; interpreting with precisely or recovering the exact replacement string is unnecessary. The corresponding value trace should preserve the relevant uncertainty instead of presenting only the old literal path.

Impact and scope

Observed on this valid non-strict CommonJS fixture in three out of three independent CLI runs: consumers receive an incorrect literal return field and an initializer-seeded trace without the write's relevant unknowns. The parent analysis does disclose partial coverage and dynamic-scope uncertainty elsewhere. In one separately analyzed control, replacing only with (unrelated) { shared.mode = "updated"; } with shared.mode = "updated"; makes the return unknown, with reason Own property mode is unknown-coverage. The unrelated keep return remains literal 1 in both fixtures. Production prevalence, other mutation forms, MCP behavior and other platforms are unknown. No input-preserving REA workaround was verified.

REA version

6.8.0 (source commit a905a71e31)

Runtime and platform

Node 24.22.1; macOS 27.0.1 arm64.

Target and artifact context

Self-authored directory application containing a package manifest and non-strict CommonJS source; retained application Evidence and its semantic companion graph. No native provider or external target is involved.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed through the public source CLI: the export-return-shapes observation for read retains value_status: "literal" and a field with state: "literal", value: "initial", reason: null; return_shape_coverage.status is partial and projection_complete is true. The semantic mode slot has presence: "present" and value_status: "literal". The parent graph separately contains write:/mode with property_slot_resolution: "unresolved", an object-flow / ambiguous-target unknown on that write, and a module-bound data-flow / dynamic-scope unknown. None of these unknowns appears in the four-node literal trace printed above. The parent is therefore not globally free of uncertainty; the defect is retaining the stale local value and losing that uncertainty from its downstream trace.

Source-only (not executed as isolated functions): src/domain/javascript/javascriptSemanticState.ts:328 retains possible outer ownership as uncertain-binding across a with environment, while its certain-only resolver at line 272 returns no binding for that outcome. src/domain/javascript/javascriptSemanticMemberMutations.ts:545 uses that resolver, and line 844 handles the missing binding as an ambient-effect path. src/domain/javascript/javascriptSemanticCalls.ts:390 records the dynamic-scope frontier; src/application/javascript/JavaScriptSemanticGraphFlowProjection.ts:319 projects it onto the module or enclosing callable.

The documented resolved relation status identifies a unique admitted static target; it does not prove that a path executed. The Node oracle and the explicit stale literal field establish the value error independently of that relation status.

Typed outcome or recovery expected

Retain an unknown value for the potentially written local property and its later return, or a justified post-write value. Preserve a relevant unknown or candidate path when tracing through the affected read. Keep the unrelated property's known value; no new execution capability or particular graph topology is required.

Analysis or suspected cause

Source-only (not executed as isolated functions): the lookup owner retains a possible lexical binding, but the member-mutation consumer asks only for a certain binding. Discarding the possible owner prevents the write from invalidating the outer object's member. Projecting a dynamic-scope frontier at the module does not repair the local value state, and that frontier is outside the initializer's reachable trace. These inspected code paths support the diagnosis; the public CLI reproduction establishes the defect without requiring that repair design.

Additional context

The open and closed issue index and live issue/PR searches found related but distinct work. Closed #6189 concerns property-seed selection of slotless accesses; this reproduction already has a literal seed and local property slot. Closed #2567 concerns mutation-target read/write classification; this target is already labeled as a write. Closed #2655 and #1665 concern getter-result mutations, and closed #4875 concerns direct eval effects. Closed #6193 concerns assignment dependency edges. Their restoration criteria do not establish invalidation for this explicit member assignment inside with. The baseline includes the merged dynamic-scope analysis from PR #538, and its frontier is present in the observed parent graph. Open PR #6190 changes property-seed matching rather than this value-invalidation path. npm reports latest rea-agents version 6.8.0, matching the baseline's version; the published package itself was not executed. No last-known-good revision was established.

Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7418 ### 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 `analyze-javascript-application` retains the exported `read` function's return as literal `"initial"` after an unconditional `shared.mode = "updated"` inside `with (unrelated)`. Running the same self-contained CommonJS fixture in Node returns `"updated"`. The parent analysis records dynamic-scope uncertainty and marks return-shape coverage `partial`, but its retained return field and property slot still claim a literal. A forward `trace-javascript-semantics` query seeded by `"initial"` reaches the later property read and return through three resolved relations with zero relevant unknowns. Both the stale value and this trace behavior reproduced in three independent CLI analysis-and-trace attempts. ### Reproduction 1. Use the prepared baseline checkout, with its installed dependencies and built runtime, as `rea` in an otherwise empty working directory. Run the following from that directory with Node 24 on PATH. The reproduction used the supplied checkout through a read-only directory link; no checkout files need changing. ```sh mkdir -p app home cache cat > app/package.json <<'JSON' {"name":"with-write-probe","version":"1.0.0","main":"main.js"} JSON cat > app/main.js <<'JS' const shared = { mode: "initial", keep: 1 }; const unrelated = {}; with (unrelated) { shared.mode = "updated"; } module.exports.read = function read() { return shared.mode; }; module.exports.keep = function keep() { return shared.keep; }; JS node -e 'const m=require("./app/main.js"); console.log("oracle",m.read(),m.keep());' ``` Observed oracle output: ```text oracle updated 1 ``` 2. Analyze the inert source and trace the initializer literal using the complete returned Evidence. REA does not execute the fixture. ```sh env HOME="$PWD/home" XDG_CACHE_HOME="$PWD/cache" node rea/scripts/rea.mjs analyze-javascript-application ./app --json > replay-analysis.json 2> replay-analysis.stderr python3 - <<'PYQUERY' import json json.dump({'application': json.load(open('replay-analysis.json')), 'query': {'seed': {'kind':'literal','value':'initial'}, 'direction':'forward-influence'}},open('replay-query.json','w')) PYQUERY env HOME="$PWD/home" XDG_CACHE_HOME="$PWD/cache" node rea/scripts/rea.mjs trace-javascript-semantics ./replay-query.json --json > replay-trace.json 2> replay-trace.stderr ``` 3. Print the retained return field and relevant trace facts. ```sh python3 - <<'PYRESULT' import json for n in json.load(open('replay-analysis.json'))['normalized_result']['graph']['nodes']: for o in n.get('observations',[]): p=o.get('properties',{}) if p.get('semantic_role')=='export-return-shapes' and p['exported_name']=='read': f=p['static_return_shapes'][0]['fields'][0] print('read', f['state'],f['value'],'coverage',p['return_shape_coverage']['status']) r=json.load(open('replay-trace.json'))['normalized_result'];s=r['summary'] print('trace',r['status'],'seeds',s['total_seed_matches'],'nodes',s['traversed_nodes'],'relations',s['traversed_relations'],'unknowns',s['relevant_unknowns']) for n in sorted(r['nodes'],key=lambda n:(n['kind'],n['label'])): print('node',n['kind'],n['label']) for e in sorted(r['relations'],key=lambda e:e['relation']):print('relation',e['relation'],e['resolution']) PYRESULT ``` Observed output on the final replay, matching the preceding two attempts: ```text read literal initial coverage partial trace found seeds 1 nodes 4 relations 3 unknowns 0 node expression read:/mode node literal "initial" node property-slot mode node return-site return relation aliases resolved relation defines resolved relation reads-property resolved ``` ### Expected behavior Under “Semantic relation tracing,” `docs/javascript-application-workflows.md:236-243` says “Mutation tracking covers explicit member assignments, updates, deletion, and loop assignment targets,” and lines 259-262 say dynamic names and unsupported API shapes “stay explicit unknowns.” The guide does not document `with` statements as excluded inputs. The analyzer already recognizes this statement as a dynamic binding environment. A write whose receiver may be the outer `shared` object must prevent the later read from retaining the pre-write literal as its known value. Conservative unknown state is sufficient; interpreting `with` precisely or recovering the exact replacement string is unnecessary. The corresponding value trace should preserve the relevant uncertainty instead of presenting only the old literal path. ### Impact and scope Observed on this valid non-strict CommonJS fixture in three out of three independent CLI runs: consumers receive an incorrect literal return field and an initializer-seeded trace without the write's relevant unknowns. The parent analysis does disclose partial coverage and dynamic-scope uncertainty elsewhere. In one separately analyzed control, replacing only `with (unrelated) { shared.mode = "updated"; }` with `shared.mode = "updated";` makes the return `unknown`, with reason `Own property mode is unknown-coverage.` The unrelated `keep` return remains literal `1` in both fixtures. Production prevalence, other mutation forms, MCP behavior and other platforms are unknown. No input-preserving REA workaround was verified. ### REA version 6.8.0 (source commit a905a71e31f71457cb21fd056b234f872126974e) ### Runtime and platform Node 24.22.1; macOS 27.0.1 arm64. ### Target and artifact context Self-authored directory application containing a package manifest and non-strict CommonJS source; retained application Evidence and its semantic companion graph. No native provider or external target is involved. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Observed through the public source CLI: the `export-return-shapes` observation for `read` retains `value_status: "literal"` and a field with `state: "literal", value: "initial", reason: null`; `return_shape_coverage.status` is `partial` and `projection_complete` is `true`. The semantic `mode` slot has `presence: "present"` and `value_status: "literal"`. The parent graph separately contains `write:/mode` with `property_slot_resolution: "unresolved"`, an `object-flow` / `ambiguous-target` unknown on that write, and a module-bound `data-flow` / `dynamic-scope` unknown. None of these unknowns appears in the four-node literal trace printed above. The parent is therefore not globally free of uncertainty; the defect is retaining the stale local value and losing that uncertainty from its downstream trace. Source-only (not executed as isolated functions): `src/domain/javascript/javascriptSemanticState.ts:328` retains possible outer ownership as `uncertain-binding` across a `with` environment, while its certain-only resolver at line 272 returns no binding for that outcome. `src/domain/javascript/javascriptSemanticMemberMutations.ts:545` uses that resolver, and line 844 handles the missing binding as an ambient-effect path. `src/domain/javascript/javascriptSemanticCalls.ts:390` records the dynamic-scope frontier; `src/application/javascript/JavaScriptSemanticGraphFlowProjection.ts:319` projects it onto the module or enclosing callable. The documented `resolved` relation status identifies a unique admitted static target; it does not prove that a path executed. The Node oracle and the explicit stale `literal` field establish the value error independently of that relation status. ### Typed outcome or recovery expected Retain an unknown value for the potentially written local property and its later return, or a justified post-write value. Preserve a relevant unknown or candidate path when tracing through the affected read. Keep the unrelated property's known value; no new execution capability or particular graph topology is required. ### Analysis or suspected cause Source-only (not executed as isolated functions): the lookup owner retains a possible lexical binding, but the member-mutation consumer asks only for a certain binding. Discarding the possible owner prevents the write from invalidating the outer object's member. Projecting a dynamic-scope frontier at the module does not repair the local value state, and that frontier is outside the initializer's reachable trace. These inspected code paths support the diagnosis; the public CLI reproduction establishes the defect without requiring that repair design. ### Additional context The open and closed issue index and live issue/PR searches found related but distinct work. Closed #6189 concerns property-seed selection of slotless accesses; this reproduction already has a literal seed and local property slot. Closed #2567 concerns mutation-target read/write classification; this target is already labeled as a write. Closed #2655 and #1665 concern getter-result mutations, and closed #4875 concerns direct eval effects. Closed #6193 concerns assignment dependency edges. Their restoration criteria do not establish invalidation for this explicit member assignment inside `with`. The baseline includes the merged dynamic-scope analysis from PR #538, and its frontier is present in the observed parent graph. Open PR #6190 changes property-seed matching rather than this value-invalidation path. npm reports latest `rea-agents` version 6.8.0, matching the baseline's version; the published package itself was not executed. No last-known-good revision was established.
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#4424
No description provided.