Repository navigation
fix(desktop): fall back to older verified rollback backup - #6010
huangruiteng merged 1 commit into
Conversation
Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=xhigh
APPROVE:未发现阻塞问题。精确 head 10dd711cbcd87de5ea2ee3729d67e657cb2a5c8f,实际差异基线 bae23168dc0df7da094a52cbb2ace0090400dabd。
动机
在 macOS 桌面 App 中用已有回退按钮修复损坏安装的用户。最新 previous 备份在轮转后损坏时,旧版即使保留了可用 older 备份也会直接回退失败;现在按最新到最旧跳过缺失版本或签名损坏的候选,选择第一个可用备份,再经过原有复制校验、日志和原子替换。真实 ad-hoc 签名的合成 App 对照证明旧版选择损坏的 previous,当前选择最新有效 older 并修复损坏目标;全部不可用时安全拒绝,正常 previous 和复制前再次损坏的保护保持。 不修改更新权限、签名标准、备份写入格式、现有界面布局或运行中的 App 安装;不宣称完整签名发布 App 的回退旅程已独立验收。 原问题会把本来可用的恢复路径变成人工处理,本次在原安全边界内恢复了这条路径。
改动思路
备份选择与签名约束继续归现有 Rust update_backup owner;共享排序消除重复知识,观察状态与执行校验保留各自成本和作用。 当前 PR 只修复现有回退动作的候选选择并验证复制前后的安全边界,不扩展更新权限、界面或备份格式。 不改动会继续卡在损坏的 newest;直接删掉签名检查会让损坏备份覆盖安装,另写一套候选顺序又会与状态展示漂移。现有 owner 足以完成:一个内部列表同时服务便宜的结构观察与真实回退选择,只有执行时才逐个做签名检查。选择后的复制再次验证仍有必要,因为源文件可以在两次检查之间损坏。
具体改动
没有独立书面接受规格;判断来自改前 update_backup.rs@bae23168dc0df7da094a52cbb2ace0090400dabd 的 prepare 保留 older 备份、copy 强制签名校验与 restore 的受信目标、日志和原子替换契约,未拿 author test 输出作为规则。完整 diff 仅这一文件 +52/-7,其中一个真实签名合成 App 回归保护损坏 previous 后的 fallback。
关键代码讲解
recoverable_backups(144 行)把 previous 放首位,再把数字older-序列倒序列出;原结构观察recoverable_backup仍取首项,未把“可见”说成“可执行”。非法序列名按原语法忽略。verified_recoverable_backup(175 行)要求 version 可读并通过原codesign --verify --deep --strict,随后选第一个有效候选。不存在新的签名标准、持久状态或配置字段。restore(219 行)只替换候选选择;已有desktop_update的 rollback 调用仍在 blocking worker 中执行,当前运行 exe 决定目标,copy 再次验签,原 pending journal 在 swap 前写入;EXCHANGE/NOREPLACE 与原件保留不变。
对主干的风险
独立 exact-crate cargo test --locked:104 passed,三个既有 release-environment 测试 ignored;fmt、Clippy all-targets -D warnings 和 diff hygiene 通过。额外三项独立 source oracle 用真实 macOS codesign、ditto 与原子文件操作:两个有效 older 前放更高序列的损坏/缺失版本候选以及损坏 previous,当前选择最新有效项并真正修复损坏目标;全不可用返回 None;正常 previous 保持优先;选择后源再损坏时 copy 拒绝、journal callback 不调用、原目标仍完整。相同规则与 fixture 在 base 上两个 selector 断言失败、正常/复制重校验通过,head 三项通过,不是只验证 happy path。
oracle 编译实际生产模块的同一源码,保留源 hash;AppHandle 日志适配器是没有调用的 stub,因此不宣称 UI→真实 AppHandle→重启资格。独立 exact-crate 测试同时覆盖并发目标、缺失目标、journal 失败及交换失败。首次编译缺少 ignored runtime resources,补齐仅用于 test build 的本机资源后重跑通过;这些资源没有被安装或当当前发布包验证。没有查询或等待 CI,也没有替换运行中的 App。
我的整体评价
long_horizon 与 user_experience 在这个具体回退修复均 improved:保留可用的 older 备份现在能继续恢复,正常路径和安全拒绝保持,用户无需新增权限或重复输入目标。设计 proportionate,单文件可回滚。备份选择与签名约束继续归现有 Rust update_backup owner;共享排序消除重复知识,观察状态与执行校验保留各自成本和作用。 当前 PR 只修复现有回退动作的候选选择并验证复制前后的安全边界,不扩展更新权限、界面或备份格式。 未来维护已通过共享候选排序这个小重构改善;继续复用现有签名与 journal/swap,不需要新的 backup framework。新增派生列表属于本地 helper,没有新共享 vocabulary。完整签名发布 App 的 packaged UI、实际 AppHandle journal 与重启没有独立执行,三个 release-only tests 继续保留该证据边界。
English verdict: APPROVE - 10dd711; the complete native rollback diff reuses existing integrity and target authority. 104 crate tests pass, three real macOS source/OS oracles pass; identical base oracles produce two intended selection failures and one parity pass. Formatting, Clippy and diff hygiene pass; three release-only tests remain ignored and the full signed packaged-App journey is untested. CI not consulted; no merge or installation performed.
Goal And Delivered Outcome
prepareretains rotated App backups asolder-<sequence>, and the restore copy boundary verifies each App signature. Selection previously returned any structurally presentpreviousdirectory first. If that App became corrupted after rotation, restore failed without trying a retained valid backup.bae23168dc0df7da094a52cbb2ace0090400dabd, a signed syntheticpreviousApp was tampered with whileolder-1000remained valid; selection returned the corruptprevious. The regression now confirms restore selection skips it and chooses the latest older App that has a readable version and passes the signature check.mainatbae23168dc0df7da094a52cbb2ace0090400dabd.Author Declaration
Implemented against
apps/desktop/loopx-control-plane/src-tauri/src/update_backup.rsatbae23168dc0df7da094a52cbb2ace0090400dabd:prepareretains older backups andcopyaccepts an App only aftercodesign --verify --deep --strictsucceeds.verified_recoverable_backup,restoreupdate_backup::tests::corrupted_previous_backup_falls_back_to_latest_valid_older_backuprecoverable_backupsScope And Continuation
previousfirst, thenolder-*entries in descending sequence order, and chooses the first candidate with a readable version and valid App signature. The staged copy is still verified again before the swap.Validation
10dd711cbcd87de5ea2ee3729d67e657cb2a5c8fregression_paritybae23168dc0df7da094a52cbb2ace0090400dabd, the signed-bundle reproduction selected corruptpreviousinstead of validolder-1000; on this head, the regression selectsolder-1000.unitcargo testin the desktop Rust crate: 104 passed, 0 failed; 3 release-environment tests ignored.staticcargo fmt --check;cargo clippy --all-targets -- -D warnings;git diff --check.staticloopx checkpublic-boundary scan of the changed source: 0 errors; two local registry warnings did not concern the changed file.codesigngate used by production. A full signed release-App upgrade/rollback run was not performed; the staged copy retains its existing signature recheck before the atomic swap.Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
Boundary Checklist
Signed-off-bytrailer.