From 15875b6b0fee9b56a8c6acc20c90f5b99b61244f Mon Sep 17 00:00:00 2001 From: TensorNull Date: Mon, 27 Jul 2026 17:07:02 +0800 Subject: [PATCH] chore: prepare 0.1.0-alpha.2 OIDC release --- .github/workflows/publish.yml | 284 +------------------------------ .release-please-manifest.json | 2 +- CHANGELOG.md | 9 + README.md | 14 +- RELEASING.md | 37 ++-- ROADMAP.md | 29 ++-- SECURITY.md | 9 +- package-lock.json | 4 +- package.json | 2 +- scripts/publish-artifact.sh | 10 +- tests/publish-artifact.test.mjs | 35 ++-- tests/workflow-contract.test.mjs | 45 +---- 12 files changed, 76 insertions(+), 404 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3948e5a..d42841b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,12 +4,6 @@ on: release: types: - published - workflow_dispatch: - inputs: - source_run_id: - description: Failed v0.1.0-alpha.1 Publish run containing the verified artifact - required: true - type: string permissions: contents: read @@ -189,7 +183,7 @@ jobs: run: npm run test:live publish: - name: Publish with npm Trusted Publishing or alpha.1 bootstrap + name: Publish with npm Trusted Publishing needs: - live-smoke - verify @@ -223,9 +217,7 @@ jobs: path: release-artifacts - name: Publish the exact artifact with provenance env: - ALPHA1_BOOTSTRAP_ENABLED: ${{ vars.NPM_ALPHA1_BOOTSTRAP_ENABLED }} DIST_TAG: ${{ needs.verify.outputs.dist-tag }} - NODE_AUTH_TOKEN: ${{ vars.NPM_ALPHA1_BOOTSTRAP_ENABLED == 'true' && needs.verify.outputs.version == '0.1.0-alpha.1' && secrets.NPM_ALPHA1_BOOTSTRAP_TOKEN || '' }} VERSION: ${{ needs.verify.outputs.version }} run: bash scripts/publish-artifact.sh - name: Verify the public registry artifact @@ -350,277 +342,3 @@ jobs: process.exitCode = 1; }); EOF - - recover-verify: - name: Verify the failed alpha.1 publication source - if: github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - timeout-minutes: 10 - outputs: - dist-tag: next - release-commit: ${{ steps.trust.outputs.release-commit }} - source-run-id: ${{ steps.trust.outputs.source-run-id }} - version: 0.1.0-alpha.1 - permissions: - actions: read - contents: read - steps: - - name: Check out the recovery implementation - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - fetch-depth: 0 - persist-credentials: false - - name: Reject an untrusted recovery source - id: trust - env: - EXPECTED_REPOSITORY: cometapi-dev/cometapi-node - EXPECTED_TAG: v0.1.0-alpha.1 - GH_TOKEN: ${{ github.token }} - SOURCE_RUN_ID: ${{ inputs.source_run_id }} - shell: bash - run: | - set -euo pipefail - if [[ "$GITHUB_REPOSITORY" != "$EXPECTED_REPOSITORY" || \ - "$GITHUB_REF" != "refs/heads/main" ]]; then - echo "Recovery is restricted to the canonical repository's main branch." >&2 - exit 1 - fi - if [[ ! "$SOURCE_RUN_ID" =~ ^[1-9][0-9]*$ ]]; then - echo "source_run_id must be a positive integer." >&2 - exit 1 - fi - - git fetch --no-tags origin \ - "+refs/tags/${EXPECTED_TAG}:refs/tags/${EXPECTED_TAG}" \ - "+refs/heads/main:refs/remotes/origin/main" - release_commit="$(git rev-parse --verify "refs/tags/${EXPECTED_TAG}^{commit}")" - if ! git merge-base --is-ancestor "$release_commit" refs/remotes/origin/main; then - echo "The immutable release tag is not reachable from origin/main." >&2 - exit 1 - fi - - release_json="$(gh api \ - "repos/${GITHUB_REPOSITORY}/releases/tags/${EXPECTED_TAG}")" - run_json="$(gh api \ - "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}")" - jobs_json="$(gh api \ - "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}/jobs?per_page=100")" - artifacts_json="$(gh api \ - "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}/artifacts?per_page=100")" - - RELEASE_JSON="$release_json" RUN_JSON="$run_json" \ - JOBS_JSON="$jobs_json" ARTIFACTS_JSON="$artifacts_json" \ - RELEASE_COMMIT="$release_commit" EXPECTED_TAG="$EXPECTED_TAG" node <<'EOF' - const release = JSON.parse(process.env.RELEASE_JSON); - const run = JSON.parse(process.env.RUN_JSON); - const jobs = JSON.parse(process.env.JOBS_JSON).jobs; - const artifacts = JSON.parse(process.env.ARTIFACTS_JSON).artifacts; - const expectedCommit = process.env.RELEASE_COMMIT; - const expectedTag = process.env.EXPECTED_TAG; - - const reject = (message) => { - throw new Error(message); - }; - if ( - release.tag_name !== expectedTag || - release.draft !== false || - release.prerelease !== true || - release.immutable !== true - ) { - reject("Recovery requires the published immutable alpha.1 prerelease."); - } - if ( - run.event !== "release" || - run.path !== ".github/workflows/publish.yml" || - run.head_branch !== expectedTag || - run.head_sha !== expectedCommit || - run.status !== "completed" || - run.conclusion !== "failure" - ) { - reject("The source run does not match the failed alpha.1 release workflow."); - } - const conclusions = new Map(jobs.map((job) => [job.name, job.conclusion])); - if ( - conclusions.get("Verify the immutable release artifact") !== "success" || - conclusions.get("Verify the release tag against CometAPI") !== "success" || - conclusions.get( - "Publish with npm Trusted Publishing or alpha.1 bootstrap", - ) !== "failure" - ) { - reject("The source run does not have the required verify/live success boundary."); - } - const candidates = artifacts.filter( - (artifact) => - artifact.name === "npm-package-0.1.0-alpha.1" && - artifact.expired === false, - ); - if (candidates.length !== 1 || !candidates[0].digest) { - reject("The source run must contain one unexpired verified alpha.1 artifact."); - } - EOF - - echo "release-commit=${release_commit}" >> "$GITHUB_OUTPUT" - echo "source-run-id=${SOURCE_RUN_ID}" >> "$GITHUB_OUTPUT" - - recover-publish: - name: Recover the verified alpha.1 npm publication - needs: - - recover-verify - runs-on: ubuntu-latest - timeout-minutes: 15 - environment: - name: npm - url: https://www.npmjs.com/package/cometapi/v/0.1.0-alpha.1 - permissions: - actions: read - contents: read - id-token: write - steps: - - name: Check out the reviewed recovery implementation - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - name: Set up Node.js 24 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 - with: - node-version: 24.x - registry-url: https://registry.npmjs.org - - name: Use a Trusted Publishing-capable npm CLI - run: npm install --global npm@11.12.1 - - name: Download the original verified release artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - github-token: ${{ github.token }} - name: npm-package-0.1.0-alpha.1 - path: release-artifacts - repository: cometapi-dev/cometapi-node - run-id: ${{ needs.recover-verify.outputs.source-run-id }} - - name: Publish the exact recovered artifact with provenance - env: - ALPHA1_BOOTSTRAP_ENABLED: ${{ vars.NPM_ALPHA1_BOOTSTRAP_ENABLED }} - DIST_TAG: ${{ needs.recover-verify.outputs.dist-tag }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_ALPHA1_BOOTSTRAP_TOKEN }} - VERSION: ${{ needs.recover-verify.outputs.version }} - run: bash scripts/publish-artifact.sh - - name: Verify the recovered public registry artifact - env: - DIST_TAG: ${{ needs.recover-verify.outputs.dist-tag }} - VERSION: ${{ needs.recover-verify.outputs.version }} - shell: bash - run: | - set -euo pipefail - mapfile -t tarballs < <(find "$GITHUB_WORKSPACE/release-artifacts" -maxdepth 1 -type f -name '*.tgz' -print) - if [[ "${#tarballs[@]}" -ne 1 ]]; then - echo "Expected exactly one downloaded artifact for registry verification." >&2 - exit 1 - fi - local_integrity="$(node -e 'const {createHash}=require("node:crypto");const {readFileSync}=require("node:fs");process.stdout.write("sha512-"+createHash("sha512").update(readFileSync(process.argv[1])).digest("base64"))' "${tarballs[0]}")" - registry_ready="false" - for attempt in {1..12}; do - resolved="$(npm view "cometapi@${VERSION}" version 2>/dev/null || true)" - tagged="$(npm view "cometapi@${DIST_TAG}" version 2>/dev/null || true)" - registry_dist="$(npm view "cometapi@${VERSION}" dist --json 2>/dev/null || true)" - if [[ "$resolved" == "$VERSION" && "$tagged" == "$VERSION" && -n "$registry_dist" ]] && \ - REGISTRY_DIST="$registry_dist" LOCAL_INTEGRITY="$local_integrity" node <<'EOF' - let ready = false; - try { - const dist = JSON.parse(process.env.REGISTRY_DIST); - ready = - dist.integrity === process.env.LOCAL_INTEGRITY && - Boolean(dist.attestations?.url) && - dist.attestations?.provenance?.predicateType === - "https://slsa.dev/provenance/v1"; - } catch {} - process.exitCode = ready ? 0 : 1; - EOF - then - registry_ready="true" - break - fi - if [[ "$attempt" -lt 12 ]]; then - sleep 10 - fi - done - if [[ "$registry_ready" != "true" ]]; then - echo "Registry state did not converge for cometapi@${VERSION}, ${DIST_TAG}, integrity, and provenance." >&2 - exit 1 - fi - - verify_dir="$(mktemp -d)" - cd "$verify_dir" - npm init --yes >/dev/null - npm install --ignore-scripts --no-audit --no-fund \ - "openai@6.47.0" "cometapi@${VERSION}" - signatures_verified="false" - for attempt in {1..3}; do - if npm audit signatures; then - signatures_verified="true" - break - fi - if [[ "$attempt" -lt 3 ]]; then - sleep 10 - fi - done - if [[ "$signatures_verified" != "true" ]]; then - echo "Registry signature and provenance verification did not converge." >&2 - exit 1 - fi - npm ls openai --all - if [[ -d node_modules/cometapi/node_modules/openai ]]; then - echo "The registry fixture contains a nested OpenAI installation." >&2 - exit 1 - fi - - node --input-type=module <<'EOF' - import assert from "node:assert/strict"; - import { CometAPI } from "cometapi"; - - const client = new CometAPI({ - apiKey: "mock-registry-key", - maxRetries: 0, - fetch: async () => - new Response(JSON.stringify({ object: "list", data: [] }), { - status: 200, - headers: { "content-type": "application/json" }, - }), - }); - const models = await client.models.list(); - assert.deepEqual(models.data, []); - EOF - - node <<'EOF' - const assert = require("node:assert/strict"); - const { CometAPI } = require("cometapi"); - const { APIError } = require("openai"); - - const client = new CometAPI({ - apiKey: "mock-registry-key", - maxRetries: 0, - fetch: async () => - new Response( - JSON.stringify({ - error: { - message: "mock registry failure", - type: "invalid_request_error", - }, - }), - { - status: 400, - headers: { "content-type": "application/json" }, - }, - ), - }); - - (async () => { - let caught; - try { - await client.models.list(); - } catch (error) { - caught = error; - } - assert.ok(caught instanceof APIError); - })().catch((error) => { - console.error(error); - process.exitCode = 1; - }); - EOF diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d7a8735..c5e8a3e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.1" + ".": "0.1.0-alpha.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 523aad6..6d2f3fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ follows Keep a Changelog, and versions follow Semantic Versioning. ## [Unreleased] +## [0.1.0-alpha.2] - 2026-07-27 + +### Changed + +- Removed the one-time alpha.1 token bootstrap and manual recovery workflow + after configuring npm Trusted Publishing. +- Required the release workflow to publish through OIDC without registry token + credentials while preserving exact-artifact and provenance verification. + ## [0.1.0-alpha.1] - 2026-07-27 ### Added diff --git a/README.md b/README.md index ac2cbd7..f56704a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ the official OpenAI JavaScript request, response, stream, and error types while defaulting the client to CometAPI. > **Registry Alpha pre-release:** the SDK is under active 0.1 development. -> `0.1.0-alpha.1` is approved for npm publication under the `next` dist-tag, +> `0.1.0-alpha.2` is approved for npm publication under the `next` dist-tag, > and its API may change before `0.1.0`. ## Supported 0.1 surface @@ -52,8 +52,8 @@ For source-checkout testing, retain and verify one exact tarball: ```bash mkdir -p .artifacts npm pack --pack-destination .artifacts -npm run test:package -- --tarball .artifacts/cometapi-0.1.0-alpha.1.tgz -npm run test:fixtures -- --tarball .artifacts/cometapi-0.1.0-alpha.1.tgz +npm run test:package -- --tarball .artifacts/cometapi-0.1.0-alpha.2.tgz +npm run test:fixtures -- --tarball .artifacts/cometapi-0.1.0-alpha.2.tgz ``` Install that path in a separate consumer when needed. Do not treat a locally @@ -210,10 +210,10 @@ parent. The repository has completed Public Preview. Blocking CI, protected repository rules, security reporting, protected environments, and the authorized live -smoke have passed. Registry Alpha `0.1.0-alpha.1` is approved for npm -publication; mocked responses, packed artifacts, GitHub Actions, trusted live -tests, and npm publication remain separate evidence layers and must not be -represented as another. +smoke have passed. Registry Alpha `0.1.0-alpha.1` is available from npm, and +`0.1.0-alpha.2` is approved for OIDC publication. Mocked responses, packed +artifacts, GitHub Actions, trusted live tests, and npm publication remain +separate evidence layers and must not be represented as another. See: diff --git a/RELEASING.md b/RELEASING.md index 1f3d6f7..ef3c8c9 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -9,7 +9,7 @@ Release status is evidence-based: | Local code-complete | Required source, tests, documentation, metadata, fixtures, and workflows exist, and every applicable offline check passes. | | Private Remote Validation ready | Local gates pass, the sanitized history and maintainer-confirmed identity are complete, and real credential-free private default-branch CI passes. | | Public Preview ready | After visibility changes, public-only repository rules, security reporting, environments, default-branch CI, the content gate, and authorized protected live smoke all pass. | -| Registry Alpha candidate | The exact `0.1.0-alpha.1` artifact passes package and clean-install gates. | +| Registry Alpha candidate | The exact `0.1.0-alpha.2` artifact passes package and clean-install gates. | | Registry Alpha released | The public npm artifact installs from the `next` channel, passes post-publication verification, and has verified provenance plus any documented one-time bootstrap evidence. | | Stable released | Every stable 0.1.0 local, remote, live, review, provenance, and registry gate has recorded evidence. | @@ -77,11 +77,10 @@ artifacts. Authorized maintainers must supply or approve: Missing identity, credentials, ownership, or authorization blocks the corresponding release gate. Do not invent it or replace it with a mock. -The `cometapi` package does not exist in the public registry yet, so npm cannot -verify its owner before the first publication. Registry Alpha owner evidence is -complete only when `npm owner ls cometapi` lists the maintainer-confirmed -`cometapi-team` account after the first publication; until then this remains a -Registry Alpha prerequisite, not a Public Preview blocker. +The `cometapi` package exists in the public registry. Registry Alpha owner +evidence is complete only when `npm owner ls cometapi` lists the +maintainer-confirmed `cometapi-team` account; until then this remains a Registry +Alpha prerequisite. For the current Public Preview milestone, private topic pushes, pull requests, merges, and credential-free CI are the only remote actions that may be @@ -236,13 +235,12 @@ The repository maintains four independently auditable workflows: `release.published` event can trigger publication. - `publish.yml`: rejects mutable releases and tag commits outside `main`, packs and tests one exact artifact, requires a protected live smoke for that release - tag, and publishes the same file through npm OIDC by default. Only - `0.1.0-alpha.1` may use the one-time protected-token bootstrap when npm does - not permit Trusted Publisher configuration before the package exists. The - workflow verifies the dist-tag, integrity, provenance attestation, signatures, - deduplication, and public installation. A rerun resumes after an already - accepted version only when its registry integrity matches the downloaded - artifact, then repeats all bounded registry-state and signature checks. + tag, and publishes the same file through npm OIDC. Registry token credentials + are rejected. The workflow verifies the dist-tag, integrity, provenance + attestation, signatures, deduplication, and public installation. A rerun + resumes after an already accepted version only when its registry integrity + matches the downloaded artifact, then repeats all bounded registry-state and + signature checks. Third-party actions are pinned to full commit SHAs. Workflow permissions remain read-only except where a documented job requires more; `id-token: write` belongs @@ -253,14 +251,11 @@ The supported package `engines` range contains Node.js 22 and 24 only. Node.js 26 remains an advisory workflow target until it enters LTS; Node.js 18 and 20 remain unsupported. -The repository has no prior release, so the manifest is intentionally empty and -`release-please-config.json` temporarily sets `release-as` to -`0.1.0-alpha.1`. The reviewed manual alpha pull request must set the manifest to -`0.1.0-alpha.1`, consolidate the existing candidate changelog entry, and remove -`release-as` before merge. Leaving `release-as` in the default branch would -incorrectly pin later releases. The publication workflow fails unless the -manifest equals the package version, `release-as` is absent, and the version -has exactly one dated changelog heading. +The first manual alpha recorded `0.1.0-alpha.1` in the manifest and removed the +temporary `release-as` configuration. Later releases advance that manifest with +the package version. The publication workflow fails unless the manifest equals +the package version, `release-as` is absent, and the version has exactly one +dated changelog heading. ## Registry Alpha (`0.1.0-alpha.1`) diff --git a/ROADMAP.md b/ROADMAP.md index adab7a2..eb95f44 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # CometAPI TypeScript and Node.js SDK Roadmap -Status: Public Preview complete; Registry Alpha release authorized +Status: Public Preview complete; Registry Alpha OIDC closeout in progress Last updated: 2026-07-27 Repository contract: This roadmap is self-contained and is the public source of truth for this repository's release sequence. @@ -14,23 +14,22 @@ small, auditable package for supported Node.js LTS runtimes. Private Remote Validation and Public Preview are complete for the sanitized repository. The repository is public with blocking CI, protected repository and tag rules, Private Vulnerability Reporting, protected environments, and -authorized live-smoke evidence. The functional `0.1.0-alpha.1` prerelease -remains a separate evidence gate after Public Preview. Registry publication -proceeds only through Private Remote Validation, Public Preview, Registry Alpha, -and stable 0.1.0 stages. +authorized live-smoke evidence. The functional `0.1.0-alpha.1` prerelease is +available from npm; Registry Alpha remains open until `0.1.0-alpha.2` completes +the OIDC, provenance, ownership, and public-install verification sequence. ## Milestones -| Milestone | Status | User outcome | -| ---------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| Repository foundation | In progress | The repository has reproducible development, contribution, security, and release processes. | -| Private Remote Validation | Complete | The sanitized private repository passes real credential-free default-branch CI; public-only controls and live tests remain disabled. | -| Public Preview | Complete | The public repository has blocking CI, repository rules, security reporting, protected environments, and authorized live-smoke evidence. | -| 0.1.0-alpha.1 Registry Alpha | In progress | Early adopters can install a functional prerelease from npm's `next` channel and call the three required OpenAI-compatible resources. | -| 0.1.0 Stable | Planned | Users can install a fully verified package from npm's default channel. | -| 0.2.0 provider-native text | Planned | Users can opt into Anthropic Messages and Gemini text adapters through isolated subpath exports. | -| 0.3.0 CometAPI resources | Planned | Users receive typed access to the first stable CometAPI-specific account or platform resources. | -| Media and task APIs | Later | Users receive typed image, video, audio, upload, polling, and task lifecycle helpers after their contracts are stable. | +| Milestone | Status | User outcome | +| -------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| Repository foundation | In progress | The repository has reproducible development, contribution, security, and release processes. | +| Private Remote Validation | Complete | The sanitized private repository passes real credential-free default-branch CI; public-only controls and live tests remain disabled. | +| Public Preview | Complete | The public repository has blocking CI, repository rules, security reporting, protected environments, and authorized live-smoke evidence. | +| 0.1.x Registry Alpha | In progress | Early adopters can install a functional prerelease from npm's `next` channel; alpha.2 must prove the OIDC-only publication path. | +| 0.1.0 Stable | Planned | Users can install a fully verified package from npm's default channel. | +| 0.2.0 provider-native text | Planned | Users can opt into Anthropic Messages and Gemini text adapters through isolated subpath exports. | +| 0.3.0 CometAPI resources | Planned | Users receive typed access to the first stable CometAPI-specific account or platform resources. | +| Media and task APIs | Later | Users receive typed image, video, audio, upload, polling, and task lifecycle helpers after their contracts are stable. | ## Repository Foundation diff --git a/SECURITY.md b/SECURITY.md index 3fbf11d..7a95a18 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -54,8 +54,7 @@ Users are responsible for all usage and charges incurred with their keys. npm publication normally uses GitHub OIDC Trusted Publishing, a protected npm environment, provenance, an immutable reviewed tag, and post-publication -installation verification. Long-lived registry tokens are forbidden. The sole -conditional bootstrap for the first alpha is documented in -[RELEASING.md](./RELEASING.md) and is owner-controlled, one-time, and immediately -revoked. The workflow keeps this fallback disabled by default and rejects it -for every version other than `0.1.0-alpha.1`. +installation verification. Registry tokens are forbidden by the publication +workflow. The completed one-time first-alpha bootstrap is documented in +[RELEASING.md](./RELEASING.md) as historical release evidence and is not a +reusable source-controlled publication path. diff --git a/package-lock.json b/package-lock.json index 34f6c6d..bd4e1c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cometapi", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cometapi", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "license": "MIT", "dependencies": { "openai": "^6.47.0" diff --git a/package.json b/package.json index 9cbfd2f..ea7c553 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cometapi", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "description": "Official TypeScript and Node.js client for the CometAPI OpenAI-compatible API", "author": "CometAPI", "license": "MIT", diff --git a/scripts/publish-artifact.sh b/scripts/publish-artifact.sh index 82c79cc..c83dbef 100644 --- a/scripts/publish-artifact.sh +++ b/scripts/publish-artifact.sh @@ -6,11 +6,9 @@ set -euo pipefail : "${VERSION:?VERSION is required}" artifact_directory="${ARTIFACT_DIRECTORY:-release-artifacts}" -bootstrap_enabled="${ALPHA1_BOOTSTRAP_ENABLED:-}" -if [[ "$bootstrap_enabled" == "true" && \ - ( "$VERSION" != "0.1.0-alpha.1" || "$DIST_TAG" != "next" ) ]]; then - echo "The token bootstrap is restricted to cometapi@0.1.0-alpha.1 on the next dist-tag." >&2 +if [[ -n "${NODE_AUTH_TOKEN:-}" || -n "${NPM_TOKEN:-}" ]]; then + echo "Registry tokens are forbidden; publication must use npm Trusted Publishing." >&2 exit 1 fi @@ -40,10 +38,6 @@ if (dist.integrity !== process.env.LOCAL_INTEGRITY) { EOF echo "cometapi@${VERSION} already matches the verified artifact; resuming checks." elif grep -q "E404" "$view_error"; then - if [[ "$bootstrap_enabled" == "true" && -z "${NODE_AUTH_TOKEN:-}" ]]; then - echo "NPM_ALPHA1_BOOTSTRAP_TOKEN is required when the alpha.1 bootstrap is enabled." >&2 - exit 1 - fi npm publish "${tarballs[0]}" --access public --provenance --tag "$DIST_TAG" else echo "Unable to determine whether cometapi@${VERSION} already exists." >&2 diff --git a/tests/publish-artifact.test.mjs b/tests/publish-artifact.test.mjs index b0168af..19f25d4 100644 --- a/tests/publish-artifact.test.mjs +++ b/tests/publish-artifact.test.mjs @@ -59,10 +59,10 @@ function fixture() { } function runPublish({ - bootstrapEnabled = "", distTag = "next", - token = "", - version = "0.1.0-alpha.1", + nodeAuthToken = "", + npmToken = "", + version = "0.1.0-alpha.2", } = {}) { const { bin, log, root } = fixture(); const result = spawnSync("bash", [script], { @@ -70,11 +70,11 @@ function runPublish({ encoding: "utf8", env: { ...process.env, - ALPHA1_BOOTSTRAP_ENABLED: bootstrapEnabled, ARTIFACT_DIRECTORY: "artifacts", DIST_TAG: distTag, - NODE_AUTH_TOKEN: token, + NODE_AUTH_TOKEN: nodeAuthToken, NPM_CALL_LOG: log, + NPM_TOKEN: npmToken, PATH: `${bin}${delimiter}${process.env.PATH ?? ""}`, VERSION: version, }, @@ -87,33 +87,24 @@ function runPublish({ } describe("publish artifact authentication", () => { - it("uses Trusted Publishing without injecting a registry token by default", () => { - const { log, result } = runPublish({ version: "0.1.0-alpha.2" }); + it("uses Trusted Publishing with the absolute artifact path", () => { + const { log, result, root } = runPublish(); expect(result.status, result.stderr).toBe(0); expect(log).toMatch(/^\npublish .* --provenance --tag next\n$/); - }); - - it("allows the protected token bootstrap for alpha.1 on next", () => { - const { log, result, root } = runPublish({ - bootstrapEnabled: "true", - token: "opaque", - }); - expect(result.status, result.stderr).toBe(0); - expect(log).toMatch( - /^token-present\npublish .* --provenance --tag next\n$/, - ); expect(log).toContain( `publish ${join(realpathSync(root), "artifacts", "cometapi.tgz")} --access public --provenance --tag next`, ); }); it.each([ - ["another version", { bootstrapEnabled: "true", version: "0.1.0-alpha.2" }], - ["another dist-tag", { bootstrapEnabled: "true", distTag: "latest" }], - ["a missing token", { bootstrapEnabled: "true" }], - ])("rejects bootstrap mode for %s", (_name, options) => { + ["NODE_AUTH_TOKEN", { nodeAuthToken: "opaque" }], + ["NPM_TOKEN", { npmToken: "opaque" }], + ])("rejects the %s registry credential", (_name, options) => { const { log, result } = runPublish(options); expect(result.status).not.toBe(0); + expect(result.stderr).toContain( + "publication must use npm Trusted Publishing", + ); expect(log).toBe(""); }); }); diff --git a/tests/workflow-contract.test.mjs b/tests/workflow-contract.test.mjs index 5ba24b6..be997cf 100644 --- a/tests/workflow-contract.test.mjs +++ b/tests/workflow-contract.test.mjs @@ -105,44 +105,17 @@ describe("GitHub Actions workflow contract", () => { ); }); - it("keeps the token bootstrap opt-in and alpha.1-only", () => { + it("publishes only through OIDC and has no manual recovery path", () => { const publishWorkflow = workflow("publish.yml"); const publish = job(publishWorkflow, "publish"); - expect(publish).toContain( - "ALPHA1_BOOTSTRAP_ENABLED: ${{ vars.NPM_ALPHA1_BOOTSTRAP_ENABLED }}", - ); - expect(publish).toContain("secrets.NPM_ALPHA1_BOOTSTRAP_TOKEN"); - expect(publish).toContain( - "needs.verify.outputs.version == '0.1.0-alpha.1'", - ); expect(publish).toContain( "ref: ${{ needs.verify.outputs.release-commit }}", ); expect(publish).toContain("run: bash scripts/publish-artifact.sh"); - expect( - matches(publishWorkflow, /secrets\.NPM_ALPHA1_BOOTSTRAP_TOKEN/g), - ).toHaveLength(2); - }); - - it("limits manual publication recovery to the failed immutable alpha.1 run", () => { - const publishWorkflow = workflow("publish.yml"); - const recoveryVerify = job(publishWorkflow, "recover-verify"); - const recoveryPublish = job(publishWorkflow, "recover-publish"); - - expect(recoveryVerify).toContain("EXPECTED_TAG: v0.1.0-alpha.1"); - expect(recoveryVerify).toContain( - 'run.path !== ".github/workflows/publish.yml"', - ); - expect(recoveryVerify).toContain("release.immutable !== true"); - expect(recoveryVerify).toContain( - 'conclusions.get("Verify the release tag against CometAPI") !== "success"', - ); - expect(recoveryVerify).toContain( - 'artifact.name === "npm-package-0.1.0-alpha.1"', - ); - expect(recoveryPublish).toContain("environment:\n name: npm"); - expect(recoveryPublish).toContain("id-token: write"); - expect(recoveryPublish).toContain("run: bash scripts/publish-artifact.sh"); + expect(publishWorkflow).not.toContain("workflow_dispatch"); + expect(publishWorkflow).not.toContain("NPM_ALPHA1_BOOTSTRAP"); + expect(publishWorkflow).not.toContain("recover-verify"); + expect(publishWorkflow).not.toContain("recover-publish"); }); it("pins third-party actions and disables checkout credential persistence", () => { @@ -185,15 +158,9 @@ describe("GitHub Actions workflow contract", () => { expect(releasePlease).toMatch(/^ {6}pull-requests: write$/m); const publishWorkflow = workflow("publish.yml"); - expect(matches(publishWorkflow, /^\s+id-token: write$/gm)).toHaveLength(2); + expect(matches(publishWorkflow, /^\s+id-token: write$/gm)).toHaveLength(1); expect(job(publishWorkflow, "verify")).not.toContain("id-token: write"); expect(job(publishWorkflow, "live-smoke")).not.toContain("id-token: write"); expect(job(publishWorkflow, "publish")).toContain("id-token: write"); - expect(job(publishWorkflow, "recover-verify")).not.toContain( - "id-token: write", - ); - expect(job(publishWorkflow, "recover-publish")).toContain( - "id-token: write", - ); }); });