Repository navigation
assertLazyLinterStaysLazy ignores resolveSpecDistInjection's output — the counter-probe fails every console build made with OBJECTSTACK_SPEC_DIST set #5388
Copy link
Copy link
Closed
Labels
Description
Activity
- addedbugSomething isn't workingSomething isn't workingand removed
on Aug 20, 2026 - added a commit that references this issue
on Aug 20, 2026 os-project-manager commented
on Aug 20, 2026 CollaboratorAuthorMore actions{ "issue": 5388, "status": "done", "branch": "claude/issue-5388-lazy-linter-injection", "pr": "https://github.com/objectstack-ai/objectui/pull/5390", "premise_still_valid": true, "summary": "Every line number in the issue was re-verified on origin/main (0fd11444a) before editing and all were accurate. resolveSpecDistInjection now publishes specModuleTest beside vendorChunkTest, widened by one shared widen() rule; assertLazyLinterStaysLazy takes the spec test as a parameter and the config hands it the injection-aware value, exactly as the vendor-objectstack group does at line 442. specModuleTest is deliberately NOT vendorChunkTest, because the latter is the whole vendor scope minus lint and an eager @objectstack/client would satisfy it without proving the walk can see the spec chunk. LINT decision: NOT made injection-aware (no producer publishes a lint location today, so it could only be speculative and untestable), but it gains its own counter-probe, because a blind LINT fails SILENT where a blind SPEC fails LOUD. One measurement in the issue and the dispatch is wrong and is corrected in the PR body: @object-ui/console is NOT a private app. It has no private:true, carries publishConfig.access public, is one of the 40 packages in the .changeset fixed group and ships its built dist/. Nothing publishes here for a narrower reason: vite.config.ts and repo tooling are not in its files list and the emitted bundle is unchanged. Empty-frontmatter changeset added; this repo has no skip-changeset label and none was minted.", "tests": "All at HEAD b7b2b3b8c (= the final commit; the gate union below was run on that tree). BASELINE FIRST, on unfixed origin/main: OBJECTSTACK_SPEC_DIST set => EXIT=1, 'counter-probe failed: no eagerly loaded chunk contains an @objectstack/spec module ... (entry chunks: assets/index-BInyohVQ.js; eager chunks: 58/507)'; unset => EXIT=0. WITH FIX: set => EXIT=0, 8592 modules transformed; unset => EXIT=0, 8592 modules transformed. The probe FOUND the injected spec rather than skipping: the injected build's own dist/stats.html carries 13 distinct module ids under the override directory and 0 ids from the installed @objectstack+spec@17.0.0. REVERSE VERIFICATION, direction predicted before running (plain RED, and on the LINT message not the counter-probe): negative lookaheads removed from VENDOR_OBJECTSTACK_TEST so the vendor group claims the linter => EXIT=1 both with and without the override, 'RolldownError: [assert-lazy-linter-stays-lazy] `@objectstack/lint` is in the EAGER closure (assets/vendor-objectstack-BXk7O0mY.js), so every console page load pays for it - ~89 KiB gzipped'. Ablation applied on top of the committed fix and reverted with git checkout HEAD --; tree verified clean afterwards. UNIT: vitest run --project unit over vite-objectstack-spec-dist + vitest-config-alias-targets-3944 + side-effects-declaration-consistency + check-type-check-coverage => 'Test Files 4 passed (4) / Tests 381 passed (381)'. GATES at b7b2b3b8c: type-check:scripts [0]; tsc -b apps/console/tsconfig.node.json --force [0] (the project that actually contains vite.config.ts and scripts/vite-*.ts); eslint on all 3 changed files [0] (5 pre-existing-style no-explicit-any warnings, 0 errors); check-changeset-presence [0]; check-changeset-no-major [0]; check-control-bytes [0]. NOT RUN and why: apps/console's full 'tsc --noEmit' half needs a built workspace dep closure that does not exist in a fresh worktree (no packages/*/dist anywhere) - it fails identically on unmodified origin/main with TS2307/TS2882 on @object-ui/* and covers src/, which this diff does not touch. The full gate farm runs once in CI.", "open_questions": [], "out_of_scope_findings": [ "filed as #5391 (label: finding, unassigned): resolveSpecDistInjection validates the override's exports targets but not that the overriding package's own dependencies resolve from its location - a dep-less spec dist produces no error from the helper and fails ~80s later as a rolldown 'failed to resolve zod', naming zod rather than OBJECTSTACK_SPEC_DIST. Noted alongside it: while 5388 was unfixed that resolve error was completely invisible, because the counter-probe threw in generateBundle before rolldown escalated the accumulated resolve errors." ] }
Generated by Claude Code
- added a commit that references this issue
on Aug 23, 2026
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 after6d5368ac8(this repo's #5323).What breaks
scripts/build-console.shin objectstack setsOBJECTSTACK_SPEC_DISTso the console bundles that tree's spec instead of the last published one.apps/console/vite.config.tshonours it, and the console build then dies ingenerateBundle: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/mainatapps/console/vite.config.tsThe file already computes an injection-aware spec test, and one consumer reads it while the other does not:
resolveSpecDistInjection(process.env.OBJECTSTACK_SPEC_DIST, { vendorChunkTest: VENDOR_OBJECTSTACK_TEST })const vendorObjectstackTest = specDistInjection ? specDistInjection.vendorChunkTest : VENDOR_OBJECTSTACK_TESTvendorObjectstackTest✅ reads the injection-aware testassertLazyLinterStaysLazy()declares its own privateconst LINT = /@objectstack[\\/+]lint/andconst SPEC = /@objectstack[\\/+]spec/assertLazyLinterStaysLazy()is registered with no argument — it cannot seevendorObjectstackTest❌Under the injection,
resolveSpecDistInjectionrewrites all 18@objectstack/spec*specifiers to absolute paths in the framework tree (/…/objectstack/packages/spec/dist/<sub>/index.mjs). Those ids contain no@objectstacksegment at all, so the privateSPECregex 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@objectstacksegment.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.resolveSpecDistInjectionalready publishes the correct answer (its widenedvendorChunkTest); 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
LINThalf in the same pass.@objectstack/lintis 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 whetherLINTshould 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_DISTBuild failed in 11.73s, counter-probe error aboveEXIT=0, dist writtenAcceptance
OBJECTSTACK_SPEC_DISTpointing 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.@objectstack/linteager 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).Non-goals
resolveSpecDistInjectionrewrites.