[GH-ISSUE #4269] [Bug] Escaped ESM pathnames select the wrong historical dependency #1689

Closed
opened 2026-10-16 21:09:05 +00:00 by bsakaguchi · 2 comments
Owner

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

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

import-reference-source records a parsed internal ESM dependency on the literal percent-escaped filename instead of the separately inventoried file identified by that pathname escape. With both dep name.mjs and dep%20name.mjs present and carrying distinct content hashes, import "./dep%20name.mjs" targets dep%20name.mjs in REA; Node's resolve-only control identifies the existing regular file dep name.mjs. Source inventory is retained, but the relationship identifies the wrong file. CommonJS correctly retains literal percent characters for the same request spelling.

Reproduction

Run this self-contained reproduction from the built checkout root. It creates independent inert UTF-8 fixtures, invokes the public CLI, checks every inventoried file against an independent SHA-256, and asks Node to resolve each request without importing or executing any fixture module. The decoded-absent case is a negative identity control; its requested module does not exist.

python3 - <<'PYTHON'
import hashlib
import json
import os
from pathlib import Path
import subprocess
import tempfile

cases = [
    ("esm-both", "mjs", "./dep%20name.mjs", ["dep name.mjs", "dep%20name.mjs"]),
    ("esm-once", "mjs", "./dep%2520name.mjs", ["dep name.mjs", "dep%20name.mjs", "dep%2520name.mjs"]),
    ("esm-unicode", "mjs", "./caf%C3%A9.mjs", ["café.mjs", "caf%C3%A9.mjs"]),
    ("esm-decoded-only", "mjs", "./dep%20name.mjs", ["dep name.mjs"]),
    ("esm-decoded-absent", "mjs", "./dep%20name.mjs", ["dep%20name.mjs"]),
    ("cjs-both", "cjs", "./dep%20name.cjs", ["dep name.cjs", "dep%20name.cjs"]),
    ("esm-plain", "mjs", "./dep.mjs", ["dep.mjs"]),
]
oracle = '''import {createRequire} from "node:module";
import {fileURLToPath,pathToFileURL} from "node:url";
import {relative} from "node:path";
import {existsSync,statSync} from "node:fs";
const [root,kind,specifier] = process.argv.slice(2);
const main = root + "/main." + kind;
const target = kind === "cjs"
  ? createRequire(main).resolve(specifier)
  : fileURLToPath(import.meta.resolve(new URL(specifier,pathToFileURL(main)).href));
console.log(JSON.stringify({target:relative(root,target),regular:existsSync(target)&&statSync(target).isFile()}));
'''
with tempfile.TemporaryDirectory(prefix="reference-esm-") as work:
    base = Path(work).resolve()
    home = base / "home"
    home.mkdir()
    env = {"PATH": os.environ["PATH"], "HOME": str(home),
           "XDG_CONFIG_HOME": str(home / "config"),
           "XDG_CACHE_HOME": str(home / "cache"),
           "REA_REFERENCE_SECRET_PATTERNS_JSON": "[]"}
    control = base / "oracle.mjs"
    control.write_text(oracle, encoding="utf-8")
    for name, kind, specifier, files in cases:
        root = base / name
        root.mkdir()
        main = "main." + kind
        source = ("import " + json.dumps(specifier) + ";\n") if kind == "mjs" else ("require(" + json.dumps(specifier) + ");\n")
        (root / main).write_text(source, encoding="utf-8")
        for index, filename in enumerate(files):
            content = ("export const value = " + str(index) + ";\n") if kind == "mjs" else ("module.exports = " + str(index) + ";\n")
            (root / filename).write_text(content, encoding="utf-8")
        imported = subprocess.run(
            ["node", "scripts/rea.mjs", "import-reference-source", str(root), "--json"],
            env=env, capture_output=True, text=True, timeout=30)
        print(name, "CLI exit:", imported.returncode, "stderr:", repr(imported.stderr))
        graph = json.loads(imported.stdout)
        actual = {entry["path"]: entry["sha256"] for entry in graph["entries"]}
        expected = {filename: hashlib.sha256((root / filename).read_bytes()).hexdigest() for filename in files + [main]}
        assert actual == expected
        assert all(entry["content_state"] == "hashed" for entry in graph["entries"])
        print("schema:", graph["schema"], "parse_failures:", graph["parse_failures"], "exclusions:", graph["exclusions"])
        print("relationships:", json.dumps(graph["relationships"]))
        print("inventory hashes:", json.dumps(actual, ensure_ascii=False, sort_keys=True))
        resolved = subprocess.run(
            ["node", str(control), str(root), kind, specifier],
            env=env, capture_output=True, text=True, timeout=10)
        print("Node exit:", resolved.returncode, "result:", resolved.stdout.strip(), "stderr:", repr(resolved.stderr))
        assert imported.returncode == 0 and imported.stderr == ""
        assert graph["schema"] == "HistoricalSourceGraph"
        assert graph["parse_failures"] == [] and graph["exclusions"] == []
        assert resolved.returncode == 0 and resolved.stderr == ""
PYTHON

The reproduction was executed with all seven cases. These are independent fixture controls, not changes suggested for historical source. No package manifest, VCS repository, symlink, ignore rule, provider, network access or source execution is needed.

Expected behavior

For supported static local dependencies, the historical graph should relate an unambiguous explicit-extension .mjs import to the inventoried file identified by that ESM request. Here esm-both and esm-decoded-only should target dep name.mjs; esm-once should target literal dep%20name.mjs, preserving a percent character after exactly one decoding pass; and esm-unicode should target café.mjs. CommonJS should continue selecting literal dep%20name.cjs, and plain ESM should continue selecting dep.mjs. When the decoded file is absent, REA must not report an internal edge to the unrelated literal-escape file.

This expectation concerns REA's existing static reference dependency projection. It does not require complete Node loader emulation, runtime module execution, or inference for computed requests.

Impact and scope

P2, supported: valid explicit relative ESM requests produce incorrect or missing historical dependency relationships while the original source inventory remains hashed and available. The observed effect is bounded to these pathname escapes; no severe harm, universal workflow failure, target execution or downstream comparison error was demonstrated. Real-world prevalence is unknown.

A graph-preserving workaround remains unverified. Node's resolve-only control identifies the intended file for manual interpretation but does not correct structured graph data. Changing or double-encoding the import changes historical source bytes and requested identity.

REA version

Source-built CLI at 461eb161282237ad3135f6d8f8fbbc560285548e, freshly fetched from public main and compiled with npm run build:cached before the reproduction. The source CLI reports 6.5.0; this identifies the tested source build, not a reproduction of the published package.

Runtime and platform

Node.js v24.18.0; npm 11.16.0; macOS 27.0.1 arm64. The commands ran from the built checkout root with the pinned Node executable on PATH and isolated HOME/config/cache. Published-package, MCP, alternate-platform and downstream-comparison behavior were not exercised.

Target and artifact context

Independent inert local UTF-8 source trees contain explicit-extension .mjs static relative imports and separately inventoried literal-escaped and decoded filenames with distinct content hashes. A .cjs fixture checks literal-percent CommonJS lookup. No provider, network access, source execution, package manifest, VCS repository, symlink or ignore rule is required.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction. The parsed historical dependency relationship first names the wrong file or remains unresolved.

Relevant output or evidence

Every CLI call exited 0 with empty stderr and returned a direct HistoricalSourceGraph with empty parse_failures and exclusions. All file entries were hashed and matched independent SHA-256 checks. Every Node resolve-only call exited 0 with empty stderr. Observed targets were:

case                 REA target          resolution   Node pathname       regular
esm-both             dep%20name.mjs      internal     dep name.mjs        true
esm-once             dep%2520name.mjs    internal     dep%20name.mjs      true
esm-unicode          caf%C3%A9.mjs        internal     café.mjs            true
esm-decoded-only     dep%20name.mjs      unresolved   dep name.mjs        true
esm-decoded-absent   dep%20name.mjs      internal     dep name.mjs        false
cjs-both             dep%20name.cjs      internal     dep%20name.cjs      true
esm-plain            dep.mjs             internal     dep.mjs             true

The primary failing relationship is:

{"from_path":"main.mjs","to":"dep%20name.mjs","kind":"imports","resolution":"internal","parse_state":"parsed"}

In that same graph, dep name.mjs has SHA-256 f8ff18e7dea68bb8dd4181fa54e8c2401abc2aae9008c0f7595483eb6a2074ec, while dep%20name.mjs has SHA-256 5d8f65d2774e206bc9f7a7a4ad39ca2dc563b5c31e46ab57ef4874961237ce29. Both are inventoried files; this is not an alternate display spelling for one identity. Node's resolve-only API can return a nonexistent pathname, so the decoded-absent control establishes identity and absence, not successful loading.

  • Expected source: the historical-source CLI contract exposes hashes, relationships and limitations; reference-import boundary tests expect internal edges to inventoried import targets and distinguish ESM/CommonJS lookup. The pinned Node ESM specification supplies the meaning of relative ESM requests, corroborated by the executed same-tree controls.
  • Failure source: the executed public CLI reproduction above selects the literal escaped path, with both distinct file hashes inventoried, while Node identifies the decoded regular file. The reference resolver and relationship projection join and probe the whole specifier without ESM pathname decoding; inventory path lookup preserves exact filesystem spellings.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Three existing focused files passed eight tests on this source revision: src/application/ReferenceSourceImportEntries.test.ts, tests/boundary/filesystem/referenceSourceDynamicImports.test.ts, and tests/boundary/cli/referenceSourceDirectoryImports.test.ts. They do not cover these escaped ESM pathname fixtures. No full suite or downstream comparison was run. The source trace explains the observation without prescribing a repair. The application analyzer's separate URL-resolution documentation is not used as a direct contract for reference-source import.

Typed outcome or recovery expected

Rerun the unchanged reproduction and retain the original source bytes, inventoried paths and hashes. The ESM relationships must identify the expected existing files described above; the decoded-absent control must not falsely resolve to the literal escaped file. Preserve literal-percent CommonJS and ordinary ESM results, and preserve parsed import coverage rather than omitting the failing declaration. The %2520 case distinguishes exactly one pathname decode from recursive decoding. Any implementation that restores these observable identities and preserves the controls is acceptable.

Additional context

Open #3916 specifies query/fragment components becoming filename bytes, with closure on an ESM suffix fixture, an ordinary import, and literal CommonJS punctuation. This fixture has no query or fragment and concerns escapes within the pathname, including once-only decoding. The two closure obligations are independently observable: separating URL components does not establish decoded pathname identity, and decoded pathname identity does not establish suffix handling. A shared URL-aware implementation may resolve both; this report does not require separate implementation work.

CommonJS issues #3895, #3896, and #4189 concern file/index ordering or package-main selection, without this flat ESM escaped pathname contract. Invalid percent syntax and encoded path separators are outside this reproduction.

Closed #1782, #2960, #3214 and #3307 concern source-map URL lookup, encoded-byte admission, URL control-character preprocessing or empty URL segments in the separate application analyzer. Their stated closure does not cover this historical reference-import pathname identity. NOT_ENOUGH_INFO: a last-known-good version, real-world prevalence and installed-release behavior were not established.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Valid explicit relative ESM requests produce incorrect or missing historical dependency relationships while the original source inventory remains hashed and available. The observed effect is bounded to these pathname escapes; no severe harm, universal workflow failure, target execution or downstream comparison error was demonstrated.
  • Workaround status: unverified
  • Workaround basis: The resolve-only control identifies the intended file for manual interpretation but does not correct structured graph data. Changing or double-encoding the import changes historical source bytes and requested identity; no practical graph-preserving workaround for the original artifact was tested.
Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4269 ### 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 `import-reference-source` records a parsed internal ESM dependency on the literal percent-escaped filename instead of the separately inventoried file identified by that pathname escape. With both `dep name.mjs` and `dep%20name.mjs` present and carrying distinct content hashes, `import "./dep%20name.mjs"` targets `dep%20name.mjs` in REA; Node's resolve-only control identifies the existing regular file `dep name.mjs`. Source inventory is retained, but the relationship identifies the wrong file. CommonJS correctly retains literal percent characters for the same request spelling. ### Reproduction Run this self-contained reproduction from the built checkout root. It creates independent inert UTF-8 fixtures, invokes the public CLI, checks every inventoried file against an independent SHA-256, and asks Node to resolve each request without importing or executing any fixture module. The decoded-absent case is a negative identity control; its requested module does not exist. ```sh python3 - <<'PYTHON' import hashlib import json import os from pathlib import Path import subprocess import tempfile cases = [ ("esm-both", "mjs", "./dep%20name.mjs", ["dep name.mjs", "dep%20name.mjs"]), ("esm-once", "mjs", "./dep%2520name.mjs", ["dep name.mjs", "dep%20name.mjs", "dep%2520name.mjs"]), ("esm-unicode", "mjs", "./caf%C3%A9.mjs", ["café.mjs", "caf%C3%A9.mjs"]), ("esm-decoded-only", "mjs", "./dep%20name.mjs", ["dep name.mjs"]), ("esm-decoded-absent", "mjs", "./dep%20name.mjs", ["dep%20name.mjs"]), ("cjs-both", "cjs", "./dep%20name.cjs", ["dep name.cjs", "dep%20name.cjs"]), ("esm-plain", "mjs", "./dep.mjs", ["dep.mjs"]), ] oracle = '''import {createRequire} from "node:module"; import {fileURLToPath,pathToFileURL} from "node:url"; import {relative} from "node:path"; import {existsSync,statSync} from "node:fs"; const [root,kind,specifier] = process.argv.slice(2); const main = root + "/main." + kind; const target = kind === "cjs" ? createRequire(main).resolve(specifier) : fileURLToPath(import.meta.resolve(new URL(specifier,pathToFileURL(main)).href)); console.log(JSON.stringify({target:relative(root,target),regular:existsSync(target)&&statSync(target).isFile()})); ''' with tempfile.TemporaryDirectory(prefix="reference-esm-") as work: base = Path(work).resolve() home = base / "home" home.mkdir() env = {"PATH": os.environ["PATH"], "HOME": str(home), "XDG_CONFIG_HOME": str(home / "config"), "XDG_CACHE_HOME": str(home / "cache"), "REA_REFERENCE_SECRET_PATTERNS_JSON": "[]"} control = base / "oracle.mjs" control.write_text(oracle, encoding="utf-8") for name, kind, specifier, files in cases: root = base / name root.mkdir() main = "main." + kind source = ("import " + json.dumps(specifier) + ";\n") if kind == "mjs" else ("require(" + json.dumps(specifier) + ");\n") (root / main).write_text(source, encoding="utf-8") for index, filename in enumerate(files): content = ("export const value = " + str(index) + ";\n") if kind == "mjs" else ("module.exports = " + str(index) + ";\n") (root / filename).write_text(content, encoding="utf-8") imported = subprocess.run( ["node", "scripts/rea.mjs", "import-reference-source", str(root), "--json"], env=env, capture_output=True, text=True, timeout=30) print(name, "CLI exit:", imported.returncode, "stderr:", repr(imported.stderr)) graph = json.loads(imported.stdout) actual = {entry["path"]: entry["sha256"] for entry in graph["entries"]} expected = {filename: hashlib.sha256((root / filename).read_bytes()).hexdigest() for filename in files + [main]} assert actual == expected assert all(entry["content_state"] == "hashed" for entry in graph["entries"]) print("schema:", graph["schema"], "parse_failures:", graph["parse_failures"], "exclusions:", graph["exclusions"]) print("relationships:", json.dumps(graph["relationships"])) print("inventory hashes:", json.dumps(actual, ensure_ascii=False, sort_keys=True)) resolved = subprocess.run( ["node", str(control), str(root), kind, specifier], env=env, capture_output=True, text=True, timeout=10) print("Node exit:", resolved.returncode, "result:", resolved.stdout.strip(), "stderr:", repr(resolved.stderr)) assert imported.returncode == 0 and imported.stderr == "" assert graph["schema"] == "HistoricalSourceGraph" assert graph["parse_failures"] == [] and graph["exclusions"] == [] assert resolved.returncode == 0 and resolved.stderr == "" PYTHON ``` The reproduction was executed with all seven cases. These are independent fixture controls, not changes suggested for historical source. No package manifest, VCS repository, symlink, ignore rule, provider, network access or source execution is needed. ### Expected behavior For supported static local dependencies, the historical graph should relate an unambiguous explicit-extension `.mjs` import to the inventoried file identified by that ESM request. Here `esm-both` and `esm-decoded-only` should target `dep name.mjs`; `esm-once` should target literal `dep%20name.mjs`, preserving a percent character after exactly one decoding pass; and `esm-unicode` should target `café.mjs`. CommonJS should continue selecting literal `dep%20name.cjs`, and plain ESM should continue selecting `dep.mjs`. When the decoded file is absent, REA must not report an internal edge to the unrelated literal-escape file. This expectation concerns REA's existing static reference dependency projection. It does not require complete Node loader emulation, runtime module execution, or inference for computed requests. ### Impact and scope P2, supported: valid explicit relative ESM requests produce incorrect or missing historical dependency relationships while the original source inventory remains hashed and available. The observed effect is bounded to these pathname escapes; no severe harm, universal workflow failure, target execution or downstream comparison error was demonstrated. Real-world prevalence is unknown. A graph-preserving workaround remains unverified. Node's resolve-only control identifies the intended file for manual interpretation but does not correct structured graph data. Changing or double-encoding the import changes historical source bytes and requested identity. ### REA version Source-built CLI at [461eb161282237ad3135f6d8f8fbbc560285548e](https://github.com/morluto/rea/commit/461eb161282237ad3135f6d8f8fbbc560285548e), freshly fetched from public main and compiled with `npm run build:cached` before the reproduction. The source CLI reports `6.5.0`; this identifies the tested source build, not a reproduction of the published package. ### Runtime and platform Node.js v24.18.0; npm 11.16.0; macOS 27.0.1 arm64. The commands ran from the built checkout root with the pinned Node executable on PATH and isolated HOME/config/cache. Published-package, MCP, alternate-platform and downstream-comparison behavior were not exercised. ### Target and artifact context Independent inert local UTF-8 source trees contain explicit-extension `.mjs` static relative imports and separately inventoried literal-escaped and decoded filenames with distinct content hashes. A `.cjs` fixture checks literal-percent CommonJS lookup. No provider, network access, source execution, package manifest, VCS repository, symlink or ignore rule is required. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction. The parsed historical dependency relationship first names the wrong file or remains unresolved. ### Relevant output or evidence Every CLI call exited 0 with empty stderr and returned a direct `HistoricalSourceGraph` with empty `parse_failures` and `exclusions`. All file entries were hashed and matched independent SHA-256 checks. Every Node resolve-only call exited 0 with empty stderr. Observed targets were: ```text case REA target resolution Node pathname regular esm-both dep%20name.mjs internal dep name.mjs true esm-once dep%2520name.mjs internal dep%20name.mjs true esm-unicode caf%C3%A9.mjs internal café.mjs true esm-decoded-only dep%20name.mjs unresolved dep name.mjs true esm-decoded-absent dep%20name.mjs internal dep name.mjs false cjs-both dep%20name.cjs internal dep%20name.cjs true esm-plain dep.mjs internal dep.mjs true ``` The primary failing relationship is: ```json {"from_path":"main.mjs","to":"dep%20name.mjs","kind":"imports","resolution":"internal","parse_state":"parsed"} ``` In that same graph, `dep name.mjs` has SHA-256 `f8ff18e7dea68bb8dd4181fa54e8c2401abc2aae9008c0f7595483eb6a2074ec`, while `dep%20name.mjs` has SHA-256 `5d8f65d2774e206bc9f7a7a4ad39ca2dc563b5c31e46ab57ef4874961237ce29`. Both are inventoried files; this is not an alternate display spelling for one identity. Node's resolve-only API can return a nonexistent pathname, so the decoded-absent control establishes identity and absence, not successful loading. - Expected source: the [historical-source CLI contract](https://github.com/morluto/rea/blob/461eb161282237ad3135f6d8f8fbbc560285548e/docs/cli.md#L234-L289) exposes hashes, relationships and limitations; [reference-import boundary tests](https://github.com/morluto/rea/blob/461eb161282237ad3135f6d8f8fbbc560285548e/tests/boundary/filesystem/referenceSourceDynamicImports.test.ts#L38-L105) expect internal edges to inventoried import targets and distinguish ESM/CommonJS lookup. The [pinned Node ESM specification](https://nodejs.org/download/release/v24.18.0/docs/api/esm.html#import-specifiers) supplies the meaning of relative ESM requests, corroborated by the executed same-tree controls. - Failure source: the executed public CLI reproduction above selects the literal escaped path, with both distinct file hashes inventoried, while Node identifies the decoded regular file. The [reference resolver and relationship projection](https://github.com/morluto/rea/blob/461eb161282237ad3135f6d8f8fbbc560285548e/src/application/ReferenceSourceImportEntries.ts#L268-L368) join and probe the whole specifier without ESM pathname decoding; [inventory path lookup](https://github.com/morluto/rea/blob/461eb161282237ad3135f6d8f8fbbc560285548e/src/domain/referenceSourcePathIdentity.ts#L14-L40) preserves exact filesystem spellings. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Three existing focused files passed eight tests on this source revision: `src/application/ReferenceSourceImportEntries.test.ts`, `tests/boundary/filesystem/referenceSourceDynamicImports.test.ts`, and `tests/boundary/cli/referenceSourceDirectoryImports.test.ts`. They do not cover these escaped ESM pathname fixtures. No full suite or downstream comparison was run. The source trace explains the observation without prescribing a repair. The application analyzer's separate URL-resolution documentation is not used as a direct contract for reference-source import. ### Typed outcome or recovery expected Rerun the unchanged reproduction and retain the original source bytes, inventoried paths and hashes. The ESM relationships must identify the expected existing files described above; the decoded-absent control must not falsely resolve to the literal escaped file. Preserve literal-percent CommonJS and ordinary ESM results, and preserve parsed import coverage rather than omitting the failing declaration. The `%2520` case distinguishes exactly one pathname decode from recursive decoding. Any implementation that restores these observable identities and preserves the controls is acceptable. ### Additional context Open [#3916](https://github.com/morluto/rea/issues/3916) specifies query/fragment components becoming filename bytes, with closure on an ESM suffix fixture, an ordinary import, and literal CommonJS punctuation. This fixture has no query or fragment and concerns escapes within the pathname, including once-only decoding. The two closure obligations are independently observable: separating URL components does not establish decoded pathname identity, and decoded pathname identity does not establish suffix handling. A shared URL-aware implementation may resolve both; this report does not require separate implementation work. CommonJS issues [#3895](https://github.com/morluto/rea/issues/3895), [#3896](https://github.com/morluto/rea/issues/3896), and [#4189](https://github.com/morluto/rea/issues/4189) concern file/index ordering or package-main selection, without this flat ESM escaped pathname contract. Invalid percent syntax and encoded path separators are outside this reproduction. Closed [#1782](https://github.com/morluto/rea/issues/1782), [#2960](https://github.com/morluto/rea/issues/2960), [#3214](https://github.com/morluto/rea/issues/3214) and [#3307](https://github.com/morluto/rea/issues/3307) concern source-map URL lookup, encoded-byte admission, URL control-character preprocessing or empty URL segments in the separate application analyzer. Their stated closure does not cover this historical reference-import pathname identity. NOT_ENOUGH_INFO: a last-known-good version, real-world prevalence and installed-release behavior were not established. ### Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Valid explicit relative ESM requests produce incorrect or missing historical dependency relationships while the original source inventory remains hashed and available. The observed effect is bounded to these pathname escapes; no severe harm, universal workflow failure, target execution or downstream comparison error was demonstrated. - Workaround status: unverified - Workaround basis: The resolve-only control identifies the intended file for manual interpretation but does not correct structured graph data. Changing or double-encoding the import changes historical source bytes and requested identity; no practical graph-preserving workaround for the original artifact was tested.
Author
Owner

@x1mm4nu3l commented on GitHub (Oct 20, 2026):

Still reproducible on main 2538214e2, Linux/Node 24.18.0. With both filenames present, import-reference-source targets literal dep%20name.mjs, while Node resolution and analyze-javascript-application select dep name.mjs. The double-escaped case also needs exactly one decode. When only the literal-escape file exists, reference import falsely marks it internal while application analysis leaves it unresolved. CommonJS correctly keeps percent characters literal. Inventory hashes match the source files.

<!-- gh-comment-id:6286505315 --> @x1mm4nu3l commented on GitHub (Oct 20, 2026): Still reproducible on main 2538214e2, Linux/Node 24.18.0. With both filenames present, `import-reference-source` targets literal dep%20name.mjs, while Node resolution and `analyze-javascript-application` select dep name.mjs. The double-escaped case also needs exactly one decode. When only the literal-escape file exists, reference import falsely marks it internal while application analysis leaves it unresolved. CommonJS correctly keeps percent characters literal. Inventory hashes match the source files.
Author
Owner

@morluto commented on GitHub (Oct 23, 2026):

Resolved by github.com/morluto/rea@f2e5fd148a. Historical ESM lookup decodes exactly once while retaining declared source_specifier; spaces, literal percent, query/fragment versions, malformed encodings and CommonJS literal controls are checked. Validation: tests/boundary/cli/referenceSourceModuleSemantics.test.ts passed in the recorded round2/3 execution; this review did not rerun tests. Coverage is limited to the cited control; no broader real-provider, Windows or performance claim.

<!-- gh-comment-id:6376117842 --> @morluto commented on GitHub (Oct 23, 2026): Resolved by https://github.com/morluto/rea/commit/f2e5fd148a85fa737302dd194a33172c4561093e. Historical ESM lookup decodes exactly once while retaining declared source_specifier; spaces, literal percent, query/fragment versions, malformed encodings and CommonJS literal controls are checked. Validation: `tests/boundary/cli/referenceSourceModuleSemantics.test.ts` passed in the recorded round2/3 execution; this review did not rerun tests. Coverage is limited to the cited control; no broader real-provider, Windows or performance claim.
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#1689
No description provided.