Skip to content

docs: record 0.1.2 release evidence (#33) #16

docs: record 0.1.2 release evidence (#33)

docs: record 0.1.2 release evidence (#33) #16

Workflow file for this run

name: Publish immutable release
on:
push:
branches:
- main
workflow_dispatch:
inputs:
release-tag:
description: Exact immutable GitHub release tag
required: true
type: string
release-sha:
description: Exact commit resolved by the release tag
required: true
type: string
permissions:
contents: read
concurrency:
group: pypi-publish
cancel-in-progress: false
env:
UV_VERSION: 0.11.8
jobs:
release-please:
name: Maintain the reviewed release PR and release
if: >-
github.run_attempt == 1 &&
github.event_name == 'push' &&
vars.RELEASE_PLEASE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
release-created: ${{ steps.release.outputs.release_created }}
release-sha: ${{ steps.verify-release.outputs.release-sha }}
release-tag: ${{ steps.verify-release.outputs.release-tag }}
release-verified: ${{ steps.verify-release.outputs.release-verified }}
permissions:
contents: write
pull-requests: write
steps:
- name: Create the approved immutable release
id: release
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0, node24
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
skip-github-pull-request: "true"
- name: Open or update the release PR
id: release-pr
if: steps.release.outputs.release_created != 'true'
continue-on-error: true
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0, node24
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
skip-github-release: "true"
- name: Retry release PR maintenance once
id: retry-release-pr
if: >-
steps.release.outputs.release_created != 'true' &&
steps.release-pr.outcome == 'failure'
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0, node24
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
skip-github-release: "true"
- name: Verify the immutable release created by Release Please
id: verify-release
if: steps.release.outputs.release_created == 'true'
env:
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
EXPECTED_TAG: ${{ steps.release.outputs.tag_name }}
GH_TOKEN: ${{ github.token }}
run: |
test -n "$EXPECTED_TAG"
test -n "$EXPECTED_SHA"
release=""
for attempt in $(seq 1 12); do
release=$(gh api "repos/${{ github.repository }}/releases/tags/$EXPECTED_TAG") || true
if test -n "$release" && test "$(jq -r .immutable <<<"$release")" = "true"; then
break
fi
if test "$attempt" -ge 12; then
echo "release did not become immutable" >&2
exit 1
fi
sleep 5
done
test "$(jq -r .tag_name <<<"$release")" = "$EXPECTED_TAG"
test "$(jq -r .draft <<<"$release")" = "false"
test "$(jq -r .prerelease <<<"$release")" = "false"
test "$(jq -r .immutable <<<"$release")" = "true"
ref=$(gh api "repos/${{ github.repository }}/git/ref/tags/$EXPECTED_TAG")
tag_type=$(jq -r .object.type <<<"$ref")
tag_sha=$(jq -r .object.sha <<<"$ref")
if test "$tag_type" = "tag"; then
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq .object.sha)
else
test "$tag_type" = "commit"
fi
test "$tag_sha" = "$EXPECTED_SHA"
{
echo "release-tag=$EXPECTED_TAG"
echo "release-sha=$EXPECTED_SHA"
echo "release-verified=true"
} >> "$GITHUB_OUTPUT"
verify-recovery:
name: Verify the authorized immutable release recovery
if: >-
github.run_attempt == 1 &&
github.event_name == 'workflow_dispatch' &&
github.ref == format('refs/heads/{0}', github.event.repository.default_branch) &&
vars.RELEASE_RECOVERY_TAG == inputs.release-tag &&
vars.RELEASE_RECOVERY_SHA == inputs.release-sha
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
release-sha: ${{ steps.verify-release.outputs.release-sha }}
release-tag: ${{ steps.verify-release.outputs.release-tag }}
permissions:
contents: read
steps:
- name: Verify the immutable release selected for recovery
id: verify-release
env:
EXPECTED_SHA: ${{ inputs.release-sha }}
EXPECTED_TAG: ${{ inputs.release-tag }}
GH_TOKEN: ${{ github.token }}
run: |
test -n "$EXPECTED_TAG"
test -n "$EXPECTED_SHA"
release=""
for attempt in $(seq 1 12); do
release=$(gh api "repos/${{ github.repository }}/releases/tags/$EXPECTED_TAG") || true
if test -n "$release" && test "$(jq -r .immutable <<<"$release")" = "true"; then
break
fi
if test "$attempt" -ge 12; then
echo "release did not become immutable" >&2
exit 1
fi
sleep 5
done
test "$(jq -r .tag_name <<<"$release")" = "$EXPECTED_TAG"
test "$(jq -r .draft <<<"$release")" = "false"
test "$(jq -r .prerelease <<<"$release")" = "false"
test "$(jq -r .immutable <<<"$release")" = "true"
ref=$(gh api "repos/${{ github.repository }}/git/ref/tags/$EXPECTED_TAG")
tag_type=$(jq -r .object.type <<<"$ref")
tag_sha=$(jq -r .object.sha <<<"$ref")
if test "$tag_type" = "tag"; then
tag_sha=$(gh api "repos/${{ github.repository }}/git/tags/$tag_sha" --jq .object.sha)
else
test "$tag_type" = "commit"
fi
test "$tag_sha" = "$EXPECTED_SHA"
{
echo "release-tag=$EXPECTED_TAG"
echo "release-sha=$EXPECTED_SHA"
echo "release-verified=true"
} >> "$GITHUB_OUTPUT"
select-release:
name: Select one independently verified release identity
needs:
- release-please
- verify-recovery
if: >-
always() &&
!cancelled() &&
github.run_attempt == 1 &&
(
(
github.event_name == 'push' &&
needs.release-please.result == 'success' &&
needs.release-please.outputs.release-created == 'true' &&
needs.release-please.outputs.release-verified == 'true'
) ||
(
github.event_name == 'workflow_dispatch' &&
needs.verify-recovery.result == 'success'
)
)
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
release-sha: ${{ steps.select.outputs.release-sha }}
release-tag: ${{ steps.select.outputs.release-tag }}
permissions:
contents: read
steps:
- name: Select the independently verified release identity
id: select
env:
EVENT_NAME: ${{ github.event_name }}
RECOVERY_SHA: ${{ needs.verify-recovery.outputs.release-sha }}
RECOVERY_TAG: ${{ needs.verify-recovery.outputs.release-tag }}
RELEASE_PLEASE_SHA: ${{ needs.release-please.outputs.release-sha }}
RELEASE_PLEASE_TAG: ${{ needs.release-please.outputs.release-tag }}
run: |
case "$EVENT_NAME" in
push)
release_sha=$RELEASE_PLEASE_SHA
release_tag=$RELEASE_PLEASE_TAG
;;
workflow_dispatch)
release_sha=$RECOVERY_SHA
release_tag=$RECOVERY_TAG
;;
*)
exit 1
;;
esac
test -n "$release_sha"
test -n "$release_tag"
{
echo "release-sha=$release_sha"
echo "release-tag=$release_tag"
} >> "$GITHUB_OUTPUT"
build:
name: Verify and build the immutable default-branch release
if: >-
always() &&
!cancelled() &&
github.run_attempt == 1 &&
needs.select-release.result == 'success'
needs:
- select-release
runs-on: ubuntu-latest
timeout-minutes: 25
outputs:
release-commit: ${{ steps.trust.outputs.release-commit }}
version: ${{ steps.version.outputs.version }}
permissions:
contents: read
steps:
- name: Check out the published release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ needs.select-release.outputs.release-tag }}
fetch-depth: 0
persist-credentials: false
- name: Reject an untrusted release target
id: trust
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
EXPECTED_RELEASE_SHA: ${{ needs.select-release.outputs.release-sha }}
RELEASE_IMMUTABLE: "true"
RELEASE_TAG: ${{ needs.select-release.outputs.release-tag }}
run: bash scripts/verify_release_trust.sh
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
- name: Install the pinned uv frontend
run: python -m pip install --disable-pip-version-check "uv==$UV_VERSION"
- name: Reproduce the locked environment
run: uv sync --locked
- name: Verify project, manifest, changelog, release docs, and tag agreement
id: version
env:
RELEASE_TAG: ${{ needs.select-release.outputs.release-tag }}
run: |
version=$(uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs --print-version)
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Scan the immutable source for credentials and scope mistakes
run: uv run python scripts/check_secrets.py
- name: Verify release-workflow trust semantics
run: uv run python scripts/check_workflows.py
- name: Build wheel and source distribution from the tag
run: uv build
- name: Verify artifact versions against the tag
env:
RELEASE_TAG: ${{ needs.select-release.outputs.release-tag }}
run: uv run python scripts/check_version.py --tag "$RELEASE_TAG" --require-changelog --require-releasable-docs dist/*
- name: Check package metadata rendering
run: uv run twine check dist/*
- name: Inspect artifact identity and shape
run: uv run python scripts/check_artifacts.py dist/*
- name: Install and smoke-test each exact artifact
run: uv run python scripts/check_clean_install.py dist/*
- name: Record immutable artifact digests
run: sha256sum dist/* > artifact-sha256.txt
- name: Retain only the verified release bundle
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-${{ steps.version.outputs.version }}
path: |
dist/*
artifact-sha256.txt
if-no-files-found: error
retention-days: 30
release-live-smoke:
name: Verify the exact release commit against CometAPI
if: >-
always() &&
!cancelled() &&
github.run_attempt == 1 &&
needs.build.result == 'success'
needs:
- build
concurrency:
group: trusted-live-smoke
cancel-in-progress: false
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
# Required repository configuration: protect this environment without required reviewers,
# and configure COMETAPI_KEY plus the approved COMETAPI_LIVE_MODEL variable.
environment: live-smoke
env:
COMETAPI_LIVE_CONCURRENCY: "1"
COMETAPI_LIVE_MAX_OUTPUT_TOKENS: "16"
COMETAPI_LIVE_MAX_REQUESTS: "4"
COMETAPI_LIVE_MODEL: ${{ vars.COMETAPI_LIVE_MODEL || 'gpt-5.4' }}
COMETAPI_LIVE_REQUEST_TIMEOUT_SECONDS: "30"
COMETAPI_LIVE_RUN: "1"
COMETAPI_LIVE_STOP_ON_FAILURE: "1"
steps:
- name: Require the protected live credential
env:
COMETAPI_KEY: ${{ secrets.COMETAPI_KEY }}
run: test -n "$COMETAPI_KEY"
- name: Check out the verified release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.build.outputs.release-commit }}
persist-credentials: false
- name: Require the exact verified release commit
env:
RELEASE_COMMIT: ${{ needs.build.outputs.release-commit }}
run: test "$(git rev-parse HEAD)" = "$RELEASE_COMMIT"
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
- name: Install the pinned uv frontend
run: python -m pip install --disable-pip-version-check "uv==$UV_VERSION"
- name: Reproduce the locked release environment
run: uv sync --locked
- name: Run the bounded exact-release live suite
env:
COMETAPI_KEY: ${{ secrets.COMETAPI_KEY }}
run: uv run pytest -m live --maxfail=1 -q
publish:
name: Publish verified artifacts with PyPI OIDC
if: >-
always() &&
!cancelled() &&
github.run_attempt == 1 &&
needs.build.result == 'success' &&
needs.release-live-smoke.result == 'success'
needs:
- build
- release-live-smoke
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: pypi
url: https://pypi.org/project/cometapi/${{ needs.build.outputs.version }}/
permissions:
contents: read
id-token: write
steps:
- name: Download the verified release bundle
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-${{ needs.build.outputs.version }}
path: release-bundle
- name: Recheck immutable artifact digests
working-directory: release-bundle
run: sha256sum --check artifact-sha256.txt
- name: Publish through the configured PyPI Trusted Publisher
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
packages-dir: release-bundle/dist/
print-hash: true
attestations: true
verify-registry:
name: Verify the public registry artifact
if: >-
always() &&
!cancelled() &&
github.run_attempt == 1 &&
needs.build.result == 'success' &&
needs.publish.result == 'success'
needs:
- build
- publish
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Check out the registry verification source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.build.outputs.release-commit }}
persist-credentials: false
- name: Download the verified release bundle after checkout
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-${{ needs.build.outputs.version }}
path: release-bundle
- name: Require retained pre-publication digest evidence
run: test -f release-bundle/artifact-sha256.txt
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
- name: Install the pinned provenance verifier
env:
PIP_BUILD_CONSTRAINT: ""
PIP_CONFIG_FILE: /dev/null
PIP_CONSTRAINT: ""
PIP_EXTRA_INDEX_URL: ""
PIP_FIND_LINKS: ""
PIP_REQUIREMENT: ""
run: >-
python -m pip --isolated install
--disable-pip-version-check
--index-url https://pypi.org/simple/
--no-cache-dir
"pypi-attestations==0.0.29"
- name: Verify public artifact identity, digests, and provenance
env:
RELEASE_VERSION: ${{ needs.build.outputs.version }}
run: >-
python scripts/check_registry_release.py
--version "$RELEASE_VERSION"
--repository "https://github.com/${{ github.repository }}"
--digest-file release-bundle/artifact-sha256.txt
--download-directory registry-artifacts
--attempts 12
--retry-delay 10
- name: Install from public PyPI and run the isolated mocked-call smoke
env:
RELEASE_VERSION: ${{ needs.build.outputs.version }}
run: >-
python scripts/check_clean_install.py
--expected-version "$RELEASE_VERSION"
--requirement "cometapi==$RELEASE_VERSION"
--index-url https://pypi.org/simple/
--attempts 12
--retry-delay 10