feat(npm)!: Implement final npm provenance contract per ADR 0103-0112
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.workflowrecords exactly the called workflow's{ref, repository, path}; the legacy environment snapshots leave the signed record.internalParameters.githubis a closed triple ofevent_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):
resolvedDependenciesrecords the ordered chaininvocation-source,built-source,package-manifest,lockfile,node-runtime, pluspackage-manager-distributionfor non-npm managers andrunner-image, with the governed mediaType matrix. The signedpackage-manifestdescriptor carries offlinemanifest_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.versionrecords the observed{nodejs, npm, corepack?}triple, and build script invocations are recorded asrunDetails.metadata.windlass_build_script_disposition. - Runner environment (ADR 0111, 0112): The
runner-imagedescriptor's annotation set is exactlydeployment_version,operating_system,image_version,runner_label;operating_systemcomes from the same bounded on-disk metadata snapshot asimage_version, and ambientImageOSis diagnostic-only. - Public contract (ADR 0108): The
release-tagandprovenance-sidecaroptional 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-sidecarworkflow 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.Zcomment) -
step-security/harden-runneris included as the first step in every job - Job-level
permissionsare used instead of top-levelpermissions
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 revertof 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 closedinternalParameters.githubtriple, both bound to OIDC certificate claims with a migration guard rejecting mixed-shape bundles. resolvedDependenciescarries the orderedinvocation-source,built-source,package-manifest,lockfile,node-runtimechain, with offlinemanifest_evidenceon the signed package-manifest so verifiers recompute it without repository access.- The public contract is one required and six optional inputs;
release-tagandprovenance-sidecarwere never shipped. - Runner OS comes from the same on-disk metadata snapshot as image version; ambient
ImageOSis 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.gitrepository 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.