[GH-ISSUE #3207] [Bug] Local JavaScript exports appear as writes in semantic traces #1145

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

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

Analyzing an ES module containing const answer = 42; export { answer }; returns a resolved writes relation from the export specifier to the existing constant binding. A writes-only backward semantic trace includes that export as write provenance, although exporting the binding does not assign a value. The independent verifier reproduced the same false relation for export { answer as result };. Both public CLI commands exited 0.

The relation retains inferred static Evidence and the trace reports partial coverage. The observed defect is an incorrect static write path. No runtime execution or actual mutation was established.

Reproduction

  1. Clone REA, select the reproduced baseline, and prepare the runtime:
git clone https://github.com/morluto/rea.git rea-export-repro
cd rea-export-repro
git checkout 58300885207133422af235e0f2b10658cf958b03
npm ci
npm run build:cached

The independent verifier used an already prepared baseline runtime. The dependency-install and build commands above were not rerun during that verification.

  1. From the checkout root, create a local ES-module fixture and isolated CLI directories:
mkdir -p repro-export/app repro-export/home/config repro-export/home/cache repro-export/home/data
cat > repro-export/app/package.json <<'JSON'
{ "type": "module" }
JSON
cat > repro-export/app/main.js <<'JS'
const answer = 42;
export { answer };
JS
  1. Analyze the fixture with the supported CLI command:
env HOME="$PWD/repro-export/home" XDG_CONFIG_HOME="$PWD/repro-export/home/config" XDG_CACHE_HOME="$PWD/repro-export/home/cache" XDG_DATA_HOME="$PWD/repro-export/home/data" node scripts/rea.mjs analyze-javascript-application ./repro-export/app --format json > repro-export/analysis.json
  1. Extract the actual binding node ID, print the write relation's identifying fields, and construct an authenticated trace input without editing the analysis Evidence:
python3 - <<'PY'
import json
from pathlib import Path
root = Path('repro-export')
application = json.loads((root / 'analysis.json').read_text())
graph = application['normalized_result']['semantic_graph']
nodes = {node['node_id']: node for node in graph['nodes']}
binding = next(node for node in graph['nodes']
               if node['kind'] == 'binding' and node['label'] == 'answer')
for relation in graph['relations']:
    if relation['relation'] == 'writes':
        print(json.dumps({
            'relation': relation['relation'],
            'resolution': relation['resolution'],
            'source_role': nodes[relation['source_node_id']]['identity']['role_key'],
            'target_kind': nodes[relation['target_node_id']]['kind'],
            'target_label': nodes[relation['target_node_id']]['label'],
            'location': relation['evidence']['location'],
        }))
query = {
    'seed': {'kind': 'semantic-node', 'node_id': binding['node_id']},
    'direction': 'backward-provenance',
    'allowed_relations': ['writes'],
}
(root / 'trace-input.json').write_text(json.dumps({
    'application': application,
    'query': query,
}))
PY
  1. Run the public trace and inspect its result:
env HOME="$PWD/repro-export/home" XDG_CONFIG_HOME="$PWD/repro-export/home/config" XDG_CACHE_HOME="$PWD/repro-export/home/cache" XDG_DATA_HOME="$PWD/repro-export/home/data" node scripts/rea.mjs trace-javascript-semantics ./repro-export/trace-input.json --format json > repro-export/trace.json
python3 - <<'PY'
import json
from pathlib import Path
result = json.loads(Path('repro-export/trace.json').read_text())['normalized_result']
print(json.dumps({
    'status': result['status'],
    'seed_matches': result['summary']['total_seed_matches'],
    'traversed_nodes': result['summary']['traversed_nodes'],
    'traversed_relations': result['summary']['traversed_relations'],
    'relations': [relation['relation'] for relation in result['relations']],
}))
PY

Expected behavior

The semantic reference contract in src/domain/javascript/javascriptSemanticIr.ts:83-89 distinguishes export from read and write. src/domain/javascript/javascriptSemanticProjection.ts:709-725 classifies the local identifier of an export specifier as export, separately from assignment and update targets. Exporting an existing binding should retain its module relationship without creating a write relation to that binding.

docs/javascript-application-workflows.md:58-85 documents authenticated semantic tracing, relation restrictions, and the reachable semantic subgraph. src/domain/javascript/javascriptSemanticGraphSchemas.ts:124,172 defines writes as a data-flow relation. A backward trace admitting only writes should retain the matching binding seed and traverse zero relations for the fixture above.

Impact and scope

Plain and aliased local export specifiers produce false write provenance in the semantic graph and public CLI trace. The verifier's declaration-only control produced zero writes and two defines. A genuine reassignment control produced one write with source role reference:0:write:answer.

This is a bounded semantic-result defect, assessed as P2. No analysis failure, data loss, runtime mutation, or security boundary crossing was observed. Returned source nodes expose the export role and source range, so callers can exclude export-sourced edges from a derived interpretation of write provenance while retaining genuine assignment edges and leaving authenticated Evidence unchanged. This workaround does not repair the underlying graph.

The shared trace service is also registered for MCP according to source inspection, but MCP transport was not separately executed. Source re-exports were not tested.

REA version

6.4.0 (main at 5830088520)

Runtime and platform

Node v26.10.0; Darwin 27.0.0 arm64.

Target and artifact context

A synthetic local ES-module directory with package.json containing { "type": "module" } and a two-line main.js. The trace input contains the returned application Evidence unchanged and the actual binding node ID. No native analysis provider is required.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

Analysis, exit 0:
{"relation":"writes","resolution":"resolved","source_role":"reference:0:export:answer","target_kind":"binding","target_label":"answer","location":{"available":true,"value":{"kind":"source-range","source":"main.js","start":{"line":2,"column":9},"end":{"line":2,"column":15}}}}

Writes-only backward trace, exit 0:
{"status":"found","seed_matches":1,"traversed_nodes":2,"traversed_relations":1,"relations":["writes"]}

Typed outcome or recovery expected

Retain the declaration's definition relationships and module export facts, with zero export-sourced writes relations. For both plain and aliased local exports, the writes-only backward trace should return the matching binding seed and zero traversed relations. A seed-only match still returns status: "found", as the declaration-only control demonstrated. Preserve the genuine write and its traceability for let answer = 0; answer = 1;.

Analysis or suspected cause

Source-derived reasoning at the reproduced baseline: src/application/javascript/JavaScriptSemanticGraphBuilder.ts:627-658 projects every bound non-read reference as writes. The source role remains export, but that reference takes the same graph branch as an assignment. The domain classification already distinguishes these roles in src/domain/javascript/javascriptSemanticProjection.ts:709-725.

The public trace command reaches traceJavaScriptSemanticsEvidenceValidated through src/cli/applicationCommands.ts:43-54. src/application/javascript/JavaScriptSemanticTraceService.ts:25-77 validates the application Evidence and queries the authenticated semantic graph. The observed trace therefore follows the incorrect relation produced by analysis. MCP registration at src/server/registerApplicationTools/traceSemantics.ts:24-70 calls the same service, based on source inspection only.

Additional context

P2 is supported by incorrect static data-flow provenance for the reproduced local-export inputs and an available derived-result workaround. The evidence does not establish a severe downstream incident or runtime mutation.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3207 ### 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 Analyzing an ES module containing `const answer = 42; export { answer };` returns a resolved `writes` relation from the export specifier to the existing constant binding. A writes-only backward semantic trace includes that export as write provenance, although exporting the binding does not assign a value. The independent verifier reproduced the same false relation for `export { answer as result };`. Both public CLI commands exited 0. The relation retains inferred static Evidence and the trace reports partial coverage. The observed defect is an incorrect static write path. No runtime execution or actual mutation was established. ### Reproduction 1. Clone REA, select the reproduced baseline, and prepare the runtime: ```sh git clone https://github.com/morluto/rea.git rea-export-repro cd rea-export-repro git checkout 58300885207133422af235e0f2b10658cf958b03 npm ci npm run build:cached ``` The independent verifier used an already prepared baseline runtime. The dependency-install and build commands above were not rerun during that verification. 2. From the checkout root, create a local ES-module fixture and isolated CLI directories: ```sh mkdir -p repro-export/app repro-export/home/config repro-export/home/cache repro-export/home/data cat > repro-export/app/package.json <<'JSON' { "type": "module" } JSON cat > repro-export/app/main.js <<'JS' const answer = 42; export { answer }; JS ``` 3. Analyze the fixture with the supported CLI command: ```sh env HOME="$PWD/repro-export/home" XDG_CONFIG_HOME="$PWD/repro-export/home/config" XDG_CACHE_HOME="$PWD/repro-export/home/cache" XDG_DATA_HOME="$PWD/repro-export/home/data" node scripts/rea.mjs analyze-javascript-application ./repro-export/app --format json > repro-export/analysis.json ``` 4. Extract the actual binding node ID, print the write relation's identifying fields, and construct an authenticated trace input without editing the analysis Evidence: ```sh python3 - <<'PY' import json from pathlib import Path root = Path('repro-export') application = json.loads((root / 'analysis.json').read_text()) graph = application['normalized_result']['semantic_graph'] nodes = {node['node_id']: node for node in graph['nodes']} binding = next(node for node in graph['nodes'] if node['kind'] == 'binding' and node['label'] == 'answer') for relation in graph['relations']: if relation['relation'] == 'writes': print(json.dumps({ 'relation': relation['relation'], 'resolution': relation['resolution'], 'source_role': nodes[relation['source_node_id']]['identity']['role_key'], 'target_kind': nodes[relation['target_node_id']]['kind'], 'target_label': nodes[relation['target_node_id']]['label'], 'location': relation['evidence']['location'], })) query = { 'seed': {'kind': 'semantic-node', 'node_id': binding['node_id']}, 'direction': 'backward-provenance', 'allowed_relations': ['writes'], } (root / 'trace-input.json').write_text(json.dumps({ 'application': application, 'query': query, })) PY ``` 5. Run the public trace and inspect its result: ```sh env HOME="$PWD/repro-export/home" XDG_CONFIG_HOME="$PWD/repro-export/home/config" XDG_CACHE_HOME="$PWD/repro-export/home/cache" XDG_DATA_HOME="$PWD/repro-export/home/data" node scripts/rea.mjs trace-javascript-semantics ./repro-export/trace-input.json --format json > repro-export/trace.json python3 - <<'PY' import json from pathlib import Path result = json.loads(Path('repro-export/trace.json').read_text())['normalized_result'] print(json.dumps({ 'status': result['status'], 'seed_matches': result['summary']['total_seed_matches'], 'traversed_nodes': result['summary']['traversed_nodes'], 'traversed_relations': result['summary']['traversed_relations'], 'relations': [relation['relation'] for relation in result['relations']], })) PY ``` ### Expected behavior The semantic reference contract in `src/domain/javascript/javascriptSemanticIr.ts:83-89` distinguishes `export` from `read` and `write`. `src/domain/javascript/javascriptSemanticProjection.ts:709-725` classifies the local identifier of an export specifier as `export`, separately from assignment and update targets. Exporting an existing binding should retain its module relationship without creating a write relation to that binding. `docs/javascript-application-workflows.md:58-85` documents authenticated semantic tracing, relation restrictions, and the reachable semantic subgraph. `src/domain/javascript/javascriptSemanticGraphSchemas.ts:124,172` defines `writes` as a data-flow relation. A backward trace admitting only `writes` should retain the matching binding seed and traverse zero relations for the fixture above. ### Impact and scope Plain and aliased local export specifiers produce false write provenance in the semantic graph and public CLI trace. The verifier's declaration-only control produced zero writes and two defines. A genuine reassignment control produced one write with source role `reference:0:write:answer`. This is a bounded semantic-result defect, assessed as P2. No analysis failure, data loss, runtime mutation, or security boundary crossing was observed. Returned source nodes expose the export role and source range, so callers can exclude export-sourced edges from a derived interpretation of write provenance while retaining genuine assignment edges and leaving authenticated Evidence unchanged. This workaround does not repair the underlying graph. The shared trace service is also registered for MCP according to source inspection, but MCP transport was not separately executed. Source re-exports were not tested. ### REA version 6.4.0 (main at 58300885207133422af235e0f2b10658cf958b03) ### Runtime and platform Node v26.10.0; Darwin 27.0.0 arm64. ### Target and artifact context A synthetic local ES-module directory with `package.json` containing `{ "type": "module" }` and a two-line `main.js`. The trace input contains the returned application Evidence unchanged and the actual binding node ID. No native analysis provider is required. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence ```text Analysis, exit 0: {"relation":"writes","resolution":"resolved","source_role":"reference:0:export:answer","target_kind":"binding","target_label":"answer","location":{"available":true,"value":{"kind":"source-range","source":"main.js","start":{"line":2,"column":9},"end":{"line":2,"column":15}}}} Writes-only backward trace, exit 0: {"status":"found","seed_matches":1,"traversed_nodes":2,"traversed_relations":1,"relations":["writes"]} ``` ### Typed outcome or recovery expected Retain the declaration's definition relationships and module export facts, with zero export-sourced `writes` relations. For both plain and aliased local exports, the writes-only backward trace should return the matching binding seed and zero traversed relations. A seed-only match still returns `status: "found"`, as the declaration-only control demonstrated. Preserve the genuine write and its traceability for `let answer = 0; answer = 1;`. ### Analysis or suspected cause Source-derived reasoning at the reproduced baseline: `src/application/javascript/JavaScriptSemanticGraphBuilder.ts:627-658` projects every bound non-read reference as `writes`. The source role remains `export`, but that reference takes the same graph branch as an assignment. The domain classification already distinguishes these roles in `src/domain/javascript/javascriptSemanticProjection.ts:709-725`. The public trace command reaches `traceJavaScriptSemanticsEvidenceValidated` through `src/cli/applicationCommands.ts:43-54`. `src/application/javascript/JavaScriptSemanticTraceService.ts:25-77` validates the application Evidence and queries the authenticated semantic graph. The observed trace therefore follows the incorrect relation produced by analysis. MCP registration at `src/server/registerApplicationTools/traceSemantics.ts:24-70` calls the same service, based on source inspection only. ### Additional context P2 is supported by incorrect static data-flow provenance for the reproduced local-export inputs and an available derived-result workaround. The evidence does not establish a severe downstream incident or runtime mutation.
bsakaguchi 2026-10-15 18:31:29 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

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

I reproduced the plain and aliased export cases against current main, with declaration-only and genuine-assignment controls. I am working on a focused graph-projection fix and regression coverage. Windows public CLI verification is currently blocked by the missing native source-checkout addon. AI-assisted work with OpenAI Codex.

<!-- gh-comment-id:6161745001 --> @jpdefo commented on GitHub (Oct 14, 2026): I reproduced the plain and aliased export cases against current main, with declaration-only and genuine-assignment controls. I am working on a focused graph-projection fix and regression coverage. Windows public CLI verification is currently blocked by the missing native source-checkout addon. AI-assisted work with OpenAI Codex.
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#1145
No description provided.