Release Checklist¶
Maintainer runbook for cutting a release.
0. Release lane¶
Each torchfits release targets one PyTorch minor (the wheel "lane"),
because PyTorch has no stable C++ ABI across minors. Lanes live in
scripts/torch_lanes.json (single source of truth):
| PyTorch lane | torchfits release |
|---|---|
| 2.14.x | 1.3.2 (lane base. CUDA flavors cu126/cu130) |
scripts/torch_lanes.json currently defines a single lane; auxiliary
+torchNNN local-version wheels are created by adding lanes to that file and
re-running scripts/release_lane.py --apply.
1. Version sync¶
Apply the lane everywhere in one step:
pixi run python scripts/release_lane.py --lane <X.Y> --apply
pixi run check-lane
--apply rewrites the version + torch pin in:
pyproject.toml(version,[cpu]/[cuda]extra pins)constraints-wheel.txt(wheel ABI lane)pixi.tomlpackaging/conda/recipe.yaml(torch_pin)src/torchfits/__init__.py(__version__)
Release candidates (pre-tag validation runs) render the lane base plus a PEP 440 prerelease suffix:
pixi run python scripts/release_lane.py --lane <X.Y> --prerelease rc<N> --apply
e.g. --prerelease rc5 on lane 2.14 appends rc5 to that lane's torchfits_version, and
--check / check-lane accept the rc state as the lane's base version.
A plain --apply (no --prerelease) always finalizes back to the map
version.
Release-candidate gate. From 1.3.1 onward, every stable tag is preceded by
an rc tag for the same version. The rc and the stable tag carry the same
tree apart from the version strings. Publish vX.Y.Zrc1, run the gates and
install checks against that artifact, then finalize and tag the stable
version. scripts/check_release_rc.py enforces the gate before PyPI upload,
and section 9 runs it locally. 1.3.0 (2026-10-22) was published without an
rc.
check-lane fails unless all five agree with scripts/torch_lanes.json.
Update the compatibility / install docs (README.md, docs/install.md,
docs/compatibility.md) when the current-lane numbers change.
2. Changelog¶
docs/changelog.md follows Keep a Changelog.
Work in flight lives under a versionless ## Unreleased heading on top —
edge docs must never name an unreleased version.
- While developing, fold conventional commits into
## Unreleased:Generated bullets are a starting point — curate the prose afterwards.pixi run changelog-update # merge commits since the newest tag (deduped) pixi run changelog-check # CI-style drift check - When cutting a release, stamp the real version and date (this also refreshes
the
[Unreleased]/ compare link refs at the bottom of the file and opens a fresh empty## Unreleased):Run it beforepixi run changelog-release -- <version>git tag, so the tag points at the finalized changelog.
3. Tests and gates¶
pixi run preflight-push
pixi run test
pixi run ci-local
pixi run release-gate
All must pass. preflight-push includes check-lane (all five lane files
agree). ci-local and the CI lint job also run check-torch-pins, which
resolves the [cpu] / [cuda] extra pins against the PyTorch indexes on the
wheel ABI lane.
4. Public-API freeze (SemVer 1.0 / breaking cuts)¶
Before tagging a SemVer 1.0.0 (or any intentional breaking cut), run the
release API freeze review under
.cursor/skills/release-api-freeze-review/ and fix drift between
docs/api*.md and the exported surface.
5. Correctness gates¶
Covered by release-gate; re-run narrowly if needed:
pixi run pytest tests/test_package_isolation.py tests/test_docs_integrity.py -q
Upstream parity smokes run in torchfits-harness.
6. Benchmark evidence¶
Benchmark runs, published CSVs, and the parity matrix live in torchfits-harness.
Repository: https://github.com/astroai/torchfits.
Publishing: astroai/torchfits is registered on PyPI. A release-candidate
tag push builds and validates the complete artifact matrix and creates a GitHub
prerelease, but never uploads to PyPI. A stable tag push only builds and
validates. After that workflow succeeds, run it manually from the exact stable
tag with publish=true; the guarded jobs publish through OIDC on the pypi
GitHub Environment and create the stable GitHub release. Fork runs cannot
publish.
Do not make new performance claims unless the benchmark run is archived in torchfits-harness.
7. Parity and docs contract¶
- [x] Parity and migration notes live in torchfits-harness. This repo's evaluation page points there.
- [x] Core tests and the default environment do not import astropy or fitsio.
- [x] README and docs do not claim torchfits ownership of WCS, sphere geometry, HEALPix, or sky-domain simulation.
- [x] Install docs still document CPU-only (no CUDA libs) and GPU torch index
recipes, and the
[cpu]/[cuda]extra pins track the current wheel lane (pixi run check-torch-pinsenforces this).
8. Local artifact check (optional)¶
Wheel matrix (lanes × Python, built in tar copies so the working tree stays clean):
bash scripts/build_wheels_local.sh --lanes <list> --pythons "3.11 3.12 3.13 3.14 3.15" dist-local
bash scripts/verify_wheel_matrix.sh --jobs 4 dist-local
Conda package (bare-cmake build via pixi; verify in a fresh env):
pixi run build
# verify: import + torchfits --help in a fresh pixi env
CANFAR CUDA tier (optional, soft-fail): see
scripts/verify_wheel_cuda_canfar.sh (TORCHFITS_WHEEL_URL for unpublished
wheels). Or a plain smoke of the published artifact:
bash scripts/clean_install_smoke.sh
# or manually:
pip wheel . --no-deps --no-build-isolation -w dist
twine check dist/*
9. Tag and push¶
pixi run python scripts/check_release_rc.py --version X.Y.Z
git add -A
git commit -m "release: vX.Y.Z"
git tag vX.Y.Z
git push origin main --tags
10. Publish¶
Create a GitHub release for vX.Y.Z with user-facing notes (not an
internal checklist). Prefer writing the body yourself over
generate_release_notes alone.
Suggested shape:
- Install —
pip install torchfits==X.Y.Z, Python / PyTorch versions, docs URL. - Highlights — what a user can do now, with short copy-paste examples.
- Breaking changes — before/after table when needed.
- Links — changelog, compare URL, PR.
Do not lead with review filenames, logo changes, or bench run IDs unless they are the product. Put evidence in the changelog / docs site.
Publishing triggers .github/workflows/build_wheels.yml, which:
- Runs tests (each job resolves the lane's torch pin via
release_lane.py --print-pins). - Builds wheels (Linux x86_64 + aarch64 on cp311–cp315; macOS arm64 on
cp311–cp314, because torch 2.14's cp315 macOS wheel is ABI-broken —
pytorch#200779; torch
pinned to the lane via
scripts/cibw_before_build.sh). An sdist is attached to the GitHub Release only — not uploaded to PyPI. - Uploads wheels to PyPI via
OIDC trusted publishing (
id-token: writeon thepypiEnvironment). There is nopassword:/PYPI_API_TOKENin the workflow.
Comprehensive CI builds one wheel per PR (job Wheel smoke) and asserts the
packaging contract before any tag exists: the native extension and
torchfits/py.typed are present, the generated torchfits/_C.pyi stub is
shipped and not truncated, both licence files (project + vendored CFITSIO)
are under dist-info/licenses/, the metadata version matches
pyproject.toml, and no C++ source or test tree leaked in. It then installs
that wheel into a clean venv and runs the same release smoke tests
cibuildwheel runs at tag time.
pixi run wheel-smoke # same build + contents check, locally
Local / out-of-band builds (same [tool.cibuildwheel] config):
bash scripts/cibuildwheel.sh # host arch (Linux Docker or macOS)
CIBW_ARCHS=aarch64 bash scripts/cibuildwheel.sh
bash scripts/verify_wheel_cuda_canfar.sh # CUDA torch vs the CPU-linked wheel
11. Post-release verification¶
These checks intentionally remain unchecked in a development checkout. Run them only after the tagged release and its package/docs deployments exist.
- [ ]
pip install torchfits==X.Y.Zworks in a fresh environment. - [ ]
import torchfits; print(torchfits.__version__)shows correct version. - [ ]
torchfits.read(...)runs without import errors. - [ ] Stable docs load (latest
v*tag, built whenmainrunsdocs.ymlafter the release push). - [ ] Edge docs load (tip of
main). Docs deploy only frommain(not from the tag event) so Pages protection and concurrency do not cancel the post-release publish. - [ ] Changelog and release notes links resolve.