How the five packages get to PyPI; the everyday conventions are in AGENTS.md. publish_release.yml fires on push to release, so merging is what publishes, and the pipeline extracts the changelog section matching each tag as the GitHub release notes.
rel-<date>-NN→release— the PR targetsrelease, nevermain. Merging it starts the publish run.- The workflow tags and publishes on the
releaseref. Never hand-tag, and never tag onmain. release→mainwith a plain merge commit (gh pr merge <n> --merge), preserving the released commit inmain's history. The publish jobs appear asrelease-packages (<pkg>)checks on this PR; don't merge before they are green — a failed publish is easier to redo beforemainabsorbs the release commit.
The push trigger takes no inputs: with no force_packages or environment, check-package-versions compares each version against PyPI to decide what to build, and publishes to production. priority-order: bfabric,bfabric_scripts,bfabric_app_runner plus max-parallel: 1 releases sequentially, so bfabric lands before the packages that floor on it.
Install it the way a user gets it, then import whatever the release touched:
uv venv /tmp/rel -p 3.11
uv pip install --python /tmp/rel --no-sources ./bfabric # --no-sources: resolve deps from PyPI, not the workspace
uv pip list --python /tmp/rel | grep -i bfabric # check the resolved dependency floor
/tmp/rel/bin/python -c "import bfabric"Import the real [project.scripts] modules (bfabric_scripts.cli.__main__, bfabric_app_runner.cli.__main__); a guessed path raises ImportError and reads like a broken release. Don't use --help — @use_client connects before the command body runs, so it fails for config reasons unrelated to the release. A dependent package can't be verified until the core publishes, since its new floor (bfabric>=1.20.0) excludes the current rc under PEP 440 and the run doesn't pause between packages: verify bfabric before opening the PR, the dependents right after (PyPI's index lags about a minute).
- Never dispatch
environment: testas a dry run. It only switches the PyPI URL; tag creation and the GitHub release are ungated and run first, so the real tag is pushed and theproductionre-run then dies atgit tag -a. Verify locally and dispatch straight toproduction. - Pass
force_packagesexplicitly — a non-empty value skips the PyPI comparison, so only the named packages build. Confirm viaForcing release of <pkg>in thecheck-packageslog. - Leave "Set as the latest release" unchecked, except for
bfabricstable — GitHub keeps one repo-wide badge and it belongs to the core library.rcNand0.xtags are auto-flagged as prereleases.
- Keep one cumulative entry per RC line (e.g.
## [1.20.0rc2]) covering the full changeset for the upcomingX.Y.0, re-dated and extended for each RC rather than stacked below the last one. The published tags keep the per-RC history. - RC entries use flat, abbreviated, headline-first bullets — the one exception to the Keep a Changelog subsections — with dev-facing changes in a trailing
Internal:bullet. - Graduation renames
[X.Y.0rcN]to[X.Y.0]with the date; no content merge, since the entry was cumulative. - A floor on an unreleased
bfabricmust name the rc (bfabric>=1.20.0rc2,<1.21); a plain>=1.20.0excludes it under PEP 440.
- Find what is actually released on that line — check the
<pkg>/*tags and PyPI. Your base is the latest released patch and your version is that + 1; guessing collides with an existing tag or silently drops a shipped patch. - Land the fix on
mainfirst, via a normal reviewed PR under[Unreleased], so one commit is the source of truth. - Cut from the version tag and cherry-pick once that PR merges, so the recorded SHA is permanent:
git checkout -b hotfix/<pkg>-X.Y.Z <pkg>/X.Y.(Z-1), thengit cherry-pick -x <main-commit>. - Add the version bump and the
## [X.Y.Z]section on the hotfix branch — that is what the pipeline extracts into the tag and GitHub Release. - Publish out-of-band:
gh workflow run publish_release.yml --ref hotfix/<pkg>-X.Y.Z -f environment=production -f force_packages=<pkg>. Merging intoreleasewould mislabel a tree that is ahead on a newer line. - Forward-port under
[Unreleased]only — never backfill a## [X.Y.Z]section intomain, which can't hold a past-line patch without breaking version or date order. A fix that can't apply tomainleaves its changelog alone, since an[Unreleased]bullet would be promoted into the next release's notes. - Never open a PR from the hotfix branch — its diff against
mainreads as a mass revert and CI would run the old line's whole suite. The tag and GitHub Release are the record; the only PR is the forward-port.