feat(npm)!: Implement final npm provenance contract per ADR 0103-0112

Merged
YU
3d ago
+16608-4111

Summary

Implements the final npm SLSA provenance contract decided in ADR 0103-0112 (the issue #143 provenance-compatibility follow-up). The branch carries the accepted ADR set, the spec revisions (docs/architecture/), and the trusted-core implementation in SDD order: specification commits land before the implementation commits that realize them.

What changed:

  • Invocation identity (ADR 0103, 0104): externalParameters.workflow records exactly the called workflow's {ref, repository, path}; the legacy environment snapshots leave the signed record. internalParameters.github is a closed triple of event_name, repository_id, repository_owner_id, bound to the OIDC Build Trigger and Build Config URI claims (certificate OIDs .16/.18) and to the signing certificate's platform-fixed source claims. A builder identity migration guard rejects bundles that mix old and new identity shapes.
  • Dependency chain (ADR 0105, 0109, 0110): resolvedDependencies records the ordered chain invocation-source, built-source, package-manifest, lockfile, node-runtime, plus package-manager-distribution for non-npm managers and runner-image, with the governed mediaType matrix. The signed package-manifest descriptor carries offline manifest_evidence (path + base64 content, 16 MiB bound) so verifiers can recompute it without repository access; the selected manifest is snapshotted before any build script runs.
  • Builder record (ADR 0106, 0107): builder.version records the observed {nodejs, npm, corepack?} triple, and build script invocations are recorded as runDetails.metadata.windlass_build_script_disposition.
  • Runner environment (ADR 0111, 0112): The runner-image descriptor's annotation set is exactly deployment_version, operating_system, image_version, runner_label; operating_system comes from the same bounded on-disk metadata snapshot as image_version, and ambient ImageOS is diagnostic-only.
  • Public contract (ADR 0108): The release-tag and provenance-sidecar optional inputs are removed, shrinking the npm reusable workflow contract to one required input (package-directory) and six optional inputs.
  • Diagnostics and fixtures: Seven new closed-registry diagnostic IDs (resolved-dependencies-invocation-source, resolved-dependencies-built-source, resolved-dependencies-package-manifest, package-manifest-evidence, build-script-disposition-mismatch, build-trigger-mismatch, build-config-uri-mismatch); the conformance corpus is regenerated with 47 registrations across 17 requirement IDs, and new fuzz targets (FuzzDecodeInternalParameters, FuzzParseCallerWorkflowPath, FuzzManifestEvidence, FuzzFixtureManifestEvidence, FuzzBindOIDCSignerIdentity) are registered in both the lint and scheduled fuzz workflows.

Why: the issue #143 investigation fixed npm's registry-side provenance handling (npm/cli#9882, shipped in npm 11.20.0/12.1.0); this migration lands the slsa-builder-side contract that pairs with it, completing the ADR 0103-0112 decision set.

How to test: go test ./... (full suite and race), go run ./cmd/slsa-builder-internal fixture-check --index testdata/fixtures/index.json, go run ./cmd/slsa-builder-internal workflow-check --workflow .github/workflows/js-ts-npm-package-slsa3.yml --profile npm-only, plus the eight scenario contracts (happy tag push, dispatch retry, offline manifest evidence, closed-schema regression, runner evidence, builder migration guard, seven-input contract, historical P02 cryptographic verification).

Related Issues

  • Related #143
  • Related #30 (the remaining live dogfood read-back for this contract is tracked separately and requires operator authorization)

Change Type

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Test/CI
  • Breaking change
  • Other:

Changelog

  • Category: Changed / Removed (both entries carry Breaking: markers)
  • User-facing note: The npm provenance contract is finalized per ADR 0103-0112 (workflow identity, closed github triple, ordered dependency chain with offline manifest evidence, builder versions, script disposition, runner operating system), and the release-tag / provenance-sidecar workflow inputs are removed.

Changelog update:

  • CHANGELOG.md [Unreleased] updated
  • Not needed because this change is not user-facing

Checklist

General

  • PR title follows Conventional Commits format:
    type(scope): Summary
  • This PR does not expose backend/internal implementation details in a public repo.
  • No secrets, tokens, keys, or private endpoints are included.
  • Changes stay within this repository's intended scope.

CI/Workflow Changes (if applicable)

If this PR modifies GitHub Actions workflows or CI/CD configuration, it must comply with our
Supply Chain Integrity requirements:

  • All uses: references are pinned to full 40-character commit SHAs (with # vX.Y.Z comment)
  • step-security/harden-runner is included as the first step in every job
  • Job-level permissions are used instead of top-level permissions

Protocol / Compatibility Impact

  • No protocol/spec impact
  • Protocol/spec updated
  • Conformance tests updated
  • Breaking change is versioned and migration notes are included

Compatibility impact: this is a breaking change to the signed npm provenance shape and the public workflow input contract, delivered while the project is pre-release (no stable version exists). The specification revisions in docs/architecture/ define the new observable behavior, the ADR set 0103-0112 records the decisions, and CHANGELOG.md [Unreleased] carries the Breaking: entries. Callers pinned by workflow SHA are unaffected until they deliberately re-pin; the builder identity migration guard rejects mixed-shape bundles rather than misclassifying them.

Testing

  • Unit tests added/updated
  • Integration or conformance tests added/updated
  • Tests pass
  • Lint and format pass
  • Type check passes
  • Manual verification performed

Test evidence: go test ./... and go test -race ./... fully green; golangci-lint run ./... reports 0 issues and golangci-lint fmt --diff is clean; fixture-check and workflow-check --profile npm-only pass; actionlint passes; Prettier and markdownlint pass. All five new fuzz targets pass 30-second smokes. The eight migration scenario contracts pass individually (including the historical P02 production signer fixture and the dispatch-retry signed invocation context). cubic review was run per commit and over the full branch diff against origin/main; final result: no issues (seven findings were fixed during the review loop, two were rejected with documented grounds).

Documentation

  • README updated (public input contract wording, both English and Korean)
  • Spec/docs updated (seven docs/architecture/ specifications plus the ADR index)
  • Changelog decision completed above

Rollout / Risk

  • Risk level: Medium
  • Rollback plan: Pre-release contract change with no stable release to break. Callers pin the reusable workflow by commit SHA, so existing pins keep the prior behavior; rollback of this branch is git revert of the implementation commits (spec and ADR commits are decision records and stay). The M1 live dogfood (issue #30, attempt 5) verifies the migrated contract against the real npm registry before any release.

Reviewer Checklist

  • Scope is clear and minimal
  • Security and boundary checks passed
  • Tests and docs are sufficient
  • Compatibility impact is correctly handled

Summary by cubic

Lands the final npm SLSA provenance contract from ADRs 0103-0112 as the first-release record of the signed provenance shape and public workflow inputs, pairing with the registry-side handling fixed in issue #143.

  • Invocation identity is now externalParameters.workflow {ref, repository, path} plus a closed internalParameters.github triple, both bound to OIDC certificate claims with a migration guard rejecting mixed-shape bundles.
  • resolvedDependencies carries the ordered invocation-source, built-source, package-manifest, lockfile, node-runtime chain, with offline manifest_evidence on the signed package-manifest so verifiers recompute it without repository access.
  • The public contract is one required and six optional inputs; release-tag and provenance-sidecar were never shipped.
  • Runner OS comes from the same on-disk metadata snapshot as image version; ambient ImageOS is diagnostic-only.
  • The source manifest is snapshotted before build and re-checked before install and pack; any byte drift fails closed with source-manifest-drift.
  • Review hardening: corrected the Build Trigger OID constant, enforced positive decimal repository IDs, classified publish-input failures, tightened the canonical runner software URI spelling and rejected whitespace-padded detail values at capture, and reported packed-tarball inspection failures under package-pack-failed, plus fuzz fixes including the .git repository rejection and module-proxy storage allowlist.

Verification impact

  • New OIDC bindings (Build Trigger, Build Config URI) fail closed before registry or release mutation.
  • Existing callers pinned by workflow SHA keep prior behavior; re-pinning is required to adopt the new contract.
  • Conformance corpus regenerated with 47 registrations across 17 requirement IDs, plus six new fuzz targets.