Releasing
A release is a version bump, and everything else is automation. This page assumes you can run the gate from Development setup and that your change is already green. For whether a deployment is fit to upgrade, which is a different question, The release gate owns that.
The version drives everything
Section titled “The version drives everything”There are no release branches and no manual tagging. The single source of truth is version in
pyproject.toml, and .github/workflows/publish.yml compares it against the existing tags.
push to main │ ▼ ci.yml lint · lint-imports · typecheck · test │ ├─ red ──▶ nothing else runs │ └─ green ─▶ workflow_dispatch? │ ├─ no ──▶ stop here, the gate ran and that is all │ └─ yes ─▶ v<version> already a tag? │ ├─ yes ─▶ already released, no-op │ └─ no ──▶ uv build │ ▼ publish to PyPI trusted publishing, skip-existing │ ▼ tag v<version> │ ▼ GitHub release with generated notesTwo details in that flow are deliberate. The publish step runs before the tag, so a failed
upload leaves no tag behind and the next attempt simply retries. And skip-existing is on, so
re-running after a partial failure is idempotent rather than an error.
The CI job is reused rather than reimplemented. publish.yml calls ci.yml through
workflow_call, so the gate a release passes is byte for byte the gate a pull request passes.
Why publishing is manual right now
Section titled “Why publishing is manual right now”The release job carries if: github.event_name == 'workflow_dispatch', so an ordinary push to
main runs the full gate and stops. AIZK currently exercises a PostgreSQL row security API from one
reviewed SQLAlchemy commit. The bootstrap script installs that commit after the frozen environment,
but package metadata cannot require an unreleased commit while remaining a normal PyPI dependency.
This is temporary. Once the row security API ships in SQLAlchemy, pyproject.toml can name the
released version directly. The manual guard can then come out and a version bump becomes the whole
release again.
Checklist
Section titled “Checklist”- Bump
versioninpyproject.toml. - Move the
Unreleasedsection ofCHANGELOG.mdunder the new version with today’s date. - Update
README.mdand these docs if the change is user-visible, in the same commit. - Run the local gate.
- Merge to
mainand confirm CI is green. - Trigger
publish.ymlfrom the Actions tab or withgh workflow run publish.yml. - Check the PyPI project page and the docs site.
Commands
Section titled “Commands”Run every Python gate through the frozen .venv after the setup guide installs the reviewed
SQLAlchemy revision.
uv run --no-sync ruff check . && uv run --no-sync ruff format --check .uv run --no-sync lint-importsuv run --no-sync pyrefly checkuv run --no-sync ty check --python .venv --exit-zero-on-warninguv run --no-sync mypy src/aizk src/evaluv run --no-sync python -m pytest -n 4 --dist loadscope --benchmark-disablepnpm --dir docs check && pnpm --dir docs buildBuilding the wheel is the one step CI owns rather than you, using uv build on a clean checkout
inside the workflow.
The docs are a separate workflow
Section titled “The docs are a separate workflow”.github/workflows/docs.yml builds the Astro site on any change under docs/ and runs the page gate
over the result, checking the reading budget, the diagram rule, and every internal link. It publishes
nothing. The site is served from the deployment itself through the docs Compose service, so this
job only proves the build is green.
Writing these docs is the contract it enforces.
One-time setup
Section titled “One-time setup”PyPI publishing uses trusted publishing over OIDC, so
there is no API token anywhere. The publisher is registered against this repository, the workflow
file path publish.yml, and the pypi environment.
- The release gate is the operational question of whether to upgrade.
- Upgrades covers moving a running deployment forward.
- Writing these docs is what the docs workflow checks.