Found during the patch round on PR #8349 (issue #7378). Filed unassigned; not queued.
The shipped miss
PR #8349 added one runtime import inside packages/core/src/metadata-service-contract.ts:
import{pluralToSingular}from'@objectstack/spec/shared';check:test-source-alias was green on that diff. CI then went red in two packages — @objectstack/plugin-hono-server (16 test files dead at load) and @objectstack/driver-memory (23 dead) — with the exact signature the gate's rule 5 names as its live-fire correctness rule:
Error: ENOTDIR: not a directory, open '.../packages/spec/src/index.ts/shared'
❯ ../../core/src/metadata-service-contract.ts:82:1
Both configs alias the bare @objectstack/spec to spec/src/index.ts (a FILE) with per-subpath entries spelled out above it and no /shared entry, so the specifier resolved through a file extension — precisely what the gate says it "sees ... and fails on".
Why the gate could not see it
Step 1 of the gate's own contract scopes the walk: "the imports REACHABLE FROM ITS TESTS — the test files plus every intra-package file they pull in transitively." The specifier @objectstack/spec/shared appears in no file of either failing package. It enters their test-time module graphs only because those configs alias @objectstack/core to core/src/index.ts — at which point the aliased dependency's own workspace-import surface becomes part of the graph the config must resolve. The gate never resolves that surface against the config, so a specifier the config demonstrably mangles at runtime is never checked statically.
Stated as an invariant: aliasing a workspace dep to source imports that dep's import surface into the consumer's resolution domain; the gate's reachability walk stops at the package boundary, one hop short of the domain it certifies.
Blast radius, measured on the day of the miss
Nine vitest configs carry the file-mapped bare @objectstack/spec alias. Five of them also aliased @objectstack/core to src and lacked /shared: driver-memory and plugin-hono-server (both red in CI on PR #8349), plus knowledge-ragflow, plugin-dev, knowledge-memory (latently identical — their current test graphs happen not to reach the new module, so they stayed green by luck of coverage, not by resolution). PR #8349's patch commit added the /shared entry to all five; this card is about the next subpath, which will ship the same way unless the gate's walk crosses the boundary.
Distinct from the existing gate cards: #8256 (remediation hint for subpath-only importers), #8020 / #8108 (replacement-string spellings the static reader could not parse), #8180 (tsconfig axis). None of those extends the walk itself.
Candidate direction (not a design)
When a config aliases workspace package P to a path under src/, extend the reachability walk INTO P's source graph (same value-import/import type discipline) and resolve the collected specifiers against the SAME config, letting the existing rule-5 file-extension check judge them. The walk is already transitive within a package; this makes "intra-package" follow where the alias actually points.
Refs: PR #8349, #7378 (the change that exposed it), #7778 (rule 5's origin), #8256, #8020, #8180, #8108.
Generated by Claude Code
Found during the patch round on PR #8349 (issue #7378). Filed unassigned; not queued.
The shipped miss
PR #8349 added one runtime import inside
packages/core/src/metadata-service-contract.ts:check:test-source-aliaswas green on that diff. CI then went red in two packages —@objectstack/plugin-hono-server(16 test files dead at load) and@objectstack/driver-memory(23 dead) — with the exact signature the gate's rule 5 names as its live-fire correctness rule:Both configs alias the bare
@objectstack/spectospec/src/index.ts(a FILE) with per-subpath entries spelled out above it and no/sharedentry, so the specifier resolved through a file extension — precisely what the gate says it "sees ... and fails on".Why the gate could not see it
Step 1 of the gate's own contract scopes the walk: "the imports REACHABLE FROM ITS TESTS — the test files plus every intra-package file they pull in transitively." The specifier
@objectstack/spec/sharedappears in no file of either failing package. It enters their test-time module graphs only because those configs alias@objectstack/coretocore/src/index.ts— at which point the aliased dependency's own workspace-import surface becomes part of the graph the config must resolve. The gate never resolves that surface against the config, so a specifier the config demonstrably mangles at runtime is never checked statically.Stated as an invariant: aliasing a workspace dep to source imports that dep's import surface into the consumer's resolution domain; the gate's reachability walk stops at the package boundary, one hop short of the domain it certifies.
Blast radius, measured on the day of the miss
Nine vitest configs carry the file-mapped bare
@objectstack/specalias. Five of them also aliased@objectstack/coreto src and lacked/shared:driver-memoryandplugin-hono-server(both red in CI on PR #8349), plusknowledge-ragflow,plugin-dev,knowledge-memory(latently identical — their current test graphs happen not to reach the new module, so they stayed green by luck of coverage, not by resolution). PR #8349's patch commit added the/sharedentry to all five; this card is about the next subpath, which will ship the same way unless the gate's walk crosses the boundary.Distinct from the existing gate cards: #8256 (remediation hint for subpath-only importers), #8020 / #8108 (replacement-string spellings the static reader could not parse), #8180 (tsconfig axis). None of those extends the walk itself.
Candidate direction (not a design)
When a config aliases workspace package P to a path under
src/, extend the reachability walk INTO P's source graph (same value-import/import typediscipline) and resolve the collected specifiers against the SAME config, letting the existing rule-5 file-extension check judge them. The walk is already transitive within a package; this makes "intra-package" follow where the alias actually points.Refs: PR #8349, #7378 (the change that exposed it), #7778 (rule 5's origin), #8256, #8020, #8180, #8108.
Generated by Claude Code