Repository navigation
Fix vlt 1.4 pnpm-workspace.yaml members (#1564) - #1574
Conversation
Assisted-by: Claude Code:claude-opus-5-5
vlt 1.4 installs a member listed by the root pnpm-workspace.yaml packages: from the root vlt-lock.json when neither vlt.json nor package.json declares workspaces. These tests show hosted, vendored and VEX runs from such a member treating it as its own project, and pin the vlt 1.3.8 and precedence controls that must stay as they are. Assisted-by: Claude Code:claude-opus-5-5
vlt 1.4 lists workspace members from the root pnpm-workspace.yaml packages: when neither vlt.json nor package.json declares workspaces, and installs them from the root vlt-lock.json. socket-patch read only the first two sources, so hosted and vendored scans from such a member rewrote a lock vlt never reads and exited 0, and vendored VEX attested not_affected while vlt installed the unpatched package. The vlt root lookup now reads the third source. vlt 1.3.8 and older ignore the file, so its directories count as members only when the root lock has an edge from one of them, which only vlt 1.4 writes. Hosted, vendored and VEX runs share the lookup. Fixes #1564 Assisted-by: Claude Code:claude-opus-5-5
A pnpm-workspace.yaml with no vlt-lock.json beside it, such as a vendored pnpm sub-monorepo, is not shown to be vlt's, so it no longer stops the search for an outer vlt.json root that installs the member. Also shares one helper for vlt's per-group glob check and fixes the refusal code's doc. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 3608d88. Configure here.
|
Burn-down agent: labeled Ready for review at head
Generated by Claude Code |
Final review briefWhat it does. vlt 1.4 can read a workspace's members from Risk: low–medium. It only adds refusals, and only when the root has a Look here
Verified
Changes I made: none. Open questions: none blocking. vlt 1.4.0 isn't in the real-vlt CI matrix yet; the PR says that is a separate change. Auto-merge is armed (squash), so approving sends this straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1564
Summary
From vlt 1.4.0, a workspace root can list its members in
pnpm-workspace.yamlpackages:. vlt reads that list only when neithervlt.jsonnorpackage.jsonhas aworkspacesfield, and it installs those members from the root'svlt-lock.json. socket-patch now treats such a member like avlt.json- orpackage.json-listed one:redirect_workspace_lockfile_elsewhere, namingpnpm-workspace.yamland the root to run from. This applies with or without a stray member lock.vlt-lock.json: refused withvendor_lockfile_missing. Nothing is written.not_affected.Before this change, every one of these runs exited 0. The vendored run's VEX attested
not_affectedwhile vlt installed the pristine package.Root cause
hosted/governing_root.rsvlt_workspace_rootread member globs only from an ancestorvlt.json, andpackage_json_workspace_rootcovered thepackage.jsonfallback. Nothing modelled vlt 1.4's third source. The hosted refusal, the vendoredmember_stray_lock_refusal(vendor/npm_flavor.rs) and VEX discovery (ignore_member_stray_locks) all go throughvlt_workspace_root/member_stray_lock, so all three missed it.Fix
vlt_workspace_rootnow asksvlt_workspace_groupsfor each ancestor's patterns. That function mirrors vlt 1.4'sresolveWSConfig:vlt.jsonworkspacesif the field exists.package.jsonhas noworkspacesfield (vlt keys on the field existing, whatever its value), thepnpm-workspace.yamlpackages:, minus././root entries, as long as a positive pattern remains.The patterns are globbed with
WorkspacesDialect::VLT. vlt 1.4'ssplitNegatedPatternsis the same as 1.3's, so!packages/bexcludesb.Version gate. vlt 1.3.8 and older ignore
pnpm-workspace.yaml, so there a listed directory is its own project, and writing its lock is correct (the issue's control row). Both versions write the same lock format, so the root lock decides. vlt 1.4 records aworkspace~<dir>edge from every member it installed that has a dependency. A lock with an edge from apackages:-listed directory is therefore 1.4's (vlt_lock_reads_pnpm_workspace). A lock with no such edge is older vlt's (or every member has no dependency, so there's nothing to patch). A lock that can't be read keeps the refusing side, asvlt_lists_memberalready does for #1542. Apnpm-workspace.yamlroot with novlt-lock.jsonisn't shown to be vlt's, so the walk carries on to an outervlt.jsonroot.I checked the edge rule against real vlt (public registry, root
pnpm-workspace.yamllistingpackages/*and!packages/b, both members depending onleft-pad):vlt install→ edges["workspace~packages+a left-pad"]vlt install→ edges[]docs/ecosystems.mdvlt notes gain a short "Workspace members" paragraph.docs/testing/vlt-compatibility.mdstill lists 1.3.8 as the newest supported release. Adding 1.4.0 to the real-vlt CI matrix is a separate change.Tests (red → green)
The first commit (84c671a) adds only tests. All four failed on main.
vlt.jsonhosted::governing_root::tests::vlt_pnpm_workspace_yaml_member_is_refusedpackage.jsonworkspaces(incl.[]),vlt.jsonworkspaces,!in the yaml and unlisted dir don't; unreadable lock refuseshosted::governing_root::tests::vlt_pnpm_workspace_yaml_reads_the_root_lockvendor_lockfile_missing, nothing writtenvendor::npm_flavor::tests::bun_and_vlt_members_with_stray_lock_are_refused(new case)vendor_lock_entry_not_foundvex::discover::member_stray_lock_tests::a_member_lock_its_manager_ignores_attests_nothing(new case)pnpm-workspace.yamldoesn't hide the outervlt.jsonroothosted::governing_root::tests::vlt_uninstalled_pnpm_workspace_yaml_walks_onCommands run locally (Linux, as root):
cargo fmt --all -- --check: okcargo clippy --workspace --all-features -- -D warnings: okcargo test --workspace --all-features --lib: 7,435 passed. The 4 failures are environment-only write-failure tests that can't fail as root (vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,copy_tree::relax_loop_must_not_traverse_symlinked_root,pypi_poetry::wire_write_failure_maps_error_and_leaves_lock_untouched,pypi_requirements::wire_failure_rolls_back_already_written_files). They fail identically onmain.in_process_redirect,in_process_vendor,in_process_rollback_hosted,in_process_vendor_bun_takeover,e2e_vex,e2e_vex_redirect,redirect_golden,vlt_locks: all pass except 3 root-only write-failure tests inin_process_redirect(partial_lockfile_write_failure_exits_1_and_writes_no_ledger,redirect_json_mode_write_failures_emit_error_envelope,vlt::scan_redirect_vlt_heal_invalidation_failure_warns), which also fail onmainas root. The sandbox disk can't hold every test binary of the fullcargo test --workspace, so CI runs the rest (non-root).npm/,pypi/,gem/) only dispatch to the binary and need no change./code-review high: findings not fixed
vlt.json. A member with no dependency has no edge of its own but is still a member. An unreadable root lock keeps the refusing side, matchingvlt_lists_member(Hosted and vendored scans from a vlt workspace member with a stray vlt-lock.json still write to that lock and exit 0 when the root's workspaces glob uses an extglob (packages/+(a|b), packages/@(a|b)); vendored VEX then attests not_affected #1542): vlt can't install from that lock either.package.jsonworkspaces: null/{nohoist: [...]}suppresses the yaml. This matches vlt.resolveWSConfigfalls back topnpm-workspace.yamlonly whenworkspaces === undefined, andasManifestWSConfigreturns{}for an object withoutpackages. So vlt reads no members there either.pnpm-workspace.yamlper ancestor, and readspackage.jsononly where a yaml exists. That is one stat for each ancestor without a yaml, the same cost as the existingvlt.jsonprobe. Left as is.🤖 Generated with Claude Code
https://claude.ai/code/session_01P3dvgTvNF8SCp2PAoebdaA
Note
Medium Risk
Changes governing-root detection for npm-family workspaces (hosted, vendored, VEX); wrong classification could refuse valid runs or allow silent no-ops, but behavior is heavily tested and mirrors vlt 1.4 precedence.
Overview
vlt 1.4 can treat
pnpm-workspace.yamlpackages:as the workspace member list when neithervlt.jsonnorpackage.jsondefinesworkspaces. This PR teaches socket-patch to recognize those members so it no longer pins or vendors a member lock while vlt installs from the rootvlt-lock.json.Hosted runs from such a member now fail closed with
redirect_workspace_lockfile_elsewhere, namingpnpm-workspace.yamland the root. Vendored runs with a stray membervlt-lock.jsongetvendor_lockfile_missing. VEX treats the stray lock as absent (no falsenot_affected). Logic lives invlt_workspace_groups/vlt_workspace_root, with precedence matching vlt (vlt.json→package.jsonfield presence → yaml) and a lock edge gate so vlt ≤1.3 (which ignores the yaml) still allows patching the member unless the root lock shows vlt 1.4 installed from listed dirs.Docs add a short vlt “Workspace members” note; tests cover refusal, version/precedence edge cases, and nested uninstalled inner yaml.
Reviewed by Cursor Bugbot for commit 3608d88. Configure here.
Generated by Claude Code