Release Versioning
The target release policy (stable, beta and dev channels; corrections and betas) is RELEASING.md. This page describes the current procedure until that lands (#145).
Dictate uses calendar versioning for public releases:
YYYY.M.N
YYYY.Mis the month the release is cut in (UTC), without zero-padding.Nis the release count in that month. It is not the day.Nis the highest stableNalready tagged in that month, plus 1. The first release of a month is1.Ncan be more than 31.- Releases before 2026.10.1 used the day as
N. Do not change those tags. The count continues from the highest one in the month.
For example, after 2026.10.20 comes 2026.10.21, after 2026.10.31 comes
2026.10.32, and the first release of November is 2026.11.1. Sort versions
as numbers, part by part, never as text: as text, 2026.10.9 comes after
2026.10.32. In shell, sort -V and git tag --sort=-v:refname are correct.
Python package metadata uses the same PEP 440-compatible version.
Older releases sometimes added a correction -K (for example 2026.9.25-1)
when an immutable package registry already had the version. The tooling still
accepts these tags. For new code, use the next N.
The app reports the public release version through:
dictate --version
Print the next version, or check one, with (fetch the tags first):
git fetch --tags
python scripts/calver.py next
python scripts/calver.py next --today 2026-10-20
python scripts/calver.py check 2026.10.3
Synchronize release metadata before tagging with:
python scripts/sync_release_version.py # the next version
python scripts/sync_release_version.py 2026.10.3 # must be the next version
python scripts/sync_release_version.py 2026.10.3 --check
When it writes, sync_release_version.py refuses a version that is not the
next release this month (--today sets the date for a test). --check only
compares the files with the version, so it works for any released version.
Release tags and GitHub milestones should use the public version with a leading v, for example:
v2026.10.3
v2026.10.32
Windows MSI installers cannot use the public CalVer string directly because
WiX/MSI requires numeric major.minor.patch[.build], with major and minor at
most 255. Keep tauri.conf.json's public app version as YYYY.M.N[-K], but
map bundle.windows.wix.version to YY.M.N.K for MSI packaging:
2026.10.3 -> 26.10.3.0
2026.10.32 -> 26.10.32.0
2026.9.25-1 -> 26.9.25.1
After creating a v20* CalVer tag on a commit that has reached the default
branch, start .github/workflows/release.yml manually with the release_tag
input. The release workflow first verifies that the requested tag resolves to a
commit reachable from the default branch, then runs the Linux/Windows test
matrix, the hosted Windows user install smoke test, release metadata validation,
Python artifact checks, and npm package validation before publishing.
The release, npm-publish and windows-signing environments need maintainer
approval, so each publishing job waits in the run until approved. After the
Windows installer is uploaded, the release dispatches
windows-release-gate.yml, which installs that exact setup.exe on the
win11-gpu VM, launches it, checks the running engine's version, and
uninstalls. See windows-11.md.
GitHub release publication and Microsoft Store publication are separate lanes.
Publishing a GitHub release updates the downloadable source and unsigned direct-install artifacts;
it does not make an update available through the Microsoft Store. Store updates
require the Store MSIX workflow in draft mode, Partner Center review, and an
explicit publish/certification step. The draft refuses to build without a
passing Windows release gate for the same version; skip_windows_gate is the
override.
The npm package is published as @arcforgelabs/dictate and powers the hosted CDN
install/update scripts. Publishing uses GitHub OIDC trusted publishing on the
npm-publish environment. Do not dispatch a release until both publishers exist
on the package, each with direct npm publish allowed:
| Workflow file | Environment |
|---|---|
release.yml |
npm-publish |
npm-unstable.yml |
npm-publish |
Repository owner is arcforgelabs. Repository name is dictate. A long-lived
NPM_TOKEN is not used. The June 2026 granular token expired, so latest
stayed on 2026.7.4 while the GitHub release moved to 2026.9.25.
Provenance is off. npm only accepts provenance statements from public source
repositories, and the repository was private when trusted publishing was set up.
Both publish steps force publishConfig.provenance to false, so tags cut
before that change still publish. The repository is public now, so provenance
can be turned back on by removing that override and setting
publishConfig.provenance to true.
The previous personal-scope package, @iamsamuelrodda/dictate, is deprecated on npm with a migration notice pointing users to @arcforgelabs/dictate. Keep it published as a compatibility landing point for old scripts; do not unpublish it unless there is a specific security or legal reason.
The current developer/bootstrap install path is:
powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@arcforgelabs/dictate@latest/install.ps1 | iex"
Npm Channels
Use npm dist-tags as update channels for the hosted developer/source bootstrap:
| npm dist-tag | Meaning | Publisher |
|---|---|---|
latest |
Stable developer bootstrap that points at a reviewed CalVer GitHub release tag. | .github/workflows/release.yml |
unstable |
Test bootstrap for a selected branch/SHA before it is considered stable. | .github/workflows/npm-unstable.yml |
The stable release workflow publishes @arcforgelabs/dictate@latest only from a
validated v20* release tag reachable from the default branch. Do not publish
feature work to latest.
The unstable workflow is manually dispatched. It creates a unique prerelease npm
version such as 2026.7.4-unstable.123.1, patches the hosted install.ps1 and
update.ps1 scripts in the npm package so they download the exact selected
commit archive, and publishes that shim under the requested dist-tag
(unstable by default). It does not move GitHub releases, Microsoft Store
drafts, or the stable npm latest tag. The app version reported by
dictate --version still comes from the selected source commit, so branch builds
without a version bump can report the base CalVer while being delivered through
the unstable channel.
Every unstable publish produces a durable GitHub prerelease containing the matching
unsigned Windows MSI/NSIS installers and the Linux .deb, because installed
direct builds cannot update from short-lived Actions artifacts. With run_tests=true,
the unstable publish waits for the Linux/Windows Python
matrix, the Windows user install/update/uninstall smoke, the Linux user
encrypted-sync install smoke, UI build/server smoke, desktop shell compile/Rust
tests, Linux/Windows desktop bundles, and npm package dry-run. Store upload/publish
remains a separate guarded
workflow. Paid Authenticode signing for direct-download Windows installers is a
future-only lane, not a current release blocker.
Unstable is upstream of stable
The channels are ordered: everything on latest went through unstable
first, or a newer build is on unstable. A Beta (unstable) user must never
be offered something older than the current stable release. Two mechanisms
keep that true:
release.ymlmoves the npmunstabledist-tag forward to the stable version it just published whenever the unstable pointer is behind it (same-base prereleases and newer-base unstable builds are left alone).- The app's update check resolves the Beta channel to the newer of the
unstableandlatestdist-tags, so installed copies get the same answer even if the pointer was not moved.
To publish an unstable bootstrap:
gh workflow run npm-unstable.yml \
-f source_ref=feature/my-branch \
-f npm_tag=unstable \
-f run_tests=true
To install or update from the unstable bootstrap:
powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@arcforgelabs/dictate@unstable/install.ps1 | iex"
powershell -ExecutionPolicy Bypass -Command "iwr -useb https://cdn.jsdelivr.net/npm/@arcforgelabs/dictate@unstable/update.ps1 | iex"
For Linux user installs that update through the in-app/npm path, set:
dictate config set-update-channel unstable
dictate config set-update-channel stable
stable is the user-facing name for npm's latest dist-tag. unstable maps to
the unstable dist-tag. DICTATE_UPDATE_CHANNEL=unstable remains available for
temporary process-level overrides, but the CLI setting is the supported user
opt-in mechanism.
Promotion rule: once a feature set is locked, tested, and accepted, merge it to
the release branch/default branch, create the normal CalVer tag, run
.github/workflows/release.yml, then advance official channels such as
Microsoft Store through their own guarded workflows. Never promote by retagging
an unstable npm package as stable.
Windows has two supported ecosystems. Direct installs may select stable or unstable and update from the corresponding GitHub release/prerelease installer; these builds are unsigned and Windows will show the expected untrusted-publisher warning. Microsoft Store installs are stable-only and update through the Store. Do not revisit paid Windows signing until there is an explicit revenue-backed decision.
Each Windows payload contains engine/dictate-distribution.json. Direct bundles mark
themselves direct and include their exact channel package version; Store MSIX bundles
mark themselves store. The app uses this marker rather than guessing from Windows.