fix(deps): realign apps/web lockfile with declared ranges - #1114
Conversation
Six apps/web dependencies resolved *below* the ranges declared in apps/web/package.json. npm ls --all flagged each as `invalid`: @opentelemetry/api 1.9.1 (exact) -> locked 1.9.0 @sentry/nextjs ^10.66.0 -> locked 10.65.0 @stripe/stripe-js ^9.10.0 -> locked 9.9.0 @tailwindcss/postcss ^4.3.3 -> locked 4.3.2 tailwindcss ^4.3.3 -> locked 4.3.2 autoprefixer ^10.5.4 -> locked 10.5.2 Root cause: the lockfile's *recorded* ranges under packages["apps/web"] are byte-identical to the manifest, so npm's sync validation considers the edges satisfied and reify leaves the stale nodes in place. npm never re-checks a resolved node version against the range it is bound by, which is why `npm ci` passes, plain `npm install` is a no-op (lockfile SHA-256 unchanged after a full install), and only `npm ls` surfaces the drift. Fix: evict the six stale nodes (plus their nested subtrees) from the lockfile and let `npm install --package-lock-only` re-resolve exactly those edges. Preferred over the full `rm package-lock.json` regeneration suggested in #1110 because it bounds the blast radius to the affected subtrees instead of churning unrelated transitives. Resulting semantic delta is confined to the six targets, their dedupe hoist from apps/web/node_modules to the root, and four transitive bumps that the upgrades *require*: @apm-js-collab/code-transformer 0.15.0 -> 0.18.1 @apm-js-collab/code-transformer-bundler-plugins 0.5.0 -> 0.7.3 @apm-js-collab/tracing-hooks 0.10.1 -> 0.13.0 enhanced-resolve 5.21.6 -> 5.24.5 The first three are demanded by @sentry/server-utils@10.69.0 (^0.7.3 / ^0.13.0 / ^0.18.0); the fourth by @tailwindcss/node@4.3.3 (^5.24.1). Verification against the #1110 acceptance criteria: - package.json and apps/web/package.json byte-identical (SHA-256 verified) - npm ls --all invalid markers: 6 -> 0 - all 17 root-override resolutions unchanged (next 16.2.12, postcss 8.5.25/8.5.21, protobufjs 7.6.5, sharp 0.35.3, @hono/node-server 2.0.12, picomatch 4.0.5, nested minimatch>brace-expansion 1.1.18/2.1.4) - npm audit: 12 high before, 12 high after, zero new advisories - npm ci --legacy-peer-deps: exit 0 - npm run build:web: exit 0, route table identical to baseline - apps/web lint: exit 0 - apps/web type-check: exit 0 Refs #1110 Refs #1112 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔍 PR Validation |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned Files
|
Agent Completion Truth Gate: NOT_APPLICABLEEvidence agrees. Machine-readable verdict{
"details": {},
"reasons": [],
"verdict": "not_applicable"
} |
|
Automated PR-remediation scan — substance is green; blocked only on governance + human merge Real CI is fully passing on head
I did not merge: this run is unattended and Generated by Claude Code |
|
Returned this PR to draft at exact head The code/check evidence is currently green, but independent review did not materialize: Copilot could not review the lockfile and CodeRabbit skipped because the PR had no qualifying label. The required implementation execution receipt is also absent. I added |
|
Automated PR-remediation scan @ head Re-scanned after this PR returned to ready-for-review. State has materially improved since the 20:46 scan:
I did not merge: this is an unattended scheduled run, base Staged merge command (for a human): Generated by Claude Code |
|
✅ Action performedReview finished.
|
|
Returned to draft again at exact head Material progress is preserved: CodeRabbit approved this exact head at |
Closing — branch orphaned by the secret-purge force-pushNo common ancestor with current The work is unlanded — realigning the Branch retained for archive-tagging. Generated by Claude Code |
Pull request was closed
Canonical issue
Closes #1110
Duplicate report: #1112 (a self-correction of #1110's claim that
npm ciwould reject the lockfile — it does not). Both describe the same defect; #1110 is canonical and #1112 will be closed as a duplicate.Outcome
apps/webnow installs the dependency versions its manifest actually declares. Six packages were silently resolving below their declared floors, so every developer, CI run, and production build was shipping older code than the manifest promised — including a Sentry SDK a minor version behind and an@opentelemetry/apithat violated an exact pin.npm ls --allgoes from 6invalid:entries to 0, with no manifest edit and no new advisories.Scope
package-lock.jsononly — re-resolution of the six driftedapps/webedges plus the four transitive bumps those upgrades require.package.jsonedit (apps/web lockfile drift: six dependencies resolve below their declared ranges #1110 forbids it), thebrace-expansionoverride floors (filed as brace-expansion override floors permit OOM-vulnerable versions (^1.1.16 / ^2.1.2 are below the actual backport) #1115), the flaky billing test (filed as billing-chat-gating free-tier test depends on ambient AI_GATEWAY_API_KEY and passes vacuously in CI #1116), and any new CI guard (reasoning below).Risk
@sentry/nextjs10.65.0 → 10.69.0 (the only minor bump) or from the four forced transitive bumps. Mitigated by build + type-check + lint + 244/245 tests + an identical Next.js route table, all green on this head.236b6597f). The lockfile is self-contained and no other file depends on it, so revert restores the exact previous tree byte-for-byte.Verification
Every acceptance criterion in #1110, checked on head
236b6597f:apps/websuite 244/245 pass; the single failure is proven pre-existing and environmental (see below).build,test,lint-frontend,lint-python,npm-audit,dependency-review,CodeQL,trivy,bandit,gitleaks,guardsall pass on this head.package.jsonandapps/web/package.jsonSHA-256 verified byte-identical before and after.npm ls --allreports zeroinvalid:— 6 → 0, checked against the realnpm ci-installed tree, not just a lockfile parse.IDENTICAL):next16.2.12,postcss8.5.25/8.5.21,protobufjs7.6.5,sharp0.35.3,@hono/node-server2.0.12,picomatch4.0.5, and both nestedminimatch > brace-expansionpins (1.1.18 / 2.1.4).npm audit12 high before → 12 high after; set-differenced by advisory key:NEW advisories introduced: NONE.npm ci --legacy-peer-depsexit 0npm run build:webexit 0, route table diffed against baseline: identicalapps/weblint exit 0tsc --noEmitexit 0Production evidence
Vercel preview built and deployed successfully from this exact head (
236b6597f): https://vercel.com/garv1/v0-uvai/45sxaJMDnGTBMi54SzwJuBy8dMHL —Vercel,Vercel Agent Review, andVercel Preview Commentsall report SUCCESS. The Next.js production build emitted a route table byte-identical to the pre-change baseline, so the deployed surface is unchanged; the delta is confined to the versions of the code behind it.What was wrong
Six
apps/webdependencies resolved below the ranges declared inapps/web/package.json.npm ls --allflagged each asinvalid:@opentelemetry/api1.9.1(exact)@sentry/nextjs^10.66.0@stripe/stripe-js^9.10.0@tailwindcss/postcss^4.3.3tailwindcss^4.3.3autoprefixer^10.5.4Root cause (verified, not assumed)
The lockfile's recorded ranges under
packages["apps/web"]are byte-identical to the manifest:npm's sync validation compares recorded range vs manifest range only. It never re-checks the resolved node version against the range binding it. So the edge looks satisfied, reify leaves the stale node untouched, and a plain
npm installis a genuine no-op — confirmed by SHA-256: the lockfile was byte-identical after a fullnpm install --legacy-peer-deps. This is exactly whynpm cipasses while six packages sit below their floors, as #1112 correctly established.Approach — surgical, not nuclear
#1110 suggested
rm package-lock.json, while noting it "carries real risk (it may move many unrelated transitive versions)". Its hard requirements are outcome-based, so I bounded the blast radius instead: evict only the six stale nodes plus their nested subtrees, then letnpm install --package-lock-onlyre-resolve exactly those edges.The resulting semantic delta is confined to the six targets, their dedupe-hoist from
apps/web/node_modulesto the root, and four transitive bumps that the upgrades require — each traced to a concrete requirer rather than accepted as churn:@apm-js-collab/code-transformer^0.18.0@apm-js-collab/code-transformer-bundler-plugins@sentry/server-utils@10.69.0wants^0.7.3@apm-js-collab/tracing-hooks@sentry/server-utils@10.69.0wants^0.13.0enhanced-resolve@tailwindcss/node@4.3.3wants^5.24.1The raw diff is 2822 lines, which overstates the change: most of it is dedupe-hoisting (
apps/web/node_modules/X→node_modules/X), not version movement. The semantic delta is 38 nodes added, 43 removed, and only the 4 version bumps above.The one failing test is pre-existing and environmental
billing-chat-gating.test.ts > blocks free tier after daily quotatimes out at 5000ms locally. I did not assume it was unrelated:origin/main(abd93326b) into a separate worktree, ran its ownnpm ci, ran that test file — fails identically with no changes applied./api/chatfalls through togenerateText()against the Vercel AI Gateway, which is never mocked (the test mocks onlygrok-client, which covers the Pro path). Route line 124 short-circuits to 503 when no gateway key is present, so the test passes wherever no key is configured and hangs wherever one is:That is why CI's
testjob passes on this PR — and also why it passes vacuously: in CI every loop iteration returns 503, soexpect(res.status).not.toBe(402)is satisfied by the gateway being unconfigured rather than by free tier actually being served. Filed as #1116; out of scope here, since #1110 requires this be a standalone lockfile change.Note on the
brace-expansionoverrides#1110 asks that the nested
minimatch > brace-expansionpins be re-verified. They are preserved unchanged and confirmed above (1.1.18 / 2.1.4, identical to baseline).While verifying them I measured the installed copies against the OOM advisory directly rather than trusting advisory metadata, and found that the tree is safe but the floors are not: the 100000-entry expansion cap that remediates GHSA-mh99-v99m-4gvg landed in 1.1.17 and 2.1.3, whereas the pins are
^1.1.16and^2.1.2— both demonstrably OOM-vulnerable when installed directly. Today the carets float up to safe versions, so the outcome is correct but incidental rather than guaranteed. Raising the floors requires editing rootpackage.json, which #1110 forbids here, so it is filed as #1115 with the full per-version measurements and shipped in #1117.Guarding against recurrence
Deliberately proposing no CI change here. As #1112 demonstrated,
npm ciis structurally incapable of catching this class of drift, so adding it as a guard would be cargo cult. The check that would catch it isnpm ls --allgrepped forinvalid, which I'd suggest evaluating on its own merits rather than smuggling into a lockfile fix.CI note
Agent completion enforcementreports failure withmissing_trusted_publication. This is repo-wide and pre-existing: it requires a check run namedAgent Lock trusted publicationpublished by a trusted GitHub App, and that App is not publishing. PRs #1108, #1103 and #1098 all show the same failure with zero such check runs and were merged regardless. Nothing in this PR affects it.