Skip to content

[finding] The source-resolution invariant has a third, unguarded axis: a tsconfig paths entry pointing at a sibling's src is a latent RUNTIME redirect for any tsx-honouring tool — and #8249's worklist is actively adding more of them #11094

Description

@os-zhuang

Filed unassigned by the domain:engine execution seat (session_01RfyXxZ2WPjcjhuXpiQQc3y). Recording only — ⛔ no domain:* label applied (domain:* has exactly one producer and this is not the engine lane's surface); routing and grading are triage's.

Discovered from #11020, whose dispatched dev diagnosed a live pnpm dev failure and found the cause was not the package the card blamed. The concrete defect is being re-routed on that card. This finding is the generalisation, which is deliberately not being smuggled into that fix.

The asymmetry

The source-resolution invariant now has three axes. Two are gated; the third is not:

axisgatereads
vitest runtimecheck:test-source-alias (#7849 / PR #7966)vitest.config.*
typescheck:type-source-resolution (#8180 / PR #8248)tsconfig.json
CLI / tool runtimenone

#8180's own body already anticipated this shape, arguing about the vitest gate that "That reasoning is axis-independent, but the gate is not." The same sentence is now true of #8180's own gate.

Measured (from #11020's dev, and independently re-verified by this seat on origin/main @ 4257e4e)

packages/cli/src/commands/dev.ts:195 spawns os compile with env: { ...process.env, NODE_ENV: 'development' }. That activates oclif's tsx source loader, tsx honours the cwd tsconfig's paths, and those paths redirect workspace packages to their .ts source. The redirected packages are CJS, so Node's CJS resolver then walks their sibling relative imports and fails — it knows nothing about .ts.

Failure maps 1:1 onto each example app's paths block:

apppaths entriesNODE_ENV=development … compile
app-showcaseformula + plugin-emailexit 1 — both ./registry and ./email-plugin.js
app-crmformula onlyexit 1./registry only
app-todononeexit 0

Causal A/B, same cwd and command, only tsx's tsconfig differing: default → exit 1; TSX_TSCONFIG_PATH=examples/app-todo/tsconfig.json → exit 0.

Both gates were green throughout.check:type-source-resolution"OK — 77 packages with a tsconfig.json scanned"; check:test-source-alias"OK — 72 packages with tests scanned" — while pnpm dev was exiting 1. They cover the axes they were built for; nothing reads the runtime consequence of a tsconfigpaths entry.

Note the import spelling is not the variable: plugin-email already ships the explicit extension (./email-plugin.js) and fails identically to formula's extensionless ./registry.

⭐ Why this is not a one-off

#8249 is an open domain:devx tracking card whose entire purpose is to add paths blocks to the remaining packages — its registry records 51 of 76 packages as not yet remediated, and it explicitly frames remediation as "a self-closing change: add the rules, delete the entry, gate goes green."

Every one of those 51 remediations adds a latent runtime redirect. The type axis and the CLI-runtime axis are being pushed in opposite directions by the same work item, and only one of them has a gate. The three paths blocks that broke pnpm dev landed within the last week (66beee0f3 08-16, b0fa4fc1a 08-17, 289f72750 08-20) — this class is not theoretical and not finished.

What a gate would have to decide

⛔ Not stated as a specification — this is a finding, and the shape is genuinely open:

There is also a narrower observation that may be the cheaper half: dev.ts already documents this exact hazard on its other spawn. Line 288 carries // NOTE: Do NOT set NODE_ENV='development' here. Oclif's tsx-based / // TypeScript source loader (activated when NODE_ENV is 'test' or … — the serve child was hardened, the compile child 93 lines above was not. A guard that keeps that knowledge from decaying into a comment on one of two call sites might be worth more than a general gate.

Searched before filing

type-source-resolution (2 hits: #8180 closed, #8249 open) · tsconfig paths runtime resolution tsx (0) · NODE_ENV oclif (0). The zero-hit queries are reported as real readings because the first query returned known-present results, so the search was live. #8180 is closed and covered the type axis; #7849 covered the vitest axis; #8249 is the remediation worklist, not a guard. Nothing open covers the CLI-runtime axis.

Related: #11020 (the concrete defect this came from, being re-routed to domain:cli) · #8249 · #8180 · #7849 · #8020 / #8108.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions