docs(specs): amend S11 with npm provenance attestations

Summary

S11 mandates exactly one _attachments entry, so a hosted npm publish --provenance fails with 400. Full support for GA is npm hosted: publish with --provenance is refuse... (#1273) • Hayley Swimelar. No step can open while the spec mandates the refusal. The amendment defines the target: admit the .sigstore entry under a memory bound, check its shape and its subject digest against the tarball, refuse keyed attestations, store it as a second npm_files row, and serve dist.attestations plus GET .../-/npm/v1/attestations/{name}@{version}.

No sigstore dependency. Measured: a keyless bundle from a registry with no /-/npm/v1/keys makes npm audit signatures report a verified attestation, so the client does the cryptography. Full verification is npm hosted: verify the signature and certificat... (#1276) • Unassigned. The closed-beta refusal stands until the admit step lands, per npm hosted: decide how a provenance publish's .... (#1156 - closed) • Hayley Swimelar.

Correction to the issue: GitLab CI emits predicateType slsa.dev/provenance/v0.2, not v1, so the spec reads the type from the bundle.

S11's status moves from Implemented to In Progress, and its README row follows: the amendment adds acceptance criteria nothing meets yet, which spec-status-changes rules out of Implemented.

Plan MR and step MRs follow. The amendment lands whole at +672/−61 rather than split: a spec that half-defines the feature misleads the implementer more than a long one, and every section touched is the same decision seen from a different table.

Governing ADRs

ADR-009: its npm route list lacks the attestations route. The route keeps the slug-anchored grammar. The spec records the gap beside the route contract and defers the ADR edit to a handbook amendment, the path S11's three -rev/{rev} routes and its audit routes took before landing in ADR 009. No handbook MR yet.

ADR-002, ADR-004, ADR-007, ADR-008, ADR-011, ADR-023, ADR-025: conform. One nullable npm_versions column, a second npm_files row (ADR-007 describes the table as holding auxiliary files), a size cap mapped into S11 Configuration per the per-format rule, StoreBlob rather than a second staging path, and a blob in the existing reference-counted GC model.

Testing

Docs only: markdownlint-cli2, Vale at error level, and lychee (offline, fragments) pass on all three files. Eight e2e rows replace e2e.npm.publish.reject-two-attachments, not started until the step MRs land.

Context for LLM agents

Rationale

  • Full sigstore verification (sigstore-go). Rejected: 46 net-new modules, +23.9 MB binary, and a TUF trust root AR must pin and rotate (the embedded one has expired). CPU is not the reason: about 2 ms per verify.
  • Store with no check. Rejected: a subject mismatch is the failure the client reports worst, and it is the check pacote itself performs.
  • Dedicated npm_attestations table. Rejected: same cardinality and joins as npm_files, plus parallel GC, reconcile, and soft-delete arms. Inline JSONB on npm_versions. Rejected by docs/dev/database.md's serialized-data rule.
  • Chosen: subject-and-shape verification, a second npm_files row via BlobStore.StoreBlob, one nullable attestation_predicate_type column so the render never opens the blob store.

Consequences: NpmPublishCommitInput.TarballSize's one-file invariant goes. The bounded escape-aware reader for the .sigstore data string is the largest step (700 to 1000 LOC): jsontext holds 3 to 4 times the string in heap and WithByteLimit is unavailable.

Non-goals

  • Registry signing (dist.signatures, /-/npm/v1/keys). Needs a key and rotation. It is the only fix for npm audit signatures exiting 1 on a project with no attested packages.
  • Remote and virtual attestation handling (S15, S31). The remote tier passes upstream dist.attestations through unrewritten, so pacote rebases the URL onto AR's origin. No issue filed yet.

🤖 Generated with Claude Code

Related to #1273

Edited by Hayley Swimelar

Merge request reports

Loading
Loading