Skip to content

fix(metadata-core): drop the optional vitest peer so npm 10 installs it again - #23834

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-23806-metadata-core-vitest-peer
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-23806-metadata-core-vitest-peer

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23806

Clause-②: no

What changes

@objectstack/metadata-core no longer declares vitest in peerDependencies / peerDependenciesMeta. Both objects became empty and are removed. The vitest devDependency stays. One patch changeset is added, .changeset/23806-metadata-core-vitest-peer.md.

  • pnpm-lock.yaml does not move. The importer entry never recorded the peer. pnpm install --frozen-lockfile and pnpm install --lockfile-only both exit 0, and the lockfile stays byte-unchanged.
  • The built dist/ is byte-identical before and after the edit (diff -r exit 0). tsup.config.ts lists vitest in external itself, so dist/testing.js still imports it and does not bundle it.

Why: who reads vitest?

Measured on this branch's build and on the published 18.0.0-next.2 tarball:

entry file(s) imports vitest?
. (import) dist/index.js + dist/chunk-URJ4AGQ3.js no. Specifiers: @objectstack/spec subpaths, zod, module, crypto
. (require) dist/index.cjs no
./testing dist/testing.js yes, line 7: import { describe, it, expect } from "vitest"
./testing types dist/testing.d.ts no import, one mention in a comment

The runtime entry never reads vitest. Only the test-only ./testing entry does, and the docblock in src/testing.ts already tells consumers to list vitest as a devDependency. That sentence is still true, so the file is unchanged.

In-repo consumers. Every in-repo consumer of @objectstack/metadata-core/testing is a test file, in metadata-fs, metadata-protocol, rest or runtime. Each of those packages:

  • declares vitest: ^4.1.11 in its own devDependencies;
  • links metadata-core as workspace:*.

So none of them relied on the peer. ../objectui (at 63d00da) has no consumer. Of the 85 tracked package.json files, metadata-core's was the only one with vitest as a peer, a dependency or an optional dependency.

Measured: npm 10 (control first), then this branch

Each install ran in an empty scratch project on 2026-10-16. Upstream at measurement time:

  • vitest dist-tags: V4 = 4.1.12, latest = 5.0.5.
  • vitest@4.1.12 peers @vitest/browser-webdriverio: ^4.1.11.
  • The newest 4.1.x of that package is 4.1.11, which peers vitest: 4.1.11 exactly.

So the upstream peer set is still incoherent.

install npm result
published @objectstack/metadata-core@18.0.0-next.2 (control) 10.9.4 exit 1, Cannot read properties of null (reading 'edgesOut')
published, same (control) 10.9.9 exit 1, same error
this branch's packed tarball 10.9.4 exit 0, 4 packages, no vitest in the tree; import('@objectstack/metadata-core') loads 121 exports
this branch's packed tarball 10.9.9 exit 0, same
published tarball with ONLY the two peer keys deleted (isolation leg) 10.9.9 exit 0
published @objectstack/objectql@18.0.0-next.2 (a dependent) 10.9.9 exit 1, edgesOut
same, with root overrides pointing metadata-core at this branch's tarball 10.9.9 exit 0
project on vitest@5.0.5 + published metadata-core 11.21.1 exit 1, ERESOLVE: peerOptional vitest@"^4.0.0" from @objectstack/metadata-core@18.0.0-next.2
project on vitest@5.0.5 + this branch's tarball 11.21.1 exit 0

17 published packages declare metadata-core directly, among them objectql, runtime, rest, cli, metadata and metadata-protocol. The peer therefore reached every npm install of any of them.

What this does not fix

@objectstack/plugin-auth still exits 1 on npm 10.9.9 with the same error, even with metadata-core overridden to this branch's tarball. The cause is better-auth@1.7.3, which declares its own optional vitest peer (^2.0.0 || ^3.0.0 || ^4.0.0). This PR does not work around it.

Both user remedies were measured, and the changeset names both:

  • npm 11 installs plugin-auth (exit 0);
  • npm 10.9.9 installs it with a root "overrides": { "vitest": "~4.0.0" } (exit 0).

Putting that remedy where an installing user reads the docs is the domain:devx half of the card, as triage routed it. This PR touches no content/docs/**.

The one reader the peer had, and what replaces it

Under one non-default layout, the peer did have a reader that the docblock cannot serve. The probe below runs await import('@objectstack/metadata-core/testing') in a consumer that lists vitest 4.1.11 as a devDependency:

layout published (with peer) this branch
pnpm workspace sub-package, hoist=false OK ERR_MODULE_NOT_FOUND: Cannot find package 'vitest'
pnpm workspace sub-package, default hoisting OK OK
pnpm single project, hoist=false not run OK
npm 11, hoisted not run OK
npm 10.9.9, vitest ~4.0.0 plus the same override (blank-scaffold shape) install OK install OK, probe OK
pnpm workspace hoist=false, plus packageExtensions re-declaring the peer not applicable OK

The changeset gives that layout the measured packageExtensions remedy.

Keeping the peer and narrowing its range was considered and rejected, for two reasons:

  • it pins against an upstream accident, and the range must be updated again once vitest publishes a coherent 4.x;
  • it keeps the ERESOLVE above for every project on vitest 5, including projects that never import ./testing.

Verification (at a27899a51)

  • pnpm --filter @objectstack/metadata-core exec vitest run --maxWorkers=2: 18 files, 733 tests passed.
  • pnpm --filter @objectstack/metadata-core run typecheck: exit 0.
  • Derived gates. node scripts/pm/dispatch-gates.mjs --commands derived 65 commands. All 65 ran: 64 exit 0, and 1 is NOT MEASURED. That one is pnpm check:dual-build-cjs-loads, which reports PREREQUISITE NOT MET because it needs every package's dist/; CI runs it. In its place, a direct reading: require of packages/metadata-core/dist/index.cjs loads 121 keys. The --ran reconciliation reads 65 derived, 64 run, 1 NOT MEASURED, 0 UNRUN.
  • Two gates rerun after meeting their prerequisite. check-plugin-teardown-shape --self-test first exited 3; it passed after its pinned commit was fetched. check:lean-entry-closure first exited 3; it passed after objectql's closure was built. Both exit 0.
  • Roster gates under packages/. check:authz-resolver, check:error-code-casing and check:filter-alias-parity each exit 0.
  • Wide-population families. The 11 of them walk non-test .ts sources, and this diff contains none. CI runs them.

Acceptance notes

  • The docblock in src/testing.ts is left as is; its sentence is still true.
  • vitest@4.1.11 on its own also fails on npm 10.9.9: npm install -D vitest@4.1.11 in an empty project exits 1 with edgesOut. So on npm 10, consumers of ./testing are held back by upstream whatever this package declares. The ~4.0.0 shape works.
  • Whether the contract suite runs on vitest 5 was not measured. The changeset says only that this package runs it on vitest 4.

Generated by Claude Code

…ever reads

The `.` entry's built output (dist/index.js, its chunk, dist/index.cjs)
imports nothing from vitest; only the test-only `./testing` entry does,
and its docblock already tells consumers to bring vitest as a
devDependency. The optional `vitest: ^4.0.0` peer led npm 10 into
vitest@4.1.12, whose own peer set contradicts itself, and npm 10's
resolver crashed (`Cannot read properties of null (reading 'edgesOut')`)
on any install that reached this package.

Claude-Session: https://claude.ai/code/session_01TQLy3Yq6vF4SDpTXdr3Uwi
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tooling labels Oct 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/metadata-core/package.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/metadata-core/package.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 43567a8fcbed46b05056b65a45e3f90147f806d4 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ✅ ACCEPT: PR #23834 at a27899a51c · domain:engine seat 2 · session_01TQLy3Yq6vF4SDpTXdr3Uwi · 2026-10-16T08:07Z

Basis: claim 6209875401 on #23806; triage's grade 6207749551; the dev's report on #23806; the seat's own reading of the full diff (2 files, +22 / −8) and of origin/main.

Checklist

  • Shape. Draft, base main, first line Fixes #23806; no other issue number in the body. One closing target, and the merge should close the card: the docs half is the seat's domain:devx filing at landing (claim 6209875401), not this PR's.
  • Premise, measured by the seat. On npm 10.9.4, published @objectstack/metadata-core@18.0.0-next.2 in an empty project exits 1 with Cannot read properties of null (reading 'edgesOut'). The same tarball with only peerDependencies and peerDependenciesMeta deleted installs, exit 0, with no vitest in the tree. This matches the dev's control and isolation legs.
  • Reader of the peer. On origin/main, packages/metadata-core/tsup.config.ts lists vitest in external, and the src/testing.ts docblock reads: "Importing this module pulls in vitest, so downstream consumers must list vitest as a devDependency." The . entry's reading is the dev's (built dist/ byte-identical before and after); the seat did not rebuild.
  • Scope. Inside the claim's file surface; src/testing.ts and pnpm-lock.yaml untouched. No content/docs/releases/, no unrelated file. The package is published (no private), and a patch changeset is present.
  • Changeset, read sentence by sentence against the diff and the measurements: the npm 10 edgesOut exit and the dependent (objectql) case; the cause (the optional vitest: ^4.0.0 peer leading npm 10 to 4.1.12); the ERESOLVE into a vitest 5 project; "The package's main entry never imports vitest… Nothing the package exports changes"; the ./testing devDependency sentence; the hoist=false packageExtensions remedy; the plugin-auth sentence (npm 11, or overrides.vitest ~4.0.0). Each is backed by a leg in the report. Clause-②: no is on its own line in the changeset and the body.
  • Contract faces. None hit: no Clause-②: yes, no narrowing declaration, no packages/spec/src/**, no governed path. CI plus this in-seat read; no second agent.
  • Gates on the head. ci-failure.mjs --pr 23834: GREEN, 41 of 41 completed, none failed. check-expected-skips --pr 23834: OK, 6 skips, all in the roster. Local reconciliation in the report: 65 derived, 64 exit 0, 1 NOT MEASURED (check:dual-build-cjs-loads, a prerequisite; CI's run is green).
  • Landing checks. check-governed-merges --pr 23834: NOT governed, 30 changed lines. mergeable_state clean; git merge-tree against current origin/main (0cfd0c49b) is clean.

The dev's open question (option A, implemented): answered A by this seat, not escalated. The removed peer had one reader, a pnpm workspace package with hoist=false importing ./testing. The governing text is the src/testing.ts docblock, which already puts vitest on the consumer. The cost falls on a non-default layout with no measured user, and the changeset ships its measured one-stanza remedy. B (narrow the range) pins against an upstream accident and keeps the vitest 5 ERESOLVE. C (a separate test-kit package) is the long-term shape if a strict-layout ./testing user appears; there is none today.

Out of scope, one line each:

  • plugin-auth / cli on npm 10 through better-auth's optional peer → the seat's domain:devx remedy card, filed at landing if upstream is still incoherent (claim 6209875401).
  • "vitest@4.1.11 alone also fails on npm 10" → Acceptance notes (upstream; the ~4.0.0 shape works).
  • "The contract suite on vitest 5 is not measured" → Acceptance notes.

Next: ready, then auto-merge through the queue.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/s tooling

Projects

None yet

2 participants