Uh oh!
There was an error while loading. Please reload this page.
fix(cli): serve's host importer states its own resolution base - #11405
Conversation
`createHostImporter`'s undeclared leg falls back to "the importing package's own resolution", and which package that is depends on where the `import()` is physically written. #10943 made it an explicit parameter; `@objectstack/verify` and the qa/dogfood probe pass theirs. `serve`'s `importFromHost` did not, so its fallback resolved from `@objectstack/types` — which under a pnpm-isolated layout sees only `@objectstack/spec`. Thread the base, and collapse `Serve.importConfigPlugin`'s three-branch shape to two: the local `import()` its undeclared branch kept, and the re-entry it made for the failure text, are now the same call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
…climb `check:cross-package-test-inputs` flagged `resolve(HERE, '../../types/dist/ node.mjs')` in the new e2e: a filesystem climb names a repo SOURCE input no turbo glob covers. The child now resolves the helper from serve.ts's own location by specifier, which is what serve.ts itself does and what the gate asks for — a bare specifier is an installed dependency, and @objectstack/cli already declares @objectstack/types, so turbo's task graph carries that edge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
📓 Docs Drift CheckThis PR changes 1 package(s): 16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1b4995ae57dd3e64f12b09638c8d4817b241bb1b && git checkout 1b4995ae57dd3e64f12b09638c8d4817b241bb1b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8d237b40c7a731f522f1979926a49acd9e430762 2656cafb295773734c49b26a1549c2052c08ec9b && git checkout -B drift-repro 8d237b40c7a731f522f1979926a49acd9e430762 && git merge --no-ff 2656cafb295773734c49b26a1549c2052c08ec9b
node scripts/docs-audit/affected-docs.mjs --json 8d237b40c7a731f522f1979926a49acd9e430762
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11157
packages/cli/src/commands/serve.ts'simportFromHostbuilt its importer without stating its own resolution base.createHostImporter's undeclared leg falls back to "the importing package's own resolution", and which package that is depends entirely on where theimport()is physically written — Node ESM resolves a bare specifier against the module containing the call. #10943 turned that into an explicit parameter,options.fallbackImport.@objectstack/verify(bootStack) and thepackages/qa/dogfoodenterprise probe both pass theirs;packages/cliwas the one caller that declined, so its fallback resolved from@objectstack/types, which under a pnpm-isolated layout sees only@objectstack/spec.The call was re-derived, not quoted: it sat at
serve.ts:389on the merged ref, one line among three moves today.The measured accept-set delta
Exactly "packages
packages/cliitself declares" —Clause-②: no. Re-measured in this checkout withimport.meta.resolvefrom a probe placed inside each package, not taken from the card:packages/cli@objectstack/typeschalk@objectstack/plugin-auth@objectstack/plugin-audit@objectstack/spec@objectstack/service-cluster(+-redis,-postgres)@objectstack/service-i18n,@objectstack/organizations,@objectstack/service-ai,@objectstack/service-ai-studioAll three widened names are in
packages/cli's owndependencies, so the delta is a declaration surface and not a hoisting accident (pinned). All seven specifiersserveitself routes throughimportFromHostMISS from both bases — the card's "measured harmless today", re-confirmed. The #4719 declaration gate on the declared leg is untouched: reachable-but-undeclared is still refused, pinned in the e2e.Anti-vacuity: the pin, and why half of it could not live in a unit test
@objectstack/typesis a linked workspace package, so Vite processes it as source rather than externalising it and rewrites theimport()insidepackages/types/dist/node.mjsto its own resolver — which resolves from the vitest root,packages/cli. An in-processcreateHostImporter(appRoot)('chalk')with no caller base at all RESOLVES under vitest and THROWS under Node. Under vitest the two bases are the same base, so every in-process assertion about which one is in use — including the anti-vacuity control beside it — is green either way, silently.So the resolution pins spawn a real Node process (
test/serve-host-fallback-base.e2e.test.ts), and the unit file (src/commands/serve-host-fallback-base.test.ts) keeps only what is honest in-process: theundeclaredMessagebranch, which is pure logic no resolver touches. Both files say so in their headers. This also means the existingchalkassertion inserve-config-plugin-host-resolution.test.tsis a behaviour statement, not a measurement of this card — it stays green under the ablation below.Ablation — removing ONLY the
fallbackImportoption, everything else identical. Mutation confirmed on disk by grep counts either side (fallbackImport: (fallbackSpecifier)1 → 0,createHostImporter(hostRoot);0 → 1) plusgit diff --stat; no build is involved because both vitest and the spawnedtsxchild readserve.tsfrom source; the script carries atrap … EXIT INT TERMrestore.Pre-fix (ablated), the load-bearing pin:
Post-fix, same six files:
The four that move are: the resolution pin above; the e2e diagnostic pin (
imported from …/packages/cli/…, not…/packages/types/…); the unit message-branch pin; and the source pin. The standing anti-vacuity floor is the e2e'sCONTROLcase, which buildscreateHostImporter(root)with no base and asserts it fails on the same specifier and namespackages/typesas the origin it failed from.The diagnostic text — measured against the post-#11185 message
undeclaredMessagebranches onfallbackImport !== undefined, confirmed by readingpackages/types/src/node.ts(not modified). This change movesserveonto the other branch, so the note that exists to report this exact gap is no longer printed — pinned as an absence, alongsidehost app: <the app>(#11185's text, never the CWD). No assertion anywhere is written against the CWD-named text older trees emitted.Second half —
importConfigPluginre-read: COLLAPSED, three branches to twoRe-read with this change and against #10944's landed option B (a relative entry is refused at config load, before any base is chosen), as the card asked. Measured redundant, and behaviour-preserving case by case:
declared-unresolvable, the INSTALL remedyimport()inserve.tsfallbackImport— the sameimport(), in the same moduleisModuleNotFoundError(Serve.isModuleNotFoundErrordelegates to it)isDeclaredByHost(spec, root)is literallyreadHostDeclaration(spec, root).declared, which is what the helper already calls — so the pre-check was a second reading of the same manifest. The non-package branch (absolute path,file://,node:) stays inserve.ts: the helper's pass-throughimport()lives in@objectstack/types, and keeping it local is what stops a future relative spelling the refusal does not recognise from being silently re-based there.serve-config-plugin-host-resolution.test.tskeeps every behavioural assertion unchanged — that is what made the collapse safe to take. One structural assertion moved: it requiredisDeclaredByHost(pluginSpecifier, root)in this method, and requiring the second copy would have pinned the fork Prime Directive #12 exists to prevent. It now asserts the delegation and the absence of a local declaration read; the single owner is pinned inpackages/types/src/node.test.ts.Verification
Run at
2656cafb, the final commit.pnpm --filter @objectstack/cli typecheck→tsc --noEmit, exit 0.Test Files 29 passed (29) · Tests 405 passed (405).Test Files 6 passed (6) · Tests 42 passed (42).pnpm lint(repo-wideeslint . --no-inline-config) → exit 0, no narrowing claimed.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(from the merge base, not a hand-built diff) — all green:check:nul-bytes(OK … no raw ASCII control bytes),check:cross-package-test-inputs(OK: 14 package(s) read outside themselves, all declared),check:test-source-alias,check:published-files,check:route-envelope,check:slot-lookup,check:type-source-resolution,check:changeset-gate-self-tests,check:objectui-changeset,check:type-check-coverage,check-adr-0087-registration,check-changeset-no-major,check-ci-filter-parity,check-empty-changeset,check-plugin-teardown-shape,check-affected-docs.check:cross-package-test-inputswent red first onresolve(HERE, '../../types/dist/node.mjs')in the new e2e and was fixed by respelling, not by widening a glob: the child now resolves@objectstack/types/nodeby specifier fromserve.ts's own location, which is whatserve.tsitself does and what the gate asks for.check:type-check-debtNOT RUN — declared. It refuses to measure without the whole workspace built (Error: --re-measure cannot run: 1 workspace dependenc(ies) … have no built type entry point on disk — @objectstack/service-knowledge), which CI does before this step. Substituted, becausepackages/cli/tsconfig.jsonisinclude: ["src"]and the new e2e lands in the hiddentest/layer that ledger freezes at 146: atsc -pover the package's own config extended to that one file exits 0, so the frozen count is unmoved.check:type-check-coverage, the sibling that reads the hidden-file population, is green with the file present.content/docs/releases/**untouched;packages/types/src/node.tsread, not edited;packages/cli/test/helpers/serve-process.tsuntouched. Changeset:.changeset/cli-serve-host-importer-caller-base.md.Generated by Claude Code
Generated by Claude Code