Skip to content

Coordinated GitHub Release and PyPI publication

Version 0.9.0 uses the release machinery qualified during the 0.9.0rc1 ceremony. The final release is a separate immutable version; the RC remains a prerelease record.

One build, two publication surfaces

.github/workflows/release.yml owns the final ceremony:

merge the fully qualified 0.9.0 PR to protected main
        |
        v
build wheel + sdist once
        |
        +--> twine check
        +--> fresh-wheel install + smoke test
        +--> fresh-sdist install + smoke test
        |
        v
wait for every exact-main required check
        |
        v
create annotated tag v0.9.0
create final GitHub Release
attach wheel + sdist + SHA256SUMS
        |
        v
download those exact GitHub Release assets
verify SHA256SUMS
remove checksum manifest from upload directory
        |
        v
publish the SAME wheel + sdist to production PyPI through OIDC
        |
        v
install eyetrajectoriespy==0.9.0 from production PyPI
run installed-package smoke test

The wheel and sdist are never rebuilt separately for GitHub and PyPI.

Production and recovery triggers

Release automation is manual-dispatch only. Normal pushes and merges to main never publish.

The workflow exposes four explicit targets:

build-only
testpypi
production
resume-production

production is the ordinary publication path. It is intentionally strict:

  1. all version declarations must agree;
  2. the version must not be a development release;
  3. GitHub must report exact protected main;
  4. governance issue #64 must be closed;
  5. all required exact-main qualification checks must pass;
  6. the target GitHub tag/release must not already exist;
  7. the target PyPI version must not already exist.

If any release/tag/version already exists, ordinary production fails. It does not silently retain an existing GitHub Release and it does not use skip-existing on production PyPI.

resume-production is a deliberately separate recovery path. It requires an existing GitHub Release for the same version whose tag resolves to exact current main. It then reuses those immutable GitHub Release artifacts and may use PyPI skip-existing semantics to recover from a partially completed upload or post-upload verification failure.

This separation enforces the ordinary invariant:

one production invocation -> one previously unpublished version

while still providing an explicit audited recovery mechanism.

Dedicated production PyPI authority

Production and recovery jobs use only:

environment: pypi
PYPI_TRUSTED_PUBLISHING_CONFIGURED
PYPI_REQUIRED_REVIEWER_CONFIGURED

The testpypi environment is no longer accepted as production authority. Issue #69 is therefore a mandatory pre-release blocker until the production PyPI Trusted Publisher is registered with:

Owner:       stefanosbalaskas
Repository:  eyetrajectoriespy
Workflow:    release.yml
Environment: pypi

The successful 0.9.0rc1/0.9.0 OIDC publications remain historical evidence that Trusted Publishing works, but they do not count as evidence that the dedicated pypi publisher claim has been migrated.

Optional TestPyPI rehearsal

Manual workflow dispatch remains available for:

  • build-only;
  • testpypi.

TestPyPI is optional for the final 0.9.0 ceremony because the RC already exercised the complete production PyPI path successfully. It remains useful for future publisher/environment migrations.

Final 0.9.0 ceremony

  1. Align package, citation, validation, workflow and documentation versions to 0.9.0.
  2. Archive the qualified RC performance snapshot.
  3. Qualify a fresh 0.9.0 performance envelope.
  4. Pass the complete pull-request matrix.
  5. Merge to protected main.
  6. Pass the complete exact-main matrix.
  7. Create annotated tag v0.9.0 and final GitHub Release.
  8. Attach the exact wheel, sdist and SHA256SUMS.
  9. Download and checksum-verify those exact release assets.
  10. Publish only the wheel and sdist to production PyPI through OIDC.
  11. Install exactly eyetrajectoriespy==0.9.0 from production PyPI.
  12. Run the installed-package canonical smoke test.

The ceremony is complete only after step 12 succeeds.

Post-release development

After a stable release, main moves to a development version such as 0.9.1.dev0, and RELEASE_READINESS.json is disarmed. Ordinary development therefore cannot accidentally republish the previous version or create a new GitHub Release. A future release requires a reviewed version/readiness change plus explicit target=production dispatch.