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.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:
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: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 -- 1.1.0git 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.mdmarks every major FITS feature as supported, partial, unsupported, or out of scope. - [ ]
benchmarks/replays/upstream_sources.jsonreferences 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-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.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:
- 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, 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. - Uploads wheels to PyPI via
OIDC trusted publishing (
id-token: writeon thepypiEnvironment). There is nopassword:/PYPI_API_TOKENin 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.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.