Files
Zoltan Kochan 1c1f600816 feat(graph-hasher): engine-agnostic GVS hash gating (#459) (#469)
* feat(graph-hasher): engine-agnostic GVS hash gating (#459)

Port pnpm's `transitivelyRequiresBuild` + `computeBuiltDepPaths` into
`graph-hasher`, and extend `calc_graph_node_hash` with the
`builtDepPaths`-driven engine-gating that mirrors upstream's
[`calcGraphNodeHash`](https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-hasher/src/index.ts#L140-L142).

When a snapshot — and its entire transitive subtree — has no entry
in `allowBuilds` (and `dangerouslyAllowAllBuilds` is off), its GVS
hash payload now sets `engine: null` instead of `engine:
ENGINE_NAME`. Pure-JS packages share one slot directory across
Node.js versions, OSes, and CPUs, matching upstream and unlocking
the cross-host store sharing the GVS layout was designed for.

`VirtualStoreLayout::new` grows an `Option<&AllowBuildPolicy>`
parameter. `InstallFrozenLockfile::run` passes the install's
`AllowBuildPolicy` through; callers that pass `None` retain the
pre-#459 always-include-engine shape (matches upstream's
`builtDepPaths === undefined` short-circuit).

Tests:
- six new `transitively_requires_build` unit tests covering self-hit,
  transitive ancestor, unrelated tree, missing node, cycle
  termination, and cycle-doesn't-mask-sibling-builder
- four new `calc_graph_node_hash` tests covering the engine-agnostic
  vs engine-specific paths and the `built_dep_paths = None` shortcut
- two end-to-end `VirtualStoreLayout::new` tests proving a pure-JS
  snapshot's slot directory is identical across two `engine` strings,
  and that a built snapshot's slot differs

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

* docs(graph-hasher): address Copilot feedback on calc_graph_node_hash docs

- Drop the cross-module intra-doc link `[transitively_requires_build]:
  crate::dep_state::transitively_requires_build` that resolved to a
  pub(crate) item. The link rendered correctly under pacquet's CI
  (which runs `cargo doc --document-private-items`), but downstream
  doc builds without that flag would trip `private_intra_doc_links`.
  Switched to a plain-backticks reference + an inline "private to
  this crate" note so the doc remains informative either way.
- Reword the `build_required_cache` hint to avoid the ambiguous
  `&mut HashMap::new()` phrasing — temporaries-in-call-args is a
  valid pattern but a confusing one to surface at the API doc.
  Pointed callers at the explicit `let mut cache = HashMap::new();`
  + `&mut cache` shape instead.
2026-05-13 18:54:44 +02:00
..