fix(workspace): stop apps/loopover-ui silently resolving a stale published @loopover/contract - #9797
Conversation
…ished contract apps/loopover-ui declared "@loopover/contract": "^0.1.0". While contract sat at 0.1.0 that matched the workspace copy and npm linked it. The moment release-please bumped contract into the engine-and-dependents group at 3.17.0, the range stopped matching -- and npm did NOT fail. It resolved the dependency from the REGISTRY, installing the published 0.1.0 tarball into apps/loopover-ui/node_modules while every other consumer used 3.17.0. The website would have built against a contract ~17 minor versions stale: same import specifiers, different schemas and types, no error anywhere. It surfaced only as an `npm ci` lockfile-sync failure on the release PR (#9780), which reads like a mechanical lockfile problem rather than the dependency bug it is. Pin the private apps to "*" -- the one range that means "the workspace copy" and cannot drift -- and add workspace-dep-ranges:check. The check requires "*" for PRIVATE packages rather than merely checking that a range is satisfied, because satisfaction alone cannot see the trap: `^0.1.0` against contract@0.1.0 is valid today and silently wrong the day contract is bumped. That rule caught a second latent instance, discovery-index pinning engine to ^3.4.0. Published packages keep real ranges (npm consumers need them) and are checked for satisfaction instead.
|
Caution 🛑 LoopOver review result - fixes requiredReview updated: 2026-07-29 10:17:58 UTC
Review summary Nits — 5 non-blocking
Why this is blocked
📋 Copy for AI agents — paste into your coding agentCI checks failing
Decision drivers
Context & advisory signals — never blocks the verdict
Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Decision record
Visual preview
Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. Scroll preview
A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
loopover-ui | 7c2d1b0 | Jul 29 2026, 09:55 AM |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9797 +/- ##
=======================================
Coverage 90.33% 90.33%
=======================================
Files 918 918
Lines 113936 113936
Branches 26975 26975
=======================================
Hits 102926 102926
Misses 9681 9681
Partials 1329 1329
Flags with carried forward coverage won't be shown. Click here to find out more. |
Bundle ReportBundle size has no change ✅ |
…ge-drift # Conflicts: # package.json


The bug
apps/loopover-uideclared"@loopover/contract": "^0.1.0".While contract sat at
0.1.0that range matched the workspace copy, npm linked it, and everything was fine. The moment release-please bumped contract into theengine-and-dependentsgroup at 3.17.0,^0.1.0stopped matching — and npm did not fail. It quietly resolved the dependency from the registry, installing the published0.1.0tarball intoapps/loopover-ui/node_moduleswhile every other consumer used3.17.0:The website would have built against a contract ~17 minor versions stale — same import specifiers, different schemas and types, no error anywhere.
How it surfaced
As an opaque
npm cifailure on the release PR (#9780):That reads like a mechanical lockfile problem. Regenerating the lock "fixes" CI while cementing the bug — the regenerated lock is the one containing the registry fallback above. Worth flagging, since the obvious remedy is the wrong one.
The fix
Pin the private apps to
"*"— the one range that means "the workspace copy" and cannot drift — and addworkspace-dep-ranges:checktotest:ci.Why the check requires
"*"for private packages rather than just checking satisfaction: satisfaction alone cannot see the trap.^0.1.0againstcontract@0.1.0is perfectly valid today and silently wrong the day contract is bumped, so a satisfaction-only rule would have passed onmainand only failed inside a release PR — exactly what happened. A private package is never installed from a registry, so a pinned range buys it nothing and can only rot.That stricter rule immediately caught a second latent instance:
@loopover/discovery-indexpinning@loopover/engineto^3.4.0. Satisfied today, primed to break on the next major.Published packages keep meaningful ranges — real consumers install them from npm — so for those the check verifies satisfaction instead.
Tests
8 cases, including a regression for the real drift, a regression proving the trap is caught before the bump springs it, and an invariant that this repo's own workspace stays clean.
tsc,dead-source-files,import-specifiers,no-hand-written-js,db:migrations:immutableall green.