Releasing

A PyPI upload is permanent. name plus version can never be uploaded twice, so this is about getting it right once rather than recovering.

Checklist

# 1. The tree is what you think it is.
git status --short                       # must be empty
./scripts/test.sh

# 2. Delete dist/ and rebuild. Do not reuse it.
rm -rf dist && uv build

# 3. Verify the artefact, not the source tree.
python scripts/verify_wheel.py dist/*.whl
twine check dist/*

# 4. Install the wheel into a clean venv and run the five-command walkthrough
#    from it. Without the execute extra first: `plan` and `build` must work
#    with no pyarrow, no fsspec and no simulator.

# 5. Re-verify the claims against whatever [vla-eval] resolves to now.
python scripts/verify_harness_claims.py

# 6. Rehearse. TestPyPI is free and catches README rendering.
twine upload --repository testpypi dist/*

# 7. Upload.
twine upload dist/*

Why step 2 deletes rather than rebuilds

uv build adds to dist/ and does not clear it. A stale wheel from an earlier version stays, twine upload dist/* uploads both, and the older one wins on any resolver that sees it first. Nothing warns.

Why step 3 checks the artefact

scripts/verify_wheel.py compares the wheel's contents against the source tree, deriving the expected package list rather than hardcoding it.

$ python scripts/verify_wheel.py dist/refractal-0.1.0a1-py3-none-any.whl
  refractal-0.1.0a1-py3-none-any.whl
  source has 7: ['build', 'compare', 'execute', 'perturbations', 'render', 'resolve', 'schema']
  wheel has  7: ['build', 'compare', 'execute', 'perturbations', 'render', 'resolve', 'schema']

  Wheel matches the source tree (7 subpackages).

Exit 1 names what is missing or extra. Missing usually means an unanchored .gitignore pattern: build/ matches src/refractal/build/ as well as ./build/, and the result is a wheel short an entire subpackage while git status stays clean. Extra means a stale dist/.

It does not check that the source tree is what you think it is, which is why git status is step 1.

Why step 5 re-runs the claims

A published version pins a dependency range. The claims are the evidence that Refractal's assumptions hold for the harness a user will actually get, which is not necessarily the one you developed against.

Version numbers

0.1.0a1 keeps the alpha marker. Identity-bearing fields are still moving, and a version whose hashes change orphans every result computed under it.