Skip to content

Release pipeline: never draft after a failed PyPI publish, verify the index both ways, keep finals re-runnable - #15

Merged
brianstrauch merged 2 commits into
mainfrom
release/pipeline-hardening-round-2
Sep 10, 2026
Merged

brianstrauch merged 2 commits into
mainfrom
release/pipeline-hardening-round-2

Conversation

@DABH

@DABH DABH commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Second review round on the release pipeline, split out because #13 merged while it was in progress. Same scope (#12), all tooling-only.

Changes

  • No draft after a failed PyPI publish. github-release accepted a skipped smoke-pypi unconditionally, but a skipped smoke-pypi also means publish-pypi failed or a pypi reviewer rejected the deployment: a final whose PyPI upload never happened still got a draft release for a human to publish. The job now requires smoke-pypi to have succeeded whenever PyPI publication was expected.
  • Finals stay re-runnable. The version policy treated the newest published version as a violation, which blocked a fresh dispatch on a tag after its PyPI upload (the only recovery for a failure in smoke-pypi or github-release besides re-running failed jobs). It is now a warning: tags never move and verify-index-files proves the served bytes.
  • verify-index-files both ways. It also fails when the index serves files this run did not build (a foreign platform wheel would win resolution over ours) or serves yanked files; transient registry errors are retried instead of failing on the first 503; the mismatch message explains that a fresh rebuild with a different uv version changes the wheel's Generator stamp and points at "Re-run failed jobs".
  • draft-release refuses to touch a tag that any published release owns, so gh release upload <tag> can never resolve to a published release while the guard looked at a draft.
  • Artifacts: the dist upload sets overwrite: true (a re-run of the test job replaces the artifact instead of leaving two behind) and the junit artifact names carry the dependency lane (ci.yml runs two lanes for one plugin in one run).
  • Tag parsing anchors at end of string and rejects .. plugin names; GITHUB_OUTPUT values refuse line breaks; the dead paginated-JSON helper is gone; the workflow tests assert on step env values instead of serialising a whole job to search for a string.
  • AGENTS.md runbook: the dry run needs only the tag input from a branch (skip-publish is for dispatching on an existing tag ref); recovery describes "Re-run failed jobs" vs a fresh dispatch and the uv-version caveat.

Testing

  • uv run --project scripts --locked pytest scripts/tests -q: 86 passed (new: no draft unless smoke-pypi succeeded when expected; newest-published re-run warning while final gates still apply; foreign files, yanked files and transient errors in verify-index-files; .. and line-break rejection in tag parsing and outputs; overwrite: true and lane-qualified junit names).
  • scripts/ci/check_conventions.py: OK

… index both ways, keep finals re-runnable

A second review round found that github-release accepted a skipped smoke-pypi unconditionally, and a skipped smoke-pypi also means publish-pypi failed or a reviewer rejected the deployment: a final whose PyPI upload never happened would still get a draft release for a human to publish. The job now requires smoke-pypi to have succeeded whenever PyPI publication was expected.

The version policy treated the newest published version as a violation, which blocked a fresh dispatch on a tag after its PyPI upload (the only recovery for a failure in smoke-pypi or github-release besides re-running failed jobs). It is now a warning: tags never move and verify-index-files proves the served bytes, so a re-run is safe. verify-index-files also fails when the index serves files this run did not build or serves yanked files, retries transient registry errors instead of failing on the first 503, and explains that a fresh rebuild with a different uv version changes the wheel's Generator stamp.

draft-release refuses to touch a tag that any published release owns, so gh's tag lookup can no longer resolve to a published release while the guard looked at a draft. Artifacts per lane get overwrite: true and the junit names carry the lane. Tag parsing anchors at end of string, rejects '..' plugin names, and GITHUB_OUTPUT values refuse line breaks. Dead paginated-JSON helper removed; tests cover every new branch and no longer serialize a whole job to search for a string.
@brianstrauch

Copy link
Copy Markdown
Member

Can we update the AGENTS.md to use the full command from the testing section: uv run --project scripts --locked pytest scripts/tests -q?

@brianstrauch
brianstrauch enabled auto-merge (squash) September 10, 2026 20:09
@brianstrauch
brianstrauch merged commit 7fe6e4e into main Sep 10, 2026
18 checks passed
@brianstrauch
brianstrauch deleted the release/pipeline-hardening-round-2 branch September 10, 2026 20:12
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