Skip to content

test(lint): load the linked @objectstack/spec dist with Node, not vite's transform - #24048

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23900-lint-vitest-external-spec
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23900-lint-vitest-external-spec

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #23900

Clause-②: no

What

packages/lint's vitest now loads the pnpm-linked @objectstack/spec dist with Node instead of inlining it through vite's transform:

  • packages/lint/vitest.config.ts: one test.server.deps.external entry, /[\\/]packages[\\/]spec[\\/]dist[\\/]/, which matches the module's realpath. Vitest's default external covers only node_modules, and a workspace link's realpath is packages/spec/dist/..., so before this every file whose graph reaches spec inlined about 33 MB of built .mjs. The header now says the config carries two settings and why each is package-wide.
  • packages/lint/src/vitest-spec-external.test.ts (new) is the positive control the card asks for. It checks that the spec module the tests import (root and /data, resolved from packages/lint exactly as the sources resolve it) is the same instance Node's own loader holds. It loads the ESM entry the exports map names with createRequire (Node 22's require(esm) shares the ESM loader's module map) and asserts export identity. When the dist is inlined, vite evaluates its own copy, so the identity assertions go red.
  • The two // test-budget: 30 — whole-package import lines on lazy-deps.test.ts and runtime-lazy-deps.test.ts are dropped (see the measurement below).

The import resolves through exports to dist/ either way, so this changes only who evaluates the dist, never which bytes are under test.

Mock census (before the change)

git grep -nE "vi\.(mock|doMock|hoisted|unmock|importActual|importMock|spyOn)\b" -- packages/lint at 2b274600b1: 0 vi.mock / vi.doMock / vi.hoisted sites. The only hits are two vi.spyOn(console, ...), which spy on a global and are unaffected. Control leg: the same vi.mock( spelling finds 30 files in packages/cli/src, so the zero is a real reading.

Positive control: what it proves and how it goes red

Both ablations ran through scripts/ablation-replace.mjs on the committed fix be2f7c34, with the anchor hit and the blob change proven on disk:

leg config vitest-spec-external.test.ts
fix realpath pattern 2/2 pass
ablation 1 external entry deleted 2/2 fail, at the identity assertions (lines 48 and 54), after the typeof / toBeDefined lines pass
ablation 2 name-shaped /@objectstack[\\/]spec/ (the trap packages/cli/vitest.config.ts records) 2/2 fail, same lines

Restore proof: the vitest.config.ts blob on disk equals HEAD's (4ecbe245f449), and git diff HEAD is empty.

Measurement (paired, same box, under os-verify-lock.sh; shared-box seconds)

reading before (2b274600b1) after
package run, lock hold 84 s (141 files / 6802 tests, green) 53 s at be2f7c34, 54 s at 77f016c5 (142 files / 6804 tests, green; +1 file / +2 cases = the control)
vitest transform / import 23.74 s / 189.81 s 8.05 s / 96.39 s
lazy-deps.test.ts alone, two runs 15.01 s, 15.19 s 6.77 s, 6.79 s
runtime-lazy-deps.test.ts alone, two runs 14.25 s, 14.63 s 6.79 s, 6.54 s

Budget lines dropped, not re-measured. Both files now run in 6.5 to 6.8 s alone, under the 10 s FILE_BUDGET_SECONDS line. The lines' stated reason ("vitest's transform of ... every inlined @objectstack/spec dist bundle") is no longer true. A budget is a CI reading x 1.25, and no CI reading after the change exists yet to re-measure from. If an hourly reading later puts either file over 10 s, that reading is the measurement a re-declaration needs (ruling 6181973363 item 2).

Acceptance notes

  • @objectstack/formula and @objectstack/sdui-parser (lint's other two workspace deps) are still inlined. The card scopes only spec, and they are not measured here.
  • Cost recorded in the config comment: an externalised module cannot be vi.mocked. A later test that needs to mock spec has to move the entry.
  • ⛔ No test deleted, no timeout raised, no move to a slower or skipped project, no other package's config, no source change.

Verification

  • Head e0d24934 (patch round): the control now imports defineStack from @objectstack/spec and ObjectSchema from @objectstack/spec/data by name. The root namespace import broke the repo's no-restricted-imports rule for @objectstack/spec. The identity check is unchanged.
  • ESLint over the four changed files, node --stack-size=4000 node_modules/eslint/bin/eslint.js --no-inline-config ...: exit 1 on 77f016c5 (one no-restricted-imports error at vitest-spec-external.test.ts:29:8), exit 0 on e0d24934.
  • Ablation legs re-run on e0d24934 through scripts/ablation-replace.mjs. With the external entry deleted, and with the name-shaped /@objectstack[\\/]spec/, the control is 2/2 red at :48:25 and :54:26 (Object.is identity). Restore: the disk blob equals the HEAD blob 4ecbe245f449, and git diff HEAD is empty.
  • pnpm --filter @objectstack/lint test at e0d24934: 142/142 files, 6804/6804 tests (VERDICT command-exit 0, lock held 54 s).
  • pnpm --filter @objectstack/lint typecheck (including check:test-typecheck) at e0d24934: VERDICT command-exit 0; the debt ledger is unchanged (2 files / 6 errors).
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ... at e0d24934: 51 derived, 50 run with exit 0, 1 NOT-MEASURED (pnpm check:dual-build-cjs-loads exited 3 because it needs a full-repo build; CI measures it), 0 unrun. Also ran node scripts/check-console-intercept-disarm.mjs: exit 0.
  • No publish: @objectstack/lint's files[] is dist, README, CHANGELOG. None of the changed text is in the built dist/ (0 hits), while a shipped symbol (validateStackExpressions) has 8 hits as the positive control.

Written by the os-dev cloud session session_019oKRRCxbLy3xARM3CH99sf, dispatched by domain:spec seat 1 (claim 6224227197).


Generated by Claude Code

…e's transform

packages/lint's vitest inlined the pnpm-linked spec dist (about 33 MB of
built .mjs) because vitest's default external pattern is node_modules-only
and a workspace link's realpath is packages/spec/dist. Externalise it with a
realpath pattern, and add a positive control that proves the pattern matches:
the spec module the tests import must be the very instance Node's loader
holds, which goes red when the dist is inlined.

Claude-Session: https://claude.ai/code/session_019oKRRCxbLy3xARM3CH99sf
Co-authored-by: Claude <noreply@anthropic.com>
… lines

With the spec dist externalised, lazy-deps.test.ts and
runtime-lazy-deps.test.ts each run in 6.5-6.8 s alone (15.0-15.2 s and
14.2-14.6 s before, same box, under os-verify-lock), under the 10 s file
line, and the lines' stated reason, vite's transform of the inlined spec
dist, no longer holds. No post-change CI reading exists to re-measure a
budget from, so the files return to the 10 s line.

Claude-Session: https://claude.ai/code/session_019oKRRCxbLy3xARM3CH99sf
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/lint/vitest.config.ts), 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/lint/vitest.config.ts) — 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 5c2ac83a230d2eec038502a23a8142153cad814d → packageMentionDocs.

The root namespace import broke the repo's no-restricted-imports rule for
@objectstack/spec. Import defineStack from the root and ObjectSchema from
the data subpath by name; the identity check is unchanged.

Claude-Session: https://claude.ai/code/session_019oKRRCxbLy3xARM3CH99sf
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · PR #24048 @ e0d249340c (#23900) · domain:spec seat 1 · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T22:20Z

Reviewer of record. Every item below was checked against GitHub and the code, not against the reports (6224943566, 6225425392 on #23900).

  • Shape: Draft, base main. The first line is Fixes #23900, the body's only closing keyword, then Clause-②: no, matching claim 6224227197. 4 files, all under packages/lint, inside the claimed surface. skip-changeset is right: none of the changed text reaches @objectstack/lint's files[] (dist, README, CHANGELOG). Not governed (check-governed-merges.mjs --pr 24048). Test config and tests only, so no contract face and this seat's record is the review. Assignee os-elon-musk.
  • What it lands:
    • packages/lint/vitest.config.ts: one test.server.deps.external entry, /[\\/]packages[\\/]spec[\\/]dist[\\/]/, matched against the linked dist's realpath. Node loads the spec dist instead of vite's transform inlining about 33 MB of .mjs. The import still resolves through exports to the same dist/ bytes. The header states both settings and why each is package-wide, and records the cost: an externalised module cannot be vi.mocked.
    • The positive control, src/vitest-spec-external.test.ts: it asserts that defineStack (root) and ObjectSchema (/data) are the same instances Node's own loader holds, read through createRequire of the ESM entry the exports map names. Both ablations (the entry deleted, and the name-shaped /@objectstack[\\/]spec/ the cli config warns about) turn it red at the Object.is assertions (:48, :54). Restore was proven by blob on both rounds.
    • The two 30 s budget lines on lazy-deps.test.ts and runtime-lazy-deps.test.ts are dropped. Their stated reason, the inlined spec dist's transform, no longer holds.
  • Measured (paired, same box, under the lock): package hold 84 s → 53-54 s (vitest transform 23.7 s → 8.1 s), all 142 files and 6804 tests green. The two lazy-deps files alone ran 14.3-15.2 s before and 6.5-6.8 s after, under the 10 s line.
  • Mock census: 0 vi.mock / vi.doMock / vi.hoisted in packages/lint, with a 30-file control leg in packages/cli proving the search reads.
  • The patch round (record 6225029161): the ESLint no-restricted-imports red on 77f016c52a was a root namespace import in the new control. It now imports by name. ESLint over the four files exits 1 on the old head and 0 on this one. The PR body's Verification section was brought to this head by the seat (issue_patch).
  • The open questions, the seat's calls (record 6225029161): the budget lines are dropped, not re-declared (A); the 84 → 53 s baseline stands beside the card's 98 → 60 s (A); the detached primary checkout (A).
  • Noted, not filed: @objectstack/formula and @objectstack/sdui-parser are still inlined from their dist in this package's vitest. They are unmeasured, and the card scoped spec only. dispatch-gates --commands does not list the ESLint lane; the dispatch prompts from this seat now ask for ESLint on the changed files.
  • CI on this head: 40 check-runs, 33 success, 7 skipped by their filters, 0 failed, 0 in progress. mergeable: clean.

The PR goes to the merge queue in this act. The cloud dev session is archived at this ACCEPT.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 22:21
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 16, 2026 22:21
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit c91af8e Oct 16, 2026
48 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23900-lint-vitest-external-spec branch October 16, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants