From d980cbe052ea090a8ad2ed1dc51b72e7c6bc1b16 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:23:42 +0000 Subject: [PATCH 1/5] ci: use one SDK release PR --- .github/workflows/release-please.yml | 76 ++++++++++++++++++++++++++-- .github/workflows/stlc-promote.yml | 71 +++++++++++++------------- release-please-config.json | 1 + 3 files changed, 110 insertions(+), 38 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 3f6bf798..4737ca42 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -1,12 +1,21 @@ name: Release Please + +# `next` accumulates validated SDK changes in one versioned PR to `main`. +# Merging that PR creates the GitHub release; the package publishing workflow +# runs from the release event. on: push: branches: + - next - main permissions: contents: read +concurrency: + group: release-please + cancel-in-progress: false + jobs: release-please: if: github.repository == 'kernel/kernel-python-sdk' @@ -26,7 +35,68 @@ jobs: permission-pull-requests: write permission-workflows: write - - uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1 - id: release + - name: Set up Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '18.20.2' + + - name: Set up pnpm + uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4 with: - token: ${{ steps.release-token.outputs.token }} + version: '9.11.0' + run_install: false + + - name: Build pinned release tooling + id: tooling + env: + RELEASE_PLEASE_DIR: ${{ runner.temp }}/release-please + RELEASE_PLEASE_SHA: a116e1e520e0f87824acf46a2e79c91d41e819d7 + run: | + set -euo pipefail + rm -rf "$RELEASE_PLEASE_DIR" + git init "$RELEASE_PLEASE_DIR" + git -C "$RELEASE_PLEASE_DIR" remote add origin https://github.com/stainless-api/release-please.git + git -C "$RELEASE_PLEASE_DIR" fetch --depth=1 origin "$RELEASE_PLEASE_SHA" + git -C "$RELEASE_PLEASE_DIR" checkout --detach FETCH_HEAD + pnpm --dir "$RELEASE_PLEASE_DIR" install --frozen-lockfile + pnpm --dir "$RELEASE_PLEASE_DIR" build + echo "cli=$RELEASE_PLEASE_DIR/build/src/bin/release-please.js" >> "$GITHUB_OUTPUT" + + - name: Open or update the release PR + if: github.ref_name == 'next' + env: + GH_TOKEN: ${{ steps.release-token.outputs.token }} + RELEASE_PLEASE: ${{ steps.tooling.outputs.cli }} + run: | + set -euo pipefail + node "$RELEASE_PLEASE" release-pr \ + --repo-url "$GITHUB_REPOSITORY" \ + --token "$GH_TOKEN" \ + --target-branch main \ + --changes-branch next + + - name: Remove the legacy promotion PR + if: github.ref_name == 'next' + env: + GH_TOKEN: ${{ steps.release-token.outputs.token }} + run: | + set -euo pipefail + legacy=$(gh pr list --repo "$GITHUB_REPOSITORY" --head stainless/release \ + --state open --json number --jq '.[].number') + for pr in $legacy; do + gh pr close "$pr" --repo "$GITHUB_REPOSITORY" \ + --comment "Superseded by the versioned release PR from next to main." + done + gh api -X DELETE "repos/$GITHUB_REPOSITORY/git/refs/heads/stainless/release" >/dev/null 2>&1 || true + + - name: Create the GitHub release + if: github.ref_name == 'main' + env: + GH_TOKEN: ${{ steps.release-token.outputs.token }} + RELEASE_PLEASE: ${{ steps.tooling.outputs.cli }} + run: | + set -euo pipefail + node "$RELEASE_PLEASE" github-release \ + --repo-url "$GITHUB_REPOSITORY" \ + --token "$GH_TOKEN" \ + --target-branch main diff --git a/.github/workflows/stlc-promote.yml b/.github/workflows/stlc-promote.yml index 5123a90a..d917658c 100644 --- a/.github/workflows/stlc-promote.yml +++ b/.github/workflows/stlc-promote.yml @@ -1,8 +1,9 @@ -name: Promote SDKs +name: Promote SDK changes -# Production requires pull requests, so staging is promoted through a merge- -# commit PR. Never squash or rebase this cross-repo PR: preserving the incoming -# commits keeps production and staging on one ancestry chain. +# Staging is the generator's integration history. Production `next` is the +# developer-facing queue for the next release. This workflow combines the +# latest released state with validated staging changes, then advances `next`. +# Release automation maintains the single versioned PR from `next` to `main`. on: push: branches: [main] @@ -35,49 +36,49 @@ jobs: repositories: kernel-python-sdk permission-contents: write permission-workflows: write - permission-pull-requests: write - - name: Fetch production main + - name: Fetch production branches + id: production env: GH_TOKEN: ${{ steps.production-token.outputs.token }} PRODUCTION_REPO: kernel/kernel-python-sdk run: | - git remote add production "https://x-access-token:${GH_TOKEN}@github.com/${PRODUCTION_REPO}.git" + set -euo pipefail + git remote add production \ + "https://x-access-token:${GH_TOKEN}@github.com/${PRODUCTION_REPO}.git" git fetch production main - - - name: Check whether production already has staging's content - id: diff - run: | - MERGED=$(git merge-tree --write-tree production/main origin/main) || MERGED=conflict - PRODUCTION_TREE=$(git rev-parse 'production/main^{tree}') - if [ "$MERGED" = "$PRODUCTION_TREE" ]; then - echo "Production already contains staging's content. Nothing to promote." - echo "synced=true" >> "$GITHUB_OUTPUT" + if git ls-remote --exit-code --heads production next >/dev/null 2>&1; then + git fetch production next + echo "has_next=true" >> "$GITHUB_OUTPUT" else - echo "synced=false" >> "$GITHUB_OUTPUT" + echo "has_next=false" >> "$GITHUB_OUTPUT" fi - - name: Push the production release branch - if: steps.diff.outputs.synced == 'false' + - name: Prepare the next release branch env: + APP_SLUG: ${{ steps.production-token.outputs.app-slug }} GH_TOKEN: ${{ steps.production-token.outputs.token }} - PRODUCTION_REPO: kernel/kernel-python-sdk - run: git push production origin/main:refs/heads/stainless/release --force + HAS_NEXT: ${{ steps.production.outputs.has_next }} + run: | + set -euo pipefail + bot_id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id) + git config user.name "${APP_SLUG}[bot]" + git config user.email "${bot_id}+${APP_SLUG}[bot]@users.noreply.github.com" + + git checkout -B stlc/promote-next origin/main + if ! git merge-base --is-ancestor production/main HEAD; then + git merge --no-edit production/main + fi - - name: Open or update the promote PR - if: steps.diff.outputs.synced == 'false' + if [ "$HAS_NEXT" = "true" ] && ! git merge-base --is-ancestor production/next HEAD; then + echo "::error title=Promotion blocked::production next has commits that are not present in staging or production main." + exit 1 + fi + + - name: Update the pending release env: GH_TOKEN: ${{ steps.production-token.outputs.token }} - PRODUCTION_REPO: kernel/kernel-python-sdk run: | - body=$(mktemp) - git log --oneline production/main..origin/main > "$body" - existing=$(gh pr list --repo "$PRODUCTION_REPO" --head stainless/release --state open --json number --jq 'if length == 0 then "" else .[0].number end') - if [ -z "$existing" ]; then - gh pr create --repo "$PRODUCTION_REPO" --base main --head stainless/release --title "Release SDK updates" --body-file "$body" - else - gh pr edit "$existing" --repo "$PRODUCTION_REPO" --title "Release SDK updates" --body-file "$body" - fi - if ! gh pr merge stainless/release --repo "$PRODUCTION_REPO" --merge --auto; then - echo "::warning title=Manual promotion required::Merge the promote PR with a merge commit." - fi + set -euo pipefail + git push production HEAD:refs/heads/next + echo "Updated production next; the versioned release PR will be opened or refreshed." diff --git a/release-please-config.json b/release-please-config.json index bdba3b86..6ffb6a57 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -10,6 +10,7 @@ "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": false, "pull-request-header": "Automated Release PR", + "pull-request-footer": "Merge this pull request with a merge commit. Merging creates the GitHub release and publishes the package.", "pull-request-title-pattern": "release: ${version}", "changelog-sections": [ { From 65c00dc5404e9c886352b6831e54c1326f2557e6 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 10 Aug 2026 21:08:51 +0000 Subject: [PATCH 2/5] Preserve pending SDK release ancestry --- .github/workflows/stlc-promote.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/stlc-promote.yml b/.github/workflows/stlc-promote.yml index d917658c..f625307d 100644 --- a/.github/workflows/stlc-promote.yml +++ b/.github/workflows/stlc-promote.yml @@ -65,14 +65,21 @@ jobs: git config user.name "${APP_SLUG}[bot]" git config user.email "${bot_id}+${APP_SLUG}[bot]@users.noreply.github.com" - git checkout -B stlc/promote-next origin/main + if [ "$HAS_NEXT" = "true" ]; then + git checkout -B stlc/promote-next production/next + else + git checkout -B stlc/promote-next production/main + fi + if ! git merge-base --is-ancestor production/main HEAD; then git merge --no-edit production/main fi + if ! git merge-base --is-ancestor origin/main HEAD; then + git merge --no-edit origin/main + fi - if [ "$HAS_NEXT" = "true" ] && ! git merge-base --is-ancestor production/next HEAD; then - echo "::error title=Promotion blocked::production next has commits that are not present in staging or production main." - exit 1 + if [ "$HAS_NEXT" = "true" ]; then + git merge-base --is-ancestor production/next HEAD fi - name: Update the pending release From 0bd6a7f0e857efb66257c428da50eeba24dee175 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 10 Aug 2026 21:34:49 +0000 Subject: [PATCH 3/5] Open recovery PRs for promotion conflicts --- .github/workflows/stlc-promote.yml | 44 ++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stlc-promote.yml b/.github/workflows/stlc-promote.yml index f625307d..e63aaf9e 100644 --- a/.github/workflows/stlc-promote.yml +++ b/.github/workflows/stlc-promote.yml @@ -35,6 +35,7 @@ jobs: owner: kernel repositories: kernel-python-sdk permission-contents: write + permission-pull-requests: write permission-workflows: write - name: Fetch production branches @@ -59,12 +60,49 @@ jobs: APP_SLUG: ${{ steps.production-token.outputs.app-slug }} GH_TOKEN: ${{ steps.production-token.outputs.token }} HAS_NEXT: ${{ steps.production.outputs.has_next }} + PRODUCTION_REPO: kernel/kernel-python-sdk run: | set -euo pipefail bot_id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id) git config user.name "${APP_SLUG}[bot]" git config user.email "${bot_id}+${APP_SLUG}[bot]@users.noreply.github.com" + open_conflict_pr() { + source_ref=$1 + source_name=$2 + advance_next=$3 + conflict_branch=stlc/promotion-conflict + + git merge --abort + existing=$(gh pr list --repo "$PRODUCTION_REPO" --base next \ + --head "$conflict_branch" --state open --json url --jq '.[0].url // ""') + if [ -n "$existing" ]; then + echo "::error title=SDK promotion blocked::Resolve the existing recovery PR: $existing" + exit 1 + fi + + if [ "$advance_next" = "true" ]; then + git push production HEAD:refs/heads/next + fi + git push production "$source_ref:refs/heads/$conflict_branch" --force + + body=$(mktemp) + printf '%s\n' \ + '## SDK promotion conflict' \ + '' \ + "The automated promotion could not merge $source_name into the pending next release." \ + '' \ + 'Resolve the conflicts on this branch, validate the SDK, mark this PR ready, and merge it with a merge commit.' \ + '' \ + 'After merging, rerun the staging Promote SDK changes workflow to include any newer generated changes.' \ + > "$body" + recovery_url=$(gh pr create --repo "$PRODUCTION_REPO" --draft \ + --base next --head "$conflict_branch" \ + --title 'chore: resolve SDK promotion conflict' --body-file "$body") + echo "::error title=SDK promotion conflict::Resolve the recovery PR: $recovery_url" + exit 1 + } + if [ "$HAS_NEXT" = "true" ]; then git checkout -B stlc/promote-next production/next else @@ -72,10 +110,12 @@ jobs: fi if ! git merge-base --is-ancestor production/main HEAD; then - git merge --no-edit production/main + git merge --no-edit production/main || \ + open_conflict_pr production/main 'production main' false fi if ! git merge-base --is-ancestor origin/main HEAD; then - git merge --no-edit origin/main + git merge --no-edit origin/main || \ + open_conflict_pr origin/main 'validated staging changes' true fi if [ "$HAS_NEXT" = "true" ]; then From e639638e52a2abcacc2bf1d0d81da09ccb023142 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 10 Aug 2026 21:59:45 +0000 Subject: [PATCH 4/5] Preserve conflict handler error checks --- .github/workflows/stlc-promote.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stlc-promote.yml b/.github/workflows/stlc-promote.yml index e63aaf9e..3bcd88c7 100644 --- a/.github/workflows/stlc-promote.yml +++ b/.github/workflows/stlc-promote.yml @@ -110,12 +110,14 @@ jobs: fi if ! git merge-base --is-ancestor production/main HEAD; then - git merge --no-edit production/main || \ + if ! git merge --no-edit production/main; then open_conflict_pr production/main 'production main' false + fi fi if ! git merge-base --is-ancestor origin/main HEAD; then - git merge --no-edit origin/main || \ + if ! git merge --no-edit origin/main; then open_conflict_pr origin/main 'validated staging changes' true + fi fi if [ "$HAS_NEXT" = "true" ]; then From d2434504d0c0bdb10350d4705bced85f2e1e89c7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:06:18 +0000 Subject: [PATCH 5/5] release: 0.88.1 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- src/kernel/_version.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d80a91e2..cba40534 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.88.0" + ".": "0.88.1" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9137ac6f..53559a03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.88.1 (2026-08-10) + +Full Changelog: [v0.88.0...v0.88.1](https://github.com/kernel/kernel-python-sdk/compare/v0.88.0...v0.88.1) + ## [0.88.0](https://github.com/kernel/kernel-python-sdk/compare/v0.87.0...v0.88.0) (2026-08-10) ### Features diff --git a/pyproject.toml b/pyproject.toml index db2cb8e0..c164cb5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.88.0" +version = "0.88.1" description = "The official Python library for the kernel API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/kernel/_version.py b/src/kernel/_version.py index 892c18cb..6cb28d2b 100644 --- a/src/kernel/_version.py +++ b/src/kernel/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "kernel" -__version__ = "0.88.0" # x-release-please-version +__version__ = "0.88.1" # x-release-please-version