ADR-0007: Doclib 文件可达性生命周期
历史决策记录:保留当时的背景、状态和设计。当前接口与实现归属以 开发入口 和现行规范为准;文中的历史模块路径不保证仍存在。
状态: Accepted 日期: 2026-06-12 相关文档: ../architecture.md, ../workflows.md, ../cli/mineru-library.md, 0006-doclib-file-change-detection.md
背景
doclib 不只需要判断“同一路径内容是否变化”,还需要判断“已知文件路径当前是否仍可读取”。这影响:
- watch 删除事件和 scan 发现的缺失文件。
- 可插拔设备拔出与恢复。
get_file_by_path(path)、parse(path)、get_doc_by_path(path)、invalidate(path=...)等同步 path 操作。cleanup deleted与cleanup orphans的边界。fts_filenames与fts_contents的生命周期。
如果不统一建模,系统可能把 removable 设备拔出误判为文件删除,或在 cleanup 时误删仍有历史 file row 关联的 doc。
决策
文件状态
files.status 使用三种状态:
| 状态 | 含义 | 默认 find | 默认 search | 是否保护 doc |
|---|---|---|---|---|
active |
路径当前存在且可访问 | 是 | JSON 返回;CLI 优先展示 | 是 |
unreachable |
所属 removable watch 当前不可达 | 否 | JSON 返回;CLI 无 active file 时展示 | 是 |
deleted |
路径已确认不存在,且不是设备整体不可达导致 | 否 | JSON 返回;CLI 无 active file 时展示 | 是 |
deleted file row 仍保留 sha256,并继续保护对应 doc。只有完全没有任何 file row 关联的 doc 才是 orphan。
删除 / 变更检测入口
所有会观察 source file path 当前状态的入口都必须执行统一文件发现/刷新逻辑。
第一版包括:
- watch file event。
- 所有 scan 操作:
- watch 初始扫描。
- watch 周期 scan。
- removable 设备恢复后的 scan。
- 未来手动 refresh / rescan。
- 所有同步涉及 source file path 的 4 个操作:
POST /parses/ SDKensure_parse(path)/mineru parse <path>。GET /files/by-path?path=.../ SDKget_file_by_path(path)/mineru show file <path>。GET /docs/by-path?path=.../ SDKget_doc_by_path(path)。POST /invalidate { path }/ SDKinvalidate(path=...)/mineru invalidate <path>。
文件缺失判断
当某个已知 path 不存在时,不能直接删除 row。
判断规则:
- 如果该 file 属于 removable watch,且 watch root 当前不可达,标记为
unreachable。 - 否则标记为
deleted,写入deleted_at,保留sha256。 - 标记
deleted或unreachable时不清理 FTS。
可插拔设备
DeviceMonitor 只对 removable=1 的 watch target 做设备可达性判断。
设备不可达:
- watch target 标记为
unreachable。 - 该 watch 下
activefiles 标记为unreachable。 - 不清理 file row、doc、parse cache 或 FTS。
设备恢复:
- watch target 标记为
active。 - 该 watch 下
unreachablefiles 标记为active。 - 立即对该 watch 执行一次 scan:
- 文件仍不存在: 标记
deleted。 - 文件 stat 变化: 清空
sha256,等待或触发 ingest。 - 文件未变化: 保持当前绑定。
Cleanup 边界
deleted file cleanup 与 orphan doc cleanup 是两个独立动作,互不连带。
deleted file cleanup:
- 只删除
status='deleted'的 file row。 - 手动命令立即删除所有 deleted file rows。
- 后台任务只删除保留超过 7 天的 deleted file rows。
- 删除 file row 时清理对应
fts_filenames。 - 不自动删除 docs、parses、parsed JSON 或
fts_contents。
orphan doc cleanup:
- 只删除完全没有任何 file row 关联的 docs。
- 判断条件不区分 file 的
status:
SELECT d.*
FROM docs d
WHERE NOT EXISTS (
SELECT 1 FROM files f
WHERE f.sha256 = d.sha256
);
- 执行 orphan cleanup 时,才删除对应
docs、parses、fts_contents和 parsed JSON。
FTS 生命周期
FTS 生命周期跟随其对应主表记录:
| FTS 表 | 主表记录 | 生命周期 |
|---|---|---|
fts_filenames |
files |
file row 删除时删除 |
fts_contents |
docs |
doc row 删除时删除 |
因此:
- 标记
deleted时不删除fts_filenames。 - 标记
unreachable时不删除fts_filenames。 - 默认
find只返回 active file。 searchJSON 返回文档关联的全部 file aliases 及状态;非 JSON CLI 存在 active files 时只展示 active paths,否则展示全部 inactive paths。
CLI 参数
为简化第一版语义,mineru cleanup deleted-files 不接收 --older-than。
- 手动 cleanup deleted: 立即删除所有 deleted file rows。
- 后台 deleted cleanup: 固定保留 7 天后删除。
替代方案
方案 A: deleted file 不保护 doc
拒绝。用户删除路径不代表内容缓存应立即失效;删除路径记录前仍应保留 doc 的可追溯关联。
方案 B: cleanup deleted 后自动 cleanup orphans
拒绝。清理路径记录和清理内容缓存是两个不同生命周期动作,应由不同命令和后台策略控制,避免隐式连带删除。
方案 C: 标记 deleted 时立即删除 filename FTS
拒绝。fts_filenames 的生命周期应与 files row 一致。默认 find 通过 status='active' 控制可见性即可。
方案 D: search 默认返回 unreachable / deleted
部分拒绝。第一版默认 find 只返回当前可访问文件。search JSON 返回全部 aliases,使调用方能看到 active、unreachable 和 deleted 状态;非 JSON CLI 使用 active 优先展示。完全没有 file row 的已索引文档仍作为 orphan 搜索结果返回。
影响
CleanupService.find_orphan_docs()必须改为“没有任何 file row 关联”,不能只看 active file。cleanup_deleted_files()不应自动触发 orphan cleanup。mineru cleanup deleted-files立即删除所有 deleted file rows。- watch scan 和同步 path 操作需要能把缺失文件标记为
deleted或unreachable。 - DeviceMonitor 恢复后需要触发该 watch 的 scan。
fts_filenames清理必须绑定到 file row 删除,而不是 scan status 变化。
后续动作
- 修改文件发现/刷新逻辑,支持 path 不存在时按 watch 状态标记
deleted或unreachable。 - 增加 watch 周期 scan 或等价 scan 入口,补齐删除检测。
- 修改 DeviceMonitor,恢复后触发该 watch scan。
- 修改 cleanup deleted 行为,去掉
older_than参数并停止连带 orphan cleanup。 - 修改 orphan cleanup 判断。
- 补齐删除 file row 时清理
fts_filenames。 - 增加测试:
- deleted file 保留
sha256并保护 doc。 - orphan doc 只在没有任何 file row 时出现。
- cleanup deleted 不自动 cleanup orphan。
- removable 不可达不会造成 deleted。
- removable 恢复后 scan 能把真实缺失文件标记为 deleted。