fix(scan): parse pnpm v5.4/v6 lock grammars in the lockfile supplement - #203
Merged
Mikola Lysenko (mikolalysenko) merged 8 commits intoAug 19, 2026
Merged
Conversation
Fresh-clone scan of a pnpm 7 or pnpm 8 project discovered ZERO packages: inventory_pnpm_lock_at parsed only the v9 `name@version` key grammar, so v6 keys (`/name@1.2.8:`) produced a leading-slash name dropped fail-closed by is_safe_npm_name, and v5.4 keys (`/name/1.2.8:`) were skipped outright. Confirmed against real pnpm-7/8-emitted locks (2026-08-18 matrix). - split_pnpm_key handles all three grammars: peer-paren suffix trimmed, one leading slash stripped and remembered as legacy; legacy keys may use the v5 name/version form (segment after the last '/', truncated at the first '_' peer/hash suffix, digit-leading, no '@') — correctly parsing v5 peered keys like /styled-components/5.3.3_react@17.0.2; scoped v5 keys parse via the same rule. - The probe-failure fallback that reads a root pnpm-lock.yaml is narrowed to pnpm-specific refusals (vendor_lockfile_version_unsupported, vendor_pnpm_pnp_unsupported): a stale pnpm-lock.yaml left behind by a pnpm->yarn/bun migration is no longer resurrected as the live dependency set. Tests: grammar cases quoted verbatim from real captured 5.4/6.0 locks, plus migration regressions (stale lock behind .pnp.cjs / bun lock is not inventoried; legacy lock alone still is). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 6596621. Configure here.
…ouch trustLockfile, takeover reconciliation, revert guards (#213) * feat(pnpm): full pnpm 7-12 vendor+hosted support — legacy lock grammars, zero-touch trustLockfile, takeover reconciliation, revert guards Built and verified against real corepack-pinned pnpm 7.33.5 / 8.15.9 / 9.15.9 / 10.34.5 / 11.22.0 / 12.0.0-rc.7 (plus legacy 1-6 probes) in the 2026-08-18 e2e campaign. HOSTED, pnpm 7/8: the v5.4/v6.0 refusal is replaced by a real rewrite — every instance key of a dep is spliced (v5 /name/ver and _peer-suffixed, v6 (peer)-parenthesized; each owns its resolution), one ledger edit per instance; a post-splice residual detector refuses the dep set-wide if any instance shape the splice regex cannot claim remains (no partial rewrites). Frozen installs from empty stores land patched bytes on both majors; tamper fails ERR_PNPM_TARBALL_INTEGRITY. HOSTED, pnpm 11/12 zero-touch: rewriting a v9 root lock now auto-writes trustLockfile: true into pnpm-workspace.yaml (create with scaffold or byte-preserving append; ledger-recorded as redirect_pnpm_workspace_trust; --no-trust-lockfile-config opt-out; only ErrorKind::NotFound creates — an unreadable existing file falls back to guidance, never overwrite; re-scans heal a missing config on already-redirected locks). pnpm 11.22 and 12-rc frozen installs succeed with no flags and no CI changes; 9/10 ignore the key (verified); the sha512 pin still fails closed under trust. Warnings name the actual spliced host (userinfo stripped) and both per-major error codes, and pre-empt pnpm 12's own rebuild-the-lock advice that silently unpatches. VENDOR, pnpm 7/8: new pnpm-legacy backend (flavor-stamped so older binaries fail closed) — package.json pnpm.overrides + legacy lock surgery emitting exactly what those majors serialize (byte-stable under pnpm's own re-lock). pnpm <= 8 absolutizes file: specifiers, so frozen installs are path-bound: surfaced as vendor_pnpm_legacy_absolute_specifier, with plain `pnpm install --offline` as the moved-checkout path (marker bytes verified). Windows-shaped canonical paths are normalized (verbatim prefix stripped, forward slashes). CONVERSIONS + SAFETY: vendoring over a hosted-redirected npm-family purl now reconciles the redirect ledger (artifact-uuid-anchored matching — version-exact, v5 underscore keys claimed; degraded ledgers keep edits fail-closed), firing vendor_supersedes_redirect exactly once; vendor --revert byte-restores the hosted lock from the wiring originals. All six npm-family vendor backends refuse to delete an artifact the live lock still references when a repair-reconstructed entry has no wiring (vendor_wiring_unknown_revert_blocked — the revert-brick fix); repair stamps detected flavors and preserves corrupt artifacts when no rebuild source exists. Legacy-era diagnostics: shrinkwrap.yaml projects get pnpm-flavored no-lockfile guidance and join the lockfile-only supplement; vendored lock entries get redirect_pnpm_entry_vendored instead of entry-not-found. Tests: e2e_redirect_pnpm_build.rs (new hosted capstone: pnpm 7-11 real corepack legs incl. the zero-touch pnpm 11 proof, tamper negative, hermetic v5/v6 legs), e2e_vendor_pnpm_build.rs ladder (@9/@10/@11 + real pnpm 7/8 lifecycle legs), takeover/reconciliation/guard/heal unit + e2e suites — all RED-verified where behavior changed. CLI_CONTRACT.md and docs/ecosystems.md updated. Stacked on #203 (lock-inventory legacy grammars); trivially overlapping test hunks with #204/#208 carry identical content. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(pnpm-legacy): moved-checkout recovery needs --no-frozen-lockfile (pnpm defaults frozen on under CI) CI caught what local runs could not: pnpm turns --frozen-lockfile ON when CI=true, and the pnpm <= 8 moved-checkout recovery works precisely by re-resolving the path-bound absolute specifier — frozen semantics skip that re-resolution (pnpm 8: ERR_PNPM_OUTDATED_LOCKFILE; pnpm 7: stale-path install). The lifecycle legs' recovery step now passes --no-frozen-lockfile explicitly, and the vendor_pnpm_legacy_absolute_ specifier remedy (warning text, module doc, CLI_CONTRACT.md, docs/ecosystems.md) recommends `pnpm install --offline --no-frozen-lockfile` so real CI users get working advice. Verified: the full capstone (10 legs incl. real pnpm 7/8 lifecycles) passes under CI=true locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(pnpm-legacy): lock oracle uses the real path normalizer (Windows byte-exactness) Windows CI proved the production normalizer right and the test oracle wrong: the hermetic splice legs built their expected absolute specifier with raw canonicalize().display() — the \\?\C:\ verbatim form the normalizer exists to strip. normalize_canonical_root is now pub and the oracle consumes it at both assertion sites, so the expected string is built by the same transformation the backend writes and cannot drift. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(pnpm-legacy): in-file oracles also use the shared root normalizer Windows CI surfaced the same oracle-drift bug in the module's own unit tests: the fixture helper handed raw canonicalize().display() (verbatim \\?\C:\ form) to the ROOT_TOKEN substitution, the no-leak contains probe, and the moved-checkout fixture builder. All three now go through a canon_root_str() helper built on normalize_canonical_root, so every oracle spells the root exactly as the splice writes it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
enabled auto-merge (squash)
August 19, 2026 14:56
… embedded vex advisories ride the envelope Review feedback, two fixes: 1. The probe-failure discovery fallback inventoried a root pnpm-lock.yaml on vendor_lockfile_version_unsupported without looking at siblings — a pnpm→yarn/npm migration leaving an old refused lock behind surfaced DEAD pnpm resolutions as the live dependency set. The fallback now probes sibling live locks in the flavor router's own precedence (bun.lock, yarn.lock classic/berry, npm-shrinkwrap/package-lock) first; a sibling with entries wins, a present-but-empty sibling suppresses the legacy read entirely (blind beats dead resolutions), and only a genuinely lone legacy lock is inventoried as before. RED-verified: pre-fix, stale-lock-beside-live-yarn returned the dead pnpm dep. Note: supported legacy versions (5.4/6.0) route to the pnpm-legacy flavor at the probe and are governed by the router's documented pnpm-beats-yarn precedence (vendor_multiple_lockfiles warning) — this fix covers the versions the probe refuses. 2. Embedded --vex advisories (product_not_iri, vendored_tree_out_of_sync) were machine-invisible under a host's --json: note_warning silences stderr there and only the standalone vex envelope copied them out. VexSummary gains an additive warnings field (skip-if-empty; same RunWarning shape as the standalone envelope) populated by the apply, scan, and vendor hosts. Pinned by in_process_vendor::vendor_json_vex_warnings_ride_in_envelope (product advisory + the tree-sync disclosure + no-false-positive control), RED-verified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous commit's host hunks read VexWriteSummary.warnings, which is introduced by the vex diagnostics change (vex.rs note_warning plumbing + core verify.rs out-of-sync flag). Carrying those two files here keeps this branch self-contained; content is byte-identical to the sibling vex PR, so either merge order resolves cleanly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wenxin Jiang (Wenxin-Jiang)
approved these changes
Aug 19, 2026
Conflicts: - crates/socket-patch-cli/src/commands/vendor.rs: kept the branch's doc comment on the VexSummary warnings channel (code identical on both sides). - crates/socket-patch-cli/tests/in_process_vendor.rs: both sides appended tests at the same point — kept BOTH the branch's hosted_to_vendor_conversion module (#213) and main's section-12 drift-skip revert tests (#205). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er pre-revert The hosted→vendored conversion test pinned the pre-#206 semantics: vendor over a live hosted redirect fired the vendor_supersedes_redirect warning once and embedded the HOSTED splice as the wiring original. Main's #206 changed the contract — vendor now PRE-REVERTS the redirect (vendor_takeover_reverted_redirect), records the PRISTINE registry fragments as its originals, and --revert restores the registry lock. Rewritten as the pnpm twin of mode_migration_npm.rs's assertions: takeover advisory fires, supersede warning never does, no hosted residue in lock or vendor ledger, revert round-trips to the pristine registry lock. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Conflicts (crates/socket-patch-core/src/vendor/lock_inventory.rs): Main's #201 rewired inventory_npm_lock to Result<Option<...>, UnsupportedNpmLayout>, propagating the PnP-loader refusals (vendor_yarn_berry_unsupported, vendor_pnpm_pnp_unsupported) as a layout diagnosis instead of the calm None. This branch's #203/#213 work added lockfile fallbacks on the pnpm refusal codes, including reading the root lock under pnpm's node-linker=pnp. Resolution: main's semantics win for BOTH PnP codes — under a PnP loader the installed-tree crawl is structurally empty, so the honest scan outcome is the propagated diagnosis, not a lock-only inventory posing as a served project. The branch's fallbacks survive for the non-PnP refusals: vendor_lockfile_version_unsupported (live-sibling check, else direct root pnpm-lock.yaml read) and vendor_lockfile_missing (pnpm <=2 shrinkwrap.yaml). Test reconciliation: - pnpm_pnp_layout_still_inventories_root_lock → pnpm_pnp_layout_propagates_the_diagnosis (asserts the Err). - stale_pnpm_lock/stale_shrinkwrap behind a yarn-berry PnP marker now assert the propagated yarn diagnosis instead of None (same protective intent, new channel). - unsupported_flavors_yield_none's v6 comment updated: post-#213 the probe passes 6.0; the None comes from the dep-less lock. - Branch tests mechanically adapted to the Result signature. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
deleted the
fix/pnpm-lock-inventory-legacy
branch
August 19, 2026 16:35
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.

Problem
scan's lockfile-only supplement (the fresh-clone / partial-install rescue path) discovered zero packages from pnpm 7 and pnpm 8 projects, silently:inventory_pnpm_lock_atparsed only the v9name@versionkey grammar. v6 keys (/name@1.2.8:) produced a leading-slash name thatis_safe_npm_namedropped fail-closed, and v5.4 keys (/name/1.2.8:) were skipped by therfind('@')guard entirely. Confirmed against real pnpm-7/8-emitted locks in the 2026-08-18 e2e matrix.Fix
split_pnpm_key(base, legacy)handles all three grammars: peer-paren suffix trimmed first (v6/v9), one leading/stripped and remembered as legacy. Legacy keys may use the v5name/versionform — the segment after the last/, truncated at the first_(v5 peer/hash suffix), must be digit-leading with no@— which correctly parses v5 peered keys like/styled-components/5.3.3_react@17.0.2and scoped v5 keys. Anything unparseable is skipped, never guessed (the function keeps its fail-closed character).pnpm-lock.yamlis narrowed to the pnpm-specific version refusal (vendor_lockfile_version_unsupported). Previously it fired on any flavor-probe error, so a stalepnpm-lock.yamlleft behind by a pnpm→yarn-berry/bun migration was inventoried as the project's live dependency set. (Post-merge with fix(scan): surface yarn PnP refusal instead of silent success-0 across all modes #201, both PnP-loader refusals —vendor_yarn_berry_unsupportedandvendor_pnpm_pnp_unsupported— propagate as theUnsupportedNpmLayoutdiagnosis instead of falling back: under a PnP loader the installed-tree crawl is structurally empty, so the honest scan outcome is the surfaced refusal, not a lock-only inventory posing as a served project.)Verification
_-peered, v6 paren-peered, and v9-unchanged cases asserting exact (name, version) pairs..pnp.cjsmarker or a bun lock is not inventoried (the PnP marker now surfaces the fix(scan): surface yarn PnP refusal instead of silent success-0 across all modes #201 layout diagnosis); a legacy 5.4 lock alone still is; a pnpm-PnP layout propagatesvendor_pnpm_pnp_unsupportedinstead of inventorying the lock.🤖 Generated with Claude Code
Note
Medium Risk
Changes read-only lock discovery and fallback routing for scan supplements; incorrect parsing or overly broad fallback could misreport dependencies on fresh clones, though wiring/patch paths remain gated by the flavor probe.
Overview
Lockfile-only scan no longer returns an empty dependency set for pnpm 7/8 projects whose
pnpm-lock.yamluses v5.4 (/name/version) or v6 (/name@version) package keys.inventory_pnpm_lock_atnow routes keys throughsplit_pnpm_key, which strips peer parentheses, handles the leading/, and for legacy keys parses v5 slash-separated names/versions (including_peer/_hashsuffix stripping) while still failing closed on unparseable keys.When the npm flavor probe refuses the project, direct root
pnpm-lock.yamlinventory runs only for pnpm-specific codes (vendor_lockfile_version_unsupported,vendor_pnpm_pnp_unsupported)—so stale locks left after pnpm→yarn/bun migrations are not treated as the live set. Rush common-lock fallback on other probe failures is unchanged.Tests cover real v5/v6 lock shapes, exact v9 entry sets, migration regressions (yarn PnP / bun markers), and pnpm-PnP layouts that should still inventory.
Reviewed by Cursor Bugbot for commit 6596621. Configure here.