Skip to content

rfc(pnpr): integrity-addressed patch revisions - #19

Open
zkochan wants to merge 11 commits into
mainfrom
rfc/pnpr-integrity-addressed-patches
Open

rfc(pnpr): integrity-addressed patch revisions#19
zkochan wants to merge 11 commits into
mainfrom
rfc/pnpr-integrity-addressed-patches

Conversation

@zkochan

@zkochan zkochan commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

This design supersedes #14 (closed in its favor). It contains two companion
RFCs:

  • pnpr/text/0000-integrity-addressed-patch-revisions.md projects provider
    builds over the original name@version. Every artifact is served from an
    immutable, registry-scoped URL keyed by its complete sha512 digest; the
    canonical name/-/name-version.tgz URL stays pinned to the original bytes
    forever. dist.revisions retains an append-only history of every accepted
    artifact — integrity, provider, ordinal, VEX/provenance, and each
    artifact's own resolution-relevant manifest fields.
  • text/0000-integrity-addressed-registry-tarballs.md teaches pnpm the
    lockfile representation and the <version>+rN revision-addressed spec.

The revision field

The ordinal is a plain integer field, present only for replacements:

// registry metadata for a patched version
"dist": {
  "tarball": "https://registry.example/-/tarballs/sha512/<digest>",
  "integrity": "sha512-<patch-digest>",   // standard SRI, no extensions
  "revision": 2
}
# lockfile
packages:
  ejs@2.7.4:            # original — byte-identical to today's format
    resolution:
      integrity: sha512-<original>
  lodash@4.17.21:       # replacement — the field is the signal
    resolution:
      integrity: sha512-<patch-digest>
      revision: 1

Originals are never marked. The canonical URL is pinned to revision zero
by invariant, so integrity-only entries stay correct on every registry —
revision-aware or not — and a lockfile with no adopted patches needs no new
format. Only revision: N entries are gated by lockfile version and fetched
from the digest route (one direct CDN request; falling back to the canonical
URL is impossible by construction, since original bytes cannot match a
replacement's integrity). Registry feature detection (the pnpr handshake) is
advisory only — better diagnostics, optional originals-by-digest optimization
— never load-bearing: the lockfile field alone determines the fetch
convention.

Explicit revision selection: <version>+rN

The ordinal carried as semver build metadata makes a revision-addressed spec,
usable as an override target or a directly declared dependency:

{ "pnpm": { "overrides": { "ejs@2.7.4": "2.7.4+r0" } } }  // keep the original

No new override semantics: exact-version selectors already match intersecting
declared ranges (pnpm/pnpm#13470, retained for npm parity). pnpm resolves the
version half normally, then selects revision N from dist.revisions
using that revision's own manifest fields, since revisions may legally
differ in dependencies. Unknown ordinals fail hard; rewriting applies once;
conflicting revision demands for one name@version are an explicit error.
Registry policy wins: refused bytes fail the install, they are never
substituted.

Important properties

  • npm and Yarn receive registry-selected patches through dist.tarball on
    fresh resolutions; pnpm additionally gets revision management (host-free
    lockfile entries, +rN pinning, pnpm update --patches). Clients that
    reconstruct conventional URLs fail loudly, never silently wrong.
  • Existing lockfiles of every client keep installing exactly what they pin;
    canonical immutability makes that honest instead of breaking them.
  • Provider manifests carry a signed monotonic sequence; replayed older
    documents are rejected, and the checkpoint advances atomically with durable
    acceptance, so rollback requires a fresh manifest or an explicitly
    authorized operator action.
  • Revision allocation serializes on (registry, name, version) with separate
    uniqueness constraints on ordinal and digest; the registry owns the ordinal
    history — provider changes never renumber.
  • The digest endpoint is scoped beneath the addressed registry and authorizes
    against the principal's package-level access; knowledge of a digest is not
    authorization. Full sha512 values are required.
  • Immutability means stable bytes, not availability: policy refusal applies
    identically to canonical and digest routes and requires edge purge or
    non-shared caching.
  • Ordinals are registry-scoped; portability across registry configuration
    guarantees integrity, never identical ordinal meaning.
  • deprecated and other registry-managed mutable metadata stay outside the
    immutable revision records, which carry only artifact-derived fields.

Compatibility

  • Lockfiles without revision entries are byte-identical to the current
    format — no gate, readable by older pnpm.
  • Pointing a lockfile at a registry without the digest endpoint keeps the
    whole unpatched graph installable; only replacement entries fail, as
    genuine unavailability.
  • dist.integrity is a standard SRI value; npm-compatible clients ignore the
    unknown dist.revision field and follow dist.tarball normally.
  • The existing named-registry package-key collision remains a separate
    lockfile-identity problem.

Summary by CodeRabbit

  • Documentation
    • Documented integrity-addressed registry tarballs for immutable artifact retrieval using verified digest URLs.
    • Defined revision metadata, revision-zero handling, and append-only histories for original and replacement artifacts.
    • Specified lockfile behavior for integrity and revision fields, including strict verification and version gating.
    • Added guidance for selecting, pinning, and refreshing revisions with workspace targets and pnpm update --patches.
    • Covered provider ingestion, authorization, auditing, restoration, testing, and open protocol considerations.

@coderabbitai

coderabbitai Bot commented Jul 28, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f68a5f9-1f69-439d-a78d-fa15643f47a1

📥 Commits

Reviewing files that changed from the base of the PR and between e39df66 and 53e2ce1.

📒 Files selected for processing (2)
  • pnpr/text/0000-integrity-addressed-patch-revisions.md
  • text/0000-integrity-addressed-registry-tarballs.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • pnpr/text/0000-integrity-addressed-patch-revisions.md
  • text/0000-integrity-addressed-registry-tarballs.md

📝 Walkthrough

Walkthrough

The PR adds two RFCs for immutable, registry-scoped digest tarballs and revision metadata. They define provider ingestion, projected revision history, pnpm lockfile and fetch behavior, revision selection, compatibility checks, refresh operations, and audit rules.

Changes

Integrity-addressed revisions

Layer / File(s) Summary
pnpr protocol and projected metadata
pnpr/text/0000-integrity-addressed-patch-revisions.md
Defines immutable artifact identity, signed provider manifests, digest URLs, projected metadata, revision history, and canonical tarball behavior.
Revision lifecycle and audit model
pnpr/text/0000-integrity-addressed-patch-revisions.md
Defines revision refresh, atomic projection, ordinal allocation, authorization, retention, caching, auditing, implementation tasks, and tests.
pnpm resolution and digest fetching
text/0000-integrity-addressed-registry-tarballs.md
Defines metadata validation, lockfile representation, digest URL hydration, registry identity handling, strict SRI verification, and offline installation.
Historical compatibility and revision refresh
text/0000-integrity-addressed-registry-tarballs.md
Defines historical verification, older-client gating, revision overrides, conflicts, atomic refresh, implementation requirements, tests, alternatives, prior art, and open questions.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Provider
  participant pnprRegistry
  participant RegistryPackument
  participant pnpmResolver
  participant Lockfile
  Provider->>pnprRegistry: submit signed artifact revision
  pnprRegistry->>RegistryPackument: publish verified revision metadata
  pnpmResolver->>RegistryPackument: resolve name@version and revision
  pnpmResolver->>Lockfile: record integrity and revision
  pnpmResolver->>pnprRegistry: fetch registry-scoped digest URL
  pnprRegistry-->>pnpmResolver: return immutable tarball bytes
  pnpmResolver->>Lockfile: verify SRI and install selected revision
Loading

Poem

A rabbit checks each digest byte,
And marks the revisions right.
The lockfile keeps the chosen hay,
While immutable paths lead the way.
Patch refreshes hop in line.

🚥 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 clearly and concisely describes the RFC for integrity-addressed patch revisions, which is the main change in the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rfc/pnpr-integrity-addressed-patches

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.

@zkochan
zkochan marked this pull request as ready for review July 28, 2026 16:15
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

RFC: Integrity-addressed patch revisions and pnpm fetching

📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Proposes immutable, registry-scoped digest URLs for original and patched package artifacts.
• Encodes stable artifact revisions in SRI options while preserving package identity.
• Defines pnpm lockfile fetching, verification, compatibility, and revision-refresh behavior.
Diagram

graph TD
  Provider["Patch Provider"] -->|signed artifact| Registry["pnpr Registry"] -->|stores by digest| Store[("Digest Store")]
  Registry -->|projects revision| Metadata["Package Metadata"] -->|marked integrity| Resolver["pnpm Resolver"] -->|pins revision| Lockfile["pnpm Lockfile"] -->|digest request| Registry
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Provider package aliases
  • ➕ Uses established package substitution and immutable publication semantics.
  • ➕ Makes provider identity explicit in consumer dependency metadata.
  • ➖ Leaks registry policy into lockfiles, SBOMs, peer resolution, and runtime metadata.
  • ➖ Requires provider-specific registry configuration and pnpm overrides or annotations.
2. Store digest tarball paths
  • ➕ Makes the fetch convention explicit without extending SRI interpretation.
  • ➕ Can remain portable when represented as a registry-relative path.
  • ➖ Duplicates the algorithm and digest already contained in integrity.
  • ➖ Adds lockfile surface that must remain synchronized with the SRI value.
3. Header-selected revisions
  • ➕ Retains one recognizable canonical package URL.
  • ➕ Could select historical content without introducing a new URL route.
  • ➖ Requires custom CDN forwarding and high-cardinality cache variation or redirects.
  • ➖ Makes one URL represent multiple bodies and adds deployment complexity.

Recommendation: Prefer the proposed digest-addressed design if pnpm and pnpr can safely preserve raw empty SRI options and gate the lockfile format. It best preserves name/version semantics, immutable cache keys, registry-scoped authorization, and single-request installs; provider aliases remain the strongest fallback if extending SRI semantics proves incompatible with ecosystem tooling.

Files changed (2) +1227 / -0

Documentation (2) +1227 / -0
0000-integrity-addressed-patch-revisions.mdSpecify pnpr artifact revision projection and storage +724/-0

Specify pnpr artifact revision projection and storage

• Adds the registry-side RFC for ingesting provider artifacts, assigning stable revisions, and serving complete-digest URLs under the original package identity. It also defines append-only provenance history, authorization boundaries, compatibility guarantees, refresh atomicity, and audit behavior.

pnpr/text/0000-integrity-addressed-patch-revisions.md

0000-integrity-addressed-registry-tarballs.mdSpecify pnpm revision-aware integrity fetching +503/-0

Specify pnpm revision-aware integrity fetching

• Adds the companion pnpm RFC defining SRI revision markers, compact lockfile storage, digest URL construction, and response verification. It covers legacy lockfiles, lockfile format gating, registry path preservation, historical verification, and atomic patch-revision refreshes.

text/0000-integrity-addressed-registry-tarballs.md

@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: 6

🤖 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/text/0000-integrity-addressed-patch-revisions.md`:
- Around line 181-204: The “Provider input remains integrity-pinned”
specification must define anti-rollback enforcement for signed patch manifests.
Add a persisted, signed monotonic checkpoint for each provider identity and
require pnpr to reject manifests selecting revisions older than the checkpoint;
document an explicit, authorized rollback mechanism and its verification
requirements.
- Around line 491-505: Update the provider refresh sequence to make `(registry,
name@version, digest)` revision allocation unique and idempotent: claim or reuse
an existing digest mapping within a transaction/unique constraint before
assigning an ordinal, and append revision metadata plus select the mapping
atomically. Ensure concurrent refreshes and retries reuse the same stable
ordinal rather than allocating duplicates.
- Around line 282-292: Update the public CDN caching and policy sections
describing successful immutable responses, including the related sections on
revocation and cache behavior, to define authorization-aware edge enforcement
and purge/invalidation when a revision is blocked. If immediate revocation on
warm immutable hits cannot be guaranteed, weaken the policy wording to state the
actual propagation behavior instead of promising an immediate 403.
- Around line 157-171: Clarify the immutability and access-policy rules in the
RFC sections covering tarball URLs, historical artifact screening, and legacy
lockfiles: define immutability as successful responses returning unchanged
bytes, specify whether vulnerability screening applies to canonical URLs as well
as integrity URLs, and document the response legacy lockfiles receive when
access is denied.

In `@text/0000-integrity-addressed-registry-tarballs.md`:
- Around line 207-209: Restrict the registry-base portability guarantee and its
tests to hostname aliases or deployment moves of the same logical registry.
Update the digest URL resolution and hydration behavior described in the
registry portability section, including the identity gap noted around the
lockfile discussion, so changing to an unrelated configured registry is not
treated as equivalent; alternatively persist and validate registry identity
before hydration.
- Around line 312-316: Define the lockfile version gate for revision-aware
integrity and the `?vN` representation, including when pnpm emits it and how
hydrated lockfile parsing rejects it in older clients. Update the affected
proposal sections consistently and add a compatibility test verifying older
clients fail fast on the gated lockfile rather than reconstructing the canonical
`dist.tarball` URL.
🪄 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: 39f74957-4168-4ccf-809c-ebc7cea26467

📥 Commits

Reviewing files that changed from the base of the PR and between 413a998 and 3af3d0f.

📒 Files selected for processing (2)
  • pnpr/text/0000-integrity-addressed-patch-revisions.md
  • text/0000-integrity-addressed-registry-tarballs.md
📜 Review details
🧰 Additional context used
🪛 LanguageTool
pnpr/text/0000-integrity-addressed-patch-revisions.md

[grammar] ~465-~465: Ensure spelling is correct
Context: ...is longer by one sha512 digest. Patched packuments contain a small revision list. Both cos...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~481-~481: This phrase is redundant. Consider writing “same”.
Context: ...ked registry package, pnpm resolves the same exact name@version. If dist.integrity cha...

(SAME_EXACT)


[grammar] ~506-~506: Ensure spelling is correct
Context: ...validate projected full and abbreviated packuments. No tarball cache object is invalidated o...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~518-~518: To elevate your writing, try using an alternative expression here.
Context: ... returns a policy-specific 403. This matters for a vulnerable original. A reproducib...

(MATTERS_RELEVANT)

text/0000-integrity-addressed-registry-tarballs.md

[grammar] ~145-~145: Use a hyphen to join words.
Context: ...egrity`; 4. the digest is complete, well formed, and unambiguous; 5. the selected...

(QB_NEW_EN_HYPHEN)


[style] ~349-~349: This phrase is redundant. Consider writing “same”.
Context: ... pnpm resolves current metadata for the same exact name@version. If dist.integrity cha...

(SAME_EXACT)

🔇 Additional comments (2)
pnpr/text/0000-integrity-addressed-patch-revisions.md (1)

1-156: LGTM!

Also applies to: 172-180, 205-281, 293-408, 428-449, 470-490, 523-690, 691-725

text/0000-integrity-addressed-registry-tarballs.md (1)

1-206: LGTM!

Also applies to: 210-266, 278-311, 318-475, 477-499

Comment thread pnpr/text/0000-integrity-addressed-patch-revisions.md Outdated
Comment thread pnpr/text/0000-integrity-addressed-patch-revisions.md
Comment thread pnpr/text/0000-integrity-addressed-patch-revisions.md
Comment thread pnpr/text/0000-integrity-addressed-patch-revisions.md
Comment thread text/0000-integrity-addressed-registry-tarballs.md
Comment thread text/0000-integrity-addressed-registry-tarballs.md Outdated
Replace the empty-option/`?vN` markers with explicit `?r0`/`?rN` ordinals:
`r` abbreviates *registry revision*, matches `dist.revisions` numbering,
and follows Alpine/Gentoo `-rN` precedent, while an always-non-empty
option removes the raw-SRI-parsing fragility.

Specify workspace revision selection through ordinary overrides whose
targets carry the ordinal as semver build metadata (`<version>+rN`):
`+r0` pins the original, positive ordinals adopt or freeze replacements,
unknown ordinals fail hard, and registry policy 403s still win.

Require each `dist.revisions` entry to carry a `manifest` object with the
abbreviated-packument field subset, derived from the verified tarball and
never provider-declared, so a pinned non-selected revision resolves with
its own dependency metadata.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
pnpr/text/0000-integrity-addressed-patch-revisions.md (1)

328-332: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Define revision-reference equality separately from artifact equality.

Line 328 says the ?rN option is excluded from equality, while selection still depends on the digest plus ordinal and dist.revisions[] advertises each ordinal as a distinct artifact. Document that canonical digest equality ignores options, but resolving or checking ?rN must match the canonical dist.revisions[].revision for that integrity, and reject or canonicalize forged mismatched ordinals.

🤖 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/text/0000-integrity-addressed-patch-revisions.md` around lines 328 -
332, Clarify the revision semantics in the document: canonical artifact digest
equality must ignore the ?rN option, while resolving or validating a revision
reference must match the canonical dist.revisions[].revision for the
corresponding integrity digest. Specify that forged or inconsistent ordinals are
rejected or canonicalized, and keep option-only changes from creating new stored
artifacts.
🤖 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/text/0000-integrity-addressed-patch-revisions.md`:
- Line 718: Resolve the ordinal contract before retaining the conformance
requirement: explicitly require stable monotonic rN ordinals across provider
changes, or define the migration behavior that applies when providers change.
Update the surrounding unresolved questions and the conformance statement for
the first replacement so they consistently reflect the chosen rule.
- Around line 509-514: Update the revision-override handling for +rN selections
to persist the addressed registry together with the package identity and pinned
ordinal. When resolving or applying an override, require the stored registry and
name@version to match the current package before using the revision; ensure pnpm
update --patches recognizes these workspace overrides as pinned and does not
refresh them.

In `@text/0000-integrity-addressed-registry-tarballs.md`:
- Around line 149-155: Update the integrity-validation wording to compare the
decoded URL digest with the decoded payload from dist.integrity after removing
its canonical rN option, while retaining the sha512- algorithm prefix handling.
Clarify that literal comparison against the full integrity string is invalid
because the URL contains only the digest.

---

Outside diff comments:
In `@pnpr/text/0000-integrity-addressed-patch-revisions.md`:
- Around line 328-332: Clarify the revision semantics in the document: canonical
artifact digest equality must ignore the ?rN option, while resolving or
validating a revision reference must match the canonical
dist.revisions[].revision for the corresponding integrity digest. Specify that
forged or inconsistent ordinals are rejected or canonicalized, and keep
option-only changes from creating new stored artifacts.
🪄 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: d0504695-6831-4e86-b826-b5927fe1d04b

📥 Commits

Reviewing files that changed from the base of the PR and between 3af3d0f and e84a7c4.

📒 Files selected for processing (2)
  • pnpr/text/0000-integrity-addressed-patch-revisions.md
  • text/0000-integrity-addressed-registry-tarballs.md
📜 Review details
🧰 Additional context used
🪛 LanguageTool
pnpr/text/0000-integrity-addressed-patch-revisions.md

[style] ~648-~648: Consider an alternative for the overused word “exactly”.
Context: ...ements. Rejected: - an empty option is exactly the state a generic SRI library is most...

(EXACTLY_PRECISELY)

text/0000-integrity-addressed-registry-tarballs.md

[style] ~596-~596: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...n policy for pnpm update --patches? - What should the global posture setting that ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🔇 Additional comments (2)
pnpr/text/0000-integrity-addressed-patch-revisions.md (1)

19-21: LGTM!

Also applies to: 24-27, 31-33, 94-96, 118-123, 146-147, 164-166, 266-267, 308-327, 334-337, 357-388, 409-424, 458-462, 643-659, 705-706, 716-717, 719-724, 766-767

text/0000-integrity-addressed-registry-tarballs.md (1)

367-372: 🗄️ Data Integrity & Integration

Specify verification of revision manifests before using them for resolution.

dist.revisions[N].manifest becomes authoritative for dependencies, peers, engines, and install scripts, but the fetch steps only verify tarball bytes against the digest. Explicitly require the companion manifest-verification contract—including its binding to the selected name, version, revision, and digest—and fail before subtree resolution when that metadata is stale or mismatched.

Also applies to: 546-548

Comment thread pnpr/text/0000-integrity-addressed-patch-revisions.md Outdated
Comment thread pnpr/text/0000-integrity-addressed-patch-revisions.md Outdated
Comment thread text/0000-integrity-addressed-registry-tarballs.md Outdated
zkochan and others added 3 commits July 28, 2026 20:04
Exact-version override selectors already match intersecting declared
ranges in current pnpm (pnpm/pnpm#13470, retained for npm parity), so no
resolver semantics change is needed. <version>+rN becomes a plain
revision-addressed spec — usable as an override target or a directly
declared dependency — resolved by picking the version normally, then
selecting the revision from dist.revisions. State the version-pinning
trade-off and the npm degradation (pin without revision selection)
honestly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Provider manifests carry a signed monotonic sequence; pnpr rejects
  replayed lower-sequence documents, so selection rollback requires a
  fresh manifest or an explicitly authorized operator action.
- Revision allocation is transactional: (registry, name, version,
  digest) maps to exactly one ordinal, idempotent under retries.
- Immutability means stable bytes, not availability: policy refusal
  requires edge purge or non-shared caching, applies identically to
  canonical and digest routes, and the digest route authorizes against
  the principal's package-level access.
- Ordinals are documented as registry-scoped; portability guarantees
  integrity, not identical ordinal meaning across registries.
- Override rewriting applies once (no chains or cycles), and
  conflicting revision demands for one name@version fail explicitly.
- deprecated is registry-managed mutable metadata, excluded from the
  artifact-derived revision manifest subset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Allocation serializes on (registry, name, version) with separate
  uniqueness constraints on the ordinal and the digest mapping, so
  concurrent refreshes with different digests cannot share an ordinal.
- The registry owns the ordinal history: provider renames, replacements,
  or removals never renumber existing revisions (previously an open
  question, now normative since rN pins are a public API).
- The provider sequence checkpoint advances atomically with durable
  manifest acceptance, keeping a crashed sequence retryable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ordinal moves out of the integrity value: dist.revision on the
registry side and resolution.revision in the lockfile, present only for
replacements (N >= 1). Originals are never marked — the canonical URL is
pinned to revision zero by invariant, so plain integrity-only entries
stay correct on every registry, a lockfile with no adopted patches is
byte-identical to today's format, and only revision entries need the
lockfile-version gate.

dist.integrity returns to a standard SRI value that strict consumers
parse. Registry feature detection (the pnpr handshake) is specified as
advisory only — diagnostics and an optional originals-by-digest
optimization — never load-bearing for the fetch convention, which the
lockfile field alone determines. Digest-route tarballs without
dist.revision are normalized to integrity-only entries so hostnames
never enter the lockfile.

Document that npm/yarn patch delivery rides dist.tarball (unchanged by
canonical immutability), that convention-reconstructing clients fail
loudly rather than silently, and add a client-matrix verification
question. The SRI-option and mark-everything designs move to rejected
alternatives; +rN spec syntax is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
pnpr/text/0000-integrity-addressed-patch-revisions.md (1)

203-218: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Constrain provider artifact fetches before download.

entries.*.tarball is an arbitrary server-side URL, but the RFC does not restrict its origin, redirects, DNS rebinding, or private-network targets. A signed or compromised provider manifest could therefore turn refresh into SSRF. Require HTTPS provider-origin allowlists, private-IP/egress checks, redirect validation, and ensure provider credentials are never forwarded to artifact hosts.

🤖 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/text/0000-integrity-addressed-patch-revisions.md` around lines 203 -
218, The artifact download flow described for manifest entries.*.tarball must
enforce SSRF protections before fetching: require HTTPS URLs from an approved
provider-origin allowlist, reject private or otherwise disallowed resolved
addresses and egress targets, validate every redirect against the same rules,
and never forward provider credentials to artifact hosts. Apply these checks
before download and preserve complete integrity verification afterward.
🤖 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/text/0000-integrity-addressed-patch-revisions.md`:
- Around line 438-443: Update the abbreviated historical metadata specification
to retain each history entry’s integer revision ordinal alongside its integrity,
digest URL, and manifest fields. Ensure +rN resolution can map N to the correct
historical record, and add a focused test covering revision ordinals in
abbreviated packuments.
- Around line 438-443: Define a transactional carry-forward/merge rule for
revision swaps so projected version entries preserve registry-managed mutable
fields, including deprecated, when refreshed. Update the revision
refresh/projection process described in the metadata rules, keeping immutable
historical provenance separate while merging existing mutable metadata into the
replacement entry.
- Around line 220-230: Update the per-provider sequence checkpoint logic to
persist both the highest accepted sequence and its canonical manifest digest.
Treat an exact replay of the same sequence and digest as an idempotent no-op,
but reject any manifest using the same sequence with a different digest as
equivocation; retain advancement only for higher sequences and keep checkpoint
updates atomic with durable manifest acceptance.
- Around line 334-346: Define and document a single validation policy for
dist.revision across registry and client validation, covering absent,
non-integer, out-of-range, and mismatched values. Ensure full and abbreviated
packuments apply the same policy, and explicitly reconcile malformed-value
rejection with structured revision metadata being authoritative; update the
relevant test matrix and rules accordingly.

In `@text/0000-integrity-addressed-registry-tarballs.md`:
- Around line 376-381: Make revision refresh graph-transactional: when updating
the resolved entry’s revision and manifest, re-resolve the entire affected
dependency graph using the refreshed revision metadata, including dependency and
peer edges, engines, binaries, and install-script state. Commit all graph,
snapshot, and peer-resolution changes atomically, and roll back the refresh if
re-resolution produces conflicts. Add coverage for a revision whose dependency
metadata changes, including the corresponding lines around the revision refresh
logic.
- Around line 207-225: Update the revisioned digest tarball fetch requirements
at this section and the corresponding sections around lines 612 and 642 to
reject every 3xx response as a hard failure, not only redirects crossing
origins. Preserve the single authenticated GET behavior and prohibit following
same-origin or cross-origin redirects.

---

Outside diff comments:
In `@pnpr/text/0000-integrity-addressed-patch-revisions.md`:
- Around line 203-218: The artifact download flow described for manifest
entries.*.tarball must enforce SSRF protections before fetching: require HTTPS
URLs from an approved provider-origin allowlist, reject private or otherwise
disallowed resolved addresses and egress targets, validate every redirect
against the same rules, and never forward provider credentials to artifact
hosts. Apply these checks before download and preserve complete integrity
verification afterward.
🪄 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: 92882297-ba60-41e2-9dfc-8fc8fe0a3e60

📥 Commits

Reviewing files that changed from the base of the PR and between e84a7c4 and 5613a6f.

📒 Files selected for processing (2)
  • pnpr/text/0000-integrity-addressed-patch-revisions.md
  • text/0000-integrity-addressed-registry-tarballs.md
📜 Review details
🧰 Additional context used
🪛 LanguageTool
text/0000-integrity-addressed-registry-tarballs.md

[grammar] ~137-~137: Use a hyphen to join words.
Context: ...egrity; 4. the digest is complete, well formed, and unambiguous; 5. dist.revisi...

(QB_NEW_EN_HYPHEN)

🔇 Additional comments (6)
pnpr/text/0000-integrity-addressed-patch-revisions.md (2)

1-14: LGTM!

Also applies to: 26-58, 62-138, 141-192, 232-263, 265-283, 290-333, 347-410, 424-437, 445-505, 507-552, 554-604, 606-735, 737-758, 775-796, 798-835


15-22: 🗄️ Data Integrity & Integration

Manual verification needed for revision selection rules.

The excerpt raises a plausible concern about inconsistent keying between dist.tarball encoding, multiple SRI hash expressions, missing dist.revision, and absent revision handling, but the draft context is needed before ruling this as actionable.

text/0000-integrity-addressed-registry-tarballs.md (4)

130-138: Compare the URL digest with the SRI payload, not the full SRI token.

dist.integrity contains sha512-<base64>, while the URL contains only digest bytes. Define validation as decoding the SRI payload after removing the algorithm prefix and comparing those bytes with the decoded base64url path digest.


315-319: Specify the concrete lockfile gate.

The RFC requires revision fields to be gated but still leaves the lockfile version and emission/rejection behavior unresolved. Define the exact version, writer behavior, hydrated-lockfile rejection, and mixed-version compatibility matrix.

Also applies to: 606-608, 676-678


151-161: 🗄️ Data Integrity & Integration | ⚡ Quick win

Define the ordinal’s numeric bounds and canonical encoding.

“Positive integer” is insufficient for a JSON/JavaScript protocol: without a safe upper bound or canonical decimal-string representation, large ordinals can round or compare inconsistently and break revision selection. Add overflow, leading-zero, and maximum-value tests.

Also applies to: 636-637


357-368: 🎯 Functional Correctness

State that +rN overrides need new override behavior.

The linked pnpm issue is not publicly available as a stable contract here, so this should not present pnpm’s existing override behavior as sufficient if the feature allows a version-pinning override to force a selected version regardless of the declared range.

Comment on lines +220 to +230
The signed document carries a monotonic `sequence`. pnpr persists the highest
sequence it has accepted per provider and rejects any manifest whose sequence
is not greater: a valid signature over an older document must not be able to
replay earlier selections and reselect a vulnerable revision. Moving selection
backward — withdrawing a bad patch, restoring the original — therefore always
requires either a new, higher-sequence manifest from the provider or an
explicit, separately authorized operator operation, never a replayed
document. The checkpoint advances atomically with — never before — the
durable acceptance of the manifest's work: a crash mid-ingestion leaves the
previous checkpoint in place, so the same sequence can be retried rather
than becoming permanently unacceptable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make sequence checkpoints idempotent and detect equivocation.

Persist the highest sequence together with the canonical manifest digest. An exact replay of an accepted (sequence, digest) should be a no-op, while the same sequence with different contents must be rejected. The current sequence > highest rule makes successful refreshes appear invalid after timeout/retry and cannot distinguish duplicate delivery from provider equivocation.

🤖 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/text/0000-integrity-addressed-patch-revisions.md` around lines 220 -
230, Update the per-provider sequence checkpoint logic to persist both the
highest accepted sequence and its canonical manifest digest. Treat an exact
replay of the same sequence and digest as an idempotent no-op, but reject any
manifest using the same sequence with a different digest as equivocation; retain
advancement only for higher sequences and keep checkpoint updates atomic with
durable manifest acceptance.

Comment on lines +334 to +346
pnpr applies these rules:

- it emits `dist.revision` exactly when the selected artifact is a
replacement (`N ≥ 1`); originals serve unmarked metadata;
- it assigns `1` to the first distinct accepted replacement and monotonically
increasing values to later replacements;
- it never uses the provider name or provider revision in the field;
- it preserves ordinals in `dist.revisions`, audit output, and logs;
- it excludes the field from object lookup, equality, authorization, VEX, and
integrity verification;
- it treats structured revision metadata as authoritative if the field is
forged or inconsistent;
- a field-only change cannot create a new stored artifact.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Define one response to invalid dist.revision values.

This section says structured history is authoritative when dist.revision is forged or inconsistent, while the test matrix requires malformed values to be rejected. Define consistent behavior for absent, non-integer, out-of-range, and mismatched values across registry and client validation; otherwise full and abbreviated packuments may resolve differently.

Also applies to: 762-774

🤖 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/text/0000-integrity-addressed-patch-revisions.md` around lines 334 -
346, Define and document a single validation policy for dist.revision across
registry and client validation, covering absent, non-integer, out-of-range, and
mismatched values. Ensure full and abbreviated packuments apply the same policy,
and explicitly reconcile malformed-value rejection with structured revision
metadata being authoritative; update the relevant test matrix and rules
accordingly.

Comment on lines +438 to +443
additionally carry all provenance fields. Abbreviated metadata should keep
current `dist` plus the integrity, digest URL, and `manifest` of historical
revisions, so revision pinning works from the metadata pnpm actually
fetches. An unpatched version does not need a `dist.revisions` array or a
`dist.revision` field; it serves ordinary metadata, and its `dist.tarball`
may use the canonical or the digest URL.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Retain the ordinal in abbreviated historical metadata.

+rN resolution needs the mapping from N to a history record, but this requirement lists only historical integrity, URL, and manifest fields and omits revision. Retain the integer ordinal with every historical entry, and test this specifically in abbreviated packuments.

🤖 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/text/0000-integrity-addressed-patch-revisions.md` around lines 438 -
443, Update the abbreviated historical metadata specification to retain each
history entry’s integer revision ordinal alongside its integrity, digest URL,
and manifest fields. Ensure +rN resolution can map N to the correct historical
record, and add a focused test covering revision ordinals in abbreviated
packuments.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve mutable projected metadata during revision swaps.

The RFC excludes deprecated from immutable history and stores it on the projected version, but revision refreshes replace the entire projected version entry. Define a transactional carry-forward/merge rule for deprecated and all other registry-managed mutable fields; otherwise patch refreshes can erase existing policy metadata.

🤖 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/text/0000-integrity-addressed-patch-revisions.md` around lines 438 -
443, Define a transactional carry-forward/merge rule for revision swaps so
projected version entries preserve registry-managed mutable fields, including
deprecated, when refreshed. Update the revision refresh/projection process
described in the metadata rules, keeping immutable historical provenance
separate while merging existing mutable metadata into the replacement entry.

Comment on lines +207 to +225
For an entry carrying `revision: N`, pnpm:

1. converts the complete base64 digest to base64url;
2. resolves `-/tarballs/<algorithm>/<digest>` against the effective registry
base;
3. sends one ordinary authenticated GET;
4. verifies the response against the complete SRI digest before admitting it
to the content-addressed store.

If URL construction fails or the response does not match, installation fails.
pnpm must not fetch current package metadata, change the checksum, retry the
canonical URL, or fall forward to another revision. Falling back to the
canonical URL can never succeed anyway — it serves revision zero, whose bytes
cannot match a replacement's integrity — so the failure surfaces immediately
rather than after a wasted download.

The endpoint is same-origin with the configured registry, so ordinary registry
authentication and credential-scoping behavior applies. Redirects to another
origin are not part of this protocol. Knowledge of a digest is not

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'text/0000-integrity-addressed-registry-tarballs.md' . || true
echo "== file exists? =="
test -f text/0000-integrity-addressed-registry-tarballs.md && echo yes || echo no

echo "== relevant ranges =="
sed -n '195,230p' text/0000-integrity-addressed-registry-tarballs.md
echo "== nearby line counts =="
wc -l text/0000-integrity-addressed-registry-tarballs.md
echo "== references around 600-650 =="
sed -n '590,655p' text/0000-integrity-addressed-registry-tarballs.md
echo "== search redirect/tarball terms =="
rg -n "redirect|3xx|30[12]|tarballs|one GET|ordinary GET|credential|auth|same-origin|fallback|fall" text/0000-integrity-addressed-registry-tarballs.md

Repository: pnpm/rfcs

Length of output: 7610


Reject all redirects for revisioned digest tarballs.

The protocol says “one ordinary authenticated GET” and “no redirect”, but the text only explicitly requires cross-origin redirect rejection. Require that every 3xx response during this fetch is a hard failure, including same-origin redirects, since following redirects can expose different registry behavior/keys/routes while still claiming “one GET.”

Also applies to lines 612 and 642.

🤖 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 `@text/0000-integrity-addressed-registry-tarballs.md` around lines 207 - 225,
Update the revisioned digest tarball fetch requirements at this section and the
corresponding sections around lines 612 and 642 to reject every 3xx response as
a hard failure, not only redirects crossing origins. Preserve the single
authenticated GET behavior and prohibit following same-origin or cross-origin
redirects.

Comment on lines +376 to +381
2. selects registry revision `N` from the resolved entry's `dist.revisions`
instead of the selected `dist`: its integrity, its digest URL, and its
`manifest` record. Revisions may legally differ in dependencies, optional
and peer dependencies, `bin`, engines, and install-script posture, so the
pinned entry's own `manifest` drives subtree resolution — never the current
projected version's top-level fields.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make revision refresh graph-transactional.

Because revision manifests may change dependencies, peers, engines, binaries, or scripts, updating only integrity, revision, and one snapshot can leave stale graph edges or peer resolutions. Re-resolve the affected graph atomically and roll back on conflicts; add a test where a revision changes dependency metadata.

Also applies to: 450-455

🤖 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 `@text/0000-integrity-addressed-registry-tarballs.md` around lines 376 - 381,
Make revision refresh graph-transactional: when updating the resolved entry’s
revision and manifest, re-resolve the entire affected dependency graph using the
refreshed revision metadata, including dependency and peer edges, engines,
binaries, and install-script state. Commit all graph, snapshot, and
peer-resolution changes atomically, and roll back the refresh if re-resolution
produces conflicts. Add coverage for a revision whose dependency metadata
changes, including the corresponding lines around the revision refresh logic.

@jordi-heller

Copy link
Copy Markdown

pnpm might start supporting vendors like echo, that upload multiple releases that resolve to the same npm version (but differ in tarball and integrity). a feature to consider is the ability to refresh a lockfile to get the newest release while keeping the same version. Since a query of the version would only return the release the registry exposes there is no decision logic to be made by pnpr/pnpm, it should probably only query each version in the lockfile directly and replace the entry if it's different. pnpm has a feature that does something similar (npm install --fix-lockfile --lockfile-only), but what could be added is restriction to only update the hash if it belongs to a pre configured vendor etc

Versions such as `2.7.4-echo.2` are prereleases under semver. Ordinary ranges
containing `2.7.4` do not necessarily select them, peer ranges may reject them,
and the package no longer reports the version the application was tested
against. Build metadata is unsuitable because semver ignores it for precedence

@jordi-heller jordi-heller Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There's a possibility of using build metadata. npm clients will ignore it, thereby seeing it as just the regular version, but the registry itself can be organized to serve the newest revision whenever its published. This (mostly) will not break any old lockfile as long as the urls of different versions are different (you can encode them with pkg-version+vendor.N.tgz for example). If the old urls stay on the registry users can always get the latest version when they ask for it, and they will not break if they're lockfile is out of date.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Right, we do leverage the build metadata in this rfc for explicitly specifying which revision to install via overrides. For the tarball URL I have suggested to use the integrity checksum of the tarball. Alternatively we could consider adding the revision number to the url as you suggest.

@zkochan

zkochan commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

a feature to consider is the ability to refresh a lockfile to get the newest release while keeping the same version.

sure, such feature would make sense. The current architecture would make it slow though. We'd have to read the whole package document for every package found in the lockfile. I wonder if there is a better way.

zkochan and others added 2 commits July 31, 2026 02:11
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SRI-option rejection asserted that strict consumers may reject options.
The real failure is worse: pnpm's Rust ssri has no option grammar, so it
absorbs the option into the digest, round-trips it through Display, and fails
far from the parse. Integrity-string equality is also byte equality across
pnpm, so an ordinal inside the value would make a revision-only difference
read as a content change.

Record encoding the ordinal in the lockfile package key as an alternative,
since the registry-qualified keys of pnpm/pnpm#13528 invite it. It is rejected
on churn: a dep path repeats once per dependent, so one revision bump would
rewrite roughly fifty lines for a package as common as semver, and
patchedDependencies already demonstrates this with patch_hash. The key may
carry extra data; it may not carry data designed to change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants