diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 95e00f68..c188e697 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -214,12 +214,47 @@ jobs: if: matrix.python-version == '3.14' run: ls -laR dist/ - - name: Store build artifacts + - name: Stage publication artifacts + if: matrix.python-version == '3.14' + run: | + set -euo pipefail + mkdir -p publish-dist release-assets-dist + cp dist/*.tar.gz dist/*.whl publish-dist/ + cp dist/*.tar.gz dist/*.whl dist/*.sha256 release-assets-dist/ + + echo "PyPI distributions:" + ls -la publish-dist/ + echo "" + echo "GitHub release assets:" + ls -la release-assets-dist/ + + pypi_count="$(find publish-dist -maxdepth 1 -type f | wc -l | tr -d ' ')" + release_count="$(find release-assets-dist -maxdepth 1 -type f | wc -l | tr -d ' ')" + + [ "${pypi_count}" = "2" ] || { + echo "::error::Expected 2 uploadable distributions, found ${pypi_count}" + exit 1 + } + [ "${release_count}" = "3" ] || { + echo "::error::Expected 3 GitHub release assets, found ${release_count}" + exit 1 + } + + - name: Store PyPI distributions if: matrix.python-version == '3.14' uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: dist - path: dist/ + name: pypi-dist + path: publish-dist/ + retention-days: 7 + if-no-files-found: error + + - name: Store GitHub release assets + if: matrix.python-version == '3.14' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: release-assets + path: release-assets-dist/ retention-days: 7 if-no-files-found: error @@ -231,7 +266,7 @@ jobs: steps: - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: - name: dist + name: pypi-dist path: dist/ - name: Publish to TestPyPI @@ -350,7 +385,7 @@ jobs: - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: - name: dist + name: release-assets path: dist/ - name: Publish GitHub release assets @@ -383,7 +418,7 @@ jobs: steps: - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: - name: dist + name: pypi-dist path: dist/ - name: Publish to PyPI diff --git a/CHANGELOG.md b/CHANGELOG.md index 90e2091e..179d9aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **Release publication now stages PyPI uploads separately from GitHub Release assets.** The + publish workflow now uploads only the wheel and source distribution to PyPI-facing jobs while + keeping the `.sha256` receipt in the GitHub Release asset path, and the maintainer runbook now + documents rerunning `workflow_dispatch` against an existing tag to recover a partial release + without moving or recreating the tag. + ## [0.163.0] - 2026-04-22 ### Added diff --git a/docs/RELEASE_PROTOCOL.md b/docs/RELEASE_PROTOCOL.md index 878cc83f..0ac4bd13 100644 --- a/docs/RELEASE_PROTOCOL.md +++ b/docs/RELEASE_PROTOCOL.md @@ -230,6 +230,13 @@ gh run list --workflow=publish.yml --event=push --commit "$TAG_SHA" --limit=20 gh run view --log-failed ``` +Publication invariant: + +- PyPI-facing jobs may receive only uploadable distribution files (`.tar.gz` and `.whl`). +- The `ftllexengine-X.Y.Z.sha256` receipt is a GitHub Release asset, not a PyPI upload. +- If the workflow stages both distributions and the checksum receipt into the same artifact, + treat that as a release-pipeline defect and fix the workflow before rerunning publication. + If you need to rerun publication for the existing tag, rerun the workflow against that tag. Do not move or recreate the tag: @@ -238,6 +245,11 @@ gh workflow run publish.yml -f release_tag=vX.Y.Z gh workflow run publish.yml -f release_tag=vX.Y.Z -f publish_to_testpypi=true ``` +If the tag push reaches a partial success state — for example, GitHub Release assets publish but +the PyPI job fails — repair the workflow on `main`, merge the fix, and then rerun +`workflow_dispatch` for the existing tag. Do not delete, move, or recreate the tag to retrigger +publication. + If GitHub Release assets need manual convergence after the workflow, use: ```bash