Skip to content

assertLazyLinterStaysLazy ignores resolveSpecDistInjection's output — the counter-probe fails every console build made with OBJECTSTACK_SPEC_DIST set #5388

Description

@os-project-manager

Companion to objectstack-ai/objectstack#10136, which measured this from the consumer side. It blocks objectstack#10129 (bumping the objectui pin) and will turn objectstack's Console Pin Gate red on any pin at or after 6d5368ac8 (this repo's #5323).

What breaks

scripts/build-console.sh in objectstack sets OBJECTSTACK_SPEC_DIST so the console bundles that tree's spec instead of the last published one. apps/console/vite.config.ts honours it, and the console build then dies in generateBundle:

[plugin assert-lazy-linter-stays-lazy]
RolldownError: counter-probe failed: no eagerly loaded chunk contains an `@objectstack/spec` module.
(entry chunks: assets/index-mpVXni3j.js; eager chunks: 59/508)

The counter-probe is not wrong about what it saw — it is reading a module-id shape the injection deliberately abolishes, and it correctly refuses to return a verdict from a walk that sees nothing. The bug is upstream of its judgement.

The seam, verified on origin/main at apps/console/vite.config.ts

The file already computes an injection-aware spec test, and one consumer reads it while the other does not:

linewhat it does
321–322resolveSpecDistInjection(process.env.OBJECTSTACK_SPEC_DIST, { vendorChunkTest: VENDOR_OBJECTSTACK_TEST })
345–347const vendorObjectstackTest = specDistInjection ? specDistInjection.vendorChunkTest : VENDOR_OBJECTSTACK_TEST
442chunk grouping uses vendorObjectstackTestreads the injection-aware test
76–78assertLazyLinterStaysLazy() declares its own privateconst LINT = /@objectstack[\\/+]lint/ and const SPEC = /@objectstack[\\/+]spec/
366assertLazyLinterStaysLazy() is registered with no argument — it cannot see vendorObjectstackTest

Under the injection, resolveSpecDistInjection rewrites all 18@objectstack/spec* specifiers to absolute paths in the framework tree (/…/objectstack/packages/spec/dist/<sub>/index.mjs). Those ids contain no @objectstack segment at all, so the private SPEC regex matches zero modules and the counter-probe fires.

This is not path-shape-specific to one machine: on GitHub's runner the framework path is /home/runner/work/objectstack/objectstack/packages/spec, which also has no @objectstack segment.

Fix — contract-first

Make assertLazyLinterStaysLazy()take the spec test as a parameter and pass the injection-aware value at line 366, the same way line 442 already does. resolveSpecDistInjection already publishes the correct answer (its widened vendorChunkTest); the defect is a consumer that does not read its producer's output. Widening the private regex by hand, or having objectstack drop the injection, would both paper over that.

Review the LINT half in the same pass.@objectstack/lint is not injected today, so its regex still matches and the probe's primary assertion is unaffected — but objectstack#9659 proposes injecting four more @objectstack/* packages the same way, and lint is a plausible member of that set. Decide deliberately whether LINT should become injection-aware now or is genuinely out of scope; say which in the PR either way.

Reproduction — the injection is the only variable

Same tree, same commit, same command:

OBJECTSTACK_SPEC_DISTresult
setBuild failed in 11.73s, counter-probe error above
unsetEXIT=0, dist written

Acceptance

  • The console build succeeds withOBJECTSTACK_SPEC_DIST pointing at a framework spec dist, and the lazy-linter assertion still does its real job in that mode — i.e. the probe finds the injected spec in the eager closure rather than being skipped or defanged. A probe that passes because it stopped looking is a regression, not a fix.
  • The build still succeeds without the injection, with the assertion unchanged.
  • Reverse-verify: with the fix in place, deliberately make @objectstack/lint eager and confirm the linter assertion still goes red. A guard that can no longer fail is not a guard (perf(console): keep the lazily-imported @objectstack/lint out of the eager chunk #5323's own reasoning).
  • A changeset. This is build config for a private app, so state plainly whether anything publishes; empty frontmatter if not.

Non-goals

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions