Release and promotion

Changes merge to develop, receive a derived development version, publish to TestPyPI, and deploy Preview documentation. Promotion opens a develop → main PR. After exact-head checks and review policy pass, it is rebase-merged without deleting either permanent branch. Main creates the production tag, GitHub Release, PyPI distribution, GHCR package, Production documentation, provenance, and then realigns develop safely.

The Release workflow that performs the TestPyPI/PyPI publish is hand-authored by the adopting repository, not rendered by vibey-gh install: publish steps are project-specific in a way the other managed workflows are not. It must exist under the exact name Release and, on develop and main, derive and apply the version with vibey-gh version --apply, build the package, and publish through the testpypi (develop) and pypi (main) trusted-publishing environments. github-release.yml, release-surfaces.yml, and release-repair.yml all trigger from a workflow_run named exactly Release; without a correctly named and behaving workflow, none of the tag, GitHub Release, GHCR, documentation, repository-profile, or release-repair steps below ever run.

Not every push to the release branch carries a new version — a docs-only or tooling-only promotion, per version.content_paths/code_paths, is expected to publish nothing new. By default, github-release.yml treats a push whose version is already tagged at a different commit as that intentional no-op rather than a failure. Set [github_release] require_new_version = true on a repository whose release branch should carry a new version on every push, so a tag that would otherwise move is reported as the mistake it is. See docs/configuration.md.

Superseded releases

After each publish, vibey-gh report-superseded runs against the index that was just published to and writes a job-summary list of every release the new version supersedes, honouring [yank] keep and linking the project's release-management page. It reports rather than yanks because PyPI exposes no yank API — the upload endpoint answers 405 for a yank action and the web route is CSRF-protected — so the analysis is automated and the click stays human. The configuration lives in [yank]; both indexes default off.