[GH-ISSUE #6456] [Bug] compare-source-to-bundle counts one JavaScript file as split or duplicated #3868

Closed
opened 2026-10-31 00:44:47 +00:00 by bsakaguchi · 1 comment
Owner

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

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

compare-source-to-bundle reports a single main.js as split after changing its exported literal from 1 to 2, and as duplicated when its bytes are unchanged. Each result contains three candidates at the same exact path: the file asset, its source-module representation, and its export-binding representation. There is only one JavaScript file in each current directory.

Both comparisons exited successfully. The unchanged case has two exact digest matches, representing the same whole-file bytes, rather than two distinct current files.

Reproduction

Use a fresh checkout at 9eb829aca34be36ff112826337996f6139f82eff. Install locked dependencies and build first:

npm ci && npm run build:cached

These are fresh-checkout prerequisites; the observed run used already prepared dependencies and runtime. From the checkout root, run this fixture and the public CLI commands:

WORK=$(mktemp -d)
export WORK
python3 - <<'REPRO'
import json, os, subprocess
from pathlib import Path

root = Path(os.environ['WORK'])
(root / 'home').mkdir()
for name, value in [('previous', 1), ('changed', 2), ('same', 1)]:
    tree = root / name
    tree.mkdir()
    (tree / 'package.json').write_text(json.dumps({
        'name': 'fixture', 'version': '1.0.0', 'main': 'main.js'
    }))
    (tree / 'main.js').write_text(f'export const value = {value};\n')

env = dict(os.environ, HOME=str(root / 'home'), REA_LOG_LEVEL='silent')
def cli(*args):
    result = subprocess.run(
        ['node', 'scripts/rea.mjs', *map(str, args), '--json'],
        env=env, text=True, capture_output=True, timeout=120, check=True)
    return json.loads(result.stdout)

reference = cli('import-reference-source', root / 'previous')
for name in ['changed', 'same']:
    application = cli('analyze-javascript-application', root / name)
    request = root / (name + '-request.json')
    request.write_text(json.dumps({
        'reference': reference, 'application': application
    }))
    comparison = cli('compare-source-to-bundle', request)
    (root / (name + '-result.json')).write_text(json.dumps(comparison))
    item = next(i for i in comparison['normalized_result']['items']
                if i['source_path'] == 'main.js')
    print(json.dumps({
        'case': name, 'status': item['status'],
        'confidence': item['confidence'],
        'candidate_count': len(item['candidates']),
        'exact_digest_count': sum(
            any(s['kind'] == 'exact-source-digest' for s in c['signals'])
            for c in item['candidates']),
        'candidate_paths': sorted({
            value for c in item['candidates'] for s in c['signals']
            if s['kind'] == 'current-path-exact'
            for value in s['current_values']})
    }))
REPRO

Observed once per case. Neither the target JavaScript nor an external analysis engine is executed.

Expected behavior

One unchanged current source file should classify as unchanged; the same file with a changed digest should classify as modified. Preserve its graph representations as evidence without interpreting them as independent copies or split destinations.

The workflow contract classifies each historical source file and gives exact digests precedence over path inference. Its split/duplication description currently counts graph mappings/nodes without distinguishing repeated representations of one file. The single-exact-match and located-different-digest branches define unchanged and modified; this ordinary one-file input should reach those cases.

Impact and scope

The mapping status and summary give false split/duplication results for the tested ESM directory fixture. This can mislead source-retention analysis even though all candidate paths and digest signals remain available. Inspecting those retained signals and directly comparing the single current file provides a bounded workaround for this fixture.

Both application graphs had complete coverage with no truncation, omissions, or limits. The unmodified historical import was partial because of its standard pathname-race limitation; positive mapping candidates were nevertheless present. No data loss, target mutation, or security boundary crossing was observed. Other source forms, ASAR inputs, source maps, and MCP were not tested; this report makes no global unreachability claim about other statuses.

REA version

6.7.0 (source 9eb829aca3)

Runtime and platform

Node v26.12.1; macOS 27.0.1; arm64.

Target and artifact context

Three inert local application directories, each containing identical package metadata and one main.js. The historical and identical cases contain export const value = 1; plus a newline; the edited case contains export const value = 2; plus a newline. The comparison uses the importer's graph and analyzer's complete Evidence without modification.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Selected fields obtained from the public comparison JSON:

{"case":"changed","status":"split","confidence":"medium","candidate_count":3,"exact_digest_count":0,"candidate_paths":["main.js"]}
{"case":"same","status":"duplicated","confidence":"exact","candidate_count":3,"exact_digest_count":2,"candidate_paths":["main.js"]}

The changed candidates each score 85. The unchanged asset and source-module each score 185 and carry exact-source-digest; the export-binding scores 85 and carries path evidence only. The changed limitation says “Multiple equally scored current nodes retain the historical source path”; every candidate names the same current path.

Typed outcome or recovery expected

Return a successful comparison with main.js classified as modified for the edited tree and unchanged for the identical tree. Retain its candidate evidence. Genuine independent copies or split destinations should remain distinguishable.

Analysis or suspected cause

Source-derived reasoning: candidate admission accepts both asset and module representations. Projection reads the asset content digest and source-module observation digest as separate current candidates. The classifier uses the number of exact matches for duplication and equally scored path candidates for splitting, without distinguishing repeated representations of this one current source.

Additional context

Related issues #3191 and #3188 concern missing JSON candidates and application-version module correspondence respectively. Their delivered fixes do not address the one-path split/duplication behavior reproduced here. No last known good revision was established.

Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6456 ### 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 `compare-source-to-bundle` reports a single `main.js` as `split` after changing its exported literal from 1 to 2, and as `duplicated` when its bytes are unchanged. Each result contains three candidates at the same exact path: the file asset, its source-module representation, and its export-binding representation. There is only one JavaScript file in each current directory. Both comparisons exited successfully. The unchanged case has two exact digest matches, representing the same whole-file bytes, rather than two distinct current files. ### Reproduction Use a fresh checkout at `9eb829aca34be36ff112826337996f6139f82eff`. Install locked dependencies and build first: ```sh npm ci && npm run build:cached ``` These are fresh-checkout prerequisites; the observed run used already prepared dependencies and runtime. From the checkout root, run this fixture and the public CLI commands: ```sh WORK=$(mktemp -d) export WORK python3 - <<'REPRO' import json, os, subprocess from pathlib import Path root = Path(os.environ['WORK']) (root / 'home').mkdir() for name, value in [('previous', 1), ('changed', 2), ('same', 1)]: tree = root / name tree.mkdir() (tree / 'package.json').write_text(json.dumps({ 'name': 'fixture', 'version': '1.0.0', 'main': 'main.js' })) (tree / 'main.js').write_text(f'export const value = {value};\n') env = dict(os.environ, HOME=str(root / 'home'), REA_LOG_LEVEL='silent') def cli(*args): result = subprocess.run( ['node', 'scripts/rea.mjs', *map(str, args), '--json'], env=env, text=True, capture_output=True, timeout=120, check=True) return json.loads(result.stdout) reference = cli('import-reference-source', root / 'previous') for name in ['changed', 'same']: application = cli('analyze-javascript-application', root / name) request = root / (name + '-request.json') request.write_text(json.dumps({ 'reference': reference, 'application': application })) comparison = cli('compare-source-to-bundle', request) (root / (name + '-result.json')).write_text(json.dumps(comparison)) item = next(i for i in comparison['normalized_result']['items'] if i['source_path'] == 'main.js') print(json.dumps({ 'case': name, 'status': item['status'], 'confidence': item['confidence'], 'candidate_count': len(item['candidates']), 'exact_digest_count': sum( any(s['kind'] == 'exact-source-digest' for s in c['signals']) for c in item['candidates']), 'candidate_paths': sorted({ value for c in item['candidates'] for s in c['signals'] if s['kind'] == 'current-path-exact' for value in s['current_values']}) })) REPRO ``` Observed once per case. Neither the target JavaScript nor an external analysis engine is executed. ### Expected behavior One unchanged current source file should classify as `unchanged`; the same file with a changed digest should classify as `modified`. Preserve its graph representations as evidence without interpreting them as independent copies or split destinations. The [workflow contract](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/docs/javascript-application-workflows.md#L338) classifies each historical source file and gives exact digests precedence over path inference. Its split/duplication description currently counts graph mappings/nodes without distinguishing repeated representations of one file. The [single-exact-match and located-different-digest branches](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/domain/javascript/sourceToBundleComparison.ts#L183) define `unchanged` and `modified`; this ordinary one-file input should reach those cases. ### Impact and scope The mapping status and summary give false split/duplication results for the tested ESM directory fixture. This can mislead source-retention analysis even though all candidate paths and digest signals remain available. Inspecting those retained signals and directly comparing the single current file provides a bounded workaround for this fixture. Both application graphs had complete coverage with no truncation, omissions, or limits. The unmodified historical import was partial because of its standard pathname-race limitation; positive mapping candidates were nevertheless present. No data loss, target mutation, or security boundary crossing was observed. Other source forms, ASAR inputs, source maps, and MCP were not tested; this report makes no global unreachability claim about other statuses. ### REA version 6.7.0 (source 9eb829aca34be36ff112826337996f6139f82eff) ### Runtime and platform Node v26.12.1; macOS 27.0.1; arm64. ### Target and artifact context Three inert local application directories, each containing identical package metadata and one `main.js`. The historical and identical cases contain `export const value = 1;` plus a newline; the edited case contains `export const value = 2;` plus a newline. The comparison uses the importer's graph and analyzer's complete Evidence without modification. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Selected fields obtained from the public comparison JSON: ```json {"case":"changed","status":"split","confidence":"medium","candidate_count":3,"exact_digest_count":0,"candidate_paths":["main.js"]} {"case":"same","status":"duplicated","confidence":"exact","candidate_count":3,"exact_digest_count":2,"candidate_paths":["main.js"]} ``` The changed candidates each score 85. The unchanged asset and source-module each score 185 and carry `exact-source-digest`; the export-binding scores 85 and carries path evidence only. The changed limitation says “Multiple equally scored current nodes retain the historical source path”; every candidate names the same current path. ### Typed outcome or recovery expected Return a successful comparison with `main.js` classified as `modified` for the edited tree and `unchanged` for the identical tree. Retain its candidate evidence. Genuine independent copies or split destinations should remain distinguishable. ### Analysis or suspected cause Source-derived reasoning: [candidate admission](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/domain/javascript/sourceToBundleSignals.ts#L47) accepts both asset and module representations. [Projection](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/domain/javascript/sourceToBundleSignals.ts#L184) reads the asset content digest and source-module observation digest as separate current candidates. The classifier uses [the number of exact matches](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/domain/javascript/sourceToBundleComparison.ts#L188) for duplication and [equally scored path candidates](https://github.com/morluto/rea/blob/9eb829aca34be36ff112826337996f6139f82eff/src/domain/javascript/sourceToBundleComparison.ts#L209) for splitting, without distinguishing repeated representations of this one current source. ### Additional context Related issues #3191 and #3188 concern missing JSON candidates and application-version module correspondence respectively. Their delivered fixes do not address the one-path split/duplication behavior reproduced here. No last known good revision was established.
Author
Owner

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

Checked the reported fix on main at d87e73f2db.

Multiplicity now operates on producing file-occurrence identity (container digest, exact path, file digest), grouping asset/module/export observations into one candidate while retaining representation_node_ids. The unchanged and changed one-file regressions invoke production reference import and application analysis, assert exactly one candidate with unchanged/modified status, and check all representations are mapped. A separate two-file identical-byte case still asserts duplication.

Implementation: sourceToBundleSignals.ts, sourceToBundleComparison.ts.

The relevant regression checks in sourceToBundlePaths.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:6380739676 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. Multiplicity now operates on producing file-occurrence identity (container digest, exact path, file digest), grouping asset/module/export observations into one candidate while retaining representation_node_ids. The unchanged and changed one-file regressions invoke production reference import and application analysis, assert exactly one candidate with unchanged/modified status, and check all representations are mapped. A separate two-file identical-byte case still asserts duplication. Implementation: [sourceToBundleSignals.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/sourceToBundleSignals.ts#L198), [sourceToBundleComparison.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/sourceToBundleComparison.ts#L194). The relevant regression checks in [sourceToBundlePaths.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/filesystem/sourceToBundlePaths.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#3868
No description provided.