[GH-ISSUE #1488] fix(artifacts): join logical paths with the inventory NFC normalizer #507

Closed
opened 2026-10-15 18:25:41 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @cursor[bot] on GitHub (Oct 10, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1488

TL;DR: Inventory logical paths are NFC. Runtime reconciliation, HTML/module resolution, and reference-source imports join those paths with a second normalizer that does not apply NFC, so the same filename can fail to match.

This is the same class of bug as #1470: each boundary invented its own identity rule for one logical path.

Area

Evidence, artifacts, or reconstruction

Observed behavior

normalizeArtifactPath in src/artifacts/ArtifactPaths.ts NFC-normalizes every inventoried logical path. Directory names from macOS (often NFD) and ZIP names stored in NFD therefore land in the inventory as NFC.

Three other joins do not use that function:

  • mappedRuntimePaths / pathBelowRoot in src/domain/javascript/javascriptRuntimeStaticCandidates.ts relativizes a runtime file path and then calls a local normalizeArtifactPath. That copy is posix.normalize only. It returns the NFD spelling. pathsByNodeId is keyed by the graph's NFC identity.path, so reconcile_javascript_runtime reports the module unmatched.
  • confineCandidate in src/application/javascript/JavaScriptArtifactPathResolution.ts resolves HTML and module candidates with posix.normalize and looks them up in the inventory file map. An NFD reference to an NFC inventory path is not found.
  • resolveInternalSpecifier in src/application/ReferenceSourceImportEntries.ts uses posix.normalize against the source path set, with the same miss.

A fourth policy shows up in search. javascriptFeatureSeed compares seed text with NFKC, and with English (en-US) case folding when the seed is case-insensitive. That is a query fold. It is not the inventory path identity, and a compatibility character can match a path field the inventory keeps distinct.

Keyed-archive selection already compares both sides after NFC (src/artifacts/apple/KeyedArchiveReader.ts). These other joins never reached that boundary.

Expected behavior

Every join against an inventoried logical path should use the inventory normalizer, so NFC is applied once at the owning boundary. Query folding for feature seeds should stay a query, and should not be reused as path identity.

Where

  • src/artifacts/ArtifactPaths.ts — normalizeArtifactPath
  • src/domain/javascript/javascriptRuntimeStaticCandidates.ts — pathBelowRoot, local normalizeArtifactPath
  • src/application/javascript/JavaScriptArtifactPathResolution.ts — confineCandidate
  • src/application/ReferenceSourceImportEntries.ts — resolveInternalSpecifier
  • src/domain/javascript/javascriptFeatureSeed.ts — normalized
Originally created by @cursor[bot] on GitHub (Oct 10, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1488 **TL;DR:** Inventory logical paths are NFC. Runtime reconciliation, HTML/module resolution, and reference-source imports join those paths with a second normalizer that does not apply NFC, so the same filename can fail to match. This is the same class of bug as #1470: each boundary invented its own identity rule for one logical path. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `normalizeArtifactPath` in `src/artifacts/ArtifactPaths.ts` NFC-normalizes every inventoried logical path. Directory names from macOS (often NFD) and ZIP names stored in NFD therefore land in the inventory as NFC. Three other joins do not use that function: - `mappedRuntimePaths` / `pathBelowRoot` in `src/domain/javascript/javascriptRuntimeStaticCandidates.ts` relativizes a runtime file path and then calls a local `normalizeArtifactPath`. That copy is `posix.normalize` only. It returns the NFD spelling. `pathsByNodeId` is keyed by the graph's NFC `identity.path`, so `reconcile_javascript_runtime` reports the module unmatched. - `confineCandidate` in `src/application/javascript/JavaScriptArtifactPathResolution.ts` resolves HTML and module candidates with `posix.normalize` and looks them up in the inventory file map. An NFD reference to an NFC inventory path is not found. - `resolveInternalSpecifier` in `src/application/ReferenceSourceImportEntries.ts` uses `posix.normalize` against the source path set, with the same miss. A fourth policy shows up in search. `javascriptFeatureSeed` compares seed text with NFKC, and with English (`en-US`) case folding when the seed is case-insensitive. That is a query fold. It is not the inventory path identity, and a compatibility character can match a path field the inventory keeps distinct. Keyed-archive selection already compares both sides after NFC (`src/artifacts/apple/KeyedArchiveReader.ts`). These other joins never reached that boundary. ### Expected behavior Every join against an inventoried logical path should use the inventory normalizer, so NFC is applied once at the owning boundary. Query folding for feature seeds should stay a query, and should not be reused as path identity. ### Where - `src/artifacts/ArtifactPaths.ts` — `normalizeArtifactPath` - `src/domain/javascript/javascriptRuntimeStaticCandidates.ts` — `pathBelowRoot`, local `normalizeArtifactPath` - `src/application/javascript/JavaScriptArtifactPathResolution.ts` — `confineCandidate` - `src/application/ReferenceSourceImportEntries.ts` — `resolveInternalSpecifier` - `src/domain/javascript/javascriptFeatureSeed.ts` — `normalized`
bsakaguchi 2026-10-15 18:25:41 +00:00
Author
Owner

@anujsingh-cse commented on GitHub (Oct 10, 2026):

I'd like to take this one. Plan: route the three inventory joins (the local
ormalizeArtifactPath\ copy in \javascriptRuntimeStaticCandidates.ts, \confineCandidate\ in \JavaScriptArtifactPathResolution.ts, and
esolveInternalSpecifier\ in \ReferenceSourceImportEntries.ts) through the NFC normalizer that owns logical-path identity, leave the feature-seed NFKC query fold as a query, and add focused NFD-vs-NFC path fixtures for each boundary.

<!-- gh-comment-id:6096048074 --> @anujsingh-cse commented on GitHub (Oct 10, 2026): I'd like to take this one. Plan: route the three inventory joins (the local \ ormalizeArtifactPath\ copy in \javascriptRuntimeStaticCandidates.ts\, \confineCandidate\ in \JavaScriptArtifactPathResolution.ts\, and \ esolveInternalSpecifier\ in \ReferenceSourceImportEntries.ts\) through the NFC normalizer that owns logical-path identity, leave the feature-seed NFKC query fold as a query, and add focused NFD-vs-NFC path fixtures for each boundary.
Author
Owner

@anujsingh-cse commented on GitHub (Oct 10, 2026):

Update after taking this: the three joins the Where list names are already routed through
ormalizeJoinedLogicalPath\ on current main (that landed with #1513), so the described runtime-reconciliation miss, the HTML/module lookup, and the import resolution are NFC-consistent today.

While verifying, I found one more join of the same class the Where list missed: \checkSymlinks\ in \src/domain/referenceSourceGraph.ts\ resolved internal symlink targets with bare \posix.normalize\ against the inventoried path set, so an NFD-spelled target (macOS
eadlink\ commonly reports NFD) failed validation against the NFC inventory. Fixed in #1535 with red/green NFD fixtures. The feature-seed NFKC query fold stays a query, as the issue specifies.

<!-- gh-comment-id:6096584075 --> @anujsingh-cse commented on GitHub (Oct 10, 2026): Update after taking this: the three joins the Where list names are already routed through \ ormalizeJoinedLogicalPath\ on current main (that landed with #1513), so the described runtime-reconciliation miss, the HTML/module lookup, and the import resolution are NFC-consistent today. While verifying, I found one more join of the same class the Where list missed: \checkSymlinks\ in \src/domain/referenceSourceGraph.ts\ resolved internal symlink targets with bare \posix.normalize\ against the inventoried path set, so an NFD-spelled target (macOS \ eadlink\ commonly reports NFD) failed validation against the NFC inventory. Fixed in #1535 with red/green NFD fixtures. The feature-seed NFKC query fold stays a query, as the issue specifies.
Author
Owner

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

Thanks for reporting this. Closing as resolved on main. Artifact/runtime/HTML joins use the shared logical-path normalizer. Reference-source joins preserve exact source spelling and use unambiguous NFC fallback through the shared lookup.

Merged fix; reference-source identity consolidation. I checked the current source against this report; no tests or real-provider workflows were run for this closure pass.

<!-- gh-comment-id:6099805038 --> @morluto commented on GitHub (Oct 10, 2026): Thanks for reporting this. Closing as resolved on main. Artifact/runtime/HTML joins use the shared logical-path normalizer. Reference-source joins preserve exact source spelling and use unambiguous NFC fallback through the shared lookup. [Merged fix](https://github.com/morluto/rea/commit/b4db35f092d7166aebdf4387b48f7652b1d1c3b6); [reference-source identity consolidation](https://github.com/morluto/rea/commit/2ca3ad238608adfca80a7ff1aaadea6e59a58225). I checked the current source against this report; no tests or real-provider workflows were run for this closure pass.
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#507
No description provided.