fix: pin integrity of git-hosted tarballs in lockfile#11481
Conversation
The git-hosted tarball fetcher dropped the integrity computed during download, so the lockfile only stored the codeload.github.com / gitlab / bitbucket URL. A compromised git host or MITM could then serve a different tarball on subsequent installs without lockfile changes. Propagate the SHA-512 SRI of the raw tarball back to the lockfile so the worker validates it on every fresh fetch and rejects tampered tarballs with TarballIntegrityError. --- Written by an agent (Claude Code, claude-opus-4-7).
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughFetchers now surface computed tarball integrity for git-hosted packages; callers forward package ids so fetchers can register integrity-keyed store-index entries; lockfile resolutions include an ChangesGit-hosted tarball integrity propagation
sequenceDiagram
participant PR as PackageRequester
participant TF as TarballFetcher
participant RT as RemoteTarball
participant SI as StoreIndex
participant LF as Lockfile
PR->>TF: fetch(tarballUrl, pkgId, filesIndexFile, ...)
TF->>RT: download tarball
RT-->>TF: filesMap, manifest, requiresBuild, integrity
TF->>SI: write files index at filesIndexFile
TF->>SI: if integrity && pkgId write index at storeIndexKey(integrity, pkgId)
TF-->>PR: return filesMap, manifest, requiresBuild, integrity
PR->>LF: record resolution { tarball, integrity }
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
The previous deadbeef… literal was 128 hex chars stuffed after `sha512-` which doesn't match the SRI shape pnpm actually emits (88 base64 chars). Replace it with a real (but mismatched) sha512 base64 string so the fixture mirrors what a tampered lockfile entry would look like. --- Written by an agent (Claude Code, claude-opus-4-7).
There was a problem hiding this comment.
Pull request overview
This PR aims to harden pnpm's handling of git-hosted tarball dependencies by persisting a computed tarball integrity in the lockfile, so later installs can verify downloaded content. It touches the tarball fetcher, package requester, install tests, and release notes.
Changes:
- Propagate computed integrity from git-hosted tarball downloads back into package resolution data.
- Keep git-hosted tarballs on the existing store index key path to avoid unnecessary refetches with older stores.
- Add tests and snapshot updates asserting the new
resolution.integritybehavior for git-hosted dependencies.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
installing/package-requester/src/packageRequester.ts |
Special-cases git-hosted tarballs when choosing the store index key. |
installing/deps-installer/test/install/fromRepo.ts |
Updates lockfile expectations for git-hosted installs to include integrity. |
fetching/tarball-fetcher/test/fetch.ts |
Adds fetcher tests for integrity propagation and mismatch rejection. |
fetching/tarball-fetcher/src/gitHostedTarballFetcher.ts |
Returns the raw tarball integrity from the git-hosted fetch path. |
.changeset/git-tarball-integrity.md |
Documents the security-focused patch release. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…tarballs The carve-out that kept git-hosted tarballs on gitHostedStoreIndexKey even when the lockfile carried integrity made the writer disagree with every other reader: pkg-finder, store status, the FUSE mounter, and rebuild --skip-if-has-side-effects-cache all derive the index path from storeIndexKey(integrity, pkgId). Lockfile entries with integrity would silently miss the cached package and side-effects builds would re-run (potentially landing under a second key). Drop the carve-out and use the integrity-based key consistently. First install after upgrading still re-fetches once (the old store entry is under the gitHosted key), but the cafs deduplicates the underlying file content, and subsequent installs reuse the entry like every other tarball. --- Written by an agent (Claude Code, claude-opus-4-7).
Once the lockfile pins integrity for git-hosted tarballs, every
post-fetch reader (pkg-finder, store status, FUSE mounter,
skipIfHasSideEffectsCache in building/after-install) looks the package
up under storeIndexKey(integrity, pkgId). Three issues were exposed:
1. pkg-finder ran parse(packageId) and rebuilt name@version, but
packageIdFromSnapshot returns the URL alone for git-hosted (the
`:` in `https://` makes tryGetPackageId strip the `name@`
prefix). parse(URL) returns {} and the lookup key became
`…\tundefined@undefined`. Use packageId directly — that's the
form the writer in package-requester uses.
2. The FUSE handler and the after-install side-effects-cache lookup
used `${name}@${version}`, which doesn't match the resolver's
pkg.id for git-hosted (URL). Use
`nonSemverVersion ?? ${name}@${version}` so the readers compute
the same key the writer uses.
3. On the first install, integrity isn't known when
getFilesIndexFilePath runs, so the writer parks the index under
gitHostedStoreIndexKey. Once the worker computes integrity, the
git-hosted fetcher now also registers the entry under
storeIndexKey(integrity, pkgId), so subsequent integrity-based
lookups (the same install or any later one) resolve to the same
data without a refetch.
The license test that walks the lockfile right after install now
passes locally; that's the case that was failing in CI.
---
Written by an agent (Claude Code, claude-opus-4-7).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@modules-mounter/daemon/src/createFuseHandlers.ts`:
- Around line 179-185: The code in createFuseHandlers.ts uses a non-null
assertion on (pkgSnapshot.resolution as TarballResolution).integrity when
building pkgIndexFilePath with storeIndexKey, which can throw if integrity is
missing for git-hosted packages; change the logic in the block that computes
pkgIndexFilePath (used by getPkgInfo) to check whether integrity exists and, if
not, call gitHostedStoreIndexKey with the appropriate nonSemver identifier
(nameVer.nonSemverVersion ?? `${nameVer.name}@${nameVer.version}`) instead of
storeIndexKey, and add an import for gitHostedStoreIndexKey from
`@pnpm/store.index`. Ensure both branches produce the same pkgIndexFilePath
variable for downstream use.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 7253807f-bf11-40d9-90e0-878e68f6f8bc
📒 Files selected for processing (7)
building/after-install/src/index.tsfetching/fetcher-base/src/index.tsfetching/tarball-fetcher/src/gitHostedTarballFetcher.tsinstalling/package-requester/src/packageRequester.tsmodules-mounter/daemon/src/createFuseHandlers.tsstore/pkg-finder/src/index.tsstore/pkg-finder/test/readPackageFileMap.test.ts
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Two cases surfaced by the latest reviewer round: 1) FUSE handler crashed with a non-null assertion on integrity for pre-CVE-fix lockfiles that don't carry integrity for git-hosted tarballs. Fall back to gitHostedStoreIndexKey so the daemon can still serve those packages. 2) Once a lockfile committed by a newer pnpm carries integrity, getFilesIndexFilePath probes only storeIndexKey(integrity, pkgId). An older local store may still have the entry under gitHostedStoreIndexKey, so the install would refetch — and in offline mode hard-fail with NO_OFFLINE_TARBALL — even though the tarball is already on disk. Plumb the StoreIndex into the package-requester and migrate the entry to the integrity key on the fly when we detect this state. Both fallbacks are tagged TODO(v12): once v12 forces lockfile regeneration and any pnpm v11-written store will already have the integrity-keyed entry, this bridging code can be removed. --- Written by an agent (Claude Code, claude-opus-4-7).
Benchmark Results
Run 25431128576 · 15 runs per scenario · triggered by @zkochan |
…checks The migration block that copies a git-hosted tarball entry from the legacy gitHostedStoreIndexKey to storeIndexKey(integrity, pkgId) was calling storeIndex.has() on every fetch, including all npm-registry packages it has no business probing — a SELECT per package, multiplied across thousands of dependencies. Reorder so the URL prefix check (isGitHostedPkgUrl) and the integrity presence check run first, and the SQLite lookup only runs for git-hosted resolutions with integrity in the lockfile. Same migration behaviour, ~zero overhead on the hot path. --- Written by an agent (Claude Code, claude-opus-4-7).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@installing/package-requester/src/packageRequester.ts`:
- Around line 532-545: The migration guard is re-reading opts.pkg.resolution
instead of the resolution chosen earlier (the one used by
getFilesIndexFilePath), so when that selected resolution is a concrete
AtomicResolution (type 'variations') the guard misfires; change the code in the
guard to use the previously selected resolution variable (e.g. resolution or
selectedResolution) for tarballUrl and integrity checks and when building
legacyKey (keep the TarballResolution/AtomicResolution type assertions in line
with that variable), so the isGitHostedPkgUrl(tarballUrl) &
ctx.storeIndex.has(filesIndexFile) logic correctly detects and migrates legacy
git-hosted entries.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0f042243-82f7-415f-a041-cc7c7eb34c56
📒 Files selected for processing (1)
installing/package-requester/src/packageRequester.ts
A v10/v11 lockfile that doesn't yet record integrity will keep working fine: the resolver returns no integrity, getFilesIndexFilePath falls back to gitHostedStoreIndexKey, and readPkgFromCafs still finds the cached entry. The migration only mattered for one specific case — a v11-written lockfile (with integrity) landing on top of a stale v10 store — and the cost of dropping it is one extra HTTP fetch per git-hosted package, once. The fetcher's dual-write then registers the entry under both keys, so subsequent installs are cache hits. Trade-off: `pnpm install --offline` on a stale v10 store immediately after upgrading would fail with NO_OFFLINE_TARBALL for git deps until an online run warms the cache under the integrity key. That's narrow enough that keeping a permanent state-mutating shim in the hot fetch path isn't worth it. Removes the optional StoreIndex parameter on createPackageRequester and the wiring through fetchToStore that existed solely for this. --- Written by an agent (Claude Code, claude-opus-4-7).
…alias
Reviewer flagged that storeIndexKey(integrity, pkgId) collapses the
built/not-built dimension that gitHostedStoreIndexKey carries. Toggling
ignoreScripts between installs would let one variant overwrite the
other and a later install with the opposite setting would silently
read the wrong cached content.
Switch the design so git-hosted resolutions are addressed by
gitHostedStoreIndexKey(pkgId, { built }) consistently:
- Writer (getFilesIndexFilePath): detect git-hosted via tarball URL
prefix and skip the integrity-key fast path; fall through to
gitHostedStoreIndexKey regardless of whether integrity is present.
- Fetcher: drop the dual-write (no more storeIndexKey alias) and the
pkgId field on FetchOptions that only existed for it.
- Readers (pkg-finder, modules-mounter/daemon, building/after-install):
detect git-hosted via tarball URL and route to gitHostedStoreIndexKey.
The lockfile still pins integrity for security and the worker still
validates it on download — that part of the security fix is unchanged.
The FUSE TODO(v12) fallback that compensated for missing-integrity
lockfiles is gone — git-hosted is now keyed the same way regardless
of integrity, so there's no upgrade-only branch to clean up later.
---
Written by an agent (Claude Code, claude-opus-4-7).
--- Written by an agent (Claude Code, claude-opus-4-7).
Two more readers Copilot flagged that needed the same git-hosted detection used by pkg-finder / FUSE / after-install: - store/commands/src/store/storeStatus/index.ts: when checking whether the store has the index entry for a lockfile-listed package, was using storeIndexKey(integrity, id) whenever integrity was present. With git-hosted now carrying integrity in the lockfile, those entries would never be found. - installing/deps-installer/src/install/index.ts (agent-mode store controller wrapper): same shape — bypassed the regular store controller for any package with integrity, which would skip the fallback for git-hosted packages whose actual cached entry is keyed by gitHostedStoreIndexKey. Both now detect git-hosted via the tarball URL prefix and either route to gitHostedStoreIndexKey (storeStatus) or fall through to the regular store controller (deps-installer agent path). Also: - Updated the readPackageFileMap docstring in pkg-finder to document the git-hosted exception explicitly (was implying every with-integrity package is keyed by integrity). - Expanded the changeset to include @pnpm/building.after-install, @pnpm/installing.deps-installer, @pnpm/modules-mounter.daemon, @pnpm/store.commands, and @pnpm/store.pkg-finder so all touched published packages get a patch bump. --- Written by an agent (Claude Code, claude-opus-4-7).
--- Written by an agent (Claude Code, claude-opus-4-7).
Six readers + a writer all repeated the same isGitHostedPkgUrl(...) check against the resolution.tarball field — fragile, scattered, and the kind of pattern that just lost a regression to Copilot's review loop. Lift it to a typed annotation on the lockfile/resolution. - TarballResolution gains an optional `gitHosted: boolean` field in both @pnpm/lockfile.types and @pnpm/resolving.resolver-base. - The git resolver sets it whenever it produces a tarball-style resolution (codeload.github.com / gitlab.com / bitbucket.org). - The lockfile loader (@pnpm/lockfile.fs convertToLockfileObject) enriches entries written by older pnpm versions: any tarball whose URL matches a known git host gets gitHosted=true on load. This centralizes URL detection to one place and lets every downstream consumer rely on the field. - toLockfileResolution preserves the field when serializing, with a URL fallback for callers that hand it a resolution constructed ad-hoc (config-dep migrations, etc.). Every reader simplifies from the multi-line URL check to a single typed read of resolution.gitHosted: package-requester (getFilesIndexFilePath writer) store/pkg-finder (readPackageFileMap) modules-mounter/daemon (createFuseHandlers) building/after-install (skipIfHasSideEffectsCache + cache write) store/commands/storeStatus installing/deps-installer (agent-mode store-controller wrapper) …and they drop their just-added @pnpm/fetching.pick-fetcher dep. pickFetcher itself now prefers the field (URL fallback retained for ad-hoc resolutions). pkgSnapshotToResolution does the same. Lockfile schema: a new optional `gitHosted: true` appears on tarball resolutions for git-hosted entries. Old lockfiles without it work correctly via load-time enrichment. --- Written by an agent (Claude Code, claude-opus-4-7).
--- Written by an agent (Claude Code, claude-opus-4-7).
Six call sites repeated the same shape:
resolution.gitHosted
? gitHostedStoreIndexKey(pkgId, { built })
: storeIndexKey(resolution.integrity!, pkgId)
Hoist it to @pnpm/store.index next to the two key functions, behind a
single helper that takes the resolution shape and the desired `built`
flag. The helper also folds in the legacy fallback (no integrity, no
gitHosted → gitHostedStoreIndexKey).
Each consumer collapses from a 3-5 line conditional to a one-liner:
pickStoreIndexKey(resolution, pkgId, { built })
Touched: package-requester (writer), pkg-finder, FUSE daemon,
after-install (both side-effects-cache lookup and write), and
storeStatus.
---
Written by an agent (Claude Code, claude-opus-4-7).
- toLockfileResolution: import TarballResolution from @pnpm/resolving.resolver-base instead of @pnpm/lockfile.types. The function takes a resolver-side Resolution, so casting to the resolver-side TarballResolution keeps type boundaries clear. - git-resolver tests: split the bulk-appended `gitHosted: true` onto its own line for readability (linter handled the formatting once the inline pattern was no longer matched). --- Written by an agent (Claude Code, claude-opus-4-7).
Cherry-pick of #11481 from main, adapted to the v10 layout. For git-hosted tarballs (codeload.github.com / gitlab.com / bitbucket.org) the fetcher dropped the integrity it computed while downloading, so the lockfile only stored the URL. A compromised git host or man-in-the-middle could serve a substituted tarball on subsequent installs and pnpm would install it without lockfile changes. This pins the SHA-512 SRI of the raw tarball in the lockfile in the same sha512-<base64> form npm-registry tarballs use; subsequent installs verify the download against that integrity in the worker. A new optional gitHosted: boolean field is recorded on TarballResolution so every store-key consumer can route by a single typed read instead of re-deriving the routing from the URL. Lockfiles written by older pnpm versions are enriched on load (URL fallback) so the field can be relied on uniformly. 🤖 Cherry-picked by Claude (claude-opus-4-7) on behalf of @zkochan
For git-hosted tarballs (`codeload.github.com` / `gitlab.com` / `bitbucket.org`) the fetcher dropped the integrity it computed while downloading, so the lockfile only ever stored the URL. A compromised git host or man-in-the-middle could serve a substituted tarball on subsequent installs and pnpm would install it — the lockfile had no hash to compare against.
This pins the SHA-512 SRI of the raw tarball in the lockfile, in the same `sha512-<base64>` form npm-registry tarballs use. The only difference is the source: for npm we pass through `dist.integrity`, for git we compute it locally from the downloaded buffer. Subsequent installs validate the download against that integrity in the worker (`addTarballToStore` → `parseIntegrity` → hash compare), so a tampered tarball fails with `TarballIntegrityError`.
## Why git-hosted stays on `gitHostedStoreIndexKey`
The lockfile pins integrity for security, but the *store key* for git-hosted resolutions stays on `gitHostedStoreIndexKey(pkgId, { built })` rather than collapsing under the integrity-based key. Reason: git-hosted tarballs are post-processed (`preparePackage` / `packlist`), so the cached file set depends on whether build scripts ran during fetch. The integrity-only key would fold the built and not-built variants into a single slot, letting one overwrite the other and serving the wrong content if `ignoreScripts` was toggled between runs. Keeping git-hosted on the existing key shape preserves that dimension; the integrity is still validated on every fresh download.
## How the routing stays clean
The naive way to express "use gitHostedStoreIndexKey for git-hosted, integrity key for npm" is to call `isGitHostedPkgUrl(resolution.tarball)` everywhere a store key is computed — fragile, scattered, and easy to forget when adding new readers (Copilot caught two of those during review). Instead, a typed annotation: `TarballResolution` gets an optional `gitHosted: boolean` field. The git resolver sets it; the lockfile loader (`convertToLockfileObject`) backfills it for entries written by older pnpm versions; `toLockfileResolution` carries it through on serialize. Every consumer reads `resolution.gitHosted` directly. URL detection lives in exactly two places — the resolver and the loader — instead of seven.
## Changes
### Security fix
- `fetching/tarball-fetcher/src/gitHostedTarballFetcher.ts` — return the `integrity` that the inner remote-tarball fetch already computed (was being silently dropped by the destructure).
### Lockfile schema (additive)
- `@pnpm/lockfile.types` and `@pnpm/resolving.resolver-base` — `TarballResolution` gains optional `gitHosted: boolean`.
- `@pnpm/resolving.git-resolver` — sets `gitHosted: true` on every git-hosted tarball it produces.
- `@pnpm/lockfile.fs` (`convertToLockfileObject`) — backfills the field on load for older lockfiles via inlined URL detection.
- `@pnpm/lockfile.utils` (`toLockfileResolution`, `pkgSnapshotToResolution`) — preserve / read the field.
### Store-key consumers (now one-line typed reads, dropped the URL-sniffing dep)
- `installing/package-requester` (`getFilesIndexFilePath`)
- `store/pkg-finder` (`readPackageFileMap`)
- `modules-mounter/daemon` (`createFuseHandlers`)
- `building/after-install` (side-effects-cache lookup + write)
- `store/commands/storeStatus`
- `installing/deps-installer` (agent-mode store-controller wrapper)
### Fetcher routing
- `fetching/pick-fetcher` — `pickFetcher` prefers `resolution.gitHosted`; URL fallback retained for ad-hoc resolutions.
### Tests
- New integrity-validation test in `tarball-fetcher` (mismatched `integrity` on the resolution must throw `TarballIntegrityError`).
- New git-hosted lookup test in `pkg-finder` asserting routing through `gitHostedStoreIndexKey` even when integrity is present.
- New `toLockfileResolution` test asserting `gitHosted: true` flows through serialization.
- `fromRepo.ts` lockfile snapshot updated for the now-pinned integrity + `gitHosted: true`.
- `git-resolver` tests updated to assert `gitHosted: true` in produced resolutions.
Summary
For git-hosted tarballs (
codeload.github.com/gitlab.com/bitbucket.org) the fetcher dropped the integrity it computed while downloading, so the lockfile only ever stored the URL. A compromised git host or man-in-the-middle could serve a substituted tarball on subsequent installs and pnpm would install it — the lockfile had no hash to compare against.This pins the SHA-512 SRI of the raw tarball in the lockfile, in the same
sha512-<base64>form npm-registry tarballs use. The only difference is the source: for npm we pass throughdist.integrity, for git we compute it locally from the downloaded buffer. Subsequent installs validate the download against that integrity in the worker (addTarballToStore→parseIntegrity→ hash compare), so a tampered tarball fails withTarballIntegrityError.Why git-hosted stays on
gitHostedStoreIndexKeyThe lockfile pins integrity for security, but the store key for git-hosted resolutions stays on
gitHostedStoreIndexKey(pkgId, { built })rather than collapsing under the integrity-based key. Reason: git-hosted tarballs are post-processed (preparePackage/packlist), so the cached file set depends on whether build scripts ran during fetch. The integrity-only key would fold the built and not-built variants into a single slot, letting one overwrite the other and serving the wrong content ifignoreScriptswas toggled between runs. Keeping git-hosted on the existing key shape preserves that dimension; the integrity is still validated on every fresh download.How the routing stays clean
The naive way to express "use gitHostedStoreIndexKey for git-hosted, integrity key for npm" is to call
isGitHostedPkgUrl(resolution.tarball)everywhere a store key is computed — fragile, scattered, and easy to forget when adding new readers (Copilot caught two of those during review). Instead, a typed annotation:TarballResolutiongets an optionalgitHosted: booleanfield. The git resolver sets it; the lockfile loader (convertToLockfileObject) backfills it for entries written by older pnpm versions;toLockfileResolutioncarries it through on serialize. Every consumer readsresolution.gitHosteddirectly. URL detection lives in exactly two places — the resolver and the loader — instead of seven.Changes
Security fix
fetching/tarball-fetcher/src/gitHostedTarballFetcher.ts— return theintegritythat the inner remote-tarball fetch already computed (was being silently dropped by the destructure).Lockfile schema (additive)
@pnpm/lockfile.typesand@pnpm/resolving.resolver-base—TarballResolutiongains optionalgitHosted: boolean.@pnpm/resolving.git-resolver— setsgitHosted: trueon every git-hosted tarball it produces.@pnpm/lockfile.fs(convertToLockfileObject) — backfills the field on load for older lockfiles via inlined URL detection.@pnpm/lockfile.utils(toLockfileResolution,pkgSnapshotToResolution) — preserve / read the field.Store-key consumers (now one-line typed reads, dropped the URL-sniffing dep)
installing/package-requester(getFilesIndexFilePath)store/pkg-finder(readPackageFileMap)modules-mounter/daemon(createFuseHandlers)building/after-install(side-effects-cache lookup + write)store/commands/storeStatusinstalling/deps-installer(agent-mode store-controller wrapper)Fetcher routing
fetching/pick-fetcher—pickFetcherprefersresolution.gitHosted; URL fallback retained for ad-hoc resolutions.Tests
tarball-fetcher(mismatchedintegrityon the resolution must throwTarballIntegrityError).pkg-finderasserting routing throughgitHostedStoreIndexKeyeven when integrity is present.toLockfileResolutiontest assertinggitHosted: trueflows through serialization.fromRepo.tslockfile snapshot updated for the now-pinned integrity +gitHosted: true.git-resolvertests updated to assertgitHosted: truein produced resolutions.Test plan
fetching/tarball-fetcher— 22/22 (includes the new TarballIntegrityError test).installing/package-requester— 20/20.store/pkg-finder— 6/6 (includes the new git-hosted-with-integrity test).resolving/git-resolver— 71/71.lockfile/utils— 10/10 (includes the new gitHosted serialization tests).lockfile/fs— 39/39.store/commands— 34/34.installing/deps-installerfromRepo— 12/12 (3 pre-existing skipped).deps/compliance/commandslicenses— 11/11 (catches reader/writer-mismatches because it walks the lockfile + store right after install).deps/inspection/outdated— 15/15.Written by an agent (Claude Code, claude-opus-4-7).