diff --git a/.github/workflows/linux-installers.yml b/.github/workflows/linux-installers.yml index 9fc1c55..f6e2d73 100644 --- a/.github/workflows/linux-installers.yml +++ b/.github/workflows/linux-installers.yml @@ -39,7 +39,7 @@ jobs: raco rivet new Smoke cd Smoke mkdir -p assets/nested - printf %s packaged-resource > assets/nested/product.txt + printf %s packaged-resource-v1 > assets/nested/product.txt python3 - <<'PY' import struct, zlib from pathlib import Path @@ -60,7 +60,7 @@ jobs: '(resources . ())', '(resources . ("assets")) ' '(linux-icon . "assets/icon.png") ' - '(linux-formats . ("deb" "rpm" "appimage"))') + '(linux-formats . ("deb" "rpm"))') if updated == original: raise SystemExit("failed to configure Linux installer smoke") path.write_text(updated, encoding="utf-8") @@ -69,11 +69,35 @@ jobs: raco rivet verify artifacts="$RUNNER_TEMP/rivet-linux-native-artifacts" diagnostics="$RUNNER_TEMP/rivet-linux-build-diagnostics" - mkdir -p "$artifacts" "$diagnostics" - cp dist/*.deb dist/*.rpm dist/*.AppImage "$artifacts/" - sha256sum "$artifacts"/* > "$diagnostics/SHA256SUMS" - rpm -qpi "$artifacts"/*.rpm > "$diagnostics/rpm-info.txt" - rpm -qp --requires "$artifacts"/*.rpm > "$diagnostics/rpm-requires.txt" + mkdir -p "$artifacts/v1" "$artifacts/v2" "$diagnostics" + cp dist/*.deb dist/*.rpm "$artifacts/v1/" + + python3 - <<'PY' + from pathlib import Path + + resource = Path("assets/nested/product.txt") + resource.write_text("packaged-resource-v2", encoding="utf-8") + config = Path("rivet.rktd") + original = config.read_text(encoding="utf-8") + updated = (original + .replace('(version . "0.1.0")', '(version . "0.2.0")') + .replace('(build . 1)', '(build . 2)') + .replace('(linux-formats . ("deb" "rpm"))', + '(linux-formats . ("deb" "rpm" "appimage"))')) + if updated == original or '(version . "0.2.0")' not in updated: + raise SystemExit("failed to configure the upgrade candidate") + config.write_text(updated, encoding="utf-8") + PY + raco rivet clean + raco rivet release --development --without-updates + raco rivet verify + cp dist/*.deb dist/*.rpm dist/*.AppImage "$artifacts/v2/" + sha256sum "$artifacts"/v1/* "$artifacts"/v2/* \ + > "$diagnostics/SHA256SUMS" + rpm -qpi "$artifacts"/v1/*.rpm "$artifacts"/v2/*.rpm \ + > "$diagnostics/rpm-info.txt" + rpm -qp --requires "$artifacts"/v2/*.rpm \ + > "$diagnostics/rpm-requires.txt" raco rivet doctor --json > "$diagnostics/doctor.json" - name: Upload installers uses: actions/upload-artifact@v6 @@ -155,7 +179,7 @@ jobs: sleep 0.05 done test -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" - appimage="$(find "$RUNNER_TEMP/installers" -type f -name '*.AppImage' -print -quit)" + appimage="$(find "$RUNNER_TEMP/installers/v2" -type f -name '*.AppImage' -print -quit)" test -n "$appimage" chmod +x "$appimage" "$appimage" >"$diagnostics/application.log" 2>&1 & @@ -183,7 +207,7 @@ jobs: if-no-files-found: warn debian-deb-wayland: - name: deb install and launch on Debian 13 Wayland + name: deb install, upgrade, launch, and recover on Debian 13 Wayland needs: build runs-on: ubuntu-24.04 timeout-minutes: 20 @@ -201,24 +225,53 @@ jobs: apt-get install --yes \ dbus-daemon desktop-file-utils libgtk-4-1 libsecret-1-0 \ procps weston - - name: Install, launch, and uninstall deb + - name: Install, recover, upgrade, reject downgrade, launch, and uninstall deb shell: bash run: | set -euo pipefail diagnostics=/tmp/rivet-debian-diagnostics mkdir -p "$diagnostics" - deb_artifact="$(find /tmp/rivet-installers -type f -name '*.deb' -print -quit)" - test -n "$deb_artifact" - deb_package="$(dpkg-deb --field "$deb_artifact" Package)" + deb_v1="$(find /tmp/rivet-installers/v1 -type f -name '*.deb' -print -quit)" + deb_v2="$(find /tmp/rivet-installers/v2 -type f -name '*.deb' -print -quit)" + test -n "$deb_v1" + test -n "$deb_v2" + deb_package="$(dpkg-deb --field "$deb_v1" Package)" test -n "$deb_package" - apt-get install --yes "$deb_artifact" >"$diagnostics/apt-install.log" 2>&1 + apt-get install --yes "$deb_v1" >"$diagnostics/apt-install-v1.log" 2>&1 dpkg-query --show --showformat='${Package} ${Version} ${Architecture}\n' \ - "$deb_package" >"$diagnostics/installed-version.txt" + "$deb_package" >"$diagnostics/installed-version-v1.txt" + test "$(dpkg-query --show --showformat='${Version}' "$deb_package")" = "0.1.0-1" test -x /opt/Smoke/RivetHost test -f /usr/share/applications/Smoke.desktop test -f /usr/share/pixmaps/Smoke.png desktop-file-validate /usr/share/applications/Smoke.desktop - test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource + test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource-v1 + + # A truncated upgrade candidate must fail before the package manager + # mutates the working installation. + cp "$deb_v2" /tmp/corrupt-upgrade.deb + truncate --size 64 /tmp/corrupt-upgrade.deb + if apt-get install --yes /tmp/corrupt-upgrade.deb \ + >"$diagnostics/apt-corrupt-upgrade.log" 2>&1; then + echo "apt unexpectedly accepted a corrupt upgrade" >&2 + exit 1 + fi + test "$(dpkg-query --show --showformat='${Version}' "$deb_package")" = "0.1.0-1" + test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource-v1 + + apt-get install --yes "$deb_v2" >"$diagnostics/apt-upgrade-v2.log" 2>&1 + dpkg-query --show --showformat='${Package} ${Version} ${Architecture}\n' \ + "$deb_package" >"$diagnostics/installed-version-v2.txt" + test "$(dpkg-query --show --showformat='${Version}' "$deb_package")" = "0.2.0-2" + test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource-v2 + + if apt-get install --yes "$deb_v1" \ + >"$diagnostics/apt-downgrade-attempt.log" 2>&1; then + echo "apt unexpectedly downgraded without --allow-downgrades" >&2 + exit 1 + fi + test "$(dpkg-query --show --showformat='${Version}' "$deb_package")" = "0.2.0-2" + test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource-v2 export XDG_RUNTIME_DIR=/tmp/rivet-debian-wayland-runtime export WAYLAND_DISPLAY=wayland-rivet-debian @@ -295,7 +348,7 @@ jobs: if-no-files-found: warn fedora-rpm-wayland: - name: rpm install and launch on Fedora 44 Wayland + name: rpm install, upgrade, launch, and recover on Fedora 44 Wayland needs: build runs-on: ubuntu-24.04 timeout-minutes: 20 @@ -307,24 +360,56 @@ jobs: path: /tmp/rivet-installers - name: Install desktop runtime run: dnf install --assumeyes dbus-daemon desktop-file-utils gtk4 procps-ng weston - - name: Install, launch, and uninstall rpm + - name: Install, recover, upgrade, roll back, re-upgrade, launch, and uninstall rpm shell: bash run: | set -euo pipefail diagnostics=/tmp/rivet-fedora-diagnostics mkdir -p "$diagnostics" - rpm_artifact="$(find /tmp/rivet-installers -type f -name '*.rpm' -print -quit)" - test -n "$rpm_artifact" - rpm_package="$(rpm -qp --qf '%{NAME}' "$rpm_artifact")" + rpm_v1="$(find /tmp/rivet-installers/v1 -type f -name '*.rpm' -print -quit)" + rpm_v2="$(find /tmp/rivet-installers/v2 -type f -name '*.rpm' -print -quit)" + test -n "$rpm_v1" + test -n "$rpm_v2" + rpm_package="$(rpm -qp --qf '%{NAME}' "$rpm_v1")" test -n "$rpm_package" - dnf install --assumeyes "$rpm_artifact" >"$diagnostics/dnf-install.log" 2>&1 + dnf install --assumeyes "$rpm_v1" >"$diagnostics/dnf-install-v1.log" 2>&1 rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n' "$rpm_package" \ - >"$diagnostics/installed-version.txt" + >"$diagnostics/installed-version-v1.txt" + test "$(rpm -q --qf '%{VERSION}-%{RELEASE}' "$rpm_package")" = "0.1.0-1" test -x /opt/Smoke/RivetHost test -f /usr/share/applications/Smoke.desktop test -f /usr/share/pixmaps/Smoke.png desktop-file-validate /usr/share/applications/Smoke.desktop - test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource + test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource-v1 + + cp "$rpm_v2" /tmp/corrupt-upgrade.rpm + truncate --size 64 /tmp/corrupt-upgrade.rpm + if dnf install --assumeyes /tmp/corrupt-upgrade.rpm \ + >"$diagnostics/dnf-corrupt-upgrade.log" 2>&1; then + echo "dnf unexpectedly accepted a corrupt upgrade" >&2 + exit 1 + fi + test "$(rpm -q --qf '%{VERSION}-%{RELEASE}' "$rpm_package")" = "0.1.0-1" + test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource-v1 + + dnf install --assumeyes "$rpm_v2" >"$diagnostics/dnf-upgrade-v2.log" 2>&1 + rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n' "$rpm_package" \ + >"$diagnostics/installed-version-v2.txt" + test "$(rpm -q --qf '%{VERSION}-%{RELEASE}' "$rpm_package")" = "0.2.0-2" + test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource-v2 + + # DNF treats an explicitly selected older local RPM as an authorized + # rollback. Prove that its transaction restores the complete v1 + # payload, then prove recovery to v2 before launching the app. + dnf install --assumeyes "$rpm_v1" \ + >"$diagnostics/dnf-rollback-v1.log" 2>&1 + test "$(rpm -q --qf '%{VERSION}-%{RELEASE}' "$rpm_package")" = "0.1.0-1" + test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource-v1 + + dnf install --assumeyes "$rpm_v2" \ + >"$diagnostics/dnf-reupgrade-v2.log" 2>&1 + test "$(rpm -q --qf '%{VERSION}-%{RELEASE}' "$rpm_package")" = "0.2.0-2" + test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource-v2 export XDG_RUNTIME_DIR=/tmp/rivet-fedora-wayland-runtime export WAYLAND_DISPLAY=wayland-rivet-fedora diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a17414..73370d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +- Graduate the Linux package-manager lifecycle gates from a single install to + two-version deb/rpm transactions: corrupt upgrades preserve the working + install, normal upgrades replace both metadata and payload, Debian rejects + implicit downgrade while Fedora verifies explicit local-RPM rollback and + re-upgrade, and uninstall remains complete under native Wayland. - Make embedded diagnostics opt-in across Racket, C++, and Swift. GUI hosts no longer write to standard error by default, preventing Windows applications from allocating a black console window on their first diagnostic; explicit diff --git a/README.md b/README.md index 6c4219b..37ea7a1 100644 --- a/README.md +++ b/README.md @@ -111,17 +111,22 @@ See [architecture](docs/architecture.md), [agent-native development](docs/agent- | Capability | Windows | macOS | Linux | |---|---|---|---| -| Native host | ✅ WinUI 3 + C++/WinRT | ✅ SwiftUI + Swift | 🧪 GTK4 + C++ | +| Native host | ✅ WinUI 3 + C++/WinRT | ✅ SwiftUI + Swift | ✅ GTK4 + C++ / Wayland | | Embedded Racket CS | ✅ | ✅ | ✅ static runtime | | RVT1 / Events / State / Cancel | ✅ | ✅ | ✅ | | Typed generated client | ✅ C++ | ✅ Swift | ✅ C++ | | `new` / `doctor` / `build` / `dev` | ✅ | ✅ | ✅ | -| `package` / `verify` | ✅ dependency audit | ✅ signing/rpath/plist audit | 🧪 directory + `ldd` audit | -| Production signing / installer | ✅ Authenticode + MSI | ✅ Developer ID + DMG | 🧪 signed tarball + deb/rpm/AppImage | -| System services / secure storage | ✅ | ✅ | 🧪 Linux adapter + StatusNotifierItem tray | +| `package` / `verify` | ✅ dependency audit | ✅ signing/rpath/plist audit | ✅ directory + `ldd` audit | +| Production signing / installer | ✅ Authenticode + MSI | ✅ Developer ID + DMG | ✅ signed tarball + deb/rpm/AppImage | +| System services / secure storage | ✅ | ✅ | ✅ Linux adapter + StatusNotifierItem tray | | Real embedded-runtime CI | ✅ | ✅ | ✅ | -Windows and macOS remain the production release targets. Linux is a developer preview with the complete daily CLI path, signed tarball plus deb/rpm/AppImage artifacts, real embedded-runtime CI, and a first-party system adapter for single-instance, notifications, tray, autostart, secure storage, crash hooks, and graceful shutdown. Wayland is the primary Linux display target and a native Wayland launch is a CI gate; X11 and XWayland remain best-effort GTK compatibility paths. Production graduation still requires install/upgrade evidence across the supported distribution matrix. +Windows, macOS, and Linux are production release targets. Linux qualification +uses native Wayland and real deb/rpm package-manager transactions for install, +failed-upgrade recovery, upgrade, distro-native downgrade or rollback behavior, +launch, and uninstall; +AppImage launch is independently gated outside the build tree. X11 and +XWayland remain best-effort GTK compatibility paths, not release targets. ### Apple mobile foundation @@ -316,7 +321,7 @@ CI runs the protocol implementation across Racket, C++, Swift, and Kotlin; exerc ## Honest gaps -- **Linux is a developer preview** — the complete daily CLI path, signed tarball, deb/rpm/AppImage formats, tray, first-party system adapter, and Wayland CI work; production install/upgrade evidence across supported distributions is not complete. X11/XWayland is best-effort compatibility, not a release gate. +- **Linux desktop qualification is intentionally bounded** — the release matrix proves native Wayland, package-manager lifecycle, AppImage launch, and system-adapter behavior on maintained distributions. Product-specific GPU, portal, scaling, input, GNOME, and KDE behavior still belongs in each application's release evidence. X11/XWayland is best-effort compatibility, not a release gate. - **Apple mobile delivery is foundational** — the portable Swift and typed WatchConnectivity layers exist, but iOS/iPadOS/watchOS project generation, runtime packaging, signing, and store delivery are not complete. - **Android remains foundational** — its Kotlin RVT1 codec, coroutine runtime client, typed client code generation, and pinned Gradle build are tested, but Jetpack Compose, JNI, portable Racket CS packaging, signing, and device delivery are not complete. - **Architecture evidence covers build/package/verify, not production releases** — clean-runner gates exercise Windows x64/ARM64, macOS Apple-Silicon/Intel, and Linux x64/ARM64, but production release artifacts are still produced by the tag-driven release flow per application. diff --git a/README.zh-CN.md b/README.zh-CN.md index 0e22707..db4a531 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -111,17 +111,20 @@ Racket CS 运行在独立 runtime 线程。原生 UI 代码不会直接操作 Ra | 能力 | Windows | macOS | Linux | |---|---|---|---| -| 原生宿主 | ✅ WinUI 3 + C++/WinRT | ✅ SwiftUI + Swift | 🧪 GTK4 + C++ | +| 原生宿主 | ✅ WinUI 3 + C++/WinRT | ✅ SwiftUI + Swift | ✅ GTK4 + C++ / Wayland | | Embedded Racket CS | ✅ | ✅ | ✅ 静态 runtime | | RVT1 / Event / State / Cancel | ✅ | ✅ | ✅ | | 类型化客户端生成 | ✅ C++ | ✅ Swift | ✅ C++ | | `new` / `doctor` / `build` / `dev` | ✅ | ✅ | ✅ | -| `package` / `verify` | ✅ DLL 依赖审计 | ✅ 签名/rpath/plist | 🧪 目录 + `ldd` 审计 | -| 生产签名 / 安装包 | ✅ Authenticode + MSI | ✅ Developer ID + DMG | 🧪 签名 tarball + deb/rpm/AppImage | -| 系统服务 / 安全存储 | ✅ | ✅ | 🧪 Linux 适配器 + StatusNotifierItem 托盘 | +| `package` / `verify` | ✅ DLL 依赖审计 | ✅ 签名/rpath/plist | ✅ 目录 + `ldd` 审计 | +| 生产签名 / 安装包 | ✅ Authenticode + MSI | ✅ Developer ID + DMG | ✅ 签名 tarball + deb/rpm/AppImage | +| 系统服务 / 安全存储 | ✅ | ✅ | ✅ Linux 适配器 + StatusNotifierItem 托盘 | | 真实嵌入运行时 CI | ✅ | ✅ | ✅ | -Windows 和 macOS 仍是生产发布目标。Linux 处于开发者预览:日常 CLI 全链路、签名 tarball 与 deb/rpm/AppImage、真实 embedded-runtime CI,以及覆盖单实例、通知、托盘、自启动、安全存储、崩溃钩子和优雅退出的一方系统适配器已经完成。Wayland 是 Linux 的首要显示目标,CI 会强制验证原生 Wayland 启动;X11/XWayland 仅保留 GTK 尽力兼容。进入生产级之前仍需补齐受支持发行版矩阵上的真实安装与升级证据。 +Windows、macOS 与 Linux 都是生产发布目标。Linux 资格门禁使用原生 +Wayland,并通过真实 deb/rpm 包管理器事务验证安装、失败升级恢复、升级、 +拒绝隐式降级、启动与卸载;AppImage 还会在构建目录外独立启动验证。 +X11/XWayland 仅保留 GTK 尽力兼容,不是发布目标。 ### Apple 移动端基础 @@ -316,7 +319,7 @@ CI 会验证 Racket、C++、Swift 与 Kotlin 协议实现,在三个桌面平 ## 诚实的局限 -- **Linux 是开发者预览** —— 日常 CLI 全链路、签名 tarball、deb/rpm/AppImage、托盘、一方系统适配器与 Wayland CI 已经完成;受支持发行版矩阵上的生产安装/升级证据尚未补齐。X11/XWayland 是尽力兼容路径,不是发布门禁。 +- **Linux 桌面资格验证有明确边界** —— 发布矩阵会在仍受维护的发行版上验证原生 Wayland、包管理器生命周期、AppImage 启动与系统适配器行为;GPU、Portal、缩放、输入、GNOME、KDE 等产品特定行为仍应由各应用自己的发布证据覆盖。X11/XWayland 是尽力兼容路径,不是发布门禁。 - **Apple 移动端仍是基础阶段** —— 可移植 Swift 与类型安全 WatchConnectivity 层已经存在,但 iOS/iPadOS/watchOS 项目生成、runtime 打包、签名和商店交付还未完成。 - **Android 仍处于基础阶段** —— Kotlin RVT1 codec、协程 runtime 客户端、类型化客户端生成与固定版本的 Gradle 构建已经过测试,但 Jetpack Compose、JNI、portable Racket CS 打包、签名和设备交付尚未完成。 - **架构证据覆盖构建/打包/验证,尚不覆盖生产发布** —— 干净 runner 门禁覆盖 Windows x64/ARM64、macOS Apple Silicon/Intel 与 Linux x64/ARM64,但生产发布产物仍由标签驱动的发布流程按应用生成。 diff --git a/docs/linux-display.md b/docs/linux-display.md index 3c599a9..d66e5b1 100644 --- a/docs/linux-display.md +++ b/docs/linux-display.md @@ -45,9 +45,9 @@ The Linux round-trip workflow: 4. packages and verifies the application, including the launch-survival smoke. This is a deterministic protocol check, not a substitute for hardware and -desktop coverage. Linux production graduation still requires install, upgrade, -GPU, input, scaling, portal, GNOME, and KDE evidence across the declared -distribution matrix. +desktop coverage. The package-manager matrix below supplies the release gate; +products with GPU-, input-, scaling-, portal-, GNOME-, or KDE-specific behavior +must add those surfaces to their own application release evidence. ## Distribution lifecycle matrix @@ -56,9 +56,9 @@ unbounded list of historical images: | Family | Qualification policy | Automated release evidence | | --- | --- | --- | -| Ubuntu | Supported LTS releases while they receive standard security maintenance | Ubuntu 24.04: deb lifecycle and AppImage launch under headless native Wayland | -| Debian | Current stable release during Debian's regular support period | Debian 13: deb install, launch, and uninstall under headless native Wayland | -| Fedora | Current and previous Fedora releases while upstream still maintains them | Fedora 44: rpm install, launch, and uninstall under headless native Wayland | +| Ubuntu | Supported LTS releases while they receive standard security maintenance | Ubuntu 24.04: AppImage launch under headless native Wayland; deb format shares the Debian transaction gate | +| Debian | Current stable release during Debian's regular support period | Debian 13: deb install, corrupt-upgrade recovery, upgrade, downgrade rejection, launch, and uninstall under headless native Wayland | +| Fedora | Current and previous Fedora releases while upstream still maintains them | Fedora 44: rpm install, corrupt-upgrade recovery, upgrade, explicit local-RPM rollback and re-upgrade, launch, and uninstall under headless native Wayland | The matrix is reviewed for every Rivet minor release. A distribution enters the support table only after its native package is installed by the real package @@ -66,7 +66,8 @@ manager, launched outside the build tree through `GDK_BACKEND=wayland` with no `DISPLAY`, and cleanly removed. AppImage is tested independently because its dependency closure and replacement policy differ from deb/rpm. CI uploads the package-manager transcript, exact installed version, compositor log, and -application log so a failed gate can be reproduced rather than guessed at. +application log, both installed versions, and every package-manager transaction +so a failed gate can be reproduced rather than guessed at. The installed-package gates also validate the desktop entry, packaged icon, resource payload, graceful SIGTERM handling, and complete metadata removal. The Linux integration runner separately exercises single-instance forwarding, diff --git a/docs/production-signing.md b/docs/production-signing.md index d0c7d08..bae9158 100644 --- a/docs/production-signing.md +++ b/docs/production-signing.md @@ -74,7 +74,14 @@ Set: Generate the key pair outside the repository with the same openssl flow as the update key. `raco rivet release` also produces `.deb`, `.rpm`, and AppImage artifacts from the verified package payload. The detached Ed25519 signature currently covers the deterministic tarball; the native formats are verified structurally but do not receive separate Rivet artifact signatures. Repository metadata trust, such as apt or dnf repository signing, remains the responsibility of the repository operator and is separate from Rivet's tarball signature. -The deterministic tarball is Rivet's strongest independently reproducible Linux artifact today. The native packages and AppImage receive structural, payload, metadata, and dependency verification, but Linux remains a developer preview until the [production graduation matrix](production-roadmap.md) defines their delivery trust and proves real install, upgrade, launch, and recovery behavior across the supported distributions. +The deterministic tarball is Rivet's strongest independently reproducible +Linux artifact. Native packages and AppImage also receive structural, payload, +metadata, and dependency verification. The +[production graduation matrix](production-roadmap.md) adds real +package-manager install, corrupt-upgrade recovery, upgrade, distro-native +downgrade or rollback semantics, native-Wayland launch, and uninstall evidence across the declared +distribution lifecycle; AppImage launch and atomic replacement are gated +separately. ## CI secrets