test(service-storage): resolve @objectstack/core from source so a stale dist can't decide a pin (#7668) - #7778
Conversation
…tale dist can't decide a pin (#7668) `packages/services/service-storage` had no `vitest.config.ts`, so its unit suite resolved `@objectstack/core` through the workspace link to `packages/core/dist/index.js` — a build artifact. The verdict of every unit pin in the package was a function of build state, not of the source in the checkout. All 17 cases of `attachment-access-hooks.test.ts` — the only executable guard on the #4757 predicate-less unscoped-multi-delete refusal, which cannot be expressed over REST — errored with `TypeError: withoutOperationPrivateKeys is not a function` against a tree whose prebuilt core predated that export, while `packages/core/src/security/operation-private-keys.ts` was correct throughout. The loud error is the mild half: a core dist merely BEHIND rather than missing the symbol lets a pin run green against core's old behaviour, with nothing in the output saying so. This is not a task-ordering bug. `turbo.json` already declares `test` dependsOn `^build` and `turbo run test --filter=@objectstack/service-storage` passes 352/352; it needed no change. The paths that broke are the ones turbo does not mediate — `pnpm test` in the package, `vitest run <file>`, an editor runner, a QA tree built at an older commit — which is where a pin is re-run while someone is changing core. Ordering cannot fix that; taking the artifact out of the resolution path can. Verified by simulating the exact #7668 condition (core's built `index.js` stripped of the export): without the config 17/30 cases fail with the issue's verbatim TypeError; with it, 30/30 pass. Full suite 352/352 green both via `turbo run test` and via a bare `vitest run` in the package. Fixes #7668 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqnHVpBA1ij5Jb87JaMXyM
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31510769498 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31512979457 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31513732396 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31514484197 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31515154824 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31516019973 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #7668
Root cause — declared, not inherited from the issue
The issue's LEAD pointed at "build ordering / CI"; the PM brief guessed
turbo.json's task dependencies. I reproduced both paths and neither is the defect.turbo.jsonalready declares:and
pnpm turbo run test --filter=@objectstack/service-storagebuilds core first and passes 352/352 (24 files) onorigin/main@7a8476f— measured, not assumed.turbo.jsonis untouched by this PR.The actual root cause is one directory down:
That is what #7668 reports.
attachment-access-hooks.test.tsis the only executable guard on the #4757 predicate-less unscoped-multi-delete refusal (it cannot be expressed over REST —deleteManywith noids/whereis rejected 400 before the hook is reached), and against a prebuilt tree whose core dist predated the export it erroredTypeError: withoutOperationPrivateKeys is not a function— whilepackages/core/src/security/operation-private-keys.ts:117was correct the whole time.The loud error is the mild half. A core dist merely behind rather than missing the symbol lets a pin run green against core's old behaviour — a passing test that is not testing the code in the checkout, with nothing in the output saying so.
Why ordering cannot fix this. Turbo already orders correctly, so
turbo run testwas never the failing path. The paths that broke are the ones turbo does not mediate:pnpm testinside the package,vitest run <file>, an editor runner, or a QA agent in a tree built at an older commit (which is how #7635 found it). Those are exactly the paths a pin is re-run on while someone is changing core — i.e. when it most needs to be telling the truth. NodependsOnedit reaches them. Taking the artifact out of the resolution path does.The fix
A new
packages/services/service-storage/vitest.config.tsaliases@objectstack/core→packages/core/src/index.ts, matching whatservice-knowledge,plugin-audit,runtime,metadata,driver-memory,driver-sql,knowledge-memory,knowledge-ragflow,plugin-devandplugin-hono-serveralready do.@objectstack/core/loggerintocore/src/index.ts/logger(ENOTDIR). Same shape and reasoning asservice-knowledge's config.spec,observability,platform-objects,objectql) resolve to this same single core instance rather than a second copy; the sharedtsup.config.tsexternalizes workspace deps, so none of them inline one.@objectstack/coredeliberately — it is the package that owns the pin's subject symbol and the one named in the failure. Aliasing the other four as well would widen the dual-instance surface for no defect on the table.No product code and no test assertions changed. Diff is 2 new files (config + changeset).
Reproduction and verification
All run on this branch, worktree at
7a8476f.1. Baseline repro — the suite cannot load without a built core (clean
pnpm install, no dists):2.
turbo.jsonexonerated —pnpm turbo run test --filter=@objectstack/service-storage→ Test Files 24 passed, Tests 352 passed, before any change.3. The decisive A/B — the exact #7668 condition simulated by stripping the export from the built
packages/core/dist/index.js(source untouched), then running the same file twice:vitest.config.tsThe 17 failures reproduce the issue's count exactly, with its verbatim message:
So the config is demonstrably what closes the hole, and #4757 now has a pin that a build artifact cannot silence.
4. Regression sweep (core dist restored):
npx vitest runin the package (the previously-broken un-mediated path) → 24 files / 352 tests passedpnpm turbo run test --filter=@objectstack/service-storage→ 352 passednpx eslint packages/services/service-storage/vitest.config.ts --no-inline-config→ cleanpnpm check:published-files→ ✓ (the gate classifiesvitest.config.tsas test-harness config that must not ship; this package'sfileswhitelist already excludes it)pnpm check:empty-changeset→ ✓turbo.jsonwas not modified, so the hot-file conflict risk the brief flagged does not apply;git merge origin/mainon this branch was already up to date at push time.Out-of-scope findings — reported, not fixed
@objectstack/coreand ships no vitest config. This PR fixes the one package the issue names; a repo-wide sweep (or a lint gate asserting the invariant) is a separate change and should be its own issue rather than a rider here.service-storage's remaining runtime imports (@objectstack/spec/*,observability,platform-objects/*,objectql,types) still resolve to dist, so the suite continues to require a build for those. That is correct today — turbo orders it — and no observed defect argues for widening the alias set now.Known-unrelated CI red
check:platform-checklist→coverage.json · qa: UNCLASSIFIEDis the pre-existing #7347 failure on base, not from this change.Generated by Claude Code