diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6de5f40f4..5ee7ecebc 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,10 +18,6 @@ on: description: 'pnpm filter (single mode only, e.g. ./packages/sdk)' required: false default: '' - chirp_bootstrap: - description: 'One-time @bsv/chirp registry bootstrap (must use single mode and the CHIRP filter)' - type: boolean - default: false permissions: {} @@ -42,7 +38,6 @@ jobs: count: ${{ steps.artifacts.outputs.count }} filter: ${{ steps.plan.outputs.filter }} mode: ${{ steps.plan.outputs.mode }} - chirp_bootstrap: ${{ steps.plan.outputs.chirp_bootstrap }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -114,7 +109,6 @@ jobs: EVENT_NAME: ${{ github.event_name }} DISPATCH_MODE: ${{ inputs.mode }} DISPATCH_FILTER: ${{ inputs.filter }} - DISPATCH_CHIRP_BOOTSTRAP: ${{ inputs.chirp_bootstrap }} run: | mode="" filter="" @@ -145,17 +139,8 @@ jobs: fi fi - chirp_bootstrap="${DISPATCH_CHIRP_BOOTSTRAP:-false}" - if [[ "$chirp_bootstrap" == "true" ]]; then - if [[ "$EVENT_NAME" != "workflow_dispatch" || "$mode" != "single" || "$filter" != "./packages/network/chirp" ]]; then - echo "::error::The one-time CHIRP bootstrap requires a manual single-package release filtered to ./packages/network/chirp." - exit 1 - fi - fi - echo "mode=$mode" >> "$GITHUB_OUTPUT" echo "filter=$filter" >> "$GITHUB_OUTPUT" - echo "chirp_bootstrap=$chirp_bootstrap" >> "$GITHUB_OUTPUT" echo "Resolved release plan: mode=$mode filter='${filter:-}'" # Package artifacts cross a single immutable release boundary: pack once, @@ -285,24 +270,8 @@ jobs: retention-days: 90 - name: Publish the attested npm tarballs - if: needs.prepare.outputs.chirp_bootstrap != 'true' run: node scripts/package-release-artifacts.mjs publish release-artifacts/manifest.json - # npm requires a package record before a trusted publisher can be bound. - # This temporary, protected-environment path is constrained by the plan - # step to the first @bsv/chirp publication and is removed immediately - # after the package's OIDC trust relationship is established. - - name: Bootstrap the attested @bsv/chirp tarball - if: needs.prepare.outputs.chirp_bootstrap == 'true' - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_CHIRP_BOOTSTRAP_TOKEN }} - run: | - if [[ -z "$NODE_AUTH_TOKEN" ]]; then - echo "::error::The protected CHIRP bootstrap token is unavailable." - exit 1 - fi - node scripts/package-release-artifacts.mjs publish release-artifacts/manifest.json - # Publication credentials never coexist with source-controlled package build # code. The separate sync job receives only repository/PR authority and # exposes its GitHub credential solely during the push and PR steps. diff --git a/docs/reference/npm-package-supply-chain.md b/docs/reference/npm-package-supply-chain.md index ac0e20c24..64b067b98 100644 --- a/docs/reference/npm-package-supply-chain.md +++ b/docs/reference/npm-package-supply-chain.md @@ -17,15 +17,6 @@ workflow is the only supported publication path. It uses the protected `npm-production` environment and npm trusted publishing (OIDC); release automation must not use a long-lived npm write token. -`@bsv/chirp@0.1.0` is the one-time registry bootstrap exception: npm requires -the package record to exist before its trusted publisher can be configured. -The manual release input is fail-closed to single mode and -`./packages/network/chirp`, reads a one-day `@bsv`-scope token only inside the -protected publish job, and publishes the same scanned and attested tarball with -provenance. Remove the input, secret binding, token, and governed exception as -soon as the package is published and its `release.yaml` / `npm-production` -trusted-publisher relationship is verified. - ## Release boundary A release first builds and validates the workspace in an uncredentialed diff --git a/governance/repository-health/exceptions.json b/governance/repository-health/exceptions.json index 09e59de88..b5704f475 100644 --- a/governance/repository-health/exceptions.json +++ b/governance/repository-health/exceptions.json @@ -2,20 +2,6 @@ "schemaVersion": 1, "lastReviewed": "2026-08-24", "exceptions": [ - { - "id": "chirp-first-publication-bootstrap", - "category": "security", - "target": "@bsv/chirp@0.1.0 first npm publication", - "owner": "ts-stack-maintainers", - "reason": "npm requires a new package record before its trusted GitHub Actions publisher can be configured. The one-time fallback is constrained to a manual single-package CHIRP release, a one-day granular token with write access only to the @bsv scope, and the protected npm-production publish job that scans, attests, and publishes the immutable candidate with provenance.", - "evidence": [ - "https://docs.npmjs.com/trusted-publishers#troubleshooting", - "https://github.com/bsv-blockchain/ts-stack/actions/runs/33030007295" - ], - "created": "2026-08-24", - "reviewBy": "2026-08-28", - "removeWhen": "Remove the workflow input and secret binding, delete the GitHub environment secret, revoke the npm token, and delete this exception immediately after @bsv/chirp@0.1.0 is published and the bsv-blockchain/ts-stack release.yaml trusted publisher is verified for npm-production." - }, { "id": "scorecard-maintainer-merge-policy", "category": "security", diff --git a/scripts/package-release-artifacts.test.mjs b/scripts/package-release-artifacts.test.mjs index 3e03908f4..dc43228aa 100644 --- a/scripts/package-release-artifacts.test.mjs +++ b/scripts/package-release-artifacts.test.mjs @@ -421,31 +421,16 @@ test('npm release workflow preserves scan, attestation, verification, and exact- assert.match(workflow, /actions\/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d/g) assert.match(workflow, /actions\/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a/) assert.doesNotMatch(workflow, /pnpm\s+-r[\s\S]{0,100}\spublish\b/) - const bootstrapInput = workflow.indexOf('chirp_bootstrap:') - const bootstrapStep = workflow.indexOf('- name: Bootstrap the attested @bsv/chirp tarball') - assert.ok(bootstrapInput > 0) - assert.ok(bootstrapStep > 0) - assert.match(workflow, /default: false/) - assert.match(workflow, /\$filter" != "\.\/packages\/network\/chirp"/) - assert.equal(workflow.match(/NODE_AUTH_TOKEN/g)?.length, 2) - assert.doesNotMatch(workflow, /NPM_TOKEN/) + assert.doesNotMatch(workflow, /NODE_AUTH_TOKEN|NPM_TOKEN/) assert.equal(workflow.match(/runs-on: ubuntu-24\.04/g)?.length, 3) assert.equal(workflow.match(/node-version: 24\.18\.0/g)?.length, 3) assert.equal(workflow.match(/persist-credentials: false/g)?.length, 3) assert.match(workflow, /candidate: \$\{\{ steps\.artifacts\.outputs\.candidate \}\}/) assert.match(workflow, /name: \$\{\{ needs\.prepare\.outputs\.candidate \}\}/) const prepareJob = workflow.slice(workflow.indexOf(' prepare:'), workflow.indexOf(' publish:')) - assert.doesNotMatch( - prepareJob, - /id-token: write|environment: npm-production|NODE_AUTH_TOKEN|NPM_CHIRP_BOOTSTRAP_TOKEN/ - ) + assert.doesNotMatch(prepareJob, /id-token: write|environment: npm-production/) assert.match(prepareJob, /permissions:\n\s+contents: read/) - const bootstrapPublish = workflow.slice(bootstrapStep, workflow.indexOf(' sync-versions:')) - assert.match(bootstrapPublish, /needs\.prepare\.outputs\.chirp_bootstrap == 'true'/) - assert.match(bootstrapPublish, /secrets\.NPM_CHIRP_BOOTSTRAP_TOKEN/) - assert.match(bootstrapPublish, /package-release-artifacts\.mjs publish/) - const stage = workflow.indexOf('- name: Stage exact npm release artifacts') const scan = workflow.indexOf('- name: Reject high and critical package findings') const provenance = workflow.indexOf('- name: Attest npm package build provenance')