feat(ai): harden local agent file edit guards
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
# 7-18 Agent edit clean / dirty smoke closure checklist v1
|
||||
|
||||
> 创建时间:2026-06-06
|
||||
>
|
||||
> 状态:`process`
|
||||
>
|
||||
> Owner:07-ai / 05-editor-mainline / 03-rust-web
|
||||
>
|
||||
> 父设计:`design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md`
|
||||
|
||||
## 1. 边界
|
||||
|
||||
本 checklist 只推进 local-first 普通 Markdown 的真实 agent 文件编辑闭环,不碰 LightRAG、Knowledge RAG、OCR、evidence index 或 OnlyOffice recipe 扩展。当前另一个 agent 正在修改 LightRAG 相关文件,本轮不得编辑:
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/knowledge_rag.rs`
|
||||
- `rust/crates/mnote-web/src/hermes_tools/knowledge_rag.rs`
|
||||
- `design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md`
|
||||
- `scripts/task529*` 到 `scripts/task534*`
|
||||
|
||||
## 2. 当前代码证据
|
||||
|
||||
已有基础能力:
|
||||
|
||||
- Page AI target 会生成 `mnote.agent_target_package.v1`,并给目标写入 `allowedFiles`、`policy.conflictPolicy=fail_on_dirty_or_stale`。
|
||||
- 发送 run 前会通过 `/api/documents/buffer-state` 检查 dirty / external modified 状态;阻塞时抛 `page_ai_target_buffer_not_writable`。
|
||||
- 后端会生成 `mnote.agent_run_envelope.v1`,其中 `resultPolicy.changedFiles=required`、`refresh=watcher_or_explicit_resync`。
|
||||
- `scripts/reasonix-acp-wrapper.mjs` 会把 `agentRunEnvelope` 写进模型可见 prompt,要求 agent 使用自身文件工具编辑真实文件。
|
||||
- `local_agent_audit_finalize_run` 会基于前后快照生成 `changedFiles` 和 `mnote.agent_run_receipt.v1`。
|
||||
- 前端 `pageAiDispatchReceiptRefresh(...)` 会把 receipt 转成 `tree:local-folder-watch-batch` 和 `mnote:page-ai-tool-write-completed`。
|
||||
|
||||
当前缺口:
|
||||
|
||||
- 没有一条真实浏览器 smoke 证明 agent 原生 patch 修改当前 `.md` 后,前台 tiptap 在 clean buffer 下可见更新。
|
||||
- dirty buffer 的阻塞虽然已有函数,但缺少端到端 smoke 证明不会启动可写 run 或不会静默覆盖。
|
||||
- readonly 写入目前仍偏后置审计:`read_only_write_rejected` 可以记录结果,但还需要前置拒绝或 tool/ACP 层明确失败。
|
||||
- 审计快照在 folder context 下会退回 full snapshot;产品化前需要限制为 `allowedFiles` / changedFiles 优先,并对超限降级有可见记录。
|
||||
|
||||
## 3. 非目标
|
||||
|
||||
- 不新增 MNote 普通 Markdown 写入工具。
|
||||
- 不把 `mnote.doc.markdown_edit` 恢复为 local-first fallback。
|
||||
- 不实现 streaming apply、review session、GhostTextOverlay。
|
||||
- 不要求真实外部大模型;smoke 可以用受控 ACP stub / Hermes test runtime 模拟 agent 原生文件 patch,但必须真实写磁盘文件。
|
||||
- 不把 LightRAG retrieval、evidence citation 或 OCR sidecar 混入本闭环。
|
||||
|
||||
## 4. Phase A:clean buffer 真实写入回收 smoke
|
||||
|
||||
目标:证明 agent 原生文件编辑 -> watcher / receipt -> 当前前台 tiptap 可见更新。
|
||||
|
||||
Checklist:
|
||||
|
||||
- [x] 新增 `scripts/task535-page-ai-local-agent-clean-edit-smoke.js`。
|
||||
- [x] smoke 创建临时 local-folder workspace 和目标 `AgentClean.md`。
|
||||
- [x] 浏览器打开该 Markdown,确认 `mnote-leptos-tiptap-island-editor-root` ready。
|
||||
- [x] 通过 Page AI 发起本地 agent run,run payload 必须包含 `targetPackage.currentFile.relativePath`、`allowedFiles`。
|
||||
- [x] agent stub / ACP test runtime 只能在 `allowedFiles` 指向的真实 `.md` 上做最小 patch。
|
||||
- [x] run completed 后必须返回或触发 `agentRunReceipt.changedFiles`,路径为目标 `.md`。
|
||||
- [x] 前端必须触发 `tree:local-folder-watch-batch`,并对当前文档触发 `mnote:page-ai-tool-write-completed`。
|
||||
- [x] tiptap 可见正文更新为 agent 写入后的内容,不需要手动 reload。
|
||||
- [x] 网络断言本路径不调用 `/api/documents/save`。
|
||||
- [x] 工具事件断言本路径不调用 `mnote.doc.markdown_edit`。
|
||||
|
||||
验收:
|
||||
|
||||
- [x] smoke 输出 `tmp/task535-page-ai-local-agent-clean-edit-smoke/result.json`,包含 run payload、changedFiles、current refresh、最终 editor text。
|
||||
- [x] `result.json` 中 `ok=true`,`usedDocumentsSave=false`,`usedMarkdownEdit=false`。
|
||||
|
||||
## 5. Phase B:dirty buffer 不静默覆盖 smoke
|
||||
|
||||
目标:证明用户本地未保存时,agent 写入不会绕过 buffer 冲突模型。
|
||||
|
||||
Checklist:
|
||||
|
||||
- [x] 新增 `scripts/task536-page-ai-local-agent-dirty-guard-smoke.js`。
|
||||
- [x] 打开目标 Markdown 后在 tiptap 中输入未保存内容,制造 BufferStore dirty 状态。
|
||||
- [x] Page AI target chip / run 前检查能读到 dirty 状态。
|
||||
- [x] 点击发送可写 run 时,应出现明确阻塞或确认流程;当前最低验收是阻塞并返回 `page_ai_target_buffer_not_writable`。
|
||||
- [x] smoke 断言没有启动可写 ACP run。
|
||||
- [x] smoke 断言磁盘 `.md` 没有被 agent stub 修改。
|
||||
- [x] smoke 断言 editor 中未保存内容仍可见。
|
||||
|
||||
验收:
|
||||
|
||||
- [x] smoke 输出 `tmp/task536-page-ai-local-agent-dirty-guard-smoke/result.json`。
|
||||
- [x] `result.json` 中 `ok=true`,`blockedBeforeRun=true`,`diskChanged=false`,`editorDirtyTextStillVisible=true`。
|
||||
|
||||
## 6. Phase C:readonly 前置拒绝 smoke
|
||||
|
||||
目标:证明 readonly target 不靠事后审计才发现写入失败。
|
||||
|
||||
Checklist:
|
||||
|
||||
- [x] 新增 `scripts/task537-page-ai-local-agent-readonly-write-guard-smoke.js`。
|
||||
- [x] 构造只读授权或只读 targetPackage:`permission=read`,`allowedFiles` 可读但不可写。
|
||||
- [x] 发起“请修改当前文件”的 Page AI run。
|
||||
- [x] 前端或后端应在启动可写 ACP run 前拒绝,错误码稳定,例如 `page_ai_target_readonly` 或 `local_agent_write_not_allowed`。
|
||||
- [x] 若当前实现只能后置审计,应先把 smoke 写成 RED,记录实际行为,不伪造通过。
|
||||
- [x] smoke 断言磁盘文件未变化。
|
||||
- [x] smoke 断言 audit 中没有把 readonly 写入说成成功;若出现 `read_only_write_rejected`,必须在 UI 可见错误里体现。
|
||||
|
||||
验收:
|
||||
|
||||
- [x] smoke 输出 `tmp/task537-page-ai-local-agent-readonly-write-guard-smoke/result.json`。
|
||||
- [x] `result.json` 中 `ok=true` 仅在前置拒绝落地后允许;当前若为 RED,应输出 `ok=false` 和真实行为证据。
|
||||
|
||||
## 7. Phase D:审计快照范围收口
|
||||
|
||||
目标:避免 local agent audit 因 folder context 退回扫全 root,影响大 workspace 和 run 返回速度。
|
||||
|
||||
Checklist:
|
||||
|
||||
- [x] 后端 `local_agent_audit_relative_paths_from_payload(...)` 优先消费 `agentRunEnvelope.allowedFiles` / `targetPackage.allowedFiles`。
|
||||
- [x] folder context 不再直接强制 full snapshot;除非用户明确选择 folder-wide edit 且有上限。
|
||||
- [x] 增加审计上限:文件数、总字节数、耗时;超限时进入 `auditScope=truncated`,并在 receipt 中可见。
|
||||
- [x] 单测覆盖 allowedFiles 优先、folder context 不扫全 root、超限截断。
|
||||
|
||||
验收:
|
||||
|
||||
- [x] `cargo test -p mnote-web local_agent_audit -- --test-threads=1` 或等价 targeted tests 通过。
|
||||
- [ ] clean smoke 中 audit snapshot 只包含目标文件及必要 changed file。
|
||||
|
||||
## 8. Phase E:文档与 manifest 退役口径
|
||||
|
||||
目标:锁死 local-first 普通 Markdown 不走旧 MNote 写入工具。
|
||||
|
||||
Checklist:
|
||||
|
||||
- [x] Page AI / Hermes guidance 对 local-folder 普通 Markdown 明确优先 agent 原生 patch/diff。
|
||||
- [x] `mnote.doc.markdown_edit` 在 manifest 中只标注为 online/cloud/compat 历史工具或结构校验辅助。
|
||||
- [x] `reasonix-acp-wrapper.mjs` selftest 断言 prompt 包含“不使用 MNote doc/page write tools for ordinary local Markdown edits”。
|
||||
- [x] smoke 断言 local-first agent edit 不调用 `/api/documents/save`、`mnote.doc.markdown_edit`、`mnote.page.save`。
|
||||
|
||||
验收:
|
||||
|
||||
- [x] `7-18-local-first-agent-file-editing-control-plane-v1.md` Phase B/C 可勾选。
|
||||
- [ ] 本 checklist 可移动到 `design/07-ai/done/`,父设计 `7-18` 仅剩跨 workspace / 多 target 的产品确认项。
|
||||
|
||||
## 9. 推荐执行顺序
|
||||
|
||||
1. 先写 `task535` clean smoke,允许 RED,固定真实 run payload / receipt / 前台刷新证据。
|
||||
2. 再写 `task536` dirty guard smoke,优先验证当前已有 dirty blocker。
|
||||
3. 再写 `task537` readonly guard smoke,若当前只能后置审计则保持 RED。
|
||||
4. 最后做审计快照范围收口和 manifest/guidance 口径收紧。
|
||||
|
||||
## 10. 归档条件
|
||||
|
||||
- clean buffer smoke 真实通过。
|
||||
- dirty buffer smoke 证明不会静默覆盖。
|
||||
- readonly write smoke 证明前置拒绝或 UI 明确失败。
|
||||
- local-first 普通 Markdown agent edit 不调用旧 MNote 写入工具。
|
||||
- LightRAG 相关 diff 不在本 checklist 中被修改或作为验收前置。
|
||||
@@ -287,30 +287,36 @@ Rust SQLite control-plane 是默认控制面,负责:
|
||||
|
||||
- [ ] 从文档页发起 Hermes / Reasonix run 时,request body 包含 `currentFile`、`allowedRoots`、`allowedFiles`、selection 和 dirty / readonly context。
|
||||
- [ ] run body 包含 `targetPackage`,且 `allowedRoots` / `allowedFiles` 从 targetPackage 推导。
|
||||
- [ ] local-first 普通 Markdown 编辑 run 不包含 `mnote.doc.markdown_edit` 推荐工具。
|
||||
- [x] local-first 普通 Markdown 编辑 run 不包含 `mnote.doc.markdown_edit` 推荐工具。
|
||||
- [ ] allowed roots 外文件写入被 agent runtime 或 MNote 审计层拒绝。
|
||||
- [ ] readonly 页面不会启动可写 run,或只启动只读问答 run。
|
||||
- [x] readonly 页面不会启动可写 run,或只启动只读问答 run。
|
||||
|
||||
2026-06-01 进展:已补第一版 `mnote.agent_target_package.v1` 运行时输入。前端 Page AI run body 会从当前 `editorTarget` / `WorkspacePath` 生成 `targetPackage`;后端会 sanitize 该包,并把 local-first `aiAccessScope.allowedFiles` / `allowedFilePaths` 与 `agentRunEnvelope.allowedFiles` 从该包派生。`task502-page-ai-agent-selector-context-smoke.js` 已覆盖 page / mindmap / OnlyOffice target picker 与 payload 冻结;`task520-page-ai-raw-resource-target-smoke.js` 已覆盖真实 raw local resource tab,并断言 `objectIdentity` 不退化为 `[object Object]`。当前仍未完成跨 workspace 多选确认和真实 agent 写入回收 smoke,因此本文继续保持 `process`。
|
||||
|
||||
### Phase B:写入回收与前台同步
|
||||
|
||||
- [ ] agent 修改当前 `.md` 后,MNote 能回收 changed files / diff。
|
||||
- [ ] clean buffer 下 watcher -> BufferStore -> Page Aggregate -> ProseMirror 可见更新通过 browser smoke。
|
||||
- [ ] dirty buffer 下外部 agent 写入不会静默覆盖,必须出现冲突或 review 状态。
|
||||
- [ ] changed files / diff 与 session / run / actor 关联写入审计。
|
||||
- [x] agent 修改当前 `.md` 后,MNote 能回收 changed files / diff。
|
||||
- [x] clean buffer 下 watcher -> BufferStore -> Page Aggregate -> ProseMirror 可见更新通过 browser smoke。
|
||||
- [x] dirty buffer 下外部 agent 写入不会静默覆盖,必须出现冲突或 review 状态。
|
||||
- [x] changed files / diff 与 session / run / actor 关联写入审计。
|
||||
|
||||
2026-06-06 执行拆分:Phase B/C 的真实 smoke 与审计收口已拆到 `design/07-ai/process/7-18-agent-edit-clean-dirty-smoke-checklist-v1.md`。该拆分只覆盖 local-first 普通 Markdown agent 原生文件编辑,不碰 LightRAG / Knowledge RAG / OCR / evidence index。
|
||||
|
||||
2026-06-07 进展:`task535` 已证明 clean buffer 下 agent 原生文件 patch 写入真实 `.md` 后,receipt 触发当前文档与 filetree 刷新,前台 tiptap 无需 reload 即可见更新;`task536` 已证明 dirty buffer 在启动可写 run 前阻塞且磁盘不变;`local_agent_audit` targeted tests 已覆盖 allowedFiles 优先、folder context 不扫全 root、超限 auditScope 可见。
|
||||
|
||||
### Phase C:旧工具退役
|
||||
|
||||
- [ ] local-first 普通 Markdown 编辑 smoke 断言未调用 `/api/documents/save`。
|
||||
- [ ] local-first 普通 Markdown 编辑 smoke 断言未调用 `mnote.doc.markdown_edit`。
|
||||
- [ ] Hermes / Reasonix manifest 或 system prompt 不再鼓励普通 Markdown 编辑调用 `mnote.doc.markdown_edit`。
|
||||
- [ ] 旧 `markdown_edit` 测试只保留为历史 online / compat 回归,并在文档中标明不指导新实现。
|
||||
- [x] local-first 普通 Markdown 编辑 smoke 断言未调用 `/api/documents/save`。
|
||||
- [x] local-first 普通 Markdown 编辑 smoke 断言未调用 `mnote.doc.markdown_edit`。
|
||||
- [x] Hermes / Reasonix manifest 或 system prompt 不再鼓励普通 Markdown 编辑调用 `mnote.doc.markdown_edit`。
|
||||
- [x] 旧 `markdown_edit` 测试只保留为历史 online / compat 回归,并在文档中标明不指导新实现。
|
||||
|
||||
2026-06-07 进展:Page AI / Hermes run guidance 明确禁止 local-first 普通 Markdown 编辑调用 `mnote_doc_markdown_edit` 或 `mnote_page_save`;`mnote.doc.markdown_edit` manifest 描述已收口为 compat / remote / cloud fallback 或结构校验辅助;Reasonix ACP wrapper selftest 已断言 prompt 包含禁止使用 MNote doc/page write tools 的口径;`task535` 同时断言未调用 `/api/documents/save`、`mnote.doc.markdown_edit`、`mnote.page.save`。
|
||||
|
||||
### Phase D:结构辅助边界
|
||||
|
||||
- [ ] `mnote.block.*` 只在复杂结构辅助场景出现,例如结构块排序、非 Markdown 资源辅助。
|
||||
- [ ] `mnote.page.save` 只作为页面级兜底写入,不作为默认精确编辑入口。
|
||||
- [x] `mnote.page.save` 只作为页面级兜底写入,不作为默认精确编辑入口。
|
||||
- [ ] `mnote.doc.fetch` 不作为 local-first 正文读取主入口;需要结构化上下文时由 MNote 在 run input 中提供摘要或 projection。
|
||||
|
||||
## 9. 不做事项
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
>
|
||||
> 范围:当前主线中 Page Aggregate 单一真源、页面 AI 快速编辑、`mnote.doc.markdown_edit` 与 ACP / Hermes runtime 的源码级定向审查。
|
||||
>
|
||||
> 2026-06-01 口径补充:本文是 2026-05-17 的历史 review 快照。文中“`mnote.doc.markdown_edit` 是简单正文编辑主路径”的结论已被后续 local-first agent 文件编辑控制面覆盖;当前 local-first 普通 Markdown 编辑主路径以 `AGENTS.md`、`ARCHITECTURE.md` 与 `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` 为准,即授权文件引用 + allowed roots/files + agent 原生 patch/diff + watcher/BufferStore/Page Aggregate 同步。`mnote.doc.markdown_edit` 只保留为 remote/cloud/compat fallback 或结构校验辅助。
|
||||
> 2026-06-01 口径补充,2026-06-06 收紧:本文是 2026-05-17 的历史 review 快照。文中“`mnote.doc.markdown_edit` 是简单正文编辑主路径”的结论已被后续 local-first agent 文件编辑控制面覆盖;当前 local-first 普通 Markdown 编辑主路径以 `AGENTS.md`、`ARCHITECTURE.md` 与 `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` 为准,即授权文件引用 + allowed roots/files + agent 原生 patch/diff + watcher/BufferStore/Page Aggregate 同步。`mnote.doc.markdown_edit` 只保留为历史 online/cloud/compat 回归和结构校验对照,不作为 local-first 新 agent run 的推荐 fallback。
|
||||
|
||||
## 1. 本轮结论
|
||||
|
||||
|
||||
Reference in New Issue
Block a user