From b2ce2b1b2530b51c029fa2619f011cf77044fbd7 Mon Sep 17 00:00:00 2001 From: Gavin Sharp Date: Fri, 10 Jul 2026 18:42:05 -0400 Subject: [PATCH 1/5] fix(sdk-release-gate): prefix origin release tags --- .github/workflows/sdk-release-finalize.yml | 4 ++++ .github/workflows/sdk-release-gate.yml | 7 +------ README.md | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/sdk-release-finalize.yml b/.github/workflows/sdk-release-finalize.yml index 6f8f508..d89dc73 100644 --- a/.github/workflows/sdk-release-finalize.yml +++ b/.github/workflows/sdk-release-finalize.yml @@ -115,6 +115,10 @@ jobs: echo "::error::$label must not begin with '-': $tag" exit 1 fi + if [[ "$tag" =~ ^[0-9a-fA-F]{40}(/|$) ]]; then + echo "::error::$label must not begin with a 40-character SHA: $tag" + exit 1 + fi if ! git check-ref-format "refs/tags/$tag"; then echo "::error::Invalid $label: $tag" exit 1 diff --git a/.github/workflows/sdk-release-gate.yml b/.github/workflows/sdk-release-gate.yml index 803cf3e..2ed1036 100644 --- a/.github/workflows/sdk-release-gate.yml +++ b/.github/workflows/sdk-release-gate.yml @@ -30,10 +30,6 @@ on: description: "Optional prefix to prepend to the SDK version tag." type: string default: "" - origin-tag-prefix: - description: "Optional prefix to prepend to the origin tag, before originGitCommit/version_tag." - type: string - default: "" outputs: should_publish: description: "true only when the version tag did not already exist and callers should publish." @@ -81,7 +77,6 @@ jobs: SPEC_PATH: ${{ inputs.spec-path }} METADATA_PATH: ${{ inputs.metadata-path }} VERSION_TAG_PREFIX: ${{ inputs.version-tag-prefix }} - ORIGIN_TAG_PREFIX: ${{ inputs.origin-tag-prefix }} steps: - name: Checkout release commit uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 @@ -162,7 +157,7 @@ jobs: exit 1 fi - origin_tag="${ORIGIN_TAG_PREFIX}${origin_commit}/${VERSION_TAG}" + origin_tag="fern-origin/${origin_commit}/${VERSION_TAG}" if [[ "$origin_tag" == -* ]]; then echo "::error::Origin tag must not begin with '-': $origin_tag" exit 1 diff --git a/README.md b/README.md index 36980ca..e190ec7 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ Shared GitHub Actions and reusable workflows for the Phenoml SDK repos. shared pre-publish release gate for generated SDKs: extract the SDK version, report whether the release should publish, repair metadata, or skip, read Fern's `originGitCommit`, verify the bundled OpenAPI spec for new publishes, - and validate the origin release tag name. Version tags, origin release tags, - GitHub releases, package builds, and registry publish jobs stay in each SDK - repo. + and validate the prefixed `fern-origin//` + release tag name. Version tags, origin release tags, GitHub releases, package + builds, and registry publish jobs stay in each SDK repo. - [`sdk-release-finalize`](.github/workflows/sdk-release-finalize.yml) — reconciles release metadata after an SDK package publish succeeds: it ensures the normal SDK version tag/release, the Fern origin release tag, and, when From edf6317a28d1f433220d6c7af29e9aaf8aeb77bc Mon Sep 17 00:00:00 2001 From: Gavin Sharp Date: Fri, 10 Jul 2026 18:45:01 -0400 Subject: [PATCH 2/5] fix(sdk-release-gate): use backend origin tag namespace --- .github/workflows/sdk-release-gate.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdk-release-gate.yml b/.github/workflows/sdk-release-gate.yml index 2ed1036..0a88a19 100644 --- a/.github/workflows/sdk-release-gate.yml +++ b/.github/workflows/sdk-release-gate.yml @@ -157,7 +157,7 @@ jobs: exit 1 fi - origin_tag="fern-origin/${origin_commit}/${VERSION_TAG}" + origin_tag="backend-origin/${origin_commit}/${VERSION_TAG}" if [[ "$origin_tag" == -* ]]; then echo "::error::Origin tag must not begin with '-': $origin_tag" exit 1 diff --git a/README.md b/README.md index e190ec7..3b8e1f7 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Shared GitHub Actions and reusable workflows for the Phenoml SDK repos. shared pre-publish release gate for generated SDKs: extract the SDK version, report whether the release should publish, repair metadata, or skip, read Fern's `originGitCommit`, verify the bundled OpenAPI spec for new publishes, - and validate the prefixed `fern-origin//` + and validate the prefixed `backend-origin//` release tag name. Version tags, origin release tags, GitHub releases, package builds, and registry publish jobs stay in each SDK repo. - [`sdk-release-finalize`](.github/workflows/sdk-release-finalize.yml) — From 7641bf12ab291312005dc6342c3124af21ef12a9 Mon Sep 17 00:00:00 2001 From: Gavin Sharp Date: Fri, 10 Jul 2026 18:48:28 -0400 Subject: [PATCH 3/5] fix(sdk-release-workflows): use moving backend origin tags --- .github/workflows/sdk-release-finalize.yml | 51 +++++++++++++++++++--- .github/workflows/sdk-release-gate.yml | 4 +- README.md | 6 +-- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/.github/workflows/sdk-release-finalize.yml b/.github/workflows/sdk-release-finalize.yml index d89dc73..a2fd4f2 100644 --- a/.github/workflows/sdk-release-finalize.yml +++ b/.github/workflows/sdk-release-finalize.yml @@ -57,7 +57,7 @@ on: description: "The origin release tag created or verified by this workflow, or the unverified input when origin tag reconciliation is disabled." value: ${{ jobs.finalize.outputs.origin_tag }} origin_tag_created: - description: "true when a new origin release tag was pushed; false when it already existed or origin tag reconciliation is disabled." + description: "true when the origin release tag was created or moved; false when it already existed or origin tag reconciliation is disabled." value: ${{ jobs.finalize.outputs.origin_tag_created }} fern_generation_tag: description: "The Fern replay generation tag created or verified by this workflow, or empty when replay.lock is absent or Fern generation preservation is disabled." @@ -225,6 +225,9 @@ jobs: target_commit=$(git rev-parse --verify "${TAG_TARGET}^{commit}") existing_commit=$(git rev-parse -q --verify "refs/tags/${ORIGIN_TAG}^{commit}" || true) + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + if [[ -n "$existing_commit" ]]; then if [[ "$existing_commit" == "$target_commit" ]]; then echo "Tag $ORIGIN_TAG already points at $target_commit." @@ -232,13 +235,25 @@ jobs: exit 0 fi - echo "::error::Tag $ORIGIN_TAG already points at $existing_commit, not $target_commit." + if git merge-base --is-ancestor "$existing_commit" "$target_commit"; then + echo "Moving tag $ORIGIN_TAG from $existing_commit to $target_commit." + git tag -f -a "$ORIGIN_TAG" "$target_commit" -m "Latest SDK release for Fern originGitCommit $ORIGIN_COMMIT" + git push --force origin "refs/tags/$ORIGIN_TAG" + echo "created=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if git merge-base --is-ancestor "$target_commit" "$existing_commit"; then + echo "Tag $ORIGIN_TAG already points at newer release commit $existing_commit; leaving it in place." + echo "created=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "::error::Tag $ORIGIN_TAG already points at divergent commit $existing_commit, not $target_commit." exit 1 fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git tag -a "$ORIGIN_TAG" "$target_commit" -m "Release $VERSION_TAG for Fern originGitCommit $ORIGIN_COMMIT" + git tag -a "$ORIGIN_TAG" "$target_commit" -m "Latest SDK release for Fern originGitCommit $ORIGIN_COMMIT" git push origin "refs/tags/$ORIGIN_TAG" echo "created=true" >> "$GITHUB_OUTPUT" @@ -338,6 +353,30 @@ jobs: echo "$label tag $tag points at $expected_commit." } + verify_moving_tag() { + local label="$1" + local tag="$2" + local expected_commit="$3" + local actual_commit + + actual_commit=$(git rev-parse -q --verify "refs/tags/${tag}^{commit}" || true) + if [[ -z "$actual_commit" ]]; then + echo "::error::$label tag $tag does not exist." + exit 1 + fi + if [[ "$actual_commit" == "$expected_commit" ]]; then + echo "$label tag $tag points at $expected_commit." + return + fi + if git merge-base --is-ancestor "$expected_commit" "$actual_commit"; then + echo "$label tag $tag points at newer release commit $actual_commit." + return + fi + + echo "::error::$label tag $tag points at $actual_commit, not $expected_commit or a newer release commit." + exit 1 + } + target_commit=$(git rev-parse --verify "${TAG_TARGET}^{commit}") git fetch --force --tags origin @@ -353,7 +392,7 @@ jobs: fi if [[ "$CREATE_ORIGIN_TAG" == "true" ]]; then - verify_tag "originGitCommit" "$ORIGIN_TAG" "$target_commit" + verify_moving_tag "originGitCommit" "$ORIGIN_TAG" "$target_commit" fi if [[ "$PRESERVE_FERN_GENERATION" == "true" ]]; then diff --git a/.github/workflows/sdk-release-gate.yml b/.github/workflows/sdk-release-gate.yml index 0a88a19..6d69d2f 100644 --- a/.github/workflows/sdk-release-gate.yml +++ b/.github/workflows/sdk-release-gate.yml @@ -140,8 +140,6 @@ jobs: - name: Read originGitCommit id: origin - env: - VERSION_TAG: ${{ steps.version.outputs.version_tag }} shell: bash run: | set -euo pipefail @@ -157,7 +155,7 @@ jobs: exit 1 fi - origin_tag="backend-origin/${origin_commit}/${VERSION_TAG}" + origin_tag="backend-origin/${origin_commit}" if [[ "$origin_tag" == -* ]]; then echo "::error::Origin tag must not begin with '-': $origin_tag" exit 1 diff --git a/README.md b/README.md index 3b8e1f7..09e8323 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ Shared GitHub Actions and reusable workflows for the Phenoml SDK repos. shared pre-publish release gate for generated SDKs: extract the SDK version, report whether the release should publish, repair metadata, or skip, read Fern's `originGitCommit`, verify the bundled OpenAPI spec for new publishes, - and validate the prefixed `backend-origin//` - release tag name. Version tags, origin release tags, GitHub releases, package - builds, and registry publish jobs stay in each SDK repo. + and validate the prefixed `backend-origin/` release tag + name. Version tags, origin release tags, GitHub releases, package builds, + and registry publish jobs stay in each SDK repo. - [`sdk-release-finalize`](.github/workflows/sdk-release-finalize.yml) — reconciles release metadata after an SDK package publish succeeds: it ensures the normal SDK version tag/release, the Fern origin release tag, and, when From 2ba99d03b567c7564874eff9fd586091cfb1260f Mon Sep 17 00:00:00 2001 From: Gavin Sharp Date: Fri, 10 Jul 2026 18:50:28 -0400 Subject: [PATCH 4/5] fix(sdk-release-gate): reject sha-prefixed version tags --- .github/workflows/sdk-release-gate.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/sdk-release-gate.yml b/.github/workflows/sdk-release-gate.yml index 6d69d2f..c0d0640 100644 --- a/.github/workflows/sdk-release-gate.yml +++ b/.github/workflows/sdk-release-gate.yml @@ -128,6 +128,10 @@ jobs: echo "::error::Version tag must not begin with '-': $version_tag" exit 1 fi + if [[ "$version_tag" =~ ^[0-9a-fA-F]{40}(/|$) ]]; then + echo "::error::Version tag must not begin with a 40-character SHA: $version_tag" + exit 1 + fi if ! git check-ref-format "refs/tags/$version_tag"; then echo "::error::Invalid version tag name: $version_tag" exit 1 From 0e79ff7ad5369929c1689d25738a5451e3a453fd Mon Sep 17 00:00:00 2001 From: Gavin Sharp Date: Fri, 10 Jul 2026 18:54:33 -0400 Subject: [PATCH 5/5] fix(sdk-release-finalize): lease backend origin tag moves --- .github/workflows/sdk-release-finalize.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdk-release-finalize.yml b/.github/workflows/sdk-release-finalize.yml index a2fd4f2..391db32 100644 --- a/.github/workflows/sdk-release-finalize.yml +++ b/.github/workflows/sdk-release-finalize.yml @@ -223,7 +223,20 @@ jobs: set -euo pipefail target_commit=$(git rev-parse --verify "${TAG_TARGET}^{commit}") - existing_commit=$(git rev-parse -q --verify "refs/tags/${ORIGIN_TAG}^{commit}" || true) + existing_ref="" + existing_commit="" + if git ls-remote --exit-code --refs origin "refs/tags/${ORIGIN_TAG}" >/dev/null; then + git fetch --force --no-tags origin "refs/tags/${ORIGIN_TAG}:refs/tags/${ORIGIN_TAG}" + existing_ref=$(git rev-parse --verify "refs/tags/${ORIGIN_TAG}") + existing_commit=$(git rev-parse --verify "refs/tags/${ORIGIN_TAG}^{commit}") + else + status=$? + if [[ "$status" -ne 2 ]]; then + echo "::error::Could not read remote tag $ORIGIN_TAG from origin." + exit "$status" + fi + git tag -d "$ORIGIN_TAG" 2>/dev/null || true + fi git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" @@ -238,7 +251,7 @@ jobs: if git merge-base --is-ancestor "$existing_commit" "$target_commit"; then echo "Moving tag $ORIGIN_TAG from $existing_commit to $target_commit." git tag -f -a "$ORIGIN_TAG" "$target_commit" -m "Latest SDK release for Fern originGitCommit $ORIGIN_COMMIT" - git push --force origin "refs/tags/$ORIGIN_TAG" + git push --force-with-lease="refs/tags/$ORIGIN_TAG:$existing_ref" origin "refs/tags/$ORIGIN_TAG" echo "created=true" >> "$GITHUB_OUTPUT" exit 0 fi