# Vocalinux Release Process This document outlines the step-by-step process for creating a new release of Vocalinux. ## Overview Vocalinux uses [Semantic Versioning](https://semver.org/) with pre-release suffixes: - **Alpha**: Early testing phase (e.g., `0.4.1-alpha`) - **Beta**: Feature-complete, testing phase (e.g., `0.5.0-beta`) - **RC** (Release Candidate): Final testing before stable (e.g., `0.5.0-rc1`) - **Stable**: Production-ready (e.g., `1.0.0`) ## Release Infrastructure (one-time setup) These are configured once per repository. A tag pushed before they exist will fail in the corresponding job. ### PyPI trusted publishing (required) `publish-pypi` authenticates with a short-lived OIDC token instead of a stored API token, so there is no `PYPI_API_TOKEN` secret to leak or rotate. It only works once PyPI knows about this workflow. On https://pypi.org/manage/project/vocalinux/settings/publishing/ add a GitHub publisher: | field | value | |---|---| | Owner | `VocaHQ` | | Repository | `vocalinux` | | Workflow name | `release.yml` | | Environment | `pypi` | Until that publisher exists, `publish-pypi` fails with `invalid-publisher`. The old `PYPI_API_TOKEN` secret can be deleted from the repository once the first trusted publish succeeds. ### AUR (optional) `publish-aur` skips itself when `AUR_SSH_PRIVATE_KEY` is unset. See docs/AUR.md. ## Quick Release Checklist Use this checklist for every release: ```markdown ## Version Bump Checklist for X.Y.Z-PHASE ### Core Version Files - [ ] `src/vocalinux/version.py` - Update `__version__` and `__version_info__` - [ ] `pyproject.toml` - Confirm `Development Status` classifier and `requires-python` are correct for this release phase ### Documentation - [ ] `README.md` - Update short current-release blurb (not full notes) - [ ] `CHANGELOG.md` - Point "Current stable" at the new tag - [ ] `docs/INSTALL.md` - Verify install examples use `main/install.sh` (not version-pinned raw URLs) - [ ] `docs/UPDATE.md` - Add "What's New" section for new version - [ ] `SECURITY.md` - Update supported versions table ### Website - [ ] `web/src/app/page.tsx` - Update `softwareVersion` in schema - [ ] `web/src/app/page.tsx` - Update version badge / live version surfaces if present - [ ] `web/src/app/changelog/page.tsx` - Add new release entry to changelog - [ ] `web/package.json` - Update version field - [ ] `web/package-lock.json` - Keep top-level/version metadata aligned with `web/package.json` - [ ] `web/PRODUCT.md` - Keep capabilities aligned with shipped features - [ ] Confirm install docs/website mention **AppImage** when this release ships AppImages ### Testing & Verification Install `just` from https://just.systems or the distro package `just`. - [ ] Run `just test` - All tests pass - [ ] Run `just lint` - No linting errors - [ ] Run `just typecheck` - No type errors - [ ] Run `just build` - Package builds successfully - [ ] Run `PYTHON=python3.11 just reproducible-wheel` - Independent wheels match - [ ] Run `npm run build` in `web/` - Website builds successfully > Note: CI workflows also sync website version metadata from `src/vocalinux/version.py`, but source files should still be updated in-repo during release prep for review clarity. ### Git Operations - [ ] Create release branch: `git checkout -b release/vX.Y.Z-PHASE` - [ ] Commit all version changes - [ ] Push branch and create PR - [ ] After PR merge, create tag: `git tag -a vX.Y.Z-PHASE -m "Release X.Y.Z-PHASE"` - [ ] Push tag: `git push origin vX.Y.Z-PHASE` ### After the release run finishes - [ ] Run `just verify-release` - the published release verifies as published - [ ] Re-run it after any hand edit to the release: attaching an asset, or rewriting the notes ``` `verify-release.yml` runs the same check when the release workflow finishes. It cannot see a hand edit made afterwards, which is how every v0.16.2 defect arrived, so run it yourself after touching a published release. The one exception is `snap-backfill.yml`: that workflow attaches the snap, rewrites `SHA256SUMS`, and then runs `scripts/verify_release.py` itself. ### Pre-publication wheel reproducibility PR CI and the release workflow run `scripts/build_reproducible_wheel.py` before publication. It checks lock/export drift, installs the hash-pinned `release-build` toolchain into two fresh virtualenvs without system site-packages, and independently builds an sdist and its wheel in each source tree with `--no-isolation`. `SOURCE_DATE_EPOCH` comes from the checked-out commit. Both builds use the same Python 3.11 interpreter and platform; this checks repeatability under controlled inputs, not cross-platform or cross-Python-version reproducibility. Only wheels are compared byte-for-byte. Build-only environments deliberately exclude host packages and setuptools plugins. Unlike runtime/development virtualenvs, they do not need distro PyGObject: the backend reads version metadata without importing GTK. The script runs `twine check --strict` with the locked tools and stages only the canonical wheel and sdist in `dist/` after comparison succeeds. All release consumers reuse those distributions. Snap Store publication also waits for this gate. Verification builds never enter the release checksum manifest. Locally, `PYTHON=python3.11 just reproducible-wheel` includes tracked working-tree edits and non-ignored new files, but excludes ignored build/egg-info/venv state. Repeat runs automatically clean the gate's previous logs and build archives. Staged files in `dist/` are removed only if their names and digests match the retained canonical archives. Unrelated or modified files, unexpected diagnostic files, and symlinks cause cleanup to stop before deleting anything; move those files aside first. The rest of `build/` is never cleaned. Logs, hashes and both builds are retained in `build/reproducible-wheel/`; CI uploads that directory on failure. Refresh the toolchain through `just lock`, not by editing its export by hand. Cleanup removes staged copies before their canonical originals, so an interrupted cleanup can safely be retried. The gate holds a checkout lock from before cleanup through final staging; a second active run fails without changing the first run's outputs. Archives are copied to an ignored temporary sibling of `dist/` and then atomically renamed into place, so interrupted staging leaves only complete archives in `dist/` and can be retried. ### AUR source publication gate After `publish-checksums`, `aur-source-test` downloads the published canonical sdist and `SHA256SUMS`. `scripts/prepare_aur_source.py` checks the digest and cryptographically verifies provenance against this repository's release workflow, tag ref and source commit before preparing the checksum-pinned PKGBUILD. The exact prepared recipe is built, checked with namcap, installed and smoked on current Arch. `publish-aur` downloads only that verified PKGBUILD artifact and pushes it without further version/checksum rewrites (`updpkgsums: false`). This gate runs even without AUR publication credentials. Failure prevents the AUR push, but does not unpublish GitHub/PyPI artifacts already released. It is separate from PR/local `--local-unattested` fixtures, which exercise the same preparation/build path but are never used for publication. See [AUR.md](AUR.md) for the trust model and the existing AUR-provider smoke-test limitations. `vocalinux-bin` publication waits only for release creation and both AppImages; source-gate failures or unrelated checksum producers do not block it. ## Detailed Release Steps ### Step 1: Determine Version Number Follow semantic versioning: ``` Given version MAJOR.MINOR.PATCH-PHASE: MAJOR - Breaking changes (for 1.0.0: public API stable) MINOR - New features, backwards compatible PATCH - Bug fixes, backwards compatible PHASE - alpha, beta, rc1, rc2, etc. (omit for stable releases) ``` **Examples:** - Alpha → Beta: `0.4.1-alpha` → `0.5.0-beta` (new features, phase change) - Beta → RC: `0.5.0-beta` → `0.5.0-rc1` (same features, testing phase) - RC → Stable: `0.5.0-rc1` → `1.0.0` (first stable release) - Stable patch: `1.0.0` → `1.0.1` (bug fixes only) - Stable minor: `1.0.0` → `1.1.0` (new features) ### Step 2: Update Core Version Files #### 2.1 Update `src/vocalinux/version.py` ```python __version__ = "0.5.0-beta" __version_info__ = (0, 5, 0, "beta") ``` #### 2.2 Update `pyproject.toml` Change the Development Status classifier: ```toml # For Alpha: "Development Status :: 3 - Alpha", # For Beta: "Development Status :: 4 - Beta", # For Stable: "Development Status :: 5 - Production/Stable", ``` ### Patch releases within a minor line (e.g. 0.14.x) When shipping bug fixes only (PATCH bump, same MINOR): - **README / `docs/UPDATE.md` "What's New"** keeps the **minor-series feature list** (everything introduced in that 0.Y line) and adds a short **Bug fixes in this patch** subsection for the delta only. Do not make the marketing highlight table look featureless because this tag only contains fixes. - **Website changelog** (`web/src/app/changelog/page.tsx`) may list only the delta for the new entry; older entries stay historical. - **AppStream** (`packaging/flatpak/com.vocalinux.Vocalinux.metainfo.xml`): add a short `` note for the patch delta only; leave the long product feature `