Files
mnote/design/01-tree-first-graph-kernel/done/1-3-current-priority-execution-checklist-v1.md
T
lix-2026 1882db7681 收口 MNote P0 P1 P2 审查尾项
- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目
- 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线
- 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径

验证:
- cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1
- cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1
- git diff --check
- git diff --cached --check
- codegraph index . --force && codegraph status .
- codegraph sync . && codegraph status .
2026-06-01 09:29:12 +08:00

475 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 1-3 [process] 当前主线持续推进 checklist v1
> 创建时间:2026-05-19
>
> 当前状态:`DONE`
>
> 上位依据:
> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md`
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md`
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-3-local-workspace-access-control-productization-v1.md`
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
> - `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md`
>
> 目标:把 `01-05 当前主线与优先级总览` 转成可持续推进、可验证、可迁移到 `done/` 的执行清单。
---
## 0. 总原则
- [x] 产品形态固定为 `VSCode 简化版工作区内核 + tiptap markdown 编辑器 + Hermes / Reasonix agent + simplemindmap / office 插件 + Wolai 风格 web 壳 + 鉴权控制面`
- [x] 本地文件夹是默认数据真相;Rust kernel 是唯一语义真相。
- [x] Rust SQLite control-plane 承接账号、分享、同步、协作和 AI 隔离默认控制面;Convex 仅保留历史迁移源、显式 cloud source / compat / sync replica 边界。
- [x] AI 默认尽量使用 agent 原生文件读写、diff、patch 能力;MNote 只提供授权 root、页面定位、必要元数据和特殊资源工具。
- [x] 每完成一个阶段后,把对应过程稿移动到该分类 `done/`,并在本 checklist 写入验证证据。
- 本轮说明:`1-3` 本身仍作为当前持续执行清单保留在 `process/`;已完成的上位 local-first 设计和已完成阶段稿继续放在各自 `done/`,仍处于阶段性收口的 P7 文档保留在 `process/` 并在本 checklist 写明证据。
---
## 1. P0 管理员目录授权控制面
对应 `01-05` 执行顺序:`1. 管理员目录授权 UI / API`
### 1.1 后端 API 与权限底座
- [x] 管理员身份支持 `MNOTE_ADMIN_USER_IDS`
- [x] access policy 默认路径固定为 `/mnt/Data1T/Mnote_data/control-plane/access-policy.json`
- [x] 管理员可读写任意本地目录,普通用户只能访问 owner / grant 授权目录。
- [x] read grant 只能读,write grant 可写。
- [x] 新增 `GET /api/admin/access-policy`
- [x] 新增 `POST /api/admin/access-policy/validate-root`
- [x] 新增 `POST /api/admin/access-policy/grants`
- [x] 新增 `DELETE /api/admin/access-policy/grants/{grantId}`
- [x] 全入口权限审计完成:local folder、page body、tree command、Hermes / Reasonix、shared AI session。
验证证据:
- [x] `cargo test -p mnote-web local_access_policy -- --nocapture`
- [x] `cargo test -p mnote-web local_workspace_access -- --nocapture`
- [x] `cargo test -p mnote-web local_folder -- --nocapture`
- [x] `cargo test -p mnote-web hermes_client_local_acp -- --nocapture`
- [x] `cargo test -p mnote-web hermes_tools_markdown_edit_shared_read_is_forbidden -- --nocapture`
### 1.2 管理员 UI
目标:让管理员不用手写 JSON 就能管理用户目录授权。
- [x] 定位当前 Rust SSR 设置 / 管理入口,确认管理员页面应挂在 `rust/crates/mnote-web/src/ssr/pages/` 的哪个壳内。
- [x] 增加管理员访问入口:非管理员不可见,管理员可进入本地目录授权管理页。
- 实现:`/admin/access-policy` + `PageLayout` 管理员 quick action。
- [x] 增加 policy 列表:显示 `policyPath`、env admins、policy admins、grant 列表。
- 实现:`rust/crates/mnote-web/src/ssr/pages/admin.rs` 调用 `GET /api/admin/access-policy` 并展示 JSON。
- [x] 增加 validate root 表单:输入 `rootPath``rootUri` 后显示 canonical path / rootUri。
- [x] 增加 create grant 表单:`userId``permission``recursive``capabilities`
- [x] 增加 delete grant 操作:删除前显示授权目录和用户,删除后刷新列表。
- [x] 增加 UI 错误态:未登录、非管理员、目录不存在、重复授权、无效 capability。
- 实现:页面展示 API 错误;SSR route 对非管理员返回 403;API 保持后端错误码。
- [x] 补管理员 UI smoke:真实登录管理员可新增 read grant,普通用户可读不可写。
- 验证:`node scripts/task450-admin-access-policy-ui-smoke.js`
- [x] 补普通用户 smoke:普通用户无法打开管理员授权页,直接调用 API 返回 403。
- 验证:`cargo test -p mnote-web admin_access_policy -- --nocapture`
建议验证:
- [x] `cargo test -p mnote-web local_access_policy -- --nocapture`
- [x] `cargo test -p mnote-web local_workspace_access -- --nocapture`
- [x] `node scripts/task450-admin-access-policy-ui-smoke.js`
---
## 2. P1 VSCode-like 冲突处理 UI
对应 `01-05` 执行顺序:`2. VSCode-like 冲突处理 UI`
目标:tiptap 前端保存与 agent 后台写文件同时发生时,用户可以像 VSCode 一样看见冲突、选择磁盘版本、保留编辑器版本或打开 diff 合并。
### 2.1 冲突模型收口
- [x] 盘点现有 `fileVersion` / `conflictDetectionKey` 的生成、传递和校验路径。
- 结论:本地 markdown aggregate 已暴露 `fileVersion` alias`documents/save` compat 会把 `expectedFileVersion` / `conflictDetectionKey` 收敛到 `PageBodyWriteRequest.expected_file_version``/api/page-body/write` 只接受 `expectedFileVersion`,避免 alias 重复。
- [x] 确认所有本地 markdown 写入口都携带 expected file versiontiptap 保存、`mnote.doc.markdown_edit``mnote.page.save`、documents compat save。
- 实现:tiptap local-folder 保存改走 `/api/page-body/write` 并只传 `expectedFileVersion``mnote.doc.markdown_edit` 从当前 aggregate 的 `fileVersion` / `conflictDetectionKey` 取 expected version`mnote.page.save` 读取 tool 入参 `expectedFileVersion`documents compat save 继续兼容旧 `conflictDetectionKey`
- [x] 统一冲突错误 envelope:错误码、当前磁盘版本、编辑器基线版本、documentId、rootUri、建议动作。
- 实现:本地 markdown stale 写入返回 `details.conflict`,包含 `code``currentDiskVersion``editorBaseVersion``documentId``rootUri``suggestedActions`
- [x] 让冲突错误不丢失当前编辑器内容,前端可以继续保留未保存 buffer。
- 实现:冲突态优先从当前挂载的 ProseMirror DOM 捕获文本;“保留当前编辑器版本”会用最新 `fileVersion` 重新提交当前 buffer。
### 2.2 冲突交互
- [x] 设计冲突 modal / side panel:显示当前编辑器版本、磁盘版本、文件路径、最后修改来源。
- 实现:文档页内嵌 `mnote-editor-conflict-panel`,展示文件标识、来源为本地文件变更,并提供磁盘 / 当前 / diff 三个动作。
- [x] 实现“接受磁盘版本”:重新读取文件,替换编辑器 buffer。
- [x] 实现“保留编辑器版本”:用最新 fileVersion 重新提交当前编辑器内容。
- [x] 实现“打开 diff”:支持 markdown 文本并排查看;复杂块结构先降级为 markdown 文本。
- [x] 实现“合并”:支持从 diff 面板选择合并结果并写回。
- 实现:冲突 diff 面板新增合并 textarea,可选择当前版本 / 磁盘版本作为合并基础,并写回合并结果。
- [x] 合并完成后写回本地 markdown,并刷新 page aggregate / file tree snapshot。
- 验证:`node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js` 覆盖合并写回,文件内容包含当前编辑器内容、磁盘内容和新增合并内容。
- [x] AI 写入导致冲突时,提示来源为 agent run,而不是普通外部修改。
- 实现:页面收到 `mnote:page-ai-tool-write-completed` / `agent.changed_files` 后记录 agent run 来源;冲突面板来源显示为 `agent run <runId>`
建议验证:
- [x] 新增单测:stale `expectedFileVersion` 被拒绝。
- 验证:`cargo test -p mnote-web local_folder_documents_save_rejects_stale_expected_file_version -- --nocapture`
- [x] 新增 browser smoke:浏览器打开页面后外部修改同一 `.md` 文件,保存时出现冲突 UI。
- 验证:`node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js`
- [x] 新增 browser smokeagent 修改同一 `.md` 文件后,tiptap 保存触发冲突 UI。
- 验证:`node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js` 覆盖 agent 修改同一 `.md` 后的冲突 UI,并断言面板出现 `agent run <runId>`
补充验证:
- [x] `cargo fmt --check --all --manifest-path rust/Cargo.toml`
- [x] `cargo test -p mnote-web document_shell_renders_local_markdown_with_same_sidebar_surfaces -- --nocapture`
- [x] `cargo test -p mnote-web local_folder -- --nocapture`
- [x] `npm run check:local-first-convex-guard`
---
## 3. P2 Agent changed files / diff 审计
对应 `01-05` 执行顺序:`3. agent changed files / diff 审计`
目标:agent 可以像在 VSCode 里一样直接改授权目录文件,但 MNote 要记录“谁、在哪个 root、通过哪个 run、改了哪些文件、diff 摘要是什么”。
### 3.1 审计事件模型
- [x] 设计本地审计目录:建议放在 `/mnt/Data1T/Mnote_data/control-plane/agent-audit/`
- [x] 定义审计事件 JSONL 字段:`eventId``actorId``agentKind``runId``rootUri``permission``changedFiles``diffSummary``createdAt`
- [x] 区分 agent 原生文件修改与 MNote tool 写入:二者都要能归入同一个 run audit。
- 实现:agent 原生文件修改继续由 run 前后 root snapshot 生成 `changedFiles`;MNote tool 本地成功写入额外追加 `origin=mnote_tool` 的同 `runId` audit event,只读拒绝追加 `writeAttemptRejected=true` 的同 `runId` audit event。
- [x] 对只读 grant 的 agent run 写入尝试记录拒绝事件。
- 实现:`mnote.doc.markdown_edit` / `mnote.page.save` / `mnote.block.*``read_only` AI scope 下直接拒绝写入;本地 mnote tool 写入拒绝会按同一 `runId` 追加 control-plane `agent-audit.jsonl` 事件,标记 `writeAttemptRejected=true`
### 3.2 写入采集
- [x] 在 Hermes / Reasonix run 启动前记录 root snapshot:文件 mtime、size、hash。
- [x] run 结束后对比 root snapshot,生成 changed files。
- [x] 对 markdown 文件生成简短 diff summary;大文件只记录 hash / size / path。
- [x] 把审计事件落盘到 control-plane,不写进用户正文目录。
- [x] 在 AI 会话 UI 显示 changed files 列表,并可展开查看 diff 摘要。
- 实现:`run.completed.agentAudit.changedFiles` 会进入 `agent.changed_files` 工具卡并显示 changed files 与 diff summary。
- 验证:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task453-local-folder-page-ai-changed-files-smoke.js`
建议验证:
- [x] 单测:run 前后文件变化可生成 changed files。
- 验证:`cargo test -p mnote-web local_agent_audit_snapshot_detects_changed_files -- --nocapture`
- [x] 单测:只读授权下写入被拒绝并产生拒绝审计事件。
- 验证:`cargo test -p mnote-web hermes_tools_markdown_edit_shared_read -- --nocapture`
- [x] 单测:MNote tool 本地写入归入同一 run audit,并标记 `origin=mnote_tool`
- 验证:`cargo test -p mnote-web hermes_tools_page_save_local_folder_writes_markdown_file -- --nocapture`
- [x] browser smokeAI 修改一篇本地 markdown 后,会话面板显示 changed files。
- 验证:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task453-local-folder-page-ai-changed-files-smoke.js` 覆盖本地 `README.md` 写入标记后,会话面板显示 `agent.changed_files``README.md` 与 diff 摘要。
补充验证:
- [x] `cargo fmt --check --all`
- [x] `cargo test -p mnote-web hermes_client_acp_run_registers_scoped_runtime_record_in_convex -- --nocapture`(历史测试名;当前默认运行态记录已由 SQLite control-plane 承接,旧 Convex store 只保留显式 compat 覆盖)
- [x] `cargo test -p mnote-web hermes_client_local_acp -- --nocapture`
- [x] `cargo test -p mnote-web document_shell_renders_local_markdown_with_same_sidebar_surfaces -- --nocapture`
---
## 4. P3 本地搜索、反链和资源引用索引
对应 `01-05` 执行顺序:`4. 本地搜索、反链和资源引用索引`
目标:本地 workspace 不依赖 Convex search 也能搜索正文、查反链、查资源引用和标签。
### 4.1 索引边界
- [x] 设计本地索引目录:建议放在 workspace `.mnote/index/``/mnt/Data1T/Mnote_data/control-plane/index/`,二者职责需明确。
- 实现:单 workspace 本地索引落在授权 root 的 `.mnote/index/search-index.json`control-plane index 只保留给后续跨 workspace / shared cache 总索引。
- [x] 确认索引只扫描授权 root 内文件,不扫描用户未授权目录。
- 实现:`/api/search/documents``local_folder` 分支先走 `ensure_local_workspace_read_access`,再对 canonical root 内 `.md` 文件重建索引。
- [x] 定义索引输入:markdown 正文、frontmatter、附件引用、mindmap / office resource metadata。
- 实现:输入覆盖 markdown 正文、frontmatter `title/tags/mnote_id`、markdown 链接、双链、附件引用、`.mindmap.json` 与 office 文件元数据。
- 验证:`cargo test -p mnote-web local_search_index -- --nocapture`
- [x] 定义索引输出:全文 search、backlinks、resource refs、tags、recent changes。
- 实现:本地索引输出 `results``recentChanges`,结果包含全文命中、`backlinks``resourceRefs``tags``path``resourceType`,并可返回 mindmap / office 资源元数据搜索结果。
### 4.2 索引更新
- [x] 本地文件 watcher 事件触发增量索引。
- 实现:本地 markdown / mindmap / office watcher 事件会调用单文件索引更新 `refresh_local_search_index_for_path`,避免等到搜索时才重建。
- 验证:`cargo test -p mnote-web watcher_event_refreshes_local_search_index_path -- --nocapture`
- [x] 手动 refresh / resync 触发 root 全量索引。
- 实现:`POST /api/search/local-index/refresh` 会在授权 root 内全量重建 `.mnote/index/search-index.json`
- 验证:`cargo test -p mnote-web search_local_index_refresh_rebuilds_authorized_root -- --nocapture`
- [x] 文件移动、重命名、删除、恢复后同步更新索引。
- 实现:本地 `tree command` 成功后统一 best-effort 刷新 `.mnote/index/search-index.json`;验证覆盖 rename 后索引路径更新。
- 验证:`cargo test -p mnote-web local_tree_command_refreshes_search_index_after_rename -- --nocapture`
- [x] AI 写入和 tiptap 保存后更新索引。
- 实现:`save_local_markdown_page` 成功写回后统一 best-effort 刷新本地搜索索引,覆盖 tiptap 保存与 MNote tool 写入兼容链。
- 验证:`cargo test -p mnote-web local_markdown_save_refreshes_search_index_after_write -- --nocapture`
- [x] 索引损坏时可重建,不影响正文文件。
- 实现:本地搜索每次从授权 root 重新生成 `.mnote/index/search-index.json`,不会读取索引作为正文真相。
### 4.3 搜索体验
- [x] 全局搜索优先搜索当前 workspace 本地索引。
- 实现:页面搜索请求携带 `sourceKind/rootUri``local_folder` 请求直接查本地索引,不先走 Convex runtime。
- [x] 搜索结果显示文件路径、标题、命中片段和资源类型。
- 实现:搜索结果行显示标题、命中片段、路径和 `resourceType` 标识;API 同步返回 `path/title/snippet/resourceType`
- [x] 反链面板读取本地索引,不再依赖云端搜索。
- 实现:页面设置 drawer 新增“索引”页签,按当前授权 root 读取 `GET /api/search/local-index/backlinks`
- [x] 标签列表读取本地索引。
- 实现:页面设置 drawer 新增“索引”页签,按当前授权 root 读取 `GET /api/search/local-index/tags`
建议验证:
- [x] 单测:markdown 链接和双链可生成 backlinks。
- 验证:`cargo test -p mnote-web local_search_index -- --nocapture`
- [x] 单测:附件 / mindmap / office 引用可生成 resource refs。
- 验证:`cargo test -p mnote-web local_search_index -- --nocapture`
- [x] 单测:mindmap / office 原生资源元数据可进入本地索引结果。
- 验证:`cargo test -p mnote-web local_search_index -- --nocapture`
- [x] browser smoke:新建页面后立即可搜索,重命名后搜索结果路径更新。
- 验证:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task452-local-search-index-browser-smoke.js`
- [x] browser smoke:页面设置里的本地索引页签可显示反链和标签。
- 验证:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task452-local-search-index-browser-smoke.js`
补充验证:
- [x] `cargo test -p mnote-web search_local_index_backlinks_and_tags_read_authorized_root -- --nocapture`
---
## 5. P4 分享与同步闭环
对应 `01-05` 执行顺序:`5. 分享与同步闭环`
目标:个人本地空间默认隔离,显式分享后才产生共享 workspace / shared AI session;权限、同步和协作默认由 Rust SQLite control-plane 承接,Convex 只保留历史迁移源、显式 cloud source / compat / sync replica 边界。
### 5.1 分享权限
- [x] 定义 share grant 与 local access grant 的关系:share grant 是产品层共享授权,local access grant 是本机文件系统目录授权;分享不自动扩大本机 root 读写权限。
- 实现:`share_grant_admin_api_creates_reads_and_revokes_without_local_access` 验证 share grant 不授予 target 用户本地 filesystem read/write access。
- [x] 增加 canonical share grant 控制面:默认落在 `/mnt/Data1T/Mnote_data/control-plane/share-grants.json`,记录 `shareId`、owner、target、root、resource scope、permission、capabilities、生命周期和撤销状态。
- 实现:新增 `GET/POST/DELETE /api/admin/share-grants`;支持 `MNOTE_SHARE_GRANTS_FILE` 覆盖测试路径。
- [x] share grant 支持 `read` / `write` / `ai` capability,并可映射到 `AiAccessScope.permissionLevel=shared_read/shared_write`
- 实现:Hermes local ACP run 从 canonical share grant 生成 `shared_read/shared_write`
- [x] shared AI session 只能访问 share grant 允许的资源;`allowedResourceIds` / `shareContext` 必须来自 canonical share grant,不接受浏览器自报扩大范围。
- 实现:`hermes_client_run_body_shared_scope_uses_share_grant_resources``acp_runtime_env_limits_shared_scope_to_granted_files` 覆盖 `shareContext``allowedResourceIds` 和文件级 allowed roots。
- [x] 管理员可查看、创建和撤销 share grant;owner 后续可管理自己创建的 share grant。
- 实现:管理员页面新增独立“分享授权”面板,与目录授权面板分离。
### 5.2 同步缓存
- [x] 设计 shared workspace cache 目录:默认位于 `/mnt/Data1T/Mnote_data/shared/workspaces/<share-id>/`workspace 内缓存 `.mnote/share-cache.json``.mnote/sync-state.json` 只保存授权、版本、来源和同步状态,不成为第二份正文真相。
- 实现:新增 `/api/local-folder/shared-cache/record``/api/local-folder/sync/pending-change`,均只写 `.mnote` sidecar。
- [x] 云端同步到本地 cache 时保留来源、版本、权限和冲突信息:至少记录 `shareId``remoteVersion``baseVersion``permission``sourceActor``syncedAt`
- 实现:`.mnote/share-cache.json` 写入 `mnote.share_cache.v1`
- [x] 本地修改同步回云端前进行权限和版本校验:`shared_read` 永远不能上传 pending write`shared_write` 必须校验 share grant 未撤销且 base version 未过期。
- 验证:`shared_cache_and_sync_state_record_permissions_and_conflict_report` 覆盖 shared_read 拒绝 pending write、shared_write 写入 pending change、remote/base version 冲突返回 409。
- [x] 离线期间记录 pending changes,恢复在线后生成同步报告;pending changes 只能来自已有授权 cache,离线不能新增或扩大授权。
- 实现:pending changes 写入 `.mnote/sync-state.json`,写入前强制校验 canonical share grant。
### 5.3 同步冲突报告
- [x] 同步冲突复用 P1 的冲突 UI,并把冲突来源标记为 shared sync 而不是普通外部文件变更。
- 实现:`/api/local-folder/sync/conflict-report` 生成 `source=shared_sync``suggestedActions=["accept_local","accept_remote","open_diff_merge"]` 的报告 envelope,供 P1 diff / merge UI 消费。
- [x] 冲突报告包含本地版本、远端版本、base version、修改 actor、shareId 和 resource id。
- 验证:`shared_cache_and_sync_state_record_permissions_and_conflict_report`
- [x] 管理员或 owner 可导出冲突报告;报告写入 control-plane 或 workspace `.mnote/sync-reports/`,不写入正文目录。
- 实现:报告写入授权 workspace 的 `.mnote/sync-reports/`
建议验证:
- [x] 单测:share read 不允许写入。
- 验证:`cargo test -p mnote-web shared_cache -- --nocapture``cargo test -p mnote-web hermes_client_local_shared -- --nocapture`
- [x] 单测:shared AI session 不扩大 workspace root。
- 验证:`cargo test -p mnote-web acp_runtime_env_limits_shared_scope_to_granted_files -- --nocapture`
- [x] browser smoke:共享页面只读用户无法通过 AI 写入正文。
- 验证:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task454-shared-read-page-ai-browser-smoke.js`
---
## 6. P5 插件资源模型产品化
对应 `01-05` 执行顺序:`6. 插件资源模型产品化`
目标:simplemindmap / office 是 Resource Tree 对象;Markdown 正文只保留链接或嵌入引用,不把资源内容塞回 markdown 正文真相。
### 6.1 Resource Tree 对象统一
- [x] 盘点 simplemindmap 当前 object identity、保存路径、打开路径。
- 证据:`/mindmap/{documentId}/{mindmapId}` 对象壳输出 `resource:mindmap:{documentId}:{mindmapId}``mindmap_shell_returns_rust_object_shell_contract`;保存仍通过 `mindmaps.put` / `mindmap.command.apply` 兼容链。
- [x] 盘点 office 当前 object identity、保存路径、打开路径。
- 证据:新增 `/office/{documentId}/{assetId}` 对象壳输出 `resource:onlyoffice:{documentId}:{assetId}`,内部复用 `/onlyoffice` bootstrap`cargo test -p mnote-web onlyoffice_object_shell_exposes_resource_identity -- --nocapture``cargo test -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell -- --nocapture`
- [x] 统一资源创建、重命名、移动、删除、恢复命令到 `tree.resource.*`
- 实现:上传/创建走 `tree.resource.upload` 或资源对象专用创建事件;附件 rename / move / archive / restore / purge 已输出 `tree.resource.rename/move/archive/restore/purge`mindmap/table 生命周期输出 `tree.resource.*`
- 验证:`cargo test -p mnote-web resource_ -- --nocapture`;新增 `resource_move_uses_resource_command_not_document_command`
- [x] filetree 显示资源行,pagetree 只显示页面导航投影。
- 验证:`cargo test -p mnote-web file_tree_projection_ -- --nocapture``cargo test -p bridge-runtime file_tree_projection -- --nocapture`
- [x] Markdown 中插入资源引用时,只写相对链接或嵌入引用。
- 证据:`task169-mindmap-realtime-smoke.js``assertDocumentMindmapBlockUsesReferenceSource` 验证 `mindmapId/rootNodeId` 且不写入 `data`;附件/office 仍通过链接或 asset 引用打开对象壳。
### 6.2 AI 资源工具
- [x] 设计 `mnote.mindmap.*` 工具:读取结构、增删改节点、移动节点、导出 markdown summary。
- 设计:`design/07-ai/reference/7-28-resource-ai-tool-contract-v1.md`
- [x] 设计 `mnote.office.*` 工具:读取文本摘要、写入建议、导出变更摘要;真实编辑优先复用 officecli / OnlyOffice 保存链。
- 设计:`design/07-ai/reference/7-28-resource-ai-tool-contract-v1.md`
- [x] AI 资源工具必须走授权 root 和 resource capability。
- 实现:Hermes manifest 已暴露 `mnote.mindmap.fetch` / `mnote.mindmap.apply_ops` / `mnote.office.fetch_summary` / `mnote.office.propose_changes`;执行侧统一校验 `rootUri` 本地授权与 `allowedResourceIds` / object identity。
- 验证:`cargo test -p mnote-web hermes_tools_resource -- --nocapture``cargo test -p mnote-web hermes_tools_mindmap -- --nocapture``cargo test -p mnote-web hermes_tools_office -- --nocapture``cargo test -p mnote-web hermes_tools_manifest_returns_first_batch_tools -- --nocapture`
- [x] agent changed files 审计包含 mindmap / office 资源文件。
- 验证:`cargo test -p mnote-web local_agent_audit_snapshot_detects -- --nocapture`;覆盖 `.mindmap.json` 和同大小二进制 `.docx` 内容变更。
建议验证:
- [x] 单测:resource rename 不走 document command。
- 验证:`cargo test -p mnote-web resource_rename_uses_resource_command_not_document_command -- --nocapture`
- [x] 单测:mindmap resource 删除 / 恢复不破坏 markdown 页面。
- 验证:`cargo test -p mnote-web mindmap_delete_restore_keeps_markdown_reference_out_of_lifecycle_command -- --nocapture`;生命周期命令只返回 `tree.resource.archive/restore``resourceKind=mindmap`,不携带正文 `documentId` 写入语义。
- [x] browser smoke:从 markdown 打开 mindmap / office,修改保存后 filetree 和引用状态同步。
- 证据:`node scripts/task456-resource-object-shell-sync-smoke.js`
---
## 7. P6 旧 Convex 数据迁移产品化
对应 `01-05` 执行顺序:`7. 旧 Convex 数据迁移产品化`
目标:把旧 Convex workspace 迁移成本变成可视、可回滚、可验证的产品流程。
### 7.1 导出与备份
- [x] 冻结旧 Web 导出入口设想,不再作为当前落码入口。
- 历史设计:`design/old/03-rust-web/process/3-17-convex-export-web-entry-v1.md`;当前 CLI fixture 入口已支持 dry-run / apply / manifest / conflict report / rollback。由于 Convex 默认运行时与根 functions 已退役,Web route 与 admin 面板不再按该旧稿继续落码,未来如需 cloud/import UI 应按 SQLite control-plane 与 local-first 口径新建 checklist。
- [x] 导出前创建 manifest,记录迁移计划、目标 root、workspace、操作列表、冲突、created files、backup dir 与索引刷新结果。
- 实现:`scripts/export-convex-workspace-to-local.js --manifest <file>`apply 成功后同时写入 `<root>/.mnote/migration-manifest.json`
- [x] 导出页面为 `.md`,资源写为本地文件,附件保持相对路径。
- 验证:`node scripts/task444-convex-workspace-export-local-fixture-smoke.js`
- [x] 导出过程记录进度:总页面数、资源数、AI session 数、操作列表、冲突数。
- 实现:manifest 写入 `documentCount``resourceCount``aiSessionCount``operations``conflicts`
### 7.2 冲突与回滚
- [x] 目标 root 已有同名文件时生成冲突报告,不直接覆盖。
- 验证:`node scripts/task455-convex-export-plan-rollback-smoke.js`
- [x] 支持 dry run:只生成迁移计划,不写文件。
- 验证:`node scripts/task455-convex-export-plan-rollback-smoke.js`
- [x] 支持回滚:根据 manifest 删除本次新增文件或恢复备份文件。
- 验证:`node scripts/task455-convex-export-plan-rollback-smoke.js`;当前最小闭环覆盖新增文件删除,备份文件恢复按 manifest 字段实现,覆盖写入仍由冲突策略禁止。
- [x] 导出完成后自动跑本地索引重建。
- 实现:CLI apply 写入 `.mnote/index/search-index.json`,包含导出页面与资源。
建议验证:
- [x] 脚本 smokeConvex fixture 导出到本地 root。
- 验证:`node scripts/task444-convex-workspace-export-local-fixture-smoke.js`
- [x] 脚本 smoke:同名文件冲突时不覆盖。
- 验证:`node scripts/task455-convex-export-plan-rollback-smoke.js`
- [x] 脚本 smoke:回滚后 root 回到导出前状态。
- 验证:`node scripts/task455-convex-export-plan-rollback-smoke.js`
---
## 8. P7 Page Aggregate / tree command / realtime 兼容链瘦身
对应 `01-05` 执行顺序:`8. Page Aggregate / tree command / realtime 兼容链继续瘦身`
目标:不是继续扩新功能,而是减少双真相、双命令面、补偿链和旧 Convex runtime fallback。
### 8.1 Page Aggregate 单一真源
- [x] 盘点 `documents.content` 仍作为正文兼容源的入口。
- 证据:`routes/web_shell.rs` 非 local 分支仍以 `load_document_meta_result + load_document_content_result``meta + content``page.aggregate.get``routes/documents.rs` 仍保留 `documents.content.get` compat`bridge-runtime` 仍将 `CompatMetaContentJoin` / legacy `documents.content` 投影为 `blockDocument`
- [x] 让本地 `.md` 与 EditorBlockDocument projection 的读写边界写入 5-5 / 5-6。
- 证据:`5-5``5-6` 已明确 local-first 下 `.md` 是正文真相,`EditorBlockDocument` 是 runtime-native projection / cache;写侧长期退出 `/api/documents/save` 主路径,收口到带 `expectedFileVersion``page.body.write` / LocalFS executor。
- [x] 标题、正文、页面设置写入后只通过 page aggregate 刷新 UI。
- 阶段进展:文档壳初始化、外部刷新和冲突比较已优先消费 `body.blockDocument` / `editorDocument`,再 fallback 到 legacy `body.content`
- 验证:`cargo test -p mnote-web document_shell_returns_page_aggregate_snapshot -- --nocapture``cargo test -p mnote-web page_aggregate -- --nocapture``scripts/task-page-aggregate-refresh-persistence-smoke.js`
- [x] 删除或降级前端手工拼 `meta + content` 的 runtime fallback。
- 证据:文档壳初始化与外部刷新优先消费 `/api/page-aggregate`,前端只在 Page Aggregate `body.blockDocument/editorDocument` 缺失时把 legacy `body.content` 作为兼容读取;非 local 的后端 `CompatMetaContentJoin` 仍作为 cloud / legacy substrate 边界保留,不再描述为前端 runtime 真相。
- [x] AI 页面设置写入走统一 page aggregate / page command 入口。
- 证据:Hermes `mnote.page.update_options``page.layout.updateOptions`local-first 分支走 `update_local_page_options`
建议验证:
- [x] `cargo test -p mnote-web page_aggregate -- --nocapture`
- 验证:7 个测试通过。
- [x] browser smoke:标题、正文、页面设置保存后刷新仍一致。
- 证据:`scripts/task-page-aggregate-refresh-persistence-smoke.js`,结果 `tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json`
### 8.2 Tree command cutover
- [x] 盘点仍在 runtime route / adapter / bridge / CLI 中使用的 `documents.*` 命令。
- 证据:`routes/mod.rs` 仍注册 `/api/documents/*` compat`transport/convex.rs` 仍做 `documents.*` 到 legacy Convex validator 的适配;`bridge-runtime` 仍有 `documents.stats.update``documents.duplicate``documents.template``documents.emptyTrashByWorkspace` 等历史命名。
- [x] 新增命令统一命名为 `tree.*``tree.resource.*`
- 证据:`cargo test -p mnote-web tree_command -- --nocapture` 覆盖 `tree_commands_prefer_tree_protocol_names_in_command_wire`
- [x] 对历史 `documents.*` 命令只保留 compat adapter,不再扩展新语义。
- 证据:`4-6` 已冻结 `documents.* -> tree.*` 兼容映射;`cargo test -p mnote-web tree_command -- --nocapture` 覆盖 alias mapping。
- [x] 资源 rename、移动、删除、恢复补齐 `tree.resource.*`
- 证据:资源生命周期已输出 `tree.resource.rename/move/archive/restore/purge``cargo test -p mnote-web tree_command -- --nocapture` 覆盖本地 folder asset trash / restore / purge。
建议验证:
- [x] `cargo test -p mnote-web tree_command -- --nocapture`
- 验证:15 个测试通过。
- [x] browser smokefiletree 新建、重命名、移动、删除、恢复不触发页面 reload。
- 证据:`task432-filetree-trash-page-dual-browser-no-refresh-smoke.js``task446-tree-rename-dual-browser-live-smoke.js``task447-tree-move-order-dual-browser-live-smoke.js`,证据路径见 `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
### 8.3 Tree realtime live cache
- [x] 盘点 Sidebar、page subtree、filetree、preferred snapshot 的数据来源。
- 证据:Sidebar/filetree 已消费 Rust projection 与 live tree eventslocal folder 仍有 `/api/tree/local-folder-watch` + HTML refetch 补偿链;WS 主链已启用 `/api/realtime/ws`SSE 为 close 后 fallback。
- [x] 统一 snapshot / delta / resync consumer 到同一 live cache。
- 阶段进展:WS `delta` 不再无条件 `resync`,仅 `resync_required` / `requiresResync` 时请求服务端 resync,普通 delta 先交给本地 live consumer。
- 验证:`cargo test -p mnote-web tree_live_controller_marks_transport_and_closes_source_on_pagehide -- --nocapture`;本地 folder sidebar watch 不再抓整页 HTML,而是复用 `/api/tree/projections/sidebar``/api/tree/projections/file` 后交给同一 `renderSidebarSnapshot` / `renderFileProjection`
- [x] SSE fallback 只作为 WS 不可用时的降级,不作为并行主链。
- 证据:`TREE_LIVE_CONTROLLER_JS``transport=convex-command-log-ws` 时优先 `startWithWebSocket``onclose` 后才 `startWithSseFallback``cargo test -p mnote-web tree_live_controller_marks_transport_and_closes_source_on_pagehide -- --nocapture`
- [x] 双浏览器文件树操作无刷新同步。
- 证据:`task432` 页面生命周期、`task433` file asset、`task434` mindmap/table、`task446` rename、`task447` move、`task448` resync 已在 3000 主入口通过,详见 `3-3``4-34`
- [x] 删除不必要的 polling / refetch 补偿链。
- 证据:文档壳 local folder watch 已从整页 `text/html` refetch 改为 projection API 刷新;`cargo test -p mnote-web sidebar_tree_runtime_polls_local_folder_without_browser_reload -- --nocapture`
建议验证:
- [x] `cargo test -p mnote-web local_folder -- --nocapture`
- 验证:59 个测试通过。
- [x] 双浏览器 smoke:页面、文件、垃圾箱操作互相同步且不刷新页面。
- 证据:`task432` / `task433` / `task434` / `task446` / `task447` / `task448`
---
## 9. 每轮推进固定验证包
后续每次持续推进本 checklist,至少执行与改动相关的子集;跨域改动需要执行完整包。
- [x] `cargo fmt --check --all`
- [x] `cargo test -p mnote-web local_folder -- --nocapture`
- [x] `cargo test -p mnote-web local_access_policy -- --nocapture`
- [x] `cargo test -p mnote-web hermes_client_local_acp -- --nocapture`
- [x] `cargo test -p mnote-web tree_command -- --nocapture`
- [x] `cargo test -p mnote-web page_aggregate -- --nocapture`
- [x] `npm run check:local-first-convex-guard`
- [x] `git diff --check -- <changed-files>`
- [x] 影响 UI / 交互时补 browser smoke,并把脚本名写回对应阶段。
---
## 10. done 迁移标准
本文件迁入 `done/` 前必须满足:
- [x] P0 管理员目录授权 UI / API 完成并有 browser smoke。
- 证据:`node scripts/task450-admin-access-policy-ui-smoke.js``cargo test -p mnote-web admin_access_policy -- --nocapture`
- [x] P1 冲突处理 UI 完成,并覆盖 tiptap 保存与 agent 写回冲突。
- 证据:`node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js``cargo test -p mnote-web local_folder_documents_save_rejects_stale_expected_file_version -- --nocapture`
- [x] P2 agent changed files / diff 审计完成,并能在 AI 会话 UI 查看。
- [x] P3 本地搜索 / 反链 / 资源引用索引完成最小闭环。
- [x] P4 分享与同步闭环完成 read / write / ai capability 最小闭环。
- 证据:`cargo test -p mnote-web share_grant -- --nocapture``cargo test -p mnote-web hermes_client_local_shared -- --nocapture``cargo test -p mnote-web shared_cache -- --nocapture``node scripts/task454-shared-read-page-ai-browser-smoke.js`
- [x] P5 simplemindmap / office 资源模型完成 Resource Tree 产品化。
- 证据:资源命令面、资源对象壳、AI resource tools 与 changed-files 审计已闭环;`node scripts/task456-resource-object-shell-sync-smoke.js` 已通过。
- [x] P6 Convex 导出到本地 workspace 有 dry run、备份、冲突报告和回滚。
- 证据:`node scripts/task444-convex-workspace-export-local-fixture-smoke.js``node scripts/task455-convex-export-plan-rollback-smoke.js`;旧 Web 入口设想已退到 `design/old/03-rust-web/process/3-17-convex-export-web-entry-v1.md`,不作为当前 reference 或实施入口。
- [x] P7 Page Aggregate / tree command / realtime 兼容链完成阶段性瘦身,并把被替代 process 稿移入 `old/``done/`
- 证据:Page Aggregate refresh、tree command、local folder projection refresh 与 realtime 双浏览器 smoke 均有验证;`5-5``5-6``3-3``4-34` 仍作为未完全 kernel-native / local-folder no-refresh 深水区过程稿保留在 `process/`,本 checklist 记录阶段性瘦身结果。
- [x] `01-05-current-priority-overview.md` 同步更新状态,不再把已完成项描述为当前第一优先级。
- 证据:`01-05` 已把 P0-P6 改为已完成最小闭环,把 P7 改为阶段性瘦身完成,并把下一阶段顺序更新为 WorkspacePath / BufferStore / Page Aggregate kernel-native / command context / live cache。