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_attestationstable. Rejected: same cardinality and joins asnpm_files, plus parallel GC, reconcile, and soft-delete arms. Inline JSONB onnpm_versions. Rejected bydocs/dev/database.md's serialized-data rule. - Chosen: subject-and-shape verification, a second
npm_filesrow viaBlobStore.StoreBlob, one nullableattestation_predicate_typecolumn 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 fornpm audit signaturesexiting 1 on a project with no attested packages. - Remote and virtual attestation handling (S15, S31). The remote tier passes upstream
dist.attestationsthrough unrewritten, so pacote rebases the URL onto AR's origin. No issue filed yet.
Related to #1273