Skip to content

feat: add OctoSense updater with stable, RC and preview channels - #446

Merged
ymote merged 6 commits into
mainfrom
feat/in-app-updater
Oct 11, 2026
Merged

ymote merged 6 commits into
mainfrom
feat/in-app-updater

Conversation

@ymote

@ymote ymote commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

OctoSense currently requires users to find and install each host release themselves. Add an OctoSense Updates native module to desktop and Home, including a Home Settings entry. A release build can check its channel, download a verified package, and explicitly hand it to the operating system.

  • Stable, Release candidate and Preview channels; RC builds default to RC. Numeric SemVer ordering supports rc.9 → rc.10 → stable, with no channel-switch downgrade. Desktop and Home use separate product tags.
  • Public GitHub releases only, exact platform assets, matching GitHub digests and SHA256SUMS, private bounded downloads, cancellation, and revalidation before handoff. No account, model, App Hub capability or agent installation tool.
  • Desktop opens the platform installer with literal arguments. Handoff waits are bounded. Android independently checks APK package, signer, version code, minimum SDK and bytes, then requires Android confirmation. Permission changes never replay installation; cancellation and process replacement preserve durable status and app data.
  • Release packaging embeds the actual release tag, binds reuse to a binary hash receipt, and explicitly selects its native Cargo target/output directory so stale artifacts cannot acquire a new release identity. English/Chinese guides describe the flow and the OctosCode research.

Validation:

  • Full Mac checks passed for desktop default, desktop with mobile-apps, and Home with mobile-apps (run from phone/). These are compile checks, not Android installation tests.
  • Core: 33 tests and strict Clippy passed. UI/installer: 6 tests and strict Clippy passed. Home's targeted Settings navigation test passed.
  • Native app generation, source/locked graph checks, and both desktop/Home dependency-graph guards passed (host and Android, default/mobile-apps, platform exclusions, single kernel revision). Tooling: 149 tests passed. Android updater: 30 focused tests; all Home Java sources compiled against API 33 and 35. Desktop packaging: 24 passed, one Linux-only squashfs check skipped on macOS; release workflow tests: 37 passed; Home build tests: 28 passed.
  • Hidden Mac instrument: actual shell opens Updates and finds RC2 from an RC1 test identity. The isolated native view downloaded/verified the real 297 MB RC2 DMG, rejected a deliberately altered file before opening an installer, switched channels and cancelled a download. Core separately downloaded/verified the released Home beta2 APK.
  • Owned API 35 emulator: 25/25 checks using the actual Java updater classes and Android PackageInstaller. Tested permission/no replay, invalid APK rejection, visible confirmation/cancel/retry, actual upgrade with retained UID and synthetic data, and status recovery. Fixture removed and emulator stopped. Physical devices and personal installations were untouched.

Limits: the emulator fixture does not include the full Rust Home UI. Actual installation from a released Home build and macOS/Windows/Linux app replacement remain unverified. This first version uses explicit checks and native installer handoff; macOS DMG and Linux AppImage replacement still require user steps. Existing desktop RC2/Home beta2 do not contain this updater, so users need one manual installation of a future release. Home release packaging also needs the API 33 minimum fixed by #431. This PR does not publish a release or merge other work.

ymote and others added 3 commits October 10, 2026 23:22
From the review of #446:

- A desktop release is offered only when its SHA256SUMS carries GitHub's
  signature from the new sign-desktop-release workflow, run on main in the
  desktop-release-signing environment, whose reviewers approve each run:
  the model App Hub uses for its catalog. GitHub's digests and SHA256SUMS
  alone only prove the files are the release's own; anyone who can publish
  a release can make them agree, and updater downloads skip the warnings
  macOS and Windows give a browser download. signature.rs verifies the
  Sigstore bundle offline (certificate, timestamps, transparency log,
  GitHub's claims, the subject <tag>.SHA256SUMS with its exact digest),
  with the same pinned Sigstore crates as App Hub. Android Home keeps its
  pinned APK signer instead.
- A release page may hold 100 releases of up to 160 KiB each. At 2 MiB a
  page, every check failed once the repository had about 73 releases (28
  KiB each today), and installed updaters could not have fetched a fix.
  The ten-page limit now stops reading instead of failing.
- Stale downloads are removed when the shell starts and before each check:
  verified ones a day after their last write, interrupted ones after an
  hour; only the updater's own pending-/verified- folders of plain files,
  never recursively.
- Android's Install and Allow buttons now read their clicks while the tap
  is being handled (capture_actions, press and release), as the host's
  other approval sheets do. Read from Event::Actions, trusted_user_input()
  was always false, so both buttons always refused.
- Linux packages are handed over from the Downloads folder: a snap package
  handler (Ubuntu's App Center) cannot read ~/.octosense, and an AppImage's
  owner must find it. The copy never replaces a file and is checked against
  the release digest; an AppImage copy is executable.
- docs/updater.md (en/zh): the signature, the corrected statement on OS
  download checks, cleanup, Linux handoff, what is verified. desktop/README
  (en/zh): the signing step before publishing. ci_local: the signing
  workflow is a release workflow it does not run.

Tested: cargo test --locked -p octosense-updater -p octosense-updater-ui
(43 + 8 pass); clippy -D warnings on both; cargo check --example updates;
python3 tools/ci_local.py --check-drift and tools/test_ci_local.py; the
workflow's release check, run read-only against the published
desktop-v0.1.0-rc.5 and rc.2 (pass) and beta.2 (refused: no SHA256SUMS).
Unverified: a signed release end to end (none is signed yet), Android's
tap check on a device, the Linux handoff on a Linux desktop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
App Hub #210 adds `updater` to the contract's RESERVED_NAMES, so no store
app may take the native Updates window's id or namespace (ADR 0004 §3).
The shell test a_script_app_may_not_take_a_native_apps_id_or_namespace,
which failed in phone.yml / home on this branch, checks that list against
native-apps.json.

The four App Hub crates, the [patch.crates-io] contract line and
native-apps.json's App Hub source; in Cargo.lock only the five App Hub
source lines. Between d86101d7 and 938e85e7 App Hub only published catalog
16 (data) and made that change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ymote

ymote commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Review fixes

At the maintainer's request I reviewed this PR and pushed fixes onto it as fast-forward commits on top of fe67813a; nothing was force-pushed. Commits: a merge of main, 9fd43006 (the fixes), efbec29f (App Hub repin), and another merge of main.

1. Desktop releases need GitHub's signature. GitHub's asset digests and SHA256SUMS only prove the files are the release's own: anyone who can publish a release can make them agree. Updater downloads also skip the Gatekeeper and SmartScreen prompts a browser download gets, and current releases have no Developer ID or Authenticode signature. A desktop release is now offered only when its SHA256SUMS carries GitHub's signature, as App Hub's catalog does:

  • New workflow .github/workflows/sign-desktop-release.yml, run on main in the new protected desktop-release-signing environment (same reviewers as App Hub's catalog, no admin bypass, main only). It checks that SHA256SUMS lists exactly the release's files with their GitHub digests, has actions/attest sign it as <tag>.SHA256SUMS, and attaches the bundle as SHA256SUMS.sigstore.json.
  • crates/updater/src/signature.rs verifies that bundle offline with App Hub's exact Sigstore crates: certificate, SCT, transparency log, all eleven GitHub claims of the run, and the subject and predicate.
  • Home keeps its pinned APK signer.
  • desktop/README.md gains the signing step before publishing a draft. tools/ci_local.py lists the workflow in NOT_LOCAL.

2. Release list size. At 2 MiB a page, every check would have failed after about 73 releases (28 KiB each today), and installed updaters could not have fetched a fix. A page may now hold 100 releases of up to 160 KiB each, and the ten-page limit stops reading instead of failing.

3. Downloads are cleaned up. Verified downloads are removed 24 hours after their last write, interrupted ones after an hour. This runs at startup and before each check, touches only the updater's own pending-/verified- folders of plain files, and never deletes recursively.

4. Android Install and Allow buttons never worked. They read trusted_user_input() while handling Event::Actions, which makepad dispatches after the input's trust scope ends, so the value was always false. They now capture their actions while the tap is being handled and require a trusted press and release, like connected_review.rs. (makepad has input provenance only on Android and macOS, so desktop can't require it.)

5. Linux packages are handed over from Downloads. Ubuntu's App Center is a snap and can't read ~/.octosense. The copy goes to XDG_DOWNLOAD_DIR, never replaces a file, is checked against the release digest, and an AppImage copy is executable.

6. The failing Home check. App Hub #210 (merged) reserves updater, and the branch now pins App Hub 938e85e7.

Tested:

  • cargo test --locked -p octosense-updater -p octosense-updater-ui: 43 + 8 pass. Clippy -D warnings is clean on both crates, and --example updates builds.
  • tools/ci_local.py --check-drift and tools/test_ci_local.py pass.
  • The workflow's release check ran read-only against published releases: desktop-v0.1.0-rc.5 and rc.2 pass, and beta.2 is refused (no SHA256SUMS).
  • Local CI on 1e20dbc7, both runs green: --only all --linux-host --offload PASSED in 26m50s, skipping only rom's JDK, SDK and Node steps on the Linux host, and a Mac-only --only rom with JDK 17, the Android SDK and Node 24 PASSED with nothing skipped. This includes phone.yml / home, which failed on fe67813a, and the updater steps in apps.yml and desktop.yml.
  • Unverified: a signed release end to end (nothing is signed yet; the first release signed with the workflow is that test), Android's tap check on a device, and the Linux handoff on a Linux desktop.

Since this adds a workflow that ci-local cannot run, it merges only on a fully green GitHub check list.

@ymote

ymote commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Local CI passed on 5f8e621 (tools/ci-local.sh --only all, 48m49s, Darwin arm64).

GitHub would run for this PR: desktop.yml, phone.yml, apps.yml, rom.yml. Merged on this local pass (macOS runner queue); GitHub CI runs on the merge commit on main.

Job Step Result Time
ci-local / drift The local mapping fits the workflows PASS -
desktop.yml / desktop Prepare the pinned framework sources PASS 1s
desktop.yml / desktop Compile the desktop with the shared runtime PASS 7m12s
desktop.yml / desktop One copy of every shell source PASS -
desktop.yml / desktop Desktop tools and scripts PASS 30s
desktop.yml / desktop In-app updater and installer handoff PASS 21s
desktop.yml / desktop One Makepad, App Hub, octos and Rinx in the locked graph PASS 26s
desktop.yml / desktop The native apps' generated places match native-apps.json PASS 0s
desktop.yml / native-host-api Prepare the pinned framework sources PASS 2s
desktop.yml / native-host-api Build the native acceptance host and pinned Hub CLI PASS 49s
desktop.yml / native-host-api Signed app tool calls a real OS permission API PASS 3s
phone.yml / home Prepare pinned sources and the reviewed runtime patch PASS 1s
phone.yml / home Validate launcher icons and Apple asset catalogs PASS 5s
phone.yml / home Compile Home and its bundled modules PASS 6m24s
phone.yml / home Verify the single runtime graph PASS 3s
phone.yml / home The native apps' generated places match native-apps.json PASS 0s
phone.yml / home The pinned octos revision PASS 0s
phone.yml / home Build octos (unless cached) PASS -
phone.yml / home Build Home, App Hub admission and runtime policy tests PASS 4m20s
phone.yml / home Test Home, App Hub admission and runtime policy PASS 1m44s
phone.yml / home Two-lane scenario (real kernel, scripted model) PASS 30s
phone.yml / home Build opt-in AppCard tests PASS 3m13s
phone.yml / home Test opt-in AppCard PASS 7s
phone.yml / home Build craft-engine relay and system-chat tests PASS 8m04s
phone.yml / home Test craft-engine relay and system chat PASS 55s
phone.yml / home Build linked Terminal tests PASS 1m41s
phone.yml / home Test linked Terminal PASS 5s
phone.yml / home Build Wasm and runtime policy tests PASS 1m25s
phone.yml / home Test Wasm and runtime policy PASS 19s
apps.yml / services python3 tools/setup.py PASS 1s
apps.yml / services Shared system app interface PASS 0s
apps.yml / services Kernel service PASS 25s
apps.yml / services OctoSense release updater PASS 5s
apps.yml / services App peers PASS 25s
apps.yml / services AI providers config PASS 11s
apps.yml / services Connected provider protocols and Linux host adapter PASS 27s
apps.yml / services L0 in-card chat (sys.chat, host side) PASS 10s
apps.yml / services System toolbox (workflow templates) PASS 1m59s
apps.yml / kernel-security python3 tools/setup.py PASS 1s
apps.yml / kernel-security The pinned octos revision PASS 0s
apps.yml / kernel-security Build octos (unless cached) PASS -
apps.yml / kernel-security Real-kernel tests PASS 28s
apps.yml / apps python3 tools/setup.py PASS 0s
apps.yml / apps Host services (Mail, AI providers, News) PASS 1m15s
apps.yml / apps Connected accounts and Rinx editor PASS 1m08s
apps.yml / apps Shell AI services (crates/ai-host) PASS 2m05s
apps.yml / apps AppCard runtime setup safeguards PASS 0s
apps.yml / apps AppCard clippy (own crates; shared runtime compiles) PASS 40s
apps.yml / apps AppCard transport and store PASS 15s
apps.yml / apps One Makepad, App Hub, octos and Rinx in the locked graph PASS 4s
rom.yml / product python3 -m unittest discover -s tests -v PASS 38s
rom.yml / product Check generated Agent Binder client PASS 0s
rom.yml / product bash -n scripts/stage-forks.sh scripts/publish-release.sh scripts/apply-to-tree.sh scripts/build-rom.sh PASS -
rom.yml / web-installer npm ci --ignore-scripts --no-audit --no-fund PASS 5s
rom.yml / web-installer npm test PASS 1s
rom.yml / web-installer npx playwright install --with-deps chromium PASS 1s
rom.yml / web-installer npm run test:browser PASS 4s
rom.yml / web-installer python3 -m unittest discover -s tests -p test_installer_manifest.py -v PASS 0s
rom.yml / web-installer bash -n scripts/release.sh PASS 0s

Notes:

  • GitHub runs Python 3.12; this run used Python 3.9.6
  • apps.yml:services runs on ubuntu-latest in GitHub; here on macOS, so #[cfg(target_os = "linux")] code in it is not exercised (--linux-host runs it on Linux)
  • apps.yml:kernel-security runs on ubuntu-latest in GitHub; here on macOS, so #[cfg(target_os = "linux")] code in it is not exercised (--linux-host runs it on Linux)

Native GitHub acceptance passed on the same head: embedded-browser.yml.

@ymote
ymote merged commit f9f6fea into main Oct 11, 2026
11 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant