chore(release): codify all deployment channels as release rules; preview always builds from main
A release now has an explicit channel checklist (docs/RELEASING.md §5b): GH Release + stable updater manifest, preview updater channel, GHCR + Docker Hub in both CUDA and ROCm flavors, and the Docker Hub overview sync (whose continue-on-error step must be verified by step log — it 403s silently on tokens without description-edit scope). Preview/RC policy is now enforced, not just documented: release.yml's preview-gate fails publish_preview dispatches from any branch but main, since the preview manifest and rolling Docker tags all track main. Also fixes docs/RELEASING.md §4-5, which still described the pre-2026-06 versioning scheme (tauri.conf.json + Cargo.toml as sources, 'Tauri ignores package.json') — the exact opposite of the current single-source rule — and docs/update-channels.md, which invited previews off feature branches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
060d826f3c
commit
f609f57fbc
@@ -43,7 +43,7 @@ on:
|
||||
required: false
|
||||
default: "true"
|
||||
publish_preview:
|
||||
description: "Publish a rolling 'preview' prerelease (updater Preview channel) from the selected branch"
|
||||
description: "Publish a rolling 'preview' prerelease (updater Preview channel). Previews ALWAYS build from main — dispatching from any other branch fails the preview-gate."
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -148,6 +148,15 @@ jobs:
|
||||
set -euo pipefail
|
||||
event="${{ github.event_name }}"
|
||||
if [ "$event" = "schedule" ] || { [ "$event" = "workflow_dispatch" ] && [ "${{ inputs.publish_preview }}" = "true" ]; }; then
|
||||
# Preview channel policy (owner-set 2026-07-16): previews ALWAYS
|
||||
# build from main. The preview updater manifest and the Docker
|
||||
# rolling tags (:latest/:main/:rocm) all track main — a preview
|
||||
# cut from a side branch would desync the channels and could
|
||||
# ship code that never merged. Merge to main first.
|
||||
if [ "${{ github.ref }}" != "refs/heads/main" ]; then
|
||||
echo "::error::Preview builds publish from main only (got '${{ github.ref }}'). Merge to main, then dispatch with publish_preview=true."
|
||||
exit 1
|
||||
fi
|
||||
echo "is_preview=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is_preview=false" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -48,6 +48,8 @@ For anything new: prefer what's already pinned in `pyproject.toml` / `frontend/p
|
||||
|
||||
**Localization (hard rule):** No hardcoded non-English (CJK) **user-facing text** anywhere in the codebase except the translation layer (`frontend/src/i18n/`). All UI strings go through i18n (`t('...')` keys in `locales/*.json`); native language names live in `i18n/index.ts` (`LANGUAGES`). Functional CJK is allowed and tracked via the allowlist in `tests/test_no_hardcoded_cjk.py` — text-processing regexes, model/engine vocabulary & identifiers (e.g. CosyVoice speaker IDs), localized error matching, demo/eval data, and test fixtures. CI fails on any hardcoded CJK outside the allowlist; to add legitimate functional CJK, extend `_ALLOWED_FILES` there with a justification.
|
||||
|
||||
**Release deployment channels (hard rule, owner-set 2026-07-16):** a version bump is not "released" until **every** deployment channel ships it — the full checklist (sources, producing workflows, per-channel verification) lives in `docs/RELEASING.md` §5b. The channels: GitHub Release with 4-platform installers + signed `latest.json` (Stable updater channel, body from CHANGELOG); the Preview updater channel; GHCR **and** Docker Hub images in **both** flavors (CUDA `:X.Y.Z`/`:X.Y`/`:stable`, ROCm `-rocm` suffixes); the Docker Hub overview page synced from `deploy/dockerhub-overview.md` (its sync step is `continue-on-error` and 403s silently if `DOCKERHUB_TOKEN` lacks description-edit scope — verify the **step log**, never just job-green). Verify all channels after tagging; a missing channel is a release bug to fix immediately, not backlog. **Preview/RC always sources from `main`:** there are no RC tags — the rolling preview channel (preview `latest.json`, Docker `:latest`/`:main`/`:rocm`) *is* the RC, it always builds from `main` (release.yml's preview-gate refuses other branches), and previewing a fix means merging it to `main` first. Never cut a side-branch build.
|
||||
|
||||
**Fix quality (hard rule, owner-set 2026-06-16):** Fix issues *properly* and future-maintenance-proof — don't stop at the symptom. Root-cause fully, fix the whole **class** of the bug (not just the one reported instance), add a fail-before/pass-after regression test, and harden against recurrence (e.g. if a lockfile drift only fails in Docker, also make CI catch it). Go the extra mile where it durably pays off. Be token-efficient about it — extra **effort**, not extra **verbosity**: no padding, no redundant re-checks, the smallest correct change that is also recurrence-proof. Don't be shy to spend the effort a proper fix needs; do be shy about wasting tokens.
|
||||
|
||||
**Keep main green (hard rule, owner-set 2026-06-16):** A merge must **never break `main`'s CI**. Before a change lands, verify the *full* CI matrix would pass — every workflow in `.github/workflows/` **and** `deploy/Dockerfile`, not only the checks you happened to run. Dependency / lockfile / config changes must be validated against **all** consumers. Specifically: `frontend/` is a bun **workspace monorepo** — the lockfile is the repo-root `bun.lock`, and `deploy/Dockerfile` runs `bun install --frozen-lockfile`, so any `frontend/package.json` change requires regenerating root `bun.lock` and confirming `bun install --frozen-lockfile` passes (plain `bun install` in `ci.yml` silently tolerates drift, so CI-green ≠ Docker-green). Likewise re-check CodeQL/Security on code changes and the Tauri `cargo` build on Rust/dep changes.
|
||||
|
||||
+47
-14
@@ -39,26 +39,36 @@ Failures (no network, 404, signature mismatch) are silent — the app continues
|
||||
|
||||
## 4. Version bumps
|
||||
|
||||
Two files must agree before you tag a release:
|
||||
`frontend/package.json` is the **single source of truth** for the app version
|
||||
(hard rule, owner-set 2026-06-16 — full rationale in CLAUDE.md → Conventions →
|
||||
Versioning). Vite injects `__APP_VERSION__` from it, and
|
||||
`frontend/src-tauri/tauri.conf.json` derives its bundle version from it
|
||||
(`"version": "../package.json"` — never hand-edit a literal back in). Three
|
||||
toolchain-required mirrors are bumped in lockstep:
|
||||
|
||||
- `frontend/src-tauri/tauri.conf.json` → `"version": "0.2.0"`
|
||||
- `frontend/src-tauri/Cargo.toml` → `version = "0.2.0"`
|
||||
- `frontend/src-tauri/Cargo.toml`
|
||||
- `pyproject.toml`
|
||||
- `backend/core/version.py` (`_FALLBACK_VERSION`)
|
||||
|
||||
(Not `frontend/package.json` — Tauri ignores it.)
|
||||
|
||||
Keep bumps monotonic. Tauri updater uses semver comparison, so `v0.2.0` does not update clients already on `v0.2.1`.
|
||||
Lockstep is guarded by `tests/test_app_version.py`. With `AUTO_VERSION_BUMP`
|
||||
off (the current owner setting), `main` holds at the released version between
|
||||
releases; the post-release bump to `X.Y.(Z+1)` happens only when the owner
|
||||
asks. Keep bumps monotonic — the updater uses semver comparison, so `v0.2.0`
|
||||
does not update clients already on `v0.2.1`.
|
||||
|
||||
## 5. Cutting a release
|
||||
|
||||
```bash
|
||||
# 1. Bump versions in the two files above, commit.
|
||||
git add frontend/src-tauri/tauri.conf.json frontend/src-tauri/Cargo.toml
|
||||
git commit -m "release: v0.2.0"
|
||||
1. **CHANGELOG first (hard rule):** make sure `CHANGELOG.md` has a complete,
|
||||
user-facing `## [X.Y.Z] — DATE` section (rename `## [Unreleased]`).
|
||||
`release.yml` extracts that section verbatim as the GitHub Release body —
|
||||
a missing section ships a bare release.
|
||||
2. Verify the version files match the tag you're about to cut:
|
||||
`uv run pytest tests/test_app_version.py -q`.
|
||||
3. Tag and push:
|
||||
|
||||
# 2. Tag and push.
|
||||
git tag v0.2.0
|
||||
git push origin main
|
||||
git push origin v0.2.0
|
||||
```bash
|
||||
git tag vX.Y.Z
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
The `Desktop Release` workflow fires on tag push. It builds four targets in parallel on GitHub Actions runners:
|
||||
@@ -77,6 +87,29 @@ Workflow runtime: **~20-40 minutes** (PyInstaller + four platform builds). Follo
|
||||
|
||||
When it finishes, the draft release needs manual publishing — GitHub → Releases → **Edit** the draft → **Publish release**. Once published, existing clients detect the update on their next launch.
|
||||
|
||||
## 5b. Deployment channels — all must ship (hard rule, owner-set 2026-07-16)
|
||||
|
||||
A version bump is not "released" until **every** channel below carries it.
|
||||
Verify each one after the workflows finish — a missing channel is a release
|
||||
bug to fix immediately, not backlog.
|
||||
|
||||
| Channel | Source | Produced by | How to verify |
|
||||
|---|---|---|---|
|
||||
| GitHub Release: installers + signed `latest.json` (**Stable** updater channel) | the `vX.Y.Z` tag | `release.yml` on tag push | Release page has dmg (arm+intel), msi/exe, AppImage/deb, `latest.json`; body = the CHANGELOG section, not the auto-generated fallback |
|
||||
| **Preview** updater channel (rolling `preview` prerelease) | **`main` only** | `release.yml` nightly cron / manual dispatch | preview `latest.json` stamps `X.Y.Z-N` and semver-sorts above stable |
|
||||
| GHCR CUDA image: `:X.Y.Z`, `:X.Y`, `:stable` | the tag | `docker.yml` on tag push | `docker manifest inspect ghcr.io/debpalash/omnivoice-studio:X.Y.Z` |
|
||||
| GHCR ROCm image: `:X.Y.Z-rocm`, `:X.Y-rocm`, `:stable-rocm` | the tag | `docker.yml` on tag push | same, with `-rocm` suffix |
|
||||
| Docker Hub mirror of **all** the above tags | the tag | `docker.yml` (gated on `DOCKERHUB_*` secrets) | tag list at hub.docker.com/r/palashdeb/omnivoice-studio/tags |
|
||||
| Docker Hub **overview page** | `deploy/dockerhub-overview.md` @ main | `docker.yml` on main pushes | **read the step log, not the job status** — the step is `continue-on-error` and 403s silently when `DOCKERHUB_TOKEN` lacks description-edit scope |
|
||||
| Rolling Docker previews: `:latest`, `:main`, `:rocm` | **`main` only** | `docker.yml` on every main push | tag timestamps move with main |
|
||||
|
||||
**Preview/RC policy:** there are no RC tags (beta cadence — see CLAUDE.md).
|
||||
The preview channel *is* the release candidate, and it **always builds from
|
||||
`main`** — the preview-gate in `release.yml` refuses `publish_preview` from
|
||||
any other branch, and the rolling Docker tags track `main` by construction.
|
||||
To get users testing a fix: merge to `main`, then cut a preview. Never a
|
||||
side-branch build.
|
||||
|
||||
## 6. Expect-to-fail-first-time on Windows and Linux
|
||||
|
||||
mac-ARM is tested locally. The other three platforms will likely hit PyInstaller issues on their first CI run because neither dependency set nor platform quirks have been exercised. Common failures to expect:
|
||||
|
||||
@@ -60,9 +60,11 @@ Preview builds come from **`main`**, two ways:
|
||||
`preview` prerelease from `main` — but only when `main` actually moved in the
|
||||
last day, so idle days cost nothing. Preview is never more than ~24h behind
|
||||
`main`.
|
||||
- **On demand.** **Actions → Desktop Release → Run workflow**, pick a branch
|
||||
(usually `main`), set **publish_preview = true**. Useful to cut a preview off
|
||||
a feature branch, or to refresh immediately without waiting for the nightly.
|
||||
- **On demand.** **Actions → Desktop Release → Run workflow** on `main`, set
|
||||
**publish_preview = true**. Useful to refresh immediately without waiting
|
||||
for the nightly. Previews build from `main` **only** (hard rule, owner-set
|
||||
2026-07-16) — the preview-gate refuses any other branch; to preview a fix,
|
||||
merge it to `main` first.
|
||||
|
||||
Either way it builds the matrix and publishes/updates a single rolling
|
||||
`preview` **prerelease** — always flagged prerelease, and carrying the same
|
||||
|
||||
Reference in New Issue
Block a user