[GH-ISSUE #3360] [Bug] Unrelated this receivers share one resolved event identity #1227

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

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

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

Static application analysis through analyze-javascript-application combines same-named events on this in unrelated classes into one event node. A listener registration in Producer.run and a dispatch in Unrelated.run both attach to that shared node with resolved identity, creating a semantic path between different emitters.

The CLI exits with code 0. The event inventory contains one ready event with emitter_key: "this" for both classes, plus the stable identifier event. The class event has a resolved registers-listener relation to the line-3 listener and a resolved dispatches-candidate relation from the function on line 6. No event-family unknown marks the receiver ambiguity. The control shares its own binding-scoped event as expected.

Reproduction

  1. Start from a clean source checkout and prepare the runtime at the tested revision.
git clone https://github.com/morluto/rea.git
cd rea
git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063
npm ci
npm run build:cached
  1. Run the following from the repository root. It creates a disposable input and isolated configuration home, invokes the public CLI, and prints the affected facts from its complete Evidence. The analyzer reads the target as text and does not execute its calls.
work=$(mktemp -d)
mkdir "$work/app" "$work/home"
cat > "$work/app/main.mjs" <<'JS'
import { EventEmitter } from 'node:events';
class Producer extends EventEmitter {
  run() { this.on('ready', () => {}); }
}
class Unrelated extends EventEmitter {
  run() { this.emit('ready'); }
}
new Producer().run();
new Unrelated().run();
const stable = new EventEmitter();
stable.on('ready', () => {});
stable.emit('ready');
JS
python3 - "$work" <<'PY'
import json, os, pathlib, subprocess, sys
work = pathlib.Path(sys.argv[1])
child_environment = dict(os.environ, HOME=str(work / 'home'))
result = subprocess.run(
    ['node', 'scripts/rea.mjs', 'analyze-javascript-application',
     str(work / 'app'), '--format', 'json'],
    env=child_environment, capture_output=True, text=True, check=True)
evidence = json.loads(result.stdout)
(work / 'evidence.json').write_text(result.stdout)
graph = evidence['normalized_result']['semantic_graph']
print('exit:', result.returncode)
nodes = {node['node_id']: node for node in graph['nodes']}
for node in sorted(graph['nodes'], key=lambda n: ((n['identity']['source_range'] or {}).get('start', {}).get('line', 0), n['node_id'])):
    if node['kind'] in ['event']:
        print('node', node['kind'], node['identity']['source_range'], node['properties'])
for relation in graph['relations']:
    if relation['relation'] in ['registers-listener', 'dispatches-candidate']:
        source = nodes[relation['source_node_id']]
        target = nodes[relation['target_node_id']]
        print('relation', relation['relation'], relation['resolution'], source['kind'], source['node_id'], source['identity']['source_range'], '=>', target['kind'], target['node_id'], target['identity']['source_range'])
print('effect_unknowns', [(u['family'], u['detail']) for u in graph['unknowns'] if u['family'] in ['timer', 'child-process', 'event', 'request', 'boundary']])
PY
  1. Inspect evidence.json under the generated work directory, specifically normalized_result.semantic_graph.nodes for events and relations for registration/dispatch. Observe the shared resolved class event and the separate stable binding control.

Expected behavior

The two unrelated receiver contexts must not share a proven exact emitter identity. Keep their events separate, or retain explicit receiver ambiguity and candidate relationships when this cannot be resolved. The stable identifier control should still share one event across its registration and dispatch.

docs/javascript-application-workflows.md:136-143 specifies emitter-scoped event identity and one event per exact emitter/name pair. docs/javascript-application-workflows.md:96-106 requires unresolved facts to remain unknown and defines resolved target identity. A resolved static relationship does not establish that a path executed.

Impact and scope

Impact level is P2, supported by the reproduced false identity join. Same-named events in unrelated callable receiver contexts acquire false identity joins, making event traces connect registrations and dispatches that do not share a proven receiver. The observed failure concerns static semantic Evidence for the inline JavaScript fixture.

Workaround status is unverified. The stable identifier control was verified, but replacing this in an arbitrary supplied application changes the source artifact and its Evidence. No option preserving the original artifact while correcting this identity join was verified.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0, macOS 27.0.1. Architecture was not recorded in the reproduction evidence. Dependencies were installed and the runtime was built.

Target and artifact context

A local directory containing the inline main.mjs JavaScript fixture, analyzed as inert source text. The resulting artifact is application Evidence containing normalized_result.semantic_graph. No native engine, browser, or external service is needed.

Interface

CLI

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

The reproduced Evidence contains the following observations, summarized without generated node IDs.

The CLI exits with code 0.
The event inventory contains one ready event with emitter_key: "this" for both classes, plus the stable identifier event.
The class event has a resolved registers-listener relation to the line-3 listener and a resolved dispatches-candidate relation from the function on line 6.
No event-family unknown marks the receiver ambiguity.
The stable identifier control shares its own binding-scoped event as expected.

Typed outcome or recovery expected

Rerun the fixture. The Producer listener and Unrelated dispatch must not meet through one resolved exact-emitter event. Preserve explicit ambiguity where receiver identity is unavailable. The two stable-identifier operations must still share one correctly scoped event, so disabling event sharing everywhere is not a passing result.

Analysis or suspected cause

Source-derived reasoning: src/domain/javascript/javascriptSemanticAsyncEffects.ts:201-202 assigns all this receivers the same complete emitter key. src/application/javascript/JavaScriptSemanticGraphAsyncProjection.ts:20-30 uses that key to share event nodes. Together these operations explain how same-named events from unrelated receiver contexts enter one resolved event identity.

The observed failure was reproduced through the public CLI. The source-derived explanation is separate from that execution evidence.

Additional context

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3360 ### 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 Static application analysis through `analyze-javascript-application` combines same-named events on `this` in unrelated classes into one event node. A listener registration in `Producer.run` and a dispatch in `Unrelated.run` both attach to that shared node with resolved identity, creating a semantic path between different emitters. The CLI exits with code 0. The event inventory contains one `ready` event with `emitter_key: "this"` for both classes, plus the stable identifier event. The class event has a resolved `registers-listener` relation to the line-3 listener and a resolved `dispatches-candidate` relation from the function on line 6. No event-family unknown marks the receiver ambiguity. The control shares its own binding-scoped event as expected. ### Reproduction 1. Start from a clean source checkout and prepare the runtime at the tested revision. ```bash git clone https://github.com/morluto/rea.git cd rea git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063 npm ci npm run build:cached ``` 2. Run the following from the repository root. It creates a disposable input and isolated configuration home, invokes the public CLI, and prints the affected facts from its complete Evidence. The analyzer reads the target as text and does not execute its calls. ```bash work=$(mktemp -d) mkdir "$work/app" "$work/home" cat > "$work/app/main.mjs" <<'JS' import { EventEmitter } from 'node:events'; class Producer extends EventEmitter { run() { this.on('ready', () => {}); } } class Unrelated extends EventEmitter { run() { this.emit('ready'); } } new Producer().run(); new Unrelated().run(); const stable = new EventEmitter(); stable.on('ready', () => {}); stable.emit('ready'); JS python3 - "$work" <<'PY' import json, os, pathlib, subprocess, sys work = pathlib.Path(sys.argv[1]) child_environment = dict(os.environ, HOME=str(work / 'home')) result = subprocess.run( ['node', 'scripts/rea.mjs', 'analyze-javascript-application', str(work / 'app'), '--format', 'json'], env=child_environment, capture_output=True, text=True, check=True) evidence = json.loads(result.stdout) (work / 'evidence.json').write_text(result.stdout) graph = evidence['normalized_result']['semantic_graph'] print('exit:', result.returncode) nodes = {node['node_id']: node for node in graph['nodes']} for node in sorted(graph['nodes'], key=lambda n: ((n['identity']['source_range'] or {}).get('start', {}).get('line', 0), n['node_id'])): if node['kind'] in ['event']: print('node', node['kind'], node['identity']['source_range'], node['properties']) for relation in graph['relations']: if relation['relation'] in ['registers-listener', 'dispatches-candidate']: source = nodes[relation['source_node_id']] target = nodes[relation['target_node_id']] print('relation', relation['relation'], relation['resolution'], source['kind'], source['node_id'], source['identity']['source_range'], '=>', target['kind'], target['node_id'], target['identity']['source_range']) print('effect_unknowns', [(u['family'], u['detail']) for u in graph['unknowns'] if u['family'] in ['timer', 'child-process', 'event', 'request', 'boundary']]) PY ``` 3. Inspect `evidence.json` under the generated work directory, specifically `normalized_result.semantic_graph.nodes` for events and `relations` for registration/dispatch. Observe the shared resolved class event and the separate stable binding control. ### Expected behavior The two unrelated receiver contexts must not share a proven exact emitter identity. Keep their events separate, or retain explicit receiver ambiguity and candidate relationships when `this` cannot be resolved. The stable identifier control should still share one event across its registration and dispatch. `docs/javascript-application-workflows.md:136-143` specifies emitter-scoped event identity and one event per exact emitter/name pair. `docs/javascript-application-workflows.md:96-106` requires unresolved facts to remain unknown and defines resolved target identity. A resolved static relationship does not establish that a path executed. ### Impact and scope Impact level is P2, supported by the reproduced false identity join. Same-named events in unrelated callable receiver contexts acquire false identity joins, making event traces connect registrations and dispatches that do not share a proven receiver. The observed failure concerns static semantic Evidence for the inline JavaScript fixture. Workaround status is unverified. The stable identifier control was verified, but replacing `this` in an arbitrary supplied application changes the source artifact and its Evidence. No option preserving the original artifact while correcting this identity join was verified. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0, macOS 27.0.1. Architecture was not recorded in the reproduction evidence. Dependencies were installed and the runtime was built. ### Target and artifact context A local directory containing the inline `main.mjs` JavaScript fixture, analyzed as inert source text. The resulting artifact is application Evidence containing `normalized_result.semantic_graph`. No native engine, browser, or external service is needed. ### Interface CLI ### Earliest failure stage Artifact or evidence registration ### Relevant output or evidence The reproduced Evidence contains the following observations, summarized without generated node IDs. ```text The CLI exits with code 0. The event inventory contains one ready event with emitter_key: "this" for both classes, plus the stable identifier event. The class event has a resolved registers-listener relation to the line-3 listener and a resolved dispatches-candidate relation from the function on line 6. No event-family unknown marks the receiver ambiguity. The stable identifier control shares its own binding-scoped event as expected. ``` ### Typed outcome or recovery expected Rerun the fixture. The Producer listener and Unrelated dispatch must not meet through one resolved exact-emitter event. Preserve explicit ambiguity where receiver identity is unavailable. The two stable-identifier operations must still share one correctly scoped event, so disabling event sharing everywhere is not a passing result. ### Analysis or suspected cause Source-derived reasoning: `src/domain/javascript/javascriptSemanticAsyncEffects.ts:201-202` assigns all `this` receivers the same complete emitter key. `src/application/javascript/JavaScriptSemanticGraphAsyncProjection.ts:20-30` uses that key to share event nodes. Together these operations explain how same-named events from unrelated receiver contexts enter one resolved event identity. The observed failure was reproduced through the public CLI. The source-derived explanation is separate from that execution evidence. ### Additional context - https://github.com/morluto/rea/issues/2574 is a completed report about dispatch data being labeled as a listener in semantic IR; this report concerns event-node identity joining unrelated receivers, even with no dispatch data argument. - https://github.com/morluto/rea/issues/1842 is a completed report about Electron constructor identity through an interop helper; this report concerns EventEmitter receiver identity across unrelated classes. - https://github.com/morluto/rea/issues/404 is the completed semantic-tracing feature request; this report concerns a reproduced correctness failure in emitter-scoped identity.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

this emitters are now keyed by nearest class/non-arrow-function lexical owner range, with complete:false, so unrelated methods do not share the literal key this and their operations remain partial. The graph projection turns partial operations into candidate relations and explicit event-family ambiguous-target unknowns. Stable identifier emitter keys remain binding-scoped and complete when not replaced.

Implementation: javascriptSemanticAsyncEffects.ts, javascriptSemanticAsyncEffects.ts.

The relevant regression checks in javascriptSemanticAnalysis.identity_boundaries.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380711400 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. this emitters are now keyed by nearest class/non-arrow-function lexical owner range, with complete:false, so unrelated methods do not share the literal key this and their operations remain partial. The graph projection turns partial operations into candidate relations and explicit event-family ambiguous-target unknowns. Stable identifier emitter keys remain binding-scoped and complete when not replaced. Implementation: [javascriptSemanticAsyncEffects.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptSemanticAsyncEffects.ts#L224), [javascriptSemanticAsyncEffects.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptSemanticAsyncEffects.ts#L191). The relevant regression checks in [javascriptSemanticAnalysis.identity_boundaries.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptSemanticAnalysis.identity_boundaries.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#1227
No description provided.