Skip to content

Auto-update Phase A: CI workflow + GitHub Releases manifest + verification gates #38

Description

@ComfyChloe

What's wrong

ARC-Client (currently 0.93.4) has no auto-update system. Every release requires a manual download and install. Users stay on old versions and miss bug fixes or features.

What users see

  • App never prompts for an update.
  • Bug fixes require manual download from GitHub Releases.
  • No way to receive beta builds automatically.

Impact

Users stuck on old versions. Bug reports pile up against issues that are already fixed in unreleased code.

Expected behavior

  • App automatically checks for updates on launch.
  • Two channels: Stable (default, from master) and Beta (from Dev-chloe).
  • Updates are downloaded silently and prompt the user to restart.
  • SmartScreen warnings accepted as a known limitation for v1 (deferred to Phase C).

Acceptance criteria (this issue = Phase A only)

  • .github/workflows/release.yml triggers on push to master and on v*.*.*-beta.* tags.
  • Stable channel builds latest.yml; Beta channel builds latest-beta.yml.
  • NSIS per-user installer is the only released artifact for v1.
  • CI smoke test verifies manifest shape and asset hashes before upload.
  • Beta releases marked as prerelease; Stable as release.
  • Branch protection on master (PR + 1 approval + green CI).
  • Tags never force-moved.
  • Phase A gates G1, G2, G3, G5 pass.

Technical details

Architecture decisions

Element Choice
Library electron-updater@~8.0.0 (peer of electron-builder 25.1.0)
Channels Stable ← master merge + version tag; Beta ← annotated tag vX.Y.Z-beta.N
Artifact NSIS per-user installer only (cuts portable complexity)
Hosting GitHub Releases on this repository
Code signing Deferred to Phase C
User channel pick Default Stable; Settings → Updates → switch with confirmation dialog
Force-update Hardcoded MIN_REQUIRED_VERSION in main/updater.ts

Branch → channel mapping

master ──approved merge + version tag──> Stable release (tag vX.Y.Z)
Dev-chloe ──tag vX.Y.Z-beta.N──> Beta release
  • Dev-chloe is re-cast as a release-candidate branch: feature work happens on topic branches, lands on Dev-chloe via PR, and only an explicit annotated tag publishes to Beta.
  • Branch protection on master: PR + 1 approval + green CI.
  • Dev-chloe is not force-pushed (ruleset enforced).

Versioning

  • package.json version is the canonical Stable version.
  • Stable: bump package.json version in the PR that lands on master. Workflow tags v<version> from the merge commit.
  • Beta: workflow computes versionSuffix=-beta.${GITHUB_RUN_NUMBER} at build time. app.getVersion() returns 0.94.0-beta.12. Tag is v<semver>-beta.N.
  • No package.json mutation in CI.
  • All version comparisons in client code use semver.coerce().
  • Commit-message escape hatches: [skip ci] (don't run workflow) and [skip-release] (run workflow, fail it if a beta tag would publish without a CHANGELOG entry).

Build artifacts

Build target Purpose Manifest name Released?
electron-builder --win nsis Per-user NSIS installer latest.yml Yes
electron-builder --win dir (portable) Local dev / smoke tests portable-latest.yml No

NSIS config (per-user default):

win:
  target: nsis
nsis:
  oneClick: false
  perMachine: false
  allowToChangeInstallationDirectory: true
  installLocationBase: localAppData

Blockmap auto-generated by electron-builder. Differential download is automatic when both client and server have blockmaps.

GitHub Actions workflow

Single workflow file: .github/workflows/release.yml.

Triggers:

on:
  push:
    branches: [master]
    tags: ['v*.*.*-beta.*']

Steps:

  1. checkout (fetch-depth: 0).
  2. setup-node 20, cache npm.
  3. npm ci.
  4. npm run typecheck (gate).
  5. Beta-only: tag-rate-limit check (fail if previous beta tag < 30 min old; configurable via MIN_BETA_INTERVAL_MIN).
  6. Beta-only: changelog gate (fail if CHANGELOG.md or beta-notes/ not in diff vs previous beta tag and commit message does not contain [skip-release]).
  7. npm run build → electron-builder --win nsis (Beta: --config.win.versionSuffix=-beta.${GITHUB_RUN_NUMBER}).
  8. Smoke test: scripts/verify-build-output.js (manifest shape, asset hashes, version match).
  9. Rename manifest: latest.yml (Stable) / latest-beta.yml (Beta).
  10. Create GitHub Release: release_type=release (Stable) / prerelease (Beta).
  11. Upload dist/*.exe, dist/*.blockmap, manifest.

Permissions: contents: write (required to create Releases).

CI smoke test — scripts/verify-build-output.js

Verifies before manifest upload:

  • dist/latest.yml (or dist/latest-beta.yml) is well-formed.
  • Every path exists in dist/.
  • Every sha512 matches the actual file hash.
  • blockmap files exist for every executable.
  • version in the manifest matches the expected v<version> (Stable) or v<version>-beta.<run_number> (Beta).

If any check fails, the workflow fails before the GitHub Release is created.

Phase A verification gates (must pass before Phase B)

# Gate Method Pass criterion
G1 bundledLibs path resolution under NSIS Build NSIS, install to temp dir, exec .exe with --smoke process.resourcesPath + '/bundledLibs/' exists and whisper-node-addon loads its DLL from it
G2 userdata preserved across update Install v0.94 NSIS, write user data, install v0.95 over it userdata/config.json content unchanged
G3 Blockmap differential works Build v0.94 + v0.95, install v0.94, trigger update to v0.95 with verbose logging Downloaded bytes < 50% of full installer size
G5 Beta manifest resolves Fetch the actual latest-beta.yml asset URL from the first beta release HTTP 200, valid YAML, correct version, path/sha512 for the beta release

Rollback playbook (documented in docs/release-process.md)

For a bad Stable release:

  1. Stop further promotion and mark the affected GitHub release as bad; do not delete or force-move an existing tag.
  2. Identify the last-known-good commit; create a short-lived recovery branch.
  3. Bump package.json to a version higher than the bad release (usually next patch).
  4. Run the normal build and updater gates; publish as a new Stable release.
  5. Test an installed bad build against the new manifest.
  6. Keep the previous installer available for manual rollback.

Pre-flight before implementation

  1. Verify GitHub repo visibility. If private, design token delivery without embedding a long-lived token in the installer or logs.
  2. Coordinate Dev-chloe branch re-purposing.
  3. Enable branch protection on master in GitHub UI.
  4. Build v0.94 (current) and v0.95 (test) NSIS installers for gate verification.

Documentation deliverables

File Content
docs/release-process.md How to tag a beta; how to bump Stable; rollback playbook; emergency hotfix procedure
Initial CHANGELOG.md Created as part of Phase A so the changelog gate has something to check

Out of scope

  • Client runtime + UI + force-update gate — see Phase B issue.
  • Code signing & notarization (Phase C).
  • macOS / Linux support.
  • Auto-update of bundled whisper models / native addons.
  • Delta/differential updates beyond what blockmap provides.
  • Telemetry / opt-in crash reporting.
  • Multi-architecture (ARM64 Windows).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: BuildBuild pipeline / packagingArea: CICI / workflowsPriority: HighGet done before others with high preferenceenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions