From ac50ebde095ac62425afc03eb6bf388e09802d8c Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Thu, 2 Jul 2026 16:57:13 +0900 Subject: [PATCH 1/3] Gate release publishing --- .github/workflows/publish.yaml | 114 ++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index c0fb7fb13..e7ed099a3 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -3,13 +3,121 @@ name: Publish on: push: tags: - - '**' + - '[0-9]*.[0-9]*.[0-9]*' + workflow_dispatch: + inputs: + skip_test_gate: + description: "Skip the test-gate check for an emergency release. Run only with --ref ." + required: false + default: false + type: boolean env: dists-artifact-name: python-package-distributions jobs: + verify: + name: Verify release gate + runs-on: ubuntu-latest + permissions: + actions: read + checks: read + contents: read + steps: + - name: Validate release tag + env: + RELEASE_REF: ${{ github.ref }} + RELEASE_TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + if [ "$RELEASE_REF" != "refs/tags/$RELEASE_TAG" ]; then + echo "Publish must run from a release tag ref. Use: gh workflow run publish.yaml --ref " >&2 + exit 1 + fi + python3 - <<'PY' + import os + import re + import sys + + tag = os.environ["RELEASE_TAG"] + if re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+[0-9A-Za-z.+-]*", tag): + sys.exit(0) + print(f"Release tag {tag!r} must look like 0.66.3 or 0.66.3rc1.", file=sys.stderr) + sys.exit(1) + PY + - name: Verify test-gate passed + if: ${{ github.event_name != 'workflow_dispatch' || inputs.skip_test_gate == false }} + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.ref_name }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + response="$( + gh api "repos/${REPOSITORY}/commits/refs/tags/${RELEASE_TAG}/check-runs?check_name=test-gate&filter=latest" + )" + runs="$( + jq -c ' + [ + .check_runs[]? + | select(.name == "test-gate") + | { + status, + conclusion, + completed_at, + started_at, + head_sha, + html_url, + run_id: (try ((.html_url // "") | capture("/actions/runs/(?[0-9]+)/job/").id) catch "") + } + ] + | sort_by(.completed_at // .started_at // "") + | reverse + | .[] + ' <<< "$response" + )" + if [ -z "$runs" ]; then + echo "No test-gate check run was found for tag ${RELEASE_TAG}." >&2 + echo "Wait for Test on main to finish, then re-run Publish. For emergency releases, dispatch with skip_test_gate=true." >&2 + exit 1 + fi + + while IFS= read -r run; do + run_id="$(jq -r '.run_id' <<< "$run")" + head_sha="$(jq -r '.head_sha' <<< "$run")" + if [ -z "$run_id" ]; then + continue + fi + metadata="$(gh api "repos/${REPOSITORY}/actions/runs/${run_id}")" + if ! jq -e --arg head_sha "$head_sha" \ + '.name == "Test" and .event == "push" and .head_branch == "main" and .head_sha == $head_sha' \ + <<< "$metadata" > /dev/null; then + continue + fi + status="$(jq -r '.status' <<< "$run")" + conclusion="$(jq -r '.conclusion' <<< "$run")" + html_url="$(jq -r '.html_url' <<< "$run")" + if [ "$status" != "completed" ]; then + echo "test-gate for tag ${RELEASE_TAG} is ${status}; re-run Publish after it completes." >&2 + exit 1 + fi + if [ "$conclusion" != "success" ]; then + echo "test-gate for tag ${RELEASE_TAG} concluded ${conclusion}: ${html_url}" >&2 + exit 1 + fi + exit 0 + done <<< "$runs" + + echo "Found test-gate check runs for tag ${RELEASE_TAG}, but none came from the Test workflow push on main." >&2 + echo "Wait for Test on main to finish, then re-run Publish. For emergency releases, dispatch with skip_test_gate=true." >&2 + exit 1 + - name: Report skipped test gate + if: ${{ github.event_name == 'workflow_dispatch' && inputs.skip_test_gate }} + run: echo "::warning::test-gate verification was skipped for this manual release." + build: + needs: + - verify runs-on: ubuntu-latest permissions: contents: read @@ -20,6 +128,8 @@ jobs: persist-credentials: false - name: Install the latest version of uv uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + with: + enable-cache: false - name: Build package run: uv build --python 3.13 --python-preference only-managed --sdist --wheel . --out-dir dist - name: Store the distribution packages @@ -85,3 +195,5 @@ jobs: build-args: | VERSION=${{ github.ref_name }} platforms: linux/amd64,linux/arm64 + provenance: mode=max + sbom: true From 3352cf3a78a8f321a3415f6f013cfcc492c9b4a0 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Thu, 2 Jul 2026 16:58:37 +0900 Subject: [PATCH 2/3] Tighten release tag gate --- .github/workflows/publish.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index e7ed099a3..a25bff2e0 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -3,7 +3,8 @@ name: Publish on: push: tags: - - '[0-9]*.[0-9]*.[0-9]*' + - '[0-9]+.[0-9]+.[0-9]+' + - '[0-9]+.[0-9]+.[0-9]+rc[0-9]+' workflow_dispatch: inputs: skip_test_gate: @@ -40,7 +41,7 @@ jobs: import sys tag = os.environ["RELEASE_TAG"] - if re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+[0-9A-Za-z.+-]*", tag): + if re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:rc[0-9]+)?", tag): sys.exit(0) print(f"Release tag {tag!r} must look like 0.66.3 or 0.66.3rc1.", file=sys.stderr) sys.exit(1) From d2d3dc83794616283690238a08c60cc6a04154d6 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Thu, 2 Jul 2026 17:06:47 +0900 Subject: [PATCH 3/3] Allow historical backport release tags --- .github/workflows/publish.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index a25bff2e0..7bd991189 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -5,6 +5,7 @@ on: tags: - '[0-9]+.[0-9]+.[0-9]+' - '[0-9]+.[0-9]+.[0-9]+rc[0-9]+' + - '[0-9]+.[0-9]+.[0-9]+\+backport-[0-9]+' workflow_dispatch: inputs: skip_test_gate: @@ -41,9 +42,9 @@ jobs: import sys tag = os.environ["RELEASE_TAG"] - if re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:rc[0-9]+)?", tag): + if re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:rc[0-9]+|\+backport-[0-9]+)?", tag): sys.exit(0) - print(f"Release tag {tag!r} must look like 0.66.3 or 0.66.3rc1.", file=sys.stderr) + print(f"Release tag {tag!r} must look like 0.66.3, 0.66.3rc1, or 0.2.11+backport-1.", file=sys.stderr) sys.exit(1) PY - name: Verify test-gate passed