Release process
Release process
This runbook is the single place that explains how to cut a release. Follow it verbatim so every release has matching pyproject.toml version, CHANGELOG.md entry, annotated git tag, and GitHub Release with attached artifacts.
1. Decide the version
SemVer + a pre-release chain:
0.x.y-alpha.N— internal / friendly testers0.x.y-beta.N— external beta0.x.y-rc.N— release candidate0.x.y— stable milestone (pre-1.0)1.0.0-rc.N→1.0.0— first production-ready release
Decide what's in the release by walking CHANGELOG.md's [Unreleased] section:
- Security fix or breaking change: minor bump pre-1.0, major post-1.0.
- Back-compat feature: minor bump.
- Back-compat bugfix / doc / internal cleanup: patch bump.
2. Move [Unreleased] → dated section
Edit CHANGELOG.md:
- Insert a new
## [<tag>] — <YYYY-MM-DD>heading immediately below## [Unreleased]. - Move every bullet under
## [Unreleased]into the new section. - Leave
## [Unreleased]in place with*No unreleased changes.*as a placeholder. - Update the reference links at the bottom of the file: ``` [Unreleased]: https://github.com/aethon-network/platform/compare/<new-tag>...HEAD [<new-tag>]: https://github.com/aethon-network/platform/releases/tag/<new-tag> [<prev-tag>]: https://github.com/aethon-network/platform/releases/tag/<prev-tag> ```
3. Bump the version
Edit pyproject.toml:
[project]
version = "0.2.0-beta.1"
4. Commit and open a release PR
git checkout -b release/v0.2.0-beta.1
git add CHANGELOG.md pyproject.toml
git commit -s -m "Release v0.2.0-beta.1"
git push origin release/v0.2.0-beta.1
gh pr create --title "Release v0.2.0-beta.1" --body-file - <<'EOF'
## Summary
Release v0.2.0-beta.1. See CHANGELOG.md for the full set of changes.
## Test plan
- [x] Local `pytest -q` green
- [x] `pip-audit . --strict` clean
- [x] `bandit -r packages plugins apps -ll --skip B101` clean
EOF
Merge the PR once CI is green.
5. Tag + push
Annotated, signed tag from the merged main:
git checkout main
git pull --ff-only
git tag -s v0.2.0-beta.1 -m "beta.1 — first external beta"
git push origin v0.2.0-beta.1
The release.yml workflow fires automatically on tag push. It:
- Runs the full CI matrix (
test,security,contractjobs). - Builds wheel + sdist.
- Creates a GitHub Release named after the tag, with the current
CHANGELOG.mdas release notes, and attaches the built artifacts. - Marks the release as pre-release when the tag has an
-alpha/-beta/-rcsuffix.
If any gate fails, the release is never created — delete the tag and fix forward:
git tag -d v0.2.0-beta.1
git push origin :refs/tags/v0.2.0-beta.1
# fix, re-commit, re-tag
6. Announce
- Post the release URL to the team's coordination channel.
- Point testers at
QUICKSTART.mdanddocs/OPERATOR_GUIDE.md. - If a breaking change landed, call it out explicitly so consumers update their integrations.
Hotfix releases
Patches to a shipped release (e.g. a security fix for v0.2.0-beta.1 while main is on v0.3.0-alpha.1 work):
- Branch from the tag: ```sh git checkout -b hotfix/v0.2.0-beta.2 v0.2.0-beta.1 ```
- Cherry-pick or re-apply the fix commits.
- Follow steps 2–5 above. The tag is a patch bump of the pre-release chain (
-beta.1→-beta.2). - Cherry-pick forward into
mainif the fix isn't already there, so the next release includes it.
PyPI publishing (deferred)
PyPI publishing is intentionally not in the release workflow today. When the project is ready:
- Register the package name on PyPI.
- Configure trusted publishing so GitHub Actions authenticates via OIDC, not a long-lived token.
- Add a
pypijob torelease.yml: ```yaml pypi: needs: build runs-on: ubuntu-latest environment: pypi permissions: id-token: write steps:- uses: actions/download-artifact@v4 with: name: dist path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1 ```
Do not use PYPI_API_TOKEN secrets — the Codex audit flagged long-lived tokens as an anti-pattern and trusted publishing is the current best practice.