From 031f3cda12ba3ca93cafa87eaa555696c7349f1e Mon Sep 17 00:00:00 2001 From: Yunseo Kim Date: Mon, 17 Aug 2026 02:26:04 +0900 Subject: [PATCH 1/3] docs(decisions): Add ADR 0083 deferring the npm M1 publish remediation to the upstream provenance-file fix Record the decision to wait for the first reviewed npm release containing npm/cli#9882 and adopt it as the initial publish-stage npm pin under the ADR 0082 procedure, instead of implementing the local OIDC-suppression and token-injection workaround. Publish authentication returns to the ADR 0024/ADR 0029 design (npm-native trusted publishing); the ADR 0076/0081 exchange preflight is retained as an observation-only check. Includes an explicit revisit trigger (closed unmerged, materially different precedence semantics, or no merge by 12026-10-01) with the pinned-11.17.0 workaround as the default fallback. Relations: amends ADR-0082 (initial-pin identification only; reverse amended-by edge added), see-also ADR-0024/0029/0076/0081 with reverse edges. Index READMEs (both languages) and the decisions AGENTS.md updated. adr-relations-check: 84 ADRs, 194 edges, NO PROBLEMS. Signed-off-by: Yunseo Kim --- ...sted-publishing-without-publish-secrets.md | 2 + ...nerated-slsa-provenance-for-npm-publish.md | 2 + ...ights-and-first-mutation-classification.md | 2 + ...t-and-correct-token-lifetime-assumption.md | 2 + ...ied-provisioning-and-reviewed-allowlist.md | 9 + ...ion-to-the-upstream-provenance-file-fix.md | 225 ++++++++++++++++++ docs/decisions/AGENTS.md | 8 +- docs/decisions/README.ko.md | 72 +++--- docs/decisions/README.md | 4 +- 9 files changed, 288 insertions(+), 38 deletions(-) create mode 100644 docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md diff --git a/docs/decisions/0024-use-oidc-trusted-publishing-without-publish-secrets.md b/docs/decisions/0024-use-oidc-trusted-publishing-without-publish-secrets.md index d1ddbfd..b2b5e52 100644 --- a/docs/decisions/0024-use-oidc-trusted-publishing-without-publish-secrets.md +++ b/docs/decisions/0024-use-oidc-trusted-publishing-without-publish-secrets.md @@ -14,6 +14,8 @@ relations: before registry mutation by an early OIDC token exchange preflight; residual publish-time authorization failures are classified, and the absolute must-stop-before-registry-mutation wording is scoped to what the exchange and observation preflights can establish" + - type: see-also + target: ADR-0083 --- # Use OIDC Trusted Publishing Without Publish Secrets diff --git a/docs/decisions/0029-use-windlass-generated-slsa-provenance-for-npm-publish.md b/docs/decisions/0029-use-windlass-generated-slsa-provenance-for-npm-publish.md index 35b27f0..f019a3f 100644 --- a/docs/decisions/0029-use-windlass-generated-slsa-provenance-for-npm-publish.md +++ b/docs/decisions/0029-use-windlass-generated-slsa-provenance-for-npm-publish.md @@ -22,6 +22,8 @@ relations: target: ADR-0071 - type: see-also target: ADR-0082 + - type: see-also + target: ADR-0083 --- # Use Windlass-Generated SLSA Provenance for npm Publish diff --git a/docs/decisions/0076-use-observation-preflights-and-first-mutation-classification.md b/docs/decisions/0076-use-observation-preflights-and-first-mutation-classification.md index 9b88e6a..af63e08 100644 --- a/docs/decisions/0076-use-observation-preflights-and-first-mutation-classification.md +++ b/docs/decisions/0076-use-observation-preflights-and-first-mutation-classification.md @@ -28,6 +28,8 @@ relations: "the npm OIDC exchange success-response contract (unpinned in ADR-0076; pinned in ADR-0081 to the empirically observed shape) and the exchange-token lifetime assumption (the 'typically 1 hour' documentation note cited in ADR-0076; the observed lifetime is 15 minutes)" + - type: see-also + target: ADR-0083 --- # Use Observation Preflights and First-Mutation Classification diff --git a/docs/decisions/0081-pin-npm-oidc-exchange-response-contract-and-correct-token-lifetime-assumption.md b/docs/decisions/0081-pin-npm-oidc-exchange-response-contract-and-correct-token-lifetime-assumption.md index 544abb3..f949ea5 100644 --- a/docs/decisions/0081-pin-npm-oidc-exchange-response-contract-and-correct-token-lifetime-assumption.md +++ b/docs/decisions/0081-pin-npm-oidc-exchange-response-contract-and-correct-token-lifetime-assumption.md @@ -13,6 +13,8 @@ relations: hour' documentation note cited in ADR-0076; the observed lifetime is 15 minutes)" - type: see-also target: ADR-0082 + - type: see-also + target: ADR-0083 --- # Pin the npm OIDC Exchange Response Contract to the Observed Shape and Correct the Token Lifetime Assumption diff --git a/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md b/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md index 606edd6..375f50c 100644 --- a/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md +++ b/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md @@ -20,6 +20,15 @@ relations: target: ADR-0029 - type: see-also target: ADR-0081 + - type: amended-by + target: ADR-0083 + scope: + "the identification of the initial pinned publish-stage npm version: ADR-0082 records the + allowlist as initially a single version pinned when the #97 fix lands; ADR-0083 defines the + #97 remediation as adopting the upstream npm/cli#9882 fix and selects the initial pin as the + first reviewed npm release containing that fix. ADR-0082's provisioning, + integrity-verification, allowlist, and bump-procedure clauses are unchanged and remain in + force" --- # Pin the Publish-Stage npm CLI Version with Integrity-Verified Provisioning and a Reviewed Allowlist diff --git a/docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md b/docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md new file mode 100644 index 0000000..28ccaa4 --- /dev/null +++ b/docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md @@ -0,0 +1,225 @@ +--- +parent: Decisions +nav_order: 83 +status: accepted +date: 12026-08-17 +decision-makers: Yunseo Kim +relations: + - type: amends + target: ADR-0082 + scope: + "the identification of the initial pinned publish-stage npm version: ADR-0082 records the + allowlist as initially a single version pinned when the #97 fix lands; ADR-0083 defines the + #97 remediation as adopting the upstream npm/cli#9882 fix and selects the initial pin as the + first reviewed npm release containing that fix. ADR-0082's provisioning, + integrity-verification, allowlist, and bump-procedure clauses are unchanged and remain in + force" + - type: see-also + target: ADR-0024 + - type: see-also + target: ADR-0029 + - type: see-also + target: ADR-0076 + - type: see-also + target: ADR-0081 +--- + +# Defer the npm M1 Publish Remediation to the Upstream provenance-file Fix and Adopt the Fixed Release as the Initial Publish npm Pin + +## Context and Problem Statement + +The fourth M1 dogfood +([vers-js run 31840088262](https://github.com/windlasstech/vers-js/actions/runs/31840088262), +tracked as [issue #97](https://github.com/windlasstech/slsa-builder/issues/97)) pinned the npm +defect that blocks the JS/TS npm profile's publish path: npm trusted publishing auto-enables +provenance and silently discards a caller-supplied `--provenance-file` bundle, publishing an +npm-generated statement instead of the Windlass-signed Statement. The run published +`@windlass/vers-js@0.1.2` with the foreign evidence; the fail-closed read-back rejected it exactly +as designed, but only after the registry mutation had committed, and npm policy makes the burned +version number permanently unreusable. The RCA identified three interacting layers: `oidc.js` +auto-enables provenance whenever the `provenance` config is at its default, the inner +`provenance === true` branch in libnpmpublish's `buildMetadata()` then never reaches the +`verifyProvenance()` path that reads the supplied file, and every documented way to disable +automatic provenance is unusable in combination with `--provenance-file` (config-layer mutual +exclusivity, the env-layer carve-out, and publishConfig flatten timing). + +Two candidate remediations were on the table: + +1. A local workaround: suppress npm's CI/OIDC detection for the publish subprocess, inject the + self-exchanged trusted-publishing token into an isolated npmrc as the registry credential, and + enforce a closed `publishConfig` allowlist. This mechanism is source-dependent on npm internals — + exactly the verification surface ADR 0082 exists to close. +2. The upstream fix. The project filed [npm/cli#9879](https://github.com/npm/cli/issues/9879) + (report) and [npm/cli#9882](https://github.com/npm/cli/pull/9882) (fix PR, OPEN at decision + time). + +ADR 0082 pinned the publish-stage npm provisioning mechanics and recorded the allowlist as +"initially a single version, pinned when the #97 fix lands and re-verified there" — deliberately +leaving open what the #97 fix is, and therefore which npm version opens the allowlist. + +A diff review of npm/cli#9882 establishes what the fixed npm does on the profile's exact path: + +- `lib/utils/oidc.js` skips auto-enabling provenance when `opts.provenanceFile` is configured; the + option already carries the value from every config source (CLI, env, npmrc, publishConfig) by the + time the OIDC flow runs, so all entry paths are covered. The OIDC exchange itself still runs — + trusted publishing continues to authenticate the publish. +- libnpmpublish's `buildMetadata()` throws `EPROVENANCECONFLICT` when both `provenance: true` and + `provenanceFile` are set programmatically. The profile's pinned argv never sets `provenance=true`, + so this branch is unreachable on the profile's path. +- The documentation updates state the precedence rule, and the PR's new CLI regression test asserts + the profile's exact scenario: OIDC trusted publishing with a `provenance-file` publishes the + packument with a sigstore attachment deep-equal to the supplied bundle, with provenance generation + never invoked. + +The residual review surface (the error-code naming for the conflict branch, docs wording) does not +intersect the profile's path. The only outcome that would break the profile's path is a wholesale +rejection of the precedence direction, which would contradict npm's own published documentation of +`--provenance-file`. + +Two further facts frame the choice. First, ADR 0029 already decided that npm trusted publishing +authenticates the registry mutation while the Windlass-signed bundle is the canonical provenance +(building on ADR 0024's trusted-publishing authentication decision). The workaround would replace +npm's own exchange with a self-performed exchange plus an injected credential — a defect-forced +deviation from that accepted design, whose reason to exist disappears when the fix ships. Second, +the wait is expected to cost little or no schedule time: npm 12.0.1 shipped two days after 12.0.0 +and 12.0.2 seventeen days later, while the workaround path requires a specification amendment, a TDD +implementation across the publish boundary, and a dogfood run before it could land. + +## Decision Drivers + +- A mechanism that is never written is strictly cheaper than one that is written, verified, and + later retired; ADR 0082 already earmarks workaround retirement on fixed npm versions as a separate + later decision. +- The workaround's dependence on npm internals (`oidc()` ordering, config exclusivity, publishConfig + flatten timing) is precisely the verification surface ADR 0082 closes; adopting the workaround + even temporarily would expand the surface that must be reviewed per bump. +- The fix's semantics for the profile's argv are diff-verified today; the uncertain residue does not + intersect the profile's path. +- Waiting restores the ADR 0024/ADR 0029 authentication design instead of entrenching a deviation + from it. +- The ADR 0076 exchange preflight retains its value — early, mutation-free detection of + trusted-publishing misconfiguration — regardless of which party consumes the minted token, so ADR + 0081's pinned exchange contract stays in force untouched. +- Dependence on an external release schedule is the principal risk of waiting and must be bounded by + an explicit revisit trigger rather than left open-ended. + +## Considered Options + +1. Defer the #97 remediation to the first reviewed npm release containing npm/cli#9882; adopt that + release as the initial publish npm pin; authenticate the publish with npm-native trusted + publishing. +2. Implement the #97 workaround now on a pinned npm 11.17.0; adopt the upstream fix later through + the ADR 0082 bump procedure. +3. Hybrid: implement the version-agnostic ADR 0082 machinery now (the workflow provisioning step, + pin-data plumbing, and Go-side pin validation) and defer only the version selection and the + authentication-path finalization. + +## Decision Outcome + +Chosen option: "Defer the #97 remediation to the first reviewed npm release containing +npm/cli#9882", because the fix's semantics on the profile's path are already diff-verified, because +waiting restores the accepted authentication design rather than entrenching a deviation, and because +waiting is expected to add little or no schedule cost compared with specifying, implementing, and +dogfooding the workaround first. + +### Initial pin selection at adoption time + +When an npm release containing the #9882 fix ships, the initial allowlist entry is selected under +the ADR 0082 bump procedure, preferring: + +1. an npm 11-line backport, if one is released — minimal internals delta from the dogfood-exercised + 11.17.0 and alignment with the Node 24 LTS line; otherwise +2. the first 12.x release containing the fix, with bundle-verification compatibility evidence + against its bundled sigstore-js major (v5) as an added acceptance criterion, because npm's + `verifyProvenance()` path has never been exercised live against a Windlass-signed bundle. + +The pin data (version, distribution URL, distribution SHA-256) is recorded in the JS/TS npm +provenance and publish specification's allowlist per ADR 0082. + +### Publish authentication path + +With the fix in place, the publish job uses npm-native trusted publishing exactly as ADR 0024 and +ADR 0029 intended: npm performs its own OIDC exchange inside `npm publish`, and the pinned argv is +unchanged. No OIDC-environment suppression, no token injection into an isolated npmrc, and no +`publishConfig` credential allowlist is specified or implemented. The ADR 0076 exchange preflight is +retained as a non-mutating observation preflight for early detection of trusted-publishing +misconfiguration; the minted token is discarded, not consumed. The fail-closed post-publish +read-back remains the permanent safety net, unchanged. + +### Deferral scope and revisit trigger + +All #97 remediation implementation is deferred until the fixed release exists and is selected: the +specification amendment (authentication path, publish-side pin validation replacing the +build/publish npm-version equality check, the allowlist initial entry), the workflow provisioning +step, the Go changes, and the compatibility fixtures. + +This decision is revisited — with option 2 (the workaround on a pinned npm 11.17.0) as the default +fallback — when any of the following occurs: + +- npm/cli#9882 is closed unmerged; +- npm/cli#9882 is merged with precedence semantics that differ materially for the profile's argv + (for example, automatic provenance taking precedence over a supplied `--provenance-file`); +- npm/cli#9882 has not merged by 12026-10-01. + +### Relationship to existing decisions + +This ADR amends ADR 0082's identification of the initial pinned version only; ADR 0082's +provisioning, integrity-verification, allowlist, and bump-procedure clauses are unchanged and remain +in force. ADR 0024 and ADR 0029 are not modified — this ADR removes a planned deviation from them. +ADR 0076 and ADR 0081 remain fully in force: the exchange preflight keeps its observation role and +its pinned response contract. + +## Pros and Cons of the Options + +### Defer to the fixed release and adopt it as the initial pin + +- Good, because no workaround code, specification text, or fixtures are written up front for a + mechanism that becomes unnecessary if the fix ships before the initial pin is selected. +- Good, because publish authentication returns to the ADR 0024/ADR 0029 design: npm trusted + publishing authenticates the registry mutation; the Windlass bundle is the canonical provenance. +- Good, because the verification surface ADR 0082 must close stays minimal — one pinned npm whose + behavior includes first-class support for the profile's exact publish mode, covered by npm's own + regression test. +- Good, because the decision is evidence-based today: the fix's behavior on the profile's path is + pinned by the reviewed diff, not by a guess about maintainer intent. +- Bad, because npm M1 completion becomes dependent on the npm maintainers' review and release + schedule, which the project does not control; the revisit trigger bounds but does not eliminate + this dependence. +- Bad, because the fixed release's line is unknown: if the fix ships only in npm 12.x, the initial + pin carries the sigstore-js v5 bundle-verification compatibility question and a larger internals + diff-review than an 11.x pin would require. + +### Implement the workaround now on pinned 11.17.0 + +- Good, because npm M1 unblocks immediately on the project's own schedule, and 11.17.0 is the + dogfood-exercised version with live evidence. +- Bad, because the workaround is specified, implemented, and dogfooded at a calendar cost comparable + to the expected wait, and it becomes dead weight if the fix ships before the initial pin is + selected. +- Bad, because the workaround is source-dependent on npm internals across the allowed range and + entrenches a deviation from ADR 0029's authentication design, with its retirement already known to + require a separate later decision. + +### Hybrid: build version-agnostic machinery now + +- Good, because part of the wait becomes productive: the provisioning step and pin plumbing do not + depend on the selected version. +- Bad, because the specification's authentication path and the pin-validation contract cannot be + finalized before the fixed release's behavior is confirmed, so a meaningful fraction of the work + risks rework, and a partial-implementation state is harder to reason about than a clean deferral. + +## More Information + +- Motivating incident and RCA: [issue #97](https://github.com/windlasstech/slsa-builder/issues/97) + and the fourth-attempt evidence in + [issue #30](https://github.com/windlasstech/slsa-builder/issues/30). +- Upstream: [npm/cli#9879](https://github.com/npm/cli/issues/9879) (report), + [npm/cli#9882](https://github.com/npm/cli/pull/9882) (fix PR; OPEN and diff-verified as described + above at decision time). +- Issue #97's fix-requirement section described the workaround; it is superseded by this ADR and + will be rewritten when the remediation implementation begins. +- The burned `@windlass/vers-js@0.1.2` publication remains the foreign-conflict test target for the + publish read-back, and the preserved dogfood-4 bundle remains the reference bundle the fixed npm's + `verifyProvenance()` path must accept. +- If this decision is revisited under the trigger above, the fallback selects npm 11.17.0 as the + initial pin per the analysis recorded in ADR 0082's context. diff --git a/docs/decisions/AGENTS.md b/docs/decisions/AGENTS.md index 2144eb3..6a8238a 100644 --- a/docs/decisions/AGENTS.md +++ b/docs/decisions/AGENTS.md @@ -4,7 +4,7 @@ Architecture decision records for the SLSA builder. Each ADR is a MADR 4.0.0 document with a sequential four-digit number and a kebab-case title. The sequence currently runs from `0000` through -`0081`. +`0083`. ## STRUCTURE @@ -22,11 +22,13 @@ docs/decisions/ ├── 0078-treat-settings-only-pnpm-workspace-yaml-as-standalone-root-package-mode.md ├── ... # 0079–0080 ├── 0081-pin-npm-oidc-exchange-response-contract-and-correct-token-lifetime-assumption.md +├── ... # 0082 +├── 0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md ├── README.md / README.ko.md └── AGENTS.md ``` -ADR numbering is sequential from `0000` through `0081`. See the WHERE TO LOOK table below for topic +ADR numbering is sequential from `0000` through `0083`. See the WHERE TO LOOK table below for topic groupings. ## WHERE TO LOOK @@ -58,6 +60,8 @@ groupings. | Caller-specified source ref | `0079` | Tags-only `source-ref` input for fixed-pipeline release retries. | | Source binding model | `0080` | Signed provenance fields vs certificate invocation claims. | | npm OIDC exchange contract | `0081` | Observed response shape; 15-minute exchange token lifetime. | +| Publish-stage npm version pin | `0082` | Integrity-verified provisioning and a reviewed allowlist. | +| npm M1 remediation deferral | `0083` | Wait for npm/cli#9882; fixed release opens the allowlist. | ## CONVENTIONS diff --git a/docs/decisions/README.ko.md b/docs/decisions/README.ko.md index 6e35420..915dcbd 100644 --- a/docs/decisions/README.ko.md +++ b/docs/decisions/README.ko.md @@ -28,41 +28,42 @@ ## ADR 목록 ADR 파일은 MADR 4.0.0 문서이며, 네 자리 순번과 kebab-case 제목을 사용합니다. 현재 순번은 `0000`부터 -`0082`까지입니다. - -| 범위 | 주제 | 설명 | -| --------- | ---------------------------------- | ------------------------------------------------------------------------------ | -| 0000–0012 | 저장소 기반 및 개발 도구 | ADR 형식, 저장소 시작, Go, 린팅, 포맷팅, 도구 체인. | -| 0013–0037 | JS/TS npm 패키지 profile | 패키지 선택, build/pack, OIDC publishing, provenance. | -| 0038–0052 | GitHub Release asset profile | 릴리즈 에셋 subject, publisher 모델, sidecar distribution. | -| 0053–0054 | 릴리즈 매니페스트 메타데이터 | 서명 경계와 release manifest predicate URI. | -| 0055 | 서명 어댑터 Statement 구성 | `actions/attest` custom mode와 서명 후 Statement 검사. | -| 0056 | 비선택 lockfile 진단 | Manifest-selected manager에서 stale lockfile 처리. | -| 0057 | 공개 npm release-asset 모드 | 단일 npm 진입점과 advanced composition primitives. | -| 0058 | 릴리즈 publisher 권한 경계 | 동일 저장소 target과 최소 권한 mutation topology. | -| 0059 | 공개 release-asset 모드 인터페이스 | 최소 user-intent `workflow_call` 표면과 fail-closed API. | -| 0060 | 통합 npm 공개 진입점 | 하나의 공개 API와 분리된 내부 권한 경계. | -| 0061 | 중복 JSON member 거부 | 의미 검증 전 서명된 SLSA Statement를 엄격히 파싱. | -| 0062 | 신뢰 producer 정책 교집합 | Manifest와 명시적 verifier policy 충돌 처리. | -| 0063 | Yarn Berry v4+ 지원 경계 | Yarn 지원을 위한 Corepack과 `packageManager` 요구사항. | -| 0064 | npm provenance subject 호환성 | npm PURL subject와 SHA-512 및 SHA-256 tarball digest. | -| 0065 | ADR 생애주기 메타데이터 | 닫힌 status 문법과 relations 필드. | -| 0066 | 릴리즈 mutation 런 소유권 | 잡 클래스 concurrency와 직렬화된 mutation segment. | -| 0067 | 반복 run 수렴 | Run-identity 멱등성, 결과 상태, binding 증명. | -| 0068 | 검증자 신원 바인딩 | 검증을 위한 불변 builder 및 source 신원. | -| 0069 | 투명성·trust root 정책 | Rekor inclusion, 오프라인 검증, trust root 거버넌스. | -| 0070–0071 | Provenance 빌드 환경 기록 | 패키지 매니저 배포본, runner image, builder 필드. | -| 0072 | 릴리즈 에셋 run 소유권 바인딩 | Sidecar-first pair 바인딩과 custody 비귀속 선언. | -| 0073 | npm same-run attestation 바인딩 | npm 수렴에 published attestation run identity 필수화. | -| 0074 | Mutation segment 원자성 | 단일 job segment와 탐지 기반 cross-run 안전. | -| 0075 | Mutation 큐 정책 | Mutation segment contender의 `queue: max` FIFO 대기. | -| 0076 | Preflight와 첫 mutation 분류 | 관측 preflight와 probe 부재 표면의 첫 mutation 분류. | -| 0077 | Windlass provenance 서명 어댑터 | 모든 프로필의 정확한 바이트를 위한 Go-native DSSE 서명. | -| 0078 | pnpm 설정 전용 루트 패키지 모드 | `packages` 누락은 잘못된 데이터가 아니라 루트 전용 모드. | -| 0079 | 호출자 지정 빌드 소스 ref | 모든 producer profile의 기본 릴리즈 재시도 경로인 태그 전용 `source-ref` 입력. | -| 0080 | 인증서 클레임 = 호출 컨텍스트 | 소스 정책은 서명된 provenance 필드에 바인딩; cert 소스 클레임은 호출을 증명. | -| 0081 | npm OIDC exchange 응답 계약 | 성공 본문을 실측 형태에 핀; exchange 토큰 수명은 15분. | -| 0082 | publish 단계 npm 버전 핀 | 무결성 검증된 publish npm 프로비저닝 + 검토 allowlist. | +`0083`까지입니다. + +| 범위 | 주제 | 설명 | +| --------- | ------------------------------------- | ------------------------------------------------------------------------------ | +| 0000–0012 | 저장소 기반 및 개발 도구 | ADR 형식, 저장소 시작, Go, 린팅, 포맷팅, 도구 체인. | +| 0013–0037 | JS/TS npm 패키지 profile | 패키지 선택, build/pack, OIDC publishing, provenance. | +| 0038–0052 | GitHub Release asset profile | 릴리즈 에셋 subject, publisher 모델, sidecar distribution. | +| 0053–0054 | 릴리즈 매니페스트 메타데이터 | 서명 경계와 release manifest predicate URI. | +| 0055 | 서명 어댑터 Statement 구성 | `actions/attest` custom mode와 서명 후 Statement 검사. | +| 0056 | 비선택 lockfile 진단 | Manifest-selected manager에서 stale lockfile 처리. | +| 0057 | 공개 npm release-asset 모드 | 단일 npm 진입점과 advanced composition primitives. | +| 0058 | 릴리즈 publisher 권한 경계 | 동일 저장소 target과 최소 권한 mutation topology. | +| 0059 | 공개 release-asset 모드 인터페이스 | 최소 user-intent `workflow_call` 표면과 fail-closed API. | +| 0060 | 통합 npm 공개 진입점 | 하나의 공개 API와 분리된 내부 권한 경계. | +| 0061 | 중복 JSON member 거부 | 의미 검증 전 서명된 SLSA Statement를 엄격히 파싱. | +| 0062 | 신뢰 producer 정책 교집합 | Manifest와 명시적 verifier policy 충돌 처리. | +| 0063 | Yarn Berry v4+ 지원 경계 | Yarn 지원을 위한 Corepack과 `packageManager` 요구사항. | +| 0064 | npm provenance subject 호환성 | npm PURL subject와 SHA-512 및 SHA-256 tarball digest. | +| 0065 | ADR 생애주기 메타데이터 | 닫힌 status 문법과 relations 필드. | +| 0066 | 릴리즈 mutation 런 소유권 | 잡 클래스 concurrency와 직렬화된 mutation segment. | +| 0067 | 반복 run 수렴 | Run-identity 멱등성, 결과 상태, binding 증명. | +| 0068 | 검증자 신원 바인딩 | 검증을 위한 불변 builder 및 source 신원. | +| 0069 | 투명성·trust root 정책 | Rekor inclusion, 오프라인 검증, trust root 거버넌스. | +| 0070–0071 | Provenance 빌드 환경 기록 | 패키지 매니저 배포본, runner image, builder 필드. | +| 0072 | 릴리즈 에셋 run 소유권 바인딩 | Sidecar-first pair 바인딩과 custody 비귀속 선언. | +| 0073 | npm same-run attestation 바인딩 | npm 수렴에 published attestation run identity 필수화. | +| 0074 | Mutation segment 원자성 | 단일 job segment와 탐지 기반 cross-run 안전. | +| 0075 | Mutation 큐 정책 | Mutation segment contender의 `queue: max` FIFO 대기. | +| 0076 | Preflight와 첫 mutation 분류 | 관측 preflight와 probe 부재 표면의 첫 mutation 분류. | +| 0077 | Windlass provenance 서명 어댑터 | 모든 프로필의 정확한 바이트를 위한 Go-native DSSE 서명. | +| 0078 | pnpm 설정 전용 루트 패키지 모드 | `packages` 누락은 잘못된 데이터가 아니라 루트 전용 모드. | +| 0079 | 호출자 지정 빌드 소스 ref | 모든 producer profile의 기본 릴리즈 재시도 경로인 태그 전용 `source-ref` 입력. | +| 0080 | 인증서 클레임 = 호출 컨텍스트 | 소스 정책은 서명된 provenance 필드에 바인딩; cert 소스 클레임은 호출을 증명. | +| 0081 | npm OIDC exchange 응답 계약 | 성공 본문을 실측 형태에 핀; exchange 토큰 수명은 15분. | +| 0082 | publish 단계 npm 버전 핀 | 무결성 검증된 publish npm 프로비저닝 + 검토 allowlist. | +| 0083 | npm M1 remediation 업스트림 수정 대기 | 초기 publish npm 핀은 npm/cli#9882를 포함하는 첫 검토 릴리스. | ## ADR status와 relations @@ -204,6 +205,7 @@ ADR은 영향을 주는 모든 선행 ADR을 열거해야 하며, 누락은 추 | 0080 | Bind source identity policy to signed provenance fields, certificate claims as invocation context | Core profile contract, SLSA provenance v1, identity and build types, JS/TS npm provenance and publish, verification policy and fixtures | | 0081 | Pin the npm OIDC exchange response contract to the observed shape, correct token lifetime | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0082 | Pin the publish-stage npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0083 | Defer the npm M1 publish remediation to the upstream provenance-file fix | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | ### 대체 혹은 폐기된 ADR (과거 맥락으로만 참조) diff --git a/docs/decisions/README.md b/docs/decisions/README.md index a04e25f..f4bb109 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -29,7 +29,7 @@ accepted ADR, stop and write a new ADR rather than editing the accepted ADR body ## ADR inventory ADR files are MADR 4.0.0 documents with sequential four-digit numbers and kebab-case titles. The -sequence currently runs from `0000` through `0082`. +sequence currently runs from `0000` through `0083`. | Range | Topic | Notes | | --------- | ------------------------------------------- | ----------------------------------------------------------------------------------------- | @@ -64,6 +64,7 @@ sequence currently runs from `0000` through `0082`. | 0080 | Certificate claims as invocation context | Source policy binds signed provenance fields; cert source claims prove invocation. | | 0081 | npm OIDC exchange response contract | Success body pinned to the observed shape; exchange token lifetime is 15 minutes. | | 0082 | Publish-stage npm version pinning | Pinned, integrity-verified publish npm provisioning with a reviewed allowlist. | +| 0083 | npm M1 remediation deferred to upstream fix | Initial publish npm pin is the first reviewed release containing npm/cli#9882. | ## ADR status and relations @@ -207,6 +208,7 @@ of this table; they are recorded in each ADR's `relations` frontmatter field. | 0080 | Bind source identity policy to signed provenance fields, certificate claims as invocation context | Core profile contract, SLSA provenance v1, identity and build types, JS/TS npm provenance and publish, verification policy and fixtures | | 0081 | Pin the npm OIDC exchange response contract to the observed shape, correct token lifetime | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0082 | Pin the publish-stage npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0083 | Defer the npm M1 publish remediation to the upstream provenance-file fix | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | ### Superseded or deprecated ADRs (historical only) From b194b37eb47a20487150004df51fb0162fd99466 Mon Sep 17 00:00:00 2001 From: Yunseo Kim Date: Mon, 17 Aug 2026 08:58:45 +0900 Subject: [PATCH 2/3] docs(decisions): Add ADR 0084 provisioning the publish-stage npm CLI from a digest-verified registry tarball Record the provisioning-mechanism decision left open by ADR 0082: the publish job downloads the exact recorded registry tarball URL, verifies the committed distribution digest over the compressed bytes before extraction, and executes npm through a basename-"npm" launcher passed to the publish command's --npm-executable, provisioning before any npm invocation (diagnostics included). The pinned digest is SHA-512 in SRI form, byte-identical to the registry-native dist.integrity field, and the pin data (version, URL, digest) lives in one machine-readable file referenced normatively by the specification allowlist. Implementation timing remains governed by ADR 0083's deferral scope. Relations: partially-supersedes ADR-0082 (the SHA-256 digest designation only) and amends ADR-0082 (mechanism selection), with reverse partially-superseded-by/amended-by edges; see-also ADR-0083/0027 with reverse edges. Index READMEs (both languages) and the decisions AGENTS.md updated. adr-relations-check: 85 ADRs, 202 edges, NO PROBLEMS. Signed-off-by: Yunseo Kim --- ...-hosted-ubuntu-2404-and-node-24-runtime.md | 2 + ...ied-provisioning-and-reviewed-allowlist.md | 15 + ...ion-to-the-upstream-provenance-file-fix.md | 2 + ...from-a-digest-verified-registry-tarball.md | 283 ++++++++++++++++++ docs/decisions/AGENTS.md | 17 +- docs/decisions/README.ko.md | 4 +- docs/decisions/README.md | 4 +- 7 files changed, 313 insertions(+), 14 deletions(-) create mode 100644 docs/decisions/0084-provision-the-publish-stage-npm-cli-from-a-digest-verified-registry-tarball.md diff --git a/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md b/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md index 60ff3f8..1f1e33c 100644 --- a/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md +++ b/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md @@ -12,6 +12,8 @@ relations: by the selected Node.js 24 toolchain, floating with the node-24 image); the Node.js 24 runtime selection, the runner and OS image constraints, the caller-input prohibitions, the build-stage npm clauses, and the floor check and actual-version recording clauses remain in force" + - type: see-also + target: ADR-0084 --- # Use GitHub-Hosted Ubuntu 24.04 and Node.js 24 Runtime diff --git a/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md b/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md index 375f50c..256a002 100644 --- a/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md +++ b/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md @@ -29,6 +29,21 @@ relations: first reviewed npm release containing that fix. ADR-0082's provisioning, integrity-verification, allowlist, and bump-procedure clauses are unchanged and remain in force" + - type: partially-superseded-by + target: ADR-0084 + scope: + "the designation of SHA-256 as the publish-stage npm distribution digest algorithm in the + provisioning clause ('one shared definition of version plus distribution SHA-256'): the pinned + digest is SHA-512 in SRI form, byte-identical to the registry-native dist.integrity field. The + pre-use digest-verification obligation, the single shared definition, the allowlist, and the + bump procedure are unaffected and remain in force" + - type: amended-by + target: ADR-0084 + scope: + "the selection of the concrete publish-stage npm provisioning mechanism, which ADR-0082 left + unspecified: ADR-0082 mandates explicit provisioning before any npm invocation with the + distribution digest verified before first use but does not name the mechanism; ADR-0084 + narrows the open choice to the digest-verified npm registry tarball mechanism" --- # Pin the Publish-Stage npm CLI Version with Integrity-Verified Provisioning and a Reviewed Allowlist diff --git a/docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md b/docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md index 28ccaa4..cf986da 100644 --- a/docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md +++ b/docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md @@ -22,6 +22,8 @@ relations: target: ADR-0076 - type: see-also target: ADR-0081 + - type: see-also + target: ADR-0084 --- # Defer the npm M1 Publish Remediation to the Upstream provenance-file Fix and Adopt the Fixed Release as the Initial Publish npm Pin diff --git a/docs/decisions/0084-provision-the-publish-stage-npm-cli-from-a-digest-verified-registry-tarball.md b/docs/decisions/0084-provision-the-publish-stage-npm-cli-from-a-digest-verified-registry-tarball.md new file mode 100644 index 0000000..31ad872 --- /dev/null +++ b/docs/decisions/0084-provision-the-publish-stage-npm-cli-from-a-digest-verified-registry-tarball.md @@ -0,0 +1,283 @@ +--- +parent: Decisions +nav_order: 84 +status: accepted +date: 12026-08-17 +decision-makers: Yunseo Kim +relations: + - type: partially-supersedes + target: ADR-0082 + scope: + "the designation of SHA-256 as the publish-stage npm distribution digest algorithm in the + provisioning clause ('one shared definition of version plus distribution SHA-256'): the pinned + digest is SHA-512 in SRI form, byte-identical to the registry-native dist.integrity field. The + pre-use digest-verification obligation, the single shared definition, the allowlist, and the + bump procedure are unaffected and remain in force" + - type: amends + target: ADR-0082 + scope: + "the selection of the concrete publish-stage npm provisioning mechanism, which ADR-0082 left + unspecified: ADR-0082 mandates explicit provisioning before any npm invocation with the + distribution digest verified before first use but does not name the mechanism; ADR-0084 + narrows the open choice to the digest-verified npm registry tarball mechanism" + - type: see-also + target: ADR-0083 + - type: see-also + target: ADR-0027 +--- + +# Provision the Publish-Stage npm CLI from a Digest-Verified Registry Tarball + +## Context and Problem Statement + +ADR 0082 decided that the publish job of the JS/TS npm reusable workflow provisions npm explicitly +before any npm invocation, from one shared definition of version plus distribution SHA-256, with the +distribution digest verified before first use, and that the supported set lives in a reviewed +allowlist. It deliberately did not name the provisioning mechanism: "the install step verifies the +distribution digest" describes an obligation, not a mechanism, and the workflow change cannot be +implemented until a concrete one is selected. + +ADR 0083 then fixed which npm version opens the allowlist: the first reviewed npm release containing +the npm/cli#9882 fix, preferring an npm 11-line backport and otherwise the first 12.x release +carrying the fix. The provisioning mechanism must therefore be able to reach **both** pin lines on +the Node.js 24 runtime, which remains in force per ADR 0027. + +An investigation of the candidate mechanisms established the following facts: + +- `actions/setup-node` has no npm-version input and never modifies npm after installation; the npm + CLI is whatever the resolved Node artifact bundles. The Node-release-to-npm mapping is fixed per + exact release ([nodejs.org/dist/index.json](https://nodejs.org/dist/index.json)), but the entire + Node.js 24 line bundles npm 11.x only (24.19.0 bundles 11.17.0), so npm 12.x is unreachable + through any Node.js 24 artifact. +- The npm package tarball on the public registry is fully self-contained: npm declares all + production dependencies in `bundleDependencies`, and after plain extraction + `node package/bin/npm-cli.js` runs directly (verified against npm 12.0.2). +- The registry packument provides, per version, `dist.tarball` (URL), `dist.shasum` (SHA-1), + `dist.integrity` (SRI SHA-512), and `dist.signatures` (npm registry ECDSA signatures). SHA-512 in + SRI form is the registry's native strong digest; the packument provides **no SHA-256 field**. + Sampled npm CLI releases (9.5.0 through 12.0.2) carry the registry ECDSA signature but no + npm/sigstore provenance attestation and no GitHub artifact attestation for the tarball. +- `npm install -g npm@` verifies integrity only against registry metadata fetched at + install time (trust on first use); the npm CLI exposes no option to supply an expected digest, so + an independently committed digest cannot be enforced on that path. +- Corepack recognizes npm and supports exact pins with hashes (`npm@+sha512.`), but it + installs no npm shims by default, the npm team does not treat Corepack as a supported npm install + path, the January 12025 npm registry signing-key rotation broke older bundled Corepack versions, + and Node.js 25+ no longer distributes Corepack. +- The publish boundary in this repository invokes npm through a Go command that takes an absolute + executable path whose basename must be `npm` (`--npm-executable`), and the publish job currently + invokes the ambient npm in its diagnostics step before publishing. + +ADR 0082 names SHA-256 as the distribution digest algorithm. Because the registry's native strong +digest is SHA-512 in SRI form, pinning SHA-512 makes the committed value byte-identical to the +packument's `dist.integrity` field: the review-time cross-check becomes a direct equality check +instead of a cross-algorithm agreement argument, and the pin format aligns with npm ecosystem +conventions (package-lock `integrity`, Corepack hashes) and with this profile's existing SHA-512 +tarball digest recording (ADR 0064). The security properties of the two algorithms are equivalent +for this use; the choice is about verifiability and ecosystem alignment. This ADR therefore settles +the digest algorithm together with the mechanism. + +The question this ADR answers: which mechanism provisions the pinned npm in the publish job, and on +which digest algorithm the pin is recorded, while satisfying ADR 0082's clauses and ADR 0083's +pin-line reachability. + +## Decision Drivers + +- Conformance to ADR 0082's three hard clauses: provisioning before **any** npm invocation + (diagnostics included), verification of the committed distribution digest before first use, and a + single shared definition of version plus digest. +- Reachability of both candidate pin lines (an npm 11.x backport and npm 12.x) on the Node.js 24 + runtime, per ADR 0083 and ADR 0027. +- No bootstrap trust in a floating installer: the verification path must not depend on the + node-bundled npm, whose own version is unpinned. +- Fit with the existing publish boundary: the Go publish command selects npm via an absolute + executable path with basename `npm`; npm-native trusted publishing and the pinned publish argv + remain unchanged per ADR 0024, ADR 0029, and ADR 0083. +- Consistency with the repository's existing pinning stance: full-SHA action pins and `mise.lock`'s + recorded URL plus SHA-256 per tool artifact. +- Every provisioning failure must occur before any registry mutation. + +## Considered Options + +1. Digest-verified registry tarball: download `https://registry.npmjs.org/npm/-/npm-.tgz`, + verify the committed SHA-512 digest over the compressed bytes before extraction, extract, and + execute npm through a launcher named `npm`. +2. `npm install -g npm@` from the node-bundled npm. +3. Corepack `packageManager: "npm@+sha512."` pin. +4. setup-node exact Node patch coupling: pin `node-version: 24.x.y` for the npm its artifact + bundles. +5. Self-hosted vendored tarball: re-host the npm tarball as a project-controlled artifact. +6. Digest-pinned container job: run the publish job in a container image pinned by digest. + +## Decision Outcome + +Chosen option: "Digest-verified registry tarball", because it is the only option that satisfies all +three ADR 0082 hard clauses without bootstrap trust in a floating installer, because it reaches both +pin lines ADR 0083 allows, and because it matches the repository's recorded +URL-plus-committed-digest pinning stance. The pinned digest algorithm is SHA-512 in SRI form — the +registry's native strong digest — so the committed value is byte-identical to the packument's +`dist.integrity` field. + +### Canonical pin data + +The single shared definition required by ADR 0082 is one machine-readable file recording the pinned +npm version, the exact registry tarball URL, and the SHA-512 of the compressed tarball in SRI form +(`sha512-`), byte-identical to the registry packument's `dist.integrity` field. The +provisioning step and the publish-side pin validation both read this file, and the JS/TS npm +provenance and publish specification references it normatively as the allowlist's pin-data source. A +CI check fails on drift between the file and any human-readable allowlist table in the +specification. + +### Provisioning flow + +The publish job provisions npm before any npm invocation, including the existing diagnostics step: + +1. Download the exact recorded URL over HTTPS; no URL, version, or integrity value is derived from + registry metadata at run time. +2. Hash the compressed bytes and require equality with the recorded SHA-512 digest; a mismatch fails + the job before extraction. +3. Extract into a fresh private directory with archive-safety checks (a single `package/` root, no + absolute or parent-traversing entries). +4. Create a launcher named `npm` that execs the setup-node-provided `node` at its absolute path + against the extracted `bin/npm-cli.js`; the launcher's absolute path is passed to the publish + command's `--npm-executable`. No global installation and no `PATH` mutation is required. + +Digest mismatch, download failure, unsafe archive layout, a missing CLI entry point, or a +post-extraction version mismatch all fail the job before any npm execution and before any registry +mutation. Per ADR 0004, the fetch-verify-extract-emit logic lives in the trusted Go boundary; the +workflow step only invokes it. + +### Validation and evidence + +The publish-side version contract remains as ADR 0082 redefined it: the provisioned npm's reported +version must equal the pinned version recorded in the allowlist. The build stage continues to record +its toolchain-bundled npm in `externalParameters.runtime.npm_version` unchanged; the publish npm +version is recorded in the outcome evidence and is not merged into the build-time record. + +### No caching at adoption time + +No download cache is introduced. If one is added later, only the original compressed tarball may be +cached, and the recorded SHA-512 digest must be re-verified after every restore: a cache-key match +does not authenticate cached bytes. + +### Bump-review deep defense + +The ADR 0082 bump procedure gains review-time (not run-time) cross-checks for the recorded pin data: +exact equality of the packument `dist.integrity` with the committed SRI SHA-512 value, consistency +of the legacy `dist.shasum` (SHA-1) over the same bytes, presence of the npm registry ECDSA +signature, and equality of the packument `gitHead` with the npm/cli release tag. These signals all +originate from the registry or the publisher and are not independent provenance; the substance of +the bump review remains the internals diff-review of the extracted distribution per the ADR 0082 +checklist. + +### Implementation timing + +This ADR selects the mechanism only. Implementation timing remains governed by ADR 0083's deferral +scope: the specification amendment, the workflow provisioning step, the Go changes, and the +compatibility fixtures land when the fixed npm release exists and the initial pin is selected. + +### Relationship to existing decisions + +This ADR partially supersedes ADR 0082's designation of SHA-256 as the distribution digest algorithm +— the pinned digest is SHA-512 in SRI form — and amends ADR 0082 by narrowing the open +provisioning-mechanism choice to the digest-verified registry tarball. Every other ADR 0082 clause +remains in force, including the pre-use digest verification, the single shared definition, the +allowlist, the bump procedure, and the build-stage scope exclusion. ADR 0083 is untouched: the +initial pin is still selected when the fixed release ships, and this mechanism serves either pin +line. ADR 0027 is untouched: Node.js 24 remains the runtime and continues to float within the major +line, and the build stage's bundled npm is unaffected. ADR 0024 and ADR 0029 are untouched: +npm-native trusted publishing authenticates the registry mutation, and the pinned publish argv is +unchanged. + +## Pros and Cons of the Options + +### Digest-verified registry tarball + +- Good, because it satisfies all three ADR 0082 hard clauses exactly: provisioning precedes every + npm invocation, the committed digest is verified before first use, and one file is the single + shared definition. +- Good, because the verification path uses runner-provided tools (HTTPS download plus SHA-512) and + never trusts the floating node-bundled npm. +- Good, because npm 11.x and 12.x are equally reachable on Node.js 24, leaving ADR 0083's initial + pin selection unconstrained. +- Good, because the self-contained tarball needs no install step; direct execution from the + extracted tree is verified upstream packaging behavior. +- Good, because it mirrors the `mise.lock` recorded-URL-plus-committed-digest pattern already + applied to the rest of the trust boundary. +- Good, because the pinned digest is byte-identical to the registry-native `dist.integrity`, so the + bump review's registry cross-check is a direct equality check, and the pin format matches npm + ecosystem conventions (package-lock `integrity`, Corepack hashes) and the profile's existing + SHA-512 tarball digest recording (ADR 0064). +- Bad, because it adds a run-time fetch from registry.npmjs.org; the dependency is incremental + rather than new (the publish itself requires the registry), but it is one more pre-mutation + failure point. +- Bad, because the digest algorithm now differs from the `mise.lock` SHA-256 convention used for + development tools, so reviewers handle two digest algorithms across the trust boundary; mitigated + because the npm profile already records SHA-512 tarball digests and the development-tooling and + publish-trust axes are separable. + +### npm install -g npm@\ + +- Good, because it is the simplest possible step and has widespread community precedent. +- Bad, because integrity is verified only against registry metadata fetched at install time (trust + on first use) and no expected-digest input exists, so the committed-digest clause cannot be met. +- Bad, because provisioning begins with an npm invocation by the floating node-bundled npm, + conflicting with the before-any-invocation clause and trusting the unpinned installer. + +### Corepack packageManager pin + +- Good, because hash pinning (`+sha512.`) and caching are built in. +- Bad, because npm shims are not installed by default and the npm team does not treat Corepack as a + supported npm install path; invocation must go through `corepack npm`, which does not fit the + absolute-executable publish boundary. +- Bad, because bundled Corepack lags standalone releases, the 12025-01 registry key rotation broke + older Corepack, and Node.js 25+ no longer distributes Corepack: a shrinking, additional trust + surface. + +### setup-node exact Node patch coupling + +- Good, because it adds no machinery: the bundled npm arrives with the pinned Node artifact. +- Bad, because Node.js 24 artifacts carry npm 11.x only, so the npm 12.x pin line that ADR 0083 may + require is structurally unreachable. +- Bad, because nothing verifies the npm distribution independently (the Node artifact's integrity is + not the npm distribution digest), and npm bumps become coupled to Node patch bumps. + +### Self-hosted vendored tarball + +- Good, because it removes the run-time registry fetch and gives the project full custody of the + bytes. +- Bad, because re-hosting adds storage, licensing, and update-custody burden for a marginal gain: + the same review and the same committed digest are still required, and the fetch merely moves to + the hosting location. + +### Digest-pinned container job + +- Good, because it freezes the whole toolchain under one immutable digest, with the SLSA + container-based builder as precedent. +- Bad, because npm 12.x on Node.js 24 requires a custom image build-and-maintenance pipeline the + repository does not have, the image must also carry the Go toolchain, and ADR 0082's in-job + distribution-digest verification is still required — reducing the option to the tarball mechanism + inside a much heavier environment. + +## More Information + +- Motivating incident and RCA: [issue #97](https://github.com/windlasstech/slsa-builder/issues/97); + upstream [npm/cli#9879](https://github.com/npm/cli/issues/9879) (report) and + [npm/cli#9882](https://github.com/npm/cli/pull/9882) (fix). +- Mechanism investigation sources: + - setup-node inputs (no npm selection): + + - Node-to-npm mapping: + - npm self-contained tarball (`bundleDependencies`): + + - Registry metadata fields: + + - npm registry ECDSA signatures: + - Corepack npm status: ; key-rotation incident: + ; Node.js 25 unbundling: + + - Guidance: SLSA "pin by digest" (); npm + trusted publishing requirements () +- The initial pin data (version, URL, SHA-512) is recorded in the specification's allowlist when ADR + 0083's fixed release is selected, under the ADR 0082 bump procedure as augmented by the + review-time cross-checks above. diff --git a/docs/decisions/AGENTS.md b/docs/decisions/AGENTS.md index 6a8238a..f9bc0b9 100644 --- a/docs/decisions/AGENTS.md +++ b/docs/decisions/AGENTS.md @@ -4,7 +4,7 @@ Architecture decision records for the SLSA builder. Each ADR is a MADR 4.0.0 document with a sequential four-digit number and a kebab-case title. The sequence currently runs from `0000` through -`0083`. +`0084`. ## STRUCTURE @@ -13,22 +13,14 @@ docs/decisions/ ├── 0000-use-markdown-architectural-decision-records.md # MADR template ├── 0001-start-slsa-builder-as-clean-repository.md ├── 0002-use-extensible-trusted-reusable-workflow-foundation.md -├── ... # 0003–0072 -├── 0073-require-published-attestation-run-identity-for-npm-same-run-convergence.md -├── 0074-use-single-job-mutation-segments-with-detection-based-cross-run-safety.md -├── 0075-queue-mutation-segment-contenders-with-queue-max.md -├── 0076-use-observation-preflights-and-first-mutation-classification.md -├── 0077-use-go-native-sigstore-dsse-signer-for-windlass-provenance-signing.md -├── 0078-treat-settings-only-pnpm-workspace-yaml-as-standalone-root-package-mode.md -├── ... # 0079–0080 -├── 0081-pin-npm-oidc-exchange-response-contract-and-correct-token-lifetime-assumption.md -├── ... # 0082 +├── ... # 0003–0082 ├── 0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md +├── 0084-provision-the-publish-stage-npm-cli-from-a-digest-verified-registry-tarball.md ├── README.md / README.ko.md └── AGENTS.md ``` -ADR numbering is sequential from `0000` through `0083`. See the WHERE TO LOOK table below for topic +ADR numbering is sequential from `0000` through `0084`. See the WHERE TO LOOK table below for topic groupings. ## WHERE TO LOOK @@ -62,6 +54,7 @@ groupings. | npm OIDC exchange contract | `0081` | Observed response shape; 15-minute exchange token lifetime. | | Publish-stage npm version pin | `0082` | Integrity-verified provisioning and a reviewed allowlist. | | npm M1 remediation deferral | `0083` | Wait for npm/cli#9882; fixed release opens the allowlist. | +| Publish npm provisioning | `0084` | Digest-verified npm registry tarball with a committed SHA-512. | ## CONVENTIONS diff --git a/docs/decisions/README.ko.md b/docs/decisions/README.ko.md index 915dcbd..2ecd345 100644 --- a/docs/decisions/README.ko.md +++ b/docs/decisions/README.ko.md @@ -28,7 +28,7 @@ ## ADR 목록 ADR 파일은 MADR 4.0.0 문서이며, 네 자리 순번과 kebab-case 제목을 사용합니다. 현재 순번은 `0000`부터 -`0083`까지입니다. +`0084`까지입니다. | 범위 | 주제 | 설명 | | --------- | ------------------------------------- | ------------------------------------------------------------------------------ | @@ -64,6 +64,7 @@ ADR 파일은 MADR 4.0.0 문서이며, 네 자리 순번과 kebab-case 제목을 | 0081 | npm OIDC exchange 응답 계약 | 성공 본문을 실측 형태에 핀; exchange 토큰 수명은 15분. | | 0082 | publish 단계 npm 버전 핀 | 무결성 검증된 publish npm 프로비저닝 + 검토 allowlist. | | 0083 | npm M1 remediation 업스트림 수정 대기 | 초기 publish npm 핀은 npm/cli#9882를 포함하는 첫 검토 릴리스. | +| 0084 | publish npm 프로비저닝 메커니즘 | 커밋된 SHA-512로 검증하는 npm registry tarball 프로비저닝. | ## ADR status와 relations @@ -206,6 +207,7 @@ ADR은 영향을 주는 모든 선행 ADR을 열거해야 하며, 누락은 추 | 0081 | Pin the npm OIDC exchange response contract to the observed shape, correct token lifetime | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0082 | Pin the publish-stage npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0083 | Defer the npm M1 publish remediation to the upstream provenance-file fix | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0084 | Provision the publish-stage npm CLI from a digest-verified registry tarball | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | ### 대체 혹은 폐기된 ADR (과거 맥락으로만 참조) diff --git a/docs/decisions/README.md b/docs/decisions/README.md index f4bb109..cc0d932 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -29,7 +29,7 @@ accepted ADR, stop and write a new ADR rather than editing the accepted ADR body ## ADR inventory ADR files are MADR 4.0.0 documents with sequential four-digit numbers and kebab-case titles. The -sequence currently runs from `0000` through `0083`. +sequence currently runs from `0000` through `0084`. | Range | Topic | Notes | | --------- | ------------------------------------------- | ----------------------------------------------------------------------------------------- | @@ -65,6 +65,7 @@ sequence currently runs from `0000` through `0083`. | 0081 | npm OIDC exchange response contract | Success body pinned to the observed shape; exchange token lifetime is 15 minutes. | | 0082 | Publish-stage npm version pinning | Pinned, integrity-verified publish npm provisioning with a reviewed allowlist. | | 0083 | npm M1 remediation deferred to upstream fix | Initial publish npm pin is the first reviewed release containing npm/cli#9882. | +| 0084 | Publish npm provisioning mechanism | Digest-verified npm registry tarball with a committed SHA-512. | ## ADR status and relations @@ -209,6 +210,7 @@ of this table; they are recorded in each ADR's `relations` frontmatter field. | 0081 | Pin the npm OIDC exchange response contract to the observed shape, correct token lifetime | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0082 | Pin the publish-stage npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0083 | Defer the npm M1 publish remediation to the upstream provenance-file fix | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0084 | Provision the publish-stage npm CLI from a digest-verified registry tarball | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | ### Superseded or deprecated ADRs (historical only) From dad4e93c798bf1034bd1b47586b8fbe36a39e259 Mon Sep 17 00:00:00 2001 From: Yunseo Kim Date: Mon, 17 Aug 2026 19:43:05 +0900 Subject: [PATCH 3/3] docs(decisions): Add ADR 0085 pinning the Node.js 24 patch and asserting the expected bundled npm pair Record the build-stage toolchain decision left open by ADR 0082's scope exclusion: the reusable workflow resolves Node.js to an exact 24 patch recorded together with the npm version that patch bundles, and each job that uses the bundled npm asserts node/npm version equality with the recorded pair before the first npm invocation, failing closed before install or pack. The pair changes only together through a bump procedure with an npm changelog build-impact review, refreshed compatibility fixtures, and the full CI gate. The publish npm remains independently pinned per ADR 0082-0084, and an independently provisioned build npm is reserved behind an explicit escalation trigger. Relations: partially-supersedes ADR-0027 (the floating toolchain resolution clause only) with the reverse edge; see-also ADR-0016/0017/0067/0082/0083/0084 with reverse edges. Index READMEs (both languages) and the decisions AGENTS.md updated. adr-relations-check: 86 ADRs, 216 edges, NO PROBLEMS. Signed-off-by: Yunseo Kim --- ...corepack-for-pnpm-and-yarn-build-stages.md | 2 + ...cit-package-manager-version-enforcement.md | 2 + ...-hosted-ubuntu-2404-and-node-24-runtime.md | 8 + ...verge-repeated-runs-within-run-identity.md | 2 + ...ied-provisioning-and-reviewed-allowlist.md | 2 + ...ion-to-the-upstream-provenance-file-fix.md | 2 + ...from-a-digest-verified-registry-tarball.md | 2 + ...nd-assert-the-expected-bundled-npm-pair.md | 252 ++++++++++++++++++ docs/decisions/AGENTS.md | 6 +- docs/decisions/README.ko.md | 4 +- docs/decisions/README.md | 4 +- 11 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 docs/decisions/0085-pin-the-node-24-patch-version-and-assert-the-expected-bundled-npm-pair.md diff --git a/docs/decisions/0016-use-corepack-for-pnpm-and-yarn-build-stages.md b/docs/decisions/0016-use-corepack-for-pnpm-and-yarn-build-stages.md index aa77297..fdd6d20 100644 --- a/docs/decisions/0016-use-corepack-for-pnpm-and-yarn-build-stages.md +++ b/docs/decisions/0016-use-corepack-for-pnpm-and-yarn-build-stages.md @@ -7,6 +7,8 @@ decision-makers: Yunseo Kim relations: - type: see-also target: ADR-0082 + - type: see-also + target: ADR-0085 --- # Use Corepack for pnpm and Yarn Build Stages diff --git a/docs/decisions/0017-require-explicit-package-manager-version-enforcement.md b/docs/decisions/0017-require-explicit-package-manager-version-enforcement.md index 9229a93..1c3edc7 100644 --- a/docs/decisions/0017-require-explicit-package-manager-version-enforcement.md +++ b/docs/decisions/0017-require-explicit-package-manager-version-enforcement.md @@ -9,6 +9,8 @@ relations: target: ADR-0070 - type: see-also target: ADR-0082 + - type: see-also + target: ADR-0085 --- # Require Explicit Package Manager Version Enforcement diff --git a/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md b/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md index 1f1e33c..9ec7365 100644 --- a/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md +++ b/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md @@ -12,6 +12,14 @@ relations: by the selected Node.js 24 toolchain, floating with the node-24 image); the Node.js 24 runtime selection, the runner and OS image constraints, the caller-input prohibitions, the build-stage npm clauses, and the floor check and actual-version recording clauses remain in force" + - type: partially-superseded-by + target: ADR-0085 + scope: + "the toolchain resolution clause under which the exact Node.js patch version, and therefore + the bundled npm CLI, floats within the Node.js 24 major line between runs of the same builder + release; the Node.js 24 major selection, the runner and OS image constraints, the caller-input + prohibitions, the build-stage bundled-npm coupling, the npm floor check, and the + actual-version recording clauses remain in force" - type: see-also target: ADR-0084 --- diff --git a/docs/decisions/0067-converge-repeated-runs-within-run-identity.md b/docs/decisions/0067-converge-repeated-runs-within-run-identity.md index 6cb3fab..7092af3 100644 --- a/docs/decisions/0067-converge-repeated-runs-within-run-identity.md +++ b/docs/decisions/0067-converge-repeated-runs-within-run-identity.md @@ -34,6 +34,8 @@ relations: target: ADR-0075 - type: see-also target: ADR-0076 + - type: see-also + target: ADR-0085 --- # Converge Repeated Runs Within Run Identity diff --git a/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md b/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md index 256a002..0335a10 100644 --- a/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md +++ b/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md @@ -44,6 +44,8 @@ relations: unspecified: ADR-0082 mandates explicit provisioning before any npm invocation with the distribution digest verified before first use but does not name the mechanism; ADR-0084 narrows the open choice to the digest-verified npm registry tarball mechanism" + - type: see-also + target: ADR-0085 --- # Pin the Publish-Stage npm CLI Version with Integrity-Verified Provisioning and a Reviewed Allowlist diff --git a/docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md b/docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md index cf986da..56d88f7 100644 --- a/docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md +++ b/docs/decisions/0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md @@ -24,6 +24,8 @@ relations: target: ADR-0081 - type: see-also target: ADR-0084 + - type: see-also + target: ADR-0085 --- # Defer the npm M1 Publish Remediation to the Upstream provenance-file Fix and Adopt the Fixed Release as the Initial Publish npm Pin diff --git a/docs/decisions/0084-provision-the-publish-stage-npm-cli-from-a-digest-verified-registry-tarball.md b/docs/decisions/0084-provision-the-publish-stage-npm-cli-from-a-digest-verified-registry-tarball.md index 31ad872..c318cf5 100644 --- a/docs/decisions/0084-provision-the-publish-stage-npm-cli-from-a-digest-verified-registry-tarball.md +++ b/docs/decisions/0084-provision-the-publish-stage-npm-cli-from-a-digest-verified-registry-tarball.md @@ -24,6 +24,8 @@ relations: target: ADR-0083 - type: see-also target: ADR-0027 + - type: see-also + target: ADR-0085 --- # Provision the Publish-Stage npm CLI from a Digest-Verified Registry Tarball diff --git a/docs/decisions/0085-pin-the-node-24-patch-version-and-assert-the-expected-bundled-npm-pair.md b/docs/decisions/0085-pin-the-node-24-patch-version-and-assert-the-expected-bundled-npm-pair.md new file mode 100644 index 0000000..ec85ce1 --- /dev/null +++ b/docs/decisions/0085-pin-the-node-24-patch-version-and-assert-the-expected-bundled-npm-pair.md @@ -0,0 +1,252 @@ +--- +parent: Decisions +nav_order: 85 +status: accepted +date: 12026-08-17 +decision-makers: Yunseo Kim +relations: + - type: partially-supersedes + target: ADR-0027 + scope: + "the toolchain resolution clause under which the exact Node.js patch version, and therefore + the bundled npm CLI, floats within the Node.js 24 major line between runs of the same builder + release; the Node.js 24 major selection, the runner and OS image constraints, the caller-input + prohibitions, the build-stage bundled-npm coupling, the npm floor check, and the + actual-version recording clauses remain in force" + - type: see-also + target: ADR-0016 + - type: see-also + target: ADR-0017 + - type: see-also + target: ADR-0067 + - type: see-also + target: ADR-0082 + - type: see-also + target: ADR-0083 + - type: see-also + target: ADR-0084 +--- + +# Pin the Node.js 24 Patch Version and Assert the Expected Bundled npm Pair + +## Context and Problem Statement + +The JS/TS npm package profile's build stage uses the npm CLI bundled with the Node.js 24 toolchain, +and the reusable workflow resolves that toolchain with a floating `node-version: "24"` in all three +jobs. Between two runs of the same builder release, a node-24 image update can silently change both +the Node.js patch version and the bundled npm CLI: the toolchain is recorded +(`externalParameters.runtime.npm_version`, floor `>= 11.5.1`) but not fixed. ADR 0027 recorded the +intent that "the exact Node.js patch version is selected by the builder implementation and its +pinned setup mechanism for a given builder release" — an intent the floating workflow never +implemented. + +ADR 0082 pinned the publish-stage npm and deliberately left build-stage selection unchanged, while +recording that "any future parity with the manifest-first package-manager policy of ADR 0015–0017" +is explicitly out of its scope — leaving the build-stage question open for review on its own merits. +That review (12026-08-17) established the following facts: + +- The publish-incident driver does not transfer: build-stage npm failures are detected before any + registry mutation, so no version numbers are burned. The material build-stage risk is different: + silent semantic drift — a run that succeeds while producing a different install tree or different + packed tarball bytes, validly attested. +- The drift surface is real even within lockfileVersion 3 (stable across npm 9–12): npm 11 changed + optional peer and platform resolution (npm/cli#9343), npm 11.6.2 broke `npm ci` at patch level + (npm/cli#8669), npm 10.8.1 changed workspace `node_modules` cleanup (npm/cli#7692), `npm pack` + glob behavior changed across npm 8→9→10 (npm/cli#6330, npm/cli#6936, npm/cli#7514), npm 10.5 + changed pack output behavior (npm/cli#7158, npm/cli#7561), and npm 12 blocks dependency lifecycle + scripts by default and drops `npm-shrinkwrap.json` support. +- Node.js majors have historically bumped the bundled npm major mid-line (Node.js 18 shipped npm 8 + and later 9/10; Node.js 20 shipped npm 9 and later 10). "Node.js 24 bundles npm 11.x" is an + observation about today's images, not a guarantee for the line's lifetime. +- ADR 0067's same-run convergence relies on byte-reproducible pack output for identical content; a + different bundled npm between attempts of one run identity can defeat the recomputation fallback + after an earlier mutation has committed. +- The specification requires a PURL-derivation compatibility fixture "for every supported npm CLI + version"; while the build-stage npm floats, the supported set on the build path is not enumerable. +- Caller-declared npm versions (via Corepack or via the ADR 0084 tarball mechanism, with or without + a builder allowlist) conflict with the builder-owned npm principle of ADR 0016 and ADR 0017, + reaffirmed by ADR 0082; the npm CLI does not read the top-level `packageManager` field, so + manifest parity would be a builder-enforced convention rather than npm behavior; and arbitrary + caller versions would re-open the fixture enumerability problem. An independent builder-pinned + build npm (the ADR 0084 mechanism) is viable but requires amending the closed v1 + `resolvedDependencies` schema (ADR 0070) and adds a run-time registry fetch to the build job — + more change than the build-stage evidence justifies today. + +The question this ADR answers: how the build stage's Node.js/npm toolchain is fixed per builder +release, at the smallest contract change that converts toolchain drift into a review event. + +## Decision Drivers + +- One builder release must mean one deterministic Node.js/npm toolchain pair; drift between runs of + the same release becomes impossible without a reviewed bump. +- The supported npm set on the build path must be enumerable, so the per-version compatibility + fixture obligation is satisfiable. +- ADR 0067 convergence must hold across attempts of one run identity: the same builder release must + reproduce identical pack bytes for identical content. +- Minimal contract change: no new production tool, no run-time registry fetch, no Corepack, no new + distribution descriptor, no caller-facing change. +- The builder-owned npm principle (ADR 0016, ADR 0017, reaffirmed by ADR 0082) and the publish/build + separation (ADR 0082–0084) are preserved. +- Honest scope: this decision stabilizes the Node.js/npm pair; it does not claim full build + reproducibility, because the runner image, native tooling, and network inputs still drift. + +## Considered Options + +1. Pin the Node.js 24 patch per builder release and assert the expected bundled npm pair before + first npm use. +2. Keep the floating `node-version: "24"` resolution (status quo). +3. Honor caller manifest-declared npm versions (via Corepack or the digest-verified tarball, with or + without a builder allowlist). +4. Provision a builder-pinned, independently selected build-stage npm via the ADR 0084 + digest-verified tarball mechanism (an independent build allowlist, or one shared with the publish + pin). + +## Decision Outcome + +Chosen option: "Pin the Node.js 24 patch per builder release and assert the expected bundled npm +pair", because it converts toolchain drift into a review event and makes the supported set +enumerable with the smallest possible contract change, because it implements ADR 0027's recorded +pinned-setup intent rather than reversing any accepted decision, and because the heavier +alternatives are either inconsistent with builder-owned npm or buy version freedom the build stage +does not currently need. + +### The pinned toolchain pair + +The single shared definition of the build toolchain is one builder-owned machine-readable record +holding the pair: the exact Node.js 24 patch version and the npm version that exact patch bundles, +cross-checked against the Node.js release metadata (). The +reusable workflow's Node.js setup uses the exact recorded patch, never a floating major, and the two +values change only together, in one commit, through the bump procedure below. A CI check fails on +drift between the record and any human-readable runtime table in the specifications. + +### Pre-install pair assertion + +In each job that uses the bundled npm, before the first npm invocation, the job observes +`node --version` and `npm --version` and requires exact equality with the recorded pair; a mismatch +fails the job before any install, build, or pack step. The observed npm version continues to be +recorded in `externalParameters.runtime.npm_version` exactly as today — the recording semantics of +ADR 0027, ADR 0070, and ADR 0071 are unchanged; the value is now validated against the pin rather +than merely observed. + +The publish job is governed by ADR 0082 and ADR 0084 unchanged: it provisions the pinned, +digest-verified npm before any npm invocation, diagnostics included, and the bundled npm is never +invoked there. The pair assertion covers the publish job's Node.js runtime only. + +### Bump procedure + +A toolchain-pair bump pull request must: + +1. update the Node.js patch version and the expected bundled npm version together, cross-checked + against the Node.js release metadata; +2. review the npm changelog delta for build-affecting changes: install and Arborist resolution, + workspace behavior, lifecycle-script policy, and pack, packlist, and pack output behavior; +3. add or refresh the compatibility fixtures for the new pair; +4. pass the full CI gate on the new pair. + +Node.js security releases follow the same procedure on an expedited cadence; the project owns +adoption timing rather than inheriting it from image maintenance. + +### Scope and non-goals + +- The bundled npm is authenticated as part of the Node.js artifact, through the pinned setup + mechanism's own checksum verification, not independently as an npm distribution. ADR 0070's + conclusion that npm receives no package-manager-distribution descriptor remains valid, and the + `resolvedDependencies` v1 schema is untouched. +- This ADR does not pin the runner image, does not introduce container jobs, and does not claim full + build reproducibility. +- Caller manifests still must not select an npm version on any stage; ADR 0015, ADR 0016, and ADR + 0017 are untouched, and a manifest `packageManager: "npm@x.y.z"` still selects npm while its + version component is ignored. +- The `>= 11.5.1` floor check on `runtime.npm_version` is retained as defense in depth; its + rationale for the build stage is now independent of trusted publishing, which the separately + pinned publish npm governs. +- The build toolchain pair and the publish npm pin are independent records with independent review + cadences; neither selects the other. The PURL-derivation compatibility fixture's ownership moves + to the publish allowlist in the deferred ADR 0083 specification amendment; this ADR does not + change that fixture contract. + +### Escalation trigger + +An independently provisioned build-stage npm — the ADR 0084 mechanism with a separate build pin +record, never the publish pin's record — is revisited when any of the following occurs: + +- a build-required npm version is unavailable from any acceptable Node.js 24 patch; +- Node.js and npm security or update cadences become irreconcilable; +- the Node.js 24 line adopts an npm major whose build-behavior changes the project can neither + accept nor avoid within the line. + +### Relationship to existing decisions + +This ADR partially supersedes ADR 0027's floating toolchain resolution only — implementing its +recorded pinned-setup intent — and every other ADR 0027 clause remains in force: the Node.js 24 +major selection, the Ubuntu 24.04 runner constraints, the caller-input prohibitions, the build-stage +bundled-npm coupling, the npm floor check, and the actual-version recording. ADR 0014 through ADR +0017 are untouched: npm stays builder-owned, and caller manifests do not select npm versions. ADR +0067 is unchanged, and its convergence assumption is strengthened. ADR 0070 and ADR 0071 are +untouched. ADR 0082, ADR 0083, and ADR 0084 are untouched: the publish npm pin, the initial pin +selection, the deferral scope, and the provisioning mechanism are all independent of this decision. +Implementation of this ADR is not blocked by ADR 0083's deferral — it does not depend on +npm/cli#9882 — and the workflow pin, the assertion step, the specification amendments, and the +fixtures may land on their own schedule. + +## Pros and Cons of the Options + +### Pin the Node.js 24 patch and assert the bundled npm pair + +- Good, because one builder release deterministically means one Node.js/npm pair, and every + toolchain change becomes a reviewed bump with fixtures. +- Good, because the supported npm set on the build path becomes enumerable, making the per-version + compatibility fixture obligation satisfiable. +- Good, because ADR 0067's byte-reproducibility assumption holds across attempts of one run + identity. +- Good, because it adds no production tool, no run-time fetch, no Corepack, no distribution + descriptor, and no caller-facing change — the smallest contract change that closes the drift + surface, implementing ADR 0027's recorded intent. +- Bad, because npm updates are coupled to Node.js patch releases: an npm security fix arrives only + when an acceptable Node.js patch carries it, and Node.js security bumps force an npm behavior + review on the project's cadence. +- Bad, because npm 12 is unreachable on the Node.js 24 line; accepted because the build stage has no + npm 12 requirement today and the escalation trigger covers a future one. + +### Keep the floating resolution + +- Good, because it adds no machinery, and Node.js and npm security updates arrive automatically. +- Bad, because the same builder SHA can silently produce different install trees and different pack + bytes after an image update — drift that succeeds rather than fails, validly attested. +- Bad, because the supported set stays unenumerable, the convergence fallback stays exposed to + cross-attempt drift, and a future mid-line npm major (for example npm 12's lifecycle-script + defaults) would arrive unannounced. + +### Caller manifest-declared npm + +- Good, because the caller's CI toolchain could match their local tooling, and the `packageManager` + field is an ecosystem convention. +- Bad, because the npm CLI does not read the top-level `packageManager` field — the parity is + illusory outside the builder — and the option conflicts with ADR 0016, ADR 0017, and ADR 0082's + reaffirmation that callers must not select npm versions on any stage. +- Bad, because Corepack is not an npm-supported install path and is unbundled from Node.js 25+, and + arbitrary caller versions re-open fixture enumerability and multiply the review surface. + +### Builder-pinned independent build-stage npm + +- Good, because it frees the npm version from the Node.js line, reuses the reviewed ADR 0084 + mechanism, and keeps the supported set enumerable. +- Bad, because an independently fetched npm is no longer bundled with the toolchain: ADR 0070's + no-descriptor conclusion falls, the closed v1 `resolvedDependencies` schema must change, and the + build job gains a run-time registry fetch and a second review cadence — more change than the + build-stage evidence justifies today; reserved through the escalation trigger. + +## More Information + +- Motivating analysis: the 12026-08-17 build-stage npm version review, with the option space framed + as selection authority × provisioning mechanism. +- Node.js release-to-npm mapping: . Node.js mid-line npm major + bumps: the Node.js 18 and 20 release lines. +- npm build-behavior drift evidence: npm/cli#9343 (optional platform resolution), npm/cli#8669 + (11.6.2 `npm ci` regression), npm/cli#7692 (workspace cleanup), npm/cli#6330, npm/cli#6936, and + npm/cli#7514 (pack glob changes), npm/cli#7158 and npm/cli#7561 (pack output), and the npm 12 + changelog (dependency lifecycle scripts blocked by default; `npm-shrinkwrap.json` dropped). +- npm does not consume the top-level `packageManager` field: + . +- The initial pair values are recorded in the builder repository when the implementing change lands + and are reflected in the JS/TS npm build pack specification's runtime table. diff --git a/docs/decisions/AGENTS.md b/docs/decisions/AGENTS.md index f9bc0b9..2dd61f0 100644 --- a/docs/decisions/AGENTS.md +++ b/docs/decisions/AGENTS.md @@ -4,7 +4,7 @@ Architecture decision records for the SLSA builder. Each ADR is a MADR 4.0.0 document with a sequential four-digit number and a kebab-case title. The sequence currently runs from `0000` through -`0084`. +`0085`. ## STRUCTURE @@ -16,11 +16,12 @@ docs/decisions/ ├── ... # 0003–0082 ├── 0083-defer-npm-m1-publish-remediation-to-the-upstream-provenance-file-fix.md ├── 0084-provision-the-publish-stage-npm-cli-from-a-digest-verified-registry-tarball.md +├── 0085-pin-the-node-24-patch-version-and-assert-the-expected-bundled-npm-pair.md ├── README.md / README.ko.md └── AGENTS.md ``` -ADR numbering is sequential from `0000` through `0084`. See the WHERE TO LOOK table below for topic +ADR numbering is sequential from `0000` through `0085`. See the WHERE TO LOOK table below for topic groupings. ## WHERE TO LOOK @@ -55,6 +56,7 @@ groupings. | Publish-stage npm version pin | `0082` | Integrity-verified provisioning and a reviewed allowlist. | | npm M1 remediation deferral | `0083` | Wait for npm/cli#9882; fixed release opens the allowlist. | | Publish npm provisioning | `0084` | Digest-verified npm registry tarball with a committed SHA-512. | +| Build toolchain pair pin | `0085` | Exact Node.js 24 patch per builder release; bundled npm asserted pre-use. | ## CONVENTIONS diff --git a/docs/decisions/README.ko.md b/docs/decisions/README.ko.md index 2ecd345..3a2a7e4 100644 --- a/docs/decisions/README.ko.md +++ b/docs/decisions/README.ko.md @@ -28,7 +28,7 @@ ## ADR 목록 ADR 파일은 MADR 4.0.0 문서이며, 네 자리 순번과 kebab-case 제목을 사용합니다. 현재 순번은 `0000`부터 -`0084`까지입니다. +`0085`까지입니다. | 범위 | 주제 | 설명 | | --------- | ------------------------------------- | ------------------------------------------------------------------------------ | @@ -65,6 +65,7 @@ ADR 파일은 MADR 4.0.0 문서이며, 네 자리 순번과 kebab-case 제목을 | 0082 | publish 단계 npm 버전 핀 | 무결성 검증된 publish npm 프로비저닝 + 검토 allowlist. | | 0083 | npm M1 remediation 업스트림 수정 대기 | 초기 publish npm 핀은 npm/cli#9882를 포함하는 첫 검토 릴리스. | | 0084 | publish npm 프로비저닝 메커니즘 | 커밋된 SHA-512로 검증하는 npm registry tarball 프로비저닝. | +| 0085 | 빌드 단계 Node.js 패치·npm 쌍 핀 | 빌더 릴리스당 정확한 Node.js 24 패치; 첫 사용 전 번들 npm 버전 단언. | ## ADR status와 relations @@ -208,6 +209,7 @@ ADR은 영향을 주는 모든 선행 ADR을 열거해야 하며, 누락은 추 | 0082 | Pin the publish-stage npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0083 | Defer the npm M1 publish remediation to the upstream provenance-file fix | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0084 | Provision the publish-stage npm CLI from a digest-verified registry tarball | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0085 | Pin the Node.js 24 patch version and assert the expected bundled npm pair | JS/TS npm package profile, JS/TS npm build and pack, JS/TS npm provenance and publish, verification policy and fixtures | ### 대체 혹은 폐기된 ADR (과거 맥락으로만 참조) diff --git a/docs/decisions/README.md b/docs/decisions/README.md index cc0d932..96d43f1 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -29,7 +29,7 @@ accepted ADR, stop and write a new ADR rather than editing the accepted ADR body ## ADR inventory ADR files are MADR 4.0.0 documents with sequential four-digit numbers and kebab-case titles. The -sequence currently runs from `0000` through `0084`. +sequence currently runs from `0000` through `0085`. | Range | Topic | Notes | | --------- | ------------------------------------------- | ----------------------------------------------------------------------------------------- | @@ -66,6 +66,7 @@ sequence currently runs from `0000` through `0084`. | 0082 | Publish-stage npm version pinning | Pinned, integrity-verified publish npm provisioning with a reviewed allowlist. | | 0083 | npm M1 remediation deferred to upstream fix | Initial publish npm pin is the first reviewed release containing npm/cli#9882. | | 0084 | Publish npm provisioning mechanism | Digest-verified npm registry tarball with a committed SHA-512. | +| 0085 | Build-stage Node.js patch and npm pair pin | Exact Node.js 24 patch per builder release; bundled npm asserted before first use. | ## ADR status and relations @@ -211,6 +212,7 @@ of this table; they are recorded in each ADR's `relations` frontmatter field. | 0082 | Pin the publish-stage npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0083 | Defer the npm M1 publish remediation to the upstream provenance-file fix | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0084 | Provision the publish-stage npm CLI from a digest-verified registry tarball | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0085 | Pin the Node.js 24 patch version and assert the expected bundled npm pair | JS/TS npm package profile, JS/TS npm build and pack, JS/TS npm provenance and publish, verification policy and fixtures | ### Superseded or deprecated ADRs (historical only)