Skip to content

feat(pnpr): forward credentials and add per-user access grants for external private registries (#12184)#12189

Merged
zkochan merged 8 commits into
mainfrom
feat/12184
Jun 4, 2026
Merged

feat(pnpr): forward credentials and add per-user access grants for external private registries (#12184)#12189
zkochan merged 8 commits into
mainfrom
feat/12184

Conversation

@zkochan

@zkochan zkochan commented Jun 4, 2026

Copy link
Copy Markdown
Member

Closes #12184 (part 2).

#12181 shipped the per-caller access gate on POST /v1/install, which authorizes every served package against pnpr's own packages: policy — the complete answer while pnpr fetches anonymously. This PR adds the remaining piece: forwarding the caller's per-registry credentials so the accelerator can resolve/fetch external private content as the caller, and gating that content per user against the registry that actually owns it.

Credential forwarding (issue steps 1–2)

  • Wire: POST /v1/install gains an authHeaders body map ({ "//host/path/": "Bearer …" }, the shape AuthHeaders::from_map consumes / getAuthHeadersFromCreds produces) plus an HTTP Authorization header. The body map carries the upstream registry tokens; the header identifies the caller to pnpr's own gate and keys the grant table.
  • pacquet plumbing: a request-scoped Arc<AuthHeaders> is threaded via a new Install.auth_override field and an auth_override param on build_resolution_verifiers, so resolution/verification run as the caller without baking per-user auth into the interned &'static Config (which would leak one config per user).
  • Server: handle_install builds the per-request AuthHeaders and threads it through resolve, verify, and fetch_uncached (which now returns the freshly-fetched set).
  • Clients: pacquet pnpr-client and @pnpm/pnpr.client send registry / namedRegistries / authHeaders + Authorization; the TS path sources them from the caller's registry credentials via @pnpm/network.auth-header (getAuthHeadersFromCreds is newly re-exported). @pnpm/worker is unchanged — downloads happen server-side.
  • Credential scope: both clients forward the caller's full credential map, not a subset scoped to the declared registries. The registries a dependency graph touches aren't knowable up front — a transitive package can be scope-routed to another registry or pinned to a tarball URL on a host that's in .npmrc but isn't a declared registry — so pnpr attaches the right token per fetched URL exactly as a local install does. These are package-fetch credentials going to the very service the caller configured to fetch its packages.

Per-user grant table (issue steps 3–4)

Externally-resolved private content carries no pnpr policy, so the store's possession of the bytes must not authorize a user the upstream never cleared. A served package is dispatched by whether a forwarded credential was used to fetch it:

  • No forwarded cred → pnpr-as-authority: the existing local packages: policy check, unchanged.
  • Forwarded cred → upstream-as-authority: gated against a persistent (user, name@version) grant table (SQLite, modeled on VerdictCache). Freshly fetched this request ⇒ record + allow (the upstream just accepted the token). Cache hit with a standing grant ⇒ allow, no upstream trip. Cache hit, no grant ⇒ re-verify against the owning registry with the caller's credential — record on success; clear-on-discovery (purge the user's grants for the package) + deny on 401/403. TTL is the installAccelerator.grantTtl config knob (default: permanent).

Public vs private (no per-user gating for public packages)

A forwarded credential matching a registry doesn't mean a package is private — in a mixed proxy (one registry serving a company's private packages and public ones), the token matches everything, and gating public content per user would cost a grant row and a re-verify round trip per user for bytes anyone may read. So before the per-user path, a not-yet-classified cache hit is probed anonymously: a 2xx classifies the package public in a global set (no user pays for it again, no grant, no further round trip); a 401/403 means it's genuinely private and falls through to the grant / re-verify path above. Public packages thus cost one anonymous probe across the whole fleet, not one per user.

Tests

  • pnpr: grant-table + public-set mechanics, regime dispatch, the upstream-authorization paths (fresh-fetch, granted cache hit, private re-verify-and-record, denied-clears-grants, public-classified-once-then-free), and forwarded-cred-routes-around-local-policy.
  • pacquet pnpr-client: a test asserting authHeaders + Authorization travel on the wire.
  • Full suites green: pnpr (237), pacquet-package-manager (389), pacquet-pnpr-client (12), pacquet-network/config (325); clippy -D warnings, cargo fmt, rustdoc -D warnings --document-private-items, typos, and the TS compile all clean.

Scoped follow-ups (not in this PR)

  • Clear-on-discovery fires at the re-verify hook only. A 401/403 during the cold resolve aborts the request anyway (nothing is served); threading the offending package out of the deep resolve error to also clear stale grants for future requests needs structured auth errors.
  • Per-scope external registries route via the default registry, since pacquet doesn't yet surface @scope:registry routing in collect_packages.

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

Summary by CodeRabbit

  • New Features

    • pnpr install accelerator forwards caller per-registry credentials (Authorization header + authHeaders map) for resolution/fetching; per-user grants and global public-package classification gate access; configurable grant TTL controls re-verification timing
  • Tests

    • Broad unit and integration tests covering credential forwarding, grant recording/expiry, public-package classification, re-verification, and access-denial scenarios
  • Documentation

    • Changeset updated to document credential-forwarding and access-control behavior

…private registries (#12184)

The install accelerator now forwards the caller's per-registry credentials
on `POST /v1/install`, so it can resolve, verify, and fetch private
dependencies from external registries as the caller instead of reaching the
registry anonymously.

- Wire: new `authHeaders` body map (nerf-darted URI -> header) plus an
  `Authorization` request header identifying the caller to pnpr's gate.
- pacquet: thread a request-scoped `AuthHeaders` via `Install.auth_override`
  and a new `build_resolution_verifiers` param, instead of baking per-user
  auth into the interned `&'static Config`.
- Server: externally-resolved private content carries no pnpr policy, so a
  served package whose fetch used a forwarded credential is gated per user
  against the owning registry via a persistent `(user, name@version)` grant
  table — serving a cache hit only to a user the registry has cleared,
  re-verifying first-time versions, and clearing grants on a 401/403.
- Clients: pacquet `pnpr-client` and `@pnpm/pnpr.client` send
  registry/namedRegistries/authHeaders + Authorization; the TS path sources
  them from the caller's registry credentials via `@pnpm/network.auth-header`.

Co-authored-by: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Threads client per-registry Authorization headers into pnpr POST /v1/install, passes them through pacquet resolution/fetch as an auth_override, and enforces served-package access via per-user grant and global public-package SQLite caches.

Changes

Forwarded Credential Install Flow

Layer / File(s) Summary
All changed checkpoints (single review narrative)
*
Client and CLI forward per-registry authHeaders and optional pnpr Authorization; InstallOptions and /v1/install payload/header include forwarded credentials; pacquet gains per-invocation auth_override threading into verification/resolution/fetch; pnpr adds grant TTL config, SQLite-backed GrantTable and PublicPackages caches, upstream authorization probes and grant recording/clearing, and related tests; TypeScript client/server wiring and TS package references updated.

Sequence Diagram (high-level flow)

sequenceDiagram
  participant CLI as PNPM CLI
  participant PNPR as pnpr InstallAccelerator
  participant PACQUET as pacquet resolver/fetch
  participant REG as Upstream Registry
  participant DB as pnpr SQLite stores
  CLI->>PNPR: POST /v1/install (Authorization, authHeaders, registry...)
  PNPR->>PACQUET: verify_input_lockfile + resolve (auth_override=request_auth)
  PACQUET->>REG: fetch packument/tarball (with forwarded authHeaders)
  REG-->>PACQUET: 200 or 401/403
  PACQUET-->>PNPR: fetched tarballs (list of pkg_ids)
  PNPR->>DB: record grants for freshly fetched pkg_ids / check public_packages
  PNPR-->>CLI: gzipped CAFS payload (after authorize_served_packages)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • pnpm/pnpm#12181: Prior pnpr per-package local policy authorization; this PR extends that work with forwarded credentials and upstream grants.
  • pnpm/pnpm#12077: Earlier pnpr install-accelerator client/server changes extended here with per-registry credential forwarding.
  • pnpm/pnpm#11953: Related auth-header API/behavior changes in the network auth-header package referenced by the TypeScript installer.

Poem

"🐰 I hop with headers in my paw,
forwarding tokens near and far.
Grants remembered, public names told—
cached bytes won't grant control.
Hooray, installs proceed secure and bold!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: forwarding credentials and adding per-user access grants for external private registries via pnpr.
Linked Issues check ✅ Passed The PR implements all primary coding objectives from #12184: credential forwarding via authHeaders and Authorization, per-request auth threading via auth_override, persistent per-user grant tables, public-package classification, and client updates.
Out of Scope Changes check ✅ Passed All changes align with #12184 objectives. Additions to auth handling, grant tables, public-package tracking, and client credential forwarding are all within scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/12184

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.01      5.0±0.19ms   866.3 KB/sec    1.00      4.9±0.13ms   877.4 KB/sec

`cargo doc --document-private-items` (the CI Doc job) rejected two stale
`deny_unauthorized_packages` intra-doc links left over from splitting the
gate into `authorize_served_packages` (local policy + per-user grants).
@codecov-commenter

codecov-commenter commented Jun 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.25784% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.69%. Comparing base (3b76b8e) to head (d36465a).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
pnpr/crates/pnpr/src/install_accelerator.rs 97.63% 3 Missing ⚠️
...crates/pnpr/src/install_accelerator/grant_table.rs 98.41% 1 Missing ⚠️
...es/pnpr/src/install_accelerator/public_packages.rs 98.07% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12189      +/-   ##
==========================================
+ Coverage   87.56%   87.69%   +0.12%     
==========================================
  Files         269      271       +2     
  Lines       30817    31082     +265     
==========================================
+ Hits        26984    27256     +272     
+ Misses       3833     3826       -7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Each scenario has pacquet rows (direct install) and pnpr rows (the same client through the pnpr install accelerator), so pnpr@HEAD vs pacquet@HEAD is the pnpr-vs-direct ratio. Cold-store scenarios wipe the client store between runs (warm server); hot-store scenarios keep it warm. The pacquet@HEAD rows feed the pacquet Bencher testbed; the pnpr@HEAD rows feed the pnpr testbed.

Scenario: Isolated linker: fresh restore, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 4.821 ± 0.083 4.737 4.996 2.41 ± 0.06
pacquet@main 4.793 ± 0.028 4.748 4.837 2.40 ± 0.04
pnpr@HEAD 1.999 ± 0.036 1.944 2.054 1.00
pnpr@main 2.028 ± 0.072 1.945 2.138 1.01 ± 0.04
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.820990617579999,
      "stddev": 0.08265049140469188,
      "median": 4.803906982979999,
      "user": 2.51170556,
      "system": 3.7541995200000002,
      "min": 4.73748448648,
      "max": 4.99584779748,
      "times": [
        4.77224883448,
        4.91101083848,
        4.99584779748,
        4.84330121848,
        4.78843459048,
        4.75359360248,
        4.73987328948,
        4.81937937548,
        4.73748448648,
        4.848732142479999
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.793340935180001,
      "stddev": 0.028181553179487007,
      "median": 4.79193752298,
      "user": 2.53088686,
      "system": 3.7133079200000005,
      "min": 4.74828113548,
      "max": 4.83664889348,
      "times": [
        4.823453993479999,
        4.74828113548,
        4.79087325948,
        4.77611151048,
        4.81815983348,
        4.79300178648,
        4.7701744024799995,
        4.76798634948,
        4.80871818748,
        4.83664889348
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.9991289711800004,
      "stddev": 0.03562251602343498,
      "median": 2.00513712198,
      "user": 2.69144106,
      "system": 3.24936742,
      "min": 1.94425077548,
      "max": 2.05385932448,
      "times": [
        2.05385932448,
        1.9707298214800002,
        2.01027383848,
        1.9906859814800002,
        1.94988298948,
        1.94425077548,
        2.0297310714799996,
        2.00000040548,
        2.02793705748,
        2.0139384464799996
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.02791215218,
      "stddev": 0.07161453772074151,
      "median": 2.0179602914799997,
      "user": 2.70730616,
      "system": 3.2282206200000005,
      "min": 1.94451417748,
      "max": 2.13784160648,
      "times": [
        2.13784160648,
        1.97012226148,
        1.94895474548,
        2.00108842548,
        1.94451417748,
        2.0798491864799997,
        1.9745568344800002,
        2.03483215748,
        2.05775486248,
        2.1296072644799997
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 693.0 ± 46.0 672.4 823.5 1.01 ± 0.07
pacquet@main 716.6 ± 62.0 660.2 865.8 1.04 ± 0.10
pnpr@HEAD 689.4 ± 20.6 666.8 724.3 1.00
pnpr@main 731.0 ± 66.5 653.0 903.9 1.06 ± 0.10
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6929825153000001,
      "stddev": 0.04601094449235311,
      "median": 0.6794343995000001,
      "user": 0.3903781,
      "system": 1.32824612,
      "min": 0.6724159545,
      "max": 0.8234690485000001,
      "times": [
        0.8234690485000001,
        0.6852533665,
        0.6814774025000001,
        0.6794441975000001,
        0.6794246015000001,
        0.6724159545,
        0.6738170625000001,
        0.6748073845000001,
        0.6808534795000001,
        0.6788626555
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.7166063859,
      "stddev": 0.06200944835175567,
      "median": 0.697682723,
      "user": 0.3792539,
      "system": 1.34374082,
      "min": 0.6602254785,
      "max": 0.8658042355000001,
      "times": [
        0.7385158065,
        0.6712955755000001,
        0.6674156475,
        0.6844328695,
        0.6784322555000001,
        0.7363286755,
        0.7526807385000001,
        0.7109325765000001,
        0.6602254785,
        0.8658042355000001
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.6894460507000001,
      "stddev": 0.020575066904687453,
      "median": 0.6815862735000001,
      "user": 0.38863229999999993,
      "system": 1.34321032,
      "min": 0.6668454735000001,
      "max": 0.7242842885,
      "times": [
        0.7047637845000001,
        0.6744705805000001,
        0.6803398035000001,
        0.6736636745000001,
        0.6802297235000001,
        0.6828327435000001,
        0.6838931985000001,
        0.6668454735000001,
        0.7231372365000001,
        0.7242842885
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.7310278517000001,
      "stddev": 0.06645851991200141,
      "median": 0.726269542,
      "user": 0.3662406,
      "system": 1.34675842,
      "min": 0.6529946385000001,
      "max": 0.9038890695,
      "times": [
        0.9038890695,
        0.7200717305000001,
        0.7351305815000001,
        0.7272589225000001,
        0.6892658645,
        0.7252801615000001,
        0.6529946385000001,
        0.7340066575,
        0.6882085085,
        0.7341723825
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.212 ± 0.029 2.168 2.251 1.01 ± 0.03
pacquet@main 2.188 ± 0.051 2.126 2.296 1.00
pnpr@HEAD 2.214 ± 0.026 2.177 2.255 1.01 ± 0.03
pnpr@main 2.197 ± 0.052 2.131 2.309 1.00 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.21164821824,
      "stddev": 0.02858495516211038,
      "median": 2.20486558304,
      "user": 3.5550511799999995,
      "system": 2.9991082399999995,
      "min": 2.16769294354,
      "max": 2.2509739005400005,
      "times": [
        2.1814005255400004,
        2.19749052554,
        2.20103195254,
        2.2509739005400005,
        2.16769294354,
        2.20869921354,
        2.1998192045400002,
        2.2156578925400003,
        2.2456141515400003,
        2.2481018725400004
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.18797088474,
      "stddev": 0.050886605050339366,
      "median": 2.1753361250400003,
      "user": 3.5570728800000007,
      "system": 2.9803911399999996,
      "min": 2.1264871865400004,
      "max": 2.2955580245400005,
      "times": [
        2.16739256054,
        2.18952212854,
        2.13956817154,
        2.15346284254,
        2.1667703615400002,
        2.2955580245400005,
        2.21936737554,
        2.2383005065400003,
        2.1832796895400004,
        2.1264871865400004
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 2.21438627924,
      "stddev": 0.02599883901480438,
      "median": 2.2168910700400004,
      "user": 3.5661224799999993,
      "system": 2.99338464,
      "min": 2.17672893754,
      "max": 2.2553994575400003,
      "times": [
        2.24013638554,
        2.2553994575400003,
        2.23201905754,
        2.22939759054,
        2.1859242895400004,
        2.2125852065400005,
        2.2211969335400004,
        2.17672893754,
        2.18546734854,
        2.20500758554
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.19660841964,
      "stddev": 0.05181262475134038,
      "median": 2.1915762780400003,
      "user": 3.5452370799999997,
      "system": 3.0071622399999995,
      "min": 2.13073696054,
      "max": 2.30917880454,
      "times": [
        2.20291028454,
        2.2007752775400005,
        2.15783496354,
        2.25276385554,
        2.1723581695400003,
        2.30917880454,
        2.13073696054,
        2.20087725054,
        2.15627135154,
        2.18237727854
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.337 ± 0.012 1.316 1.356 1.00
pacquet@main 1.363 ± 0.026 1.331 1.408 1.02 ± 0.02
pnpr@HEAD 1.365 ± 0.038 1.328 1.458 1.02 ± 0.03
pnpr@main 1.353 ± 0.045 1.314 1.475 1.01 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.3368636365400002,
      "stddev": 0.011853297773738409,
      "median": 1.33495645084,
      "user": 1.4606222800000002,
      "system": 1.73028008,
      "min": 1.31563712634,
      "max": 1.3562401583400001,
      "times": [
        1.3352535383400002,
        1.31563712634,
        1.3346593633400001,
        1.3562401583400001,
        1.32535927534,
        1.34639021334,
        1.3428997843400001,
        1.33423213634,
        1.3478027133400001,
        1.33016205634
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.3633129912400002,
      "stddev": 0.025550819514170812,
      "median": 1.36900245634,
      "user": 1.44642148,
      "system": 1.76424708,
      "min": 1.3313702203400002,
      "max": 1.40783854834,
      "times": [
        1.3440659723400001,
        1.33650653034,
        1.38963717534,
        1.3758103463400002,
        1.3693501543400002,
        1.3313702203400002,
        1.40783854834,
        1.3686547583400002,
        1.37404821334,
        1.33584799334
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.36471296634,
      "stddev": 0.037547941990714144,
      "median": 1.36277593434,
      "user": 1.46041278,
      "system": 1.7483583799999998,
      "min": 1.32809285934,
      "max": 1.45828801434,
      "times": [
        1.32809285934,
        1.3763590463400002,
        1.37316000634,
        1.37194254034,
        1.45828801434,
        1.3475633253400001,
        1.3631948773400002,
        1.36235699134,
        1.3369307413400002,
        1.32924126134
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 1.35262440734,
      "stddev": 0.044901892089720244,
      "median": 1.3427206953400002,
      "user": 1.4305844799999998,
      "system": 1.7467543799999998,
      "min": 1.31399140734,
      "max": 1.4746106943400001,
      "times": [
        1.34238672434,
        1.31399140734,
        1.3365595133400001,
        1.34305466634,
        1.4746106943400001,
        1.3395248133400002,
        1.34799862534,
        1.36114757534,
        1.3208416093400002,
        1.3461284443400001
      ]
    }
  ]
}

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12189
Testbedpacquet

🚨 1 Alert

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
isolated-linker.fresh-restore.cold-cache.cold-storeLatency
seconds (s)
📈 plot
🚷 threshold
🚨 alert (🔔)
4.82 s
(+84.90%)Baseline: 2.61 s
3.13 s
(154.08%)

Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
2,211.65 ms
(-3.58%)Baseline: 2,293.70 ms
2,752.45 ms
(80.35%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,336.86 ms
(-8.01%)Baseline: 1,453.34 ms
1,744.00 ms
(76.65%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
🚨 view alert (🔔)
4,820.99 ms
(+84.90%)Baseline: 2,607.40 ms
3,128.87 ms
(154.08%)

isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
692.98 ms
(+3.99%)Baseline: 666.42 ms
799.70 ms
(86.66%)
🐰 View full continuous benchmarking report in Bencher

A forwarded credential matching a package's registry only means pnpr
fetched it with the caller's token, not that the package is private. When
one registry serves both private and public packages (the mixed-proxy
deployment), the token matches every package, so the grant table would
gate public content per user — a grant row and an upstream re-verify each.

Classify packages globally instead: on a cache hit for a not-yet-classified
package, probe the registry anonymously. A 2xx means it is public — record
it in a global set so every later request (any user) short-circuits with no
grant row and no round trip; a 401/403 means it is private and falls through
to the existing per-user grant / re-verify path.
@zkochan zkochan marked this pull request as ready for review June 4, 2026 13:55
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Review Summary by Qodo

Forward credentials and add per-user access grants for external private registries

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Forward caller's per-registry credentials to pnpr server for private package resolution
• Add per-user access grants table for externally-resolved private content
• Implement global public-package classification to avoid per-user gating overhead
• Gate served packages by regime: pnpr-as-authority (local policy) or upstream-as-authority
  (per-user grants)
Diagram
flowchart LR
  Client["Client<br/>(pnpm/pacquet)"]
  PnprServer["Pnpr Server<br/>(install accelerator)"]
  ExtRegistry["External<br/>Registry"]
  GrantTable["Grant Table<br/>(SQLite)"]
  PublicSet["Public Set<br/>(SQLite)"]
  
  Client -->|authHeaders + Authorization| PnprServer
  PnprServer -->|resolve/fetch with<br/>forwarded creds| ExtRegistry
  PnprServer -->|record/check<br/>per-user grants| GrantTable
  PnprServer -->|classify<br/>public packages| PublicSet
  ExtRegistry -->|401/403| PnprServer

Loading

Grey Divider

File Changes

1. pnpr/crates/pnpr/src/install_accelerator.rs ✨ Enhancement +284/-37

Thread auth headers through resolution and add authorization dispatch

pnpr/crates/pnpr/src/install_accelerator.rs


2. pnpr/crates/pnpr/src/install_accelerator/grant_table.rs ✨ Enhancement +128/-0

New SQLite-backed per-user access grants table

pnpr/crates/pnpr/src/install_accelerator/grant_table.rs


3. pnpr/crates/pnpr/src/install_accelerator/public_packages.rs ✨ Enhancement +111/-0

New SQLite-backed global public package classification

pnpr/crates/pnpr/src/install_accelerator/public_packages.rs


View more (25)
4. pnpr/crates/pnpr/src/install_accelerator/resolve.rs ✨ Enhancement +19/-5

Thread auth headers through resolve and fetch, return freshly-fetched set

pnpr/crates/pnpr/src/install_accelerator/resolve.rs


5. pnpr/crates/pnpr/src/install_accelerator/protocol.rs ✨ Enhancement +13/-0

Add authHeaders field to InstallRequest for forwarded credentials

pnpr/crates/pnpr/src/install_accelerator/protocol.rs


6. pnpr/crates/pnpr/src/install_accelerator/tests.rs 🧪 Tests +274/-16

Add comprehensive tests for grant table and upstream authorization

pnpr/crates/pnpr/src/install_accelerator/tests.rs


7. pnpr/crates/pnpr/src/install_accelerator/grant_table/tests.rs 🧪 Tests +74/-0

Unit tests for grant table record, clear, and TTL expiration

pnpr/crates/pnpr/src/install_accelerator/grant_table/tests.rs


8. pnpr/crates/pnpr/src/install_accelerator/public_packages/tests.rs 🧪 Tests +42/-0

Unit tests for public package classification and TTL

pnpr/crates/pnpr/src/install_accelerator/public_packages/tests.rs


9. pnpr/crates/pnpr/src/config.rs ✨ Enhancement +26/-0

Add configurable grant TTL from YAML installAccelerator block

pnpr/crates/pnpr/src/config.rs


10. pacquet/crates/package-manager/src/install.rs ✨ Enhancement +14/-1

Add auth_override field for per-request credential forwarding

pacquet/crates/package-manager/src/install.rs


11. pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs ✨ Enhancement +14/-4

Thread auth_override through resolution and verification

pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs


12. pacquet/crates/package-manager/src/build_resolution_verifiers.rs ✨ Enhancement +3/-2

Accept auth_override parameter for request-scoped credentials

pacquet/crates/package-manager/src/build_resolution_verifiers.rs


13. pacquet/crates/package-manager/src/install/tests.rs 🧪 Tests +62/-0

Update all test cases to include auth_override field

pacquet/crates/package-manager/src/install/tests.rs


14. pacquet/crates/package-manager/src/remove.rs ✨ Enhancement +1/-0

Initialize auth_override to None in Remove operation

pacquet/crates/package-manager/src/remove.rs


15. pacquet/crates/package-manager/src/update.rs ✨ Enhancement +1/-0

Initialize auth_override to None in Update operation

pacquet/crates/package-manager/src/update.rs


16. pacquet/crates/package-manager/src/add.rs ✨ Enhancement +1/-0

Initialize auth_override to None in Add operation

pacquet/crates/package-manager/src/add.rs


17. pacquet/crates/pnpr-client/src/lib.rs ✨ Enhancement +18/-2

Add authHeaders and authorization fields to InstallOptions

pacquet/crates/pnpr-client/src/lib.rs


18. pacquet/crates/pnpr-client/tests/integration.rs 🧪 Tests +36/-0

Add test asserting credentials travel on wire to server

pacquet/crates/pnpr-client/tests/integration.rs


19. pacquet/crates/cli/src/cli_args/install.rs ✨ Enhancement +12/-0

Forward auth headers and authorization to pnpr server

pacquet/crates/cli/src/cli_args/install.rs


20. pacquet/crates/network/src/auth.rs ✨ Enhancement +9/-0

Export entries method to retrieve all credential pairs

pacquet/crates/network/src/auth.rs


21. pacquet/crates/resolving-npm-resolver/src/lib.rs ✨ Enhancement +1/-0

Export to_registry_url utility for URL construction

pacquet/crates/resolving-npm-resolver/src/lib.rs


22. pnpr/client/src/fetchFromPnpmRegistry.ts ✨ Enhancement +39/-7

Add authHeaders and authorization to request body and headers

pnpr/client/src/fetchFromPnpmRegistry.ts


23. installing/deps-installer/src/install/index.ts ✨ Enhancement +13/-0

Build and forward auth headers and authorization to pnpr

installing/deps-installer/src/install/index.ts


24. network/auth-header/src/index.ts ✨ Enhancement +5/-0

Re-export getAuthHeadersFromCreds for credential forwarding

network/auth-header/src/index.ts


25. installing/deps-installer/package.json Dependencies +1/-0

Add dependency on @pnpm/network.auth-header

installing/deps-installer/package.json


26. .changeset/pnpr-forward-credentials.md 📝 Documentation +8/-0

Document credential forwarding and per-user grant feature

.changeset/pnpr-forward-credentials.md


27. pnpm-lock.yaml Dependencies +3/-0

Update lock file with new dependency reference

pnpm-lock.yaml


28. installing/deps-installer/tsconfig.json Additional files +3/-0

...

installing/deps-installer/tsconfig.json


Grey Divider

Qodo Logo

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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/crates/cli/src/cli_args/install.rs`:
- Around line 472-481: The code is sending all entries from
state.config.auth_headers to the pnpr server; restrict auth_headers to only the
entries relevant to the selected registry(s) before serializing: build a set of
target URLs/hosts from the chosen registry and any named_registries used for
this install, then replace the auth_headers mapping construction in the install
payload to filter state.config.auth_headers.entries() to only those whose URL
matches one of the target registry URLs/hosts (and omit the pnpr_server entry
since authorization already sends that token via authorization =
state.config.auth_headers.for_url(pnpr_server)); update the auth_headers field
(and keep authorization as-is) so only relevant credentials are forwarded to
/v1/install.

In `@pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs`:
- Around line 435-439: The code computes auth_headers from auth_override but
only passes it to resolvers, leaving fetch paths (PrefetchingResolver,
CreateVirtualStore and any fetch context construction) using config.auth_headers
and thus missing request-scoped credentials; modify the code that builds fetch
contexts and constructs PrefetchingResolver/CreateVirtualStore (and any
functions that build a FetchContext or FetchOptions around lines where
PrefetchingResolver and CreateVirtualStore are created) to accept the
auth_override-derived headers (auth_headers) and thread that Arc<Headers> into
their fetch contexts so tarball downloads use the same request-scoped auth as
the resolver; ensure the same change is applied to the other occurrences noted
(around the other blocks referenced at lines ~686-696 and ~1250-1265).
🪄 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: 4b8ad711-1d54-4260-92ba-72795edaad5c

📥 Commits

Reviewing files that changed from the base of the PR and between 3b76b8e and a6c59fc.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • .changeset/pnpr-forward-credentials.md
  • installing/deps-installer/package.json
  • installing/deps-installer/src/install/index.ts
  • installing/deps-installer/tsconfig.json
  • network/auth-header/src/index.ts
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/network/src/auth.rs
  • pacquet/crates/package-manager/src/add.rs
  • pacquet/crates/package-manager/src/build_resolution_verifiers.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/package-manager/src/remove.rs
  • pacquet/crates/package-manager/src/update.rs
  • pacquet/crates/pnpr-client/src/lib.rs
  • pacquet/crates/pnpr-client/tests/integration.rs
  • pacquet/crates/resolving-npm-resolver/src/lib.rs
  • pnpr/client/src/fetchFromPnpmRegistry.ts
  • pnpr/crates/pnpr/src/config.rs
  • pnpr/crates/pnpr/src/install_accelerator.rs
  • pnpr/crates/pnpr/src/install_accelerator/grant_table.rs
  • pnpr/crates/pnpr/src/install_accelerator/grant_table/tests.rs
  • pnpr/crates/pnpr/src/install_accelerator/protocol.rs
  • pnpr/crates/pnpr/src/install_accelerator/public_packages.rs
  • pnpr/crates/pnpr/src/install_accelerator/public_packages/tests.rs
  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
  • pnpr/crates/pnpr/src/install_accelerator/tests.rs

Comment thread pacquet/crates/cli/src/cli_args/install.rs Outdated
Comment thread pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs Outdated
… uses

Addresses a review comment: both clients forwarded the caller's entire
credential store to the pnpr server, so a token for a host unrelated to
the install leaked on every request. Scope the forwarded `authHeaders` to
the registries this install resolves against — the default registry plus
every `namedRegistries` alias — keyed by their nerf-darted URIs. This also
drops the pnpr-server's own credential from the body (it already travels
in the request `Authorization` header).

Reverts the now-unused `getAuthHeadersFromCreds` re-export from
`@pnpm/network.auth-header`; the TS path builds the scoped map with the
already-exported `createGetAuthHeaderByURI` plus `@pnpm/config.nerf-dart`.
zkochan added 2 commits June 4, 2026 16:52
Reverts the registry-scoped credential forwarding: the set of registries a
dependency graph touches isn't knowable up front. A transitive package can
be scope-routed to a different registry, or pinned to a tarball URL on a
host that lives in `.npmrc` but isn't a declared registry, so filtering to
the default + named registries silently dropped tokens that private
sub-dependencies need (a 401 on the server). Forwarding the whole map lets
pnpr attach the right token per fetched URL exactly as a local install
does — these are package-fetch credentials going to the service the caller
configured to fetch its packages. Comments at both call sites record why.
Trim the doc and inline comments introduced in this PR to a few lines
each, leaning on the descriptively-named tests for the per-path detail.
No behavior change.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pnpr/crates/pnpr/src/install_accelerator/public_packages.rs (1)

22-24: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Key this cache by registry as well as package name.

authorize_upstream_package() classifies anonymous access against a specific registry, but PublicPackages persists only name. Once foo is recorded as public from registry A, a later cache hit for private foo from registry B will skip both the grant check and the authenticated upstream probe because is_public(name, ...) returns true. That makes cross-registry name collisions an authorization bypass.

Suggested direction
 CREATE TABLE IF NOT EXISTS public_packages (
-    name             TEXT PRIMARY KEY,
+    registry         TEXT NOT NULL,
+    name             TEXT NOT NULL,
     classified_at_ms INTEGER NOT NULL
+    PRIMARY KEY (registry, name)
 );

Then thread the normalized registry URI through is_public() / record() and update the call sites in pnpr/crates/pnpr/src/install_accelerator.rs to query and record by (registry, name), not just name.

Also applies to: 42-44, 63-69

🤖 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 `@pnpr/crates/pnpr/src/install_accelerator/public_packages.rs` around lines 22
- 24, PublicPackages currently keys only by package name, causing cross-registry
collisions; change its API and storage to key by (registry, name): update the
PublicPackages struct and its underlying Connection usage to store and query a
composite key (normalized registry URI + package name), then modify the methods
is_public(name) and record(name) to accept a registry parameter (e.g.,
is_public(registry, name) and record(registry, name)) and use the normalized
registry URI when forming the key; finally, update all call sites (notably
authorize_upstream_package and the callers in
pnpr/crates/pnpr/src/install_accelerator.rs) to pass the normalized registry URI
along with the package name when querying and recording public packages.
🤖 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.

Outside diff comments:
In `@pnpr/crates/pnpr/src/install_accelerator/public_packages.rs`:
- Around line 22-24: PublicPackages currently keys only by package name, causing
cross-registry collisions; change its API and storage to key by (registry,
name): update the PublicPackages struct and its underlying Connection usage to
store and query a composite key (normalized registry URI + package name), then
modify the methods is_public(name) and record(name) to accept a registry
parameter (e.g., is_public(registry, name) and record(registry, name)) and use
the normalized registry URI when forming the key; finally, update all call sites
(notably authorize_upstream_package and the callers in
pnpr/crates/pnpr/src/install_accelerator.rs) to pass the normalized registry URI
along with the package name when querying and recording public packages.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 583dbd09-09aa-42cc-86d4-546f8b6fcf1e

📥 Commits

Reviewing files that changed from the base of the PR and between bd03511 and 39d41a1.

📒 Files selected for processing (13)
  • installing/deps-installer/src/install/index.ts
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/network/src/auth.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/pnpr-client/src/lib.rs
  • pnpr/client/src/fetchFromPnpmRegistry.ts
  • pnpr/crates/pnpr/src/config.rs
  • pnpr/crates/pnpr/src/install_accelerator.rs
  • pnpr/crates/pnpr/src/install_accelerator/grant_table.rs
  • pnpr/crates/pnpr/src/install_accelerator/protocol.rs
  • pnpr/crates/pnpr/src/install_accelerator/public_packages.rs
  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
🚧 Files skipped from review as they are similar to previous changes (10)
  • pnpr/crates/pnpr/src/install_accelerator/protocol.rs
  • pnpr/client/src/fetchFromPnpmRegistry.ts
  • pacquet/crates/network/src/auth.rs
  • pnpr/crates/pnpr/src/config.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pnpr/crates/pnpr/src/install_accelerator/grant_table.rs
  • pnpr/crates/pnpr/src/install_accelerator.rs
  • pacquet/crates/pnpr-client/src/lib.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). (7)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Dylint
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Code Coverage
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Follow Standard Style with trailing commas, preferring functions over classes, and declaring functions after they are used (relying on hoisting)
Use a single options object instead of multiple parameters when a function needs more than two or three arguments
Follow Import Order: Standard libraries first, then external dependencies (alphabetically), then relative imports
Write self-documenting code where function names, parameters, and types explain what a function does without requiring prose comments
Do not write comments that restate what the code already says; refactor via renaming, splitting helpers, or restructuring instead
Do not repeat documentation at call sites that already exists in JSDoc on the callee; update JSDoc once for all call sites to benefit
Use JSDoc only for a function's contract (preconditions, postconditions, edge cases, why the function exists), not for re-narrating the body
Do not record past implementation shape, refactor history, or 'the previous code did X' framing in code; use git log and git blame instead
Write comments only when: the reason for code is non-obvious (hidden invariant, workaround for known bug, deliberate exception), or the right name doesn't fit (temporary technical constraint)

Files:

  • installing/deps-installer/src/install/index.ts
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: Log emissions are part of matching pnpm — when porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(...), or streamParser.write(...), mirror the call site, payload, and ordering so @pnpm/cli.default-reporter parses pacquet's NDJSON the same way
Declare a newtype wrapper for branded string types instead of collapsing the brand into a plain String or &str in Rust
If upstream TypeScript always validates before construction of a branded string, validate in the Rust wrapper too via TryFrom<String> and/or FromStr and 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 infallible From<String> constructor
If upstream TypeScript occasionally constructs a branded string without validation, expose from_str_unchecked in 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 handwriting impl blocks; use manual impl only when conversion needs custom logic
Model TypeScript string literal unions (like 'auto' | 'always' | 'never') as Rust enums 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 in CODE_STYLE_GUIDE.md — imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching, pipe-trait, error handling, test layout, and cloning of Arc and Rc
Choose owned vs. borrowed parameters to minimize copies; widen to t...

Files:

  • pacquet/crates/package-manager/src/install.rs
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/install_accelerator/public_packages.rs
🧠 Learnings (25)
📓 Common learnings
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12189
File: pacquet/crates/cli/src/cli_args/install.rs:0-0
Timestamp: 2026-06-04T14:55:48.516Z
Learning: In `pacquet/crates/cli/src/cli_args/install.rs` (pnpm/pnpm repo), the `install_via_pnpr` function intentionally forwards the **full** `state.config.auth_headers` map to the pnpr server (not filtered to only the declared default/named registries). This is required for correctness: transitive dependencies can be scope-routed to registries not in the explicit registry list, or pinned to tarball URLs on hosts present in `.npmrc` but not a declared registry. Filtering to the declared registries silently drops tokens those sub-dependencies need, causing 401s on the server. pnpr uses the forwarded map to attach the right token per fetched URL exactly as a local install does (`AuthHeaders::for_url`). The pnpr-server's own credential is sent separately in the `Authorization` header and is excluded from the body map. Do NOT flag this as a credential-leakage issue — the rationale is documented in a comment at both call sites.
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12189
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:435-439
Timestamp: 2026-06-04T14:40:25.306Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs` (pnpm/pnpm repo), the pnpr install accelerator always invokes `Install` with `lockfile_only: true` (hard-coded in `pnpr/crates/pnpr/src/install_accelerator/resolve.rs`). Under `lockfile_only: true`:
1. The `PrefetchingResolver` wrapper is skipped — the bare `inner_resolver` is used instead, so `PrefetchContext { config }` is never constructed.
2. The function returns before `CreateVirtualStore` is reached, so `install_package_by_snapshot` and its `config.auth_headers` fetch path are never hit.
pnpr's tarball fetch is handled separately in `resolve::fetch_uncached`, which independently receives the request-scoped `auth_headers`. Therefore, `auth_override` only needs to be threaded into the resolver-side components (NpmResolver, TarballResolver, NamedRegistryResolver) — not into PrefetchingResolver or CreateVirtualStore — on the pnpr path. For local installs (`lockfile_only: false`), `auth_override` is always `None` a...
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12181
File: worker/src/start.ts:504-520
Timestamp: 2026-06-04T06:04:01.216Z
Learning: In pnpm/pnpm's pnpr install accelerator, the `/v1/install` response has a two-level framing structure:
1. **Outer layer** (full HTTP body): `[u32 outer header length][outer header JSON][files payload]` — `fetchFromPnpmRegistry` (pnpr/client/src/fetchFromPnpmRegistry.ts) strips the outer layer with `body.subarray(4 + headerLength)` and passes the remaining bytes to `writeCafsFiles`.
2. **Inner layer** (files payload): the files payload itself starts with its own `[u32 inner json length][inner header JSON]` prefix (built by the server's `build_files_payload` / `empty_files_payload_prefix`), followed by `[64-byte digest][u32 size][1-byte exec][content]` frames and a 64-zero-byte end marker.

`writeCafsFiles` in `worker/src/start.ts` is correct to read `jsonLen = payload.readUInt32BE(0)` and start frames at `offset = 4 + jsonLen` — this skips the inner header. The same two-level structure is mirrored in the Rust reference client (`parse_inline_response` + `write_files_payload`). Do not fla...
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-25T12:36:42.202Z
Learning: User-visible changes (CLI flags, defaults, environment variables, lockfile/manifest/state-file formats, error codes/messages, log emissions, store layout, hook semantics) in pnpm must be mirrored to pacquet in the same PR
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In the pacquet Rust port (pnpm/pnpm repo), the `ResolvedPackage.optional` AND-folding on revisit intentionally mirrors pnpm's `resolveDependencies.ts:1627-1648` behavior: only the directly-revisited package's `optional` flag is updated; transitive descendants are not re-walked. pnpm CLI corrects stale optional flags downstream via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`, which tracks a `nonOptional` set and re-stamps any package reachable by an all-non-optional path. Pacquet does not yet have this pruner equivalent, so the stale flags flow directly through `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up to port `copyDependencySubGraph` is planned; until then, do not flag the resolver-layer optional propagation gap as a bug in pacquet PRs — it is intentional parity with pnpm's resolver layer.
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11773
File: pacquet/crates/package-manager/src/install_package_from_registry.rs:111-114
Timestamp: 2026-05-20T20:41:50.322Z
Learning: In pacquet (pnpm/pnpm repo, Rust codebase), `install_package_from_registry` is the npm-only install path. The npm resolver always stamps `ResolveResult.id` (a `PkgResolutionId`) as `nameversion`. Parsing it back through `PkgNameVer` with `.expect()` is intentional — a parse failure means a mis-dispatch bug, not malformed external input. Per pacquet's CLAUDE.md: "Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees." Do not suggest replacing such `.expect()` calls with graceful error handling.
📚 Learning: 2026-06-04T14:55:48.516Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12189
File: pacquet/crates/cli/src/cli_args/install.rs:0-0
Timestamp: 2026-06-04T14:55:48.516Z
Learning: In `pacquet/crates/cli/src/cli_args/install.rs` (pnpm/pnpm repo), the `install_via_pnpr` function intentionally forwards the **full** `state.config.auth_headers` map to the pnpr server (not filtered to only the declared default/named registries). This is required for correctness: transitive dependencies can be scope-routed to registries not in the explicit registry list, or pinned to tarball URLs on hosts present in `.npmrc` but not a declared registry. Filtering to the declared registries silently drops tokens those sub-dependencies need, causing 401s on the server. pnpr uses the forwarded map to attach the right token per fetched URL exactly as a local install does (`AuthHeaders::for_url`). The pnpr-server's own credential is sent separately in the `Authorization` header and is excluded from the body map. Do NOT flag this as a credential-leakage issue — the rationale is documented in a comment at both call sites.

Applied to files:

  • installing/deps-installer/src/install/index.ts
  • pacquet/crates/package-manager/src/install.rs
📚 Learning: 2026-06-04T14:40:25.306Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12189
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:435-439
Timestamp: 2026-06-04T14:40:25.306Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs` (pnpm/pnpm repo), the pnpr install accelerator always invokes `Install` with `lockfile_only: true` (hard-coded in `pnpr/crates/pnpr/src/install_accelerator/resolve.rs`). Under `lockfile_only: true`:
1. The `PrefetchingResolver` wrapper is skipped — the bare `inner_resolver` is used instead, so `PrefetchContext { config }` is never constructed.
2. The function returns before `CreateVirtualStore` is reached, so `install_package_by_snapshot` and its `config.auth_headers` fetch path are never hit.
pnpr's tarball fetch is handled separately in `resolve::fetch_uncached`, which independently receives the request-scoped `auth_headers`. Therefore, `auth_override` only needs to be threaded into the resolver-side components (NpmResolver, TarballResolver, NamedRegistryResolver) — not into PrefetchingResolver or CreateVirtualStore — on the pnpr path. For local installs (`lockfile_only: false`), `auth_override` is always `None` a...

Applied to files:

  • installing/deps-installer/src/install/index.ts
  • pacquet/crates/package-manager/src/install.rs
  • pnpr/crates/pnpr/src/install_accelerator/public_packages.rs
📚 Learning: 2026-05-25T12:36:42.202Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-25T12:36:42.202Z
Learning: User-visible changes (CLI flags, defaults, environment variables, lockfile/manifest/state-file formats, error codes/messages, log emissions, store layout, hook semantics) in pnpm must be mirrored to pacquet in the same PR

Applied to files:

  • installing/deps-installer/src/install/index.ts
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Match how the same feature is implemented in the TypeScript pnpm CLI — any change in pacquet must match pnpm's behavior, logic, edge cases, config resolution, error messages, file/lockfile formats, and existing tests

Applied to files:

  • installing/deps-installer/src/install/index.ts
📚 Learning: 2026-06-04T06:04:01.216Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12181
File: worker/src/start.ts:504-520
Timestamp: 2026-06-04T06:04:01.216Z
Learning: In pnpm/pnpm's pnpr install accelerator, the `/v1/install` response has a two-level framing structure:
1. **Outer layer** (full HTTP body): `[u32 outer header length][outer header JSON][files payload]` — `fetchFromPnpmRegistry` (pnpr/client/src/fetchFromPnpmRegistry.ts) strips the outer layer with `body.subarray(4 + headerLength)` and passes the remaining bytes to `writeCafsFiles`.
2. **Inner layer** (files payload): the files payload itself starts with its own `[u32 inner json length][inner header JSON]` prefix (built by the server's `build_files_payload` / `empty_files_payload_prefix`), followed by `[64-byte digest][u32 size][1-byte exec][content]` frames and a 64-zero-byte end marker.

`writeCafsFiles` in `worker/src/start.ts` is correct to read `jsonLen = payload.readUInt32BE(0)` and start frames at `offset = 4 + jsonLen` — this skips the inner header. The same two-level structure is mirrored in the Rust reference client (`parse_inline_response` + `write_files_payload`). Do not fla...

Applied to files:

  • installing/deps-installer/src/install/index.ts
📚 Learning: 2026-05-24T08:18:06.019Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11895
File: pnpm/test/deploy.ts:91-95
Timestamp: 2026-05-24T08:18:06.019Z
Learning: In the pnpm/pnpm repository, integration tests that hit the real `registry.npmjs.org` (e.g., for `pacquet` or `pnpm/pacquet`) do NOT use a runtime env-var gate (such as `PNPM_RUN_PUBLIC_REGISTRY_TESTS`). They simply pass `--config.registry=https://registry.npmjs.org/` directly to `execPnpm` and set a higher timeout. This is the established pattern, as seen in `pnpm/test/install/pacquet.ts` and `pnpm/test/deploy.ts`. Do not suggest adding env-var guards for these tests.

Applied to files:

  • installing/deps-installer/src/install/index.ts
📚 Learning: 2026-05-18T20:35:22.917Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11729
File: pacquet/crates/resolving-npm-resolver/src/fetch_attestation_published_at.rs:55-57
Timestamp: 2026-05-18T20:35:22.917Z
Learning: In `pacquet/crates/resolving-npm-resolver/src/fetch_attestation_published_at.rs`, the npm attestation endpoint (`/-/npm/v1/attestations/{pkg_name}@{version}`) intentionally does NOT percent-encode the package name — the endpoint accepts literal `/` in scoped package names (e.g. `scope/pkg`). This matches upstream pnpm's `fetchAttestationPublishedAt.ts` behavior. Do not flag missing URL encoding here. By contrast, the full-metadata fetch paths (`fetch_full_metadata`, `fetch_full_metadata_cached`) DO percent-encode via the `registry_url::to_registry_url` helper, matching upstream's `toUri` behavior.

Applied to files:

  • installing/deps-installer/src/install/index.ts
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Do not change lockfile format, store layout, `.npmrc` semantics, or CLI surface unless pnpm changed them first

Applied to files:

  • installing/deps-installer/src/install/index.ts
📚 Learning: 2026-05-20T20:41:50.322Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11773
File: pacquet/crates/package-manager/src/install_package_from_registry.rs:111-114
Timestamp: 2026-05-20T20:41:50.322Z
Learning: In pacquet (pnpm/pnpm repo, Rust codebase), `install_package_from_registry` is the npm-only install path. The npm resolver always stamps `ResolveResult.id` (a `PkgResolutionId`) as `nameversion`. Parsing it back through `PkgNameVer` with `.expect()` is intentional — a parse failure means a mis-dispatch bug, not malformed external input. Per pacquet's CLAUDE.md: "Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees." Do not suggest replacing such `.expect()` calls with graceful error handling.

Applied to files:

  • installing/deps-installer/src/install/index.ts
📚 Learning: 2026-05-26T18:31:14.579Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11967
File: .changeset/git-fetcher-reject-non-sha-commits.md:2-2
Timestamp: 2026-05-26T18:31:14.579Z
Learning: In the pnpm monorepo, the `fetching/` directory contains multiple separate npm packages each with their own scoped name using a dot-separator convention, e.g., `pnpm/fetching.git-fetcher` (declared in `fetching/git-fetcher/package.json`) and `pnpm/fetching.tarball-fetcher`. There is no top-level `pnpm/fetching` package. Changesets targeting the git-fetcher should use `"pnpm/fetching.git-fetcher"`.

Applied to files:

  • installing/deps-installer/src/install/index.ts
📚 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/src/install/index.ts
📚 Learning: 2026-05-23T09:14:43.635Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11867
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:726-730
Timestamp: 2026-05-23T09:14:43.635Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs`, the fresh-lockfile path intentionally does not run lifecycle scripts (`BuildModules` is not invoked, and `side_effects_maps_by_snapshot` from `CreateVirtualStoreOutput` is discarded). This is pre-existing behavior documented by an in-code comment mirroring upstream `link.ts:167-170`. The frozen-lockfile path wires `BuildModules` end-to-end as normal. Wiring lifecycle scripts into the fresh-lockfile path is tracked as future work, separate from this file's concern.

Applied to files:

  • pacquet/crates/package-manager/src/install.rs
📚 Learning: 2026-05-23T09:14:43.635Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11867
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:726-730
Timestamp: 2026-05-23T09:14:43.635Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs`, the fresh-lockfile path intentionally does not invoke `BuildModules` and discards `side_effects_maps_by_snapshot` from `CreateVirtualStoreOutput`. This is pre-existing, documented behavior (mirroring upstream `link.ts:167-170`): `importing_done` fires once extraction and symlink linking are complete, and the fresh-lockfile path does not run lifecycle scripts. The frozen-lockfile path wires `BuildModules` end-to-end as normal. Do not flag this omission as a bug; wiring lifecycle scripts into the fresh-lockfile path is tracked as future work separate from perf refactors.

Applied to files:

  • pacquet/crates/package-manager/src/install.rs
📚 Learning: 2026-05-23T16:55:36.507Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11878
File: pacquet/crates/cli/tests/lockfile_verification.rs:158-162
Timestamp: 2026-05-23T16:55:36.507Z
Learning: In `pacquet/crates/cli/tests/lockfile_verification.rs`, the `trust_lockfile_skips_verification` and `trust_lockfile_cli_flag_skips_verification` tests intentionally do NOT assert `output.status.success()`. The hand-rolled fixture lockfile uses a placeholder integrity hash (`sha512-AAA…`), so the install always fails the downstream tarball integrity check regardless of the supply-chain gate. The contract being tested is "gate-skipped, not install-succeeded"; asserting success would require generating a real lockfile via the `generate_lockfile` pattern (see `hoist.rs`) which is considered not worth the extra wiring for an opt-out smoke test.

Applied to files:

  • pacquet/crates/package-manager/src/install.rs
📚 Learning: 2026-05-24T21:11:04.272Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In the pacquet Rust port (pnpm/pnpm repo), the `ResolvedPackage.optional` AND-folding on revisit intentionally mirrors pnpm's `resolveDependencies.ts:1627-1648` behavior: only the directly-revisited package's `optional` flag is updated; transitive descendants are not re-walked. pnpm CLI corrects stale optional flags downstream via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`, which tracks a `nonOptional` set and re-stamps any package reachable by an all-non-optional path. Pacquet does not yet have this pruner equivalent, so the stale flags flow directly through `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up to port `copyDependencySubGraph` is planned; until then, do not flag the resolver-layer optional propagation gap as a bug in pacquet PRs — it is intentional parity with pnpm's resolver layer.

Applied to files:

  • pacquet/crates/package-manager/src/install.rs
📚 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/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/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/package-manager/src/install.rs
📚 Learning: 2026-06-02T16:13:39.456Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12144
File: pnpr/crates/pnpr/src/install_accelerator/verdict_cache.rs:57-61
Timestamp: 2026-06-02T16:13:39.456Z
Learning: In `pnpr/crates/pnpr/src/install_accelerator/verdict_cache.rs`, the `lockfile_verdicts` SQLite table intentionally uses `hash` alone (not a composite `(hash, policy)` key) as the primary key — last-write-wins per hash. This mirrors the local `lockfile-verified.jsonl` cache design in pnpm. A looser current policy can trust a stricter cached pass via `can_trust_past_check`; alternating-policy re-verification is an accepted trade-off. A composite key was explicitly rejected to maintain parity with the local cache model.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/public_packages.rs
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Applies to pnpr/**/pnpr/crates/**/Cargo.toml : Cargo.toml files for new registry-only crates must use the pnpr- prefix for the package name

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/public_packages.rs
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Applies to pnpr/**/pnpr/crates/**/Cargo.toml : New registry-only crates must be placed under pnpr/crates/<short-name>/ and named pnpr-<short-name> in Cargo.toml, never using the pacquet- prefix

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/public_packages.rs
📚 Learning: 2026-05-25T14:58:11.105Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11931
File: pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs:560-589
Timestamp: 2026-05-25T14:58:11.105Z
Learning: In `pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs`, all per-`(registry, name[, version])` caches in `NpmResolutionVerifier` (`published_at`, `full_meta`, `full_meta_for_trust`, `abbreviated_meta`, `local_meta`) intentionally use the same pattern: lock → miss-check → release lock → await fetch/load → re-acquire lock → insert. This uniform pattern is deliberate; do not flag individual caches for using it. The known follow-up improvement (replacing the pattern with `tokio::sync::OnceCell` per key inside a `Mutex<HashMap<…>>`) is tracked as a future structural change to cover all five caches simultaneously.

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/public_packages.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/*.rs : Follow Rust API Guidelines for naming

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/public_packages.rs
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Applies to 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

Applied to files:

  • pnpr/crates/pnpr/src/install_accelerator/public_packages.rs

…error path

Adds the missing keystone tests for the feature's core behavior:
- a forwarded upstream token lets the accelerator resolve and fetch
  `@pnpm.e2e/needs-auth` (which the test registry gates behind
  `$authenticated`), and the same install fails without it;
- an unreachable upstream during re-verify surfaces as a 502.

The end-to-end tests register a user with the shared test registry to
obtain a real bearer token, so resolution and fetch exercise the forwarded
credential rather than mocking the gate in isolation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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/crates/pnpr-client/tests/integration.rs`:
- Around line 55-58: The nerf_key helper currently strips registry path prefixes
by only keeping the host[:port]; update nerf_key to preserve a leading registry
path segment when present: parse the URL after "://" into segments (e.g., let
mut parts = url.split("://").nth(1).unwrap_or(url).split('/'); let host =
parts.next().unwrap_or(""); let maybe_prefix = parts.next().filter(|s|
!s.is_empty()); then format the key as "//{host}/{prefix}/" when maybe_prefix
exists or "//{host}/" otherwise so registries with path prefixes are correctly
represented; modify the nerf_key function accordingly.
- Around line 179-181: The test currently only checks that
client.install(opts).await returned an Err (result), which may hide unrelated
failures; update the assertion to match the expected authentication-denied error
shape from the pnpr client/server (inspect the returned Result from
client.install(opts).await and assert it contains the server error with HTTP
status 401 or 403). Concretely, replace the generic assert!(result.is_err())
with code that unwraps/matches result and asserts the error variant contains a
server response with status code 401 or 403 (referencing the variables/functions
result, client.install, and opts to locate the assertion).
🪄 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: 4bbc731d-5504-490a-b3b5-d8d8649ab60e

📥 Commits

Reviewing files that changed from the base of the PR and between 39d41a1 and 0ab3993.

📒 Files selected for processing (3)
  • pacquet/crates/pnpr-client/Cargo.toml
  • pacquet/crates/pnpr-client/tests/integration.rs
  • pnpr/crates/pnpr/src/install_accelerator/tests.rs
✅ Files skipped from review due to trivial changes (1)
  • pacquet/crates/pnpr-client/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • pnpr/crates/pnpr/src/install_accelerator/tests.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). (7)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Code Coverage
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (2)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: Log emissions are part of matching pnpm — when porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(...), or streamParser.write(...), mirror the call site, payload, and ordering so @pnpm/cli.default-reporter parses pacquet's NDJSON the same way
Declare a newtype wrapper for branded string types instead of collapsing the brand into a plain String or &str in Rust
If upstream TypeScript always validates before construction of a branded string, validate in the Rust wrapper too via TryFrom<String> and/or FromStr and 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 infallible From<String> constructor
If upstream TypeScript occasionally constructs a branded string without validation, expose from_str_unchecked in 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 handwriting impl blocks; use manual impl only when conversion needs custom logic
Model TypeScript string literal unions (like 'auto' | 'always' | 'never') as Rust enums 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 in CODE_STYLE_GUIDE.md — imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching, pipe-trait, error handling, test layout, and cloning of Arc and Rc
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 — use tempfile::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 scalar assert_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 with insta and carefully review diffs when intentional changes alter snapshots; accept with cargo insta review only after careful review
Tests that need the mocked registry should start pnpr through pacquet-testing-utils; cargo test / cargo nextest run should not require a separate just registry-mock launch step

Files:

  • pacquet/crates/pnpr-client/tests/integration.rs
🧠 Learnings (16)
📓 Common learnings
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12189
File: pacquet/crates/cli/src/cli_args/install.rs:0-0
Timestamp: 2026-06-04T14:55:48.516Z
Learning: In `pacquet/crates/cli/src/cli_args/install.rs` (pnpm/pnpm repo), the `install_via_pnpr` function intentionally forwards the **full** `state.config.auth_headers` map to the pnpr server (not filtered to only the declared default/named registries). This is required for correctness: transitive dependencies can be scope-routed to registries not in the explicit registry list, or pinned to tarball URLs on hosts present in `.npmrc` but not a declared registry. Filtering to the declared registries silently drops tokens those sub-dependencies need, causing 401s on the server. pnpr uses the forwarded map to attach the right token per fetched URL exactly as a local install does (`AuthHeaders::for_url`). The pnpr-server's own credential is sent separately in the `Authorization` header and is excluded from the body map. Do NOT flag this as a credential-leakage issue — the rationale is documented in a comment at both call sites.
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12189
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:435-439
Timestamp: 2026-06-04T14:40:25.306Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs` (pnpm/pnpm repo), the pnpr install accelerator always invokes `Install` with `lockfile_only: true` (hard-coded in `pnpr/crates/pnpr/src/install_accelerator/resolve.rs`). Under `lockfile_only: true`:
1. The `PrefetchingResolver` wrapper is skipped — the bare `inner_resolver` is used instead, so `PrefetchContext { config }` is never constructed.
2. The function returns before `CreateVirtualStore` is reached, so `install_package_by_snapshot` and its `config.auth_headers` fetch path are never hit.
pnpr's tarball fetch is handled separately in `resolve::fetch_uncached`, which independently receives the request-scoped `auth_headers`. Therefore, `auth_override` only needs to be threaded into the resolver-side components (NpmResolver, TarballResolver, NamedRegistryResolver) — not into PrefetchingResolver or CreateVirtualStore — on the pnpr path. For local installs (`lockfile_only: false`), `auth_override` is always `None` a...
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12181
File: worker/src/start.ts:504-520
Timestamp: 2026-06-04T06:04:01.216Z
Learning: In pnpm/pnpm's pnpr install accelerator, the `/v1/install` response has a two-level framing structure:
1. **Outer layer** (full HTTP body): `[u32 outer header length][outer header JSON][files payload]` — `fetchFromPnpmRegistry` (pnpr/client/src/fetchFromPnpmRegistry.ts) strips the outer layer with `body.subarray(4 + headerLength)` and passes the remaining bytes to `writeCafsFiles`.
2. **Inner layer** (files payload): the files payload itself starts with its own `[u32 inner json length][inner header JSON]` prefix (built by the server's `build_files_payload` / `empty_files_payload_prefix`), followed by `[64-byte digest][u32 size][1-byte exec][content]` frames and a 64-zero-byte end marker.

`writeCafsFiles` in `worker/src/start.ts` is correct to read `jsonLen = payload.readUInt32BE(0)` and start frames at `offset = 4 + jsonLen` — this skips the inner header. The same two-level structure is mirrored in the Rust reference client (`parse_inline_response` + `write_files_payload`). Do not fla...
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In the pacquet Rust port (pnpm/pnpm repo), the `ResolvedPackage.optional` AND-folding on revisit intentionally mirrors pnpm's `resolveDependencies.ts:1627-1648` behavior: only the directly-revisited package's `optional` flag is updated; transitive descendants are not re-walked. pnpm CLI corrects stale optional flags downstream via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`, which tracks a `nonOptional` set and re-stamps any package reachable by an all-non-optional path. Pacquet does not yet have this pruner equivalent, so the stale flags flow directly through `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up to port `copyDependencySubGraph` is planned; until then, do not flag the resolver-layer optional propagation gap as a bug in pacquet PRs — it is intentional parity with pnpm's resolver layer.
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11773
File: pacquet/crates/package-manager/src/install_package_from_registry.rs:111-114
Timestamp: 2026-05-20T20:41:50.322Z
Learning: In pacquet (pnpm/pnpm repo, Rust codebase), `install_package_from_registry` is the npm-only install path. The npm resolver always stamps `ResolveResult.id` (a `PkgResolutionId`) as `nameversion`. Parsing it back through `PkgNameVer` with `.expect()` is intentional — a parse failure means a mis-dispatch bug, not malformed external input. Per pacquet's CLAUDE.md: "Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees." Do not suggest replacing such `.expect()` calls with graceful error handling.
📚 Learning: 2026-06-04T14:55:48.516Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12189
File: pacquet/crates/cli/src/cli_args/install.rs:0-0
Timestamp: 2026-06-04T14:55:48.516Z
Learning: In `pacquet/crates/cli/src/cli_args/install.rs` (pnpm/pnpm repo), the `install_via_pnpr` function intentionally forwards the **full** `state.config.auth_headers` map to the pnpr server (not filtered to only the declared default/named registries). This is required for correctness: transitive dependencies can be scope-routed to registries not in the explicit registry list, or pinned to tarball URLs on hosts present in `.npmrc` but not a declared registry. Filtering to the declared registries silently drops tokens those sub-dependencies need, causing 401s on the server. pnpr uses the forwarded map to attach the right token per fetched URL exactly as a local install does (`AuthHeaders::for_url`). The pnpr-server's own credential is sent separately in the `Authorization` header and is excluded from the body map. Do NOT flag this as a credential-leakage issue — the rationale is documented in a comment at both call sites.

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Tests that need the mocked registry should start `pnpr` through `pacquet-testing-utils`; `cargo test` / `cargo nextest run` should not require a separate `just registry-mock launch` step

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-06-04T14:40:25.306Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12189
File: pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs:435-439
Timestamp: 2026-06-04T14:40:25.306Z
Learning: In `pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs` (pnpm/pnpm repo), the pnpr install accelerator always invokes `Install` with `lockfile_only: true` (hard-coded in `pnpr/crates/pnpr/src/install_accelerator/resolve.rs`). Under `lockfile_only: true`:
1. The `PrefetchingResolver` wrapper is skipped — the bare `inner_resolver` is used instead, so `PrefetchContext { config }` is never constructed.
2. The function returns before `CreateVirtualStore` is reached, so `install_package_by_snapshot` and its `config.auth_headers` fetch path are never hit.
pnpr's tarball fetch is handled separately in `resolve::fetch_uncached`, which independently receives the request-scoped `auth_headers`. Therefore, `auth_override` only needs to be threaded into the resolver-side components (NpmResolver, TarballResolver, NamedRegistryResolver) — not into PrefetchingResolver or CreateVirtualStore — on the pnpr path. For local installs (`lockfile_only: false`), `auth_override` is always `None` a...

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Prefer real fixtures over dependency-injection seams — use `tempfile::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

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-05-24T08:18:06.019Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11895
File: pnpm/test/deploy.ts:91-95
Timestamp: 2026-05-24T08:18:06.019Z
Learning: In the pnpm/pnpm repository, integration tests that hit the real `registry.npmjs.org` (e.g., for `pacquet` or `pnpm/pacquet`) do NOT use a runtime env-var gate (such as `PNPM_RUN_PUBLIC_REGISTRY_TESTS`). They simply pass `--config.registry=https://registry.npmjs.org/` directly to `execPnpm` and set a higher timeout. This is the established pattern, as seen in `pnpm/test/install/pacquet.ts` and `pnpm/test/deploy.ts`. Do not suggest adding env-var guards for these tests.

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Use snapshot tests with `insta` and carefully review diffs when intentional changes alter snapshots; accept with `cargo insta review` only after careful review

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-05-20T21:18:56.391Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11778
File: pacquet/crates/resolving-local-resolver/tests/resolve.rs:365-372
Timestamp: 2026-05-20T21:18:56.391Z
Learning: In `pacquet/crates/resolving-local-resolver/tests/resolve.rs`, the test `fail_when_resolving_from_not_existing_directory_an_injected_dependency` intentionally uses `injected: false`. The test is a verbatim port of the upstream pnpm TypeScript test (resolving/local-resolver/test/index.ts at ef87f3ccff). The `injected` flag only affects the file/link protocol choice for plain directory paths; when the `file:` scheme is explicit in the bare specifier, the flag has no effect on the resolution code path. The misleading test name is inherited from upstream.

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-05-23T16:55:36.507Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11878
File: pacquet/crates/cli/tests/lockfile_verification.rs:158-162
Timestamp: 2026-05-23T16:55:36.507Z
Learning: In `pacquet/crates/cli/tests/lockfile_verification.rs`, the `trust_lockfile_skips_verification` and `trust_lockfile_cli_flag_skips_verification` tests intentionally do NOT assert `output.status.success()`. The hand-rolled fixture lockfile uses a placeholder integrity hash (`sha512-AAA…`), so the install always fails the downstream tarball integrity check regardless of the supply-chain gate. The contract being tested is "gate-skipped, not install-succeeded"; asserting success would require generating a real lockfile via the `generate_lockfile` pattern (see `hoist.rs`) which is considered not worth the extra wiring for an opt-out smoke test.

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-05-20T20:41:50.322Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11773
File: pacquet/crates/package-manager/src/install_package_from_registry.rs:111-114
Timestamp: 2026-05-20T20:41:50.322Z
Learning: In pacquet (pnpm/pnpm repo, Rust codebase), `install_package_from_registry` is the npm-only install path. The npm resolver always stamps `ResolveResult.id` (a `PkgResolutionId`) as `nameversion`. Parsing it back through `PkgNameVer` with `.expect()` is intentional — a parse failure means a mis-dispatch bug, not malformed external input. Per pacquet's CLAUDE.md: "Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees." Do not suggest replacing such `.expect()` calls with graceful error handling.

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-05-23T17:30:06.849Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11878
File: resolving/npm-resolver/src/createNpmResolutionVerifier.ts:381-418
Timestamp: 2026-05-23T17:30:06.849Z
Learning: In `resolving/npm-resolver/src/pickPackage.ts` (pnpm/pnpm), the resolver's `PackageMetaCache` keys by `name` (abbreviated) and `name:full` (full metadata) only — no registry component is included. This is a pre-existing limitation meaning that if two different registries serve packages of the same name in one install, the cache will only hold the first fetched entry. The `createNpmResolutionVerifier.ts` shares this same cache and inherits the limitation; a `validateSharedMeta` name-check guards against cross-package contamination but cannot distinguish same-named packages from different registries. Tightening to a registry-qualified key would require a coordinated change to the resolver's cache key shape. The Pacquet/Rust side is already registry-qualified (`{registry}\x00{name}:full`).

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-06-04T06:04:01.216Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12181
File: worker/src/start.ts:504-520
Timestamp: 2026-06-04T06:04:01.216Z
Learning: In pnpm/pnpm's pnpr install accelerator, the `/v1/install` response has a two-level framing structure:
1. **Outer layer** (full HTTP body): `[u32 outer header length][outer header JSON][files payload]` — `fetchFromPnpmRegistry` (pnpr/client/src/fetchFromPnpmRegistry.ts) strips the outer layer with `body.subarray(4 + headerLength)` and passes the remaining bytes to `writeCafsFiles`.
2. **Inner layer** (files payload): the files payload itself starts with its own `[u32 inner json length][inner header JSON]` prefix (built by the server's `build_files_payload` / `empty_files_payload_prefix`), followed by `[64-byte digest][u32 size][1-byte exec][content]` frames and a 64-zero-byte end marker.

`writeCafsFiles` in `worker/src/start.ts` is correct to read `jsonLen = payload.readUInt32BE(0)` and start frames at `offset = 4 + jsonLen` — this skips the inner header. The same two-level structure is mirrored in the Rust reference client (`parse_inline_response` + `write_files_payload`). Do not fla...

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Port relevant pnpm tests to Rust tests whenever they translate when porting behavior from pnpm

Applied to files:

  • pacquet/crates/pnpr-client/tests/integration.rs
📚 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

Comment thread pacquet/crates/pnpr-client/tests/integration.rs
Comment thread pacquet/crates/pnpr-client/tests/integration.rs Outdated
Per review: assert the no-credential install fails specifically with a
401-bearing server error (not just any error), and make the test's
`nerf_key` helper preserve a registry path prefix so it can't key a
forwarded credential wrong for a path-prefixed registry.
@zkochan zkochan merged commit 5192edf into main Jun 4, 2026
25 checks passed
@zkochan zkochan deleted the feat/12184 branch June 4, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pnpr install accelerator: remove /v1/files and add forwarded-credential access for external private registries

2 participants