[ENG-3120] Fall back to the installed truth when the lockfile is stale#31
Merged
Merged
Conversation
Verified live on the ENG-3120 Lovable sample app: `npm install -D @patchstack/connect` (the README's own instruction) plants a package-lock.json in a bun-managed project. Lovable's native dependency flow then updates package.json / bun lockfile / node_modules but never that file — and detectLockfile's fixed priority reads package-lock.json first, so every scan and mark-build fingerprint froze at the moment of that npm install. Dependencies added afterwards (including vulnerable ones) would never reach the manifest: a silent false-safe one layer below the parity system. scanLockfile now validates each source against package.json (dependencies + devDependencies, ignoring file:/link:/workspace:/portal: specifiers). A source missing declared dependencies is a fossil: fall through to the next candidate, ultimately walking node_modules/ — the installed truth regardless of package manager — and surface warnings on the new Manifest.warnings field (printed by scan and mark-build). A fully-consistent lockfile stays authoritative, no-package.json behaves exactly as before, and nothing fails harder than today. README: recommend the project's own package manager for installs (bun add -d on Lovable/Bolt) and document the stale-lockfile behavior.
|
Robust fallback logic elegantly handles stale lockfiles with clear diagnostics. 🎯 Quality: 100% Elite · 📦 Size: Medium 📈 This month: Your 57th PR — above team average · Averaging Excellent |
Contributor
Author
|
/review |
daniloradovic
approved these changes
Jul 3, 2026
Merged
devlob
added a commit
that referenced
this pull request
Jul 6, 2026
Ships the stale-lockfile fallback (#31) to npm. Claude-Session: https://claude.ai/code/session_017di4qAcBaFYxtHnZKDw7Wj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug (found live, on the ENG-3120 Lovable sample app)
Three consecutive Lovable republishes — including one that added a dependency — produced the byte-identical build fingerprint (
0862cb8967c0), and the server manifest never gained the new package. Root cause chain, verified against production data:npm install --save-dev @patchstack/connect) was run on a bun-managed Lovable project → npm planted apackage-lock.json.package.json, the bun lockfile andnode_modules/— but neverpackage-lock.json.detectLockfilepickspackage-lock.jsonfirst by fixed priority, so every subsequentscanandmark-buildread the fossil.The manifest and fingerprint freeze at the moment of that one
npm install. A vulnerable package added afterwards through the platform's native flow would never reach Patchstack — a silent false-safe one layer below the manifest-parity system this ticket built. (Server-side evidence: the site's manifest only changed on days someone explicitly prompted annpm install; the natively-added dependency never appeared.)The fix
scanLockfilenow validates every source againstpackage.json(dependencies+devDependencies, ignoringfile:/link:/workspace:/portal:specifiers):node_modules/, the installed truth regardless of package manager.Manifest.warningsfield, printed byscanandmark-build(naming the stale file and the missing packages).package.json→ exactly the old behavior. Nothing fails harder than today (mark-buildstays best-effort).README: recommends installing with the project's own package manager (
bun add -don Lovable/Bolt) and documents the stale-lockfile behavior.Testing
vitest run— 113/113 (6 new: fossil-shadowed-by-node_modules, bare-node_modules last resort, consistent-lockfile-stays-authoritative + no-warnings, non-registry specifiers ignored, nothing-consistent still scans with warning, no-package.json unchanged)tsc --noEmitcleanAfter merge
Needs a release (
v0.2.8) to reach npm — publish fires on a published GitHub release, notworkflow_dispatch(that path is dry-run only).Ref ENG-3120
🤖 Generated with Claude Code
Ground truth confirmed after filing (case closed)
The sample project's actual
package.jsonand repo tree settle the causal chain conclusively:package.jsondeclares"dayjs": "^1.11.21"(added through Lovable's native flow) — yet three subsequent builds'mark-buildfingerprints stayed byte-identical, meaning the lockfile parsed at build time does not contain dayjs: stale versuspackage.json, precisely the condition this PR detects..gitignoredoesn't hide one) — so the fossilpackage-lock.jsonlives in Lovable's persistent build workspace, created by the one-offnpm install -D @patchstack/connect@0.2.7prompt. The platform's own installer maintainsnode_modulesbut never rewrites that file.@esbuild/*variants (aix-ppc64,android-arm, …) — only a lockfile parse lists those; anode_moduleswalk would see a single platform binary. Confirms the scan is reading the fossil, not the installed truth.With this fix, that exact state produces:
package-lock.json looks stale: package.json declares 1 dependency it does not contain (dayjs)→ falls through → walksnode_modules/→ dayjs reaches the manifest and the fingerprint moves.