feat(pnpr): registry mounts as the only routing model (RFC pnpm/rfcs#13)#12747
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR replaces pnpr’s uplinks/packages.proxy routing with a validated mount graph, then threads that model through config parsing, storage namespacing, journal replay, server read/write dispatch, and integration tests. The pacquet benchmark mock config is also updated to emit the newer mounts/defaultTarget shape. Changespnpr mount-graph routing
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant Server
participant Mounts
participant Storage
Client->>Server: GET packument or tarball
Server->>Mounts: resolve_mount_source(mount, package)
Mounts-->>Server: Concrete mount / NoRoute / UnknownMount
Server->>Storage: load hosted or upstream data
Storage-->>Server: packument bytes or not found
Server-->>Client: response
Possibly related PRs
Suggested labels: ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #12747 +/- ##
==========================================
+ Coverage 85.24% 85.37% +0.13%
==========================================
Files 407 410 +3
Lines 61864 62634 +770
==========================================
+ Hits 52734 53473 +739
- Misses 9130 9161 +31 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Integrated-Benchmark Report (Linux)Commit: Each scenario reports direct installs and pnpr installs. Bencher consumes pacquet@HEAD and pnpr@HEAD. Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.40671840772,
"stddev": 0.20165171355345998,
"median": 4.40601575532,
"user": 4.0901232400000005,
"system": 3.64463948,
"min": 4.14575552132,
"max": 4.78322368932,
"times": [
4.14575552132,
4.78322368932,
4.25897511632,
4.5773172543200005,
4.24809281432,
4.54558763732,
4.47405809432,
4.33797341632,
4.49743445332,
4.19876608032
]
},
{
"command": "pacquet@main",
"mean": 4.2816521313199996,
"stddev": 0.16745090947701924,
"median": 4.2164246503200005,
"user": 4.131361839999999,
"system": 3.5975447799999998,
"min": 4.09886014632,
"max": 4.55367885532,
"times": [
4.15760097532,
4.55367885532,
4.14359436032,
4.09886014632,
4.21549151232,
4.22826458532,
4.21735778832,
4.20691092232,
4.48200663032,
4.51275553732
]
},
{
"command": "pnpr@HEAD",
"mean": 2.13382803462,
"stddev": 0.0985733730554716,
"median": 2.12059731132,
"user": 2.73200244,
"system": 3.1114828799999996,
"min": 2.02056764632,
"max": 2.3424702973200002,
"times": [
2.17694084132,
2.12476980432,
2.11642481832,
2.06780533832,
2.02478507532,
2.22958441932,
2.1555000143200003,
2.02056764632,
2.3424702973200002,
2.07943209132
]
},
{
"command": "pnpr@main",
"mean": 2.2015779191200004,
"stddev": 0.1284008494972573,
"median": 2.2078973938199997,
"user": 2.6982103400000006,
"system": 3.07788958,
"min": 2.02126418032,
"max": 2.36102232932,
"times": [
2.30024776732,
2.18632120932,
2.22947357832,
2.02126418032,
2.16213830932,
2.05156506532,
2.04874902332,
2.34438947832,
2.31060825032,
2.36102232932
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.67177031444,
"stddev": 0.014566811815515338,
"median": 0.67546993014,
"user": 0.39426264,
"system": 1.355362,
"min": 0.64294460464,
"max": 0.69415839964,
"times": [
0.64294460464,
0.6804995866400001,
0.67978567664,
0.6671359506400001,
0.6593642656400001,
0.6614934196400001,
0.6813813806400001,
0.69415839964,
0.67120465864,
0.67973520164
]
},
{
"command": "pacquet@main",
"mean": 0.7088804024400001,
"stddev": 0.0949341920186914,
"median": 0.67579751814,
"user": 0.40581094,
"system": 1.3657997000000002,
"min": 0.65132508064,
"max": 0.9730396986400001,
"times": [
0.6993817546400001,
0.67089771064,
0.68004559964,
0.65132508064,
0.66450826864,
0.67154943664,
0.69013489464,
0.9730396986400001,
0.72131341764,
0.66660816264
]
},
{
"command": "pnpr@HEAD",
"mean": 0.72387122784,
"stddev": 0.03286628038800479,
"median": 0.7194398521400001,
"user": 0.41962834000000004,
"system": 1.3712358999999998,
"min": 0.68124918164,
"max": 0.7794600176400001,
"times": [
0.76858115364,
0.75177828164,
0.7180167096400001,
0.72086299464,
0.7239787306400001,
0.68124918164,
0.7794600176400001,
0.69413230764,
0.7009368356400001,
0.69971606564
]
},
{
"command": "pnpr@main",
"mean": 0.7411101846400001,
"stddev": 0.0489920621854953,
"median": 0.7223029856400001,
"user": 0.41266744,
"system": 1.3865869,
"min": 0.6946696826400001,
"max": 0.8717598106400001,
"times": [
0.74230251364,
0.76131511464,
0.72283138264,
0.7217745886400001,
0.6946696826400001,
0.7328554526400001,
0.72119585464,
0.7217731606400001,
0.8717598106400001,
0.72062428564
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.2751197665200005,
"stddev": 0.028498423111423032,
"median": 4.27088247372,
"user": 3.81250376,
"system": 3.4406883199999996,
"min": 4.233058194720001,
"max": 4.33204650772,
"times": [
4.26683110872,
4.33204650772,
4.264644794720001,
4.246208144720001,
4.27267256972,
4.30988839172,
4.233058194720001,
4.28091725072,
4.269092377720001,
4.2758383247200005
]
},
{
"command": "pacquet@main",
"mean": 4.378128736720001,
"stddev": 0.046254314370711765,
"median": 4.38798085222,
"user": 3.90203196,
"system": 3.463350319999999,
"min": 4.30380403672,
"max": 4.44332250572,
"times": [
4.376550600720001,
4.4023223407200005,
4.39941110372,
4.41586053472,
4.35974947272,
4.44332250572,
4.34670933772,
4.30380403672,
4.41890210572,
4.314655328720001
]
},
{
"command": "pnpr@HEAD",
"mean": 2.18290531492,
"stddev": 0.13164919108093018,
"median": 2.12720878272,
"user": 2.5708867599999996,
"system": 2.96761242,
"min": 2.0480227587199997,
"max": 2.44067598672,
"times": [
2.08347658372,
2.0480227587199997,
2.44067598672,
2.14334239572,
2.2788331677199998,
2.07209560972,
2.11107516972,
2.33396714672,
2.22686014272,
2.0907041877199997
]
},
{
"command": "pnpr@main",
"mean": 2.2195839512199997,
"stddev": 0.11434937818191204,
"median": 2.20267538422,
"user": 2.54886066,
"system": 2.98964952,
"min": 2.08820757772,
"max": 2.48243353872,
"times": [
2.08820757772,
2.1878165417199997,
2.2259342587199997,
2.48243353872,
2.15036138472,
2.28646098372,
2.29378016772,
2.1364738757199997,
2.21753422672,
2.12683695672
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.4448239061600001,
"stddev": 0.017710645194208424,
"median": 1.44179447996,
"user": 1.4352273999999998,
"system": 1.7498289599999999,
"min": 1.42197174496,
"max": 1.47832080796,
"times": [
1.43794271896,
1.43862936296,
1.46904031896,
1.4510527099600001,
1.47832080796,
1.44495959696,
1.42197174496,
1.44670118196,
1.42721951996,
1.43240109896
]
},
{
"command": "pacquet@main",
"mean": 1.48011301936,
"stddev": 0.06956494978290728,
"median": 1.46326918296,
"user": 1.4580832000000004,
"system": 1.7471806599999997,
"min": 1.43379828296,
"max": 1.67480977396,
"times": [
1.67480977396,
1.45179612096,
1.46610000796,
1.48010311796,
1.4668174469600002,
1.46103829896,
1.46550006696,
1.45221321596,
1.4489538609600001,
1.43379828296
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6987501366600001,
"stddev": 0.03832686014593345,
"median": 0.68798036496,
"user": 0.3689619999999999,
"system": 1.30621606,
"min": 0.6783397349600001,
"max": 0.8059909269600001,
"times": [
0.6903146559600001,
0.8059909269600001,
0.6904437839600001,
0.6792995379600001,
0.6830054049600001,
0.6856460739600001,
0.6952068439600001,
0.69927758596,
0.6783397349600001,
0.6799768179600001
]
},
{
"command": "pnpr@main",
"mean": 0.71332472686,
"stddev": 0.07873956804704688,
"median": 0.6872931609600001,
"user": 0.36604559999999997,
"system": 1.31222306,
"min": 0.67871638296,
"max": 0.9362826459600001,
"times": [
0.6855784799600001,
0.68192022496,
0.69272773996,
0.67871638296,
0.6880992289600001,
0.68144979396,
0.6864870929600001,
0.70512960296,
0.9362826459600001,
0.6968560759600001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 3.09439206934,
"stddev": 0.05252000633357554,
"median": 3.09660376014,
"user": 1.86342812,
"system": 2.00351914,
"min": 3.01867883214,
"max": 3.18142428314,
"times": [
3.05342933914,
3.01867883214,
3.09799544814,
3.03668931014,
3.15941383414,
3.1321564721399997,
3.09521207214,
3.18142428314,
3.06649393614,
3.10242716614
]
},
{
"command": "pacquet@main",
"mean": 3.15582300794,
"stddev": 0.0481103131024168,
"median": 3.15109345764,
"user": 1.8841947199999995,
"system": 2.0244440399999997,
"min": 3.10869767214,
"max": 3.27488072814,
"times": [
3.17296113314,
3.10869767214,
3.1213733381399997,
3.15819009414,
3.14399682114,
3.15986458214,
3.1259585901399998,
3.27488072814,
3.17543419314,
3.1168729271399997
]
},
{
"command": "pnpr@HEAD",
"mean": 0.69404097564,
"stddev": 0.01665677732859752,
"median": 0.6945057826400001,
"user": 0.36129652,
"system": 1.3178772399999998,
"min": 0.66918567114,
"max": 0.72308709114,
"times": [
0.69062738814,
0.71308961814,
0.68571418214,
0.69838417714,
0.66918567114,
0.6780853691400001,
0.7022711961400001,
0.72308709114,
0.67955159214,
0.7004134711400001
]
},
{
"command": "pnpr@main",
"mean": 0.6865971040400002,
"stddev": 0.04520031238828661,
"median": 0.67539656264,
"user": 0.35726732,
"system": 1.2984097399999999,
"min": 0.6515708051400001,
"max": 0.8123924991400001,
"times": [
0.6827314711400001,
0.6758145551400001,
0.6839801871400001,
0.6749785701400001,
0.66708537314,
0.6515708051400001,
0.6662305631400001,
0.8123924991400001,
0.67917011114,
0.67201690514
]
}
]
}Scenario: Isolated linker: fresh restore, cold cache + cold store + cold pnpr
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 7.0496734843799995,
"stddev": 0.10508650987323757,
"median": 7.027582044179999,
"user": 4.22432682,
"system": 3.88389618,
"min": 6.94309597318,
"max": 7.279620255179999,
"times": [
6.98014766518,
7.279620255179999,
7.025233539179999,
6.96103099318,
7.1567313301799995,
7.10260920018,
6.9648345661799995,
7.0299305491799995,
7.05350077218,
6.94309597318
]
},
{
"command": "pacquet@main",
"mean": 7.8223275245799995,
"stddev": 0.1510333686547177,
"median": 7.82494637868,
"user": 4.32398522,
"system": 3.916239579999999,
"min": 7.65781133518,
"max": 8.21321331918,
"times": [
7.65781133518,
7.83538024118,
7.73862409818,
7.73573343118,
7.82012224218,
7.8397421521799995,
8.21321331918,
7.72079635118,
7.82977051518,
7.83208156018
]
},
{
"command": "pnpr@HEAD",
"mean": 4.980619983079999,
"stddev": 0.08355256540812521,
"median": 4.9895543496800006,
"user": 2.79836452,
"system": 3.2834410799999993,
"min": 4.82451506618,
"max": 5.0909496141799995,
"times": [
4.8856150101799996,
4.97340179718,
5.0909496141799995,
5.04886440718,
5.00570690218,
4.97230112218,
4.82451506618,
4.92090234118,
5.02248180518,
5.06146176518
]
},
{
"command": "pnpr@main",
"mean": 5.66421750528,
"stddev": 0.1464181373902031,
"median": 5.682372990679999,
"user": 2.86388482,
"system": 3.3146973800000006,
"min": 5.44136900218,
"max": 5.82935088218,
"times": [
5.7952412851799995,
5.44136900218,
5.58318732118,
5.57460903318,
5.46405562818,
5.7645150651799995,
5.620793455179999,
5.82510085418,
5.74395252618,
5.82935088218
]
}
]
} |
|
| Branch | pr/12747 |
| Testbed | pnpr |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | Latency | milliseconds (ms) |
|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot | 2,182.91 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 694.04 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 698.75 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 2,133.83 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store.cold-pnpr | 📈 view plot | 4,980.62 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 723.87 ms |
Micro-Benchmark ResultsLinux |
PR Summary by Qodopnpr: replace Verdaccio-shaped routing with validated registry mounts
AI Description
Diagram
High-Level Assessment
Files changed (18)
|
There was a problem hiding this comment.
Pull request overview
Implements pnpr’s “registry mounts” routing model (RFC pnpm/rfcs#13) as the sole routing surface, replacing the legacy Verdaccio-shaped uplinks: + packages: proxy: fallback model. The server now resolves every request (reads and writes) through a validated mount graph that deterministically maps each package name to exactly one concrete origin (hosted org or single upstream), eliminating cross-origin fall-through.
Changes:
- Added a new
mountmodule with a decidablePackagePatternlanguage plus static router validation (rejects shadowed/unreachable routes, duplicate patterns, unknown/self/non-concrete sources, baddefaultTarget). - Reworked pnpr request handling to route packuments/tarballs/version manifests and write flows (publish/dist-tag/unpublish/journal recovery) through mounts, including hosted-org namespaced storage.
- Updated configs and tests to the mount model; adjusted cache layout (public upstream cache under
~public/<digest>/...) and removed conditional-GET validator plumbing.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpr/crates/pnpr/tests/server.rs | Updates e2e server tests for mount routing, public-cache paths, and removes legacy multi-uplink fallback scenarios. |
| pnpr/crates/pnpr/tests/policy.rs | Switches policy tests to mount-based routing so packages: acts as ACL-only. |
| pnpr/crates/pnpr/tests/auth_publish.rs | Updates publish/auth tests to use a hosted+router mount setup for path-less routing. |
| pnpr/crates/pnpr/src/upstream/tests.rs | Adjusts upstream tests to reflect removed validator-capture behavior. |
| pnpr/crates/pnpr/src/upstream.rs | Removes per-uplink validator capture/storage and simplifies fetched packument payload. |
| pnpr/crates/pnpr/src/streaming/tests.rs | Updates tarball tmp/caching tests to use per-namespace uplink cache paths. |
| pnpr/crates/pnpr/src/storage/tests.rs | Removes tests for deleted validator-sidecar behavior. |
| pnpr/crates/pnpr/src/storage.rs | Adds hosted-store namespacing (for_hosted), removes shared proxy-cache validator sidecar, and simplifies stale cache representation. |
| pnpr/crates/pnpr/src/server.rs | Central refactor: mount-based dispatch for reads/writes, deterministic provenance routing, hosted-org namespaced writes + journaling, and updated cache namespacing. |
| pnpr/crates/pnpr/src/s3.rs | Adds namespaced() support for hosted-org object-key prefixes. |
| pnpr/crates/pnpr/src/mount/tests.rs | New unit tests covering pattern parsing/matching/coverage, resolution, and validation failures. |
| pnpr/crates/pnpr/src/mount.rs | New mount graph implementation with router resolution + static validation to prevent shadowing and misrouting. |
| pnpr/crates/pnpr/src/lib.rs | Exposes mount types publicly and wires the new module into the crate. |
| pnpr/crates/pnpr/src/journal.rs | Threads hosted-org namespace through the publish journal for correct crash recovery roll-forward. |
| pnpr/crates/pnpr/src/config/tests.rs | Migrates config parsing tests from legacy uplinks/packages routing to mount-based routing and validation checks. |
| pnpr/crates/pnpr/src/config.rs | Adds mount/hosted config structures and YAML parsing for mounts: + defaultTarget, removing packages: proxy: routing. |
| pnpr/crates/pnpr/config.yaml | Updates bundled config to document and use the mount model (registry-mock shape). |
| pacquet/tasks/integrated-benchmark/src/work_env.rs | Updates cold-mock config generation to include both new mount shape and legacy proxy shape for cross-revision compatibility. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (6)
pnpr/crates/pnpr/src/storage.rs (1)
492-494: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUpdate the stale branch comment.
The code no longer loads validators here, so this comment now describes removed behavior.
♻️ Proposed comment fix
- // Stale: load only the validators for the conditional refetch. - // The body is read later, on demand, and only if needed. + // Stale: do not read the body; callers refetch instead.As per path instructions, follow pnpr Rust comment guidance for changed Rust code.
🤖 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/storage.rs` around lines 492 - 494, Update the stale-branch comment in CachedPackument::Stale handling inside storage.rs so it no longer says validators are loaded; rewrite it to describe the current behavior of returning stale data without mentioning removed logic, and keep it aligned with the surrounding Rust comment style used in this codepath.Source: Path instructions
pnpr/crates/pnpr/src/upstream.rs (1)
173-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove validator-return wording from the modified fetch docs.
FetchedPackumentno longer carries validators, but theModifiedvariant comment still says it does.♻️ Proposed doc fix
- /// Upstream returned a fresh body, along with its cache validators. + /// Upstream returned a fresh body. Modified(FetchedPackument),As per path instructions, follow pnpr Rust comment guidance for changed Rust code.
🤖 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/upstream.rs` around lines 173 - 183, The `PackumentFetch::Modified` doc comment is outdated because `FetchedPackument` no longer includes cache validators. Update the wording in the `PackumentFetch` enum comments to describe only the fresh body returned by the upstream, and remove any mention of validator-return behavior while keeping the docs aligned with `FetchedPackument`.Source: Path instructions
pnpr/crates/pnpr/src/upstream/tests.rs (1)
98-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRename the test to match the new contract.
This no longer verifies validator capture, so the test name and ETag/Last-Modified fixture are stale.
♻️ Proposed cleanup
-async fn fetch_packument_captures_validators_from_response() { +async fn fetch_packument_returns_modified_body() { @@ - .with_header("etag", r#""abc123""#) - .with_header("last-modified", "Wed, 21 Oct 2015 07:28:00 GMT")🤖 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/upstream/tests.rs` around lines 98 - 115, The test function `fetch_packument_captures_validators_from_response` has a name that no longer matches what it actually tests. Rename this test function to accurately reflect its current purpose (verifying that a modified fetch carries the packument body) and remove or update the stale ETag and Last-Modified mock headers that are configured but never validated by the assertion.pnpr/crates/pnpr/src/config.rs (1)
833-838: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUpdate the remaining
Config::packagespublic docs.These docs now correctly say
packages:is ACL-only, but theConfig::packagesfield docs above still mentionproxy-based routing. That leaves generated docs contradictory after the proxy field removal.🤖 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/config.rs` around lines 833 - 838, The public docs for Config::packages still describe proxy-based routing, which now conflicts with the updated ACL-only behavior. Update the Config::packages field documentation to match the PackagePolicies / mount graph wording used in the nearby package access rules comment, and remove any mention of proxy routing so the generated docs stay consistent after the proxy field removal.pnpr/crates/pnpr/tests/server.rs (1)
365-367: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winKeep
cache: falsepackument refetch covered.The shared helper now accepts one packument fetch, but the
cache:falsetest expects two requests to refetch both packument and tarball. Parameterize the helper’s expected count or define the mock inline withexpect(2)for that test.Also applies to: 2023-2025
🤖 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/tests/server.rs` around lines 365 - 367, The shared packument mock setup in the server tests is now too permissive and no longer preserves the `cache: false` refetch behavior. Update the helper around the packument expectation so it can be configured per test, or keep the shared helper for the `cache: true` case and define the `cache: false` mock inline with an expected count of 2 in the affected test. Use the existing helper/test names in `server.rs` to locate the packument mock and make sure the `cache:false` scenario still verifies both packument and tarball requests.pnpr/crates/pnpr/tests/auth_publish.rs (1)
1416-1449: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDelete or re-enable this stale async test.
Removing
#[tokio::test]leaves this as dead code, so the search behavior is no longer covered. Either delete it with the upstream augmentation removal or re-enable/rename it for the new local-only behavior.🤖 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/tests/auth_publish.rs` around lines 1416 - 1449, The async search test is now stale because it is no longer marked as a test and only remains as dead code, so the search behavior is not being exercised. In auth_publish.rs, either remove search_augment_skips_when_upstream_404s if the upstream augmentation path was intentionally deleted, or restore it as an actual #[tokio::test] and rename/update it to match the current local-only search behavior so it continues to cover router/config/request handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pnpr/crates/pnpr/src/config.rs`:
- Around line 1678-1696: The upstream config validation in the `file.public`
handling path only rejects `auth`, but still allows contradictory `access:` and
credential-bearing custom headers to slip through or be dropped implicitly.
Update the validation in `config.rs` around the `file.public` checks so public
mounts fail closed when any access control or credential config is present,
including `file.access` and any `Authorization`-style header settings associated
with the mount. Keep the existing `RegistryError::InvalidConfig` flow, using the
same `name`-based error context in the public validation branch before `let
access = if file.public { None } else { file.access };`.
In `@pnpr/crates/pnpr/src/mount.rs`:
- Around line 80-84: The scoped package matching in PackagePattern::matches is
too permissive because AnyScoped accepts any string starting with @ and
Scope(scope) relies on scope_of without requiring a /name segment. Update
PackagePattern::matches (and scope_of if needed) so scoped patterns only match
valid scoped package names that include both scope and package segments, and
ensure @*/* and `@scope/`* do not match bare `@scope`.
In `@pnpr/crates/pnpr/src/s3.rs`:
- Around line 148-152: The S3Store::namespaced method is adding an extra slash
when segment is empty, which breaks the flat hosted namespace used by
for_hosted(""). Update namespaced (and any prefix construction it relies on) so
an empty segment preserves the existing prefix exactly instead of producing a
new “/” suffix, while still appending “segment/” for non-empty namespaces.
Ensure the behavior stays consistent for flat hosted orgs and nested namespaces
alike.
In `@pnpr/crates/pnpr/src/server.rs`:
- Around line 1043-1054: The upstream read path in load_packument_for_read
currently skips package access checks, allowing get_dist_tags to expose
dist-tags for restricted packages. Add an authorize(..., Action::Access) check
for the package before calling load_upstream_packument_for or returning upstream
data, using the existing Identity, AppState, and PackageName flow in
load_packument_for_read so the upstream branch enforces the same access policy
as other reads.
- Around line 2299-2303: The search path in serve_search only queries
state.inner.storage, so hosted org namespaces from storage.for_hosted(org) are
skipped. Update serve_search to run search through the mount graph’s reachable
hosted namespaces (using the same routing logic that selects the default hosted
org/router), then keep the existing ACL filtering on the resulting body. Use the
existing symbols serve_search and crate::search::run_local_search to locate the
query flow and replace the direct storage-only scan with the mount-aware search
path.
- Around line 954-973: The cache namespace logic in uplink_cache_namespace only
treats Authorization as a private input, so custom credential headers can still
fall back to the stable public namespace. Update this function to derive the
private cache namespace from all configured credential-bearing upstream headers
for the given uplink, or otherwise mark any uplink with configured headers as
private material. Use the existing uplink config lookup in
AppState/inner.config.uplinks and the cache digest helpers in crate::route to
keep the namespace secret-backed and stable for all credentialed cases.
---
Nitpick comments:
In `@pnpr/crates/pnpr/src/config.rs`:
- Around line 833-838: The public docs for Config::packages still describe
proxy-based routing, which now conflicts with the updated ACL-only behavior.
Update the Config::packages field documentation to match the PackagePolicies /
mount graph wording used in the nearby package access rules comment, and remove
any mention of proxy routing so the generated docs stay consistent after the
proxy field removal.
In `@pnpr/crates/pnpr/src/storage.rs`:
- Around line 492-494: Update the stale-branch comment in CachedPackument::Stale
handling inside storage.rs so it no longer says validators are loaded; rewrite
it to describe the current behavior of returning stale data without mentioning
removed logic, and keep it aligned with the surrounding Rust comment style used
in this codepath.
In `@pnpr/crates/pnpr/src/upstream.rs`:
- Around line 173-183: The `PackumentFetch::Modified` doc comment is outdated
because `FetchedPackument` no longer includes cache validators. Update the
wording in the `PackumentFetch` enum comments to describe only the fresh body
returned by the upstream, and remove any mention of validator-return behavior
while keeping the docs aligned with `FetchedPackument`.
In `@pnpr/crates/pnpr/src/upstream/tests.rs`:
- Around line 98-115: The test function
`fetch_packument_captures_validators_from_response` has a name that no longer
matches what it actually tests. Rename this test function to accurately reflect
its current purpose (verifying that a modified fetch carries the packument body)
and remove or update the stale ETag and Last-Modified mock headers that are
configured but never validated by the assertion.
In `@pnpr/crates/pnpr/tests/auth_publish.rs`:
- Around line 1416-1449: The async search test is now stale because it is no
longer marked as a test and only remains as dead code, so the search behavior is
not being exercised. In auth_publish.rs, either remove
search_augment_skips_when_upstream_404s if the upstream augmentation path was
intentionally deleted, or restore it as an actual #[tokio::test] and
rename/update it to match the current local-only search behavior so it continues
to cover router/config/request handling.
In `@pnpr/crates/pnpr/tests/server.rs`:
- Around line 365-367: The shared packument mock setup in the server tests is
now too permissive and no longer preserves the `cache: false` refetch behavior.
Update the helper around the packument expectation so it can be configured per
test, or keep the shared helper for the `cache: true` case and define the
`cache: false` mock inline with an expected count of 2 in the affected test. Use
the existing helper/test names in `server.rs` to locate the packument mock and
make sure the `cache:false` scenario still verifies both packument and tarball
requests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 30d955d2-e624-48d6-ada2-8a10cb15859f
📒 Files selected for processing (18)
pacquet/tasks/integrated-benchmark/src/work_env.rspnpr/crates/pnpr/config.yamlpnpr/crates/pnpr/src/config.rspnpr/crates/pnpr/src/config/tests.rspnpr/crates/pnpr/src/journal.rspnpr/crates/pnpr/src/lib.rspnpr/crates/pnpr/src/mount.rspnpr/crates/pnpr/src/mount/tests.rspnpr/crates/pnpr/src/s3.rspnpr/crates/pnpr/src/server.rspnpr/crates/pnpr/src/storage.rspnpr/crates/pnpr/src/storage/tests.rspnpr/crates/pnpr/src/streaming/tests.rspnpr/crates/pnpr/src/upstream.rspnpr/crates/pnpr/src/upstream/tests.rspnpr/crates/pnpr/tests/auth_publish.rspnpr/crates/pnpr/tests/policy.rspnpr/crates/pnpr/tests/server.rs
Code Review by Qodo
1. Cached tarball skips bind
|
|
Code review by qodo was updated up to the latest commit 8cb686f |
|
Code review by qodo was updated up to the latest commit ef024e9 |
|
Code review by qodo was updated up to the latest commit ef024e9 |
|
Code review by qodo was updated up to the latest commit 22e8b7d |
|
Code review by qodo was updated up to the latest commit a79955b |
|
Code review by qodo was updated up to the latest commit e1a9bee |
|
Code review by qodo was updated up to the latest commit e1a9bee |
An upstream mount's cache namespace was derived from the mount name (and credential epoch) only, so repointing a mount's `url:` kept serving packuments and tarballs cached from the previous origin — amplified by the cache-first warm tarball path, which serves a cached entry without re-binding it against the current packument. Fold the URL into both namespace shapes (the public digest and the private HMAC epoch): the cache is a mirror of one declared origin, and repointing now abandons the old origin's content. Tests that reused a warm storage under a *different* URL to simulate an offline replay now reuse the same origin (the `expect(1)` mocks prove the upstream is not consulted again); the tampered-tarball test drops the mock server instead, keeping its cache-was-never-written proof. Added `repointing_an_uplink_url_abandons_the_old_origins_cache`, verified to fail without the fix.
… path Name the two consequences the fast path accepts — an unpublished version stays downloadable from the disposable mirror until wiped, and a later hostile packument rewrite cannot retroactively affect bytes verified on the way in — and why the fail-closed bind protects fetches of new bytes rather than cache hits.
1 similar comment
|
Code review by qodo was updated up to the latest commit 2a829f4 |
Catch the RFC up to what pnpm/pnpm#12747 landed during review: - mount names are validated at load as single URL-safe path segments - the hosted kind is named Hosted (not HostedOrg); duplicate hosted org namespaces are rejected - a public upstream rejects any custom request header, not just Authorization - router validation also rejects a single shadowed pattern inside an otherwise-reachable route - upstream cache namespaces are keyed by origin URL as well, so repointing a mount abandons the previous origin's cache - stale-cache serving is scoped to transient failures; an authoritative 4xx surfaces and a definitive 404 purges the cached packument - warm tarball hits serve without re-reading the packument (bound and verified at write time; OSV forces the bind first) - caller-gated responses carry private-cache headers on every URL surface, including the path-less base when the package ACL gates anonymous access
…e resolver (#832) Reflects the pnpr redesign in pnpm/pnpm#12700 (authorization-aware resolver cache, default-deny fetch allowlist, no forwarded upstream credentials) and pnpm/pnpm#12747 (registry mounts as the only routing model): mounts:/defaultTarget: replace uplinks: and packages: proxy:, packages: is ACL-only, and every mount is served at /~<mount>/.
…e ACL The full-purity registry-mock config (#12747) broke the TypeScript test suite in ways TS CI never caught (it was path-filter-skipped on that pnpr-only merge): - The @zkochan/* and @pnpm/* routes claimed those entire REAL npm scopes with no fall-through, 404ing real packages that proxied dependency trees need (@zkochan/async-regex-replace, @pnpm/error). The fixture packages in those scopes are now routed individually; the rest of each scope proxies npm again. - Unscoped names tests publish to the mock (test-publish-*, batch-*, project-100, ...) routed to the npmjs upstream, where a write is rejected. They are enumerated exactly; @pnpmtest/* covers dynamically-suffixed publish tests. Deliberately no unscoped prefix wildcards: a test-* route would swallow real packages like test-exclude (istanbul's dependency tree). - The migration dropped the '**' ACL entry, and the built-in default admits no one to unpublish, so unpublish tests got 403. Restored: $all access, $authenticated publish and unpublish.
…m writes (#12769) * fix(pnpr): route the registry mock by exact name and restore its write ACL The full-purity registry-mock config (#12747) broke the TypeScript test suite in ways TS CI never caught (it was path-filter-skipped on that pnpr-only merge): - The @zkochan/* and @pnpm/* routes claimed those entire REAL npm scopes with no fall-through, 404ing real packages that proxied dependency trees need (@zkochan/async-regex-replace, @pnpm/error). The fixture packages in those scopes are now routed individually; the rest of each scope proxies npm again. - Unscoped names tests publish to the mock (test-publish-*, batch-*, project-100, ...) routed to the npmjs upstream, where a write is rejected. They are enumerated exactly; @pnpmtest/* covers dynamically-suffixed publish tests. Deliberately no unscoped prefix wildcards: a test-* route would swallow real packages like test-exclude (istanbul's dependency tree). - The migration dropped the '**' ACL entry, and the built-in default admits no one to unpublish, so unpublish tests got 403. Restored: $all access, $authenticated publish and unpublish. * test(pnpm11): stop dist-tagging and publishing over real npm packages Under the mounts model a write to an upstream-routed name is rejected, and the old materialize-on-write overlay is gone on purpose — so tests may only write to packages the mock hosts. Migrate every real-npm write target to a dedicated fixture: - @pnpm.e2e/multi-version-{a,b,c} replace is-negative/is-positive/micromatch in the update, overwrite, and interactive-update tests. - @pnpm.e2e/circular-{iterator,ext,symbol} replace the es6-iterator/es5-ext/es6-symbol circular trio; circular-ext requires ^2.0.1 so both circular-iterator versions land in the tree, which is the point of the concurrency test. - @pnpm.e2e/function-with-clone replaces lodash where the test executes the installed code (module and module.clone are functions). - @scoped/exports-function replaces @rstacruz/tap-spec in the scoped devDependencies-save test. - @pnpm.e2e/has-build-metadata{,-dep} replace @monorepolint/{core,cli}: the dependency range carries build metadata (^0.5.0-alpha.51+f10fea0), which is what #2928 is about; the hardcoded real-npm integrity becomes getIntegrity(). - The search tests query a hosted fixture (search scans hosted stores only). - Dynamically-suffixed publish names move into the @pnpmtest scope, since exact routes cannot cover generated names. - The vestigial addDistTag('foo') calls in the workspace-protocol tests are dropped; those resolve via workspace:, never the registry. Read-only usages of real npm packages are untouched — they keep proxying. getIntegrity() in the registry-mock helper also learns the proxy cache's post-mounts layout (.pnpr-cache/~public/<digest>/), which the patch tests depend on for proxied is-positive. * fix(registry-mock): re-enumerate proxy-cache namespaces on every getIntegrity retry The ~public namespace directory is created lazily together with the first cached packument, so a candidate list built once before the retry loop could never discover a namespace that appears while the retries are running. * fix(pnpr): align Config::proxy routing with the bundled registry-mock config Route the @pnpm and @zkochan fixture packages by exact name in REGISTRY_MOCK_LOCAL_PATTERNS too, so pacquet's in-process test registry proxies the rest of those real npm scopes exactly like the bundled config.yaml does. Also filter the getIntegrity() proxy-cache namespace enumeration to directories, so a stray file under ~public/ cannot turn a retryable miss into an ENOTDIR error.
Summary
Implements the pnpr side of RFC: Registry mounts for pnpr (pnpm/rfcs#13) as a full replacement of the legacy Verdaccio-shaped model — not an additive layer.
pnpr now models every addressable registry origin as a registry mount exposed at
/~<mount>/: a pnpr-hosted organization registry, a single-origin upstream registry, or a router mapping package-name patterns to exactly one concrete source. The invariant is provenance is declared, never inferred: a package resolves to exactly one declared origin, and no configuration can express a cross-origin fall-through (not on "not found", not on "unavailable"). This closes the dependency-confusion class by construction.Highlights:
mounts:/defaultTarget:is the only routing surface. The legacyuplinks:,packages: proxy:fallback chains, the hosted-first-then-proxy serving path, and the multi-uplink tarball fallback are removed.packages:is now an access-control layer only.**— a duplicate pattern, or a source that is unknown, self-referential, or another router. Decidable because the pattern language (**,@*/*,@scope/*, exact) makes coverage statically decidable.dist.tarballURLs stay canonical for the base the client addressed (path-less vs/~<mount>/) so lockfiles drop them rather than baking in a mount name. The per-package access ACL is enforced centrally on every mount-served read, hosted-org or upstream.orgis the flat root) so two orgs hosting the samename@versioncannot collide. Writes route into the resolved org's namespace; a write routed to an upstream is rejected. The org is threaded through staging, commit, and the publish journal (org-namespaced roll-forward) so crash recovery promotes into the right org. Unauthorized reads of a private hosted org return 404, not 403.~public/<digest>), shared across restarts; a private mount keeps the credential+secret-keyed namespace. The now-unused shared-mirror cache and conditional-GET validator machinery are deleted.config.yamlis the registry-mock shape in mount form (fixture scopes → a flat-namespace hosted org,**→ npmjs), so registry-mock keeps working with no task or fixture-seed changes. The integrated-benchmark cold-mock config is converted to the mount model.pnpr-only by design: the RFC's lockfile registry-identity changes for the TypeScript CLI and pacquet are intentionally out of scope here.
Review-round additions
Landed on this PR during review, on top of the mount model itself:
serve_tarball_via_uplinkinitially loaded and JSON-parsed the whole packument on every tarball request before checking the cache, which regressed cold-store benchmarks ~24%; a cached hit is now served without touching the packument (the bind still runs first when OSV screening is enabled). Benchmarks returned to parity with main.organd every on-disk segment reject:(a Windows drive-relative prefix escapesPathBuf::join); duplicate hosted orgs andpublicupstreams carrying any custom header are rejected.pnpr::serve_timingdiagnostic target. Opt-in per-phase serve profiling (RUST_LOG=pnpr::serve_timing=debug), plus an opt-in--serve-timingflag in the integrated benchmark and a failure-only CI step that dumps install output and aggregates the profile. Off by default so it never skews timed runs.Squash Commit Body
Checklist
config.yamldocuments the mount model).Written by an agent (Claude Code, claude-opus-4-8).
Summary by CodeRabbit