Skip to content

feat(npmprofile): Pin the publish npm CLI and Node.js toolchain with digest-verified provisioning - #131

Merged
yunseo-kim merged 24 commits into
mainfrom
feat/npm-toolchain-pins
Oct 13, 2026
Merged

yunseo-kim merged 24 commits into
mainfrom
feat/npm-toolchain-pins

Conversation

@yunseo-kim

@yunseo-kim yunseo-kim commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Summary

Implements ADR 0082/0083/0084 (publish-stage npm CLI pin) and ADR 0085 (build-stage Node.js toolchain pair pin), and completes the in-repo preparation for the M1 dogfood retry as vers-js v0.1.3.

What changed:

  • Pins (machine-readable, embedded): internal/toolchainpins/publish-npm.json pins the publish npm CLI to 11.21.0 (tarball URL + SHA-512 SRI, byte-identical to the packument dist.integrity); internal/toolchainpins/node-runtime.json pins the build toolchain pair to Node.js 24.21.0 / npm 11.19.0. Both change only through the bump pull-request procedures those ADRs define (pin and digest or pair updated together in one commit, internals/changelog review, fixtures, full CI gate); a version bump does not require an ADR amendment.
  • Publish-stage provisioning (ADR 0084): new npm-profile-provision-npm subcommand downloads only the recorded URL through the hardened fetcher, verifies the compressed SHA-512 before creating any directory, extracts with the no-links archive policy, validates the extracted package.json version, and emits a private launcher named npm. Provisioning executes neither node nor npm; every failure is pre-mutation with registered diagnostics.
  • Pair assertion (ADR 0085): new npm-profile-toolchain-assert subcommand requires exact node --version / npm --version equality with the recorded pair before the first npm invocation (build/sign jobs; node-only mode in the publish job).
  • Publish pin validation (ADR 0082): the publish job's npm version must equal the allowlist pin, replacing the build/publish equality check; the observed version is recorded as diagnostic_metadata.publish_npm_version on every path reaching Publish, including same-run convergence.
  • Static contract + drift gate: workflow-check enforces the exact Node patch, assertion steps, provisioning ordering, and zero ambient npm in the publish job; new pin-check subcommand (wired into lint.yml) fails on drift between the records and the spec tables.
  • Workflow: all three jobs set up Node.js 24.21.0; the publish job provisions the pinned npm 11.21.0 before any npm invocation, diagnostics included.
  • Fixtures: PURL compatibility fixtures for npm 11.21.0 and 11.19.0 (scoped + unscoped), cross-checked live against both real CLIs' own npm-package-arg.toPurl provenance-subject code path.
  • Docs: spec amendments (allowlist + review note, provisioning, pin validation, auth path finalization per ADR 0083, pair table, npm invocation definition), dogfood runbook retargeted to v0.1.3, README (English/Korean) publish-stage nuance, CHANGELOG [Unreleased] entries, and the attempt-4 dogfood evidence record with forward metadata retargeted.

Why: the fourth M1 dogfood published @windlass/[email protected] with npm-generated provenance because npm silently discarded the supplied --provenance-file bundle (#97). The upstream fix (npm/cli#9882) shipped in npm 12.1.0 and 11.20.0; ADR 0083 selects the fixed release as the initial publish pin, and the operator selected npm 11.21.0, the latest 11-line release carrying the fix. Pinning both toolchains converts toolchain drift into reviewed bump events. The pinned 11.21.0 passed the ADR 0082 internals checklist and ADR 0084 registry cross-checks (recorded in the spec's allowlist review note, including the documented deviation that npm/cli published no git tag or GitHub Release for v11.21.0, so the gitHead cross-check resolves at the release-commit level).

How to test: go test ./..., go test -race ./..., golangci-lint run ./..., actionlint .github/workflows/*.yml, pnpm exec prettier --check ., pnpm exec markdownlint-cli2 "**/*.md", fixture-check, workflow-check, pin-check, and the gated live evidence SLSA_BUILDER_LIVE_TOOLCHAIN=1 go test -run 'TestProvisionNpmLive|TestNPMPURLCLICompatibilityLive' ./internal/npmprofile/ -v.

Related Issues

Change Type

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

Changelog

  • Category: Added
  • User-facing note: the JS/TS npm profile now pins the publish-stage npm CLI (initially npm 11.21.0, carrying the upstream fix: provenance-file takes precedence over OIDC auto-generated provenance npm/cli#9882 --provenance-file fix) provisioned from a digest-verified registry tarball, and pins the build-stage Node.js/npm toolchain pair (currently Node.js 24.21.0 / npm 11.19.0) asserted before first use; publish-side npm validation compares against the allowlist pin and records publish_npm_version outcome evidence; a pin-check CI gate fails on drift between the machine-readable pin records and the specification tables.

Changelog update:

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

Checklist

General

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

CI/Workflow Changes (if applicable)

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

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

Protocol / Compatibility Impact

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

If impacted, describe compatibility impact:

The publish-side npm version contract changes from exact equality with the build-time npm record to allowlist pin validation (ADR 0082); the diagnostics report schema gains the optional closed diagnostic_metadata.publish_npm_version member following the package_manifest extension precedent; new conformance fixtures cover the PURL compatibility family for both pinned npm versions. The project is pre-release and the profile workflow is referenced by SHA, so no caller observes any change until it re-pins to a SHA carrying this merge; no versioned migration is required.

Testing

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

Describe test evidence:

  • go test ./... and go test -race ./... green (all 16 packages); golangci-lint run ./... 0 issues; actionlint clean; Prettier and markdownlint clean.
  • Executable contract gates green: fixture-check, workflow-check --profile npm-only, and the new pin-check (repo-state GREEN; a spec copy with one flipped SRI character fails exit 1 with toolchain-pin-drift).
  • Live evidence (gated, non-CI): TestProvisionNpmLive downloads the recorded npm 11.21.0 tarball, verifies its SHA-512 against the pinned SRI, and reports npm 11.21.0 from the provisioned launcher; TestNPMPURLCLICompatibilityLive exercises both pinned CLIs' own npm-package-arg.toPurl provenance-subject code path for all four fixtures (4/4 pass).
  • TDD throughout: RED outputs captured before every behavior change (pin validation divergence/mismatch cases, convergence metadata, drift table mutations, checker contract fixtures).
  • Independent review: local cubic review (2 findings triaged as design-consistent fail-closed tradeoffs) and an adversarial reviewer pass that cited 4 blockers (setup-node version logging, fixture CLI evidence, tag-less release cross-check, convergence metadata); all 4 resolved, including two by explicit operator decision recorded in the spec, and the delta re-review approved.

Documentation

  • README updated
  • Spec/docs updated
  • Changelog decision completed above

Rollout / Risk

  • Risk level: Medium
  • Rollback plan: revert the merge commit. Callers pin the reusable workflow by SHA, so merging changes no caller behavior until the caller is re-pinned; the fail-closed post-publish read-back remains the permanent safety net for anything outside the pinned surface. The remaining rollout steps are external: bump the vers-js caller pin to a main SHA carrying this work, then dispatch the v0.1.3 dogfood per docs/dogfood/npm-m1-verification-procedure.md phases 1-6.

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

Pins the publish-stage npm CLI and the build-stage Node.js/npm toolchain pair with digest-verified provisioning. Previously the publish job used the npm bundled with Node.js 24 and required the publish npm version to equal the build-time npm version; the latest dogfood attempt published npm-generated provenance because npm discarded the supplied --provenance-file bundle. The publish stage now provisions npm 11.21.0, the latest 11-line release carrying the upstream fix, from a digest-verified registry tarball before any npm invocation.

Provisioning and assertion

  • New npm-profile-provision-npm downloads only the recorded tarball, verifies the SHA-512 SRI, extracts with the no-links archive policy and explicitly bounded copies, and emits a private launcher; it never executes node or npm, and every failure stays pre-mutation.
  • The publish execution boundary accepts exactly the provisioned private launcher layout and rejects every sibling layout.
  • New npm-profile-toolchain-assert requires exact node --version/npm --version equality with the pinned pair (Node.js 24.21.0 / npm 11.19.0) before first npm use; the publish job asserts node only.
  • Publish now validates npm against the allowlist pin instead of equality with the build record, and records the observed version as publish_npm_version on every path that reaches publish, including same-run convergence.
  • New pin-check CI gate fails on drift between the machine-readable pin records and the spec tables.

Workflow, fixtures, and rollout

  • All three workflow jobs set up Node.js 24.21.0; the publish job provisions npm 11.21.0 before any npm invocation including diagnostics, and ambient npm resolution is gone from the job.
  • The static checker rejects skippable assertions, ambient or hardcoded executable rebinding, spoofed --npm-executable arguments, and any preflight-report path binding other than the uploaded outcome report; preflight failures persist typed reports to the outcome path.
  • Added PURL compatibility fixtures for both pinned npm versions, cross-checked live against each CLI's own provenance-subject derivation; diagnostic metadata now rejects null members at any depth.
  • Callers pin the workflow by SHA, so no behavior changes until they re-pin; remaining steps are the vers-js caller pin bump to a main SHA carrying this work (now verified against the caller's uses: reference via a ?ref=main fetch) and the v0.1.3 dogfood dispatch.

Written for commit f69846e. Summary will update on new commits.

View guided diff Turn on auto-fix

Implement the ADR 0082/0083/0084 specification surface for the npm M1
dogfood retry:

- Add the publish-stage npm CLI allowlist with the initial entry
  npm 11.21.0 (tarball URL + SHA-512 SRI pin data), the 12026-10-13
  internals-checklist and registry cross-check review note, and the
  recorded deviation that npm/cli published no git tag or GitHub
  Release for v11.21.0 so the gitHead cross-check resolves at the
  release-commit level.
- Specify digest-verified registry-tarball provisioning before any
  npm invocation in the publish job, including diagnostics.
- Redefine the publish-side version contract as allowlist pin
  validation, replacing the build/publish npm equality check, and
  record the publish npm version in outcome evidence as
  publish_npm_version.
- Finalize the authentication path per ADR 0083: npm-native trusted
  publishing with the ADR 0076 exchange preflight retained as a
  non-mutating observation whose minted token is discarded.
- Register the five new publish-side diagnostics
  (npm-provision-download, npm-provision-digest-mismatch,
  npm-provision-archive-unsafe, npm-provision-version-mismatch,
  publish-npm-pin-mismatch) in the closed registry and the shared
  taxonomy.

Signed-off-by: Yunseo Kim <[email protected]>
- Add the pinned runtime table (Node.js 24.21.0, bundled npm 11.19.0)
  to the build and pack specification with
  internal/toolchainpins/node-runtime.json as the single shared
  machine-readable definition and a CI drift check.
- Specify the pre-install pair assertion: exact node/npm version
  equality with the recorded pair before the first npm invocation in
  each job that uses the bundled npm, failing closed with
  toolchain-pair-mismatch; the publish job runs the assertion in
  node-only mode under ADR 0082/0084.
- Replace floating Node.js 24 descriptions in the build-pack and
  package-profile specifications with the exact pinned patch; retain
  the npm >= 11.5.1 floor and runtime.npm_version recording semantics
  as defense in depth.
- Complete the PURL-derivation compatibility fixture keying with the
  build-side half and register toolchain-pair-mismatch and
  toolchain-pin-drift in the closed registry and shared taxonomy.

Signed-off-by: Yunseo Kim <[email protected]>
Create internal/toolchainpins holding the two builder-owned
machine-readable records as embedded files with strictly decoded,
concurrency-safe typed loaders:

- publish-npm.json: the ADR 0082/0084 publish-stage npm pin
  (npm 11.21.0, exact registry tarball URL, SHA-512 SRI byte-identical
  to the packument dist.integrity).
- node-runtime.json: the ADR 0085 build toolchain pair
  (Node.js 24.21.0, bundled npm 11.19.0).

Decoding reuses canonicaljson duplicate-member rejection, rejects
unknown fields, and validates exact three-part SemVer versions,
uncredentialed HTTPS tarball URLs, and sha512 SRI form with a 64-byte
digest. go:embed makes the single-shared-definition property
structural: the provisioning step, publish-side pin validation, and
the pair assertion all read these records by import.

Signed-off-by: Yunseo Kim <[email protected]>
…rball

Implement ADR 0084 provisioning and the npm-profile-provision-npm
subcommand:

- Download only the embedded pin URL through the hardened fetcher,
  verify the compressed SHA-512 against the pinned SRI before creating
  any directory, extract into a private per-job directory with the
  existing no-links archive policy (duplicate-entry rejection, bounded
  decompressed bytes and entry count, gzip drain against truncation),
  and validate the extracted package.json version and CLI entry
  against the pin.
- Emit a private launcher named npm that execs the resolved absolute
  node path against package/bin/npm-cli.js; provisioning executes
  neither node nor npm, keeping every failure pre-execution and
  pre-mutation. Partial extractions are removed on failure.
- Map failures to the registered diagnostics npm-provision-download,
  npm-provision-digest-mismatch, npm-provision-archive-unsafe, and
  npm-provision-version-mismatch (check npm.publish.provision).
- Register FuzzExtractNPMDistribution in both fuzz CI matrices per the
  testing guide.

Verified live (SLSA_BUILDER_LIVE_TOOLCHAIN=1): the recorded URL
serves bytes whose SHA-512 equals the pinned SRI and the provisioned
launcher reports npm 11.21.0.

Signed-off-by: Yunseo Kim <[email protected]>
Implement ADR 0085 pre-install pair assertion and the
npm-profile-toolchain-assert subcommand:

- Observe node --version and npm --version through the exec boundary
  and require exact equality with the embedded pair record
  (Node.js 24.21.0, bundled npm 11.19.0) before any install, build,
  or pack step; mismatch fails closed with toolchain-pair-mismatch
  (check npm.build.toolchain).
- --node-only mode asserts the Node.js runtime without looking up or
  executing npm, for the publish job where the bundled npm is never
  invoked (ADR 0082/0084).
- The npm >= 11.5.1 floor and Node-major-24 gate in toolchain.go are
  unchanged as defense in depth.

Real-surface evidence: npm-profile-toolchain-assert --node-only
passes on the pinned development runtime (node v24.21.0).

Signed-off-by: Yunseo Kim <[email protected]>
Extend the closed diagnostic_metadata report shape with the optional
publish_npm_version string member (exact three-part SemVer, non-trust
observational metadata, never merged into the build-time
externalParameters.runtime.npm_version record), following the
package_manifest extension precedent, and name the exact placement in
the publish pin-validation section: the outcome report's
diagnostic_metadata on success and on observed failures, plus the
failing diagnostic's evidence on pin mismatch.

Signed-off-by: Yunseo Kim <[email protected]>
Replace the publish-side build/publish npm-version equality check
with ADR 0082 pin validation:

- validateNPMVersion now compares the observed publish npm version
  against the embedded allowlist pin (npm 11.21.0); a mismatch fails
  closed with publish-npm-pin-mismatch (check npm.publish.toolchain)
  before any registry mutation, carrying the observed version in the
  failing diagnostic's evidence.
- The observed publish npm version is recorded as
  diagnostic_metadata.publish_npm_version on success and on failures
  after observation, per the extended closed report contract
  (diagnostic.go + strict metadata decoding in metadata_json.go).
- The build-stage externalParameters.runtime.npm_version recording
  and the --npm-executable path validation are unchanged; failed or
  malformed version observation keeps the existing
  builder-version-mismatch fallback without fabricated metadata.

RED-first semantic proof: publish 11.21.0 with build-observed 11.19.0
now passes, and publish 11.20.0 equal to the build record now fails
against the pin with no mutation attempted.

Signed-off-by: Yunseo Kim <[email protected]>
Teach the static workflow checker the ADR 0082/0084/0085 contract:

- All three jobs of the npm reusable workflow must set up Node.js at
  the exact patch from the embedded pair record (24.21.0), replacing
  the floating "24" literal in the build job and adding the check to
  the provenance-sign and publish jobs.
- Build and sign jobs must run npm-profile-toolchain-assert before
  any step whose run block invokes npm.
- The publish job must run npm-profile-toolchain-assert --node-only,
  then an identified npm-profile-provision-npm step ordered before
  the diagnostics and publish steps; every npm reference in the job
  must bind to the provisioning step's output expression, and ambient
  npm resolution (including command -v npm) is rejected.

Command-layer writer tests now use an inline conforming fixture
instead of the (currently nonconforming) production workflow; the
production workflow check itself is the expected-RED gate until the
workflow is updated.

Signed-off-by: Yunseo Kim <[email protected]>
Implement the ADR 0084/0085 drift gate:

- pin-check compares the disk records against the human-readable spec
  tables: the publish allowlist row (version, tarball URL, SHA-512
  SRI) in the provenance-publish spec and the runtime pair row in the
  build-pack spec, requiring exact equality and failing with
  toolchain-pin-drift (check toolchain.pins.drift) on any drift,
  missing table, or unparsable row. Markdown table parsing tolerates
  Prettier padding and inline-code delimiters but is exact on data
  values.
- Wire pin-check into the existing Go job in lint.yml with no
  permission or hardening changes.

Real-surface artifacts: repo-state run prints "Toolchain pin records
match specification tables." (exit 0); a spec copy with one flipped
SRI character fails exit 1 with a canonical toolchain-pin-drift
report.

Signed-off-by: Yunseo Kim <[email protected]>
Land the ADR 0082/0084/0085 toolchain contract on the npm reusable
workflow:

- All three jobs set up Node.js at the exact pinned patch 24.21.0.
- The build and provenance-sign jobs assert the pinned toolchain pair
  (Node.js 24.21.0, bundled npm 11.19.0) immediately after setup,
  before any npm invocation.
- The publish job asserts the pinned Node.js runtime in node-only
  mode, then provisions the digest-verified npm 11.21.0 CLI before
  any npm invocation including diagnostics; the diagnostics and
  publish steps invoke npm only through the provisioning step's
  npm-executable output, and ambient npm resolution is gone from the
  job.

Verified: actionlint clean, prettier clean, and workflow-check
--profile npm-only passes with the pinned-toolchain contract checks.

Signed-off-by: Yunseo Kim <[email protected]>
Add the spec-required PURL-derivation compatibility fixture family,
one key per supported npm CLI version with scoped and unscoped
coverage:

- npm-purl-publish-11-21-0-{scoped,unscoped}-valid, keyed to the
  publish allowlist pin npm 11.21.0 (ADR 0082).
- npm-purl-build-11-19-0-{scoped,unscoped}-valid, keyed to the
  build-stage pair npm 11.19.0 bundled with Node.js 24.21.0
  (ADR 0085).

Each fixture records the npm CLI version its expectation was verified
against and the expected Package URL, asserted against the producer's
NPMPackagePURL derivation; the live CLI cross-check rides with the
gated Live tests. Registered in internal/fixture/registry.go,
testdata/fixtures/index.json, and the expected-case map. Pre-existing
fixtures are byte-untouched.

Signed-off-by: Yunseo Kim <[email protected]>
- Retarget the verification procedure to the pending v0.1.3 retry:
  v0.1.3 dispatch and evidence expectations, tag identity recorded at
  dispatch time rather than pinned to attempt-4 values, the caller
  pin bump to a slsa-builder main SHA carrying the ADR 0082/0084/0085
  pinned-toolchain implementation, and the note that the provisioned
  pinned npm 11.21.0 carries the upstream npm/cli#9882 fix so the
  Windlass-signed bundle attaches as-is under trusted publishing. The
  existing 0.1.2 publication from run 31840088262 remains the Phase
  6.4 foreign-conflict target.
- Record the attempt-4 evidence file and retarget its forward-looking
  metadata (tag refs/tags/v0.1.3, peeled commit pending) for the
  retry.
- README (English and Korean): the npm support-window paragraph now
  states the build stage uses the npm bundled with the exactly pinned
  Node.js 24.21.0 patch (npm 11.19.0, asserted before first use per
  ADR 0085) and the publish stage provisions a separately pinned,
  digest-verified npm CLI (npm 11.21.0) per ADR 0082/0084.
- AGENTS.md standing item: pin selected as npm 11.21.0 per ADR 0083's
  11-line preference; provisioning and pair pin landed on the feature
  branch; remaining steps are the vers-js caller pin bump and the
  v0.1.3 dispatch with runbook phases 1-6.
- CHANGELOG [Unreleased]: Added entries for the publish npm pin and
  provisioning, the toolchain pair pin and assertion, the publish pin
  validation with publish_npm_version outcome evidence, and the
  pin-check drift CI gate.

Signed-off-by: Yunseo Kim <[email protected]>
Clarify in both the provenance-publish and build-pack specifications
that every npm-invocation requirement (the ADR 0084 before-any-
invocation provisioning clause and the ADR 0085 first-invocation pair
assertion) governs invocations of the npm CLI by a step of the job's
workflow, and that the pinned setup mechanism's internal, read-only
environment logging, such as the version reporting actions/setup-node
performs after installing Node.js, is part of the authenticated
toolchain setup and is not an npm invocation. ADR 0085 authenticates
the bundled npm through the pinned setup mechanism's own checksum
verification, so the setup action's version logging is inherent to
the authenticated setup; it touches no package operation, and the npm
it reports is the pair-pinned bundled npm the assertion validates.

Signed-off-by: Yunseo Kim <[email protected]>
The same-run already-committed convergence path returned success
before version validation, so the converged run's outcome report
omitted diagnostic_metadata.publish_npm_version even though the job
observed the provisioned npm. Validate the provisioned npm against
the allowlist pin on every path that reaches Publish, including
convergence: the read-only --version observation runs after
provisioning per the ADR 0084 ordering, the observed version is
recorded in the report metadata, a pin mismatch fails with
publish-npm-pin-mismatch instead of converging silently, and
validation failures preserve the classified registry state. Registry
classification order is unchanged.

Signed-off-by: Yunseo Kim <[email protected]>
Close the compatibility-evidence gap for the npm PURL fixtures with
the Live-gated TestNPMPURLCLICompatibilityLive: for each supported
npm CLI version and each fixture package it exercises the CLI's own
provenance subject derivation (npm-package-arg toPurl, the exact code
path libnpmpublish uses to build and exact-match the provenance
subject) and asserts equality with both the fixture-recorded PURL and
NPMPackagePURL. npm 11.21.0 is provisioned through the
digest-verified ProvisionNPM path; npm 11.19.0 is the pair-pinned
bundled CLI. Verified 12026-10-13: 4/4 fixture cases pass, and the
four fixture READMEs now record the completed cross-check evidence.
The comparison publishes nothing and contacts no registry.

Signed-off-by: Yunseo Kim <[email protected]>
@cubic-dev-ai

cubic-dev-ai Bot commented Oct 12, 2026

Copy link
Copy Markdown

Running ultrareview automatically — this PR appears to change infrastructure or deployment configuration, where a missed bug can break builds, releases, or production environments, so a deeper multi-pass review is worth running. I'll post findings when complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultrareview ran automatically — this PR appears to change infrastructure or deployment configuration, where a missed bug can break builds, releases, or production environments, so a deeper multi-pass review is worth running.

All reported issues were addressed across 71 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread internal/npmprofile/provision_npm.go Resolved
Comment thread internal/workflowcheck/toolchain.go Resolved
Comment thread internal/workflowcheck/toolchain.go Resolved
…und archive copies explicitly

Address PR #131 review findings against the provisioning implementation.

- The publish execution boundary rejected the provisioned launcher with
  trusted-core-boundary-violation because $RUNNER_TEMP/windlass-npm-cli was not a
  trusted root. runPublishCommand now derives a publish-only shim root that
  accepts exactly the workflow-owned private layout (absolute clean path under
  the resolved RUNNER_TEMP anchor, npm-provision-* parent, npm basename, no
  symlink components, 0700 directory and regular launcher); every sibling
  layout stays rejected. Caught by cubic review before the v0.1.3 dogfood.
- Both archive io.Copy sites now use io.CopyN with explicit bounds (entry size
  for entries, remaining budget for the drain), making the pre-existing size
  policy explicit for static analysis (semgrep zip-bomb findings) without
  changing behavior or diagnostics.
- Add deterministic coverage for the duplicate-entry, empty-archive, and
  budget-exhaustion rejection branches and for clean provisioning failure
  paths; reuse requireLiveToolchain in the PURL live test.

Signed-off-by: Yunseo Kim <[email protected]>
…s at prose

Address PR #131 review findings against the pin records and drift gate.

- decodePublishPin checked URL.Host, which is non-empty for a port-only
  authority such as https://:443/npm.tgz. Require a nonempty Hostname so
  malformed pins are rejected before provisioning.
- The specification table scan treated a prose line directly after the last
  row as an unparsable row. The scan now stops at the first non-pipe line
  while pipe-prefixed malformed rows are still rejected, so harmless
  formatting changes do not fail pin-check.

Signed-off-by: Yunseo Kim <[email protected]>
Address a PR #131 review finding against the closed metadata contract.

The top-level null check did not cover members nested inside package_manifest,
so inputs such as {"package_manifest":{"description":null}} and
{"package_manifest":{"keywords":[null]}} were accepted after decoding erased
the null values. The specification requires explicit nulls to fail with
diagnostics-contract-invalid at any depth ("Absent source fields are omitted;
null ... fail"). UnmarshalJSON now scans each raw member value for null at any
depth after duplicate-member rejection, with the whole-member check keeping its
existing message.

Signed-off-by: Yunseo Kim <[email protected]>
…table bindings, and structural argument validation

Address three P1 PR #131 review findings against the static toolchain contract.

- Step conditions: contract-relevant steps in a job must all carry the exact
  same whitespace-normalized if: expression (or none), and constant-false
  conditions are rejected, so an assertion can never be skipped while an npm
  operation runs. The production publish segment's uniform upstream-success
  gate remains compatible.
- Executable binding: the publish diagnostics and publish steps must resolve
  NPM_EXECUTABLE to exactly the provisioning step output expression, at step
  or job env scope, rejecting ambient or hardcoded rebinding.
- Argument validation: shell comments are stripped before matching and the
  --npm-executable argument token must be exactly the pinned expression;
  comment-supplied evidence, duplicate flags, concatenated tokens, unknown
  variables, and single-quoted literal variable names are rejected.
- Preflight report contract: the node-only assertion and provisioning steps
  must now pass --report-path "$REPORT_PATH" with REPORT_PATH bound to the
  outcome report path, so the persistence wiring added for the outcome-report
  finding cannot be dropped silently. Fixtures model the production shape.

Signed-off-by: Yunseo Kim <[email protected]>
…ror branches

Address PR #131 review findings against the command adapters and outcome
reporting.

- npm-profile-provision-npm and npm-profile-toolchain-assert accept an
  optional --report-path and persist their typed failure report there
  atomically (0600) in addition to the stdout report; a successful run writes
  no file. The publish job wires REPORT_PATH and the flag into both preflight
  steps, so a provisioning or assertion failure uploads the specific
  diagnostic instead of the finalizer's generic input-unavailable.
- Add deterministic coverage for adapter flag-validation failures and
  downstream failure propagation across the provision, assert, and pin-check
  adapters, plus dispatch and flags edge cases.
- TestWorkflowCheckAcceptsProductionWorkflow runs the workflowcheck contract
  against the real js-ts-npm-package-slsa3.yml, so unit tests catch production
  conformance regressions alongside the lint.yml CLI gate.

Signed-off-by: Yunseo Kim <[email protected]>
…ifications

Address PR #131 review findings against the specification documents.

- The build specification claimed the bundled npm is authenticated through
  the pinned setup mechanism's checksum verification. The pinned
  actions/setup-node performs no Node.js artifact checksum verification, so
  the contract now states the honest guarantee: version identity is pinned
  observationally by the pair assertion, and artifact integrity rests on the
  runner image and the setup mechanism's TLS download.
- The pair-assertion sentence now defines the Node.js version
  canonicalization (strip the single leading v; npm compared verbatim),
  matching toolchain_assert.go, and names the pair assertion as the exempt
  preflight so the wording is not circular under the npm-invocation
  definition.
- The provenance/publish source ADR inventory gains ADR 0085, and the
  runtime.node_version rule is clarified as a defense-in-depth floor with the
  exact pinned pair asserted before first use; stale examples move to the
  pinned pair (24.21.0 / 11.19.0).
- The diagnostics taxonomy now documents the complete archive-safety
  rejection set (link and special-file entries) and both pin-record/spec
  comparisons covered by toolchain-pin-drift.
- The npmprofile live-test inventory includes npm_purl_live_test.go.

Signed-off-by: Yunseo Kim <[email protected]>
Address a PR #131 review finding against the Phase 0 runbook.

Phase 0 recorded the caller file blob SHA but never verified that publish.yml
actually references the selected builder commit, so a stale caller could
dispatch the old reusable workflow and bypass the pinned npm. The runbook now
requires updating the caller's uses: reference to the full 40-hex merged
builder SHA, fetching and decoding the file, failing closed unless the SHAs
match, and recording both the caller blob SHA and the builder SHA before
dispatch.

Signed-off-by: Yunseo Kim <[email protected]>
…k fixture

Align the command-level workflow fixture with the production publish job: the
node-only assertion and provisioning steps bind REPORT_PATH and pass
--report-path, matching the workflowcheck contract added in 049796e.

Signed-off-by: Yunseo Kim <[email protected]>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 existing issue remains and no new issues found across 35 files (changes from recent commits).

Confidence score: 4/5

  • The valid fragment in docs/architecture/verification-policy-and-fixtures.md has mismatched Node.js versions, so it violates the observed-version equality rule. Set builder.version.nodejs to v24.21.0.

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread internal/workflowcheck/check_test.go Resolved
Address the three remaining findings from the cubic re-review at 2031ea6.

- Add a wrong-value REPORT_PATH mutation for the node-only assertion step,
  locking the checker's rejection of any binding other than the uploaded
  outcome report path (the suite previously covered only a missing binding).
- Pin the dogfood runbook's caller-workflow fetch with ?ref=main so the
  verified publish.yml content matches the dispatched ref.
- Update the capture example's builder.version nodejs to v24.21.0, matching
  the example's own runner-image node_version under the observed-version
  equality rule; shape-only builder.version examples stay aligned with the
  governed fixtures.

Signed-off-by: Yunseo Kim <[email protected]>
@yunseo-kim
yunseo-kim merged commit 29c5a55 into main Oct 13, 2026
55 of 56 checks passed
@yunseo-kim
yunseo-kim deleted the feat/npm-toolchain-pins branch October 13, 2026 04:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants