[GH-ISSUE #2663] fix(javascript): preserve callback metadata from repeated chunk registrations #965

Closed
opened 2026-10-15 18:29:57 +00:00 by bsakaguchi · 0 comments
Owner

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

Summary

Static JavaScript application analysis loses entry-module and async-chunk facts when one source asset contains two recognized chunk registrations with the same runtime, chunk IDs and factory text but different runtime callbacks. The application graph retains the first callback's facts and relationships while reporting complete coverage. This was reproduced through source parsing and the production inventory-to-graph components on owned fixtures.

Steps to reproduce

Use the source baseline under Runtime or environment with its pinned dependencies available. This is the reduced input observed in the source graph integration check:

(globalThis.webpackChunkFixture = globalThis.webpackChunkFixture || []).push([[1], {"a": function() {}}, function(__webpack_require__) { __webpack_require__(7); __webpack_require__.e("chunk-a"); }]);
(globalThis.webpackChunkFixture = globalThis.webpackChunkFixture || []).push([[1], {"a": function() {}}, function(__webpack_require__) { __webpack_require__(8); __webpack_require__.e("chunk-b"); }]);

For a public workflow reproduction, place those two lines in bundle.js inside an otherwise empty local directory, then run the following command with that directory's absolute path. This CLI invocation was not executed during this investigation; the source producer and production graph construction were executed directly.

rea analyze-javascript-application "$fixture_dir" --artifact-format directory --json > evidence.json
python3 - <<'PYCODE'
import json
with open("evidence.json") as stream:
    graph = json.load(stream)["normalized_result"]["graph"]
for node in graph["nodes"]:
    for observation in node["observations"]:
        p = observation.get("properties", {})
        if p.get("runtime") == "webpackChunkFixture" and "chunk_keys" in p:
            print({"entry_module_keys": p["entry_module_keys"],
                   "async_chunk_keys": p["async_chunk_keys"]})
for edge in graph["edges"]:
    p = edge.get("properties", {})
    if p.get("kind") in ("bundler-entry-module", "bundler-async-chunk"):
        print(p)
print(graph["coverage"])
PYCODE

Keep evidence.json outside the analyzed directory. The missing entries are unresolved references in this small fixture, and should still appear as recovered facts and unresolved relationships.

A source check can reproduce the producer's loss without a compiled CLI. Save the following as src/domain/javascript/registrationMetadata.test.ts in a disposable checkout and run npm run test:local -- src/domain/javascript/registrationMetadata.test.ts. It expresses the restoration expectation and fails on the observed baseline. The investigation's focused tests exercised the same producer, individual statements, reversed registration order, identical repetition, and differing chunk-ID controls.

import { expect, it } from "vitest";
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";

it("preserves facts from different runtime callbacks", () => {
  const first = '(globalThis.webpackChunkFixture = globalThis.webpackChunkFixture || []).push([[1], {"a": function() {}}, function(__webpack_require__) { __webpack_require__(7); __webpack_require__.e("chunk-a"); }]);';
  const second = '(globalThis.webpackChunkFixture = globalThis.webpackChunkFixture || []).push([[1], {"a": function() {}}, function(__webpack_require__) { __webpack_require__(8); __webpack_require__.e("chunk-b"); }]);';
  const result = analyzeJavaScriptStaticSource(first + "\n" + second);
  expect(result.parse_status).toBe("complete");
  const registrations = result.bundler_registrations;
  console.log(registrations.map(({ entry_module_keys, async_chunk_keys }) =>
    ({ entry_module_keys, async_chunk_keys })));
  expect([...new Set(registrations.flatMap(r => r.entry_module_keys))].sort())
    .toEqual(["7", "8"]);
  expect([...new Set(registrations.flatMap(r => r.async_chunk_keys))].sort())
    .toEqual(["chunk-a", "chunk-b"]);
});

Expected behavior

Preserve the statically recovered facts from both admitted registrations in the application graph. Entry-module keys 7 and 8 and async-chunk keys chunk-a and chunk-b should remain inspectable, with their corresponding unresolved relationships. The registrations may remain separate or share a chunk node with distinct observations. This expectation concerns source observations, not whether a running bundler would invoke both callbacks.

An identical repeated registration should continue to deduplicate. Each statement alone must retain its own facts, and changing only the chunk ID of the second statement must continue to preserve both sets.

Actual behavior

The executed source integration check returned one registration observation and only the first callback's two runtime relationships. The relevant output was:

{
  "graph_chunks": [{"entry_module_keys":["7"],"async_chunk_keys":["chunk-a"]}],
  "graph_runtime_edges": [
    {"kind":"bundler-entry-module","module_key":"7"},
    {"kind":"bundler-async-chunk","chunk_key":"chunk-a"}
  ],
  "graph_coverage": {"status":"complete","truncated":false,"omitted_count":0,"limits":[]}
}

Four focused producer cases tested Webpack and Rspack runtime names with entry and async differences separately. Every source parsed completely. Each statement alone retained its key; concatenating them retained only the first key; reversing them retained only the other key. Identical repetition yielded one registration, and using different chunk IDs preserved both. Reducing the empty factory to function() {} kept the failure. A fifth source integration case inventoried and read the one-file directory, parsed and projected its source, and built the validated application graph shown above. The final focused run passed all five observation checks and exited 0.

Runtime or environment

Tested source checkout at a2d8b94dd5 using Node.js 24.18.0 and npm 11.16.0. The artifact is a local directory containing one inert .js asset. No published-package, compiled CLI or MCP transport execution was performed; the public route was traced in source. No Webpack/Rspack compiler or native analysis provider was needed.

Rechecked current upstream at 3ae826ceba by source comparison: registration recovery, both registration-deduplication steps and the affected graph projection remain unchanged; no new runtime reproduction was run.

Evidence

  • Expected source: docs/javascript-artifact-reconstruction.md:1-57,216-221 documents directory analysis and recovery of Webpack/Rspack AST-literal registrations. src/domain/javascript/javascriptStaticAnalysisTypes.ts:81-94 defines their recovered entry and async metadata, which src/application/javascript/JavaScriptArtifactGraphBundlers.ts:89-110,186-260 emits as graph observations and relationships.
  • Failure source: The executed producer and inventory-to-graph results above. src/domain/javascript/javascriptStaticAnalysisBundler.ts:42-88 recovers callback metadata before suppressing registrations by registrationKey; src/domain/javascript/javascriptStaticAnalysisHelpers.ts:685-698 keys only runtime, chunk IDs, unknown chunk count and module key/digest pairs. src/domain/javascript/javascriptStaticAnalysisFindings.ts:116-123 retains the first key. Finalization in src/domain/javascript/javascriptStaticAnalysis.ts also deduplicates using that key.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The source integration used the production artifact inventory, directory reader, cooperative JavaScript analysis and graph builder. It bypassed the process worker and adapter transports. The ordinary .js input passes inventory/text inclusion and the runtime-name, array chunk-ID, object table, literal factory and callback-parameter guards. No parser errors or resource truncation explain the loss.

Restoration check

Analyze the two-statement fixture and retain both entry keys and both async keys in the recovered chunk observations and corresponding unresolved graph relationships. Reverse the statements and verify that the recovered facts remain the same. Run each statement alone, an identical repeated statement, and a second registration with a distinct chunk ID as controls. Preserve deduplication for genuinely identical registrations and the input's source provenance. Confirm the result through the public CLI or MCP workflow after the source correction.

Additional context

Related: #1576. The closed cross-runtime module lookup report was checked with its comments. It concerns routing module relationships across different runtimes while retaining both factory sets. This failure drops callback metadata before graph composition even within one runtime. The current open/closed issue inventories and the earlier same-run draft search contained no exact matching failure.

Related: #2624 concerns differing WebMCP declaration metadata in captured-script analysis. Its declaration-inventory contract is distinct from chunk-registration metadata and graph relationships.

Only literal push registrations were verified. Standalone bootstrap keys include source offsets and were not shown to collide. Unknown callback-key counts were not tested, and no production incidence was measured.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Admitted registrations lose recovered metadata and graph relationships while analysis reports complete coverage. This is a consequential bounded analysis defect; no full analysis outage or production frequency was established.
  • Workaround status: unverified
  • Workaround basis: No public registration-deduplication override was found. Splitting statements into separate artifacts may avoid the per-source collision, but preserving the same artifact identity, source locations and graph result through that approach was not demonstrated.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2663 ## Summary Static JavaScript application analysis loses entry-module and async-chunk facts when one source asset contains two recognized chunk registrations with the same runtime, chunk IDs and factory text but different runtime callbacks. The application graph retains the first callback's facts and relationships while reporting complete coverage. This was reproduced through source parsing and the production inventory-to-graph components on owned fixtures. ## Steps to reproduce Use the source baseline under Runtime or environment with its pinned dependencies available. This is the reduced input observed in the source graph integration check: ```js (globalThis.webpackChunkFixture = globalThis.webpackChunkFixture || []).push([[1], {"a": function() {}}, function(__webpack_require__) { __webpack_require__(7); __webpack_require__.e("chunk-a"); }]); (globalThis.webpackChunkFixture = globalThis.webpackChunkFixture || []).push([[1], {"a": function() {}}, function(__webpack_require__) { __webpack_require__(8); __webpack_require__.e("chunk-b"); }]); ``` For a public workflow reproduction, place those two lines in `bundle.js` inside an otherwise empty local directory, then run the following command with that directory's absolute path. This CLI invocation was not executed during this investigation; the source producer and production graph construction were executed directly. ```sh rea analyze-javascript-application "$fixture_dir" --artifact-format directory --json > evidence.json python3 - <<'PYCODE' import json with open("evidence.json") as stream: graph = json.load(stream)["normalized_result"]["graph"] for node in graph["nodes"]: for observation in node["observations"]: p = observation.get("properties", {}) if p.get("runtime") == "webpackChunkFixture" and "chunk_keys" in p: print({"entry_module_keys": p["entry_module_keys"], "async_chunk_keys": p["async_chunk_keys"]}) for edge in graph["edges"]: p = edge.get("properties", {}) if p.get("kind") in ("bundler-entry-module", "bundler-async-chunk"): print(p) print(graph["coverage"]) PYCODE ``` Keep `evidence.json` outside the analyzed directory. The missing entries are unresolved references in this small fixture, and should still appear as recovered facts and unresolved relationships. A source check can reproduce the producer's loss without a compiled CLI. Save the following as `src/domain/javascript/registrationMetadata.test.ts` in a disposable checkout and run `npm run test:local -- src/domain/javascript/registrationMetadata.test.ts`. It expresses the restoration expectation and fails on the observed baseline. The investigation's focused tests exercised the same producer, individual statements, reversed registration order, identical repetition, and differing chunk-ID controls. ```ts import { expect, it } from "vitest"; import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js"; it("preserves facts from different runtime callbacks", () => { const first = '(globalThis.webpackChunkFixture = globalThis.webpackChunkFixture || []).push([[1], {"a": function() {}}, function(__webpack_require__) { __webpack_require__(7); __webpack_require__.e("chunk-a"); }]);'; const second = '(globalThis.webpackChunkFixture = globalThis.webpackChunkFixture || []).push([[1], {"a": function() {}}, function(__webpack_require__) { __webpack_require__(8); __webpack_require__.e("chunk-b"); }]);'; const result = analyzeJavaScriptStaticSource(first + "\n" + second); expect(result.parse_status).toBe("complete"); const registrations = result.bundler_registrations; console.log(registrations.map(({ entry_module_keys, async_chunk_keys }) => ({ entry_module_keys, async_chunk_keys }))); expect([...new Set(registrations.flatMap(r => r.entry_module_keys))].sort()) .toEqual(["7", "8"]); expect([...new Set(registrations.flatMap(r => r.async_chunk_keys))].sort()) .toEqual(["chunk-a", "chunk-b"]); }); ``` ## Expected behavior Preserve the statically recovered facts from both admitted registrations in the application graph. Entry-module keys `7` and `8` and async-chunk keys `chunk-a` and `chunk-b` should remain inspectable, with their corresponding unresolved relationships. The registrations may remain separate or share a chunk node with distinct observations. This expectation concerns source observations, not whether a running bundler would invoke both callbacks. An identical repeated registration should continue to deduplicate. Each statement alone must retain its own facts, and changing only the chunk ID of the second statement must continue to preserve both sets. ## Actual behavior The executed source integration check returned one registration observation and only the first callback's two runtime relationships. The relevant output was: ```json { "graph_chunks": [{"entry_module_keys":["7"],"async_chunk_keys":["chunk-a"]}], "graph_runtime_edges": [ {"kind":"bundler-entry-module","module_key":"7"}, {"kind":"bundler-async-chunk","chunk_key":"chunk-a"} ], "graph_coverage": {"status":"complete","truncated":false,"omitted_count":0,"limits":[]} } ``` Four focused producer cases tested Webpack and Rspack runtime names with entry and async differences separately. Every source parsed completely. Each statement alone retained its key; concatenating them retained only the first key; reversing them retained only the other key. Identical repetition yielded one registration, and using different chunk IDs preserved both. Reducing the empty factory to `function() {}` kept the failure. A fifth source integration case inventoried and read the one-file directory, parsed and projected its source, and built the validated application graph shown above. The final focused run passed all five observation checks and exited 0. ## Runtime or environment Tested source checkout at a2d8b94dd50dab8d966239dd63ab84718aa8b4b4 using Node.js 24.18.0 and npm 11.16.0. The artifact is a local directory containing one inert `.js` asset. No published-package, compiled CLI or MCP transport execution was performed; the public route was traced in source. No Webpack/Rspack compiler or native analysis provider was needed. Rechecked current upstream at 3ae826ceba6f25b08bce50bcb51001209bc16b57 by source comparison: registration recovery, both registration-deduplication steps and the affected graph projection remain unchanged; no new runtime reproduction was run. ## Evidence - Expected source: `docs/javascript-artifact-reconstruction.md:1-57,216-221` documents directory analysis and recovery of Webpack/Rspack AST-literal registrations. `src/domain/javascript/javascriptStaticAnalysisTypes.ts:81-94` defines their recovered entry and async metadata, which `src/application/javascript/JavaScriptArtifactGraphBundlers.ts:89-110,186-260` emits as graph observations and relationships. - Failure source: The executed producer and inventory-to-graph results above. `src/domain/javascript/javascriptStaticAnalysisBundler.ts:42-88` recovers callback metadata before suppressing registrations by `registrationKey`; `src/domain/javascript/javascriptStaticAnalysisHelpers.ts:685-698` keys only runtime, chunk IDs, unknown chunk count and module key/digest pairs. `src/domain/javascript/javascriptStaticAnalysisFindings.ts:116-123` retains the first key. Finalization in `src/domain/javascript/javascriptStaticAnalysis.ts` also deduplicates using that key. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The source integration used the production artifact inventory, directory reader, cooperative JavaScript analysis and graph builder. It bypassed the process worker and adapter transports. The ordinary `.js` input passes inventory/text inclusion and the runtime-name, array chunk-ID, object table, literal factory and callback-parameter guards. No parser errors or resource truncation explain the loss. ## Restoration check Analyze the two-statement fixture and retain both entry keys and both async keys in the recovered chunk observations and corresponding unresolved graph relationships. Reverse the statements and verify that the recovered facts remain the same. Run each statement alone, an identical repeated statement, and a second registration with a distinct chunk ID as controls. Preserve deduplication for genuinely identical registrations and the input's source provenance. Confirm the result through the public CLI or MCP workflow after the source correction. ## Additional context Related: #1576. The closed [cross-runtime module lookup report](https://github.com/morluto/rea/issues/1576) was checked with its comments. It concerns routing module relationships across different runtimes while retaining both factory sets. This failure drops callback metadata before graph composition even within one runtime. The current open/closed issue inventories and the earlier same-run draft search contained no exact matching failure. Related: #2624 concerns differing WebMCP declaration metadata in captured-script analysis. Its declaration-inventory contract is distinct from chunk-registration metadata and graph relationships. Only literal push registrations were verified. Standalone bootstrap keys include source offsets and were not shown to collide. Unknown callback-key counts were not tested, and no production incidence was measured. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Admitted registrations lose recovered metadata and graph relationships while analysis reports complete coverage. This is a consequential bounded analysis defect; no full analysis outage or production frequency was established. - Workaround status: unverified - Workaround basis: No public registration-deduplication override was found. Splitting statements into separate artifacts may avoid the per-source collision, but preserving the same artifact identity, source locations and graph result through that approach was not demonstrated.
bsakaguchi 2026-10-15 18:29:57 +00:00
  • closed this issue
  • added the
    bug
    label
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#965
No description provided.