Skip to content

[ci] Allow release corrections before PyPI publication - #108

Merged
nhairs merged 8 commits into
nhairs:mainfrom
nhairs-bot:blue-toad/release-retagging
Oct 31, 2026
Merged

nhairs merged 8 commits into
nhairs:mainfrom
nhairs-bot:blue-toad/release-retagging

Conversation

@nhairs-bot

@nhairs-bot nhairs-bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Allow an unpublished release tag to move to a corrected commit without recovering artifacts from the obsolete attempt. Check target-version eligibility before CI, always build fresh distributions, and replace only draft releases. Public GitHub releases require owner cleanup, and any existing PyPI version blocks normal publishing, including partial uploads.

After pypi approval, recheck the live tag, original bundle, draft assets and PyPI absence through the same Python validation helpers used by finalization. Check out only release.py at the run commit, without persisted credentials; preflight uses isolated Python and only the standard library. Upload PyPI first, then finalize GitHub in a separate job after both public hashes match. Failed GitHub finalization can rerun independently using the original artifact ID. Keep per-tag serialization and owner cancellation of obsolete waiting runs.

Retain generated attestations and publication/finalization outcomes for 90 days, even after failure. Finalization evidence is recorded through release.py rather than inline YAML. Replace the upload-subset artifact with immutable attempt-specific bundles and evidence artifacts. Document manual partial-upload recovery and the limits of saved attestations.

nhairs-bot-ref: T0027

Test Plan

  • 415 local tests passed, including 44 release-policy tests.
  • actionlint passed for release.yml; formatting, pyproject validation, package Pylint and type checks for src/tests/benchmarks plus release.py passed.
  • Built wheel/sdist, verified their metadata and installed the wheel in a clean environment; isolated release smoke check passed.
  • GitHub/PyPI API operations and attestation creation still require owner-triggered live validation after merge. No tags, releases, uploads or repository settings were changed.

Allow an unpublished release tag to move to a corrected commit without recovering artifacts from the obsolete attempt. Check target-version eligibility before CI, always build fresh distributions, and replace only draft releases. Public GitHub releases require owner cleanup, and any existing PyPI version blocks normal publishing, including partial uploads.

After `pypi` approval, recheck the live tag, original bundle, draft assets and PyPI absence. Upload PyPI first, then finalize GitHub in a separate job after both public hashes match. Failed GitHub finalization can rerun independently using the original artifact ID. Keep per-tag serialization and owner cancellation of obsolete waiting runs.

Retain generated attestations and publication/finalization outcomes for 90 days, even after failure. Replace the upload-subset artifact with immutable attempt-specific bundles and evidence artifacts. Document manual partial-upload recovery and the limits of saved attestations.

nhairs-bot-ref: T0027

## Test Plan

- 413 local tests passed, including 42 release-policy tests.
- actionlint passed for release.yml; formatting, pyproject validation, package Pylint and type checks for src/tests/benchmarks plus release.py passed.
- Built wheel/sdist, verified their metadata and installed the wheel in a clean environment; isolated release smoke check passed.
- GitHub/PyPI API operations and attestation creation still require owner-triggered live validation after merge. No tags, releases, uploads or repository settings were changed.
Comment thread .github/workflows/release.yml Resolved
Comment thread .github/workflows/release.yml Resolved
Comment thread .github/workflows/release.yml Resolved
Comment thread .github/workflows/release.yml Resolved
Comment thread scripts/release.py
Comment on lines +100 to +101
from packaging.utils import canonicalize_name # pylint: disable=import-outside-toplevel
from packaging.version import Version # pylint: disable=import-outside-toplevel

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't these imported at top level? (same for other functions)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These local imports keep the publishing preflight standard-library-only. The approved OIDC job checks out release.py but deliberately does not install packaging; it uses only the bundle/live-tag/GitHub/PyPI verification helpers. Version parsing and distribution metadata validation need packaging, so those commands import it when called in jobs that already install it. A top-level import would make every command, including preflight, require packaging. This was the tradeoff made when moving preflight into the shared script. Keeping imports at module scope would also be reasonable if we install packaging in the publishing job, or split the build-only commands into another module.

Comment thread tests/test_release.py Resolved
@nhairs
nhairs merged commit 125a0e9 into nhairs:main Oct 31, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants