Skip to content

fix(desktop): fall back to older verified rollback backup - #6010

Merged
huangruiteng merged 1 commit into
loopx-project:mainfrom
mikamikasuki:codex/loopx-rollback-backup-fallback
Oct 9, 2026
Merged

huangruiteng merged 1 commit into
loopx-project:mainfrom
mikamikasuki:codex/loopx-rollback-backup-fallback

Conversation

@mikamikasuki

Copy link
Copy Markdown
Contributor

Goal And Delivered Outcome

  • Outcome basis / optional anchor: Reproduced macOS rollback defect in the retained-backup selection path.
  • Goal/source and gap: prepare retains rotated App backups as older-<sequence>, and the restore copy boundary verifies each App signature. Selection previously returned any structurally present previous directory first. If that App became corrupted after rotation, restore failed without trying a retained valid backup.
  • Observable before → after, with the validation row that proves it: On base bae23168dc0df7da094a52cbb2ace0090400dabd, a signed synthetic previous App was tampered with while older-1000 remained valid; selection returned the corrupt previous. The regression now confirms restore selection skips it and chooses the latest older App that has a readable version and passes the signature check.
  • Issue/task and intended base: Self-contained defect; base main at bae23168dc0df7da094a52cbb2ace0090400dabd.

Author Declaration

  • Written by: model_agent — GPT-6 Luna Medium (OpenAI)

Implemented against

  • Specification and revision: Existing backup integrity and retention contract in apps/desktop/loopx-control-plane/src-tauri/src/update_backup.rs at bae23168dc0df7da094a52cbb2ace0090400dabd: prepare retains older backups and copy accepts an App only after codesign --verify --deep --strict succeeds.
  • Criteria:
Criterion (spec clause) Disposition Symbol / path Test or command
Restore only from a backup with a readable version and valid signature implemented verified_recoverable_backup, restore update_backup::tests::corrupted_previous_backup_falls_back_to_latest_valid_older_backup
Try retained backups newest first when a newer candidate is invalid implemented recoverable_backups Same signed-bundle regression test
  • Self-check before submission: Reproduced the selector failure on the stated base, confirmed the synthetic older App verifies and the tampered App does not, ran the full Rust crate tests and Clippy, and reviewed the final one-file diff.

Scope And Continuation

  • Completed scope and remaining work: Restore selection now checks previous first, then older-* 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.
  • Slice boundary / successor: Complete within this scope. The status probe remains a structural availability check; restore performs signature verification before using a candidate.

Validation

  • Tested revision: 10dd711cbcd87de5ea2ee3729d67e657cb2a5c8f
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
regression_parity passed On base bae23168dc0df7da094a52cbb2ace0090400dabd, the signed-bundle reproduction selected corrupt previous instead of valid older-1000; on this head, the regression selects older-1000.
unit passed cargo test in the desktop Rust crate: 104 passed, 0 failed; 3 release-environment tests ignored.
static passed cargo fmt --check; cargo clippy --all-targets -- -D warnings; git diff --check.
static passed loopx check public-boundary scan of the changed source: 0 errors; two local registry warnings did not concern the changed file.
  • Coverage and gaps: The regression uses ad-hoc-signed synthetic macOS Apps and verifies the same codesign gate 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

  • UI impact: none; no visual surface changed.
  • Before: N/A
  • After: N/A
  • States and viewports shown: N/A
  • Source data: synthetic
  • Attention review: N/A; no visual or interaction layout changed.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Build, packaging, installer, or CI
  • Host or runtime integration

Technical Direction

  • Direction / acceptance reference, when applicable: N/A; this is a scoped rollback recovery defect.

Shared-authority RFC fixture impact

  • Production-scale fixture schema: N/A.
  • Semantic dimensions changed, or reviewed no-impact rationale: N/A; no shared-authority vocabulary or schema changed.
  • Provider conformance arms run: N/A.
  • Read-only legacy/file/PostgreSQL three-arm rehearsal: N/A.

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths.
  • I did not duplicate maintainer-owned benchmark work; this change does not touch benchmark work.
  • I kept the change scoped to the reproduced rollback defect.
  • I completed the visual evidence section; no visual surface changed.
  • Every commit includes a DCO Signed-off-by trailer.

Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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。

关键代码讲解

  1. recoverable_backups(144 行)把 previous 放首位,再把数字 older- 序列倒序列出;原结构观察 recoverable_backup 仍取首项,未把“可见”说成“可执行”。非法序列名按原语法忽略。
  2. verified_recoverable_backup(175 行)要求 version 可读并通过原 codesign --verify --deep --strict,随后选第一个有效候选。不存在新的签名标准、持久状态或配置字段。
  3. 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.

@huangruiteng
huangruiteng merged commit 376644b into loopx-project:main Oct 9, 2026
30 of 36 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.

3 participants