Files
voculinux/docs/RELEASE_PROCESS.md
T
xavierk d3a3f54204 docs(release): describe the Gitea xbps release and the template bump pull request
Cover the one-time setup (the XBPS_SIGNING_KEY secret, the committed public
key and its fingerprint, the runner and token needs), the tag-push gates and
what the workflow publishes to the tag release and to latest, and the
checklist items for verifying the run. The template version and checksum
bump stays a normal pull request, never a bot commit to main, and now says
the checksum is the sha256 of the fork's own release asset.

The cherry-pick policy from ticket #30 was already in this file and is
enforced by tests/test_upstream_intake_policy.py. Correct what it names as
the retained internal module: it is vocalinux (src/vocalinux/), not
voculinux.
2026-11-06 18:28:11 +05:30

664 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Voculinux Release Process
This document outlines the step-by-step process for creating a new release of Voculinux.
## Overview
Voculinux 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/voculinux/settings/publishing/
add a GitHub publisher:
| field | value |
|---|---|
| Owner | `VocaHQ` |
| Repository | `voculinux` |
| 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.
### Void xbps repository (required for a Void release)
The signed Void repository is released by `.gitea/workflows/xbps-release.yml`
on the fork's Gitea host, not by `release.yml`. Set it up once:
| what | value |
|---|---|
| Secret | `XBPS_SIGNING_KEY`: the PEM-encoded RSA 4096 repository private key (Gitea repository settings, Actions, Secrets) |
| Public key | `packaging/void/repository-key.plist`, committed, fingerprint `fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25` |
| Runner | `bongbetic-ci` with podman (the workflow installs it with apt when absent). Never Docker |
| Token | the job token must be allowed to create releases and upload release assets |
Without the secret the workflow fails and publishes nothing; it never signs
with a different key either, because the fingerprint of the key that signed
must equal the one above. The private key is never committed; only the public
`.plist` is. Key generation and the check that a private key matches the
committed public key are in `packaging/void/README.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
- [ ] `packaging/void/srcpkgs/` - After the tag, bump template `version`/`checksum` to the new release as a pull request (never direct to `main`)
### 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` (this also starts the Gitea xbps workflow, see Step 8)
### 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.
`voculinux-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.voculinux.Voculinux.metainfo.xml`): add a short `<release>` note for the patch delta only; leave the long product feature `<ul>` as series/product-level.
- **SECURITY.md** supported versions table usually stays on the minor line (`0.14.x`) without a row per patch.
- **AUR source PKGBUILD**: the release gate prepares `pkgver`, `_tag`, the normalized `_version`, and the canonical sdist digest, then tests that exact file. Do not introduce `SKIP` or rehash it during publication. The separate `voculinux-bin` path still pins both AppImage digests before its own publication.
- **Dates**: patch release dates must not predate the previous release in changelog / AppStream / version history.
### GitHub Release Notes Writing Rules
Use these rules for every GitHub Release body (and for the draft pasted into the release-prep PR). Website changelog entries stay shorter; see below.
#### Sources of truth
- Delta commits: `git log vPREV..HEAD` plus merged PR titles/bodies.
- Closed issues via PR `Fixes` / `Closes` references only - do not invent issue numbers.
- Do not invent benchmarks, user counts, testimonials, or features not in the tree.
#### Required structure
1. `# Voculinux vX.Y.Z` title
2. One to three plain sentences: what this release is for (no hype)
3. `## Highlights` - markdown table, about 4-8 rows
4. `## New Features` - bullets with PR + author; include issue closes when real
5. `## Bug Fixes` - group by area (IBus, Installer, AUR, Text injection, …)
6. Optional: `## Improvements`, `## Docs`, `## Packaging`
7. `## Thanks` - external PR authors and issue reporters by `@handle`
8. `## Install / Upgrade` - `install.sh`, AUR, PyPI, **AppImage**, Flatpak, Snap,
`.deb` / `.rpm`
(Store channels plus GitHub `.snap` sideload when Store review is pending)
9. `### Verifying what you downloaded` (required, and easy to lose). `release.yml`
generates it, with `sha256sum -c --ignore-missing SHA256SUMS` and
`gh attestation verify`. A hand-written body replaces the generated one, so carry
this block over deliberately. v0.16.2 published a checksum manifest and build
provenance and mentioned neither, so nothing a user reads told them either existed
10. Footer compare link: `https://github.com/VocaHQ/vocalinux/compare/vPREV...vX.Y.Z`
#### Include / exclude
- **Include:** user-visible features, install/packaging changes, desktop reliability fixes, docs that change user instructions.
- **Exclude or demote:** Dependabot-only bumps, CI matrix tweaks, agent-env docs, pure refactors - short “CI / maintenance” subsection at most.
#### Attribution and voice
- Feature/fix bullets: `(#N by @author; fixes #M reported by @reporter)` when known.
- Practical, specific, Linux-native. Name the symptom users hit.
- Avoid promotional filler (“seamless”, “pivotal”, “game-changer”, fake significance).
#### Website changelog vs GitHub Release
- **Website** (`web/src/app/changelog/page.tsx`): 3-10 concise user-facing bullets for the new entry.
- **GitHub Release**: fuller narrative + install block + thanks. Draft in the release-prep **PR body**; paste/edit onto the release after the tag workflow runs (workflow install stub + generated notes are a starting point only).
#### Minor vs patch (reminder)
- **Minor** (e.g. `0.15.0`): rewrite README / UPDATE “What’s New” for the new series; full website changelog entry; AppStream release note summarizes the series.
- **Patch** (e.g. `0.15.1`): keep series feature table; add “Bug fixes in this patch” only; website changelog lists delta only.
#### Draft delivery
- Release-prep PRs should include a `## Draft GitHub Release Notes` section in the PR description for review before tag.
- Do not tag from the prep branch alone; tag after merge to `main`.
### Step 3: Update Documentation
#### 3.1 Update `README.md`
**README current-release blurb:**
Keep a short one-line pointer to the new tag and [docs/UPDATE.md](UPDATE.md). Do not paste the full release notes into the README.
**Install commands:**
Keep install examples on `main/install.sh` (download-then-run preferred; installer resolves latest release tag):
```bash
curl -fsSL https://raw.githubusercontent.com/VocaHQ/vocalinux/main/install.sh -o /tmp/vl.sh
bash /tmp/vl.sh
```
#### 3.2 Update `docs/INSTALL.md`
Confirm install examples point to `main/install.sh` and not release-tag raw URLs.
#### 3.3 Update `docs/UPDATE.md`
Add a new "What's New" section at the top:
```markdown
## What's New in v0.5.0-beta
- Feature 1
- Feature 2
- Bug fix 1
See the [full changelog](https://github.com/VocaHQ/vocalinux/releases/tag/v0.5.0-beta).
---
## Previous Versions
### v0.4.1-alpha
...
```
#### 3.4 Update `SECURITY.md`
Update the supported versions table:
```markdown
| Version | Supported |
| ------- | ------------------ |
| 0.5.x | :white_check_mark: |
| 0.4.x | :x: |
| < 0.4 | :x: |
```
#### 3.5 Update Void templates (pull request)
After the tag is cut, the Void templates in `packaging/void/srcpkgs/` must
point at the new release: bump `version` in the `voculinux` template (and
any template whose upstream moved) and refresh every pinned `checksum`
(`sha256` sums of the release sdist and distfiles). The `voculinux` template
builds the fork's release asset `voculinux-X.Y.Z.tar.gz`; its `checksum` is
the sha256 of that exact downloaded asset, never a value copied from
upstream, and stays `PENDING_FORK_RELEASE_SHA256` (which fails closed in
`xbps-src`) until the asset exists. Land the bump as a **pull request**
through the normal test, lint, and type gates, never as a direct or bot
commit to `main`, and never hand-edited onto the release itself (see
`just verify-release`). `tests/test_void_app_template.py` fails if the
template version or checksum drifts from `src/vocalinux/version.py` and the
published sdist.
Nothing automates this: the xbps workflow never commits or pushes. The
signed repository a tag publishes is built from the templates as committed
at that tag, so a bump merged after tag `vX.Y.Z` first ships with the next
tag push.
### Step 4: Update Website
#### 4.1 Update `web/src/app/page.tsx`
Update the schema.org softwareVersion (around line 68):
```typescript
"softwareVersion": "vX.Y.Z-PHASE",
```
Update the version badge in the header (around line 293):
```tsx
vX.Y.Z Beta
```
#### 4.2 Update `web/src/app/changelog/page.tsx`
Add new release entry to the `releases` array at the top of the file.
#### 4.3 Update `web/package.json`
```json
{
"name": "voculinux-website",
"version": "0.5.0"
}
```
Also align `web/package-lock.json` top-level `version` fields with `web/package.json`.
### Step 5: Verify Everything Works
Run the full verification suite. Install `just` from
https://just.systems or the distro package `just`.
```bash
# Run tests
just test
# Run linting
just lint
# Run type checking
just typecheck
# Build package
just build
# Website build
cd web && npm run build && cd ..
# Install script sanity checks (optional but recommended)
bash -n install.sh
./install.sh --help | grep -- --tag
```
### Upstream Fix Intake (Cherry-Pick Policy)
Upstream Vocalinux fixes enter this fork only as hand cherry-picks: taken at
defined sync points, never as continuous tracking and never as wholesale
merges. Like every other change, each port goes through the pull request flow
with the standard test, lint, and type gates. Never land one as a direct or
bot commit to `main`, and follow the worktree rules in `AGENTS.md` (one
worktree per branch, every change via a pull request, never commit on `main`
— only the pull request base changes).
- **When intake happens.** At each release-preparation cycle, while working
through the steps above on the release-prep branch, plus on demand for
critical fixes: crashes, data loss, and security issues. Critical fixes
follow the Emergency Hotfix Process below.
- **When intake freezes.** Intake freezes once the release branch opens
(Step 6). After the freeze, only justified release-blockers are admitted;
everything else waits for the next cycle.
- **One upstream fix per pull request.** Each port is its own pull request,
referencing both the upstream fix and the fork issue/PR numbers.
- **How conflicts resolve.** Keep the upstream logic and re-apply the fork's
renames on top. Conflicts concentrate at the renamed user-visible surfaces
(application name, entry points, desktop and D-Bus identity, configuration
and data directories, package, website) precisely because the internal
Python module name stays `vocalinux` (`src/vocalinux/`) as a retained
internal name, so only the user-visible surfaces diverge from upstream, and
that split is what keeps hand cherry-picks nearly conflict-free everywhere
else.
Upstream URLs and changelog history stay untouched for provenance.
- **What qualifies.** Fixes and documentation that changes user instructions
port. Features are re-decided per release, not ported automatically.
Dependency and CI churn ports only when a security advisory names it.
Out-of-scope material is dropped at triage.
### Step 6: Create Release Branch and PR
```bash
# Create branch
git checkout -b release/v0.5.0-beta
# Stage all changes
git add -A
# Commit with conventional commit format
git commit -m "chore(release): prepare v0.5.0-beta
- Update version from 0.4.1-alpha to 0.5.0-beta
- Update status badges from Alpha to Beta
- Update documentation version references
- Update website schema and badges
- Update pyproject.toml classifiers
- Update SECURITY.md supported versions table"
# Push branch
git push origin release/v0.5.0-beta
# Create PR (use GitHub CLI or web interface)
gh pr create --title "chore(release): prepare v0.5.0-beta" \
--body "This PR prepares the repository for the v0.5.0-beta release.
## Changes
- Bumped version to 0.5.0-beta
- Updated status badges from Alpha to Beta
- Updated all documentation version references
- Updated website version info
- Updated SECURITY.md supported versions
## Checklist
- [x] All tests passing
- [x] Linting clean
- [x] Type checking clean
- [x] Package builds successfully"
```
### Step 7: Create and Push Tag (After PR Merge)
Once the PR is merged to main:
```bash
# Checkout main
git checkout main
git pull origin main
# Create annotated tag
git tag -a v0.5.0-beta -m "Voculinux Beta Release 0.5.0
Key changes since 0.4.1-alpha:
- Feature 1
- Feature 2
- Bug fix 1
Full changelog: https://github.com/VocaHQ/vocalinux/releases"
# Push tag
git push origin v0.5.0-beta
```
### Step 8: Monitor Release
After pushing the tag, the GitHub Actions workflow will automatically:
1. Build the Python package (wheel and sdist) - **once**, with `SOURCE_DATE_EPOCH`
pinned to the tagged commit. Every later job downloads that artifact instead of
rebuilding, so the wheel on PyPI is byte-for-byte the wheel on the release
2. Build and attach AppImages for x86_64 and aarch64, both from that same wheel
3. Create a GitHub Release with auto-generated notes
4. Attach `SHA256SUMS` covering every GitHub asset (wheel, sdist, both AppImages,
both Flatpaks, the amd64 snap, the `.deb` and `.rpm` for both arches) and
generate build provenance from that
manifest (runs after the aarch64 AppImage, both Flatpaks, and the snap land,
so a partial manifest never gets published)
5. Publish to PyPI via trusted publishing
6. Publish the AUR package (when the `AUR_SSH_PRIVATE_KEY` secret is configured)
7. Publish the signed self-hosted Flatpak remote to `VocaHQ/vocalinux-flatpak`
(stable tags only, once the GitHub Release exists, when
`FLATPAK_GPG_PRIVATE_KEY` + `FLATPAK_REPO_TOKEN` are configured — see
docs/FLATPAK_REMOTE.md)
8. Deploy the website to voculinux.com
9. Build the amd64 snap from locked Python exports and run the installed-artifact
gate described in [SNAP.md](SNAP.md), then attach it to the GitHub Release and
try Snap Store
`edge`/`candidate`. Store human review (for example a super-privileged
`allow-installation` plug) must not block the GitHub `.snap`. `stable` does not auto-release (#783):
after candidate QA, dispatch `snap-promote.yml`, which releases the
candidate revision to `latest/stable`
10. Mark as pre-release if version contains alpha/beta/rc
Monitor at: https://github.com/VocaHQ/vocalinux/actions
#### Void xbps repository (Gitea tag workflow)
Pushing the tag to `origin` (the fork's Gitea repository) also starts
`.gitea/workflows/xbps-release.yml`. Pushing a tag is a human act and is
approved each time: the workflow has no schedule and no manual trigger.
1. **Tag equals version.** The tag must be exactly `v` plus `__version__` in
`src/vocalinux/version.py`. Any other tag, including one that merely
contains the version, exits 1 and publishes nothing.
2. **Signing key required.** An empty `XBPS_SIGNING_KEY` exits 1 before any
container runs. The key is written to a 0600 temp file, passed to
`packaging/void/repo.sh`, and removed by a step that always runs.
3. **Build and sign.** `repo.sh` is the only path that builds or signs: the
pinned Void glibc container under podman, every template in smoke-gate
order, the full `x86_64` index, every package and the metadata signed.
4. **Verify.** Packages, signatures, repodata, `FINGERPRINT` and the client
key must all be present, nothing but glibc `x86_64` data may be present,
every package must be signed, and the signing fingerprint must be the
pinned one. A partial or unsigned repository never attaches.
5. **Publish.** The complete signed set attaches to the tag's release on
Gitea and replaces the assets of the rolling `latest` release, which the
client registration URL
(`https://git.bongbetic.com/xavierk/voculinux/releases/download/latest`)
serves. Push tags in version order: pushing an older tag refreshes
`latest` with the older repository.
The workflow never commits or pushes to any branch and never bumps a
template; that is the pull request in section 3.5. The `voculinux` template
fetches the fork's `vX.Y.Z` release asset, so that asset must exist and its
checksum must be in the template before the build can pass. A tag whose
templates still carry the pending checksum fails at the build step, by
design.
### Step 9: Post-Release Tasks
- [ ] Verify GitHub Release was created correctly
- [ ] Verify `SHA256SUMS` is attached and lists every GitHub asset (wheel, sdist,
both AppImages, both Flatpaks, the amd64 snap, the `.deb` and `.rpm` for
both arches) - the release notes tell users to run `sha256sum -c`
against it
- [ ] If the Store holds the snap for human review, confirm the GitHub `.snap`
is still attached and the notes document `snap install --dangerous`
- [ ] After candidate QA (install, tray, mic, model download, typing into a real
app), promote the snap to stable:
`gh workflow run snap-promote.yml -f tag=vX.Y.Z` - it fails unless
`latest/candidate` carries a revision whose version matches the tag. An
optional `snap-stable` environment with required reviewers adds a second
approval gate on the dispatch
- [ ] Verify provenance: `gh attestation verify <artifact> --repo VocaHQ/vocalinux`
- [ ] Verify PyPI package was published (if applicable), and that its wheel sha256
matches the line for that wheel in `SHA256SUMS`
- [ ] Verify website was deployed (check voculinux.com)
- [ ] Verify the Gitea xbps run succeeded, and that both the tag release and
`latest` carry the full signed set (`*.x86_64.xbps`, `*.sig2`,
`x86_64-repodata`, `FINGERPRINT`, the `.plist`) and that the fingerprint
matches the one in `README.md`
- [ ] Open the Void template bump pull request (section 3.5); never commit
it directly to `main`
- [ ] If the Flatpak remote is configured, verify it picked up the tag:
`flatpak remote-info vocahq com.voculinux.Voculinux` (docs/FLATPAK_REMOTE.md)
- [ ] Announce on social media/communities
- [ ] Update any pinned issues or discussions
## Emergency Hotfix Process
For critical bugs in a released version:
```bash
# Create hotfix branch from the tag
git checkout -b hotfix/v0.5.1-beta v0.5.0-beta
# Make fix, commit, push
git add -A
git commit -m "fix: critical bug description"
git push origin hotfix/v0.5.1-beta
# Create PR targeting main
# After merge, tag new version
git tag -a v0.5.1-beta -m "Hotfix release v0.5.1-beta"
git push origin v0.5.1-beta
```
## Version History
| Version | Date | Phase | Notes |
|---------|------|-------|-------|
| 0.2.0-alpha | 2024 | Alpha | Initial alpha |
| 0.3.0-alpha | 2024 | Alpha | - |
| 0.4.0-alpha | 2024 | Alpha | Multi-language support |
| 0.4.1-alpha | 2024 | Alpha | Language selector UI |
| 0.5.0-beta | 2025 | Beta | First beta release |
| 0.6.0-beta | 2026-02-11 | Beta | Web SEO, mobile responsiveness, improved Fedora support |
| 0.6.1-beta | 2026-02-12 | Beta | Device handling improvements |
| 0.6.2-beta | 2026-02-17 | Beta | Bug fixes |
| 0.6.3-beta | 2026-02-19 | Beta | GPU detection improvements |
| 0.7.0-beta | 2026 | Beta | Autostart, tabbed settings, Intel GPU support, single instance |
| 0.8.0-beta | 2026-03-01 | Beta | Push-to-talk mode, optional voice commands, input/audio compatibility fixes |
| 0.9.0-beta | 2026-03-14 | Beta | Left/right modifier keys, sound effects toggle, Wayland clipboard fallback, leading-space fix |
| 0.10.0-beta | 2026-03-25 | Beta | Keyboard/audio reliability hardening, IBus/tray stability fixes, CI + test coverage improvements |
| 0.10.1-beta | 2026-03-27 | Beta | Stability patch: tray resource bundling, engine-switch segfault prevention, settings close action, XKB layout preservation |
| 0.10.2-beta | 2026-04-08 | Beta | Non-ASCII text injection, IBus Wayland detection, Pop!_OS deps, code quality refactor |
| 0.11.0-beta | 2026-05-30 | Beta | Advanced Settings tab, IBus/recognition hardening, installer fixes, distro compatibility |
| 0.12.0-beta | 2026-06-07 | Beta | Remote API engine, Silero VAD, threading/IBus hardening, settings and installer fixes |
| 0.13.0-beta | 2026-06-30 | Beta | Guided whisper.cpp model variants, Wayland text-injection reliability, hotplug keyboard support, dictation spacing fix, website docs refresh |
| 0.14.0-beta | 2026-07-13 | Beta | Configurable modifier+key hotkeys, FunASR/SenseVoice remote-API support, GNOME/KDE Wayland IBus reliability fixes, audio crash fix, hybrid-CPU efficiency fix |
| 0.14.1 | 2026-07-17 | Stable | Flatpak packaging, AUR package, layout-aware hotkeys, installer/text-injection fixes |
| 0.14.2 | 2026-07-17 | Stable | IBus engine launch + FocusIn gate; settings tabs scroll to fit monitor |
| 0.15.0 | 2026-07-28 | Stable | Searchable settings + sidebar dictation footer, AppImage, expanded languages, dictation polish, auto-pause/keepalive, Vulkan device selection, ibus-wayland, Bluetooth mic + shortcut UI fixes |
| 0.16.0 | 2026-08-23 | Stable | Update checker + tray notify, Right Alt PTT default (new installs), searchable language list, delete unused models, Voca tone picker (family preview WAVs), About page, AGPL-3.0, family mic icons, installer/just/uv pinning, Test Dictation missing-model message, IBus/audio/clipboard/GPU/AppImage reliability, voculinux.com family workbench restyle |
| 0.17.0 | 2026-09-16 | Stable | Faster Whisper + Parakeet engines, Speech Model simple setup, first-run system language, Snap with ydotool/uinput, Flatpak bundles on the GitHub Release, XWayland/layout paste and settings/audio fixes |
| 0.18.0 | 2026-10-02 | Stable | Dictation Pad, evdev hotkey suppression, PipeWire capture + system audio, RemoteDesktop portal injection, per-language shortcuts + tray history, TinyDiarize file transcription, D-Bus activation, custom dictionary, postprocessing hook, floating overlay, audio ducking, AUR -bin + self-hosted Flatpak remote + snap-promote |
| 0.18.1 | 2026-10-08 | Stable | Settings Proxy page, opt-in JSONL transcript persistence, .deb/.rpm release packages, slimmer tray menu, dictation-pad Wayland ghost fix, evdev keyboard-discovery tightening, KWin 6 case fix, staged model picker |
| 0.18.5 | 2026-10-27 | Stable | Patch: racing stop can no longer leave the mic recording, stalled-mic reads cannot hang Stop, keyboards survive suspend and the GNOME lock screen, Wayland dialogs stay on their own workspace, Ctrl+wheel zoom restored, installer upgrade mode keeps existing config, paste copies marked sensitive on wl-copy 2.2+, AUR source gate on attested sdists |
| 0.16.2 | 2026-09-05 | Stable | Patch: KDE leftover IBus skip, Wayland/IBus shortcuts via wtype/ydotool, BackSpace delete that, installer glslc on Fedora/Arch, nightly version stamp, release integrity pins, AUR PKGBUILD CI gate, distro CI/docs drift, Gateway Beta on site |
| 0.16.1 | 2026-08-30 | Stable | Patch: per-engine model at startup, leftover tray idle, terminal Ctrl+Shift+V paste, GNOME XWayland layout after IBus, Python 3.11 floor + verified downloads, AppImage glibc that boots on Debian 12, Settings/About polish |
## Questions?
If you're unsure about any step:
1. Check previous releases for examples
2. Review the GitHub Actions workflows in `.github/workflows/`
3. Ask in the project's discussions
---
**Note**: This document should be updated when the release process changes.