Context
Found by the C1 content pre-read for #19047 AC-4 (receipt). neomjs/pages deploys by running npm run build-all with cwd: node_modules/neo.mjs (pages/buildScripts/updateNeoVersion.mjs:154-158) and exits on any non-zero status. With the engine packed from dev@526b65e0ee, the dist/esm step fails:
dist/esm: 3214 import(s) still address the workspace engine at node_modules/neo.mjs:
Every import in the tree is flagged, ./ siblings included: src 1,344, examples 1,090, apps 730, docs 47, ServiceWorker.mjs 3.
The guard came in with #17921 / #17932 (34499f737b, 2026-08-31). No release tag contains it, so 13.1.0's deploy never ran it. v13.2's deploy will.
The Problem
findUnresolvableImports (buildScripts/util/esmDistTransforms.mjs:450) fails an import when addressesSourceEngine(resolved) is true, and resolved is the import target's absolute path (buildScripts/build/esmodules.mjs:165). addressesSourceEngine matches any path containing /node_modules/neo.mjs/.
- In a workspace, that segment appears only when an import reaches the workspace's own engine: the two-graph defect the guard exists for.
- In an engine built in place inside a consumer's
node_modules/neo.mjs, the build root itself contains the segment, so every resolved path does.
The check is keyed to where the build runs, not to where the import points.
The Architectural Reality
esmodules.mjs sets root = path.resolve() (the cwd) and derives insideNeo from the package name. Pages' flow is insideNeo: true with root <pages>/node_modules/neo.mjs.
- The guard's second arm,
addressesSourceEngine(specifier), reads the specifier as written and does not depend on the root.
- The resolved-path arm still earns its place. It catches a specifier that names the engine only after normalisation (
node_modules/./neo.mjs/…). The text arm misses that form. rewriteImportPaths does too, because it keys on the literal /node_modules/neo.mjs/, so it leaves the import pointing at the workspace engine.
test/playwright/unit/buildScripts/esmDistTransforms.spec.mjs pins the pure function. esmWorkspaceBuild.spec.mjs drives the real esmodules.mjs over synthetic workspaces. None of those workspaces sits under a node_modules/neo.mjs path, which is why no spec saw this.
Measured A/B, same in-place tree (3,503 emitted modules): the 526b65e0ee guard exits 1 with 3,214 flagged. Judging the resolved path relative to the build root exits 0 in 3.35 s. With that one change, pages' full build-all exits 0 in 36 s.
The Fix
findUnresolvableImports(modules, exists, resolve, root) judges the resolved-path arm on path.relative(root, resolved). The root's own location is never evidence. Everything below and beside it is judged as before, so a hoisted ../node_modules/neo.mjs/… still matches.
esmodules.mjs passes its root.
- Specs:
esmDistTransforms: an in-place sibling import reports nothing (red on today's code).
- A control: an in-place import that really names
node_modules/neo.mjs still fails as engine-identity.
- A normalisation arm that pins why the resolved-path arm stays.
esmWorkspaceBuild: the real script, with cwd under …/node_modules/neo.mjs, exits 0 (red today).
Acceptance Criteria
Out of Scope
- The rest of #19047 AC-4: topology, devindex, and regenerating the portal indexes.
- Containment for in-place builds: an emitted import reaching the root's own
src/ outside dist/esm. It is not flagged before this change or after it.
Related
Sweeps
- Live latest-open sweep: latest 20 open issues at 2026-09-23T03:08Z. No equivalent.
- A2A in-flight sweep: last 15 inbox messages, all read-states. No claim on this scope.
- MC sweep:
dist/esm build engine-identity addressesSourceEngine node_modules/neo.mjs false positive build root inside node_modules pages build-all. 6 results, no prior decision.
- Own-assignment sweep: 9 open, none overlapping.
- Keyword sweep:
still address the workspace engine, addressesSourceEngine, engine-identity dist/esm. Hits are only #17921 (closed, the origin) and #19047.
Decision Record impact: none. Structure-map gate: N/A (buildScripts/, not ai/).
Origin Session ID: 19051047-2f38-4bf0-be51-efd6b957564d
Retrieval Hint: query_raw_memories("dist/esm engine-identity guard engine built in place node_modules/neo.mjs pages build-all")
Authored by 🖖 Grace · @neo-opus-grace · Claude Opus 5.5 · Claude Code
Context
Found by the C1 content pre-read for #19047 AC-4 (receipt).
neomjs/pagesdeploys by runningnpm run build-allwithcwd: node_modules/neo.mjs(pages/buildScripts/updateNeoVersion.mjs:154-158) and exits on any non-zero status. With the engine packed fromdev@526b65e0ee, thedist/esmstep fails:Every import in the tree is flagged,
./siblings included:src1,344,examples1,090,apps730,docs47,ServiceWorker.mjs3.The guard came in with #17921 / #17932 (
34499f737b, 2026-08-31). No release tag contains it, so 13.1.0's deploy never ran it. v13.2's deploy will.The Problem
findUnresolvableImports(buildScripts/util/esmDistTransforms.mjs:450) fails an import whenaddressesSourceEngine(resolved)is true, andresolvedis the import target's absolute path (buildScripts/build/esmodules.mjs:165).addressesSourceEnginematches any path containing/node_modules/neo.mjs/.node_modules/neo.mjs, the build root itself contains the segment, so every resolved path does.The check is keyed to where the build runs, not to where the import points.
The Architectural Reality
esmodules.mjssetsroot = path.resolve()(the cwd) and derivesinsideNeofrom the package name. Pages' flow isinsideNeo: truewith root<pages>/node_modules/neo.mjs.addressesSourceEngine(specifier), reads the specifier as written and does not depend on the root.node_modules/./neo.mjs/…). The text arm misses that form.rewriteImportPathsdoes too, because it keys on the literal/node_modules/neo.mjs/, so it leaves the import pointing at the workspace engine.test/playwright/unit/buildScripts/esmDistTransforms.spec.mjspins the pure function.esmWorkspaceBuild.spec.mjsdrives the realesmodules.mjsover synthetic workspaces. None of those workspaces sits under anode_modules/neo.mjspath, which is why no spec saw this.Measured A/B, same in-place tree (3,503 emitted modules): the
526b65e0eeguard exits 1 with 3,214 flagged. Judging the resolved path relative to the build root exits 0 in 3.35 s. With that one change, pages' fullbuild-allexits 0 in 36 s.The Fix
findUnresolvableImports(modules, exists, resolve, root)judges the resolved-path arm onpath.relative(root, resolved). The root's own location is never evidence. Everything below and beside it is judged as before, so a hoisted../node_modules/neo.mjs/…still matches.esmodules.mjspasses itsroot.esmDistTransforms: an in-place sibling import reports nothing (red on today's code).node_modules/neo.mjsstill fails asengine-identity.esmWorkspaceBuild: the real script, with cwd under…/node_modules/neo.mjs, exits 0 (red today).Acceptance Criteria
node_modules/neo.mjsdirectory buildsdist/esmwith exit 0 when its imports resolve. A real-script arm pins this and reds on today's code.node_modules/neo.mjsbelow or beside the root fails asengine-identity, including one that names it only after normalisation.main@1847ca65, engine packed from the fix head) runsbuild-allpast thedist/esmstep. Any later failure is reported on #19047 as a defect of its own.Out of Scope
src/outsidedist/esm. It is not flagged before this change or after it.Related
Sweeps
dist/esm build engine-identity addressesSourceEngine node_modules/neo.mjs false positive build root inside node_modules pages build-all. 6 results, no prior decision.still address the workspace engine,addressesSourceEngine,engine-identity dist/esm. Hits are only #17921 (closed, the origin) and #19047.Decision Record impact: none. Structure-map gate: N/A (
buildScripts/, notai/).Origin Session ID: 19051047-2f38-4bf0-be51-efd6b957564d
Retrieval Hint:
query_raw_memories("dist/esm engine-identity guard engine built in place node_modules/neo.mjs pages build-all")Authored by 🖖 Grace ·
@neo-opus-grace· Claude Opus 5.5 · Claude Code