perf: run lockfile verification concurrently with frozen install#12227
Conversation
📝 WalkthroughWalkthroughThis PR runs lockfile verification concurrently with fetch/link during frozen installs, gates lifecycle scripts and final success on verification passing, adds a pnpr verification-only endpoint and client method, and threads verification overrides and tarball prefetching into pacquet's frozen-install fast path. ChangesConcurrent Lockfile Verification
Sequence Diagram(s)sequenceDiagram
participant CLI as pacquet CLI
participant PnprClient as PnprClient
participant Server as pnpr Server
participant Resolver as Resolver
participant Installer as Install/FrozenInstall
CLI->>PnprClient: request verify_lockfile (VerifyLockfileOptions)
PnprClient->>Server: POST /v1/verify-lockfile
Server->>Resolver: handle_verify_lockfile(body)
Resolver-->>PnprClient: terminal NDJSON frame (done|violations|error)
CLI->>Installer: run frozen Install (with optional override)
Installer->>Verifier: await verifyLockfile callback before lifecycle
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Benchmark Results
Run 27396467220 · 10 runs per scenario · triggered by @zkochan |
Micro-Benchmark ResultsLinux |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #12227 +/- ##
==========================================
+ Coverage 87.74% 87.91% +0.16%
==========================================
Files 291 291
Lines 36107 36559 +452
==========================================
+ Hits 31683 32141 +458
+ Misses 4424 4418 -6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Integrated-Benchmark Report (Linux)Each scenario reports direct installs and pnpr installs. Bencher consumes pacquet@HEAD and pnpr@HEAD. Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 3.911990438280001,
"stddev": 0.18767666054223558,
"median": 3.8381010202800003,
"user": 3.7474253400000004,
"system": 3.30302892,
"min": 3.74577882278,
"max": 4.2441488637800004,
"times": [
4.2441488637800004,
3.7715134537800004,
4.20095422378,
3.8590943617800004,
3.75876485578,
4.05957187378,
3.8171076787800002,
3.8853000087800003,
3.74577882278,
3.7776702397800004
]
},
{
"command": "pacquet@main",
"mean": 4.11945938128,
"stddev": 0.09045897569978009,
"median": 4.0993153832800004,
"user": 3.7038308399999993,
"system": 3.2972945200000003,
"min": 3.9926448807800003,
"max": 4.28682467578,
"times": [
4.08025576978,
4.06499031778,
4.10818236278,
4.09044840378,
4.252285590780001,
4.15223519078,
4.28682467578,
3.9926448807800003,
4.04623610378,
4.12049051678
]
},
{
"command": "pnpr@HEAD",
"mean": 2.1427464862800005,
"stddev": 0.1333137945741719,
"median": 2.13263401628,
"user": 2.7005783400000003,
"system": 2.86437822,
"min": 1.9272505797800001,
"max": 2.32561372478,
"times": [
2.31185965278,
2.2802547517800003,
2.16813450878,
2.09827518678,
2.0098519777800004,
2.32561372478,
2.1033297757800002,
2.16193825678,
2.04095644778,
1.9272505797800001
]
},
{
"command": "pnpr@main",
"mean": 2.30935213488,
"stddev": 0.13155189722514338,
"median": 2.31453569528,
"user": 2.6920381399999997,
"system": 2.8743460199999995,
"min": 2.11705185878,
"max": 2.54650143578,
"times": [
2.2601286117800004,
2.16632793078,
2.19150845378,
2.42565695178,
2.3492343887800002,
2.3691905437800003,
2.54650143578,
2.38808417178,
2.2798370017800003,
2.11705185878
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6643652237400001,
"stddev": 0.009394964484256994,
"median": 0.6679630399400001,
"user": 0.38805639999999997,
"system": 1.3008908199999996,
"min": 0.64554504794,
"max": 0.67365483094,
"times": [
0.66866620094,
0.67365483094,
0.66021742794,
0.66779373594,
0.66294538294,
0.65176920494,
0.66813234394,
0.64554504794,
0.67290386094,
0.67202420094
]
},
{
"command": "pacquet@main",
"mean": 0.6977805962400001,
"stddev": 0.09390096237733639,
"median": 0.66704153194,
"user": 0.37071209999999993,
"system": 1.3262577199999999,
"min": 0.64360620194,
"max": 0.95701882094,
"times": [
0.72215058294,
0.67774156894,
0.66302445494,
0.64380606394,
0.64360620194,
0.68333642494,
0.66668673994,
0.66739632394,
0.65303877994,
0.95701882094
]
},
{
"command": "pnpr@HEAD",
"mean": 0.7107919254399999,
"stddev": 0.015567164745545427,
"median": 0.71052314994,
"user": 0.38877229999999996,
"system": 1.3350637199999997,
"min": 0.68471965094,
"max": 0.72993422494,
"times": [
0.71360762694,
0.68906836694,
0.70743867294,
0.68471965094,
0.72993422494,
0.70318392494,
0.72750708394,
0.70732911794,
0.71962765794,
0.72550292694
]
},
{
"command": "pnpr@main",
"mean": 0.78636427104,
"stddev": 0.013323594361210531,
"median": 0.78585798744,
"user": 0.3939145,
"system": 1.3306725199999998,
"min": 0.76666792494,
"max": 0.8133207289400001,
"times": [
0.79044621494,
0.76666792494,
0.78798847994,
0.79299879694,
0.78372749494,
0.78204242794,
0.79640324594,
0.8133207289400001,
0.77927342994,
0.77077396594
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 3.822628512,
"stddev": 0.06254913747144179,
"median": 3.8220302816,
"user": 3.5792077,
"system": 3.2267067799999998,
"min": 3.7060613141,
"max": 3.9243816871,
"times": [
3.9243816871,
3.8815351401,
3.8208426951,
3.8059947761,
3.8232178681,
3.8271101141,
3.8069343670999998,
3.7553537571,
3.8748534010999998,
3.7060613141
]
},
{
"command": "pacquet@main",
"mean": 3.8266757666,
"stddev": 0.04117442524837456,
"median": 3.8179892031,
"user": 3.5508942999999995,
"system": 3.2204318800000005,
"min": 3.7631213421000003,
"max": 3.9083962941,
"times": [
3.8587211661,
3.8154246691,
3.8205537371,
3.8437803101,
3.8066636811,
3.7848963370999997,
3.8531043541,
3.9083962941,
3.8120957751,
3.7631213421000003
]
},
{
"command": "pnpr@HEAD",
"mean": 2.1742044055000003,
"stddev": 0.16002853871739245,
"median": 2.1359355071,
"user": 2.5035109999999996,
"system": 2.7535432799999997,
"min": 1.9696593790999999,
"max": 2.4545337231,
"times": [
2.3245884920999997,
2.2336279311,
2.1280450081,
2.3382902171,
2.1438260061,
1.9696593790999999,
2.0282355161,
2.4545337231,
2.1190334881,
2.0022042941
]
},
{
"command": "pnpr@main",
"mean": 2.1553271540999996,
"stddev": 0.15182468466119797,
"median": 2.1007015491,
"user": 2.5111084999999997,
"system": 2.7391932799999994,
"min": 1.9927967501000001,
"max": 2.3381442581,
"times": [
2.1506526741,
2.2931836651,
2.0507504241,
2.3339329171,
2.3320578811,
2.0145222271,
2.0234665070999998,
2.3381442581,
1.9927967501000001,
2.0237642371
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.0998141423,
"stddev": 0.007463650068356752,
"median": 1.0973841579,
"user": 1.07256114,
"system": 1.6776818800000002,
"min": 1.0905164779,
"max": 1.1109710739,
"times": [
1.1089615119,
1.1047753989,
1.1109710739,
1.1068323029,
1.0961164799,
1.0960745719,
1.0986518359000002,
1.0921770409,
1.0930647289000002,
1.0905164779
]
},
{
"command": "pacquet@main",
"mean": 1.1070197253,
"stddev": 0.029718806211795526,
"median": 1.1010465979000001,
"user": 1.06594964,
"system": 1.6913265799999997,
"min": 1.0671559049000001,
"max": 1.1672208829000001,
"times": [
1.0919893219,
1.1425455459,
1.0999689209,
1.0899817939,
1.1672208829000001,
1.1167018559000002,
1.1129586709000001,
1.0795500809,
1.0671559049000001,
1.1021242749000002
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6961167569000001,
"stddev": 0.08384877380664288,
"median": 0.6715059644,
"user": 0.33024654,
"system": 1.2831906800000001,
"min": 0.6525289869,
"max": 0.9332046629,
"times": [
0.6709930619,
0.6714183949,
0.6575668469,
0.6753294229,
0.6525289869,
0.9332046629,
0.6767204219,
0.6715935339,
0.6861778979000001,
0.6656343389
]
},
{
"command": "pnpr@main",
"mean": 0.7231288257,
"stddev": 0.08842628343812448,
"median": 0.6923104134,
"user": 0.32998464,
"system": 1.3013437800000003,
"min": 0.6817244239,
"max": 0.9730359369,
"times": [
0.7098701899000001,
0.6918475459,
0.6910534129,
0.6862306469,
0.6927732809,
0.6817244239,
0.6890731789,
0.7003695939,
0.9730359369,
0.7153100469
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.59880495146,
"stddev": 0.054881240458828806,
"median": 2.58906788976,
"user": 1.50407192,
"system": 1.9658132199999998,
"min": 2.55556212076,
"max": 2.74742531776,
"times": [
2.60320088076,
2.5975914687599997,
2.55618529176,
2.56920335276,
2.59153006576,
2.59716086876,
2.58660571376,
2.58358443376,
2.55556212076,
2.74742531776
]
},
{
"command": "pacquet@main",
"mean": 2.5854306151599995,
"stddev": 0.05225447923327142,
"median": 2.5684705327599997,
"user": 1.4976663199999998,
"system": 1.94977482,
"min": 2.55121581376,
"max": 2.7292508017599997,
"times": [
2.5700630317599997,
2.56465510976,
2.59336269676,
2.58706077676,
2.55173183976,
2.55121581376,
2.57411937476,
2.56596867276,
2.5668780337599997,
2.7292508017599997
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6885936340600001,
"stddev": 0.015042276011885677,
"median": 0.6896673602600001,
"user": 0.33782982,
"system": 1.27363612,
"min": 0.66504778076,
"max": 0.7108406807600001,
"times": [
0.7108406807600001,
0.69584909976,
0.69299715876,
0.68593039576,
0.66504778076,
0.68633756176,
0.7065367757600001,
0.69693890876,
0.67074115776,
0.6747168207600001
]
},
{
"command": "pnpr@main",
"mean": 0.66922436296,
"stddev": 0.015779471364458075,
"median": 0.66522356976,
"user": 0.33423331999999994,
"system": 1.2632276199999999,
"min": 0.65682431176,
"max": 0.70984428676,
"times": [
0.67373472176,
0.6573606307600001,
0.66778162376,
0.66676466876,
0.65914171276,
0.66368247076,
0.70984428676,
0.65682431176,
0.66034809976,
0.67676110276
]
}
]
} |
|
| Branch | pr/12227 |
| Testbed | pacquet |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result milliseconds (ms) (Result Δ%) | Upper Boundary milliseconds (ms) (Limit %) |
|---|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 3,822.63 ms(-53.03%)Baseline: 8,137.86 ms | 9,765.44 ms (39.14%) |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot 🚷 view threshold | 2,598.80 ms(-42.45%)Baseline: 4,515.97 ms | 5,419.16 ms (47.96%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,099.81 ms(-18.36%)Baseline: 1,347.15 ms | 1,616.58 ms (68.03%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 3,911.99 ms(-56.54%)Baseline: 9,000.90 ms | 10,801.08 ms (36.22%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 664.37 ms(-0.14%)Baseline: 665.32 ms | 798.39 ms (83.21%) |
|
This doesn't help unfortunately |
f92498c to
dcedd65
Compare
Why the benchmark shows no speedup — investigationLooked into why Direct
|
Lockfile verification (the minimumReleaseAge/trustPolicy policy gate plus the tarball-URL anti-tamper check, which issues a registry round trip per lockfile entry) blocked every later install stage. Kick it off without awaiting so it overlaps fetching and linking, and reconcile the verdict in settleInstall: a verification failure aborts the install even mid-flight, and an install that finishes first is held back until the verdict arrives. Dependency lifecycle scripts are still gated behind a successful verification via a verifyLockfile callback awaited immediately before every buildModules call, so no dependency script ever runs on an unverified lockfile.
…install error
The full-resolution `pnpm add` path can throw its own resolution-policy
error ("violations produced but no handleResolutionPolicyViolations callback
was wired") for the same underlying violation. Racing it against verification
let that generic error mask the specific minimumReleaseAge/trustPolicy error.
Await the verification verdict first so it takes precedence and still fails
fast, matching the original sequencing where verification gated the install.
…e frozen-install fetch Ports the pnpm-side optimization to pacquet. Lockfile verification (the minimumReleaseAge / trustPolicy='no-downgrade' gate plus the tarball-URL anti-tamper check) used to block the whole install: it ran to completion before any tarball was fetched, and each entry is a registry round trip. Now the verifiers are built up front but the fan-out is dispatched per path. On the frozen materialization path it runs concurrently with CreateVirtualStore via tokio::try_join!, so the per-entry round trips overlap the downloads and a rejected lockfile aborts the fetch in flight (fail-fast). The verdict is always reached before linking and BuildModules, so no dependency lifecycle script runs on an unverified lockfile. The lockfile-only / up-to-date short-circuits and the fresh-resolve path keep an eager blocking gate (they have no fetch to overlap). A verification failure surfaces as the same InstallError::LockfileVerification variant regardless of which path produced it.
…erifier API The rebase onto main picked up #12309, which threads an ObservedDistStats sink through build_resolution_verifiers and verify_input_lockfile and adds size-hint parameters to TarballPrefetcher::prefetch. Pass an empty sink where the overlap paths have no use for the stats, and prefetch lockfile tarballs without a work estimate (the lockfile records no dist sizes).
|
| Branch | pr/12227 |
| Testbed | pnpr |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | Latency | milliseconds (ms) |
|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot | 2,174.20 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 688.59 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 696.12 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 2,142.75 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 710.79 ms |
Code Review by Qodo
1. Prefetch downloads skipped deps
|
PR Summary by Qodoperf: Run lockfile verification concurrently with frozen installs WalkthroughsDescription• Run lockfile verification concurrently with fetch/link on frozen installs to reduce wall time. • Gate dependency lifecycle scripts on verification verdict across headless and full install paths. • Add pnpr /v1/verify-lockfile flow for pacquet and tests ensuring scripts never run unverified. Diagramgraph TD
A["pnpm frozen install"] --> C["fetch/link phases"] --> D["lifecycle scripts (buildModules)"]
A --> B["verifyLockfileResolutions"]
B --> D
E["pacquet frozen install"] --> F["try_join verify+fetch"] --> D
F --> G["pnpr /v1/verify-lockfile"]
High-Level AssessmentThe following are alternative approaches to this PR: 1. Defer node_modules/linking until verification completes
2. Add cleanup/rollback on verification failure
3. Batch verification metadata requests (reduce per-entry round trips)
Recommendation: Keep the PR’s approach: overlapping verification with fetch/link provides the performance win while maintaining the critical security guarantee via an explicit pre-script gate. The chosen reconciliation strategy (await verification first for error precedence + fail-fast) is appropriate, and the added tests specifically cover the highest-risk regression (scripts running before verification). File ChangesEnhancement (9)
Tests (3)
Documentation (1)
|
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
installing/deps-installer/src/install/index.ts (1)
404-432:⚠️ Potential issue | 🟠 Major | ⚡ Quick winStart verification after
preResolutionhas finished mutating the lockfile.
verifyLockfileResolutions()is kicked off on Lines 404-407 before thepreResolutionhooks run on Lines 421-432, but those hooks receive the livectx.wantedLockfileobject. A hook that rewrites importer/package snapshots can therefore change what_install()fetches and builds after the verifier has already snapshotted and approved the old contents, which reopens the resolver-policy bypass this gate is meant to prevent.verifyLockfilePromiseneeds to be created after the hooks finish, or from a post-hook clone of the lockfile.🤖 Prompt for 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. In `@installing/deps-installer/src/install/index.ts` around lines 404 - 432, The verification promise is started too early: verifyLockfileResolutions is invoked (creating verifyLockfilePromise) before opts.hooks.preResolution run and can observe a lockfile that hooks will mutate; move the creation of verifyLockfilePromise (and the derived verifyLockfile thunk) to after the preResolution loop or alternatively call verifyLockfileResolutions with a deep clone of ctx.wantedLockfile performed after the hooks complete so the verifier always checks the final lockfile; update references to verifyLockfilePromise/verifyLockfile accordingly (functions: verifyLockfileResolutions, verifyLockfilePromise, verifyLockfile, and opts.hooks.preResolution).
🧹 Nitpick comments (1)
pacquet/crates/pnpr-client/tests/integration.rs (1)
308-355: ⚡ Quick winAdd a
/v1/verify-lockfilecredential-forwarding test.These new tests cover accept/reject semantics, but the new endpoint has its own request builder in
PnprClient::verify_lockfile. A typo in theauthorizationheader orauthHeadersbody shape would break frozen installs against private registries without failing this suite, because both cases use a public package and never assert the wire contract. Please mirrorforwards_credentials_and_the_identity_header()for/v1/verify-lockfile.🤖 Prompt for 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. In `@pacquet/crates/pnpr-client/tests/integration.rs` around lines 308 - 355, Add a credential-forwarding integration test for the /v1/verify-lockfile endpoint similar to the existing forwards_credentials_and_the_identity_header test: exercise PnprClient::verify_lockfile (using VerifyLockfileOptions::from_resolve_options and a TestRegistry/start_pnpr setup) and assert the outgoing HTTP request both sets the Authorization header and includes the authHeaders body shape/identity header. Update or add the test in the same integration.rs test module to mirror the semantics and assertions of forwards_credentials_and_the_identity_header so any typo in PnprClient::verify_lockfile's request builder (authorization header name/value or authHeaders payload) will fail the suite.
🤖 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/deps-restorer/src/index.ts`:
- Around line 583-585: The guard that ensures dependency lifecycle scripts don't
run on an unverified lockfile is only executed inside the buildModules() branch;
when enableModulesDir === false headlessInstall() still runs project lifecycle
hooks without awaiting opts.verifyLockfile. Move the await
opts.verifyLockfile?.() check so it executes before any script-execution path
(i.e., above both the buildModules(...) call and the call to
runLifecycleHooksConcurrently(...)), or alternatively add an explicit await
opts.verifyLockfile?.() immediately before runLifecycleHooksConcurrently() in
headlessInstall(); ensure references to enableModulesDir, buildModules,
headlessInstall, runLifecycleHooksConcurrently, and opts.verifyLockfile are
updated accordingly.
In `@pacquet/crates/package-manager/src/install_frozen_lockfile.rs`:
- Around line 627-687: The current tokio::try_join!(verify_fut,
create_virtual_store_fut) can drop verify_fut and let CreateVirtualStore errors
surface first; change this by awaiting both futures to completion but giving
precedence to verification errors: implement a small "settle" pattern (e.g.
spawn or join both futures and then inspect results) so that verify_fut's Err is
returned as InstallFrozenLockfileError::LockfileVerification whenever
verification fails, otherwise return CreateVirtualStoreOutput on success; update
the code around verify_fut, create_virtual_store_fut and the tokio::try_join!
call to use that helper so verification is never dropped and its error always
wins.
In `@pacquet/crates/package-manager/src/install.rs`:
- Around line 732-735: The verification cache write uses
workspace_root.join(Lockfile::FILE_NAME) instead of the computed
derived_lockfile_path, causing mismatch when callers pass lockfile_path; update
the fresh-resolve block that calls record_lockfile_verified(...) to pass the
derived_lockfile_path (or its Path) instead of reconstructing
workspace_root.join(Lockfile::FILE_NAME) so the same lockfile path used for
cache key/verification is recorded; locate references to derived_lockfile_path,
record_lockfile_verified, and any workspace_root.join(Lockfile::FILE_NAME) in
install.rs and replace the latter with the derived_lockfile_path value (handling
the Option/Path conversion consistently).
- Around line 719-730: The verification path is reusing the same ThrottledClient
(http_client_arc from state.http_client) so concurrent verification competes
with tarball downloads; change the caller to create and pass a dedicated HTTP
client (or a new ThrottledClient instance with its own semaphore/permit budget
or reserved permits) into build_resolution_verifiers rather than
Arc::clone(&http_client_arc) so verification traffic has an independent budget;
update the call site around build_resolution_verifiers and any type wiring
(http_client_arc, state.http_client, CreateVirtualStore usage) so the verifier
receives the new client while the frozen/install path continues to use the
original client.
In `@pacquet/crates/package-manager/src/tarball_prefetch.rs`:
- Around line 289-294: registry_tarball_url currently always uses
config.registry, causing mismatches for packages from scoped/named registries;
change registry_tarball_url to determine the package's actual registry using the
same per-package registry selection logic used by the install path (the client
registry map / per-package registry lookup) for the given PackageKey, and only
fall back to config.registry if no override exists, then build the URL from that
resolved registry, package_key.name, package_key.suffix.version(), and
name.bare.
---
Outside diff comments:
In `@installing/deps-installer/src/install/index.ts`:
- Around line 404-432: The verification promise is started too early:
verifyLockfileResolutions is invoked (creating verifyLockfilePromise) before
opts.hooks.preResolution run and can observe a lockfile that hooks will mutate;
move the creation of verifyLockfilePromise (and the derived verifyLockfile
thunk) to after the preResolution loop or alternatively call
verifyLockfileResolutions with a deep clone of ctx.wantedLockfile performed
after the hooks complete so the verifier always checks the final lockfile;
update references to verifyLockfilePromise/verifyLockfile accordingly
(functions: verifyLockfileResolutions, verifyLockfilePromise, verifyLockfile,
and opts.hooks.preResolution).
---
Nitpick comments:
In `@pacquet/crates/pnpr-client/tests/integration.rs`:
- Around line 308-355: Add a credential-forwarding integration test for the
/v1/verify-lockfile endpoint similar to the existing
forwards_credentials_and_the_identity_header test: exercise
PnprClient::verify_lockfile (using VerifyLockfileOptions::from_resolve_options
and a TestRegistry/start_pnpr setup) and assert the outgoing HTTP request both
sets the Authorization header and includes the authHeaders body shape/identity
header. Update or add the test in the same integration.rs test module to mirror
the semantics and assertions of forwards_credentials_and_the_identity_header so
any typo in PnprClient::verify_lockfile's request builder (authorization header
name/value or authHeaders payload) will fail the suite.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c64aa0b3-594a-434d-9ac6-9f9b93103281
📒 Files selected for processing (13)
.changeset/parallel-lockfile-verification.mdinstalling/deps-installer/src/install/index.tsinstalling/deps-installer/test/install/trustLockfile.tsinstalling/deps-restorer/src/index.tspacquet/crates/cli/src/cli_args/install.rspacquet/crates/cli/tests/pnpr_install.rspacquet/crates/package-manager/src/install.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/package-manager/src/tarball_prefetch.rspacquet/crates/pnpr-client/src/lib.rspacquet/crates/pnpr-client/tests/integration.rspnpr/crates/pnpr/src/resolver.rspnpr/crates/pnpr/src/server.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/*.rs: Log emissions are part of matching pnpm — when porting a function that firespnpm:<channel>events throughglobalLogger,logger.debug(...), orstreamParser.write(...), mirror the call site, payload, and ordering so@pnpm/cli.default-reporterparses pacquet's NDJSON the same way
Declare a newtype wrapper for branded string types instead of collapsing the brand into a plainStringor&strin Rust
If upstream TypeScript always validates before construction of a branded string, validate in the Rust wrapper too viaTryFrom<String>and/orFromStrand do not provide an infallible public constructor
If upstream TypeScript never validates a branded string, just brand for type-safety in Rust by exposing an infallibleFrom<String>constructor
If upstream TypeScript occasionally constructs a branded string without validation, exposefrom_str_uncheckedin Rust as an escape hatch alongside the validating constructor
Match upstream serde behavior for branded strings crossing JSON, YAML, or INI boundaries by using#[serde(try_from = "String")]for deserialization and#[serde(into = "String")]for serialization
Derive simple conversions for branded strings using#[derive(derive_more::From)]and#[derive(derive_more::Into)]instead of handwritingimplblocks; use manualimplonly when conversion needs custom logic
Model TypeScript string literal unions (like'auto' | 'always' | 'never') as Rustenums instead of newtype wrappers, since the set of valid values is closed
Treat TypeScript string template literal types (like`${string}@${string}`) the same as branded string types in Rust, using a newtype wrapper with validation
Follow the code style guide inCODE_STYLE_GUIDE.md— imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching,pipe-trait, error handling, test layout, and cloning ofArcandRc
Choose owned vs. borrowed parameters to minimize copies; widen to t...
Files:
pacquet/crates/pnpr-client/tests/integration.rspacquet/crates/cli/tests/pnpr_install.rspacquet/crates/package-manager/src/tarball_prefetch.rspacquet/crates/pnpr-client/src/lib.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/cli/src/cli_args/install.rspacquet/crates/package-manager/src/install.rs
pacquet/**/tests/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/tests/**/*.rs: Prefer real fixtures over dependency-injection seams — usetempfile::TempDir, the mocked registry, or integration tests spawning the actual binary for happy paths and most error paths; use the DI seam only for filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths
Follow test-logging guidance — log before non-assert_eq!assertions,dbg!complex structures, skip logging for simple scalarassert_eq!
Tests must not be tolerant of missing build/runtime environment by silently returning early when a tool isn't found — let the test panic when a required tool is absent
Prefer#[cfg_attr(target_os = "windows", ignore = "...")](or matching#[cfg(unix)]gates) over runtime probe-and-skip helpers for platform-locked tools
Port relevant pnpm tests to Rust tests whenever they translate when porting behavior from pnpm
Use snapshot tests withinstaand carefully review diffs when intentional changes alter snapshots; accept withcargo insta reviewonly after careful review
Tests that need the mocked registry should startpnprthroughpacquet-testing-utils;cargo test/cargo nextest runshould not require a separatejust registry-mock launchstep
Files:
pacquet/crates/pnpr-client/tests/integration.rspacquet/crates/cli/tests/pnpr_install.rs
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Use Standard Style with trailing commas, prefer functions over classes, declare functions after they are used (relying on hoisting), limit functions to no more than two or three arguments, and use a single options object for functions needing more parameters
Follow import order: standard libraries first, then external dependencies (sorted alphabetically), then relative imports
Do not write comments that restate what the code already says; rename variables, split helpers, or move checks to more obvious places instead
Do not repeat documentation at call sites that already lives on the callee; update the JSDoc once and let every call site benefit
Use JSDoc for the function's contract (preconditions, postconditions, edge cases, why the function exists), not for re-narrating the function body
Do not record past implementation shape, refactor history, or removed code in comments; use git log and git blame for that information instead
Write comments only when the reason for code is non-obvious, a hidden invariant exists, a workaround for a known bug is needed, or an exception to surrounding pattern is deliberate
Files:
installing/deps-installer/test/install/trustLockfile.tsinstalling/deps-installer/src/install/index.tsinstalling/deps-restorer/src/index.ts
pnpr/**/pnpr/**/*.rs
📄 CodeRabbit inference engine (pnpr/AGENTS.md)
pnpr/**/pnpr/**/*.rs: Follow the pacquet code-style guide (../pacquet/CODE_STYLE_GUIDE.md) for Rust-level conventions including imports, naming, ownership, and error handling
Follow the pacquet contributing guide (../pacquet/CONTRIBUTING.md) for test layout and Rust conventions
Files:
pnpr/crates/pnpr/src/server.rspnpr/crates/pnpr/src/resolver.rs
🧠 Learnings (8)
📚 Learning: 2026-05-26T21:01:06.666Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11966
File: .changeset/require-tarball-integrity.md:6-6
Timestamp: 2026-05-26T21:01:06.666Z
Learning: In pnpm lockfile-related release notes/docs (especially changeset markdown), preserve URL hostnames exactly as they appear in pnpm-lock.yaml tarball resolution entries—keep hosts like `codeload.github.com`, `bitbucket.org`, and `gitlab.com` in lowercase. Do not “correct” them to title-case/preserve brand capitalization (e.g., LanguageTool rules like `GITHUB` capitalization) because these are literal URL fragments, not platform brand names.
Applied to files:
.changeset/parallel-lockfile-verification.md
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.
Applied to files:
pacquet/crates/pnpr-client/tests/integration.rspacquet/crates/cli/tests/pnpr_install.rspacquet/crates/package-manager/src/tarball_prefetch.rspacquet/crates/pnpr-client/src/lib.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/cli/src/cli_args/install.rspacquet/crates/package-manager/src/install.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.
Applied to files:
pacquet/crates/pnpr-client/tests/integration.rspacquet/crates/cli/tests/pnpr_install.rspacquet/crates/package-manager/src/tarball_prefetch.rspacquet/crates/pnpr-client/src/lib.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/cli/src/cli_args/install.rspacquet/crates/package-manager/src/install.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.
Applied to files:
pacquet/crates/pnpr-client/tests/integration.rspacquet/crates/cli/tests/pnpr_install.rspacquet/crates/package-manager/src/tarball_prefetch.rspacquet/crates/pnpr-client/src/lib.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/cli/src/cli_args/install.rspacquet/crates/package-manager/src/install.rs
📚 Learning: 2026-06-06T18:58:37.156Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12243
File: pacquet/crates/package-manager/src/install_package_by_snapshot.rs:319-322
Timestamp: 2026-06-06T18:58:37.156Z
Learning: When reviewing Rust code, do not assume `matches!(expr, Pattern(_))` will move out of `expr` if `Pattern(_)` contains no by-value bindings. `matches!` desugars to a `match` that auto-borrows the scrutinee for discrimination, so even if `expr` is a non-`Copy` value behind a shared reference (e.g., `&T`), the macro should not move-out of the borrowed data purely due to `matches!`. Treat `matches!(&expr, Pattern(_))` as a readability/clarity improvement, not a correctness requirement. Only flag potential move-out-of-borrow risks when the pattern includes by-value bindings that would require moving the matched value.
Applied to files:
pacquet/crates/pnpr-client/tests/integration.rspacquet/crates/cli/tests/pnpr_install.rspacquet/crates/package-manager/src/tarball_prefetch.rspnpr/crates/pnpr/src/server.rspnpr/crates/pnpr/src/resolver.rspacquet/crates/pnpr-client/src/lib.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/cli/src/cli_args/install.rspacquet/crates/package-manager/src/install.rs
📚 Learning: 2026-05-14T09:04:00.133Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/test/publishedBy.test.ts:350-354
Timestamp: 2026-05-14T09:04:00.133Z
Learning: In the pnpm/pnpm repository, ESLint is the authoritative style linter. Do not raise review findings for missing trailing commas in multiline function calls (e.g., `fs.writeFileSync(...)`) when this repo’s ESLint configuration does not report them and lint passes. Prefer deferring to the ESLint results for this specific trailing-comma rule rather than enforcing it manually in code review.
Applied to files:
installing/deps-installer/test/install/trustLockfile.tsinstalling/deps-installer/src/install/index.tsinstalling/deps-restorer/src/index.ts
📚 Learning: 2026-06-05T13:47:05.929Z
Learnt from: vsumner
Repo: pnpm/pnpm PR: 12190
File: installing/deps-installer/test/install/frozenStore.ts:2-17
Timestamp: 2026-06-05T13:47:05.929Z
Learning: In the pnpm/pnpm repository, the shared Jest preset keeps `injectGlobals` at its default (`true`), so `test` and `expect` are available as Jest globals. Therefore, reviewers should not flag (or treat as TypeScript/compilation errors) missing `import { test, expect } from 'jest/globals'` when a test file uses `test`/`expect` without importing them. Importing from `jest/globals` may still be used for consistency with sibling files, but it is not required for execution in this repo unless a Jest preset is explicitly configured with `injectGlobals: false`.
Applied to files:
installing/deps-installer/test/install/trustLockfile.ts
📚 Learning: 2026-06-05T13:47:26.046Z
Learnt from: vsumner
Repo: pnpm/pnpm PR: 12190
File: installing/deps-installer/src/install/index.ts:2337-2343
Timestamp: 2026-06-05T13:47:26.046Z
Learning: In the pnpm/pnpm codebase, `PnpmError` automatically prefixes `err.code` with `ERR_PNPM_` when you pass a code that does not already start with `ERR_PNPM_` (it normalizes `this.code` via `code.startsWith('ERR_PNPM_') ? code : `ERR_PNPM_${code}``). Therefore, during code review you should NOT flag `new PnpmError(...)` call sites for passing a bare error code (e.g., `new PnpmError('FROZEN_STORE_INCOMPATIBLE_WITH_PNPR', ...)`); the resulting `err.code` will still be `ERR_PNPM_FROZEN_STORE_INCOMPATIBLE_WITH_PNPR`.
Applied to files:
installing/deps-installer/test/install/trustLockfile.tsinstalling/deps-installer/src/install/index.tsinstalling/deps-restorer/src/index.ts
🔇 Additional comments (5)
.changeset/parallel-lockfile-verification.md (1)
1-8: LGTM!pacquet/crates/pnpr-client/src/lib.rs (1)
85-123: LGTM!Also applies to: 246-307, 409-454
pnpr/crates/pnpr/src/resolver.rs (1)
20-23: LGTM!Also applies to: 300-334, 551-554
pnpr/crates/pnpr/src/server.rs (1)
163-165: LGTM!Also applies to: 1781-1785
installing/deps-installer/test/install/trustLockfile.ts (1)
1-7: LGTM!Also applies to: 60-82
… the verification override
…tore On a warm store the frozen restore's prefetch_lockfile spawned one task per lockfile entry, each doing its own SQLite lookup plus a per-file CAS verification pass through a fresh verified-files cache — duplicating, in un-batched form, the single batched verified pass the materializer runs anyway. The /v1/resolve path never pays this on a verdict-cached restore (bare done frame, no package frames), so the new frozen path regressed the hot-cache/hot-store fresh-restore benchmark against pnpr. Stage the candidates and filter them through one batched index.db existence probe (StoreIndex::contains_many, keys-only SELECT) before spawning: a fully warm store now prefetches nothing, a cold store is unchanged, and a row whose CAS files vanished is still re-downloaded by the materializer's per-snapshot fallback. The prefetch URLs are derived via the shared tarball_url_and_integrity so they stay byte-identical with the materializer's. The frozen pnpr CLI test now also swaps the registry for a zero-expectation server before the restore, pinning that a warm-store frozen restore makes no registry requests.
|
Code review by qodo was updated up to the latest commit 8f19611 |
…gate The gate only covered buildModules, but the projects' own preinstall/postinstall hooks run outside that branch — always in the headless installer, and in the full-resolution path whenever no new dep paths made the build branch run. With enableModulesDir set to false the build branch is skipped entirely, so those hooks were the one script-execution path left ungated and could run while verification was still in flight. Await the verdict before runLifecycleHooksConcurrently at both call sites. The new test uses a slow-rejecting verifier: an instantly-rejecting one aborts the install before the hooks are even attempted, hiding a missing gate.
… error tokio::try_join! surfaces whichever error lands first, so an unrelated fetch failure could mask a rejected lockfile. Settle the two futures with a select instead: a verification failure still aborts the fetch in flight, while a fetch failure now waits for the verdict and only surfaces once the lockfile is known trusted — mirroring pnpm's settleInstall precedence.
…plied lockfile path The eager gates key the lockfile-verified cache off derived_lockfile_path, but the fresh-resolve record wrote against workspace_root/pnpm-lock.yaml — a caller-supplied lockfile_path would verify one path and record another, costing the next install the verification round trip.
Each verify call targets a fresh pnpr so neither the whole-lockfile verdict cache nor the metadata mirror warmed by an earlier call can satisfy it without exercising the forwarded credential map.
|
Code review by qodo was updated up to the latest commit 9c6981b |
|
Addressed the remaining review feedback (CodeRabbit review + Qodo report) in 8659fa3…9c6981b408: Fixed
Not fixed, with rationale
Written by an agent (Claude Code, claude-fable-5). |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
pacquet/crates/pnpr-client/tests/integration.rs (1)
396-399: ⚡ Quick winNarrow the failure assertion to the expected auth/verification path.
At Line 397,
.is_err()is too broad and can pass on unrelated failures (transport/protocol/startup), which weakens this contract test.Suggested tightening
- assert!( - PnprClient::new(anonymous_pnpr_url).verify_lockfile(anonymous_verify_opts).await.is_err(), - "without the credential the gated entry's metadata fetch must fail closed", - ); + let err = PnprClient::new(anonymous_pnpr_url) + .verify_lockfile(anonymous_verify_opts) + .await + .expect_err("without the credential, verification should fail closed"); + assert!( + err.to_string().contains("401") || err.to_string().contains("unauthorized"), + "expected an auth-related verification failure, got: {err}", + );🤖 Prompt for 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. In `@pacquet/crates/pnpr-client/tests/integration.rs` around lines 396 - 399, Capture the Result from PnprClient::new(anonymous_pnpr_url).verify_lockfile(anonymous_verify_opts).await into a variable and assert it specifically matches the authentication/verification failure variant instead of using .is_err(); e.g., match on the error type returned by verify_lockfile (reference PnprClient and verify_lockfile) and assert it is the expected auth/credential error variant (or use a provided method like is_auth_error()/is_verification_failure on the error), otherwise fail the test and include the actual error for diagnostics.
🤖 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.
Nitpick comments:
In `@pacquet/crates/pnpr-client/tests/integration.rs`:
- Around line 396-399: Capture the Result from
PnprClient::new(anonymous_pnpr_url).verify_lockfile(anonymous_verify_opts).await
into a variable and assert it specifically matches the
authentication/verification failure variant instead of using .is_err(); e.g.,
match on the error type returned by verify_lockfile (reference PnprClient and
verify_lockfile) and assert it is the expected auth/credential error variant (or
use a provided method like is_auth_error()/is_verification_failure on the
error), otherwise fail the test and include the actual error for diagnostics.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 445ab7b7-72f2-4f44-8d1e-306bda91da6c
📒 Files selected for processing (6)
installing/deps-installer/src/install/index.tsinstalling/deps-installer/test/install/trustLockfile.tsinstalling/deps-restorer/src/index.tspacquet/crates/package-manager/src/install.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/pnpr-client/tests/integration.rs
🚧 Files skipped from review as they are similar to previous changes (4)
- installing/deps-installer/src/install/index.ts
- installing/deps-restorer/src/index.ts
- pacquet/crates/package-manager/src/install.rs
- pacquet/crates/package-manager/src/install_frozen_lockfile.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Analyze (javascript)
- GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Use Standard Style with trailing commas, prefer functions over classes, declare functions after they are used (relying on hoisting), limit functions to no more than two or three arguments, and use a single options object for functions needing more parameters
Follow import order: standard libraries first, then external dependencies (sorted alphabetically), then relative imports
Do not write comments that restate what the code already says; rename variables, split helpers, or move checks to more obvious places instead
Do not repeat documentation at call sites that already lives on the callee; update the JSDoc once and let every call site benefit
Use JSDoc for the function's contract (preconditions, postconditions, edge cases, why the function exists), not for re-narrating the function body
Do not record past implementation shape, refactor history, or removed code in comments; use git log and git blame for that information instead
Write comments only when the reason for code is non-obvious, a hidden invariant exists, a workaround for a known bug is needed, or an exception to surrounding pattern is deliberate
Files:
installing/deps-installer/test/install/trustLockfile.ts
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/*.rs: Log emissions are part of matching pnpm — when porting a function that firespnpm:<channel>events throughglobalLogger,logger.debug(...), orstreamParser.write(...), mirror the call site, payload, and ordering so@pnpm/cli.default-reporterparses pacquet's NDJSON the same way
Declare a newtype wrapper for branded string types instead of collapsing the brand into a plainStringor&strin Rust
If upstream TypeScript always validates before construction of a branded string, validate in the Rust wrapper too viaTryFrom<String>and/orFromStrand do not provide an infallible public constructor
If upstream TypeScript never validates a branded string, just brand for type-safety in Rust by exposing an infallibleFrom<String>constructor
If upstream TypeScript occasionally constructs a branded string without validation, exposefrom_str_uncheckedin Rust as an escape hatch alongside the validating constructor
Match upstream serde behavior for branded strings crossing JSON, YAML, or INI boundaries by using#[serde(try_from = "String")]for deserialization and#[serde(into = "String")]for serialization
Derive simple conversions for branded strings using#[derive(derive_more::From)]and#[derive(derive_more::Into)]instead of handwritingimplblocks; use manualimplonly when conversion needs custom logic
Model TypeScript string literal unions (like'auto' | 'always' | 'never') as Rustenums instead of newtype wrappers, since the set of valid values is closed
Treat TypeScript string template literal types (like`${string}@${string}`) the same as branded string types in Rust, using a newtype wrapper with validation
Follow the code style guide inCODE_STYLE_GUIDE.md— imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching,pipe-trait, error handling, test layout, and cloning ofArcandRc
Choose owned vs. borrowed parameters to minimize copies; widen to t...
Files:
pacquet/crates/pnpr-client/tests/integration.rs
pacquet/**/tests/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/tests/**/*.rs: Prefer real fixtures over dependency-injection seams — usetempfile::TempDir, the mocked registry, or integration tests spawning the actual binary for happy paths and most error paths; use the DI seam only for filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths
Follow test-logging guidance — log before non-assert_eq!assertions,dbg!complex structures, skip logging for simple scalarassert_eq!
Tests must not be tolerant of missing build/runtime environment by silently returning early when a tool isn't found — let the test panic when a required tool is absent
Prefer#[cfg_attr(target_os = "windows", ignore = "...")](or matching#[cfg(unix)]gates) over runtime probe-and-skip helpers for platform-locked tools
Port relevant pnpm tests to Rust tests whenever they translate when porting behavior from pnpm
Use snapshot tests withinstaand carefully review diffs when intentional changes alter snapshots; accept withcargo insta reviewonly after careful review
Tests that need the mocked registry should startpnprthroughpacquet-testing-utils;cargo test/cargo nextest runshould not require a separatejust registry-mock launchstep
Files:
pacquet/crates/pnpr-client/tests/integration.rs
🧠 Learnings (7)
📚 Learning: 2026-05-14T09:04:00.133Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/test/publishedBy.test.ts:350-354
Timestamp: 2026-05-14T09:04:00.133Z
Learning: In the pnpm/pnpm repository, ESLint is the authoritative style linter. Do not raise review findings for missing trailing commas in multiline function calls (e.g., `fs.writeFileSync(...)`) when this repo’s ESLint configuration does not report them and lint passes. Prefer deferring to the ESLint results for this specific trailing-comma rule rather than enforcing it manually in code review.
Applied to files:
installing/deps-installer/test/install/trustLockfile.ts
📚 Learning: 2026-06-05T13:47:05.929Z
Learnt from: vsumner
Repo: pnpm/pnpm PR: 12190
File: installing/deps-installer/test/install/frozenStore.ts:2-17
Timestamp: 2026-06-05T13:47:05.929Z
Learning: In the pnpm/pnpm repository, the shared Jest preset keeps `injectGlobals` at its default (`true`), so `test` and `expect` are available as Jest globals. Therefore, reviewers should not flag (or treat as TypeScript/compilation errors) missing `import { test, expect } from 'jest/globals'` when a test file uses `test`/`expect` without importing them. Importing from `jest/globals` may still be used for consistency with sibling files, but it is not required for execution in this repo unless a Jest preset is explicitly configured with `injectGlobals: false`.
Applied to files:
installing/deps-installer/test/install/trustLockfile.ts
📚 Learning: 2026-06-05T13:47:26.046Z
Learnt from: vsumner
Repo: pnpm/pnpm PR: 12190
File: installing/deps-installer/src/install/index.ts:2337-2343
Timestamp: 2026-06-05T13:47:26.046Z
Learning: In the pnpm/pnpm codebase, `PnpmError` automatically prefixes `err.code` with `ERR_PNPM_` when you pass a code that does not already start with `ERR_PNPM_` (it normalizes `this.code` via `code.startsWith('ERR_PNPM_') ? code : `ERR_PNPM_${code}``). Therefore, during code review you should NOT flag `new PnpmError(...)` call sites for passing a bare error code (e.g., `new PnpmError('FROZEN_STORE_INCOMPATIBLE_WITH_PNPR', ...)`); the resulting `err.code` will still be `ERR_PNPM_FROZEN_STORE_INCOMPATIBLE_WITH_PNPR`.
Applied to files:
installing/deps-installer/test/install/trustLockfile.ts
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.
Applied to files:
pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.
Applied to files:
pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.
Applied to files:
pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-06-06T18:58:37.156Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12243
File: pacquet/crates/package-manager/src/install_package_by_snapshot.rs:319-322
Timestamp: 2026-06-06T18:58:37.156Z
Learning: When reviewing Rust code, do not assume `matches!(expr, Pattern(_))` will move out of `expr` if `Pattern(_)` contains no by-value bindings. `matches!` desugars to a `match` that auto-borrows the scrutinee for discrimination, so even if `expr` is a non-`Copy` value behind a shared reference (e.g., `&T`), the macro should not move-out of the borrowed data purely due to `matches!`. Treat `matches!(&expr, Pattern(_))` as a readability/clarity improvement, not a correctness requirement. Only flag potential move-out-of-borrow risks when the pattern includes by-value bindings that would require moving the matched value.
Applied to files:
pacquet/crates/pnpr-client/tests/integration.rs
🔇 Additional comments (1)
installing/deps-installer/test/install/trustLockfile.ts (1)
84-120: LGTM!
Problem
pnpm installwith a frozen lockfile got noticeably slower because lockfile verification blocks every later install stage. The verification gate (theminimumReleaseAge/trustPolicypolicy revalidation plus the tarball-URL anti-tamper check) issues a registry round trip per lockfile entry, and the whole install waited for it to finish before any fetching or linking could begin.Change (pnpm / TypeScript)
Run lockfile verification concurrently with fetching and linking instead of blocking on it, while keeping two guarantees intact:
verifyLockfilegate is threaded into bothbuildModulescall sites —headlessInstall(frozen path) and_installInContext(full-resolution path) — and awaited immediately before any dependency lifecycle script runs. The projects' ownpreinstall/postinstallhooks are held to the same gate at bothrunLifecycleHooksConcurrentlycall sites, covering theenableModulesDir: falsepath that skips the build phase. If verification failed, the gate throws before a single script executes.settleInstall(_install(), verifyLockfilePromise)awaits the verification verdict first so it takes precedence and fails fast (even mid-install), then surfaces the install's result/error. This also covers paths that skip the build phase entirely (ignoreScripts,lockfileOnly, empty lockfile).Verification's synchronous prologue (cache lookup, lockfile hash, candidate collection) still runs against the pristine lockfile before
_install()mutatesctx.wantedLockfile, so the concurrent async fan-out reads a stable snapshot — no data race.The verification verdict deliberately takes precedence over a concurrent install error:
pnpm add's full-resolution path can throw its own generic "resolution-policy violations produced but no handler wired" for the same underlying violation, andsettleInstallmakes sure the specificminimumReleaseAge/trustPolicyerror is what surfaces.Change (pacquet / Rust)
Same optimization ported to
pacquet/crates/package-manager/.Install::runbuilds the resolution verifiers up front but dispatches the verification fan-out per path:CreateVirtualStore(the fetch), settled with aselect!so the verdict takes precedence: a rejected lockfile aborts the fetch in flight (fail-fast), while a fetch failure waits for the verdict and only surfaces once the lockfile is known trusted — an unrelated fetch error can't mask a rejected lockfile. The verdict is always reached before linking andBuildModules, so no dependency lifecycle script runs on an unverified lockfile.A verification failure surfaces as the same
InstallError::LockfileVerificationvariant regardless of which path produced it.On the pnpr client, a frozen restore now skips resolution entirely: tarball downloads start from the local lockfile at t=0 (filtered through one batched store-index existence probe, so a warm store prefetches nothing) while the server delivers only the trust verdict via the new
POST /v1/verify-lockfileendpoint, concurrently with the fetch.Tests
test/install/trustLockfile.tscovers the rejection itself, thetrustLockfileopt-out, and both script gates — a dependencypostinstallnever runs when verification fails, and the projects' own lifecycle hooks never run either, asserted on theenableModulesDir: falsepath with a slow-rejecting verifier (an instantly-rejecting one aborts the install before the hooks are attempted, which would hide a missing gate). Existing verification/lifecycle/minimumReleaseAgesuites pass.frozen_lockfile_gate_rejects_under_huge_minimum_release_age(unit) andinstall_fails_under_huge_minimum_release_age(CLI) assert the frozen install aborts with no virtual-store materialization on verification failure — proving the fail-fast settle cancels the fetch. New:without_store_hits+StoreIndex::contains_manyunit tests pin the warm-store prefetch filter, and the frozen pnpr CLI test swaps the registry for a zero-expectation server before the restore, proving a warm-store frozen restore makes no registry requests./v1/verify-lockfileaccepting a clean lockfile, rejecting a policy violation, honoringtrustLockfile, and forwarding the client's credential map (each verify call targets a fresh pnpr so no verdict/metadata cache can satisfy it without exercising the credential).cargo doc -D warnings/ rustfmt / eslint clean; package-manager, lockfile-verification, store-dir, pnpr-client, and CLI pnpr-install suites all pass.Behavioral nuance
On a rejected lockfile, fetching/linking may now have partially populated the store/
node_modulesbefore the abort (previously nothing ran, since verification went first). The command still fails with the same error code and no lifecycle scripts run.Written by agents (Claude Code: claude-opus-4-8, claude-fable-5).
Summary by CodeRabbit
New Features
Improvements
Tests