wai-quantum-desktop: installers on a tag for macOS, Linux and Windows, signed updates, the cask and the Scoop manifest #549

Merged
dcharlot merged 6 commits from wai/quantum-desktop-release into main 2026-10-30 12:04:28 -04:00
Owner

Quantum Studio for the desktop gains three things:

  • installers for macOS, Linux and Windows, built on a tag;
  • signed updates, offered in the Studio;
  • CI legs on all three platforms.

Nothing is signed until the owner adds the keys (see below). Until then a release has unsigned installers and no update.

What it adds

The app (wai/wai-quantum-desktop)

  • The updater, with two commands, update_check and update_install. The app asks one address, https://wai.transaction.science/quantum-studio-desktop/latest.json. It installs an update only when the person asks, and only when the update's signature verifies with the public key in tauri.conf.json and names the version announced (requireSignedVersion). Then it restarts into the new version.
  • Only copies made by the app's own installer update themselves: the macOS app (outside its disk image), the AppImage, the deb and rpm packages, and the Windows installer. A copy built with cargo, or the portable zip that Scoop installs, does not.
  • pubkey is empty for now, so no copy updates itself yet.
  • Two new subcommands:
    • verify-update FILE SIG VERSION makes the same check the updater makes;
    • update-status prints, as JSON, which installer a copy came from.
  • icons/icon.ico and icon.icns. The app had never been built on Windows, and the Windows build needs the .ico.

The Studio: a bar above the frame, shown only in the desktop app, in English and Chinese (src/update.js). It is tested without a browser in test/update.test.mjs. The views page is rebuilt to match.

Releases (wai-quantum-desktop-release.yml, on a wai-quantum-desktop-v* tag)

  • The tag must equal the version in both Cargo.toml and tauri.conf.json.
  • What each platform builds:
    • macOS, aarch64 and x86_64: a .dmg.
    • Linux, x86_64 and aarch64: an AppImage, a deb and an rpm.
    • Windows: an NSIS -setup.exe and a portable .zip.
  • Each build runs what it made before attaching it: check, update-status, the macOS code signature, hdiutil verify, the AppImage itself, and the deb unpacked. When the release signs updates, it also runs verify-update on every update it signed.
  • Uploads go straight to the forge over the private network, with the run's own token.
  • A last job writes the Homebrew cask, the Scoop manifest and latest.json, and attaches them to the version's own release. Nothing is pushed to the tap or the bucket.
  • The bundler is pinned at tauri-cli 2.12.3, installed in a folder of its own on the host runners.

The update manifest's address is the website, not a release. Each signed release attaches its own latest.json. The website copies it from that release at its next deploy, using scripts/sync-desktop-update.sh <version> in the website's repository. That script refuses:

  • a manifest for another version;
  • an update not on that version's release;
  • a signature not made for that version;
  • a version older than the one the site serves.

There is no standing pre-release.

CI (wai-quantum-studio.yml): new legs desktop app · linux (act-22.04 plus the web view's libraries) and desktop app · windows (pwsh). Each runs the tests, requiring the update tests by name, plus clippy, check and update-status. The Linux leg also runs release/manifests_test.py, and the Studio job runs update.test.mjs.

Checked

Where Result
Linux build host, in catthehacker/ubuntu:act-22.04 17 tests passed, clippy clean, check ok. The AppImage (81.66 MiB) runs and reports appimage. The deb runs and reports deb. The rpm is 7.24 MiB.
macOS build host 17 tests passed, clippy clean. The .app is ad-hoc signed with hardened runtime and passes codesign --verify --deep --strict; the bundled app's check passes and it reports app. The DMG is VALID under hdiutil verify.
Windows host 17 tests passed, clippy clean, check and update-status ok. The NSIS installer is 5.50 MiB. The portable copy reports no installer. verify-update refuses a forged signature.
Studio Browserless tests passed, including 1153 locale checks. build-views --check passed. studio.test.mjs passed 241/241, twice.
Release scripts manifests_test.py: 15 checks, including the cask parsing as Ruby. The site script's checks pass on 8 fixtures (2 accepted, 6 refused).
Repository gates Repository checks all ok. The breaking-change gate reports no unacknowledged break. Docs blocks pass. Every workflow parses. wfsteps.py found no problems. Name checks are clean.

Not run:

  • The Linux arm64 release leg. Its only runner is the CI VM, and this left it alone.
  • The macOS x86_64 app is built but not run, because the macOS host has no Rosetta.
  • No tag has been pushed, so no release has been made and no installer has been installed.
  • The Windows run used webpki-root-certs 1.0.9. It was bumped to 1.0.10 afterwards, which is 11 days old and the first release that fixes RUSTSEC-2026-0383. This PR's Windows leg is the first run with 1.0.10.

For the owner (also in the desktop README)

  1. Make the update key on a machine you trust: cargo tauri signer generate -w ~/.tauri/wai-quantum-desktop.key. Put the .pub line in tauri.conf.json as plugins.updater.pubkey, and commit it.
  2. Set the forge secrets WAI_QUANTUM_DESKTOP_UPDATER_KEY (the .key file's text) and WAI_QUANTUM_DESKTOP_UPDATER_KEY_PASSWORD.
  3. For macOS signing and notarisation:
    • set APPLE_SIGNING_IDENTITY;
    • set APPLE_CERTIFICATE and APPLE_CERTIFICATE_PASSWORD (a Developer ID .p12), unless the runner's keychain already holds the identity;
    • for notarisation, set either APPLE_API_ISSUER, APPLE_API_KEY and APPLE_API_KEY_P8, or APPLE_ID, APPLE_PASSWORD and APPLE_TEAM_ID.

Windows Authenticode signing is not wired up.

🤖 Generated with Claude Code

Quantum Studio for the desktop gains three things: - installers for macOS, Linux and Windows, built on a tag; - signed updates, offered in the Studio; - CI legs on all three platforms. Nothing is signed until the owner adds the keys (see below). Until then a release has unsigned installers and no update. ## What it adds **The app** (`wai/wai-quantum-desktop`) - The updater, with two commands, `update_check` and `update_install`. The app asks one address, `https://wai.transaction.science/quantum-studio-desktop/latest.json`. It installs an update only when the person asks, and only when the update's signature verifies with the public key in `tauri.conf.json` and names the version announced (`requireSignedVersion`). Then it restarts into the new version. - Only copies made by the app's own installer update themselves: the macOS app (outside its disk image), the AppImage, the deb and rpm packages, and the Windows installer. A copy built with cargo, or the portable zip that Scoop installs, does not. - `pubkey` is empty for now, so no copy updates itself yet. - Two new subcommands: - `verify-update FILE SIG VERSION` makes the same check the updater makes; - `update-status` prints, as JSON, which installer a copy came from. - `icons/icon.ico` and `icon.icns`. The app had never been built on Windows, and the Windows build needs the `.ico`. **The Studio:** a bar above the frame, shown only in the desktop app, in English and Chinese (`src/update.js`). It is tested without a browser in `test/update.test.mjs`. The views page is rebuilt to match. **Releases** (`wai-quantum-desktop-release.yml`, on a `wai-quantum-desktop-v*` tag) - The tag must equal the version in both `Cargo.toml` and `tauri.conf.json`. - What each platform builds: - macOS, aarch64 and x86_64: a `.dmg`. - Linux, x86_64 and aarch64: an AppImage, a deb and an rpm. - Windows: an NSIS `-setup.exe` and a portable `.zip`. - Each build runs what it made before attaching it: `check`, `update-status`, the macOS code signature, `hdiutil verify`, the AppImage itself, and the deb unpacked. When the release signs updates, it also runs `verify-update` on every update it signed. - Uploads go straight to the forge over the private network, with the run's own token. - A last job writes the Homebrew cask, the Scoop manifest and `latest.json`, and attaches them to the version's own release. Nothing is pushed to the tap or the bucket. - The bundler is pinned at `tauri-cli` 2.12.3, installed in a folder of its own on the host runners. **The update manifest's address** is the website, not a release. Each signed release attaches its own `latest.json`. The website copies it from that release at its next deploy, using `scripts/sync-desktop-update.sh <version>` in the website's repository. That script refuses: - a manifest for another version; - an update not on that version's release; - a signature not made for that version; - a version older than the one the site serves. There is no standing pre-release. **CI** (`wai-quantum-studio.yml`): new legs `desktop app · linux` (act-22.04 plus the web view's libraries) and `desktop app · windows` (pwsh). Each runs the tests, requiring the update tests by name, plus clippy, `check` and `update-status`. The Linux leg also runs `release/manifests_test.py`, and the Studio job runs `update.test.mjs`. ## Checked | Where | Result | |---|---| | Linux build host, in `catthehacker/ubuntu:act-22.04` | 17 tests passed, clippy clean, `check` ok. The AppImage (81.66 MiB) runs and reports `appimage`. The deb runs and reports `deb`. The rpm is 7.24 MiB. | | macOS build host | 17 tests passed, clippy clean. The `.app` is ad-hoc signed with hardened runtime and passes `codesign --verify --deep --strict`; the bundled app's `check` passes and it reports `app`. The DMG is VALID under `hdiutil verify`. | | Windows host | 17 tests passed, clippy clean, `check` and `update-status` ok. The NSIS installer is 5.50 MiB. The portable copy reports no installer. `verify-update` refuses a forged signature. | | Studio | Browserless tests passed, including 1153 locale checks. `build-views --check` passed. `studio.test.mjs` passed 241/241, twice. | | Release scripts | `manifests_test.py`: 15 checks, including the cask parsing as Ruby. The site script's checks pass on 8 fixtures (2 accepted, 6 refused). | | Repository gates | Repository checks all ok. The breaking-change gate reports no unacknowledged break. Docs blocks pass. Every workflow parses. `wfsteps.py` found no problems. Name checks are clean. | **Not run:** - The Linux arm64 release leg. Its only runner is the CI VM, and this left it alone. - The macOS x86_64 app is built but not run, because the macOS host has no Rosetta. - No tag has been pushed, so no release has been made and no installer has been installed. - The Windows run used `webpki-root-certs` 1.0.9. It was bumped to 1.0.10 afterwards, which is 11 days old and the first release that fixes RUSTSEC-2026-0383. This PR's Windows leg is the first run with 1.0.10. ## For the owner (also in the desktop README) 1. Make the update key on a machine you trust: `cargo tauri signer generate -w ~/.tauri/wai-quantum-desktop.key`. Put the `.pub` line in `tauri.conf.json` as `plugins.updater.pubkey`, and commit it. 2. Set the forge secrets `WAI_QUANTUM_DESKTOP_UPDATER_KEY` (the `.key` file's text) and `WAI_QUANTUM_DESKTOP_UPDATER_KEY_PASSWORD`. 3. For macOS signing and notarisation: - set `APPLE_SIGNING_IDENTITY`; - set `APPLE_CERTIFICATE` and `APPLE_CERTIFICATE_PASSWORD` (a Developer ID `.p12`), unless the runner's keychain already holds the identity; - for notarisation, set either `APPLE_API_ISSUER`, `APPLE_API_KEY` and `APPLE_API_KEY_P8`, or `APPLE_ID`, `APPLE_PASSWORD` and `APPLE_TEAM_ID`. Windows Authenticode signing is not wired up. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A copy installed by its own installer (the macOS app, the AppImage, the
Debian or RPM package, the Windows installer) asks latest.json on the
pre-release wai-quantum-desktop-latest for a newer version as it opens.
The Studio offers it in a bar above the frame (src/update.js, its words
in English and Chinese); nothing installs until the person says so, and
then only an update whose signature verifies with the public key in
tauri.conf.json and names the version announced (requireSignedVersion),
after which the app restarts into it.

- The updater plugin 2.14.0 registered; commands update_check and
  update_install. A copy built from source or unpacked by a package
  manager does not update itself, and the check answers why
  (installed_by / updates_itself).
- verify-update FILE SIG VERSION: the same signature and version check
  from the command line, for the release to run on every update it signs.
- update-status: the installer this copy came from, as JSON, for the
  release to ask of each installer's copy.
- tauri.conf.json: the updater's one https endpoint, an empty public key
  until the owner makes the release key, update artifacts off unless a
  release turns them on, the macOS app signed ad hoc unless a Developer ID
  is given, and the publisher and homepage the packages carry.
- icons/icon.ico and icons/icon.icns, made from icons/icon.png by the
  bundler's icon command: a Windows build needs the .ico for its resource
  file (the app had not been built on Windows before), and the macOS app
  takes the .icns.
- Tests: the published test vectors held to the key and the signed
  version (tampered bytes, another key, another version, no version, no
  key, bad base64 all refused); which copies update themselves; the
  configuration's endpoint and flags; test/update.test.mjs for the bar.
- New crates in the lockfile at versions at least two weeks old, except
  webpki-root-certs at 1.0.10, the first release with RUSTSEC-2026-0383
  fixed.
- The Studio's README and its translation say what the bar does.
- The views page rebuilt (bash wai/wai-quantum-views/build.sh), since it
  is built from the Studio's English words and styles.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- .github/workflows/wai-quantum-desktop-release.yml, on a tag
  wai-quantum-desktop-v<version> equal to Cargo.toml's and
  tauri.conf.json's: a disk image for macOS (aarch64 and x86_64, with the
  GPU state vector); an AppImage, a Debian and an RPM package for Linux
  (x86_64 and arm64); an installer and a portable archive for Windows.
  Each build runs what it made (check, update-status, the code signature,
  the disk image's checksums, the AppImage, the Debian package unpacked),
  then attaches it with its SHA-256 over the private network.
- Signing is skipped, and the log says so, while its secrets are absent:
  WAI_QUANTUM_DESKTOP_UPDATER_KEY (and its password) signs updates, and
  the built app's verify-update checks every one before it is attached;
  the APPLE_* secrets sign and notarise the macOS app.
- release/manifests.py writes the Homebrew cask, the Scoop manifest and
  latest.json from the release's assets, from templates beside it, and
  the last job attaches them and puts latest.json on the pre-release the
  app asks unless that one names a newer version. Nothing is pushed to the
  tap or the bucket. release/manifests_test.py holds it to releases made
  up here.
- wai-quantum-studio.yml: the desktop app's tests, clippy, check and
  update-status on Linux (act-22.04 with the web view's libraries) and on
  Windows as well as macOS, the update tests required by name; the Linux
  leg runs the manifests test, and the Studio job test/update.test.mjs.
- The desktop README: the installers, which copies update themselves,
  the command that makes the update key and every secret's name. The
  changelog entry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A release whose updates are not signed wrote two blank lines where
`auto_updates true` would go. The test now holds every cask it writes to
no run of blank lines, and to how it ends, and parses it as Ruby where
ruby is installed (18 checks).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The views workflow rebuilds the page on its Linux image and compares
the engine inside it by bytes. The page from the previous commit was
built on macOS: its script and styles are the same bytes as the Linux
build's, its engine is not. Built now in catthehacker/ubuntu:act-22.04,
so the workflow's rebuild matches it in every part.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum-desktop: the update manifest served by the website, not a standing pre-release
All checks were successful
wai-quantum HIP member / member · HIP absent (linux-arm64) (pull_request) Successful in 25s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 25m52s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 7m8s
wai-quantum npu-cores member / member · npu-cores absent (linux-arm64) (pull_request) Successful in 25s
wai-quantum tensor-engine member / member · tensor engine absent (linux-arm64) (pull_request) Successful in 19s
wai-quantum portable tensor program / tensor program · CPU, mutants, GPU builds (pull_request) Successful in 1m4s
wai-quantum CPU vector units / member · vector unit, rvv 1024-bit (riscv64, emulated) (pull_request) Successful in 32s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m9s
wai-quantum CPU vector units / member · vector unit, sve 128-bit (aarch64, emulated) (pull_request) Successful in 44s
wai-quantum CPU vector units / member · vector unit, rvv 128-bit (riscv64, emulated) (pull_request) Successful in 14s
wai-quantum CPU vector units / member · vector unit, sve 2048-bit (aarch64, emulated) (pull_request) Successful in 10s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 5m26s
wai-quantum CPU vector units / member · vector unit, avx2 (x86_64 silicon) (pull_request) Successful in 21s
wai-quantum CPU vector units / member · vector unit, sve 512-bit (aarch64, emulated) (pull_request) Successful in 21s
wai-quantum signal processor / processor and member · signal processor, emulated (pull_request) Successful in 15m43s
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m28s
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (pull_request) Successful in 1m55s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 7m53s
wai-quantum views in agent hosts / views · the page and its engine (pull_request) Successful in 2m36s
wai-quantum tensor-engine member / member · tensor engine, the kernel toolchain's simulator (linux) (pull_request) Successful in 8m1s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 1m8s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 2m33s
wai-quantum views in agent hosts / views · a scripted host (pull_request) Successful in 3m40s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (pull_request) Successful in 21m55s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 4m9s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m54s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m53s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 6m59s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m43s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h1m21s
6073a83fdc
The app asked latest.json on a pre-release, wai-quantum-desktop-latest, which
each signed release replaced. That release would have sat in the repository's
release list beside the versioned ones. Now:

- the app asks https://wai.transaction.science/quantum-studio-desktop/latest.json;
- each signed release attaches its own latest.json and nothing else;
- the website takes it from that release when it is next deployed, with
  scripts/sync-desktop-update.sh <version> in its repository. That script
  refuses a manifest for another version, an update not on that release, a
  signature not made for that version, or a version older than the one the
  site serves.

manifests.py loses its pointer file and the check for a newer manifest, which
moves to the website's script. publish.sh loses RELEASE_PRERELEASE and
RELEASE_BODY, which only that release used.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!549
No description provided.