Conversation
|
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:
📝 WalkthroughWalkthroughThe integrated benchmark now plans and spawns per-revision registry mocks with latency proxies, and pnpr’s tarball serving path no longer reads or writes cached-tarball integrity sidecars. Related storage helpers and tests were removed, and server tests were updated for the new cache behavior. ChangesPer-revision registry mocks in integrated benchmark
Tarball cache integrity sidecar removal
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
✨ Finishing Touches🧪 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 |
Micro-Benchmark ResultsLinux |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #12709 +/- ##
==========================================
+ Coverage 85.20% 85.29% +0.08%
==========================================
Files 397 404 +7
Lines 61268 61572 +304
==========================================
+ Hits 52206 52515 +309
+ Misses 9062 9057 -5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Integrated-Benchmark Report (Linux)Commit: 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": 4.16084052054,
"stddev": 0.27675121102565586,
"median": 4.10752481484,
"user": 3.7569393399999997,
"system": 3.4924717199999997,
"min": 3.78060459434,
"max": 4.61496208734,
"times": [
4.25769946634,
4.61496208734,
4.2812876943400004,
4.56231826134,
4.03577756134,
4.17883287934,
3.78060459434,
4.03621675034,
4.01894527034,
3.84176064034
]
},
{
"command": "pacquet@main",
"mean": 4.73055966534,
"stddev": 0.17573648263351296,
"median": 4.69099406234,
"user": 3.8524516399999995,
"system": 3.49795622,
"min": 4.53133841934,
"max": 5.04091410934,
"times": [
4.70203467134,
5.04091410934,
4.53810730334,
4.6799534533400005,
4.81250079434,
4.54014371934,
4.67774439434,
4.53133841934,
4.93619453134,
4.84666525734
]
},
{
"command": "pnpr@HEAD",
"mean": 2.15029018534,
"stddev": 0.1251483047953714,
"median": 2.10570863984,
"user": 2.6575227399999997,
"system": 3.0617255199999995,
"min": 1.9827924723400001,
"max": 2.40526906634,
"times": [
2.40526906634,
2.05329777034,
2.15719234634,
2.28773272734,
1.9827924723400001,
2.10866142934,
2.08957860434,
2.23446608634,
2.08115550034,
2.10275585034
]
},
{
"command": "pnpr@main",
"mean": 3.03517864684,
"stddev": 0.10240409314525827,
"median": 2.99788237984,
"user": 2.73340234,
"system": 3.05770852,
"min": 2.94591992934,
"max": 3.29161001034,
"times": [
2.94591992934,
3.00075984934,
2.94959966134,
2.96760973434,
3.06435934634,
3.08007609534,
3.29161001034,
2.99500491034,
2.98963713234,
3.06720979934
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.66301842792,
"stddev": 0.011430521325628485,
"median": 0.66239593622,
"user": 0.39397384,
"system": 1.33938088,
"min": 0.63805058072,
"max": 0.68037321372,
"times": [
0.66434468172,
0.63805058072,
0.65994595172,
0.65957352772,
0.66044719072,
0.65588731672,
0.68037321372,
0.6706579597200001,
0.6714324057200001,
0.66947145072
]
},
{
"command": "pacquet@main",
"mean": 0.6717858862200001,
"stddev": 0.0708251451003117,
"median": 0.64684206822,
"user": 0.39397713999999995,
"system": 1.33710758,
"min": 0.62229921172,
"max": 0.86465983572,
"times": [
0.6440883457200001,
0.62229921172,
0.66116758172,
0.6427808297200001,
0.6495957907200001,
0.63335444472,
0.64328886472,
0.65699663472,
0.86465983572,
0.6996273227200001
]
},
{
"command": "pnpr@HEAD",
"mean": 0.7515061862200001,
"stddev": 0.022302747830847954,
"median": 0.74923235322,
"user": 0.40795773999999996,
"system": 1.4131675799999999,
"min": 0.72194229672,
"max": 0.78890307272,
"times": [
0.76722816472,
0.72194229672,
0.7512794607200001,
0.73592014772,
0.74718524572,
0.73582040272,
0.73134780072,
0.7838003707200001,
0.78890307272,
0.7516348997200001
]
},
{
"command": "pnpr@main",
"mean": 0.7410405257200001,
"stddev": 0.0590788995727284,
"median": 0.7271690767200001,
"user": 0.41621553999999994,
"system": 1.3698100799999997,
"min": 0.70163605072,
"max": 0.90366145072,
"times": [
0.7361627527200001,
0.71321062272,
0.7040589997200001,
0.74368249172,
0.7231115367200001,
0.70163605072,
0.73122661672,
0.7413165167200001,
0.90366145072,
0.71233821872
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.383262611759999,
"stddev": 0.03338612735037397,
"median": 4.38297818646,
"user": 3.8846257,
"system": 3.4131755199999994,
"min": 4.33714475646,
"max": 4.46133999646,
"times": [
4.46133999646,
4.38383386846,
4.37179442046,
4.35037634446,
4.369049428459999,
4.38212250446,
4.38933351646,
4.38567587946,
4.40195540246,
4.33714475646
]
},
{
"command": "pacquet@main",
"mean": 4.8701052882599996,
"stddev": 0.05905926337881084,
"median": 4.861389707959999,
"user": 3.9353656,
"system": 3.42920302,
"min": 4.783181777459999,
"max": 4.979907894459999,
"times": [
4.979907894459999,
4.80389072546,
4.91380115746,
4.859982716459999,
4.86279669946,
4.841388974459999,
4.783181777459999,
4.920473288459999,
4.8991040114599995,
4.836525637459999
]
},
{
"command": "pnpr@HEAD",
"mean": 2.2226014432599994,
"stddev": 0.12667786784614857,
"median": 2.23932319396,
"user": 2.5250641000000003,
"system": 2.98261312,
"min": 2.03983294346,
"max": 2.44221058446,
"times": [
2.34479785446,
2.06877710846,
2.44221058446,
2.03983294346,
2.20238895146,
2.09335705146,
2.29158248846,
2.23616132446,
2.26442106246,
2.24248506346
]
},
{
"command": "pnpr@main",
"mean": 2.8487906435600006,
"stddev": 0.0769785230503732,
"median": 2.8249017484600003,
"user": 2.5668422,
"system": 2.97396252,
"min": 2.76907604746,
"max": 3.02640782246,
"times": [
2.88105285746,
3.02640782246,
2.82353695746,
2.79116196246,
2.82626653946,
2.78163735746,
2.86375811146,
2.76907604746,
2.8134521924600002,
2.9115565874600002
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.39181140606,
"stddev": 0.011831796181524244,
"median": 1.38826238456,
"user": 1.38426744,
"system": 1.70740646,
"min": 1.37965123506,
"max": 1.41228238106,
"times": [
1.41228238106,
1.41180820506,
1.37965123506,
1.38361546806,
1.38105441606,
1.3853603880599998,
1.39277408006,
1.39116438106,
1.3957735950599999,
1.38462991106
]
},
{
"command": "pacquet@main",
"mean": 1.42121798196,
"stddev": 0.07261576702916818,
"median": 1.39604972606,
"user": 1.3855946399999999,
"system": 1.7102042599999998,
"min": 1.36992026606,
"max": 1.62037294606,
"times": [
1.38738394406,
1.62037294606,
1.38744125806,
1.4310286830599999,
1.41223857906,
1.42684821006,
1.38484648106,
1.40234792906,
1.38975152306,
1.36992026606
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6973124565600002,
"stddev": 0.02617802823314962,
"median": 0.6871538295600002,
"user": 0.35370984,
"system": 1.3174836599999997,
"min": 0.6810344570600001,
"max": 0.7677954090600001,
"times": [
0.6879564880600001,
0.6863511710600001,
0.6810344570600001,
0.7677954090600001,
0.7002381160600001,
0.6817954300600001,
0.6812310440600001,
0.68394532106,
0.7032911450600001,
0.6994859840600001
]
},
{
"command": "pnpr@main",
"mean": 0.6939318545600001,
"stddev": 0.03730515415972974,
"median": 0.68474413706,
"user": 0.36808663999999996,
"system": 1.29109236,
"min": 0.6717218620600001,
"max": 0.7972096570600001,
"times": [
0.6717218620600001,
0.6791644010600001,
0.67427837306,
0.6854378340600001,
0.6730643010600001,
0.6857186040600001,
0.7972096570600001,
0.68405044006,
0.7009741160600002,
0.6876989570600001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 3.19188278086,
"stddev": 0.044491575057182126,
"median": 3.1907892154599997,
"user": 1.94335712,
"system": 2.0077632,
"min": 3.12951239946,
"max": 3.2741669934599997,
"times": [
3.2741669934599997,
3.20544951746,
3.1759129534599997,
3.17840369246,
3.23988138046,
3.1721596224599997,
3.1329136904599997,
3.20317473846,
3.20725282046,
3.12951239946
]
},
{
"command": "pacquet@main",
"mean": 3.2049756935600002,
"stddev": 0.058603781261783124,
"median": 3.1944073179599997,
"user": 1.9633478199999999,
"system": 2.0229043,
"min": 3.1372338734599996,
"max": 3.33989838346,
"times": [
3.17781552646,
3.1856413574599998,
3.19357728546,
3.1372338734599996,
3.20158183146,
3.14601295546,
3.19523735046,
3.33989838346,
3.2620678064599997,
3.2106905654599998
]
},
{
"command": "pnpr@HEAD",
"mean": 0.70527640206,
"stddev": 0.014604565716731376,
"median": 0.70439825396,
"user": 0.35953011999999995,
"system": 1.3412924,
"min": 0.68235040946,
"max": 0.73166365546,
"times": [
0.7051140464600001,
0.6951002444600001,
0.71910235146,
0.68235040946,
0.68935133246,
0.7011034744600001,
0.70368246146,
0.7158706744600001,
0.7094253704600001,
0.73166365546
]
},
{
"command": "pnpr@main",
"mean": 0.7111088024600002,
"stddev": 0.06068804559609077,
"median": 0.6911490369600001,
"user": 0.37165452,
"system": 1.3124242999999998,
"min": 0.67575317946,
"max": 0.88083519446,
"times": [
0.69994012346,
0.69288622046,
0.6894118534600001,
0.68857791946,
0.68177612346,
0.6856215254600001,
0.88083519446,
0.7161267234600001,
0.70015916146,
0.67575317946
]
}
]
} |
|
| Branch | pr/12709 |
| 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 | 4,383.26 ms(-7.96%)Baseline: 4,762.30 ms | 5,714.75 ms (76.70%) |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot 🚷 view threshold | 3,191.88 ms(+4.42%)Baseline: 3,056.85 ms | 3,668.22 ms (87.01%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,391.81 ms(+2.48%)Baseline: 1,358.09 ms | 1,629.71 ms (85.40%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 4,160.84 ms(-14.10%)Baseline: 4,843.80 ms | 5,812.56 ms (71.58%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 663.02 ms(+2.75%)Baseline: 645.27 ms | 774.33 ms (85.62%) |
|
| Branch | pr/12709 |
| 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,222.60 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 705.28 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 697.31 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 2,150.29 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 751.51 ms |
Findings — closing this PR (no code change needed)After full diagnosis backed by the integrated-benchmark on CI, CI benchmark on
|
| Scenario | pnpr@main |
pacquet@main (direct) |
win |
|---|---|---|---|
| fresh-install · cold-cache · cold-store | 3.04 s | 5.00 s | ~1.6× |
| fresh-restore · cold-cache · cold-store | 3.09 s | 4.89 s | ~1.6× |
| fresh-install · cold-cache · hot-store | 0.75 s | 3.26 s | ~4.4× |
| fresh-install · hot-cache · hot-store | 0.74 s | 1.54 s | ~2.1× |
Why cold-store's win is smaller than hot-store's (and why that's not a regression)
The cold-store scenarios are dominated by per-connection fetch latency + client-side CAS materialization (extracting/writing ~1308 packages), which the hot-store scenarios skip entirely. pnpr's win is resolution offload — it can't make the client's tarball fetches or CAS writes cheaper. So cold-store lands at ~1.6× (the resolution it removes) while hot-store, where downloads/CAS are absent, shows the full ~4.4×.
What I tried, and what the benchmark proved
- Original change in this PR (replay sized
packageframes on the resolution cache hit). CI showed it regressed cold-store (fresh-install.cold-cache.cold-store2.82 s → 4.71 s) via a redundant download path. Reverted — this is what "much slower" referred to. - Route public tarballs through pnpr's warm cache (so the client fetches from pnpr's uncapped link instead of the bandwidth-capped registry). Implemented and CI-tested: no measurable change (cold-store 2.96 s vs 3.04 s, within noise). cold-store is not bandwidth-bound — the 200 Mbit/s cap never bites for the fixture's small tarballs; the cost is latency + CAS. So this lever doesn't help, and it would reverse feat(pnpr): make resolver cache authorization-aware #12700's stateless-public-tarball design for no gain. Abandoned.
Conclusion
main is already at (or better than) pre-auth performance for every scenario — #12700 fixed the auth-era resolution-cache regression, and the remaining cold-store cost is inherent (downloads + CAS), not a regression. No further legitimate speedup is available without making pnpr cache/serve tarballs and assuming a faster-than-registry client↔pnpr link — an architecture decision, not a bug fix.
(Note: a small, unrelated macOS/APFS robustness fix for the benchmark harness — retry rm -rf on transient "Directory not empty" — is worth landing on its own if you run the benchmark on macOS.)
Written by an agent (Claude Code, claude-opus-4-8).
The proxy-cache tarball serve path re-hashed each cached `.tgz` against the version's `dist.integrity` on every request (#12570). On a cold-store install the client pulls ~1300 tarballs from pnpr, so the accelerator paid a full SRI pass per tarball — the cold-store regression visible in Bencher from the auth-era change cluster onward (~2.1s -> ~2.9s, persistent). The re-hash is redundant. A tarball only enters the cache through `download_verified_to_cache`, which verifies the bytes against `dist.integrity` as they are written, so nothing unverified is ever stored. Every install client also re-verifies the tarball it receives against that same integrity. Serve the cached bytes directly instead. Write-time verification (and rejection of a poisoned upstream tarball, never caching it) is preserved, so GHSA-5f9g-98vq-2jxw stays mitigated. The namespaced `/~<uplink>/` route keeps its sidecar-keyed integrity cache and is unaffected. Drops the now-dead `record_cached_tarball_integrity`/`discard_cached_tarball` helpers and the per-serve cached-tarball integrity sidecar.
Removing only the on-read SRI re-hash recovered part of the cold-store regression but not all of it: #12570 also made every tarball serve load and parse the package's packument to bind the request to a version and its `dist.integrity`. On a warm-cache cold-store install (~1300 tarballs pulled from pnpr) that is a packument parse per tarball — the larger half of the regression. The cache hit needs neither binding. A tarball only enters the proxy cache through `download_verified_to_cache`, which resolves the version and verifies the bytes against `dist.integrity` as they are written, so the cache only ever holds correct bytes for a given (name, filename); the install client re-verifies whatever it receives against its own expected integrity regardless. The OSV screen on the filename's version still runs before the cache read. So serve cached bytes directly and defer the packument load to the cache-miss download path, which still needs the integrity to verify the freshly-fetched bytes before caching them. This matches the pre-regression cache-hit serve cost while keeping write-time verification (which the pre-regression path lacked), so the bytes that enter the cache are still verified and GHSA-5f9g-98vq-2jxw stays mitigated.
Benchmark proof: cold-store restored to the pre-#12570 baselineI bisected the regression on Bencher's
#12700 recovered the resolution half but not the tarball-serve half. #12570 added two costs to every tarball serve (warm cache hits included): an SRI re-hash, and — the larger half — a packument load+parse to bind the request to a version. The benchmark mock is pnpr and a cold-store install pulls ~1300 tarballs through it, so both are paid ~1300×. This PR serves cached bytes directly (no re-hash, no packument load) and defers the packument load to the cache-miss download path, which still verifies freshly-fetched bytes before caching them. Result on this PR's CI run (Bencher
|
| Scenario | this PR | main baseline |
direct pacquet |
|---|---|---|---|
| fresh-install · cold-cache · cold-store | 2,191 ms | 2,823 ms | 4,293 ms |
| fresh-restore · cold-cache · cold-store | 2,158 ms | ~2,820 ms | 3,894 ms |
Cold-store is back to the pre-#12570 baseline (~2,159 ms) — a ~630 ms / 23% improvement over current main, restoring the win to ~2× over a direct install. (Both pnpr@HEAD and pnpr@main read through this PR's fixed mock in a single run, so they read equal in the side-by-side table; the regression is only visible against the historical main baseline above, which ran against the re-hashing mock.)
Security
Write-time verification is fully preserved: the download path still resolves the version, requires a supported dist.integrity, rejects a poisoned upstream tarball, and never caches unverified bytes — so GHSA-5f9g-98vq-2jxw stays mitigated. This is strictly more protective than the pre-#12570 serve path, which did no write-time verification at all, while matching its performance. The install client also re-verifies every tarball it receives against its own expected integrity.
Written by an agent (Claude Code, claude-opus-4-8).
…from its own pnpr mock The integrated benchmark fronts all arms with a single registry-mock built from the PR's HEAD (`just integrated-benchmark` runs `cargo build --release --bin=pnpr`, and the mock resolves that one `target/release/pnpr`). Tarball serving — where the pnpr proxy spends its per-request time — happens in that shared mock, not in the per-revision `pnpr@<rev>` accelerators (which only offload resolution; clients fetch tarballs from the client registry). So a tarball-serve change slows or speeds every arm equally and cancels out of the `pnpr@HEAD` vs `pnpr@main` comparison — the blind spot that let the serve-path regression in #12570 merge looking flat and only step up on the `main` trend afterward. Give each compared revision its own tarball-serving mock, built from that revision's `pnpr`. `plan_revision_mocks` assigns a client-facing latency-proxy port to every revision that has a `pnpr@<rev>` target (so its binary is built) before `init()` bakes the port into each target's `.npmrc`; `benchmark()` then spawns the mock (after `build()` produced the binary, after `init()` warmed the shared storage) and fronts it with the same latency + bandwidth link the shared mock uses. `pacquet@<rev>` and `pnpr@<rev>` of the same revision share that revision's mock, so the pnpr-vs-direct ratio stays fair within a revision while the serve-path delta becomes visible across revisions. All revisions share the one warm runtime storage: serving a warm cache is read-only (a hit neither re-downloads nor, after #12709, writes a sidecar), so concurrent mocks don't contend. Non-Verdaccio modes plan no mock and keep the existing single-registry behavior. Also retry `rm -rf` on the transient "Directory not empty" macOS/APFS raises between a just-finished install's store writes settling and the next iteration's cleanup, so the benchmark can run locally on macOS at all.
PR Summary by Qodoperf(pnpr): skip packument+SRI work when serving cached proxy tarballs Description
Diagram
High-Level Assessment
Files changed (6)
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@pacquet/tasks/integrated-benchmark/src/work_env.rs`:
- Around line 842-847: The planned mock port in the RevisionMockRegistry setup
is selected too early and left unreserved until start_revision_mock() runs, so
another process can steal it before LatencyProxy::spawn_on binds. Update the
work_env.rs flow around mocks.entry(...), RevisionMockRegistry, and
start_revision_mock() to keep a listener/guard alive from the planning step
through init/build, or otherwise reserve the chosen port until the mock server
actually starts.
In `@pnpr/crates/pnpr/src/server.rs`:
- Around line 1200-1204: The cached tarball fast path in
server::tarball_response logic can bypass uplink priority because
should_read_cache is driven by any(|u| u.caches()), allowing a lower-priority
cacheable uplink to shadow an earlier non-caching uplink. Update the cache-read
decision to preserve the same provenance/order contract as the upstream fallback
loop, using the highest-priority applicable uplink or an explicit origin-aware
cache check before calling state.inner.storage.open_cached_tarball.
- Around line 1188-1204: The cached tarball fast path in server.rs bypasses
authoritative-version OSV screening by returning bytes from open_cached_tarball
after only checking the filename-derived version. Update the cache-hit path
around resolve_upstreams, should_read_cache, and tarball_response so the
resolved packument version is still validated before serving cached content,
either by storing lightweight resolved-version metadata with the cache entry or
by falling back to packument lookup when that metadata is absent. Keep the
existing OSV check on the canonical resolved version consistent with the miss
path, and add tests covering cached non-canonical or stale entries being
blocked.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: dd55b384-a282-4639-bbb2-b20dfaafd951
📒 Files selected for processing (6)
pacquet/tasks/integrated-benchmark/src/work_env.rspacquet/tasks/registry-mock/src/pnpr_command.rspnpr/crates/pnpr/src/server.rspnpr/crates/pnpr/src/storage.rspnpr/crates/pnpr/src/storage/tests.rspnpr/crates/pnpr/tests/server.rs
💤 Files with no reviewable changes (2)
- pnpr/crates/pnpr/src/storage.rs
- pnpr/crates/pnpr/src/storage/tests.rs
Code Review by Qodo
1.
|
|
Code review by qodo was updated up to the latest commit 095a408 |
Serving a cached tarball directly skipped the resolved-version OSV re-screen: the screen on the filename-carried version still ran, but a non-canonical tarball name (one whose declaring version differs from the version in its filename) could let a cached tarball for an OSV-blocked version slip past on an unblocked filename version — e.g. after an OSV database update that blocks the resolved version. When OSV screening is enabled, resolve the tarball's authoritative version from the packument and re-screen it before trusting cached bytes, exactly as the cache-miss path does. The work is shared through a new `resolve_tarball_dist_and_screen` helper and reused on a miss, so an OSV-enabled serve resolves the packument at most once. When OSV is disabled the resolution is pure overhead and stays skipped, so the warm-cache cold-store fast path is unaffected. Found by Qodo review on #12709.
|
Thanks — addressing both Qodo findings. 🐞 Bug 1 — OSV gate skipped on cache hit — fixed in da4d81bReal gap, good catch. The filename-version OSV screen still ran before the cache read, so canonical npm names (the norm) were covered, but a non-canonical tarball name — one whose declaring version differs from the version in its filename — could let a cached tarball for an OSV-blocked version slip past on an unblocked filename version (e.g. after an OSV DB update). Fix: when OSV screening is enabled, 🐞 Bug 2 — cached tarball served without re-verification — working as intendedThis is the deliberate core of the PR, not an oversight. Integrity is enforced at the two points that actually protect the client:
Re-hashing on every serve (what #12570 added) is exactly the per-tarball SRI pass this PR removes to fix the cold-store regression, so re-introducing it unconditionally would undo the fix. The residual risk is narrow — an entry written by a pre-write-verification pnpr, or one modified out-of-band on disk — and it fails loudly at the client rather than corrupting an install. A cheap self-heal (evict + refetch when a client reports an integrity failure, or a size-invariant check on hit) would be a reasonable follow-up to recover such entries automatically without paying a per-serve hash; I've left it out of this PR to keep the change focused. Happy to add it as a follow-up if you'd like. Written by an agent (Claude Code, claude-opus-4-8). |
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 `@pnpr/crates/pnpr/tests/server.rs`:
- Around line 1294-1316: The test currently does not prove the second request is
a cache hit because it can still return FORBIDDEN even if the warm-up never
wrote the tarball to disk. Update the regression in server.rs to explicitly
verify, after the initial warming_app request and before enabling OSV, that the
tarball exists in the proxy cache for the requested package/version, using the
same cache_dir and the cache/tarball handling path exercised by router and
config_for. Keep the final screened_app assertion, but make the test fail if
open_cached_tarball would not be hit on the second request.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 4fa2c717-b6b7-4d02-be5e-b81186b7730a
📒 Files selected for processing (2)
pnpr/crates/pnpr/src/server.rspnpr/crates/pnpr/tests/server.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- pnpr/crates/pnpr/src/server.rs
|
Code review by qodo was updated up to the latest commit da4d81b |
…t plan time The per-revision mock picked its client-facing proxy port during planning but did not bind it until `benchmark()` spawned the proxy — after `init()` and a multi-minute `build()`. Another process could claim the port in that window, failing the run before it measured anything. Bind the proxy's listening socket at plan time instead, reserving the port for the whole init + build window, and hand the live socket to the proxy via the new `LatencyProxy::spawn_with_listener`. Nothing connects to it before the benchmark starts, so holding it idle through the build is harmless. Found by CodeRabbit review on #12709.
|
Code review by qodo was updated up to the latest commit da4d81b |
…-hit screen The regression test for the resolved-version OSV screen on cache hits could pass even if the warm-up never wrote the tarball to disk: the OSV-enabled path resolves and screens the packument before reading the cache, so a request for a blocked resolved version is refused whether or not it is a genuine cache hit. Assert the tarball is in the proxy cache after warming so the screened request actually exercises the cache-hit path the fix guards. Found by CodeRabbit review on #12709.
|
Code review by qodo was updated up to the latest commit 2a0da90 |
1 similar comment
|
Code review by qodo was updated up to the latest commit 2a0da90 |
Two follow-ups to the macOS/APFS cleanup retry, from Qodo review on #12709: - `remove_dir_all_with_retry` retried every error kind, so a non-transient failure (permissions, I/O) slept ~4s before surfacing. Retry only the transient `DirectoryNotEmpty` that APFS raises while a store's writes settle, and fail fast otherwise. - `build_cleanup_command` repeated the full removal-target list three times in the hyperfine `--prepare` string (once per retry), bloating the command and risking the OS arg-length limit with many targets. List the targets once and retry per-path with a shell `for` loop (`|| exit 1` still aborts the iteration if a path can't be removed), and assemble the command from `&&`-joined parts so an empty removal set can't produce a dangling `&& cp`.
Looking for bugs?Check back in a few minutes. Qodo's review agents are on it. |
|
Thanks for the re-review on Fixed in 4fc41eb
By design (no change)
Acknowledged, not changing
Already resolved — OSV gate on cache hit ( Written by an agent (Claude Code, claude-opus-4-8). |
- Drop the refactor-history reference (what #12570 added and why the old serve path was slow) from the cache-hit comment in serve_tarball; keep the current invariant (write-time + client verification) that explains why a hit can serve directly. That history belongs in the commit log. - Put the "per-revision mock makes a serve-path delta visible (a shared mock cancels it out)" rationale in one place — `plan_revision_mocks` — and have `registry_for`, `benchmark()`, and `pnpr_command_with_binary` reference or state it briefly instead of re-deriving it. - Drop the "served unscreened before this fix" framing from the OSV cache-hit test comment; describe the current behavior instead.
The cache-hit block in serve_tarball spelled out the OSV non-canonical-version scenario that `osv_refuses_vulnerable_cached_tarball_under_noncanonical_name` already demonstrates. Tests document behavior; keep only the parts a reader can't recover from the code — why a hit is safe to serve unverified (write-time plus client verification) and why OSV still needs the packument — and let the test carry the rest.
|
Code review by qodo was updated up to the latest commit 50920c9 |
1 similar comment
Extract the "resolve and screen the version when OSV is enabled" gate from serve_tarball into `screen_cached_tarball_osv`, whose name and doc carry what an inline comment otherwise had to. The call site keeps only the irreducible why — that a cache hit is safe to serve without re-hashing (write-time plus client verification) — and the OSV behavior is documented once on the function.
|
Code review by qodo was updated up to the latest commit e4067a6 |
|
Code review by qodo was updated up to the latest commit b1240f9 |
1 similar comment
|
Code review by qodo was updated up to the latest commit b1240f9 |
Brings in the filter->recursive promotion (#12726), the `bin` (#12703) and `repo` (#12702) commands, and a pnpr cold-store perf fix (#12709). Resolved one conflict in cli_args/dispatch_query.rs: the publish and bin handlers and their imports were both added at the same spot, so both were kept. Adapted recursive publish to #12726's new shared-function signatures: `discover_workspace_projects` now returns `(projects, patterns)` and `select_recursive_projects` takes an `AutoExcludeRoot`. Publish passes `AutoExcludeRoot::Disabled` because it is not in pnpm's run/exec/add/test root-auto-exclusion set; its private/unnamed eligibility check drops the workspace root instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KtBQzmLLDU3RcGzzCMopPB
Summary
Restores pnpr's cold-store install performance, which regressed from
#12570 ("verify proxied tarball integrity"). Bencher's history for the
pnprtestbed shows both cold-store install scenarios stepping up at that commitfrom ~2.15s to ~3.15s (the single biggest jump, +666ms, is exactly #12570; later
auth commits added the rest). #12700 recovered the resolution-cache part
(the two hot-store scenarios and ~325ms of cold-store), leaving ~2.82s — still
well above the ~2.15s baseline.
The remaining cost is on the tarball serve path. The benchmark's
registry-mock is pnpr, and a cold-store install pulls ~1300 tarballs through
it, so anything #12570 added to each serve is paid ~1300 times. #12570 added two
things to every serve, including warm cache hits:
.tgzagainst the version'sdist.integrity.integrity in the first place — the larger half.
Both are redundant on a cache hit:
download_verified_to_cache,which resolves the version and verifies the bytes against
dist.integrityasthey are written. The cache only ever holds correct bytes for a given
(name, filename).expected integrity regardless.
So this serves cached bytes directly: no re-hash, and the packument load is
deferred to the cache-miss download path (which still needs the integrity to
verify freshly-fetched bytes before caching them). The OSV screen on the
filename's version still runs before the cache read, exactly as the pre-#12570
path did.
Security: write-time verification — resolving the version, requiring a
supported
dist.integrity, rejecting a poisoned upstream tarball, and nevercaching unverified bytes — is fully preserved, so GHSA-5f9g-98vq-2jxw stays
mitigated. This is actually more protective than the pre-#12570 serve path,
which did no write-time verification at all, while matching its performance. The
namespaced
/~<uplink>/route keeps its own sidecar-keyed integrity cache and isunchanged.
This is a pnpr-only (Rust registry server) change — no TypeScript or pacquet CLI
surface, so nothing to mirror and no changeset (pnpr is not a published npm
package).
Squash Commit Body
Checklist
pacquet/port, or the description notes what still needs porting. (pnpr-only; no CLI surface to mirror.)Written by an agent (Claude Code, claude-opus-4-8).
Summary by CodeRabbit