Release Process
Arbiter publishes several Python distributions from one repository:
server:arbiter-server, the real server runtime package- plugin keys discovered from
plugins/*/pyproject.toml, such asimapforarbiter-imapandsmtpforarbiter-smtp meta:all:arbiter-suite, a zero-code dependency bundle for all real packagesclient:arbiter-client, the platform-tagged native client wheel setskill:arbiter-skill, the platform-neutral agent skill package that declaresarbiter-clientas its native-client companion wheel
Meta package keys do not expand to their dependencies. Selecting meta:all
publishes only the arbiter-suite package.
The native client and skill package versions come from arbiter-server. They
are generated wheel-only artifacts and do not have separate towncrier release
notes.
This page describes publishing mechanics only.
News fragments
Final server, plugin, and meta releases require package-scoped towncrier release
notes for every package that will publish. Final client and skill releases use
generated draft GitHub Release notes because they are wheel-only artifacts tied
to the server version. Dev releases such as 0.9.0.dev1 do not consume release
notes.
Add fragments under the package that changed:
server/news/123.feature.md
plugins/imap/news/123.bugfix.md
plugins/smtp/news/+smtp-only-change.feature.md
meta/arbiter-suite/news/+meta-package-change.feature.md
Use a GitHub issue or PR number when one exists. Use the + orphan prefix when
there is no issue or PR.
Before a final release, preview and build the notes for each package that will publish:
.venv/bin/python -m towncrier build --draft --config server/pyproject.toml --version 0.9.0
.venv/bin/python -m towncrier build --yes --config server/pyproject.toml --version 0.9.0
.venv/bin/python -m towncrier build --draft --config plugins/imap/pyproject.toml --version 0.9.0
.venv/bin/python -m towncrier build --yes --config plugins/imap/pyproject.toml --version 0.9.0
.venv/bin/python -m towncrier build --draft --config plugins/smtp/pyproject.toml --version 0.9.0
.venv/bin/python -m towncrier build --yes --config plugins/smtp/pyproject.toml --version 0.9.0
.venv/bin/python -m towncrier build --draft --config meta/arbiter-suite/pyproject.toml --version 0.9.0
.venv/bin/python -m towncrier build --yes --config meta/arbiter-suite/pyproject.toml --version 0.9.0
Commit the updated NEWS.md files and removed fragments before publishing.
Prepare and publish workflow
Final releases use a two-step GitHub Actions flow and are published from a
stable release branch for the release line. For example, the 0.9 stable line
should have a dedicated branch such as release/0.9; security and maintenance
patches for that line are backported to the same branch and published from
there. New development can continue on main, while the release branch owns
the package state, release notes, draft releases, and patch releases for its
stable line.
There are two release kinds:
new-release-line: move the whole compatibility line, such as0.9.0.dev2to0.9.0; this always prepares all package targets together.regular: publish new plugins, updates to existing plugins, or updates to server/client packages already prepared on the current line.
- Run Prepare Release with the target
release_line,release_kind,publish_packages, and target branch. - If version files or package release notes need changes, the workflow opens or updates a release preparation PR and runs the full platform integration suite on that prepared branch.
- Review and merge the release preparation PR after the PR contents and integration run are approved.
- Run Prepare Release again on the stable release branch. With the release files already prepared, it creates or refreshes the draft GitHub Release tags for the selected final packages and runs the full integration suite on that branch.
- Run Publish with the same
release_lineandpublish_packages. Publish validates the prepared draft releases, reruns lint, the full platform unit matrix, build/smoke checks, and the full platform integration gates from the same stable release branch, uploads to PyPI only after those gates pass, then promotes the draft GitHub Releases.
Because Arbiter can publish several packages at once, prepare and publish are
keyed by package selection rather than by a single release tag. Packages with
the same final version share a draft GitHub Release tag, such as v0.9.0.
Independent plugin patch releases can create separate draft release tags in the
same prepare run.
For regular releases, publish_packages=all means all selected packages whose
local version is newer than PyPI, or whose PyPI project does not exist yet.
Use a comma-separated package key list to release a specific target set.
Dev releases such as 0.9.0.dev1 do not require package release notes or draft
GitHub Releases. Use Publish directly for dev package uploads after normal
CI is green and the matching PyPI trusted publisher is ready.
Publish planning
Before dispatching a publish workflow, use the planner locally to check what the workflow will publish:
.venv/bin/python tools/plan_pypi_publish --packages all
The planner compares local versions with PyPI and reports packages whose local version is newer, or whose PyPI project does not exist yet. It rejects local versions that are older than PyPI.
Limit the publish set with package keys:
.venv/bin/python tools/plan_pypi_publish --packages server
.venv/bin/python tools/plan_pypi_publish --packages server,imap
.venv/bin/python tools/plan_pypi_publish --packages smtp
.venv/bin/python tools/plan_pypi_publish --packages client
.venv/bin/python tools/plan_pypi_publish --packages skill
The planner discovers plugin package keys from plugins/*/pyproject.toml and
reads each selected package's local version independently. Use
tools/upgrade_release_line 0.9 --check to validate that packages remain on
the intended compatibility line.
The CI publish workflow builds the selected distributions itself. During that
workflow it reruns the planner with --prepare-output-dir to copy only the
selected publishable files into dist-publish/, then publishes from that CI
artifact. Maintainers do not need to prepare dist-publish/ locally before
dispatching the workflow. Local --prepare-output-dir is only useful for a
local publish rehearsal or for inspecting the exact staged artifact set.
Initial PyPI bootstrap
GitHub publishing uses the shared pypi environment. PyPI must have a matching
trusted publisher for each project that will be uploaded.
For the initial bootstrap, PyPI currently allows only one pending trusted publisher per GitHub repo/workflow/environment. Use manual workflow dispatch with one selected package at a time, creating the matching pending publisher before each run:
server(arbiter-server)imap(arbiter-imap)smtp(arbiter-smtp)meta:all(arbiter-suite)client(arbiter-client)skill(arbiter-skill)
Dev releases
Use Publish manual workflow dispatch for dev package releases such as
0.9.0.dev1:
release_line: theMAJOR.MINORline, such as0.9publish_packages: one key or a comma-separated key listpublish_to_pypi: enable only when the matching PyPI trusted publisher is ready
Dev releases do not require release notes and do not create or update GitHub releases.
For a full dev package publish on an already-prepared line, first inspect the publish plan:
.venv/bin/python tools/plan_pypi_publish --packages all
Then dispatch the publish workflow:
gh workflow run publish.yml \
--repo omry/arbiter \
-f release_line=0.9 \
-f publish_packages=all \
-f publish_to_pypi=true
For a dry run, leave PyPI publishing disabled:
gh workflow run publish.yml \
--repo omry/arbiter \
-f release_line=0.9 \
-f publish_packages=all \
-f publish_to_pypi=false
Use tools/bump_release_version before publishing when package versions need
to move. The command updates package version fields and the matching
arbiter-suite exact dependency pins. It accepts the same package keys as the
release build and publish tooling: all, server, plugin keys such as smtp
or imap, meta:all, client, and skill.
tools/bump_release_version --bump patch
tools/bump_release_version --packages smtp --bump patch
tools/bump_release_version --bump dev
tools/bump_release_version --bump minor
tools/bump_release_version 0.9.1.dev1 --check
--bump dev increments only the dev component, such as 0.9.0.dev2 to
0.9.0.dev3. --bump patch advances to the next patch dev release, such as
0.9.0.dev2 to 0.9.1.dev1. --bump minor advances to the next minor dev
line, such as 0.9.0.dev2 to 0.10.0.dev1; minor bumps must select all
packages because they also update plugin SERVER_API_VERSION declarations.
Version bumps keep the server version family together. Selecting any of
server, client, or skill updates the shared server/client/skill version
state and the suite pins for arbiter-server and arbiter-client. Build,
publish, and publish planning still use the package keys literally, so
--packages skill can publish only the skill wheel after the shared version
files have been prepared.
Selective patch and dev bumps are for independent package releases. For a
plugin-only bump, the tool updates that plugin's version and the suite pin, but
does not raise the plugin's arbiter-server lower bound. If the server and a
plugin are selected together, the selected plugin's server lower bound moves to
the new server version. --check verifies an explicit target version and does
not infer a bump.
Final releases
For new release lines where all package versions follow the suite meta
package, run Prepare Release with release_kind=new-release-line and
publish_packages=all, then run Publish with publish_packages=all.
For regular final releases, use release_kind=regular in Prepare Release:
release_line: theMAJOR.MINORline, such as0.9publish_packages:allfor all new publishable targets, or a package key list such assmtp,clientpublish_to_pypi:truein the Publish workflow
Prepare validates or builds package release notes only for final packages that
will publish, adds generated entries for final client and skill artifacts, then
creates draft GitHub Release tags from each published package version, such as
v0.9.1. Publish requires those releases to still be drafts and to point at the
commit being published. Publish also reruns the full platform integration
matrix and full platform unit matrix, and the PyPI upload job depends on those
jobs completing successfully.
Additional meta packages, such as a future meta:mail, should follow the same
non-expanding package-key model.