Repository navigation
Preserve workflow skill backups after failed publication - #6211
huangruiteng merged 1 commit into
Conversation
Preserve the previous install when both skill publication and rollback fail. Refuse stale backups before a later install can overwrite recoverable state. Signed-off-by: Duang777 <duangjl007@gmail.com>
|
@loopx-agent review |
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 — 未发现阻断项。本结论绑定 917d76406af4cba7e9820135b3c8c823628084c6,独立核验完整 base→head 和失败后的实际恢复。
动机
升级托管 workflow skill 的操作者,在新目录发布失败且旧目录恢复也失败时,需要保住仍可恢复的旧技能。
此前异常后的 finally 清理会删除唯一旧备份,下一次安装又可能忽略其它进程留下的备份。现在旧副本保留,带未处理备份的安装会明确拒绝;操作者检查并恢复后可以继续安装。
真实 CLI 路径实测双重失败后旧字节保留、重试零内容改动,人工恢复后安装、幂等重装和卸载均成功。
本 PR 只修复已有 global workflow installer 的恢复安全,不自动选择备份、改写项目 Goal、扩大主机权限或重构多技能安装事务。
改动思路
发布和恢复仍使用既有目录替换与文件锁;改变的是旧副本的删除时机。成功发布后可以删备份,异常退出时不能删最后一份可恢复数据。残留备份本身就是文件系统事实,不需要额外 journal、状态字段或新 capability。针对同一个目标名的检查在 unchanged 快路径之前执行,使“live 文件碰巧相同”也不能掩盖未处理恢复现场。
旧副本保存和恢复现场拒绝都属于现有 workflow installer 的文件系统不变量;Python 在这里是既有本地 IO owner,没有新增通用控制面决策源。
当前 PR 的边界是保留失败发布的旧目录,并阻止该目标在残留备份未处理前再次替换;正常安装、读回与卸载复用原路径。
比较了 project-skill delivery、configuration/local-state migration 与 history export 的备份边界。它们的事务范围和恢复 owner 不同,没有理由为本次三行清理调整引入统一抽象或合并独立策略。相邻整理无需扩大本 PR。
具体改动
基线为 be15bf4ffb3bf354fb255853a96f14b047874bb8,2 个文件 +67/-3:生产文件 +10/-3,测试 +57,无生成物、安装 payload 文本或 uv.lock 变化。
独立依据为修改前 docs/guides/installing-loopx.md,spec_revision=be15bf4ffb3bf354fb255853a96f14b047874bb8,以及旧版本中实际可复现的最后副本丢失。验收映射:Host Command Surfaces 已实现,原 CLI 安装与 revision/digest readback 继续工作;Recovery material preservation 已实现,既有保留 sole copies/冲突需明确处理的原则延伸到本 installer 的残留备份;Skill Discovery Scope 保持不变,文件安装仅改变发现路径,不授予运行或外部操作权限。
关键代码讲解
_install_one_skill(workflow_skill_install.py:169)先枚举.<target>.previous.*,命中就带路径报错;检查在 hash 相等返回之前。temporary 仍在 finally 清理,backup 清理由异常分支之后的正常控制流执行。publish/restore 双故障会抛出异常且保留旧 backup;publish 失败、restore 成功仍恢复原 live 目录。workflow_skill_install(同文件 :274)复用_exclusive_install_lock和固定 packaged skill 列表,再生成$loopxentry、写 revision/digest receipt 并独立读回。恢复前拒绝不会伪造新的成功 manifest;这是已有整体调用者,不是只有 helper 能到达的补丁。handle_workflow_skills_command(cli_commands/workflow_skills.py:61)维持显式 install/uninstall/dry-run 分流;安装文件没有获得执行 Goal 或合并 PR 的权限。人工选择恢复材料后走同一 CLI 即可重试。
对主干的风险
最强反例是实际 CLI 在相同 live 内容下绕过备份,或一个不相关的备份错误阻断全部目标。独立故障脚本经 loopx.cli.main workflow-skills 使用真实临时文件系统,仅对 publish/restore 两次 Path.replace 注入 IO 失败。基线旧备份消失且重试覆盖现场;head 旧字节保持,重试明确 RuntimeError 且文件摘要无变化。移回旧目录后,head install 成功、下一次全为 unchanged、uninstall 成功,证明拒绝之后有真实恢复而不只是一条 blocker。
范围反例也实际执行:同 root 的无关技能备份不阻塞 packaged 安装且被保留;安装后新出现的另一 packaged skill 备份会阻止下一轮;相同 live 内容不能逃过检查;dry-run 不执行替换。命中的备份要由操作者检查,这是防止数据丢失的必要恢复步骤,没有新增重复确认或手工同步字段。
3 个新回归在不改生产代码的旧基线上全部失败:双故障后的 FileNotFoundError,以及有/无 live 的残留备份未拒绝。当前聚焦 installer、discovery 与 packaged metadata suite 63/63 通过,基线同套既有测试 60/60;Ruff、registry IO manifest check、semantic advisory 均通过。premerge 的 5 项 direct 与 5 项选中 canary 通过,包括实际 packaged install、update、maintainability 和完整 semantic drift;没有 failure、skip 或 manual hold。未查询远端 CI,没有读写操作者真正的技能目录或发布私有 evidence。
这是默认安全行为的有意修复,PR 描述和新回归明确旧缺陷与新拒绝;不是新的 opt-in 策略,也未改变自动加载的指令内容。错误文本中“requires inspection”是实际强制拒绝,未将硬义务称作 guidance。未增加 shared vocabulary;检查本地既有备份事实,未创建 authority 生命周期。没有前端/Lark companion 变化;已有 workflow CLI 就是受影响安装入口。
我的整体评价
持续工作与使用体验均为 improved:升级异常之后保留恢复选项,纠正现场后继续使用,而不是永久丢失旧副本。保留既有文件锁和 manifest/hash readback 有实际消费者;独立 project-skill 事务不应强行复用成新的泛化安装框架。此改动与最后可恢复副本丢失的风险相称,生产差异很窄且可回退。
无阻断 finding。尚未证明进程崩溃后的自动恢复、恶意并发 writer 或所有原生 Windows 文件锁语义;本 PR 也不承诺它们。异常保留材料并主动拒绝重试的边界已通过真实 IO 及完整安装恢复链验证。备份究竟对应哪个意图仍需人工检查,避免用猜测自动覆盖。批准不等于 merge 权限,维护者仍负责合并这项 runtime 变更。
English verdict: APPROVE
The exact-head fix preserves the previous skill after publication and restoration both fail, and refuses a stale recovery backup before replacement or the unchanged fast path. Real CLI/filesystem tests demonstrate baseline data loss, head preservation, scoped refusal, and successful manual recovery followed by install/reinstall/uninstall. Local checks pass; remote CI was not consulted. No new authority or automatic recovery is granted.
Summary
.previous.*backups before the unchanged fast path or a new replacementRoot cause
_install_one_skilldeleted its backup infinally. If publishing the staged skill and restoring the previous install both failed, cleanup removed the only recoverable copy. A later process also ignored backups created under another PID and could continue from incomplete recovery state.Validation
FileNotFoundError; both stale-backup cases failed to raise.tests/test_workflow_skill_install.pypasses, 28 tests total.python -m ruff check loopx/workflow_skill_install.py tests/test_workflow_skill_install.pypython scripts/generate_project_registry_io_manifest.py --checkpython scripts/generate_semantic_inventory.py --changed-from HEADloopx check --scan-path loopx/workflow_skill_install.py --scan-path tests/test_workflow_skill_install.pyready=true, a repeated install reported onlyunchanged, and uninstall reportedok=true.loopx canary premerge --from-git-diff --git-diff-base upstream/mainpassed all 5 selected canaries.Sibling sweep
Reviewed configuration backup, local state migration, static-site publication, and OpenViking history export. Those paths keep backups until publication succeeds or fail closed on stale recovery state. This PR does not change them.
uv.lockis unchanged.