Skip to content

[ENG-3120] Fall back to the installed truth when the lockfile is stale#31

Merged
devlob merged 1 commit into
mainfrom
feature/ENG-3120-stale-lockfile-detection
Jul 6, 2026
Merged

[ENG-3120] Fall back to the installed truth when the lockfile is stale#31
devlob merged 1 commit into
mainfrom
feature/ENG-3120-stale-lockfile-detection

Conversation

@devlob

@devlob devlob commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

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:

  1. The README's install instruction (npm install --save-dev @patchstack/connect) was run on a bun-managed Lovable project → npm planted a package-lock.json.
  2. Lovable's native dependency flow (bun) updates package.json, the bun lockfile and node_modules/ — but never package-lock.json.
  3. detectLockfile picks package-lock.json first by fixed priority, so every subsequent scan and mark-build read 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 an npm install; the natively-added dependency never appeared.)

The fix

scanLockfile now validates every source against package.json (dependencies + devDependencies, ignoring file:/link:/workspace:/portal: specifiers):

  • A source missing declared dependencies is treated as stale → fall through to the next candidate → ultimately walk node_modules/, the installed truth regardless of package manager.
  • Diagnostics surface on the new Manifest.warnings field, printed by scan and mark-build (naming the stale file and the missing packages).
  • A fully-consistent lockfile stays authoritative (deterministic full graph preferred over the walk).
  • No package.json → exactly the old behavior. Nothing fails harder than today (mark-build stays best-effort).

README: recommends installing with the project's own package manager (bun add -d on 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 --noEmit clean

After merge

Needs a release (v0.2.8) to reach npm — publish fires on a published GitHub release, not workflow_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.json and repo tree settle the causal chain conclusively:

  • package.json declares "dayjs": "^1.11.21" (added through Lovable's native flow) — yet three subsequent builds' mark-build fingerprints stayed byte-identical, meaning the lockfile parsed at build time does not contain dayjs: stale versus package.json, precisely the condition this PR detects.
  • The repo contains no lockfile at all (and .gitignore doesn't hide one) — so the fossil package-lock.json lives in Lovable's persistent build workspace, created by the one-off npm install -D @patchstack/connect@0.2.7 prompt. The platform's own installer maintains node_modules but never rewrites that file.
  • The manifest contains all-platform @esbuild/* variants (aix-ppc64, android-arm, …) — only a lockfile parse lists those; a node_modules walk 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 → walks node_modules/ → dayjs reaches the manifest and the fingerprint moves.

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.
@coderbuds

coderbuds Bot commented Jul 3, 2026

Copy link
Copy Markdown

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

See how your team is trending →

@devlob

devlob commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

/review

@devlob devlob merged commit 7be8f24 into main Jul 6, 2026
4 checks passed
@devlob devlob deleted the feature/ENG-3120-stale-lockfile-detection branch July 6, 2026 05:49
@devlob devlob mentioned this pull request Jul 6, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants