feat(pnpm): pnpm 7-12 vendor+hosted support — legacy grammars, zero-touch trustLockfile, takeover reconciliation, revert guards - #213
Merged
Mikola Lysenko (mikolalysenko) merged 4 commits intoAug 19, 2026
Conversation
…rs, 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>
…(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>
… 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>
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>
Mikola Lysenko (mikolalysenko)
merged commit Aug 19, 2026
bdc47f4
into
fix/pnpm-lock-inventory-legacy
172 of 175 checks passed
Mikola Lysenko (mikolalysenko)
deleted the
feat/pnpm-legacy-and-zero-touch-trust
branch
August 19, 2026 14:54
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
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>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Aug 19, 2026
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)
added a commit
that referenced
this pull request
Aug 19, 2026
#203) * fix(scan): parse pnpm v5.4/v6 lock grammars in the lockfile supplement 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> * feat(pnpm): pnpm 7-12 vendor+hosted support — legacy grammars, zero-touch 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> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
Closes the pnpm support gap end to end: every pnpm major since 7 now works in vendor and hosted mode, pnpm 11/12 hosted installs are zero-touch (no flags, no CI changes), and the conversion/revert/repair lifecycle around them is fail-closed. Everything here was built spike-first 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 in the 2026-08-18 e2e campaign (54 matrix/probe legs), then adversarially reviewed (10 verified findings, all remediated in this diff, each RED-verified).
Stacked on #203 (lock-inventory legacy grammars — this diff extends the same file). Three test hunks overlap #204/#208 with identical content (env-var mirrors for the new global flag); merge those first and this rebases clean.
Hosted mode
/name/ver+_peer-suffixed, v6 parenthesized-peer; each key owns its ownresolution:), one revert-ledger edit per instance. A post-splice residual detector refuses the dep set-wide if any instance shape the splice cannot claim remains — no partial rewrites (review finding: nested-paren suffixes provably unmatchable by the regex are now a refusal, not a silent skip). Proven: frozen installs from empty stores land patched bytes on real pnpm 7/8; tamper failsERR_PNPM_TARBALL_INTEGRITY.trustLockfile: trueintopnpm-workspace.yaml(create-with-scaffold or byte-preserving merge; ledger editredirect_pnpm_workspace_trust;--no-trust-lockfile-configopt-out). Hardened per review: onlyErrorKind::NotFoundcreates (an unreadable existing file gets a warning, never an overwrite), and re-scans heal a missing config on already-redirected locks. Verified: pnpm 11.22 + 12-rc frozen installs succeed with no flags; 9/10 ignore the key; the sha512 pin still fails closed under trust. The warning names the actual spliced host (userinfo stripped, only spliced-lock hosts), both per-major error codes, and pre-empts pnpm 12's ownpnpm clean --lockfileadvice that silently unpatches; legacy locks get legacy-correct guidance (pnpm 7/8 reject--trust-lockfile).Vendor mode
pnpm-legacybackend for 5.4/6.0 locks (flavor-stamped so older binaries fail closed on revert):package.jsonpnpm.overrides+ lock surgery emitting byte-exactly what those majors serialize (idempotent under pnpm's own re-lock; byte-identical revert). Discovered + documented inherent limit: pnpm ≤ 8 absolutizesfile:specifiers, so frozen installs are path-bound — surfaced asvendor_pnpm_legacy_absolute_specifier; a moved checkout runs onepnpm install --offline(marker bytes verified). Windows-shaped canonical paths normalized (verbatim prefix stripped, forward slashes; synthetic-input unit tests — a Windows CI leg should confirm pnpm's own emission spelling).pnpm-workspace.yamlamong committables and tell users to reinstall (the live tree keeps pre-vendor bytes by design).Conversions, revert safety, repair
vendor_supersedes_redirectfires exactly once.vendor --revertbyte-restores the hosted lock from the wiring originals.vendor_wiring_unknown_revert_blocked, recovery guidance included); repair stamps detected flavors so the right guard fires, and preserves corrupt artifacts when no rebuild source exists (forensics over ENOENT).shrinkwrap.yamlprojects (pnpm ≤ 2) get pnpm-flavored no-lockfile guidance and join scan's lockfile-only supplement; a vendored lock entry getsredirect_pnpm_entry_vendoredinstead of a misleading entry-not-found;--mode redirectparses as the legacy alias of hosted.Tests
e2e_redirect_pnpm_build.rs— the hosted capstone pnpm never had (npm/yarn/bun did): real corepack pnpm 7/8/9/10/11 legs (the pnpm 11 leg is the zero-touch proof + opt-out control), tamper negative, hermetic v5/v6 splice legs. 14/14 green locally including all gated legs.e2e_vendor_pnpm_build.rsladder: Anson (@10) primary, @9/Doug Rudolph (@11) opportunistic, real pnpm 7/8 full-lifecycle legs (same-path frozen+offline proof, moved-checkout recovery, byte-identical revert).Review status
Two adversarial review rounds ran over this campaign's diffs (10 + 10 verified findings); all are fixed here or in the sibling PRs #202/#203/#204/#208. Known accepted residuals are documented in code/PR-body where they live (rollback fan-out of patch-added files in store twins fails toward patched bytes; pnpm ≤ 3 custom non-
registry.*hosts; Windows emission spelling needs a Windows CI leg).🤖 Generated with Claude Code
Note
Medium Risk
Changes lockfile and workspace mutation paths, redirect/vendor ledger persistence, and install-time pnpm trust behavior—high impact for CI installs but heavily covered by new e2e and contract tests.
Overview
pnpm hosted (
scan --mode hosted) now rewrites lockfileVersion 5.4/6.0/9.0 grammars (not only v9), with pnpm-specific no-lockfile diagnostics and clearer warnings when a lock entry is already vendored. For root v9 locks, the CLI can auto-writetrustLockfile: trueintopnpm-workspace.yaml(ledger editredirect_pnpm_workspace_trust), with heal-on-rerun when the lock was already redirected, safe handling of unreadable workspace files, and opt-out via new global--no-trust-lockfile-config/SOCKET_NO_TRUST_LOCKFILE_CONFIG. Legacy locks get guidance that does not recommend--trust-lockfileon pnpm 7/8.Vendor / repair / mode switches: Documents and implements
pnpm-legacyvendor flavor for 5.4/6.0 locks; post-vendor human hints namepnpm-workspace.yamlfor pnpm ≥11 and suggest reinstall commands sonode_modulescatches rewired locks. When vendoring supersedes hosted redirects, npm-family purls can automatically drop matching halves fromredirect-state.json(with reconciled vs manual warning text).repairstamps reconstructed ledger entries with the npm lock flavor from the referencing lockfile, movesvendor_wiring_unknownto run-levelwarnings[], and defers deleting corrupt vendor artifacts until a rebuild source exists.Contracts & tests:
CLI_CONTRACT.mdand rollback JSON semantics (package_not_installedmarker records) are updated; newe2e_redirect_pnpm_build.rsand expanded CLI/apply invariant tests pin trust config, splice grammars, and unmatched-purl exit behavior.Reviewed by Cursor Bugbot for commit 2d49af0. Configure here.