Tolaria ADR-0042:自动清除回收站笔记的安全模型——从五重校验到被“确认删除“取代的演进
2026/9/13 19:26:03 网站建设 项目流程

Tolaria ADR-0042:自动清除回收站笔记的安全模型——从五重校验到被"确认删除"取代的演进

【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria

本文以 Tolaria 的架构决策记录 ADR-0042《Trash auto-purge safety model》为主体,完整还原这套"自动永久删除"机制的设计:五重安全校验、操作系统回收站软删除、按小时节流的触发条件与审计日志;同时结合仓库当前源码,说明该方案后来如何被 ADR-0045 取代,以及如今 src-tauri/src/vault/trash.rs 中实际的删除实现长什么样。读完你会理解:为一个高危不可逆操作设计"防御性删除"时,校验清单、恢复路径与触发节流各自解决什么问题,以及为什么有些项目最终选择用"确认弹窗 + git 历史"替代整套回收站机制。

背景:UI 承诺了 30 天清理,但从未真正执行

ADR-0042(docs/adr/0042-trash-auto-purge-safety-model.md)的出发点非常具体:Tolaria 的 Trash 视图界面上已经展示了一句警告——"Notes trashed more than 30 days ago will be permanently deleted"(回收超过 30 天的笔记将被永久删除),但应用从未真正执行过这条规则。文档给出的态度是:既然向用户承诺了,就必须实现;否则就是虚假承诺。

文档同时开宗明义地强调了这件事的风险等级——这是整个应用中最危险的操作之一:一次 bug 就可能导致不可逆的数据丢失。因此安全模型必须"显式且保守"(explicit and conservative)。这两句话构成了后续所有设计约束的源头:每一条校验规则、每一个触发条件、每一行审计日志,都是在回应"不可逆"这三个字。

决策核心:五重安全校验,缺一不动手

ADR-0042 的决策一句话概括是:在应用启动和窗口重新聚焦时(最多每小时一次)自动清除回收超过 30 天的笔记;使用trash::delete移入操作系统回收站实现软删除;每个文件必须通过 5 项强制安全校验;并写入.laputa/purge.log审计日志。

其中最值得拆解的是"每个文件删除前必须全部通过的 5 项校验":

序号校验项目的
1frontmatter 中存在_trashed: true(或旧别名Trashedtrashed)且为真值确认该笔记确实处于"已回收"状态,而不是普通笔记
2_trashed_at(或旧别名Trashed attrashed_at)存在且可解析为日期确认回收时间来源可靠,解析失败的笔记不删
3解析出的日期严格早于30 天前(恰好 30 天 = 跳过)边界保守:宁可多等一天,不提前删
4文件在预期路径上真实存在于磁盘防止基于过期索引删除不存在的文件
5文件规范路径必须位于 vault 根目录之内防路径穿越,杜绝误删 vault 外部文件

文档还规定了两条执行纪律:

  • 任一校验失败 → 跳过该文件并记录 warning 日志,而不是抛出错误或中断整个流程;
  • purge 永不提前中止——所有候选文件独立处理,一个文件失败不影响其他文件的清理。

这套"逐文件独立判定 + 失败降级为跳过"的模型,本质上是把批量删除操作拆成了一组互不牵连的单文件事务,任何一项证据不足就放弃该文件,从机制上把误删概率压到最低。

删除方式:为什么选操作系统回收站而不是直接删

ADR 明确拒绝了fs::remove_file这种"一步到位"的做法,选择了trashcrate 的trash::delete,把文件移动到 macOS 废纸篓 / Windows 回收站。理由链很清晰:

  • 给用户留一条最后恢复路径:即使 5 重校验全部通过、笔记也被正确标记了 30 天以上,用户仍可能事后意识到"其实不该删",OS 回收站提供了应用外的第二道安全网;
  • 失败降级:如果移入 OS 回收站失败,才回退到fs::remove_file硬删除,并记录 warning——即软删除是首选,硬删除只是兜底。

文档在"Options considered"一节完整列出了三个候选方案的取舍:

  • Option A — OS 回收站(trashcrate)(当时胜出):可恢复,安全默认值,代价是引入一个平台相关依赖;
  • Option B —fs::remove_file直接永久删除:实现最简单、无依赖,但对一个自动后台执行的操作来说,没有恢复路径风险过高;
  • Option C — 移动到.laputa/purged/归档目录:自定义恢复机制,但会污染 vault 目录结构,且用户根本不会想到去那里找回文件。

ADR 还诚实列出了 Option A 的平台代价:trashcrate 在 macOS 依赖NSFileManager、Windows 依赖IFileOperation、Linux 遵循 freedesktop 规范——这是一个跨平台行为不完全一致的依赖,属于"为了安全接受的复杂度"。

触发条件与节流:每小时最多跑一次

自动清除的触发点被限定在两处:

  1. 应用启动时(在run_startup_tasks中执行);
  2. 窗口重新获得焦点时WindowEvent::Focused(true)),并用Mutex<Instant>时间戳做节流,最多每小时执行一次

节流条款直接回应了一个真实场景:用户在多窗口间快速切换时会密集触发 focus/unfocus 事件,如果没有节流,purge 逻辑可能被高频重复执行,造成不必要的磁盘 I/O。用一把Mutex保护一个Instant时间戳,是最小代价的进程内限流实现。

审计日志:.laputa/purge.log记录每一次清除

每次 purge 运行都会向 vault 内的.laputa/purge.log追加写入,内容包含:时间戳、本次检查的文件数、实际清除的文件数、以及每个被清除文件的完整路径。

这个设计把"自动删除"从黑盒变成了可审计操作:用户随时可以打开这个文本文件,核对"什么时间、哪些文件、被自动删了"。ADR 在 Consequences 一节也承认了它的副作用——日志会随时间无限增长,但对纯文本日志而言可以接受;并且预设了复查条件:如果用户反馈 OS 回收站被 vault 文件塞满,需要重新评估。

后续演进:ADR-0045 推翻了整个回收站体系

理解 ADR-0042 不能只看它自己。该文档 frontmatter 中标注了status: supersededsuperseded_by: "0045"。紧随其后的 ADR-0045:Permanent delete with confirm modal — no Trash system 给出了替代结论,而仓库当前源码可以印证这次转向已经落地。

ADR-0045 的论点是:回收站体系引入了过多复杂度——trashed/trashedAtfrontmatter 字段、侧边栏过滤、编辑器横幅、Inspector 组件、专门的 smoke 测试、以及trashcrate 依赖;而用户真正需要的安全保证其实是不可逆操作前的确认提示,不是软删除缓冲——毕竟 vault 本身就是一个 git 仓库(参见 ADR-0014 与 ADR-0034 确立的 git 恢复机制)。该文档记载,2026-04-06 的提交e581ad36一次性移除了整个 Trash 体系(123 个文件变更,约 3164 行删除)。

仓库现状与这一决策互相印证:

  • 现在的删除实现位于 src-tauri/src/vault/trash.rs:delete_note(L6-L17)直接调用fs::remove_file永久删除单个文件,batch_delete_notes批量删除时跳过不存在的文件并记录 warning——与 ADR-0042 中"逐文件独立判定、失败即跳过"的执行纪律一脉相承,只是删除动作本身不再经过 OS 回收站;
  • src-tauri/src/vault/mod.rs 将batch_delete_notes, delete_notetrash模块重新导出,模块名保留了trash的历史痕迹,但实现已是 ADR-0045 定义的"立即永久删除 + 确认弹窗(useDeleteActions)"单一安全门;
  • src-tauri/Cargo.toml 中已无trashcrate 依赖,与 ADR-0045 所述"依赖已移除"一致;
  • 一些化石细节仍可追溯:src-tauri/src/vault/frontmatter.rs 的注释里Trashed仍被列在 YAML 解析失败时的兜底提取字段中,src-tauri/src/vault/mod_tests.rs 仍保留一行指向vault/trash.rspurge_trash测试注释——从源码结构看,这些是回收站时代留下的痕迹,符合 ADR-0045 所述"trashed字段被解析器静默忽略、无需迁移、无数据丢失"的处理方式。

这套演进给"自动删除类功能"的设计启示

把 ADR-0042 与 ADR-0045 放在一起读,能提炼出一条完整的设计推演路径,对任何要实现"自动清理/自动删除"的功能都有参考价值:

  1. 承诺必须可执行:UI 上写出的"30 天后永久删除"要么实现、要么删掉文案,中间状态(承诺了但不执行)是最差的选项;
  2. 不可逆操作要按文件独立校验:把批量操作拆成单文件判定,任一证据不足即跳过该文件而非中断流程,误删面最小化;
  3. 边界取保守值:"恰好 30 天"按不删处理,解析失败按不删处理,宁可慢一天不可错一秒;
  4. 恢复路径要分层:应用内回收站 → 应用内自动清除 → OS 回收站 → git 历史。Tolaria 最终把前两层整体砍掉,只保留"确认弹窗 + git 历史"两层——这提醒设计者先问一句:用户手里是不是已经有一个够用的恢复机制(比如版本库),如果有,软删除缓冲可能只是徒增状态复杂度;
  5. 触发要节流、删除要留痕:自动运行的清理逻辑必须有频率上限,且每次运行应留下可人工核对的审计记录(ADR-0042 的.laputa/purge.log是正面范例)。

需要说明的是:ADR-0042 中描述的purge_trash调度、.laputa/purge.log写入等机制在当前代码中已不存在(对应测试注释虽残留,但模块内已无 purge 实现),本文以仓库文档与源码的实际现状为准——ADR-0042 的价值在于它完整记录了 Tolaria 如何为"最危险的操作"构建安全模型,又如何在实践中承认该模型相对 git 恢复机制属于过度设计,从而完成了从"回收站自动清除"到"确认弹窗永久删除"的收敛。

【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询