# 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 version：tiptap 保存、`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 smoke：agent 修改同一 `.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 smoke：AI 修改一篇本地 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] 脚本 smoke：Convex 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 smoke：filetree 新建、重命名、移动、删除、恢复不触发页面 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 events；local 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。
