Filed unassigned by the domain:cli dev seat implementing #14278 (session session_016yfqQh2dBgPAymYd7xipza, branch claude/issue-14278-aliased-install-host-importer), as an out-of-scope finding of that card. ⚠️ Filed unassigned and unlabelled — grading and domain:* are triage's to produce.
The residue
#14278 fixed the ALIAS blindness in createHostImporter's fallback finder (hostInstalledPackageDir, the leg that runs inside hostRequire.resolve's catch). The same blindness survives one leg over, on the path where the CJS resolve succeeds — the #13330 condition re-decision:
packages/types/src/node.ts
packageRootOf(resolvedFile, packageName) // walks up from the resolved entry
if (manifest.name === packageName) return dir;
packageName there is the DECLARATION KEY. For an aliased install:
{ "dependencies": { "foo": "npm:bar@1" } }hostRoot/node_modules/foo/package.json is named bar, so the walk never matches, packageRootOf returns undefined, esmEntryForDeclared returns undefined, and createHostImporter falls back to resolved — the entry the CJS resolver answered, i.e. the require condition.
⇒ For an aliased dual-published package the host importer silently keeps the pre-#13330 behaviour: it loads the CommonJS build while the caller's own ESM chain loads the import build, which is exactly the two-instances-of-one-package split #13330 exists to remove (a plugin registry, a singleton kernel, a module-level cache: one copy each).
Why this is NOT what #14278 fixed
#14278's ruled route was deliberately confined to the fallback finder, whose whole leg was a hard failure before #14041 — so a change there is strictly additive and cannot move a currently-succeeding load. This path is the opposite: the load SUCCEEDS today, and correcting the expectation here changes which file a working import() returns. That is a real blast radius and a decision of its own, which is why the implementing dev declined to fold it in and filed this instead.
The mechanism the fix would reuse already exists after #14278: the declaration is parsed for the manifest name it promises (npm:name@range, aliased workspace:name@range), and packageRootOf would take that expected name instead of the key. The judgment this card owes is whether changing a succeeding load's selected entry is right here, not how to compute the name.
Direction
Not an access-control consequence and nothing newly reachable: the wrong ENTRY of the RIGHT package is loaded, never another package. The damage is a duplicated module instance for aliased dual publishes, plus the import-condition contract silently not holding for them.
Re-check
git grep -n "manifest.name === packageName" -- packages/types/src/node.ts
Reverse-check any zero against a term known present in the same file (hostRequire.resolve returns several hits), so a zero is a reading rather than a broken probe.
Refs
Filed unassigned by the⚠️ Filed unassigned and unlabelled — grading and
domain:clidev seat implementing #14278 (sessionsession_016yfqQh2dBgPAymYd7xipza, branchclaude/issue-14278-aliased-install-host-importer), as an out-of-scope finding of that card.domain:*are triage's to produce.The residue
#14278 fixed the ALIAS blindness in
createHostImporter's fallback finder (hostInstalledPackageDir, the leg that runs insidehostRequire.resolve's catch). The same blindness survives one leg over, on the path where the CJS resolve succeeds — the #13330 condition re-decision:packageNamethere is the DECLARATION KEY. For an aliased install:{ "dependencies": { "foo": "npm:bar@1" } }hostRoot/node_modules/foo/package.jsonis namedbar, so the walk never matches,packageRootOfreturnsundefined,esmEntryForDeclaredreturnsundefined, andcreateHostImporterfalls back toresolved— the entry the CJS resolver answered, i.e. therequirecondition.⇒ For an aliased dual-published package the host importer silently keeps the pre-#13330 behaviour: it loads the CommonJS build while the caller's own ESM chain loads the
importbuild, which is exactly the two-instances-of-one-package split #13330 exists to remove (a plugin registry, a singleton kernel, a module-level cache: one copy each).Why this is NOT what #14278 fixed
#14278's ruled route was deliberately confined to the fallback finder, whose whole leg was a hard failure before #14041 — so a change there is strictly additive and cannot move a currently-succeeding load. This path is the opposite: the load SUCCEEDS today, and correcting the expectation here changes which file a working
import()returns. That is a real blast radius and a decision of its own, which is why the implementing dev declined to fold it in and filed this instead.The mechanism the fix would reuse already exists after #14278: the declaration is parsed for the manifest name it promises (
npm:name@range, aliasedworkspace:name@range), andpackageRootOfwould take that expected name instead of the key. The judgment this card owes is whether changing a succeeding load's selected entry is right here, not how to compute the name.Direction
Not an access-control consequence and nothing newly reachable: the wrong ENTRY of the RIGHT package is loaded, never another package. The damage is a duplicated module instance for aliased dual publishes, plus the
import-condition contract silently not holding for them.Re-check
Reverse-check any zero against a term known present in the same file (
hostRequire.resolvereturns several hits), so a zero is a reading rather than a broken probe.Refs
"foo": "npm:bar@1") fails createHostImporter's manifest-name check, so an ESM-only aliased package keeps the wrong INSTALL wording #14278 / its PR — the alias fix on the fallback leg; this file's other manifest-name check.