Uh oh!
There was an error while loading. Please reload this page.
fix(test-harness): one anchored rule for every @objectstack/spec subpath in nine vitest alias tables - #9703
Conversation
…bpath in the vitest alias tables (#9457) `packages/runtime`'s vitest alias table enumerated twelve `@objectstack/spec` subpaths by hand and had gone stale in three places — `cloud`, `integration` and `studio` are published subpath exports with no entry. A string `find` matches by PREFIX, so with a FILE replacement the bare `@objectstack/spec` entry swallowed each of them and resolved it to `…/spec/src/index.ts/<sub>`: `ENOTDIR`, at run time, from a config that reads as correct. The one-line alias is not the fix; the hand-maintained population is the defect. All nine enumerated configs are converted to the anchored-regex shape `packages/qa/downstream-contract` (PR #8129), `service-knowledge`, `service-settings` and `plugin-audit` already carry — one rule for every namespace, which cannot go stale as tests reach new subpaths. `packages/runtime/src/spec-subpath-alias-coverage.pin.test.ts` pins the RULE: it derives the subpath population from spec's published `exports` map, so a namespace this rule stops covering fails there rather than surfacing as an `ENOTDIR` inside whichever plugin performed the import. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WKSnqAaoqtW3QX7SSf1Vk
…time-vitest-spec-subpath-alias
…a cold Vite transform (#9457) Measured in both directions: the fifteen namespace imports take ~5.3s on a cold transform, against vitest's 5000ms default, so the case reported `Test timed out in 5000ms` instead of the resolution failure it exists to name. That is the points-at-the-wrong-thing diagnostic this card is about, reproduced inside its own pin. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WKSnqAaoqtW3QX7SSf1Vk
…time-vitest-spec-subpath-alias
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 9 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 36 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-zhuang
commented
Aug 18, 2026
PM review — accepted, ready + auto-merge armed. And a scope worry of mine, checked and withdrawn.
⛔ My own concern, raised and then withdrawn on the recordSeeing nine It is not, and the authorisation predates both me and the dev. The triage seat's grading comment set this scope explicitly:
and my own claim declared it forward: "plus the sibling vitest configs the sweep names." These are test-harness configs, not package source; Clause-② is no. Nothing crossed a lane line. Recording the retraction rather than quietly dropping it, because an unwithdrawn suspicion in a review thread reads as a finding. ⭐ The detail worth keeping: the numbers matched and one of them was still wrongRun 1 of the pin predicted 4 fail / 2 pass on the reverted config and observed exactly 4 fail / 2 pass. The dev did not stop there — it checked why each failed, and found one had died on vitest's 5000ms default timeout (fifteen cold Vite transforms measure ~5.3s) rather than naming the resolution failure. That is this card's own defect reproduced inside its own pin: a failure that points at the wrong thing. The whole card exists because ⭐ A matching count is not a passing verification. This is the clearest instance of that all day, and the dev caught it on itself. The fix is the class, not the instanceThe card asked for one missing alias. The enumerated-list shape is the defect: a bare Checked rather than assumed: PR #8128's shape transfers because spec's export map is uniform — no FILE-shaped subpath of the Sweep was exhaustive and classified, not just converted: 36 configs repo-wide — 9 converted, 4 already anchored, 23 with no spec alias. The type axis was swept too (exactly one Zone 2, falsified in the useful directionThe card's premise understated itself: three published subpaths were missing ( Two more things handled correctly
Out-of-scope finding #9674 filed unassigned, no Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9457
packages/runtime's vitest alias table enumerated twelve@objectstack/specsubpaths by hand. A stringfindmatches by prefix, so with a FILE replacement the bare@objectstack/specentry swallowed every published subpath the list had not reached and resolved it to…/spec/src/index.ts/(sub)—ENOTDIR, at run time, from a config that reads as correct.Reproduced on
origin/main(ca2e020e4) before touching anything, from a throwaway test inpackages/runtimethat did nothing butawait import('@objectstack/spec/cloud'):A Zone-2 assumption was falsified. The card names one missing subpath; the enumeration had gone stale in three places.
cloud,integrationandstudioare all published subpath exports of@objectstack/specwith no entry in the table.integrationis already reached bysrc/http-dispatcher.test.ts— as animport type, erased before resolution, so it sits one deleted keyword away from the sameENOTDIR.The one-liner was the wrong deliverable
The hand-maintained population is the defect, and its harm is diagnostic: the error names whichever module performed the import (measured:
MetadataPlugin._parseAndRegisterArtifact), never the alias table. So all nine enumerated configs are converted to the anchored-regex shape thatpackages/qa/downstream-contract(PR #8129),service-knowledge,service-settingsandplugin-auditalready carry:Why PR #8128's shape transfers — checked, not assumed.
platform-objectsneeds a hand-written entry ahead of its namespace rule because./pluginissrc/plugin.ts, a FILE, while every other subpath is a directory.@objectstack/spec's export map is uniform: all fifteen published namespaces aredist/(ns)/index.*, i.e.src/(ns)/index.tsin source, with no file-shaped subpath at all. The two non-namespace keys (./openapi.json,./package.json) contain a dot, which([a-z-]+)does not match, so they now fall through to node resolution and land on the real files instead of being mangled by the old prefix-matching entry.Converting the object form to the array form is resolution-neutral by construction: Vite normalises an alias object into exactly this list, in this order, first match wins. Every specifier that resolved before resolves to the same file; the only specifiers whose behaviour changes are the ones that were failing.
The sweep — full population, and how it was enumerated
find . -name 'vitest.config.*'outsidenode_modules/.git⇒ 36 configs, all classified. Counts are echoed rather than inferred from a silent filter, and the zero-hit half carries a control: of the 23 configs that mention no@objectstack/spec, two (packages/adapters/hono,packages/triggers/trigger-record-change) do alias other@objectstack/*packages, so the grep discriminates rather than missing quietly, and 25 of the 36 have an alias block at all.@objectstack/spec— enumerated, converted hereruntime,metadata,drivers/driver-memory,drivers/driver-sql,plugins/embedder-openai,plugins/knowledge-memory,plugins/knowledge-ragflow,plugins/plugin-dev,plugins/plugin-hono-server@objectstack/spec— already an anchored regex, unchangedqa/downstream-contract,plugins/plugin-audit,services/service-knowledge,services/service-settings@objectstack/specpackages/metadatais the one config that already listedcloud— which is precisely why the failure surfaced there and pointed away from the table that was actually wrong.The type-axis twin was swept too, and needed nothing. Exactly one
tsconfig.jsonin the repo enumerates spec subpaths inpaths(packages/qa/downstream-contract), and it is already a star pattern ("@objectstack/spec/*": ["../../spec/src/*/index.ts"]), not a list.pnpm check:type-source-resolutionis green.The pin is over the rule, not over
cloudpackages/runtime/src/spec-subpath-alias-coverage.pin.test.tsderives its population from@objectstack/spec's publishedexportsmap and checks three axes:import()ed, so this is Vite's own resolution and not a re-implementation of it. This is the axis that fails withENOTDIR.dist— axis 1 alone would stay green with the alias table deleted outright, because the specifiers would then resolve throughexportstopackages/spec/dist. The discriminator is@objectstack/spec/conversions, which exists in the source tree and is deliberately absent from theexportsmap, so throughexportsit isERR_PACKAGE_PATH_NOT_EXPORTEDand can only arrive via a source alias. Same discriminatorpackages/qa/downstream-contract/test/source-resolution.pin.test.tsuses.…/spec/src/(namespace)/index.ts, plus a case that rejects any stringfindbeginning with@objectstack/spec. That is what goes red on a revert to the enumerated shape, instead of staying green until somebody reaches an unlisted namespace.The population is read through node's own resolution of the dependency (
createRequire(...).resolve('@objectstack/spec/package.json')), never a repo-relative path climbing out of the package — that is the readpnpm check:cross-package-test-inputsexists to keep declared, and an installed dependency reached by its package specifier is covered by the ordinary dependency edge instead. That gate is green with no new declaration. Bothimport()specifiers are held in variables rather than written as literals, so a vitest-alias defect cannot surface as atscerror or be masked bytsconfig.json; this file is inside the programcheck:type-check-debtre-measures.Shown failing without the fix
Predictions were written down before each run, and the second run exists because the first one caught a defect in the pin itself.
Run 1 — predicted 4 fail / 2 pass; actual 4 fail / 2 pass, but one of the four failed for the wrong reason:
imports every one of themhit vitest's 5000ms default timeout rather than reporting the resolution failure. Cold-transforming fifteen spec namespaces measures ~5.3s, so the case was flaky green-path when the file ran alone and — worse — reportedTest timed out in 5000mson the red path. That is the same points-at-the-wrong-thing diagnostic this card is about, reproduced inside its own pin. Fixed with an explicit 60s timeout, and the reason is written into the file rather than left as a bare number.Run 2, on the final tree, predicted before running:
finds a populationimports every one of themcloud/integration/studioresolves a namespace exports does NOT publishconversionsis unlisted too)ENOTDIR … /spec/src/index.ts/conversionsmaps every published subpath …cloudcloud: expected…/spec/src/cloud/index.ts, received…/spec/src/index.ts/cloudkeeps the bare package entry on sourcerefuses a prefix-matching string entry@objectstack/spec/ai@objectstack/spec/aiReverted with
git checkout origin/main -- packages/runtime/vitest.config.ts, restored withgit checkout claude/issue-9457-runtime-vitest-spec-subpath-alias -- packages/runtime/vitest.config.ts;git statusclean afterwards, confirmed in the same script.Blast radius
This change alters how every test in ten packages resolves
@objectstack/spec, so the suites were run rather than reasoned about. All of it atd9d7580a4, after the secondorigin/mainmerge, with the workspace closure built first (turbo run build --filter=./packages/* --filter=./packages/*/*, 70 tasks):@objectstack/runtime@objectstack/driver-memory@objectstack/driver-sql@objectstack/metadata@objectstack/embedder-openai@objectstack/knowledge-memory@objectstack/knowledge-ragflow@objectstack/plugin-dev@objectstack/plugin-hono-serverpnpm --filter @objectstack/runtime typecheckexit 0. Gates run locally on this same head, all exit 0:check:test-source-alias,check:cross-package-test-inputs,check:type-source-resolution,check:nul-bytes,check:query-options-erasure,check:engine-double-contract,check:where-matcher,check:type-check-coverage,scripts/docs-audit/check-affected-docs.mjs, and the ratchetcheck:type-check-debt --re-measure— "33 ledger entries re-measured, 1926 raw tsc errors total, none above its recorded number; surplus: none". That last one matters here:@objectstack/runtime's TEST_DEBT entry sits exactly at its measurement with no slack, so the new test file adding a single type error would have been red.Changeset
skip-changeset. The diff is ninevitest.config.tsfiles and one*.test.ts. Neither is publishable package source under this repo's convention:tsupbuilds each package from itssrcentry point and shipsdist/, so a vitest config is never compiled into any published artifact and a*.test.tsis in no package's entry graph. This PR releases nothing.Out of scope, filed not fixed
#9674 — the same class one package over. Those same configs alias bare
@objectstack/coreand@objectstack/typeswith prefix-matching string entries, and both publish a FILE-shaped subpath (./logger,./node). The #9457 shape deliberately does not transfer there (a([a-z-]+)rule would send@objectstack/core/loggertosrc/logger/index.ts), and the fix has to movecheck:test-source-alias's registry with it, so it is its own card rather than a rider here.Generated by Claude Code
Generated by Claude Code