Maintainer release runbook#

GPN releases are immutable, reviewed scientific artifacts. PyPI publishing is an effect of publishing a GitHub Release; maintainers never upload a wheel or source distribution from a workstation.

Release boundary#

  • Package releases use semantic versions and tags of the form v<version>.

  • Hugging Face model revisions are separate immutable compatibility inputs. A package release does not move or rewrite a model revision.

  • main is the only release branch. The release workflow rejects a tag whose commit is not an ancestor of main or whose name differs from the package version.

  • The pypi environment has no required human reviewer while there is only one active release maintainer. The release event, exact tag, ancestry check, locked build tools, isolated publish job, and PyPI Trusted Publisher are the safeguards.

Before final approval#

  1. Freeze scope and assemble one review PR against main.

  2. Keep Hugging Face asset auditing and card changes outside this release; that work remains tracked separately in issue #81.

  3. Set the final package version and update CHANGELOG.md.

  4. Confirm that the published-model fixture baseline uses the intended immutable revisions. Run the opt-in published-model tests deliberately if a compatibility input changed.

  5. Complete the review packet and external-mutation manifest under release/. Record the exact PR head and tree. A squash merge creates a new commit ID, so approval binds to the reviewed tree; record the resulting main commit and verify its tree immediately after merge.

  6. Obtain explicit approval for the complete code diff and only the pending mutations marked approval_ready. Entries marked deferred are explicitly outside that approval and require a later, separate approval after their blockers are resolved. Do not merge the review PR without explicit maintainer authorization.

Reproduce the release candidate#

Use a fresh clone or worktree checked out at the exact candidate commit so an old dist/ directory cannot contaminate the review. The commands below refuse a dirty checkout and write artifacts to new temporary directories. Record candidate_commit, candidate_tree, and the final hashes in the review packet. Normal tests are network-free.

set -euo pipefail
test -z "$(git status --porcelain)"
candidate_commit="$(git rev-parse HEAD)"
candidate_tree="$(git rev-parse HEAD^{tree})"
artifact_dir="$(mktemp -d)"
rebuilt_wheel_dir="$(mktemp -d)"
uv sync --locked --python 3.13 --extra train --group dev --group docs --group release
uv run pre-commit run --all-files --show-diff-on-failure
HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 uv run pytest
python docs/prepare_notebooks.py
uv run sphinx-build -n -W --keep-going -b html docs docs/_build/release
test -z "$(git status --porcelain)"
uv build --no-build-isolation --out-dir "${artifact_dir}"
uv run --no-sync twine check "${artifact_dir}"/*
uv run --no-sync check-wheel-contents "${artifact_dir}"/*.whl
uv build --no-build-isolation "${artifact_dir}"/*.tar.gz \
  --wheel --out-dir "${rebuilt_wheel_dir}"
uv run --no-sync twine check "${rebuilt_wheel_dir}"/*.whl
uv run --no-sync check-wheel-contents "${rebuilt_wheel_dir}"/*.whl
sha256sum "${artifact_dir}"/* "${rebuilt_wheel_dir}"/*

Install the rebuilt wheel into a clean CPU environment outside the checkout; run gpn --version, gpn --help, import gpn, and call gpn.register_auto_classes(). Record SHA-256 hashes for both distributions in the review packet. The rebuilt wheel hash should match the directly built wheel; if it does not, investigate rather than selecting one artifact for release.

If a change affects training, device placement, precision, or notebook output, also run its documented manual validation in a dedicated Slurm allocation with no more than eight CPUs and one GPU. Never download a whole-genome MSA for release validation.

Merge and publish#

After approval, squash-merge the single reviewed PR. Do not publish while the resulting main tree differs from the approved PR tree. Record the new main commit, verify its tree is the approved tree, and rerun the release-candidate checks on that commit.

  1. Create the annotated historical archive tag/Release only if it is among the approved external mutations.

  2. Create tag v<version> at the exact approved main commit and publish its GitHub Release. Do not run the publishing workflow manually and do not upload distributions yourself.

  3. The release workflow builds once, passes the immutable artifacts between jobs, and publishes with the pypi environment, GitHub OIDC, and PyPI attestations.

  4. Verify the GitHub Actions run, artifact hashes, PyPI metadata and attestations, and a clean pip install gpn==<version> on Python 3.13.

  5. Apply only the separately approved Hub and documentation mutations, then verify their public rendering without moving pinned model revisions.

PyPI files cannot be replaced. If a release is defective, stop downstream mutations, document the incident, yank the affected version when appropriate, and publish a new patch release. Never reuse a tag or version.

Repository rules#

release/main-ruleset.json is the reviewable proposal for main. It requires a PR, the four offline CI contexts, an up-to-date branch, and resolved review threads; each context is bound to the GitHub Actions app rather than accepting a same-named status from another source. It blocks force pushes and deletion. It intentionally requires zero approvals so a solo maintainer is not deadlocked. The absence of bypass actors means the rules also apply to administrators. In an emergency, an administrator may temporarily disable the ruleset in repository settings, document why, restore it immediately, and route the resulting change through a PR.

Research branches remain outside this ruleset and may live indefinitely. Automatic deletion applies only when GitHub recognizes a branch as the merged PR head.