Skip to content

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.13.x 1.1.0 (primary PyPI release lane; CUDA flavors cu126/cu129/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:

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.toml
  • packaging/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:

python scripts/release_lane.py --lane <X.Y> --prerelease rc<N> --apply

e.g. --prerelease rc5 on lane 2.13 renders 1.0.0rc5 everywhere, 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.

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:
    pixi run changelog-update          # merge commits since the newest tag (deduped)
    pixi run changelog-check           # CI-style drift check
    
    Generated bullets are a starting point — curate the prose afterwards.
  • 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):
    pixi run changelog-release -- 1.1.0
    
    Run it before 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_fitsio_upstream_smoke.py tests/test_astropy_upstream_smoke.py -q
pixi run pytest tests/test_package_isolation.py tests/test_docs_integrity.py -q

6. Benchmark evidence

Published multi-host benchmark runs (MPS + CANFAR CPU + CANFAR CUDA):

pixi run bench-install
bash scripts/selfcheck_canfar_launcher.sh
# Launch CANFAR first (async), then local:
pixi run bench-exhaustive-canfar-cpu
pixi run bench-exhaustive-canfar-cuda
pixi run bench-exhaustive-local
# After CANFAR finishes:
bash scripts/fetch_canfar_bench_vos.sh exhaustive_cpu_<stamp>
bash scripts/fetch_canfar_bench_vos.sh exhaustive_cuda_<stamp>
# Local leg: bench-exhaustive-local prints its run-id (exhaustive_<cpu|mps>_<stamp>).
pixi run bench-release-scorecard -- \
  benchmarks_results/exhaustive_<cpu-or-mps>_<localstamp> \
  benchmarks_results/exhaustive_cpu_<stamp> \
  benchmarks_results/exhaustive_cuda_<stamp>

Mirror CSVs into docs/assets/bench/<run-id>/ and update Published paths in docs/benchmarks.md. Companion suites: pixi run bench-megacam, bench-ml.

Quick local check (not a published run): pixi run bench-all / pixi run bench-mps. Manual CI refresh: .github/workflows/bench-report.yml (workflow_dispatch only; CPU-only).

Repository: https://github.com/astroai/torchfits.

PyPI publishing: astroai/torchfits is registered; tag pushes trigger .github/workflows/build_wheels.yml (publishes via OIDC trusted publishing on the pypi GitHub Environment; no API token).

Do not make new performance claims unless the benchmark run is archived and the comparison target is listed in docs/parity.md.

7. Parity and docs contract

  • [ ] docs/parity.md marks every major FITS feature as supported, partial, unsupported, or out of scope.
  • [ ] benchmarks/replays/upstream_sources.json references the parity tests that justify comparator claims.
  • [ ] README and docs do not claim torchfits ownership of WCS, sphere geometry, HEALPix, or sky-domain simulation.
  • [ ] 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-pins enforces 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.10 3.11 3.12 3.13 3.14" 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

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:

  1. Install — pip install torchfits==X.Y.Z, Python / PyTorch versions, docs URL.
  2. Highlights — what a user can do now, with short copy-paste examples.
  3. Breaking changes — before/after table when needed.
  4. 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:

  1. Runs tests (each job resolves the lane's torch pin via release_lane.py --print-pins).
  2. Builds wheels (Linux x86_64 + aarch64, macOS arm64, cp310–cp314, torch pinned to the lane via scripts/cibw_before_build.sh). An sdist is attached to the GitHub Release only — not uploaded to PyPI.
  3. Uploads wheels to PyPI via OIDC trusted publishing (id-token: write on the pypi Environment). There is no password: / PYPI_API_TOKEN in the workflow.

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

  • [ ] pip install torchfits==X.Y.Z works 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 when main runs docs.yml after the release push).
  • [ ] Edge docs load (tip of main). Docs deploy only from main (not from the tag event) so Pages protection and concurrency do not cancel the post-release publish.
  • [ ] Changelog and release notes links resolve.