[GH-ISSUE #6655] [Bug] Historical reference import drops exported TypeScript import-equals dependencies #4033

Closed
opened 2026-10-31 00:46:14 +00:00 by bsakaguchi · 0 comments
Owner

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

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

Observed at the production importReferenceSource application boundary: export import dep = require("./dep.cjs"); in exported.cts produces no relationship. Its plain import-equals control, followed by export { dep }, produces the expected parsed internal requires edge to dep.cjs.

Both source files and the dependency are hashed, with no entry limitations, parse failures, or exclusions. A separate emit-only control using installed esbuild 0.28.2 accepted both forms without warnings and emitted const dep = require("./dep.cjs"); for both. Emitted code was not executed.

Reproduction

The production importer completed this ten-file probe once. Save the importer portion below as /tmp/reference-import-probe.mts, then run it from the checkout with its locked dependencies and a working platform binary for tsx:

import { mkdir, writeFile } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
const repo = resolve(process.argv[2]);
const root = resolve(process.argv[3]);
await mkdir(root, { recursive: true });
const files = {
  'literal-control.mjs': 'export const load = () => import("./dep.mjs");\n',
  'template.mjs': 'export const load = () => import(`./dep.mjs`);\n',
  'cast.ts': 'export const load = () => import("./dep.mjs" as const);\n',
  'exported.cts': 'export import dep = require("./dep.cjs");\n',
  'plain.cts': 'import dep = require("./dep.cjs"); export { dep };\n',
  'query.ts': 'export type Alias = import("./types").Thing;\n',
  'type-control.ts': 'import type { Thing } from "./types"; export type Alias = Thing;\n',
  'dep.mjs': 'export const value = 1;\n',
  'dep.cjs': 'module.exports = { value: 1 };\n',
  'types.ts': 'export interface Thing { value: number }\n',
};
for (const [name, text] of Object.entries(files)) await writeFile(join(root, name), text);
const load = (name) => import(pathToFileURL(join(repo, 'src', name + '.ts')).href);
const { importReferenceSource } = await load('application/ReferenceSourceImport');
const { ArtifactResourceScope } = await load('artifacts/ArtifactResourceScope');
const resources = new ArtifactResourceScope();
try {
  const result = await importReferenceSource({
    root, caller: 'research-probe', policy: { secretPatterns: [] },
  }, resources);
  if (!result.ok) throw new Error(JSON.stringify(result.error));
  const graph = result.value;
  console.log(JSON.stringify({
    entries: graph.entries.map(({ path, content_state, limitations }) => ({ path, content_state, limitations })),
    relationships: graph.relationships, parse_failures: graph.parse_failures,
    exclusions: graph.exclusions, limitations: graph.limitations,
    inventory_state: graph.inventory_state,
  }));
} finally { await resources.close(); }
work="$(mktemp -d /tmp/reference-import.XXXXXX)"
node --import "$PWD/node_modules/tsx/dist/loader.mjs" \
  /tmp/reference-import-probe.mts "$PWD" "$work/source"

Only source inspection runs; no fixture module is loaded or executed. The recorded graph preceded an unrelated module-not-found failure in an optional compiler control after this importer portion. The claim here is the observed importer result, not a clean whole-process or public CLI exit. No probe was rerun after this draft was written.

Expected behavior

Retain the external import-equals dependency when the same declaration is exported, preserving source hashes and its existing requires resolution semantics. The existing import-equals producer and application-boundary test already establish plain declaration support. Adding export must not erase that source dependency.

Impact and scope

A valid exported TypeScript declaration silently loses its dependency from historical evidence. This is bounded relationship incompleteness; source inventory remains available. Real-world prevalence and downstream comparison effects were not measured. Rewriting the declaration into the control form changes the analyzed source bytes and is not a demonstrated result-preserving workaround.

REA version

Package metadata 6.7.0; source revision 23def04096869b85c4d3c14a73ed09168483c9f6 with locked dependencies. Published tarball execution was not tested.

Runtime and platform

Node.js v24.22.1; Linux x64, kernel 7.2.8-9-omarchy; installed locked tsx with matching esbuild 0.28.2 supplied from a scratch dependency installation. No native provider or analyzed source execution. The separate emit-only control used esbuild 0.28.2 and exited 0 without warnings.

Target and artifact context

Benign UTF-8 source tree shown above. Relevant files are exported.cts, plain.cts, and dep.cjs; other files are independent syntax controls in the same executed import.

Interface

Other

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

The complete returned requires relationship list contains only:

[
  {"from_path":"plain.cts","to":"dep.cjs","kind":"requires","resolution":"internal","parse_state":"parsed"}
]

exported.cts, plain.cts, and dep.cjs all appear as hashed files with limitations: []; parse_failures and exclusions are []. The emit-only control reports no warnings and retains require("./dep.cjs") in both outputs.

Evidence provenance: executed production importer plus an independent compiler emission control. CLI/MCP, published-package execution, other platforms, and downstream comparison were not tested.

Typed outcome or recovery expected

Add the parsed internal requires edge from exported.cts to dep.cjs; retain the existing edge from plain.cts and all original hashes. Omitting the exported declaration without an unavailable-coverage diagnostic does not preserve the same source contract.

Analysis or suspected cause

Source-derived: declaration extraction handles top-level export declarations only when they carry a source, then continues without examining their nested declaration. The separate TSImportEqualsDeclaration case is reached only for direct body members. The runtime-call traversal does not handle TSExternalModuleReference nodes. This leaves the exported form without a producer.

Additional context

Closed #2058 concerns analyze-javascript-application and plain import-equals source/module evidence. Closed #3932 concerns erased type-only dependencies in source-map originals. Here the historical importer already resolves the plain control but silently omits its exported counterpart. Bounded searches of open/closed issues and PRs found no matching historical-import report; the duplicate search was refreshed immediately before submission. Last known good version: NOT_ENOUGH_INFO; no regression range was established.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: An executed valid TypeScript fixture silently omits a required historical dependency while retaining its files. No severe downstream result or broad workflow failure was established.
  • Workaround status: unverified
  • Workaround basis: The equivalent separate-export form resolves, but rewriting reference input changes its source identity. No REA workflow restoring the graph while preserving those bytes was tested.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6655 ## 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 Observed at the production `importReferenceSource` application boundary: `export import dep = require("./dep.cjs");` in `exported.cts` produces no relationship. Its plain import-equals control, followed by `export { dep }`, produces the expected parsed internal `requires` edge to `dep.cjs`. Both source files and the dependency are hashed, with no entry limitations, parse failures, or exclusions. A separate emit-only control using installed esbuild 0.28.2 accepted both forms without warnings and emitted `const dep = require("./dep.cjs");` for both. Emitted code was not executed. ## Reproduction The production importer completed this ten-file probe once. Save the importer portion below as `/tmp/reference-import-probe.mts`, then run it from the checkout with its locked dependencies and a working platform binary for `tsx`: ```typescript import { mkdir, writeFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; const repo = resolve(process.argv[2]); const root = resolve(process.argv[3]); await mkdir(root, { recursive: true }); const files = { 'literal-control.mjs': 'export const load = () => import("./dep.mjs");\n', 'template.mjs': 'export const load = () => import(`./dep.mjs`);\n', 'cast.ts': 'export const load = () => import("./dep.mjs" as const);\n', 'exported.cts': 'export import dep = require("./dep.cjs");\n', 'plain.cts': 'import dep = require("./dep.cjs"); export { dep };\n', 'query.ts': 'export type Alias = import("./types").Thing;\n', 'type-control.ts': 'import type { Thing } from "./types"; export type Alias = Thing;\n', 'dep.mjs': 'export const value = 1;\n', 'dep.cjs': 'module.exports = { value: 1 };\n', 'types.ts': 'export interface Thing { value: number }\n', }; for (const [name, text] of Object.entries(files)) await writeFile(join(root, name), text); const load = (name) => import(pathToFileURL(join(repo, 'src', name + '.ts')).href); const { importReferenceSource } = await load('application/ReferenceSourceImport'); const { ArtifactResourceScope } = await load('artifacts/ArtifactResourceScope'); const resources = new ArtifactResourceScope(); try { const result = await importReferenceSource({ root, caller: 'research-probe', policy: { secretPatterns: [] }, }, resources); if (!result.ok) throw new Error(JSON.stringify(result.error)); const graph = result.value; console.log(JSON.stringify({ entries: graph.entries.map(({ path, content_state, limitations }) => ({ path, content_state, limitations })), relationships: graph.relationships, parse_failures: graph.parse_failures, exclusions: graph.exclusions, limitations: graph.limitations, inventory_state: graph.inventory_state, })); } finally { await resources.close(); } ``` ```sh work="$(mktemp -d /tmp/reference-import.XXXXXX)" node --import "$PWD/node_modules/tsx/dist/loader.mjs" \ /tmp/reference-import-probe.mts "$PWD" "$work/source" ``` Only source inspection runs; no fixture module is loaded or executed. The recorded graph preceded an unrelated module-not-found failure in an optional compiler control after this importer portion. The claim here is the observed importer result, not a clean whole-process or public CLI exit. No probe was rerun after this draft was written. ## Expected behavior Retain the external import-equals dependency when the same declaration is exported, preserving source hashes and its existing `requires` resolution semantics. The [existing import-equals producer](https://github.com/morluto/rea/blob/23def04096869b85c4d3c14a73ed09168483c9f6/src/domain/referenceSourceImportParsing.ts#L184) and [application-boundary test](https://github.com/morluto/rea/blob/23def04096869b85c4d3c14a73ed09168483c9f6/src/application/ReferenceSourceImportEntries.test.ts#L156) already establish plain declaration support. Adding `export` must not erase that source dependency. ## Impact and scope A valid exported TypeScript declaration silently loses its dependency from historical evidence. This is bounded relationship incompleteness; source inventory remains available. Real-world prevalence and downstream comparison effects were not measured. Rewriting the declaration into the control form changes the analyzed source bytes and is not a demonstrated result-preserving workaround. ## REA version Package metadata 6.7.0; source revision `23def04096869b85c4d3c14a73ed09168483c9f6` with locked dependencies. Published tarball execution was not tested. ## Runtime and platform Node.js v24.22.1; Linux x64, kernel `7.2.8-9-omarchy`; installed locked `tsx` with matching esbuild 0.28.2 supplied from a scratch dependency installation. No native provider or analyzed source execution. The separate emit-only control used esbuild 0.28.2 and exited 0 without warnings. ## Target and artifact context Benign UTF-8 source tree shown above. Relevant files are `exported.cts`, `plain.cts`, and `dep.cjs`; other files are independent syntax controls in the same executed import. ## Interface Other ## Earliest failure stage Artifact or evidence registration ## Relevant output or evidence The complete returned `requires` relationship list contains only: ```json [ {"from_path":"plain.cts","to":"dep.cjs","kind":"requires","resolution":"internal","parse_state":"parsed"} ] ``` `exported.cts`, `plain.cts`, and `dep.cjs` all appear as hashed files with `limitations: []`; `parse_failures` and `exclusions` are `[]`. The emit-only control reports no warnings and retains `require("./dep.cjs")` in both outputs. Evidence provenance: executed production importer plus an independent compiler emission control. CLI/MCP, published-package execution, other platforms, and downstream comparison were not tested. ## Typed outcome or recovery expected Add the parsed internal `requires` edge from `exported.cts` to `dep.cjs`; retain the existing edge from `plain.cts` and all original hashes. Omitting the exported declaration without an unavailable-coverage diagnostic does not preserve the same source contract. ## Analysis or suspected cause Source-derived: [declaration extraction](https://github.com/morluto/rea/blob/23def04096869b85c4d3c14a73ed09168483c9f6/src/domain/referenceSourceImportParsing.ts#L145) handles top-level export declarations only when they carry a `source`, then continues without examining their nested declaration. The separate `TSImportEqualsDeclaration` case is reached only for direct body members. The runtime-call traversal does not handle `TSExternalModuleReference` nodes. This leaves the exported form without a producer. ## Additional context Closed [#2058](https://github.com/morluto/rea/issues/2058) concerns `analyze-javascript-application` and plain import-equals source/module evidence. Closed [#3932](https://github.com/morluto/rea/issues/3932) concerns erased type-only dependencies in source-map originals. Here the historical importer already resolves the plain control but silently omits its exported counterpart. Bounded searches of open/closed issues and PRs found no matching historical-import report; the duplicate search was refreshed immediately before submission. Last known good version: NOT_ENOUGH_INFO; no regression range was established. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: An executed valid TypeScript fixture silently omits a required historical dependency while retaining its files. No severe downstream result or broad workflow failure was established. - Workaround status: unverified - Workaround basis: The equivalent separate-export form resolves, but rewriting reference input changes its source identity. No REA workflow restoring the graph while preserving those bytes was tested.
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#4033
No description provided.