Skip to content

Make the lockfile freshness check use the resolver's local-dependency rules #255

Closed

Description

@zkochan

Part of #253.

Property

A frozen install or freshness check judges local, injected, and directory dependencies the way resolution would.

Evidence

pnpm/crates/package-manager/src/install/lockfile_freshness has 24 fixes on 530 lines in the 90 days to 2026-11-03. The fixes copy one resolver rule each:

Flaw

One decision with two deciders. lockfile_freshness keeps its own copy of the resolver's rules for directory and injected dependencies, and each fix copies one more rule.

Plan

  • Move the resolver's judgment of local, injected, and directory dependencies into a function in the resolving crates that both the resolver and the freshness check call.
  • Replace the copies in install/lockfile_freshness with calls to it.
  • Add a differential test: for each freshness fixture, run resolution and the freshness check and assert they agree on whether the lockfile is current.

Written by an agent (Claude Code, claude-opus-5-5).

Activity

  1. self-assigned this
    on Nov 3, 2026
  2. zkochan commented on Nov 5, 2026

    @zkochan
    MemberAuthor

    Rule map and progress

    This maps the resolver rules that lockfile_freshness/directory_deps/ and directory_manifests.rs copy, so the remaining extraction can be planned rule by rule.

    Already shared: the child-spec walk (local_directory_child_specs), peer shadowing, PeerSet, resolve_from_catalog, the reuse gate (locked_child_admits), the manifest hook chain, VersionsOverrider, and publishes_from_directory.

    Copied:

    Rule Freshness copy Resolver Status
    1. Children of a file: directory skip catalogs children.rs, directory_manifests.rs (!declared_as_file) resolves_children_through_catalogs copied, same behavior
    2. Child catalog anchor ChildCatalogs.package_dir declaring_manifest_dir copied; the resolver also normalizes absolute dirs
    3. Peer catalog ranges peers.rs check_recorded_peer_specs_match extract_peer_dependencies differs: ignores the file: exception and the taken-peers filter
    4. Allowed snapshot keys children.rs check_snapshot_keys_are_children declared_peer_dependencies differs: keeps peers that are also own dependencies
    6. file:/link: path equality spec.rs resolve_local_spec_path pnpm_local_spec::normalize_specifier, local resolver resolve_path copied
    7. Directory vs tarball for file: is_local_tarball_path is_tarball_filename done in pnpm/pnpm#17706
    8. workspace: grammar spec.rs parse_workspace_range, is_workspace_path WorkspaceSpec::parse, resolve_from_workspace alias split done in pnpm/pnpm#17706; path forms still differ
    9. workspace: range matching spec.rs, manifest_agreement.rs resolve_workspace_range done in pnpm/pnpm#17706 (fixed a non-semver version bug in both CLIs)
    10. Publish directory injected_manifest.rs resolve_workspace_package_dir probed linkDirectory: false, no disagreement found
    11. Manifest source order injected_manifest.rs unhooked_manifest local resolver resolve_spec, resolve_from_local_package differs for file:: project manifests before disk
    12. Which manifest the hooks run on package.rs, directory_manifests.rs apply_manifest_hooks copied
    13. When a dependency is injected importer_deps.rs dependency_is_injected resolve_dependency_tree.rs dependency_is_injected copied, also reads lockfile settings
    15. Peer scope scope.rs ParentPkgAliases rebuilt from lockfile keys

    Rules 5, 14, 16 and 17 depend on the lockfile's shape and have no resolver counterpart. Rule 16 goes away with #228, planned for pnpm v13.


    Written by an agent (Claude Code, claude-opus-5-5).

  3. zkochan commented on Nov 5, 2026

    @zkochan
    MemberAuthor

    Final status

    The plan is done. pnpm/pnpm#17706, pnpm/pnpm#17720, and pnpm/pnpm#17722 move every rule in the map that can be shared into the resolving crates. pnpm/pnpm#17716 extends the freshness matrix.

    Rule Status
    1. Children of a path-declared directory skip catalogs shared (is_local_filesystem_specifier) in pnpm/pnpm#17722. Fixed a bug in both CLIs: a catalog: child of an injected ../lib (and in v11 also file:../lib) passed the check, on frozen installs too
    2. Child catalog anchor shared (directory_package_dir) in pnpm/pnpm#17722
    4. Allowed snapshot keys shared (declared_peer_dependencies) in pnpm/pnpm#17722
    6. file:/link: path equality shared in pnpm/pnpm#17720
    7. Directory vs tarball shared in pnpm/pnpm#17706
    8. workspace: path forms shared (is_workspace_path_specifier) in pnpm/pnpm#17722
    9. workspace: range matching shared in pnpm/pnpm#17706
    13. When a dependency is injected shared (dependency_is_injected, dependency_meta_is_injected) in pnpm/pnpm#17722

    Stay separate:

    Differential test: the freshness matrices in both CLIs (the Rust matrix and pnpm11/pnpm/test/monorepo/injectedFreshnessMatrix.ts) assert, for each layout, injection form, edit, and filter, that a frozen install fails exactly when a non-frozen install changes the lockfile.

    Left over: #274. With an injected package that depends on a sibling through ../child or link:../child, a frozen install rejects the lockfile pnpm install just wrote, and the two CLIs record that link differently.

    Closing as completed.


    Written by an agent (Claude Code, claude-opus-5-5).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions