Phase A — EditorRuntimeActor 内存缓存层 - 新增 editor_actor.rs: EditorBlockDocument 内存态 + apply_command + load_or_init - block.rs 四个写工具(replace/insert/delete/move)接入 actor 路径 - editor_actor feature flag(MNOTE_WEB_ENABLE_EDITOR_ACTOR=true 默认开启) - bridge-runtime 三个核心函数公开化 - rust-toolchain: 1.89 → stable(修复 spike WASM 编译阻塞) Phase B — 编辑器增量 delta channel - BlockDelta/DeltaOperation 类型 + actor.build_block_delta() - leptos-tiptap spike: mnote:editor:block-delta CustomEvent 监听 + JSON patch - DocumentAiAgentPanel: 拦截 blockDelta → window dispatchEvent - 工具响应含 blockDelta 字段供前端消费 Phase C — 事件 stream delta - broadcast channel 在 AppState/actor/SSE 三层贯通 - tree_events SSE 端点发 block.delta 事件 - 旧客户端降级兼容 环境修复 - rustc recursion_limit = 1024(修复 Leptos SSR 类型深度溢出) - run-convex-deploy.js(封装 Convex function 部署到本地后端 3210) ref: design/07-ai/process/7-13-page-block-editor-runtime-actor-v1.md
46 KiB
08 [process] Kernel 架构收口与下一阶段优先级 Review / Checklist v1
更新时间:2026-05-16
执行状态:
process关联文档:
/mnt/Data1T/mnote/AGENTS.md/mnt/Data1T/mnote/ARCHITECTURE.md/mnt/Data1T/mnote/design/01-05-current-priority-overview.md/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md/mnt/Data1T/mnote/design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md/mnt/Data1T/mnote/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md/mnt/Data1T/mnote/design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md/mnt/Data1T/mnote/design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md
1. Review 结论
当前内核架构的骨架已经基本成立,但还不能说“已完善”。
已经成立的部分:
tree-first graph kernel是长期语义事实源。mnote-web已是3000主 Web 执行面。- 文档页默认主编辑器已切到页面内
leptos-tiptapisland。 - Page Aggregate 已进入 Rust-first 读取链,并已输出
blockDocument / blockProjectionVersion / projectionSource。 - 页面/块 AI tools 已开始走
mnote.doc.*/mnote.block.*、Page Aggregate block projection 和 RustEditorCommand。
仍未完善的部分:
- Page Aggregate 仍是过渡态,block projection 主要从
documents.content/ local markdown content 投影,不是 EditorBlockDocument 原生落库完成态。 - 标题、正文、页面设置、page tree、AI 写入口尚未完全闭环到同一组 projection / command family。
tree.*已是 preferred command name,但documents.*兼容命令面仍未完全降级。/api/tree/events已是 tree realtime 主链,但 Sidebar、page subtree、filetree、preferred snapshot 还没有完全统一到一套 live cache。- AI 块工具已有最小闭环,但 selection、page_xml/text、多块插入、复杂块移动矩阵、持久 preview/review UI 和冲突矩阵仍未补齐。
补充 BlockNote / Tiptap AI 参考后的判断:
- BlockNote AI 和 Tiptap AI Toolkit 可以参考的是 AI runtime shape 与 tool contract,不是 mnote 的事实源。
- AI 方向下一步只应补基础底座:
PageAIContextBuilder、scope=selection、page_xml/text、tool manifest annotations、PageAIReviewSession、accept/reject/retry/abort 状态机。 - 在非 AI 主架构和 AI 基础合同打牢之前,不应扩展新的 AI agent 工作流、复杂 AI UI 或跨页面智能功能。
因此,下一阶段不应优先“大改架构”或“大量加新功能”,而应优先做:
架构收口 + 验收矩阵 + 定向找 bug。
更具体地说:
先打牢除 AI 以外的 Page Aggregate / tree command / tree realtime 主架构,再把 AI 的基础读写、上下文、冲突与审阅底座打牢;之后才进入 AI 功能扩展。
2. 下一步总优先级
总原则:
- 非 AI 主架构优先级高于 AI 功能扩展。
- AI 当前只推进基础设施,不推进新功能面。
- AI 基础设施必须服从 Rust kernel / Page Aggregate / EditorCommand / Hermes audit,不引入 BlockNote runtime 或 Tiptap/ProseMirror editor truth。
P0:Page Aggregate 单一真源收口
目标:
- 标题、正文、页面设置、page tree、AI 读写入口继续收敛到同一组 Page Aggregate projection / command family。
- 前端不在页面壳、island 外侧、Sidebar preferred snapshot 外再拼第二份页面真相。
- 明确当前 block projection 过渡态和长期 EditorBlockDocument 原生落库目标之间的边界。
判断标准:
- 页面读取只走 Rust
/api/page-aggregate/:id正式读链。 - 页面标题、正文、页面设置写入后,Page Aggregate、页头、Sidebar、Breadcrumb、File Tree、AI fetch 回读一致。
- 失败时明确 degraded / conflict / stale,不返回看似成功的旧快照。
P0:Tree Command Cutover
目标:
tree.*成为正式命令面。documents.*只保留为兼容层,不再继续扩写长期业务语义。- 页面新建、重命名、移动、归档、恢复、删除、资源生命周期都能通过正式 tree/page command 解释。
P0:Tree Realtime Live Cache 统一
目标:
/api/tree/events的 snapshot / delta / resync 成为 Sidebar、Page Tree、File Tree、page subtree 的共同 live cache 来源。- 减少 query/refetch/freshness 补偿链和旧快照回闪。
P0:AI 基础工具与审阅底座
目标:
- 不继续扩新 AI 功能,先把当前
mnote.doc.*/mnote.block.*变成可靠、可测、可审阅、可回滚的基础工具链。 - 参考 BlockNote / Tiptap 的架构形态,但保持 mnote Rust-owned tool runtime。
必须补齐:
PageAIContextBuilder与scope=selection。format=page_xml/text/json的稳定输出边界。- tool manifest annotations:
readonly/destructive/requiresApproval/selectionEffect/runtimeOwner/writeOwner。 PageAIReviewSession:preview -> accept/reject/retry/abort。- 多块插入的边界与 inserted block ids。
- 复杂块移动阻断矩阵。
- stale revision / stale blockRevisionRef / idempotency 重放。
明确不做:
- 不新增跨页面 AI agent 工作流。
- 不新增复杂 AI 自动化功能面。
- 不把
@blocknote/xl-ai或 BlockNoteAIExtension作为 runtime dependency。 - 不让 AI 写入绕过
dryRun/idempotencyKey/revision/conflictDetectionKey/revisionRef。
P1:定向 Bug Hunt
目标:
- 不做泛泛“找 bug”,只围绕已知架构风险做定向排查。
优先找:
- Page Aggregate 回流不一致。
- AI 块写入 conflict / stale revision。
- File Tree
{title}.md与 page title sync。 - tree stream resync / 双浏览器一致性。
- debug / compat / fallback 是否混入主链。
3. 可执行 Checklist
3.1 Page Aggregate 单一真源
- 盘点
wolai-frontend/src/lib/documents/page-aggregate-loader.ts是否仍有 runtime fallback 或 TS builder 读取分支。 - 盘点
wolai-frontend/src/lib/documents/page-aggregate-builder*的引用,确认只剩 test helper / historical adapter。 - 检查
/api/documents/page仍返回明确410,不参与 runtime 主链。 - 跑文档页打开 smoke,记录
/api/page-aggregate/:id是首要读链。 - 新建页面后检查 Page Aggregate
identity/head/body/tree/stats字段完整。 - 修改标题后检查 Page Aggregate、页头、Breadcrumb、Sidebar、File Tree
{title}.md同步。 - 修改正文后检查 Page Aggregate
body.revision/conflictDetectionKey/blockDocument同步。 - 修改页面设置后检查 Page Aggregate 与 island runtime page options 同步。
- 刷新页面后检查标题、正文、页面设置不回退到旧快照。
- 破坏或暂停 Convex query,检查响应是 degraded/error,不返回伪 fixture。
- 在
design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md勾选已验证项,并补证据路径。
2026-05-16 静态审计证据:
wolai-frontend/src/lib/documents/page-aggregate-loader.ts只通过loadPageAggregateFromRustSnapshot请求 Rust/api/page-aggregate/:documentId,loadPageAggregate不再回退 TS builder;buildServerBridgeRequest("/documents/page")只用于构造转发 header/request,不调用 Next/api/documents/pagecompat route。rg -n "buildPageAggregateFromDocumentPayloads|page-aggregate-builder" wolai-frontend/src rust scripts design --glob '!design/05-editor-mainline/reference-code/**' --glob '!node_modules/**'显示 runtime 非测试引用仅剩 builder 定义;代码引用只有wolai-frontend/src/lib/documents/page-aggregate-builder.test.ts。wolai-frontend/src/app/api/documents/page/route.ts明确返回410,错误文案指向/api/page-aggregate/:documentId;route.test.ts覆盖该行为。- 已通过:
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate、cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate、cd wolai-frontend && pnpm test src/app/api/documents/page/route.test.ts src/lib/documents/page-aggregate-builder.test.ts。 - 全仓
git diff --check当前被既有删除文件rust/spikes/leptos-tiptap-spike/trunk-8123.err阻断:fatal: unable to generate checkdiff for ...;本轮未清理该无关脏改动,已改用限定文件 diff check 复核。
2026-05-16 真实 3000 smoke 证据:
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task110-page-title-single-truth-smoke.js通过,证据:tmp/page-aggregate-single-truth-smoke/20260516-182244/task110.stdout.json。该脚本创建两页、修改标题,并验证页头、Breadcrumb、Sidebar、Page Tree、File Tree 与刷新后标题一致。- 文档页打开与 Page Aggregate snapshot 验证通过,证据:
tmp/page-aggregate-single-truth-smoke/20260516-182244/page-open-snapshot.stdout.json,截图:tmp/page-aggregate-single-truth-smoke/20260516-182244/page-open-snapshot.png。文档响应x-mnote-web-owner=mnote-web、x-mnote-web-shell=document,HTML 包含data-page-aggregate-snapshot="mnote.page_aggregate.v1"与data-page-tree-source="page_aggregate.tree.pageSubtree",同一临时页/api/page-aggregate/:id回读schema=mnote.page_aggregate.v1。 - 新建页面 Page Aggregate 字段完整性验证通过,证据:
tmp/page-aggregate-single-truth-smoke/20260516-182244/page-aggregate-fields.stdout.json。identity/head/body/tree/stats全部为 true,body.blockProjectionVersion=1,projectionSource=documents.content。 - 说明:曾尝试在浏览器 network 中直接捕获
/api/page-aggregate/:id,证据page-open-network.stderr.log;当前 Rust SSR 主入口会直接把 Page Aggregate snapshot 写入 HTML,浏览器侧不必出现该 API 请求,因此该尝试不作为失败验收项。
2026-05-16 正文写入后 body 同步证据:
- 新增并运行
scripts/task-page-aggregate-body-sync-smoke.js,通过真实 3000 文档页输入正文,等待/api/documents/save成功后轮询/api/page-aggregate/:id。 - 证据:
tmp/page-aggregate-body-sync-smoke/mp87mgz7.json,截图:tmp/page-aggregate-body-sync-smoke/mp87mgz7.png,stdout:tmp/page-aggregate-body-sync-smoke/latest.stdout.json。 - 验证结果:
body.revision从0更新到1,body.conflictDetectionKey从tree_1778927703753_1:0更新到tree_1778927703753_1:1,body.blockProjectionVersion=1,projectionSource=documents.content,blockDocument.blocks[0]回读到文本Page Aggregate body sync mp87mgz7与revisionRef=pageRev:1:block:block_1:hash:fnv1a64:208e0e63c853eef5。 - 配套测试已通过:
cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_save_route_executes_page_body_save_command、cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate_get_projects_legacy_content_to_block_document。
2026-05-16 页面设置写入后 pageOptions 同步证据:
- 新增并运行
scripts/task-page-aggregate-options-sync-smoke.js,通过真实 3000 文档页打开页面设置,依次修改wideLayout=true、smallText=true、layoutDensity=compact,等待/api/documents/options返回page.layout.updateOptions后轮询/api/page-aggregate/:id。 - 证据:
tmp/page-aggregate-options-sync-smoke/mp87y6j3.json,截图:tmp/page-aggregate-options-sync-smoke/mp87y6j3.png,stdout:tmp/page-aggregate-options-sync-smoke/latest.stdout.json。 - 验证结果:Page Aggregate
layout.pageOptions依次回读到wideLayout=true、smallText=true、layoutDensity=compact;运行时 DOM 同步为data-page-wide-layout="true"、data-page-small-text="true"、data-layout-density="compact",islandeditorRoot与.editor-surface同步,字号从16px到15px,段落间距从8px到4px。 - 配套测试已通过:
cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options、cd wolai-frontend && pnpm test src/lib/documents/page-command-client.test.ts src/components/editor/leptos-tiptap-island-editor-host.test.tsx src/lib/documents/page-option-semantics.test.ts。
2026-05-16 刷新后标题 / 正文 / 页面设置不回退证据:
- 新增并运行
scripts/task-page-aggregate-refresh-persistence-smoke.js,同一临时页内依次通过 UI 写入标题、正文和页面设置,等待 Page Aggregate 回读最新head/body/layout后刷新页面,再断言页头、正文 DOM、页面设置控件、runtime DOM 与/api/page-aggregate/:id均保持最新值。 - 证据:
tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json,截图:tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.png,stdout:tmp/page-aggregate-refresh-persistence-smoke/latest.stdout.json。 - 验证结果:刷新前后
head.title=Page Aggregate refresh mp88fr6k、body.revision=1、body.conflictDetectionKey=tree_1778929070007_1:1、blockDocument.blocks[0].text=Page Aggregate refresh body mp88fr6k、layout.pageOptions.wideLayout/smallText/layoutDensity=true/true/compact;刷新后页头标题、.ProseMirror正文、设置控件、documentElement、.document-shell、island root 和.editor-surface均保持最新值。 - 修复点:
rust/crates/mnote-web/src/ssr/pages/layout.rs将initializePageUiSurfaces延后到DOMContentLoaded后执行,避免 layout 脚本早于嵌入 Page Aggregate JSON / island DOM 完成时把 runtime 属性按默认 pageOptions 应用。 - 缺陷记录:
bugs/05-editor-mainline/done/5-12-page-options-refresh-runtime-attrs-v1.md。 - 配套测试已通过:
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate、cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options。
2026-05-16 Convex query 失败时不返回伪 fixture 证据:
- 在
rust/crates/mnote-web/src/routes/web_shell.rs补充负向 route 测试,构造隔离配置convex_url=http://127.0.0.1:9、allow_dev_fixtures=false、query_fixtures_json=None,模拟 Convex query 不可达。 /api/page-aggregate/doc_1?workspaceId=ws_demo返回503 SERVICE_UNAVAILABLE、x-error-code=convex_unavailable、x-error-phase=query_send、x-upstream-service=convex,body 为ok=false/code=convex_unavailable,且没有schema/result。/documents/doc_1?workspaceId=ws_demo返回同类错误 JSON,HTML body 不包含mnote.page_aggregate.v1、data-mnote-dev-fixture、data-page-aggregate-snapshot。- 验证命令已通过:
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate_endpoint_errors_without_convex_or_fixture -- --nocapture、cargo test --manifest-path rust/Cargo.toml -p mnote-web document_shell_errors_without_convex_or_fixture -- --nocapture、cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate。
验证命令建议:
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate
node scripts/task110-page-title-single-truth-smoke.js
node scripts/task-page-aggregate-body-sync-smoke.js
node scripts/task-page-aggregate-options-sync-smoke.js
node scripts/task-page-aggregate-refresh-persistence-smoke.js
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate_endpoint_errors_without_convex_or_fixture
cargo test --manifest-path rust/Cargo.toml -p mnote-web document_shell_errors_without_convex_or_fixture
3.2 Page Block AI Tooling
- 以
7-10为工具执行清单、7-11为 AI runtime 基础设计,逐 phase 检查已勾选项是否都有代码、测试、smoke 证据。 - 给
mnote.doc.fetch scope=selection补真实选区上下文输入和返回结构。 - 给
mnote.doc.fetch format=page_xml补最小 PageXML 输出。 - 给
mnote.block.fetch format=text/page_xml补格式分支。 - 给 tool manifest 补
annotations,区分 readonly / destructive / requiresApproval / selectionEffect。 - 定义
mnote.page_ai_context.v1,明确 context 只来自 Page Aggregate projection。 - 定义
mnote.page_ai_review_session.v1,明确 accept / reject / retry / abort 语义。 - 给
mnote.block.insert_after补多块插入限制和返回 inserted block ids。 - 给
mnote.block.move_after补标题带子块阻断 smoke。 - 给
mnote.block.move_after补列表项阻断 smoke。 - 给
mnote.block.move_after补表格 / mindmap / resource 阻断 smoke。 - 给
mnote.block.replace补 stale revision 失败用例。 - 给
mnote.block.replace补 stale blockRevisionRef 失败用例。 - 给写工具补重复 idempotencyKey 的端到端用例。
- 检查
mnote.page.save在 manifest / UI 中继续标为页面级兜底,不显示为精确块编辑主入口。 - 检查任何新增 AI surface 是否只是基础 review/context/tooling 验收,不是新功能扩展。
- 更新
design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md的每个 phase 证据。 - 更新
design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md的 Hermes 工具路由与审阅面 checklist。
2026-05-16 Page Block AI context / format focused smoke 证据:
- 新增并运行
scripts/task-page-block-ai-context-format-smoke.js,通过真实 3000 + 测试账号创建临时页,用mnote.page.save初始化p_1/p_2/p_3,再验证 Hermes mnote tools 的 context / format / annotations / selection guard。 - 证据:
tmp/page-block-ai-context-format-smoke/mp8ddr4n.json,截图:tmp/page-block-ai-context-format-smoke/mp8ddr4n-page.png。 - 验证结果:
GET /api/hermes/tools/mnote/manifest中mnote.doc.fetch、mnote.block.fetch、mnote.block.replace、mnote.page.save均包含readonly/destructive/idempotent/requiresApproval/approvalMode/runtimeOwner/writeOwner/selectionEffect;mnote.page.save明确destructive=true、approvalMode=yolo,继续定位为页面级粗粒度兜底。mnote.doc.fetch scope=selection selectedBlockIds=["p_2"] format=page_xml返回schema=mnote.page_ai_context.v1、allowedTargetBlockIds=["p_2"]、revision/conflictDetectionKey、revisionRef,content只包含p_2与第二段 mp8ddr4n,不包含未选中的p_1/p_3。mnote.doc.fetch scope=selection format=text返回[p_2] 第二段 mp8ddr4n,不包含未选中块。mnote.block.fetch blockId=p_2 format=page_xml/text返回目标块、revisionRef和同父级 before/after 上下文p_1/p_3。mnote.doc.apply_block_ops dryRun=true allowedTargetBlockIds=["p_2"]尝试 replacep_1被拒绝,HTTP400,错误码mnote_block_target_out_of_scope。
- 边界:本轮只验证
mnote.doc.apply_block_ops的 selection scope guard;单个mnote.block.replace/insert_after/move_after尚未校验allowedTargetBlockIds,不能据此勾选完整 selection 写保护矩阵。 - 配套验证已通过:
node --check scripts/task-page-block-ai-context-format-smoke.js、cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture、MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task-page-block-ai-context-format-smoke.js。
2026-05-16 Page Block AI stale / idempotency focused smoke 证据:
- 新增并运行
scripts/task-page-block-ai-conflict-idempotency-smoke.js,通过真实 3000 + 测试账号创建临时页,用mnote.page.save初始化p_1/p_2,再围绕mnote.block.replace验证冲突和幂等安全边界。 - 证据:
tmp/page-block-ai-conflict-idempotency-smoke/mp8dyqiq.json,截图:tmp/page-block-ai-conflict-idempotency-smoke/mp8dyqiq-page.png。 - 验证结果:
- 首次
mnote.block.replace携带最新revision/conflictDetectionKey/blockRevisionRef/idempotencyKey成功写入p_2,返回commandName=page.body.save与commandId=page_body_save_req_1778938353824_11。 - 使用同一
idempotencyKey=idem_conflict_replace_mp8dyqiq再次调用mnote.block.replace,即使请求 content 改成不同文本,也 replay 同一commandId,Page Aggregate 回读p_2仍为首次写入文本,revisionAfterFirst=2、revisionAfterReplay=2,确认不重复写入。 - 使用旧
revision/conflictDetectionKey调用mnote.block.replace返回 HTTP400、错误码mnote_tool_conflict,正文保持首次写入结果。 - 使用最新
revision/conflictDetectionKey但旧blockRevisionRef调用mnote.block.replace返回 HTTP400、错误码mnote_tool_conflict,正文保持首次写入结果。
- 首次
- 边界:本轮只覆盖
mnote.block.replace直接 tool executor 的 stale revision / stale blockRevisionRef / idempotency replay;不代表mnote.block.insert_after幂等矩阵、所有写工具幂等矩阵、review session accept stale 或 accept/reject/retry/abort 已完成。 - 配套验证已通过:
node --check scripts/task-page-block-ai-conflict-idempotency-smoke.js、cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture、MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task-page-block-ai-conflict-idempotency-smoke.js。
验证命令建议:
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime editor_document
node scripts/task-page-block-ai-tools-smoke.js
3.3 Tree Command Cutover
rg -n "documents\\.create|documents\\.title\\.update|documents\\.move|documents\\.archive|documents\\.restore" rust wolai-frontend盘点旧命令引用。- 将仍在 runtime 主链的旧命令按 owner 分类:必须迁移、兼容保留、测试 fixture。
- 确认页面新建默认输出
tree.node.create或正式 tree command。 - 确认页面重命名默认输出
tree.node.rename或正式 page/tree command。 - 确认移动页面默认输出
tree.subtree.move。 - 确认归档 / 恢复 / 永久删除的 command family 与 resource lifecycle 设计一致。
- 对 compat alias 返回增加 owner / deprecated 标识,避免被当作主链。
- 更新
design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md或新增后续 process checklist。
2026-05-16 Tree Command Cutover 静态盘点与 alias 标识证据:
- 只读盘点命令:
rg -n "documents\\.(create|title\\.update|move|archive|restore|delete|purge|copy_tree)" rust wolai-frontend scripts --glob '!node_modules/**'。 - 前端主链:
wolai-frontend/src/lib/documents/tree-command-client.ts的新建、重命名、移动、归档、恢复、永久删除和复制均 POST/api/tree/commands,返回 meta 使用TREE_COMMAND_PROTOCOL.*.preferredCommandName;TREE_COMMAND_PROTOCOL中的documents.*只保留为compatCommandName。 - Next tree command route:
wolai-frontend/src/app/api/tree/commands/route.ts在 create / move / rename / archive / restore 分支分别构造tree.node.create、tree.subtree.move、tree.node.rename、tree.node.archive、tree.node.restore。 - Rust Web tree route:
rust/crates/mnote-web/src/routes/tree.rs的create_command_wire输出tree.node.create、tree.node.rename、tree.subtree.move、tree.node.archive、tree.node.restore、tree.node.purge、tree.subtree.copy;tree_commandroute 只接收 action,不接收旧documents.*command name 作为主链输入。 - 兼容保留:
bridge-runtime仍接受documents.create/title.update/move/delete/restore/purge/copy_tree,但本轮已在对应 execution plan 的args_json.commandProtocol增加family=tree、owner=rust-runtime-kernel、preferredCommandName、compatCommandName、deprecatedAlias;旧documents.*alias 会标记deprecatedAlias=true。 - Transport 边界:
rust/crates/mnote-web/src/transport/convex.rs发送给 Convex legacy mutation 前会剥离commandProtocol、streamDeltaHint、domainEventHint、domainEventPlan(s),避免 legacy validator 把审计字段当写入参数。 - 仍保留为后续兼容收口点:
wolai-frontend/src/app/api/documents/create-child/route.ts/page-command-adapter.ts仍构造documents.create,当前分类为 compat-only;mnote-cli仍有历史documents.*CLI 构造,不属于 3000 主交互链。
2026-05-16 Tree Command Cutover 验证命令:
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime tree_ -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_archive_artifacts_for_legacy_mutation -- --nocapture
验证命令建议:
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime tree_command
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command
node scripts/task122-rust-web-create-page-ui-smoke.js
3.4 Tree Realtime Live Cache
- 盘点 Sidebar、Page Tree、File Tree、page subtree 仍依赖 query/refetch/freshness 补偿的位置。
- 确认
/api/tree/eventssnapshot / delta / resync payload 覆盖 page/file/resource row。 - 新建页面后,双浏览器 A/B 检查另一端无需刷新出现页面。
- 重命名页面后,双浏览器 A/B 检查 Sidebar / Breadcrumb / File Tree 一致更新。
- 移动页面后,双浏览器 A/B 检查 tree order 不回闪。
- 删除 / 恢复后检查 trash 与主树事件一致。
- 断开 SSE 后恢复,检查 resync 能把 UI 拉回正确状态。
- 更新
design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md的已验证项。
2026-05-16 Tree Realtime Live Cache 静态盘点与 payload 覆盖证据:
- 只读审查确认 React
AppLayoutShell同时接入useSidebarData与useSidebarTreeStream,usePreferredSidebarSnapshot负责 freshness 仲裁;Page Tree / File Tree 通过 preferred snapshot 消费kernelSidebarTree与kernelFileTreeProjection。 - 仍未统一的补偿链:
useSidebarData还保留 Convex query、HTTP/api/sidebarfallback 与手动refetch;mutation 后仍有refreshTree/sidebarQuery.refetch;Rust SSR shell 与 React hook 目前各自可建立 EventSource;page subtree 仍从 Page Aggregate client state 派生,不应误标为已统一 live cache。 - Rust stream 盘点确认 workspace snapshot 已同时加载
KernelProjectionKind::SidebarTree与KernelProjectionKind::FileTree,SSE payload 的data.dataset.kernel_sidebar_projection/data.dataset.kernel_file_tree_projection覆盖 page/file row;subtree snapshot 仍只覆盖page_tree,不含 file tree projection。 - 本轮新增
stream_change_preserves_remove_asset_delta_fields,确认tree.resource.delete的remove_assetdelta 字段保真;structural_delta_requires_projection_snapshot已覆盖remove_asset需要 projection snapshot,避免资源删除类事件只靠局部 patch。 - 本轮强化
scripts/task123-rust-web-tree-live-stream-consumer-smoke.js:解析 SSEsnapshotdata,断言kind=snapshot、stream=workspace、projection=sidebar_tree、x-mnote-tree-stream-owner=rust-web,并确认 workspace snapshot 中有kernel_sidebar_projection、kernel_file_tree_projection和临时页doc:<documentId>file tree row。
2026-05-16 Tree Realtime Live Cache 验证命令:
cargo test --manifest-path rust/Cargo.toml -p mnote-web stream_change_preserves_remove_asset_delta_fields -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web structural_delta_requires_projection_snapshot -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web routes::stream_support -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_events -- --nocapture
cd wolai-frontend && pnpm test src/components/sidebar/use-preferred-sidebar-snapshot.test.tsx src/lib/tree-stream/use-sidebar-tree-stream.test.tsx
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task123-rust-web-tree-live-stream-consumer-smoke.js
3000 smoke 证据:
tmp/tree-live-cache-smoke/20260516-task123/task123.stdout.json,结果owner=rust-web、stream=/api/tree/events、snapshotProjection=sidebar_tree、fileTreeRows=59。
2026-05-16 双浏览器 no-refresh 验证与 purge 修复证据:
- 复用
scripts/task432-filetree-trash-page-dual-browser-no-refresh-smoke.js做真实 3000 双浏览器验证:A 端通过真实 auth +/api/tree/commands创建页面、归档、恢复、彻底删除与清空垃圾箱;B 端同时打开 File Tree 与 Trash,断言目标行无刷新出现 / 消失,且navigationEvents在初始打开后为空。 - 修复前该 smoke 在
purge-visible-on-b前失败:documents:purge收到 artifact-only 字段commandProtocol,Convex legacy validator 返回ArgumentValidationError: Object contains extra field commandProtocol。 - 修复点:
rust/crates/mnote-web/src/transport/convex.rs将tree.node.purge/documents.purge加入strip_tree_artifact_fields剥离范围,避免 compat mutation 接收 tree command audit 字段。 - 缺陷记录:
bugs/04-tree-domain/done/4-41-tree-node-purge-command-protocol-leaks-to-convex-v1.md。 - 修复后
task432通过,证据tmp/tree-live-cache-smoke/20260516-task432/result.json:ok=true,create-visible-on-b通过tree:resync出现在 B 端 File Tree;archive-visible-on-b通过tree:delta remove_document同步 File Tree 与 Trash;restore-visible-on-b通过tree:delta upsert_document同步恢复;purge-visible-on-b与empty-trash-visible-on-b通过tree:resync拉回正确状态。
2026-05-16 purge 修复验证命令:
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_purge_artifacts_for_legacy_mutation -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_archive_artifacts_for_legacy_mutation -- --nocapture
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task432-filetree-trash-page-dual-browser-no-refresh-smoke.js
2026-05-16 双浏览器 rename live cache 修复与验证记录:
- 新增 regression smoke:
scripts/task446-tree-rename-dual-browser-live-smoke.js。脚本使用 A/B 独立 browser context,A 端通过正式/api/tree/commands创建并执行{ action: "rename", workspaceId, documentId, title },B 端分别保持目标文档页与 File Tree 页面不刷新,记录tree:snapshot/tree:delta/tree:resync、/api/tree/events请求、DOM 状态、导航事件与截图路径。 - RED 证据:修复前真实 3000 smoke 失败,B 端已收到
tree:delta、op=upsert_document、liveApplied=delta、liveError="",Sidebar / Page Tree 与 File Tree{renamedTitle}.md已更新,但当前打开目标文档页的titleInputValue、Breadcrumb 与document.title仍停留旧标题,导致waitForBDocumentRename超时。 - 根因:Rust SSR 文档 shell 的
updateTitleEverywhere已更新 Page Tree / File Tree 行标题,但 title-onlyupsert_document未同步当前文档页 chrome(页头标题输入框、Breadcrumb、document.title)。 - 修复点:
rust/crates/mnote-web/src/ssr/pages/layout.rs的updateTitleEverywhere(documentId, title)现在同步当前文档 chrome,并保持 scoped tree row 更新;字符串合同单测新增当前页 title input 与 Breadcrumb selector 覆盖。 - 构建阻断补齐:当前工作区已有
AppState.editor_actor/AppConfig.enable_editor_actor脏改动但缺少真实editor_actor.rs文件,导致 mnote-web 无法编译启动;本轮把 misplaced 的EditorRuntimeActor实现补到rust/crates/mnote-web/src/editor_actor.rs,并补齐测试配置中的enable_editor_actor字段,以恢复 3000 验证入口。 - GREEN 证据:重新启动最新
desktop:hot后运行MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task446-tree-rename-dual-browser-live-smoke.js通过。证据tmp/tree-live-cache-smoke/20260516-task446-rename/result.json,截图tmp/tree-live-cache-smoke/20260516-task446-rename/b-document-after-rename.png与tmp/tree-live-cache-smoke/20260516-task446-rename/b-filetree-after-rename.png。 - 验证结果:B 文档页
documentTitle/titleInputValue/breadcrumbTitle/sidebarTitle均为新裸标题;B File TreefileTreeTitle={renamedTitle}.md;两端liveApplied=delta、liveError="";rename 后navigationEvents=[],确认 B 端无刷新 / 无导航。 - 缺陷记录:
bugs/05-editor-mainline/done/5-13-tree-rename-live-document-chrome-stale-v1.md。
2026-05-16 双浏览器 move order live cache 修复与验证记录:
- 新增 regression smoke:
scripts/task447-tree-move-order-dual-browser-live-smoke.js。脚本使用 A/B 独立 browser context,A 端创建同父级A/B/C后执行正式/api/tree/commands:{ action: "move", documentId: C, parentId: root, sortOrder: 1 },B 端分别保持目标文档页与 File Tree 页面不刷新,记录 direct child order、tree events、导航事件与截图路径。 - RED 证据:合法
sortOrder=1下修复前真实 3000 smoke 失败。B 端收到tree:delta move_document,payload 含sortOrder=1,liveApplied=delta且liveError="",但 Page Tree / File Tree DOM 顺序仍为A/B/C,导致waitForExpectedOrder超时。 - 根因:Rust SSR 文档 shell 的
applyMoveDocumentDelta(data)未读取sortOrder,moveDocumentRowForMode(mode, documentId, parentId)固定appendChild到目标父节点末尾;tree:local-commandoptimistic move 分支也未传body.sortOrder。 - 修复点:
rust/crates/mnote-web/src/ssr/pages/layout.rs增加sortOrderFromDelta与insertTreeNodeAtSortOrder,让 live delta 与 local command move 按目标父节点直系 sibling index 插入;rust/crates/mnote-web/src/routes/tree.rs补充 route/artifact 单测断言sortOrder保留在 result 与streamDelta。 - GREEN 证据:重启最新
desktop:hot后运行MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task447-tree-move-order-dual-browser-live-smoke.js通过。证据tmp/tree-live-cache-smoke/20260516-task447-move-order/result.json,截图tmp/tree-live-cache-smoke/20260516-task447-move-order/b-document-after-move.png与tmp/tree-live-cache-smoke/20260516-task447-move-order/b-filetree-after-move.png。 - 验证结果:B 文档页与 B File Tree 页面中的 Page Tree / File Tree direct child order 均从
A/B/C变为A/C/B;两端liveApplied=delta、liveError="";move 后navigationEvents=[],确认无刷新 / 无导航。 - 缺陷记录:
bugs/04-tree-domain/done/4-42-tree-move-live-delta-sort-order-ignored-v1.md。
2026-05-16 SSE resync / reconnect recovery 验证记录:
- 新增
scripts/task448-tree-resync-recovery-dual-browser-smoke.js,通过 B 端 EventSource URL 注入pollMs=5000,A 端在同一 poll 间隔内连续创建两个子页,强制/api/tree/events进入非单条 delta 的event: resync分支。 task448验证结果:B 文档页与 B File Tree 页面均收到tree:resync,liveApplied=resync、liveError="",Page Tree / File Tree direct child order 从仅有初始子页恢复为包含新增b/c两个子页,navigationEvents=[]。证据tmp/tree-live-cache-smoke/20260516-task448-resync/result.json,截图tmp/tree-live-cache-smoke/20260516-task448-resync/b-document-after-resync.png与tmp/tree-live-cache-smoke/20260516-task448-resync/b-filetree-after-resync.png。- 新增
scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js,通过 Playwrightcontext.setOffline(true)模拟 B 端 SSE 断线,A 端离线期间连续创建两个子页,B 端恢复在线后等待 EventSource 恢复事件把 UI 拉回最新。 task449验证结果:B 文档页与 B File Tree 页面恢复后liveStatus=connected、liveApplied=resync、liveError="",新增b/c子页出现在 Page Tree 与 File Tree,navigationEvents=[]。证据tmp/tree-live-cache-smoke/20260516-task449-reconnect/result.json,截图tmp/tree-live-cache-smoke/20260516-task449-reconnect/b-document-after-reconnect.png与tmp/tree-live-cache-smoke/20260516-task449-reconnect/b-filetree-after-reconnect.png。- 说明:当前 Rust SSR controller 的真实恢复合同是“断线期间错过多条变化后,恢复时通过 snapshot/resync 类完整投影拉回 UI”;本轮实测恢复事件为
tree:resync,不是浏览器刷新或导航。
验证命令建议:
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_events
node --check scripts/task447-tree-move-order-dual-browser-live-smoke.js
node --check scripts/task448-tree-resync-recovery-dual-browser-smoke.js
node --check scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js
node scripts/task123-rust-web-tree-live-stream-consumer-smoke.js
node scripts/task432-filetree-trash-page-dual-browser-no-refresh-smoke.js
node scripts/task446-tree-rename-dual-browser-live-smoke.js
node scripts/task447-tree-move-order-dual-browser-live-smoke.js
node scripts/task448-tree-resync-recovery-dual-browser-smoke.js
node scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js
3.5 定向 Bug Hunt
- 建立 bugs 分类:Page Aggregate 问题归
bugs/05-editor-mainline/process/。 - Tree command / realtime / File Tree 问题归
bugs/04-tree-domain/process/。 - AI tools 问题归
bugs/07-ai/process/。 - 每个 bug 必须包含复现步骤、期望、实际、证据截图或 JSON、owner 判断。
- 先补最小 failing smoke,再修实现。
- 修复后移动到对应
done/,并记录验证命令。
重点 bug 方向:
- Page Aggregate 与页头标题不一致。
- Page Aggregate 与 File Tree
{title}.md不一致。 - AI 写入成功但 Page Aggregate 回读旧内容。
- stale revision 未阻断写入。
- tree stream 断线恢复后 UI 停在旧快照。
- 旧 compat / debug route 在 3000 首屏被误用。
3.6 2026-05-16 暂停交接进展摘要
本轮按用户要求暂停继续实现,只记录当前进展与恢复点;未继续推进 3.2 Page Block AI Tooling 的实现或 smoke。
已完成到可继续接力的阶段:
3.1 Page Aggregate 单一真源已完成本 checklist 中的当前验证项,并补齐真实 3000 smoke、Rust route 测试、前端单测和失败降级证据。关键证据包括tmp/page-aggregate-single-truth-smoke/20260516-182244/、tmp/page-aggregate-body-sync-smoke/mp87mgz7.json、tmp/page-aggregate-options-sync-smoke/mp87y6j3.json、tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json。3.3 Tree Command Cutover已完成当前收口:3000 主链使用tree.*preferred command,documents.*保留为 compat alias,并在 Rust execution plan / artifact 中标记 owner、preferredCommandName、compatCommandName、deprecatedAlias。3.4 Tree Realtime Live Cache已完成本轮重点验证与修复:snapshot/delta/resync payload 覆盖、双浏览器新建 / 归档 / 恢复 / purge、rename 当前文档 chrome 同步、move sortOrder live 排序、SSE resync 与 reconnect recovery。- 本轮新增或更新的 Tree Realtime 关键证据:
tmp/tree-live-cache-smoke/20260516-task432/result.jsontmp/tree-live-cache-smoke/20260516-task446-rename/result.jsontmp/tree-live-cache-smoke/20260516-task447-move-order/result.jsontmp/tree-live-cache-smoke/20260516-task448-resync/result.jsontmp/tree-live-cache-smoke/20260516-task449-reconnect/result.json
- 本轮已归档的缺陷:
bugs/04-tree-domain/done/4-41-tree-node-purge-command-protocol-leaks-to-convex-v1.mdbugs/04-tree-domain/done/4-42-tree-move-live-delta-sort-order-ignored-v1.mdbugs/05-editor-mainline/done/5-12-page-options-refresh-runtime-attrs-v1.mdbugs/05-editor-mainline/done/5-13-tree-rename-live-document-chrome-stale-v1.md
当前暂停点:
- 已进入
3.2 Page Block AI Tooling的只读审计阶段,但尚未勾选 3.2 的任何新 checkbox。 - 已确认现有代码中
mnote.doc.fetch已支持scope=selection、format=page_xml/text/markdown/json、schema=mnote.page_ai_context.v1、allowedTargetBlockIds、truncation/warnings/continuation 等基础字段;mnote.block.fetch已支持format=page_xml/text;manifest 已包含readonly/destructive/idempotent/requiresApproval/approvalMode/runtimeOwner/writeOwner/selectionEffect等 annotations。 - 已确认
scripts/task-page-block-ai-tools-smoke.js覆盖基础块工具闭环,但还没有专门覆盖scope=selection、page_xml/text、manifest annotations、选区外写入阻断、review session / conflict / idempotency 的完整验收。 - 用户目标中提到的
design/07-ai/process/7-11-blocknote-tiptap-ai-reference-and-mnote-ai-tool-runtime-v1.md当前在design/07-ai/process/下不存在;实际可读历史参考位于design/old/07-ai/process/7-11-blocknote-tiptap-ai-reference-and-mnote-ai-tool-runtime-v1.md,当前执行口径应继续以design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md为准。
恢复时的下一步建议:
- 继续从
3.2 Page Block AI Tooling开始,不要跳到定向 bug hunt。 - 优先新增一个聚焦 smoke,例如
scripts/task-page-block-ai-context-format-smoke.js,只验证基础底座,不扩新 AI 功能:- manifest annotations 中
mnote.doc.fetch/mnote.block.fetch/mnote.block.replace/mnote.page.save的 readonly、destructive、requiresApproval、selectionEffect、owner 字段。 mnote.doc.fetch scope=selection selectedBlockIds=[...] format=page_xml只返回选区块,返回schema=mnote.page_ai_context.v1、allowedTargetBlockIds、revisionRef。mnote.doc.fetch scope=selection format=text不包含未选中块。mnote.block.fetch format=page_xml/text返回稳定块内容和revisionRef。- 选区冻结后写工具尝试修改
allowedTargetBlockIds外块时返回mnote_block_target_out_of_scope。
- manifest annotations 中
- 配套验证建议从这些命令开始:
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture
node --check scripts/task-page-block-ai-context-format-smoke.js
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task-page-block-ai-context-format-smoke.js
交接注意事项:
- 当前工作区有大量既有未提交改动和文件迁移,继续执行时必须只碰当前小项相关文件,不要回滚、删除或清理无关脏文件。
git diff --check全仓可能仍会被既有删除文件阻断;验证本轮改动时可先用限定路径或git diff --no-index --check /dev/null <new-file>。rust/crates/mnote-web/src/ssr/pages/layout.rs中已有 Tree Realtime rename / move order 相关改动,同时可能混有先前 editor actor 相关改动;继续改动前必须重新读 diff,避免覆盖用户或前序 agent 的更改。3000相关 smoke 必须继续保留 JSON / 截图证据路径,并在本 checklist 与对应design/07-ai或bugs/*文档同步记录。
4. 当前不优先做
- 不优先增加新的编辑器 UI 大功能。
- 不优先新增 AI Agent 工作流功能。
- 不优先扩展 AI 功能面;AI 只做基础上下文、工具合同、审阅会话、冲突与回滚。
- 不优先大规模替换 Convex。
- 不优先重写
leptos-tiptap输入层。 - 不把
mnote.page.save包装成精确块编辑长期方案。 - 不在 compat route 继续扩写长期业务语义。
- 不把 BlockNote / Tiptap AI runtime 作为 mnote runtime 依赖。
5. 完成定义
本 checklist 不能迁入 done/,直到:
- Page Aggregate 的标题 / 正文 / 页面设置 / page tree / AI fetch 回读形成同一组可验证真相。
7-10的 Page Block AI Tooling 剩余矩阵完成或明确拆出后续 process 文档。7-11的 AI 基础 runtime 口径完成:context、format、manifest annotations、review session、状态机边界都有代码或明确后续 checklist。tree.*command 面对主要页面生命周期动作成为唯一 preferred runtime 主链。- tree realtime live cache 覆盖 Sidebar、Page Tree、File Tree、page subtree 的关键写后更新。
- 至少一轮定向 bug hunt 完成,所有 P0/P1 blocker 已归档到
bugs/*/done/或明确保留为后续 process。
6. 给后续 /goal 的持续执行 Prompt
/goal objective: 持续执行 /mnt/Data1T/mnote/design/10-review/process/08-kernel-architecture-next-priority-review-and-checklist.md,按 P0 -> P1 顺序推进 MNOTE 架构收口、AI 基础底座和定向 bug hunt。每轮开始先读取 /home/lix/.codex/memories/PROFILE.md 与 ACTIVE.md,再读取 AGENTS.md、ARCHITECTURE.md、design/01-05-current-priority-overview.md、本 checklist、design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md 和 design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md。必须保护用户已有未提交改动,不回滚、不覆盖、不删除无关文件。优先使用多个 subagent 并行做只读审查和浏览器验证,主线程只整合证据和做小范围实现。执行顺序固定为:1) Page Aggregate 单一真源;2) tree command cutover;3) tree realtime live cache;4) AI 基础工具与审阅底座,只补 context/selection/page_xml/tool annotations/review session/conflict/idempotency,不扩新 AI 功能;5) 定向 bug hunt。每完成一个小项都要更新本 checklist 和对应 design/bugs 文档,补真实验证命令或证据路径。不要优先扩新功能,不要大改架构,不要把 compat/debug/fallback 当主链,不要把 BlockNote/Tiptap AI runtime 作为 mnote runtime 依赖。验证至少包含 git diff --check、相关 cargo test / smoke;如涉及 3000 页面,使用 mnote-tester 或浏览器自动化并保留截图/JSON 证据。