Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 1 addition & 283 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.1.0-alpha.1"
".": "0.1.0-alpha.2"
}
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:

Expand Down
Loading