Skip to content

Releasing geometric-function-atlas

This is the maintained release procedure. The first GitHub software release is separate from PyPI publication and from versioned registry-data snapshots.

Free-tier GitHub policy

The repository is public. CI uses only standard ubuntu-24.04 and windows-2025 GitHub-hosted runners, which GitHub documents as free for public repositories. The workflow does not use larger runners, Actions artifact uploads, or persistent Actions caches. Release files are attached directly to the GitHub release; GitHub documents no total release-size or bandwidth limit, subject to a 2 GiB limit per asset.

Do not add larger runners, paid products, or persistent Actions storage without explicit approval from the repository owner.

Software release

  1. Start from a clean branch/worktree at the current origin/main commit; keep the published previous-version tag/assets, artifact snapshots, frozen PyPI manifest, and installer URLs unchanged.
  2. Update src/geometric_function_atlas/version.py, CITATION.cff, and CHANGELOG.md together. Set date-released and the changelog date in the reviewed release commit before tagging; if publication slips to another day, update both and rerun CI before creating the tag. Run the version-contract test red before the bump and green afterward. Review README.md and docs/PROVENANCE.md for bounded evidence claims.
  3. Regenerate uv.lock when the version changes (uv lock), then run the local gate:
uv sync --extra test --extra build --extra lab --locked
uv run --frozen --extra test --extra lab python -m ruff check src tests scripts
uv run --frozen --extra test --extra lab python -m mypy src --ignore-missing-imports
uv run --frozen --extra test --extra lab python -W error -m pytest -q
rm -rf dist build
uv run --frozen --extra build python -m build
uv run --frozen --extra build python -m twine check dist/*
uv run --frozen python scripts/check_distribution.py dist
# The build directory must contain exactly one current wheel.
wheel=$(uv run --frozen python -c 'from pathlib import Path; wheels = list(Path("dist").glob("geometric_function_atlas-*.whl")); assert len(wheels) == 1, wheels; print(wheels[0])')
uv run --frozen python scripts/check_clean_install.py "$wheel"
uv run --frozen python scripts/check_uv_tool_install.py "$wheel" --python 3.12
  1. Obtain an independent review of the exact diff.
  2. Push a branch, open a pull request, and wait for every Linux and Windows CI job to pass.
  3. Merge to main and verify the merge commit is green.
  4. Build once more from the clean merge commit. Record SHA-256 sums.
  5. Create tag vX.Y.Z and a GitHub release with the wheel, sdist, checksums, and notes from CHANGELOG.md.
  6. Download the published assets and rerun the clean-install check against the downloaded wheel.
  7. Publish the release to PyPI through the frozen-manifest procedure below.
  8. Only after both the GitHub assets and PyPI version are live, open a small follow-up pull request that switches install.sh, install.ps1, their tests, and public installation examples from the previous release to the new one.
  9. Merge that follow-up after CI, run both package-repository public installer commands, and verify gfa --version plus the new first-use command. The website-hosted /install.sh and /install.ps1 endpoints are maintained in the website repository: check their live versions separately and update them there before advertising those URLs as current. This order prevents a broken installer URL or a PyPI command that names an unpublished version.

PyPI publication

PyPI publication is a separate, explicitly triggered step after a GitHub release has passed every gate above. The publish-pypi.yml workflow uses PyPI Trusted Publishing; no PyPI password or API token belongs in this repository.

  1. Update and independently review .github/pypi-publish.json so it pins the release tag, commit, exact asset names, and independently recorded SHA-256 digests.
  2. Confirm that the PyPI trusted publisher matches this public repository, .github/workflows/publish-pypi.yml, and the pypi GitHub environment.
  3. Manually dispatch Publish to PyPI. It accepts no release input; the reviewed manifest is the sole publication target.
  4. The workflow verifies the pinned tag/commit, downloads the exact wheel, sdist, and SHA256SUMS, checks both the manifest-pinned digests and the release checksum file, and runs the metadata and distribution-content gates.
  5. A separate minimal OIDC job downloads and reverifies the pinned distribution digests before uploading only those artifacts through short-lived OpenID Connect credentials.
  6. Verify the new PyPI release through its JSON API and install it by name in a fresh uv-managed Python environment.

The workflow uses a standard public-repository runner and does not upload Actions artifacts or use persistent Actions caches. Release and CI environments are resolved from the committed uv.lock with --locked/--frozen; uv, Python for publishing, and every third-party Action are pinned (Actions by full commit SHA). Runtime dependency ranges remain compatible library metadata, while the lockfile is the exact reproducibility record. Do not enable automatic tag-triggered publishing, mutable Action tags, dependency caches, or long-lived PyPI credentials.

Maintained scripts

The scripts/ directory contains only permanent entry points:

  • install.sh and install.ps1: supported end-user installers;
  • check_distribution.py: wheel/sdist content contract;
  • check_clean_install.py: clean wheel API/CLI test;
  • check_uv_tool_install.py: fresh uv-managed-Python installation test.

Do not commit ad hoc migration, profiling, review, or release-upload scripts.

Registry-data releases

Registry snapshots are not bundled into the wheel. Each snapshot is versioned independently and needs a consistent SQLite file, schema dump, manifest, checksums, population definitions, and successful SQLite integrity checks.