Repository navigation
feat(desktop): signed Windows NSIS updates on the main channel - #6854
JasonBuildAI wants to merge 20 commits into
Conversation
Real-machine receipt: Windows main-channel signed auto-updateExecuted on the current head Positive journey (launch auto-update)
Positive journey (manual panel)
Negative cases
Fix discovered during the journey (included in this head)The packaged Windows App initially never left State
|
|
Follow-up: launch-time auto-update re-verified directly on the fixed build. The main receipt above was completed before the navigation-fence fix landed in the head, so I re-ran the launch-path case on a build equivalent to
So the full launch journey - auto-check, signature-verified download, passive install, automatic restart, post-restart re-check - is directly observed on Windows, matching the macOS semantics. |
ff68b24 to
f2653e4
Compare
|
Review hardening landed on A review pass over the delivered journey surfaced non-blocking hardening items; they are now committed and validated on the new head. Feed / workflow (
Rust (
Copy / docs (
Validation on the new head
Still flagged for the maintainer, unchanged in intent: the signed Windows bundling path is covered pre-merge by the real-machine transcript and post-merge by the main-channel canary, not by PR CI ( |
|
Follow-up on the review-hardening pass (head
Validation on this head: The remaining verified observations need no code change: |
|
Final head for this round: One follow-up on the previous hardening comment: the malformed-signature loop in Local state at this head: CI note: |
songoow
left a comment
There was a problem hiding this comment.
结论:请求修改。 在 b2e8e1bb109852ffea533e7c960a56263aa9395d 上发现两项 Windows 更新生命周期问题:手动更新成功会绕过 App 自有服务的关闭路径;安装器启动失败会把仍在运行的窗口隐藏。两项均以本仓库和锁定依赖的源码调用链确认,原生 Windows 现场复现尚未执行。
Reviewer: model_agent · GPT-6 Astra Pro · OpenAI · self_reported
[P1] Windows 更新退出前回收 App 自有服务
定位:apps/desktop/loopx-control-plane/src-tauri/src/maintenance.rs:386–390。
在 Windows 已进入工作区、App 已启动自己的 Chat/status 子进程后手动 apply 一个有效更新,新开放的分支会调用 update.install(bytes)。锁定的 updater 2.11.0 在启动 NSIS 后直接 std::process::exit(0);默认 before-exit hook 只调用 Tauri cleanup,既不产生项目依赖的 RunEvent::Exit/ExitRequested,也不会运行 Rust Drop。因此 apps/desktop/loopx-control-plane/src-tauri/src/lib.rs:308–317 中的 shutting_down、当前路由保存和 ServiceSet::stop() 均被跳过。这里的子进程通过 apps/desktop/loopx-control-plane/src-tauri/src/services.rs:339–353 的 group_spawn() 创建,锁定 command-group 5.0.1 默认 kill_on_drop=false,只有显式启用才设置 KILL_ON_JOB_CLOSE。旧 Chat/status 服务因此可在旧 App 退出后继续运行,而新 App 会分配新端点并启动另一组服务;重复更新会累积失去 owner 的进程和后台工作。
请把 Windows 更新交接接入现有服务关闭/路由保存 owner,并与 supervisor 同步,确保成功退出前仅回收本 App 的服务及其后代。补一条原生 Windows 回归:记录手动更新前的服务 PID/端点,更新后断言旧进程退出且仅有新服务组、独立 CLI 服务不受影响。此问题由 exact-head 调用链和锁定依赖源码确认,未在本 Linux 环境现场复现;base 的 Windows apply 会在平台门禁处拒绝,所以这是本次启用带来的新可达路径。e3d 审查时的五条作者评论未覆盖该所有权检查。
[P2] 安装器启动失败后恢复窗口再承诺可继续使用
定位:apps/desktop/loopx-control-plane/src-tauri/src/maintenance.rs:667–668。
当签名已经验证、安装器已解包,但 ShellExecuteW 因执行限制等原因返回 <=32 时,Windows 分支将当前 exe 仍在磁盘上视为可继续使用的证明。然而锁定 updater 2.11.0 在调用 ShellExecuteW 之前已执行默认 before-exit hook,而 Tauri 2.11.5 cleanup 会清空资源表并隐藏所有 Windows 窗口。启动失败随后返回到本函数,现有 handler 只发布 app_install_failed,没有重新显示窗口或恢复被清理的资源。于是进程仍活着,但当前启动页/工作区以及错误、检查、重试控件已经不可见;仅检查 exe 存在不能支持“可继续使用”的承诺。
请让 Windows 安装交接在启动失败时保持或恢复可用的原生窗口,避免在尚未确认交接成功时不可逆地清理 Tauri。补一条真正越过签名验证的负向测试:拒绝 NSIS 的 ShellExecuteW 启动,确认原窗口仍可见、状态和检查/重试可用、现有服务未损坏。现有 tampered-signature 测试在 before-exit hook 之前失败,不能覆盖此路径。这里是锁定依赖的源码证明,未冒称原生复现;用户另行启动第二实例可能唤回窗口,但不能代替此次操作的同窗失败恢复。base 不开放 Windows apply,现有评论也没有此启动失败场景。
动机
直接受影响的是已安装 main 通道 Windows NSIS App、希望保留当前工作现场完成升级的用户,以及维护签名发布流水线的操作者。旧版本的 Windows App 不接受内置更新;本次改动让它在启动时或更新面板中获取 main feed、验证安装器并交给 NSIS 更新,随后应继续进入工作区。该切片能减少 Windows main 用户手动下载、安装和回到工作区的操作负担,签名检查又把发布产物与受信更新绑定。但用户真正需要的是升级后继续工作,所以评估必须走到旧进程退出及失败恢复,不能仅止于安装器被启动。
已验证的增量是 main feed 输出专属 NSIS 目标、stable feed 保持 macOS 范围、坏签名结构和缺失目标被拒绝;原生安装成功后的服务回收及安装器启动失败后的窗口恢复仍存在源码可确认的缺口。剩余缺口是 Windows 更新交接必须遵守现有 App 服务所有权,并在安装器未能启动时保留可见且可重试的窗口。本次审查不要求补齐 Windows 捆绑运行时修复、回滚、MSI 自动更新、stable Windows 自动更新或 Authenticode;这些不是本 PR 承诺的交付范围。
规范依据为 docs/architecture/rfcs/desktop-execution-frontends-v0.md,固定在 4f6e20a13b090318e14efeb20b91790e33fe694b。该文没有逐条编号,以下沿用原文小节/句首作为定位标签:Native service ownership 要求原生窗口拥有自己启动的服务,退出和重开只影响其子进程,此处因 P1 未满足;A failed manual update or recovery 要求保留错误、诊断和重试入口,此处因 P2 未满足;An available signed App update 的启动阻塞可操作性仍由现有 maintenance 状态及 UI 保留,源码与模拟入口测试覆盖,不能等同于完整原生安装资格。关联任务 #5204 的重启、更新和单一执行 owner 要求与上述边界一致。
改动思路
签名产物选择属于发布脚本,安装与退出交接属于原生维护层,能力展示属于现有启动页和更新面板;沿用这些边界和现有服务 owner 即可完成修复,无需新增平行更新框架。当前 PR 应交付 main NSIS 的签名更新以及成功退出、失败留窗这两个必要生命周期不变量;Windows 运行时修复、回滚和 stable 扩展可由后续平台切片独立验收。
main 流水线增加 NSIS 签名产物并以 installer-specific target 进入 feed;原生层按平台开放动作,并排除 macOS 的安装 journal、运行时修复及回滚;两个前端根据原生能力调整恢复提示。这些机制有真实入口,适合一起交付。问题集中在复用的插件安装 API 与项目现有退出协议之间:Windows API 成功时不返回,失败时却可能已经清理窗口,两个条件都与普通维护状态机的假设不同。
具体改动
完整 diff 共 10 个文件、增加 509 行、删除 57 行。发布 workflow、feed 生成器、原生入口/维护层、启动页/工作区面板及配套测试均已阅读;也检查了未改的 service owner、workspace handoff、runtime selection、backup 和 pinned updater 调用链。
关键代码讲解
build(scripts/desktop_update_feed.py:93) 消费实际产物目录、版本、tag 和 channel;main 需要 darwin 与 windows-x86_64-nsis 两个目标,stable 仍只需要 darwin。_minisign_signature检查编码与四行结构,真正的安装器签名验证仍归 Tauri updater。已用相同结构性 fixture 经 base/head 公开 CLI 执行五组对照,stable 输出一致,main 新增 NSIS,缺目标或坏结构在 head 拒绝。desktop_update(maintenance.rs:399) 是启动页和工作区共用的原生入口;新动作表在 Windows 允许 check/apply/forget/restart,supervision 锁保持串行。它把 apply 交给perform,后者在非 macOS 直接调用插件 install,所以必须把 Windows 的非返回式退出纳入既有服务 owner,详见 P1。failed_install_left_previous_app_usable(maintenance.rs:667) 为 Windows 改成检查当前 exe 存在,再由finalize_install_failure决定错误状态。排除 macOS journal 是正确的平台边界,但磁盘完整性不包含窗口是否已被 Tauri cleanup 隐藏,详见 P2。workspace_navigation(lib.rs:97) 接受实际 boot origin,同时保留 workspace 的精确 origin 限制;scheme、host 和 port 的相似值没有获得维护权限。Windows 的 http tauri.localhost 别名不会把任意外站变为 App origin。
Windows 运行时仍来自独立 CLI 的选择/发现,bundled_repair_available 是原生能力投影;沿用缺字段兼容有真实的独立 shell/CLI 部署边界。本次没有新增 Goal、Agent 协调协议或义务 flag。共享语义使用既有维护/服务 owner;两项问题需要在原生交接处修复,无需改变 Goal vocabulary。
对主干的风险
长期持续工作的主要风险是 P1 的孤儿服务:安装成功不能证明旧 owner 已关闭,后续启动可能形成新的进程组而旧后台工作仍在。用户体验的主要回归是 P2:错误快照存在也不能说明用户看得到恢复入口。签名篡改在安装前被拒绝、纯状态机通过以及正常更新 receipt 都无法排除这两个分支。
最终审查绑定 b2e8e1bb109852ffea533e7c960a56263aa9395d。它与已执行验证的 e3d54dd36eae4dba016991580ac0bf7fd20ef363 是同一完整代码树:本地 fetch 后两个 tree OID 均为 a8c4e794b76f330385cb2ebaec02c08025a1e15c,git diff --exit-code 返回 0;GitHub compare 也没有文件差异。因此两项发现及行号均保留,沿用未失效的证据;没有声称在 b2 重新运行测试。
实际在 e3d 执行了 25 个 desktop Python 单测和五组 base/head feed CLI 对照,全部通过。e3d 相对初审 3405a5ceeb6717ffd50ae9e678ca6ff6424ce63c 只增加三行 Darwin 签名 fixture 恢复,确保后续 Windows 拒绝断言单独依赖 Windows 签名;生产代码与依赖未变。初审 3405 执行的 132 个 CI policy 测试、release workflow smoke、dashboard TypeScript/Vite 构建和 desktop-update-browser-smoke 均通过,仍适用于同一生产树。workflow smoke 初次遇到 sandbox 子进程 EPERM,在获准的隔离测试环境重跑同命令后通过;browser smoke 初次缺浏览器路径,显式指定已有 headless shell 后通过。browser 测试使用 production bundle 和 synthetic native IPC,不安装软件;检查了完整 viewport,英文恢复面板清晰,当前 Linux 缺中文字体导致中文截图为 tofu,未据此声称原生字体验收。这些测试不能观察 Windows process exit、job object 或 ShellExecuteW。
完整审查期间在前一 head 3405a5ceeb6717ffd50ae9e678ca6ff6424ce63c 观察到 Windows desktop、macOS desktop、validate 均成功,另有测试分片待完成;这不冒充新 head 的 CI 结论。windows-powershell 的三个 long-path budget 用例均为 heartbeat_prompt_brief/json 10522 > 10500,相关生产路径及测试不在本 diff;没有相同 Windows 环境的 base 对照,保留 baseline 未证状态,不能冒称已经证明是旧失败。cancel-obsolete-pr 则是远端取消旧 run 后未及时进入终态,属于未改维护工作流的外部取消问题。e3d 审查时的作者评论还将同一 budget 失败关联到 #6843,并报告 main 同样失败;本审查没有重跑其同 Windows base 环境,因此只记录该说明,未把作者声明转成独立基线证明。这些 CI 状态作为独立合并就绪信息记录,两个代码 finding 不以它们为依据。
更新后的 CI 资格单独保留:在 e3d,macOS job 117938977194 的 bundle_dmg.sh 失败并返回 exit 1;目前没有充分证据归属根因。Windows job 117942236228 也报告 budget 失败,其独立 Windows base 对照仍未建立。这两项按历史未解决 CI 状态记录,不能用 3405 的通过状态代表它们。最终 b2 的原生 packet(生成于 2026-10-19T07:55:30Z)未提供 status-check rollup,所以本审查不作 b2 全部 CI 通过的结论;完整 Windows/macOS 安装及上述两条生命周期回归仍未取得本审查的原生运行资格。
我的整体评价
这是有实际价值、范围适当的 main NSIS 增量;新增签名/feed 规则、动作隔离和能力文案有必要,未发现需要拆成另一套更新框架的理由。
请先复用现有 App 服务关闭 owner 修复成功交接,并给失败交接补齐可见窗口恢复,再在真正的 Windows App 上分别验证已连接状态手动更新后的旧 PID 清理,以及已通过签名但 NSIS 启动被拒绝后的同窗重试。重审应保留当前 feed/平台反例和 macOS 路径,核对届时最新 head。作者现有正常更新及坏签名 receipt 有帮助,但没有覆盖这两条具体生命周期路径。
English verdict: REQUEST_CHANGES — at b2e8e1b, the newly reachable Windows updater path bypasses App-owned service shutdown on success and hides the current window before a launch failure can be recovered. Source-confirmed against pinned dependencies; native Windows reproduction remains unverified.
| fn app_update_action_supported_on(platform: &str, action: &str) -> bool { | ||
| platform != "windows" | ||
| || matches!( | ||
| action, | ||
| "check" | "apply" | "forget_runtime_selection" | "restart" |
There was a problem hiding this comment.
[P1] Windows 更新退出前回收 App 自有服务
在 Windows 已进入工作区、App 已启动自己的 Chat/status 子进程后手动 apply 一个有效更新,新开放的分支会调用 update.install(bytes)。锁定的 updater 2.11.0 在启动 NSIS 后直接 std::process::exit(0);默认 before-exit hook 只调用 Tauri cleanup,既不产生项目依赖的 RunEvent::Exit/ExitRequested,也不会运行 Rust Drop。因此 apps/desktop/loopx-control-plane/src-tauri/src/lib.rs:308–317 中的 shutting_down、当前路由保存和 ServiceSet::stop() 均被跳过。这里的子进程通过 apps/desktop/loopx-control-plane/src-tauri/src/services.rs:339–353 的 group_spawn() 创建,锁定 command-group 5.0.1 默认 kill_on_drop=false,只有显式启用才设置 KILL_ON_JOB_CLOSE。旧 Chat/status 服务因此可在旧 App 退出后继续运行,而新 App 会分配新端点并启动另一组服务;重复更新会累积失去 owner 的进程和后台工作。
请把 Windows 更新交接接入现有服务关闭/路由保存 owner,并与 supervisor 同步,确保成功退出前仅回收本 App 的服务及其后代。补一条原生 Windows 回归:记录手动更新前的服务 PID/端点,更新后断言旧进程退出且仅有新服务组、独立 CLI 服务不受影响。此问题由 exact-head 调用链和锁定依赖源码确认,未在本 Linux 环境现场复现;base 的 Windows apply 会在平台门禁处拒绝,所以这是本次启用带来的新可达路径。现有五条作者评论未覆盖该所有权检查。
There was a problem hiding this comment.
Fixed in 21747c654, with the native regression in 6e2859ba2; current head 7e87c1ca0.
The Windows handoff now goes through the existing App-service shutdown and route-save owner instead of skipping it:
ServiceLifecycle(apps/desktop/loopx-control-plane/src-tauri/src/service_lifecycle.rs) owns theServiceSet, the shutdown/handoff/reclaimed state and the supervisor park.begin_app_update_handoffparks the supervisor first, then runs the same route save the window exit runs (workspace_location::remember_current) and takes and stops the owned set, so the reclaim happens inside this App's own job objects and the group's descendants leave with it.- The window installs the shared production constructor
service_lifecycle::app_update_pre_exit_hookfrommaintenance.rs::find_update, i.e. the same ownerRunEvent::Exit/ExitRequesteduses, so the hook and the normal exit path cannot drift apart. - Supervisor synchronization: the supervisor neither starts nor recovers services while
handoff_active()(lib.rs); a start that raced the handoff re-checks the park under the same lock and is handed back to the caller that started it instead of being adopted, so ownership stays single-valued and repeated updates cannot accumulate ownerless groups. - Scope: only services this App owns are reclaimed. A process this App never started (a CLI-owned listener on its own port) keeps running.
Native Windows regression: src/service_lifecycle_windows_update_tests.rs::a_verified_artifact_whose_launch_fails_keeps_the_window_usable_and_returns_the_runtime plus service_lifecycle::tests::windows_reclaim::an_update_handoff_reclaims_the_owned_group_and_its_descendants. Both hold a real owned service group and a real independent process, record their PIDs, and assert the owned PID exits while the independent PID survives and the replacement set is the only new owner (exactly one restore owed). The update is driven through the pinned tauri-plugin-updater 2.11.0 with a real loopback HTTP feed, the plugin's own minisign verification of a per-run throwaway key and its own ZIP extraction; a tampered artifact announced under the same signature must fail that verification first, so the "verified" claim is not vacuous. These run on native Windows (locally and in the new windows-desktop-tests leg).
Boundary I am not over-claiming: this is a native Windows process with the locked plugin and real job objects, but the App shell is the mock runtime. It does not reproduce endpoint allocation for the real Chat/status services, and the successful path's std::process::exit(0) (which happens only once Windows accepts the launch) cannot be reproduced in-process -- the regression asserts the reclaim the hook performs on that path instead.
| fn failed_install_left_previous_app_usable(_app: &AppHandle) -> bool { | ||
| std::env::current_exe().is_ok_and(|executable| executable.is_file()) |
There was a problem hiding this comment.
[P2] 安装器启动失败后恢复窗口再承诺可继续使用
当签名已经验证、安装器已解包,但 ShellExecuteW 因执行限制等原因返回 <=32 时,Windows 分支将当前 exe 仍在磁盘上视为可继续使用的证明。然而锁定 updater 2.11.0 在调用 ShellExecuteW 之前已执行默认 before-exit hook,而 Tauri 2.11.5 cleanup 会清空资源表并隐藏所有 Windows 窗口。启动失败随后返回到本函数,现有 handler 只发布 app_install_failed,没有重新显示窗口或恢复被清理的资源。于是进程仍活着,但当前启动页/工作区以及错误、检查、重试控件已经不可见;仅检查 exe 存在不能支持“可继续使用”的承诺。
请让 Windows 安装交接在启动失败时保持或恢复可用的原生窗口,避免在尚未确认交接成功时不可逆地清理 Tauri。补一条真正越过签名验证的负向测试:拒绝 NSIS 的 ShellExecuteW 启动,确认原窗口仍可见、状态和检查/重试可用、现有服务未损坏。现有 tampered-signature 测试在 before-exit hook 之前失败,不能覆盖此路径。这里是锁定依赖的源码证明,未冒称原生复现;用户另行启动第二实例可能唤回窗口,但不能代替此次操作的同窗失败恢复。base 不开放 Windows apply,现有评论也没有此启动失败场景。
There was a problem hiding this comment.
Fixed in 21747c654, with the negative regression in 6e2859ba2; current head 7e87c1ca0.
The hook this window installs is no longer the plugin's default cleanup_before_exit. service_lifecycle::app_update_pre_exit_hook reclaims this App's services and performs none of Tauri's cleanup: it does not clear the resource tables and does not hide the window, precisely because a launch that fails returns in-process. Tauri's cleanup is gone from this path, so the failure branch cannot leave an invisible-but-running App.
Recovery in the same window: install_verified_app releases the handoff (release_app_update_handoff) when the plugin reports an install error, and the live supervisor sees restore_pending() and starts the same runtime again in place (maintenance::restore_services_after_update_failure), without re-announcing a phase, so the failure snapshot the operator retries from stays on screen with its check/retry controls. failed_install_left_previous_app_usable remains the layout-level guard for the same-window retry promise, but it is no longer the only thing behind "still usable", because the window is never hidden on this path.
The negative test now crosses the signature boundary instead of asserting the cause: the real plugin verifies a real signature over an NSIS archive, unpacks it, and Windows refuses the resulting ShellExecuteW launch, so install(bytes) returns Err with the pre-exit hook already executed. It then asserts the App and window resource sentinels survive, the window is still addressable, the reclaimed runtime returns to the supervisor (exactly one restore owed), and a service this App never started is untouched. A contrast run with the plugin's default cleanup_before_exit hook shows those survival assertions detect the difference, and a mutation check (adding app.cleanup_before_exit() to the shared production hook) fails the regression at "the App resources must survive a failed installer launch" instead of passing vacuously.
Local evidence at this head, native Windows: cargo test --locked 135 passed / 0 failed / 2 ignored, cargo fmt --check clean, and the new windows-desktop-tests CI leg runs these lifecycle regressions on windows-latest.
|
This pull request has merge conflicts with Choose the remote for the base repository, not an out-of-date fork. git fetch upstream
git rebase upstream/main
# Resolve each conflict, git add the resolved files, then git rebase --continue.
git push --force-with-lease origin HEADFor a same-repository clone whose Keep the DCO |
b2e8e1b to
5ee7baf
Compare
|
Both blockers from the review are addressed on the current head, and the branch is rebased and mergeable again. Re-review requested. P1 — Windows update handoff now reclaims this App's own services
P2 — a refused launch keeps a usable window
Rebase, conflicts, scope
Validation (head
|
|
Follow-up: the native Windows job is green on head The first run of the new Job The signed-desktop lane is green on this head: Still unverified, as in the previous comment: this is a native Windows test job, not a full NSIS install run, so the real-machine receipt for the two scenarios stays outstanding. |
|
Thanks for the review -- both findings are addressed on head Fixes
Rebase and integration The branch is rebased onto current Validation at
Boundary The regressions run real Windows processes with the shipped dependencies, but the App shell is the mock runtime: they do not cover an installed NSIS App being replaced, endpoint allocation for the real Chat/status services, or a launch that Windows accepts. Those stay with the canary run after merge; I am not claiming a native installed-App upgrade receipt. The branch is currently 20 commits behind Requesting re-review. I am not merging this myself. |
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max
动机
REQUEST_CHANGES,针对 7e87c1ca07892eb60c625951e8d502a986dea157。当前可复现的问题是更新面板把 Windows 自动更新的承诺扩到了稳定通道;真实安装窗口的更新成功与启动失败恢复资格也仍未证明。后者是验收缺口,不是声称已复现一个新的 Windows 进程泄漏。
使用 Windows NSIS main 预览版的用户,希望从现有 App 完成签名更新,并在失败后继续使用原窗口。过去 Windows 只能手工替换安装包;本次增加启动时检查、验证、安装和重新打开的路径,同时继续让独立安装的 CLI 由原来的安装方式维护。独立验证确认了 feed 的通道分离、本机 macOS 回归和实际构建面板的部分交互,但尚不能确认真实 Windows 安装后的服务所有权和同窗口恢复。范围限于 Windows NSIS main 更新与既有 macOS 路径,不包括 Windows stable/MSI 自动更新、Windows repair/rollback、Authenticode 或完成 #5204。
做这项功能有实际价值:手动下载、确认版本和重开窗口会重复占用用户注意力。但“取消默认 cleanup”或“停止一组测试进程”仍不是更新后恢复原工作区的完整结果。原两条评审意见提出的源代码缺口已得到针对性修复;本次重新审视整个 17 文件提案,没有继承旧 head 的结论,也没有把作者的 Windows 运行声明算作本机执行。
改动思路
签名验证继续交给固定版本 updater,服务所有权集中到同一个 ServiceLifecycle,安装失败由既有 Maintenance 和 supervisor 恢复;无需再建一套安装状态机或服务发现机制。当前 PR 应交付 main/NSIS 的受限更新路径、独立 CLI 不受影响的恢复结果和真实平台证据;stable/MSI 与 Windows 修复回滚保持原边界。
链路为:发布工作流构建两平台产物,feed 仅向 main 宣告 NSIS target;原生启动检查发现可用更新,下载并验签后调用安装;插件在启动安装器前运行共享 hook,保存路由、停住 supervisor 并回收本窗口的进程组。若启动安装器失败,则释放 handoff、恢复被回收的服务,并保留失败信息供用户重试。插件成功启动后直接退出进程,因此不能依赖 RunEvent::Exit 才回收服务。
这一组织方式符合既有 owner,新增恢复债务也有实际的生产消费者。较小的修复仍应留在这些 owner 内:纠正面板的通道承诺,补齐已承诺平台的真实安装验证。无需增加授权确认、新的 Goal 任务树或通用恢复框架。这里用文字描述分支,没有把未核验的示意图当作证据。
具体改动
评审基准是 Desktop execution RFC,spec_ref 为 docs/architecture/rfcs/desktop-execution-frontends-v0.md,spec_revision 为 cd1b2b4c69caddac4cfbf5a51f8eb961730cf591,并读取了 #5204 的既有验收。本提案的实际 merge base 是 3a816411785fea545c379769a1ff98f5e881bd5d。下列标签映射既有要求,没有新增父级验收门。
| 既有要求映射 | 当前判断 |
|---|---|
| NS-ownership:原生窗口只拥有自己启动的服务,退出和恢复不影响独立 CLI | not_met:共享 hook 和单 owner 的源代码修复成立;当前 head 的真实 Windows 安装窗口、独立 CLI 与更新后端点读回仍缺证据 |
| NS-failure:失败更新保留可用窗口、诊断与重试,普通重开及同窗口恢复分别验证 | not_met:mock Runtime 的资源表生存与注入 ServiceSet 是支持证据,尚未执行完整安装窗口与真实 status/Chat 服务恢复 |
| NS-offline:自动更新源不可用时,现有 App 仍可启动 | implemented:既有失败继续路径、macOS 本机回归及源代码分支覆盖;Windows 的完整安装资格仍受前两行限制 |
| NS-truthful-scope:每个平台的支持声明和可行动状态必须与实际范围一致 | not_met:稳定通道的实际构建面板宣称当前通道自动更新,而 feed 排除了 Windows stable target |
关键符号及完整改动覆盖:
automatic_app_update增加 Windows 启动检查与 apply,macOS 继续使用已准备 archive;action table 拒绝 Windows repair、align_runtime、rollback,journal 也保持 macOS 范围。app_update_pre_exit_hook同时被生产 updater 和 Windows 回归使用。adopt/reclaim/release管理同一个 ServiceSet;lib.rs 的 supervisor 在 handoff 时停住,在失败时恢复服务,关闭窗口仍走该 owner。find_update覆盖插件默认退出 hook。对照本地固定依赖 tauri-plugin-updater 2.11.0,Windows 顺序确实是 extract、hook、ShellExecuteW、成功后 process::exit;取消默认 cleanup 能避免失败前清空窗口资源。build从已有发布边界选择 main 的 Darwin/NSIS 或 stable 的 Darwin,拒绝缺失/歧义/空产物及不合法签名结构。工作流补 Windows 构建、版本名检查、上传和原生测试;build.rs 与目标专用 dev-dependencies 支持 Windows libtest 的 manifest。- 更新面板、boot fallback、导航 origin、两处 README 和测试一起检查。Windows 的 bundled boot alias 仅按精确 origin 接受;测试覆盖相似主机、错误端口/协议和非 Web 链接。全部改动为 +1623/-98,包含大量 cfg(test) 与 Windows 专用验证代码,不能全算运行时代码。
对主干的风险
[P2] 自动更新文案必须保留 main/NSIS 限定。 在真实构建 bundle 中注入明确不可 repair/rollback 的 Windows 状态,默认稳定通道仍显示“App 会在当前通道自动更新……Windows 版在启动时检查并安装”。同一界面的检查随后只能显示该通道没有本机更新包。feed 的 stable 平台表以及 README 均明确 stable/MSI 需手动更新。最小修复是在这条文案说明 main/NSIS 的范围及 stable/MSI 的手动路径;复用既有通道信息,无需再加设置。回归应断言 stable/MSI 不承诺自动安装,并保留 macOS prepared 与 main/NSIS 的正确说明。
Windows 安装资格继续保留。 当前原生回归使用 MockRuntime、手动注入的进程组和无效 PE 的已签名 ZIP,能区分 hook、签名与资源清理行为;它没有执行真实 NSIS 替换成功后的退出/重开,也没有让实际 supervisor 重新建立真实 status/Chat 端点。作者最新回复也明确这两个安装场景未验证。按上述既有原生所有权及失败恢复验收,合入后再测不能替代当前资格。最小补证是在隔离的真实 Windows NSIS 安装中,分别验证成功更新与已验签但安装器启动失败,读回原窗口/路由、App-owned 子树、独立 CLI 存活和可重试控制。未执行这些场景,结论仍为 not_yet_proven。
本机执行结果:桌面 Python 25 项通过;Rust libtest 161 项通过、6 项忽略;精确源代码 premerge 的直接检查、编译和 19 个所选检查通过。实际生产 bundle 已构建,并用 Ego 检查稳定通道、main 可用状态、禁用修复/隐藏回滚,以及签名错误后保留检查入口的完整视口;native IPC/status 为合成替身,不冒称 Windows 安装。浏览器早期注入脚本的失败已纠正,不归因于产品。没有查询 CI,也没有安装、升级、启动用户模型或改变独立 CLI。
我的整体评价
REQUEST_CHANGES。long_horizon 的 Windows 更新后继续工作仍为 not_yet_proven;user_experience 存在稳定通道承诺范围回归。签名验证继续交给固定版本 updater,服务所有权集中到同一个 ServiceLifecycle,安装失败由既有 Maintenance 和 supervisor 恢复;无需再建一套安装状态机或服务发现机制。当前 PR 应交付 main/NSIS 的受限更新路径、独立 CLI 不受影响的恢复结果和真实平台证据;stable/MSI 与 Windows 修复回滚保持原边界。
源代码处理了原两条阻塞的关键原因,不能因此把未执行的原生旅程判为通过。建议先修正范围文案、提供上述精确平台证据及可公开的实际视口材料,再复审。保留维护者的独立合并与发布权限;本评审不关闭 #5204,也不证明长期收益或 model adoption。
English verdict: REQUEST_CHANGES at 7e87c1ca07892eb60c625951e8d502a986dea157. The shared pre-exit hook addresses the earlier source defects, but stable-channel UI overstates Windows automatic-update scope, and the current installed NSIS success/failure recovery journey remains unqualified. Local macOS Rust/Python/premerge and built-UI fixture checks do not substitute for that Windows evidence.
7e87c1c to
8230083
Compare
|
Rebased onto current Fix — the panel no longer promises updates the channel cannot deliver
Validation at
|
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max
Exact head: 82300835779b36ededd6e3d0f2a527567c31a790 · merge base: 907ede6e1faff1fab0f2f765dde4358c69c7b7ff
动机
使用 Windows NSIS main 预览版的用户,希望从现有 App 完成签名更新,并在失败后继续使用原窗口。过去 Windows 只能手工替换安装包;本次增加启动时检查、验证、安装和重新打开的路径,同时继续让独立安装的 CLI 由原来的安装方式维护。
本轮已在当前生产构建页面验证平台与通道提示的修复;真实 Windows 安装后的服务所有权和同窗口恢复仍未完成验收。范围限于 Windows NSIS main 更新与既有 macOS 路径,不包括 Windows stable/MSI 自动更新、Windows repair/rollback、Authenticode 或完成 #5204。
此前稳定通道的自动更新承诺已修复,本评审不继续把它列为当前缺陷。作者的新说明也明确保留 installed-NSIS 验收缺口。剩余工作是确认更新后能继续原任务,而不是再增加一个安装管理器或设置入口。
改动思路
签名验证继续交给固定版本 updater,服务所有权集中到同一个 ServiceLifecycle,安装失败由既有 Maintenance 和 supervisor 恢复;无需再建一套安装状态机或服务发现机制。
当前 PR 应交付 main/NSIS 的受限更新路径、独立 CLI 不受影响的恢复结果和真实平台证据;stable/MSI 与 Windows 修复回滚保持原边界。新提交仅从既有 native status 增加可推导的 host OS,再由既有更新面板选择文案,没有让用户重复声明平台或增加确认轮次。未知或旧版 native status 使用中性提示。
| 入口或阶段 | 当前责任及边界 |
|---|---|
| 官方 feed | main 提供 Windows NSIS;stable 不发布 Windows 自动更新目标 |
| 下载与安装 | 固定 updater 验签;已验证字节才进入同一个 pre-exit hook 与安装流程 |
| App 子服务 | 同一个 ServiceLifecycle 管理停机、移交及失败恢复债务,不接管独立 CLI |
| 面板 | Windows stable/MSI 指向手动安装,main 明示 NSIS,macOS 保留已准备更新的说明 |
这是当前源码责任分工的说明表,不是 Windows 安装执行证明。继续使用原 owner 比复制安装、密码学或服务发现规则更容易审查和恢复。
具体改动
当前完整 PR 为 17 文件 +1674/-100,覆盖 updater workflow、feed 和签名格式检查、Rust 安装与服务生命周期、导航 fence、启动页、更新面板、两份 README 及验证代码。与前次 7e87c1ca 相比,17 个路径中 14 个的 base/head 文件均逐字节相同;真正的提案增量是 native status、面板与 browser smoke。合入主干的相邻 UI 变化单独核对,不混进本 PR 的规模或沿用旧 verdict。
关键代码讲解
desktop_update_feed.build根据显式 channel 选择合格目标,拒绝缺失、空、重复或结构不合法的签名;stable 保留 Darwin-only 的 feed 边界。workflow 的 Windows 签名构建与 pointer 推进条件属于同一路径。app_update_pre_exit_hook让生产安装和平台回归测试调用同一个 App 子服务回收入口;与退出及 supervisor 共用所有权,避免默认 cleanup 提前销毁所需资源。失败释放移交后仍欠恢复,不能把空 ServiceSet 当成恢复完成。install_verified_app接在验签之后,使用固定 updater 的安装顺序,区分 macOS journal 与 Windows 路径;成功退出和安装器无法启动后的恢复须分别判断。desktop_update_status增加由std::env::consts::OS得出的platform。这是现有状态的附加投影,不是新的授权、配置或持久状态 owner;旧客户端仍可忽略它。DesktopUpdate.idleMessage按实际平台与当前通道选择提示。没有platform的旧 status 不再获得无条件的 Windows 自动安装承诺;可用性仍由 native/feed 拒绝或允许。
验收依据为变更前已接受的 Desktop execution RFC,spec_revision=907ede6e1faff1fab0f2f765dde4358c69c7b7ff,以及 #5204 的逐操作系统验证边界。该 RFC 与上次接受依据的正文相同。以下标识定位原有要求,不增加父路线的完成条件:
| criterion_id | 当前处置 |
|---|---|
| NS-ownership | not_met:源码共用同一服务 owner;当前真实已安装 NSIS 的 status/Chat 及独立 CLI 共存读回仍缺 |
| NS-failure | not_met:已验签但无法启动安装器时,真实原窗口、supervisor 和两个端点恢复仍缺当前执行证据 |
| NS-offline | implemented(已验证源码范围):不可用自动 feed 不阻止原 App 启动,保留错误与后续操作;Windows 安装资格另列 |
| NS-truthful-scope | implemented:五种平台、通道与语言状态通过当前构建页面检查,稳定通道明确手动路径 |
对主干的风险
本轮在这个 exact head 实际执行:macOS cargo test --locked --lib 161 passed / 6 ignored;desktop Python 25 passed;Rust 格式、dashboard TypeScript 与生产 Chat bundle 构建通过;以当前 merge base 运行原生 source premerge,5 direct +19 selected 均通过。这些检查覆盖变更的本地不变量,不能把其中未在本机运行的 Windows 专用测试写成通过。
实际打开当前构建的完整 1280×900 视口并检查截图:Windows stable 的中文、英文均指向手动 signed installer;Windows main 明示 NSIS 的启动时更新;macOS 保留“下次打开安装”;旧 status 缺少平台时使用中性说明。稳定通道检查返回 update_platform_unavailable 后,错误与“检查更新”仍可见,没有调用安装。旧头的稳定通道截图曾显示自动安装承诺,因此这一反例能区分旧错误与当前修复。
这里使用了真实生产 web bundle,但 native IPC/status 是合成替身,不能证明 WebView2、原生端点、NSIS 替换或 Windows 进程所有权。
当前 REQUEST_CHANGES 仅保留真实平台资格缺口,没有新增已复现的代码 blocker。 Windows 回归模块使用真实子进程和固定 plugin,但窗口为 MockRuntime、服务集合有注入;这不能替代通过发布 feed 替换真实已安装 App。历史版本的机器记录也不能直接覆盖当前生命周期修复后的结果。
最小补齐是:在真实已安装 Windows NSIS main App、同时存在独立 CLI 的隔离环境,分别执行成功替换/退出重开,以及验签后安装器无法启动的失败分支;核对原窗口的实际 status/Chat、选定 runtime 与子进程,确认独立 CLI 未被停止或覆盖。沿现有平台 canary/发布验收交付这些观察即可,不要求 MSI、stable rollout、付费模型或完成整个 #5204。
语义与 CI 对齐
复用既有 update phase、服务生命周期与平台/action gate;新增的是现有 status 的可选平台投影,没有新增通用工作义务、权限词汇或第二套状态机。界面提示是解释,feed、签名、origin 与 native action 才执行约束。此前 shared copy 越过 stable 边界的问题已修复;当前未通过的是原已接受的 native ownership/recovery 资格。按当前配置没有查询、轮询或等待 CI。
我的整体评价
机制与范围可以保留,本次文案修复已由独立构建页面确认,旧 P2 不再作为当前 finding。problem_context=not_yet_proven;源码与面板范围的用户理解已有改善,但完整 Windows 旅程的 long_horizon=not_yet_proven、user_experience=not_yet_proven,原因是原生安装、失败恢复及继续原任务的关键观察未完成。结论为 REQUEST_CHANGES,保留已验证修复,补齐上述现有平台验收后再判断完整 head。
English verdict: REQUEST_CHANGES at 8230083. The stable-channel copy regression is fixed and independently verified in the current production web bundle. macOS Rust tests (161 passed, 6 ignored), 25 desktop Python tests, types, build and current-base premerge pass. The remaining blocker is qualification of the real installed Windows NSIS success and post-verification launch-failure recovery journeys, including owned endpoints and independent CLI coexistence; mocked native status and older machine receipts do not establish that outcome.
|
Thanks — the re-review confirms the stable-channel copy fix on the current production bundle, and records no new code blocker. This round
Remaining acceptance As your review states, the open item is the real installed Windows NSIS journey: successful replacement/restart with owned status/Chat endpoints and an independent CLI left alive, plus a verified-but-unlaunchable installer whose failure leaves the same window usable. Source review, mocked native status, and the earlier-head receipts do not settle it. Please choose the acceptance path:
If author-side evidence is wanted first, I can produce an installed-NSIS receipt with a loopback feed and a temporary test key. The only deltas from the shipped build are the feed URL and key; that boundary would be stated in the receipt. I am not merging or closing this PR, and I am not claiming the installed journey from the earlier head. |
Real installed-NSIS receipt at
|
|
This pull request has merge conflicts with Choose the remote for the base repository, not an out-of-date fork. git fetch upstream
git rebase upstream/main
# Resolve each conflict, git add the resolved files, then git rebase --continue.
git push --force-with-lease origin HEADFor a same-repository clone whose Keep the DCO |
8230083 to
b70491e
Compare
dcbc045 to
8eadd9a
Compare
|
Rebased onto current The only conflict was Validation on the rebased head:
The latest review approved |
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max
动机
使用 Windows NSIS main 预览版、希望更新后继续原工作区的用户,以及维护 stable 的 macOS 发布者。
过去 Windows 需要手工替换安装包;这次接入签名检查、安装和重开,并在安装器无法启动时恢复本窗口拥有的服务,保留独立 CLI。
main/NSIS 的更新与失败恢复形成有界增量,stable 的 Darwin-only 上传也已验证恢复;当前共享前端的故障回归覆盖却被删掉,尚不能按现有验收规则批准。
本次不启用 Windows stable/MSI 自动更新,不认证官方 feed/key、Authenticode、Windows repair/rollback,也不关闭 #5204。
剩余阻塞是在现有浏览器 smoke 中恢复主干的下载、验签失败和读取不触发安装回归;官方 Windows 发布资格继续由原 canary/发布 owner 验证。
改动思路
沿用 Maintenance、固定 updater 与一个共享 ServiceLifecycle,足以覆盖 Windows 插件直接退出和失败后同窗恢复;无需另建更新器或第二个进程所有权来源。
当前 PR 应止于 main/NSIS 更新及恢复、stable 发布隔离和真实可用的现有面板,并保留主干已接受的故障回归;官方通道和其他 Windows 安装形式留在原发布验收范围。
Windows 插件成功启动安装器后直接退出进程,普通窗口退出事件不足以回收服务;失败时进程继续运行,又不能提前清空窗口资源。当前共享 owner 先保存位置、停本窗口子服务,失败后解除交接,由原监督器恢复,并让错误留在原面板。独立 CLI 从未进入拥有集合。
| 路径 | 当前边界与独立观察 |
|---|---|
| main / NSIS | 固定插件验签,已有服务 owner 交接;安装后继续原窗口任务的证据来自有界作者安装回执 |
| stable / Darwin-only | main-only Windows 构建不再阻塞;真实上传脚本可到达 feed 和最后的通道指针 |
| 下载或验签失败 | 当前实际页面能显示原因、读取不安装、重试只检查;这些主干已有的 durable 回归必须继续保留 |
表格说明源码责任与验证边界,不是一次官方 Windows 发布记录。
具体改动
关键代码讲解
本轮完整复审 8eadd9a,实际合并基线 9870977,17路径 +1957/-170,包括 workflow/feed、Rust 依赖与构建、生命周期/维护/导航、boot/更新面板、两份 README 和验证。旧批准 dcbc045 到当前头的变化与完整 PR 分开核对;不能因为旧文件字节相同,就忽略新 base 的新增验收。
- ServiceLifecycle / app_update_pre_exit_hook 统一窗口退出、supervisor 与 updater 的进程所有权。交接时拒绝晚到的启动结果,失败后保留恢复债务,接纳新服务才结清;没有接管独立 CLI。
- find_update / install_verified_app 安装同一个 hook,保留固定插件的签名与传输路径;失败释放交接并恢复原 runtime,不把 Windows 启动失败当作 macOS journal/rollback。直接检查固定2.11.0依赖的 ShellExecuteW 与直接 exit 顺序支持这个分工。
- build 与 publish 分别负责通道资格和指针顺序:main 必须有 NSIS 专属目标,stable 保持 Darwin;四行签名结构检查不冒充密码学验证。nullglob 上传只枚举真实文件,拒绝上传时不会推进 feed/pointer。
- DesktopUpdate.idleMessage 从既有 native status 得到平台,区分 Windows stable/main、macOS 和旧 payload;boot 使用相同的不可用恢复边界。用户不必再填平台,也没有新增设置或确认轮次。
规范为 https://github.com/loopx-project/loopx/blob/98709770d80e9c332f3c07d2b545226a6dd17521/docs/architecture/rfcs/desktop-execution-frontends-v0.md,spec_revision=98709770d80e9c332f3c07d2b545226a6dd17521,关联 #5204;覆盖保留规则另见 good-smokes.md。未编号条文沿用原文短语定位:Native service ownership 与 A failed manual update or recovery 在 main/NSIS 切片内有界实现;Default/off paths 通过 stable/main 实际消费者对照;Consolidate Without Losing Coverage 当前未满足,见下方 P2;Reconcile the RFC 留在既有 #5204/发布 owner 的完整平台验收,不据此关闭父任务。
作者真实 installed-NSIS 回执固定于823008357:旧 App/拥有服务退出,新窗口和两服务接入,独立 CLI 存活;已签名无效 PE 被系统拒绝后,同窗口错误与服务恢复。当前 lifecycle、lib.rs、Cargo.lock、Windows 插件回归与该回执相同。相邻 main 增量是 macOS Node 搜索/诊断及下载错误分类,已审阅并以当前测试和生产页面复核。回执仍只证明测试 key、loopback feed、显式仓库 CLI 与无效 PE 场景;不是我在 Windows 上重新执行,也不证明官方 key/feed 或策略拦截的有效安装器。
对主干的风险
[P2] 请保留主干已有的下载、验签失败端到端回归。 browser smoke:229 以及同文件512附近,相对当前 main 删除两段共59行:通用失败、六组双语 timeout/signature/network 错误、读取不重试或安装、不展示 Apply、显式重试只调用 check、窄屏失败,以及 boot 的错误/diagnostics/no-effect 断言。这不是 Windows 通道文案必须删除的覆盖。
我搜索了实际 examples/tests:保留的 desktop-recovery-diagnostics-test 使用 VM 的 DOM 替身,只测 boot.js;Rust 验签/传输回归不经过工作区组件。它们都不替代上述真实 dashboard 和操作控制边界。本轮实际生产 bundle 的六组失败状态与两组 boot 读取均通过,也说明这是仍有产品意义的可执行场景。当前没有复现新的运行时 UI 缺陷;问题是这些独有保护在仓库中消失,剩余检查全绿也不再检查它们。
最小修复是把既有 main 故障块恢复到同一个 smoke,并保留新增 Windows stable/main 用例;若迁移覆盖,须给出同入口、反例与副作用断言的等价证明。构建当前 chat bundle 后重跑 node examples/desktop-update-browser-smoke.mjs,并保留现有桌面和源码检查。无需另建框架、重跑官方发布或扩大到整个 #5204。
旧 stable 构建依赖、未匹配 exe glob 和描述缺首屏的问题已解决,不继续作为 finding:本轮从精确 YAML 提取生产 Bash,7组基线/旧坏头/新头/上传拒绝对照符合预期;stable 只有 Darwin 仍先上传 feed 再指针,拒绝上传则两者都不发生。独立公开 feed CLI 的7组输入覆盖 stable 同输出、main 完整/缺 Windows,以及坏签名结构拒绝;合成签名形状不代表真实密码学通过。
当前 macOS Rust 164 passed /11 ignored,clippy、28项 desktop Python、dashboard 类型、官方 runtime/bundle 构建通过;source premerge 5 direct +19 selected 通过,19提交都有 DCO。生产 bundle 经 Ego 检查五种平台/通道/旧状态、六组双语失败的无副作用读取和 check-only 重试、两组 boot 诊断,以及390×844无横向溢出。native IPC 为合成边界;截图已检查完整视口。描述内五张作者图片保留其原测试提交和 installed/synthetic 边界。未查询或等待 CI,未把11个 ignored、历史缺少可选发布包输入或作者早先 loopback 间歇失败改写为本机通过。
语义与 CI 对齐
沿用既有 phase/action/channel 和本窗口服务所有权,platform 为派生投影,没有新增 Goal/Agent 权限或义务。前端文字是说明,feed、签名、origin 与 native action 才执行约束。当前违反的是已接受的 smoke 覆盖保留规则;不是要求 Windows-off 路径增加功能,也不以官方 canary 未跑来重复旧阻塞。
我的整体评价
REQUEST_CHANGES,仅要求恢复这组独有的共享前端负向覆盖。main/NSIS 的更新和恢复是有价值且相称的增量;long_horizon 与 user_experience 在已验证的更新、错误恢复和 stable 发布路径上改善,不能从测试/回执数量推导完整长程收益。共享生命周期的局部重构已应用,未发现需要再建框架;官方发布资格与父 RFC 的其余平台保持明确缺口。修复回归覆盖后按新 exact head 重审,不继承旧批准,也不要求重复已解决的问题。
English verdict: REQUEST_CHANGES at 8eadd9a. The native lifecycle, bounded installed-NSIS receipt, stable build/upload isolation and visual fixes are retained. The current-base diff drops59 lines of unique production-bundle transfer-failure/no-install/check-only-retry and boot regressions; remaining VM/native checks are not equivalent dashboard coverage. Restore those existing smoke blocks while keeping Windows-channel cases. Current local Rust,desktop,types,build,CLI/shell and source gates pass within the stated bounds; no official Windows rollout or CI qualification is claimed.
| await delayedRecoveryStatus.returned.promise; | ||
| await page.waitForTimeout(50); | ||
| assert.equal(await page.getByText("恢复上个版本", { exact: true }).count(), 0, "an older terminal read cannot replace current recovery metadata"); | ||
| checkUpToDate = false; |
There was a problem hiding this comment.
[P2] Preserve the existing transfer-failure integration regressions
Against current base98709770, this rebase removes59 lines covering generic and six bilingual timeout/signature/network failure states, no Apply or install effects on read, check-only retry, narrow failure presentation, and boot diagnostics/no-effects. The remaining VM diagnostics test only exercises boot.js and native Rust transport tests never render DesktopUpdate, so they do not replace this dashboard/control boundary. Current production-bundle probes still pass these meaningful cases; the issue is lost durable protection, not a newly reproduced runtime bug. Restore the existing main failure blocks alongside the Windows channel cases, or retain equivalent tracked integration coverage, then rebuild and run desktop-update-browser-smoke.mjs.
|
Restored the shared failure coverage on
PR description updated to tested revision CI on the new head is rerunning; the previously approved head's desktop checks ( |
The signed updater pipeline only built macOS, so Windows desktop kept manual updates. Build the signed NSIS updater installer on windows-latest, advertise windows-x86_64-nsis in the main-channel feed (fail closed when artifacts are incomplete), and open the App's Windows gate for the signed update journey: manual check/install plus the launch-time automatic update. Windows install errors keep the safe-restart classification because the pinned installer only runs after ShellExecuteW succeeds and the process exits at that point. The macOS-only repair, align and verified-backup rollback now report an explicit unavailable state on Windows instead of a half-built path. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
Record what ships now: main-channel signed NSIS updates with passive install and auto restart, and that the stable channel, MSI installs and App rollback/repair stay manual or unavailable on Windows for now. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…orkflow The feed now validates that a published .sig decodes as a minisign signature instead of only checking that it is non-empty, so a truncated or unrelated file cannot reach clients. Tests pin the signed Windows build step, its embedded-version assertion, and the channel-pointer upload staying inside the advance branch. The artifact rename loop is idempotent. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…path install_failure_state no longer reclassifies a verified safe restart as app_install_incomplete when the continuation journal does not apply; the staged-replacement journal exists only where a replacement can be interrupted (macOS). The platform/action gate becomes a pure table so both host columns are covered from any test process, and the navigation fence pins the transport-alias port and scheme lookalikes. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
The boot surface and the dashboard panel suggested repair or rollback in states that have no dedicated error code, while the Windows release disables repair and hides rollback. Both surfaces now switch to repair-free wording when the bundled-runtime repair is unavailable, and the desktop update browser smoke asserts that wording and the disabled control. The smoke also runs on Windows. The root README names the missing rollback/repair and the absent Authenticode certificate. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…eir consumers The feed now parses the decoded minisign structure the client needs - four lines with 74-byte and 64-byte payloads - instead of only checking the comment prefix, so a partially written .sig fails before publication. Tests add the comment-only, wrong-payload-length and embedded-newline cases, plus the rename idempotence guard. finalize_install_failure no longer attempts the staged-replacement journal effect where no such journal can exist, with a test pinning the classification and the incomplete-installation latch. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…e cause The malformed-signature loop left the Darwin signature invalid for the whole iteration, so the second assertRaises fired because of the Darwin platform and never proved the Windows validator. Restoring the qualified Darwin signature between the two builds makes the Windows rejection load-bearing; a Windows-only skip of the validator now fails the test. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
The macOS DMG bundling step failed once without a code cause (the same job passed on the previous head and on another branch within the same minute), and a superseded run left the Release Artifacts build cancelled. No content change. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…ndoff The pinned updater (tauri-plugin-updater 2.11.0) verifies the artifact, unpacks the NSIS archive, runs its pre-exit hook and only then launches the installer; as soon as that launch succeeds it calls std::process::exit(0), so RunEvent::Exit/ExitRequested never run. The previous shutdown path therefore skipped the route save and `ServiceSet::stop()`: the old App's Chat/status group kept running after the App had been replaced, the new App started a second group on new endpoints, and repeated updates accumulated ownerless processes (command-group spawns with `kill_on_drop=false`). Give the App-owned runtime one owner that the live supervisor, the window exit and the App-update handoff all go through: - `ServiceLifecycle` owns the `ServiceSet`, the shutdown/handoff/reclaimed state and the supervisor park; `begin_app_update_handoff` saves the workspace route, parks the supervisor and stops the owned set through its own job objects, so the group's descendants leave with it while a service this App never started (for example a CLI-owned listener) keeps running. - `RunEvent::Exit/ExitRequested` reclaims through the same owner. - A start that raced the handoff or the closing window is handed back and stopped by the caller that started it instead of being adopted. - The installed hook is deliberately not Tauri's `cleanup_before_exit`: a launch that fails returns in-process, and hiding the window and clearing resource tables would leave the operator without the window they retry from. On that failure the handoff is released, the supervisor restores the same runtime in place and the failure snapshot stays on screen. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…andoff The review asked for a Windows regression that crosses the real boundary instead of asserting the failure cause, so this test drives the pinned tauri-plugin-updater through a real loopback HTTP feed, its own minisign verification of a throwaway key, its own ZIP extraction of an NSIS archive, and the real ShellExecuteW launch that Windows refuses for the payload -- against a real window on the mock runtime. On that failure path it asserts: - the pre-exit hook is the same hook the production window installs (`app_update_pre_exit_hook`), so the hook under test cannot drift from the one `perform` runs; - exactly this App's owned service group, descendants included, is reclaimed while a service this App never started keeps running; - the App and window resource tables and the live window survive, which the plugin's default `cleanup_before_exit` hook would have cleared -- a contrast run with that default proves the assertions detect the difference; - the released handoff owes exactly one restore, settled by a live set. A tampered artifact announced with the same signature must fail the plugin's verification first, so the "verified" claim above is not vacuous. The Windows resource is linked into every target so the libtest binary carries the Common Controls v6 manifest, and the Windows-only dev-dependencies build the throwaway key, signature and NSIS archive. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…atic-update split Rebasing onto main's prepared-update work splits the launch-time automatic update: an ordinary open consumes an already prepared archive (macOS), while a CLI-requested upgrade keeps the bounded online check. Three integration points were missing on the rebased base: - Windows has no staged-replacement archive to consume -- the pinned plugin installs only from a checked feed -- so an ordinary Windows launch keeps the bounded check and applies the verified installer before any App-owned service exists. That is the journey this PR's receipts verified; macOS keeps main's prepared-archive flow. - The update panel's idle copy names that difference instead of promising the macOS prepare/next-open cadence on the Windows App. - `finalize_install_failure` gained the platform journal flag in this PR, so main's `rejected_prepared_update_keeps_diagnostics_without_false_boot_stop` now passes that flag and asserts the platform's own journal expectation instead of the macOS one. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
… runner The lifecycle and update regressions behind the signed Windows updater are Windows-native (service job objects, `ShellExecuteW`, real child processes) and cannot execute on the macOS validate leg. Compile the whole test target on windows-latest and run those regressions there, so the review's requested Windows regression is enforced by CI instead of only by local receipts. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…-update copy Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
The crate's build script requires the signed runtime archive the macOS validate leg prepares; without it the new native-Windows test job fails inside tauri_build before any regression runs. Prepare the same archive here and keep the Rust toolchain and cache steps after it. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
The idle panel copy promised that the Windows App checks and installs at launch on the selected channel. The feed publishes Windows NSIS artifacts on the main preview channel only, so an installed stable build advertised automatic updates it could never receive; MSI installs have no in-App updater at all. Project the host OS in desktop_update_status and choose the idle copy from that platform plus the selected channel: stable/Windows names the manual signed installer path, main/Windows keeps the launch-time promise, macOS keeps the prepared-update wording, and an older payload without the field falls back to neutral channel wording. The browser smoke now covers macOS, Windows stable, Windows main and the English stable copy, and rejects the old unconditional Windows sentence. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
Windows NSIS is advertised on the main channel only. Running it in the same required build matrix as macOS meant a stable Windows packaging failure could withhold the Darwin-only stable feed before any publish. Split the build into a macOS job for stable/main and a Windows job gated to main. Publish now uses explicit result checks: it runs when the Darwin job and validate succeed, and additionally requires a successful Windows job on main; a skipped Windows job is valid only on stable. Add a workflow regression that evaluates the publish condition for both channels and pins the job dependency edges. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
Stable releases intentionally build only the Darwin artifacts. Enable nullglob before the upload enumeration so the unmatched .exe pattern does not reach gh and abort before the feed and pointer upload. Add a shell-level regression that runs the production upload block for stable Darwin-only, main complete, and main missing-Windows inputs. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
The rebase conflict in the update smoke was resolved from the branch side, which dropped main's general/transfer-failure and boot readback blocks. Those blocks cover the durable dashboard behavior: failures are readable without installing, only check can retry, and boot diagnostics stay side-effect free. Restore those blocks from current main and keep the Windows stable/main channel cases. Also keep main's "open the local version first" idle copy for macOS, with the manual-update sentence, instead of replacing it with the older prepared-update wording. Validated with the production bundle: desktop-update-browser-smoke passed, tests/desktop 28 passed, dashboard tsc --noEmit passed. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
6ce607c to
7392feb
Compare
|
Restored the shared coverage and rebased again on latest main. New head:
PR description updated to tested revision |
|
@loopx-agent The P2 in review #5598385819 is addressed on I rebuilt Validation on the new head: production-bundle smoke passed, Please re-review the exact head |



Goal And Delivered Outcome
main, the signed desktop-update pipeline builds macOS only (desktop-updater.ymlhas a singlemacos-15matrix entry;scripts/desktop_update_feed.pypublishes onlydarwin-aarch64), and the App's update surface is gated to macOS. A Windows install therefore has no signed-update journey at all, only manual installers and a separately installed CLI.823008357(loopback feed + test key; the channel/platform copy logic is unchanged at7392feb15, details in the receipt comment). The receipt covers successful replacement/restart and a verified-but-unlaunchable failure with service recovery; the official-feed/key and policy-blocked-installer variants remain for the canary.main: rebased ontoc97f921e2f18fbd7e155495b7eb5029031c97fe3for this revision (see Validation).Included Fix
Windows webview boot surface: while running the real-machine journey, the packaged Windows App stayed on
about:blankbecause the navigation fence accepted only thetauri://scheme and the workspace origin, while Windows serves the bundled boot page over the WebView2 transport aliashttp://tauri.localhost.workspace_navigationinlib.rsnow also accepts that exact boot origin, covered by the extendednavigation_keeps_the_workspace_fence_and_hands_external_web_links_off_onceunit test: both boot variants stay inside the fence, lookalike hosts (http://tauri.localhost.evil.example/,http://elsewhere.localhost/) are handed off to the browser exactly once, andfile:///javascript:/mailto:remain unreachable. This repairs pre-existing Windows behavior that the delivered update journey depends on; without it the update surface cannot render on Windows at all.Channel-scoped panel copy (re-review finding,
823008357):desktop_update_statusnow exposes the hostplatform, and the update panel chooses its idle copy from that platform plus the selected channel. Windows stable/MSI names the manual signed-installer path, Windows main keeps the launch-time NSIS promise, macOS keeps the prepared-update wording, and a legacy payload withoutplatformfalls back to neutral channel wording. The browser smoke asserts macOS, Windows stable, Windows main and English stable, and rejects the old unconditional Windows sentence. The re-review confirmed the fix on the current production bundle.Author-side installed-NSIS receipt (test key/loopback feed): installed A
0.0.0-main.1.0, updated to B0.0.0-main.2.0through the real panel; the old App and both owned services exited, B restarted, new owned services connected, and an independent CLI process survived. A signed invalid-PE payload stopped the owned services, Windows refused launch, and after dismissing the OS error dialog the same window returned toapp_install_failedwith services restored and retry available. Details, PIDs and hashes are in the receipt comment.Stable release isolation (re-review finding): Windows NSIS now builds in a main-only
build-windowsjob.build-macosremains the stable/main leg, andpublishuses explicit result checks: a stable release publishes the Darwin feed whenbuild-windowsis skipped, while a main release still rejects a skipped or failed Windows build.tests/desktop/test_update_feed.pyevaluates the publish condition for both channels and pins the dependency edges.Stable upload consumer (re-review finding,
dcbc04589): the upload step enablesnullglobbefore enumerating.gz/.sig/.exe, so a Darwin-only stable release reaches the immutable feed anddesktop-stablepointer instead of passing the unmatched glob togh. Main still builds Windows and the feed still rejects a missing Windows artifact. The regression executes the exact production shell block for stable Darwin-only and main complete, checking theghcall log and publication order. Main with missing Windows fails in the preceding feed builder before the upload step.Shared smoke coverage restoration (re-review finding): the rebase had dropped main's general/transfer-failure and boot-readback blocks from
examples/desktop-update-browser-smoke.mjs. Restored those blocks from main while keeping the Windows stable/main channel cases, and kept main's "open the local version first" macOS copy with the manual-update sentence. The production-bundle smoke passes with the restored failure, no-install, check-only-retry and boot-diagnostics assertions.Review-hardening round (owner review of the delivered journey): the feed now rejects a
.sigthat is not a decodable minisign signature instead of only checking for non-empty content, and the workflow tests pin the signed Windows build step, its embedded-version assertion and the pointer upload staying inside the advance branch, while the artifact rename becomes idempotent. Rust-side, a failed install no longer folds the macOS continuation journal into a Windows-safe restart (install_failure_statetakes an explicit journal-applies input), the platform/action gate is a pure two-column table so both host columns are covered from any test process, and the navigation-fence test pins transport-alias port and scheme lookalikes, the published.sigmust match the four-line minisign structure the client parses (74-byte and 64-byte payloads) with tests for comment-only, wrong-length and embedded-newline inputs, andfinalize_install_failureno longer attempts the journal effect where no such journal can exist. Both update surfaces stop naming repair or rollback in states where this release disables them (boot.jsunrepairedFallback,desktop-update.tsxphaseMessage), the desktop update browser smoke asserts that wording, and the smoke's static-file guard now also serves Windows paths so the smoke runs on Windows.Author Declaration
Implemented against
main@dc9fdbb51. That comment said "no PR before direction agreement"; the repository owner then directed proceeding, with these decisions: minisign only (no Authenticode in this phase), NSIS only (MSI stays manual), main channel first and the stable rollout as a separate maintainer decision..github/workflows/desktop-updater.yml,scripts/desktop_update_feed.pypython -m unittest discover -s tests/desktop -p 'test_update_feed.py'QUALIFIED_PLATFORMS["main"]test_immutable_source_and_qualified_platformbuild()inscripts/desktop_update_feed.pytest_windows_artifact_fails_closed_on_the_main_channelQUALIFIED_PLATFORMS["stable"]test_stable_channel_excludes_windows_until_that_rollout_is_approvedapp_update_action_supportedinmaintenance.rswindows_keeps_the_signed_app_update_journey_without_macos_recovery_actions(CIvalidate, macOS)update_action_platform_unavailable+boot.js/desktop-update.tsxcopyautomatic_app_updateinmaintenance.rswindows-desktop-testsnative regression; author-side installed receipt at823008357tests/test_ci_resource_policy.py,cargo fmt --check(reports a pre-existingservices.rsdiff also present onupstream/main; not claimed clean),cargo clippy --all-targets --locked,examples/desktop-release-workflow-smoke.pyandscripts/check_rfc_bilingual.pylocally. Deliberately left out: no MSI feed entry, no Authenticode, no Windows rollback mechanism, no stable-channel change. An author-side installed-NSIS receipt was produced on this head with a loopback feed and test key; it covers the success and launch-failure journeys. The official-feed/public-key and policy-blocked-installer variants are not claimed.Scope And Continuation
Validation
7392feb15368a26175cd99a9884213b6633e5bab(merge basec7d8c3c6fad76e297da6b5e5eaef7c28b647ccee). The installed-NSIS screenshots were captured at823008357;git diff --quietshowsservice_lifecycle.rsandlib.rsbyte-identical at7392feb15;desktop-update.tsxkeeps the channel/platformidleMessageand now uses main's "open the local version first" macOS copy.cargo fmt --checkreports a pre-existingservices.rsformatting diff identical onupstream/main; no Rust file is changed by this round, so formatting is not claimed clean.python -m unittest discover -s tests/desktop: 28 passed at7392feb15, including the exact production upload-loop regression for stable Darwin-only, main with Windows complete, and main missing Windows.cargo test --locked --lib -- service_lifecycle --nocaptureatdcbc04589: 6 passed, including the Windows update handoff and failed-launch recovery paths.cargo test --locked --libon Windows at823008357: 134 passed, 1 failed, 2 ignored. The single failure is the pre-existingservices::tests::timed_output_large_output_does_not_deadlockin the untouchedservices.rs; this host resolvespython3to the Microsoft Store stub.npx tsc --noEmitandnpm run build:chatat823008357.examples/desktop-update-browser-smoke.mjsat823008357, including the macOS, Windows stable, Windows main and English stable idle-copy assertions and the repair-free wording.tests/desktop/test_update_feed.pyevaluates thepublishcondition (stable with a skipped/failed Windows leg publishes Darwin; main rejects it), pins the build/publish dependencies, and executes the exact upload shell block with a fakeghlog. Stable Darwin-only uploads the feed and pointer without an.exeor literal glob; main uploads its Windows installer; feed upload precedes pointer upload; main without Windows fails closed in the feed builder.loopx canary premerge --from-git-diff --git-diff-base upstream/mainatdcbc04589: diff hygiene, changed-Python compile, all 8 risk-profile smokes, public/private boundary and 9/10 catalog canaries passed. The remaininghot-path-interface-budget-smokefails in the pre-existing Windows extension-doctor executable-bit check;readiness.pyis outside this diff.dcbc045validateandwindows-desktop-testspassed atdcbc045(including the previously flaky loopback test). The unrelatedchat-bundle-browser,windows-powershellandRelease Artifacts buildjobs still fail outside this diff; some Python shards were still running. The new head7392feb15reruns the checks; release build jobs are skipped on PR as expected.0.0.0-main.1.0-> panel update B0.0.0-main.2.0: old App PID 41724 and owned services 43428/31044 exited, B PID 52560 restarted, new owned services 43608/28136 connected, independent CLI PID 37672 stayed alive (HTTP 200). Failure: signed invalid-PE ZIP stopped the owned services via the pre-exit hook, Windows refused launch, and after the OS error dialog was dismissed the same App/window returned toerror/app_install_failedwith services restored (40576/44664), retry available and the independent CLI alive. Bounded: loopback feed + test key, repository CLI viaLOOPX_BIN, invalid-PE payload rather than a policy-blocked installer.Frontend / Visual Evidence
about:blanktransport page (see Included Fix), so the update panel did not render at all, anddesktop_updatereturned the genericplatform_update_not_supportedfallback. The "this channel has no update package for this platform" copy is the post-change behavior when a channel has no Windows entry, not the pre-PR state.repair/align_runtime/rollbackreturn the explicitupdate_action_platform_unavailablestate with dedicated Chinese and English copy, and states without a dedicated code switch to repair-free wording instead of naming the disabled repair or hidden rollback controls; the check / install / restart journey renders as on macOS. The idle copy is channel- and platform-scoped: Windows stable/MSI points at the manual signed installer, Windows main names the launch-time NSIS path, macOS keeps the prepared-update wording, and a legacy status withoutplatformuses neutral channel wording. Buttons remain hidden or disabled through the existingbundled_repair_available/rollback_availablestate fields - no layout or control change.823008357run (loopback feed + test key); the browser images use synthetic native status from the same tree. The channel/platform copy andservice_lifecycle.rs/lib.rsare unchanged at7392feb15;desktop-update.tsxnow also carries main's current macOS idle copy. No private data.Installed A, real panel:
0.0.0-main.2.0is available after the signed check.After the passive NSIS install and automatic restart: the B workspace is interactive.
After the refused launch and dismissing the Windows error dialog:
app_install_failed, services restored, check/retry still available.Windows-stable idle copy in the production bundle at 1280×900: manual signed installer path, main/NSIS scope.
The same copy at 390×844; the panel wraps without horizontal overflow.
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
Coordination And Merge
apps/**behavior changes require maintainer authorization; merge readiness is left to the maintainer after validation.loopx update/--loopx-upgrade-request) remains macOS-only, and a Windows desktop keeps using the separately installed CLI.build-windowsjob. A stable release publishes the Darwin-only feed without depending on that leg, while a main release still requires it; stable release pages keep the unsigned installers for manual installation.always()plus per-job result checks), not a prose convention: the workflow regression intests/desktop/test_update_feed.pymodels stable-Darwin success with a skipped/failed Windows leg and main rejection of a missing Windows leg.Boundary Checklist
python3 scripts/check_rfc_bilingual.py --base main --head HEADpasses, or no paired RFC changed (no paired RFC changed; the check passes).none.Signed-off-bytrailer (git commit -s).