Files
mnote/rust/design/core/01-domain-model-v0.md
T

666 lines
12 KiB
Markdown
Raw Normal View History

# 01. Domain Model v0
更新时间:2026-04-11
适用范围:`/mnt/Data1T/mnote-rust`
---
## 1. 目标
本文件定义 `mnote-rust` 的**核心领域模型**。
它要解决的问题不是“前端怎么渲染”,而是:
- 系统里到底有哪些一等对象
- 哪些对象是事实层真相,哪些只是派生视图
- AI / CLI / Web / Desktop 应该围绕什么稳定对象工作
- OnlyOffice、Mindmap、OCR、RAG 这类能力应挂在哪一层
本文件优先保证:
- 长期稳定
- 脱离 UI 依赖
- 适合 Rust 内核实现
- 适合 AI 通过结构化协议读写
---
## 2. 建模原则
### 2.1 事实与视图分离
以下对象属于**事实层**
- Workspace
- Page
- Block
- Asset
- Reference
- Task
- CommandLog
- Event
- AgentSession
以下对象默认属于**派生层 / 视图层**:
- 目录(TOC
- 反链聚合
- 搜索结果列表
- Mindmap 视图树
- OnlyOffice 当前光标/当前页/当前选区
- AI 面板当前消息列表 UI 状态
原则:
> 派生层可以重建;事实层必须稳定、可审计、可迁移。
### 2.2 人与 AI 共用同一套领域对象
不能做人类一套模型、AI 一套模型。
要求:
- 人类编辑页面,本质是修改 `Page / Block / Asset / Reference`
- AI 编辑页面,本质也是修改同样的对象
- CLI 批处理、同步任务、导入器也修改同样的对象
### 2.3 第三方编辑器不是事实源
Block editor、OnlyOffice、mindmap editor 都不是系统真相。
它们只能是:
- 某类对象的视图/交互适配器
- 输入输出变换器
- 外部文档能力宿主
### 2.4 结构化修改优先
优先通过:
- 显式对象
- 显式字段
- 显式命令
- 显式 ops
避免通过:
- UI 状态猜测
- 整文全文字符串替换
- 第三方编辑器内部瞬时结构直接落库
---
## 3. 对象总览
```text
Workspace
├─ Page
│ ├─ DocumentBody
│ │ └─ Block*
│ ├─ PageProperty*
│ ├─ PageLink*
│ └─ Snapshot*
├─ Asset*
├─ Task*
├─ AgentSession*
├─ CommandLog*
└─ Event*
Reference
├─ block -> block
├─ block -> page
├─ block -> asset-fragment
├─ page -> asset
└─ task -> page/block/asset
```
---
## 4. Workspace
`Workspace` 是系统的顶层容器。
建议字段:
- `workspace_id`
- `slug`
- `title`
- `created_at`
- `updated_at`
- `owner_actor_id`
- `default_locale`
- `storage_policy`
- `sync_policy`
- `feature_flags`
- `archived_at`
职责:
- 隔离页面、资产、任务、日志
- 挂载存储策略与同步策略
- 作为权限边界与导出边界
不负责:
- 具体页面内容
- UI 布局
- 当前用户会话态
---
## 5. Page
`Page` 是笔记系统的主内容容器。
### 5.1 Page 的定位
它不是“某个编辑器文件”,而是:
- 一条知识对象
- 一个文档入口
- 一个块树正文容器
- 一个可被引用、索引、审计的实体
### 5.2 建议字段
- `page_id`
- `workspace_id`
- `parent_page_id`(允许页面树)
- `title`
- `slug`
- `icon`
- `cover_asset_id`
- `body_root_block_id`
- `page_type``note | doc | database_record | inbox | template | system`
- `status``active | archived | deleted`
- `created_at`
- `updated_at`
- `last_edited_at`
- `last_edited_by`
- `current_revision`
### 5.3 PageProperty
页面属性不应和前端表格视图绑死。
建议独立对象:
- `property_key`
- `value_type`
- `value`
- `display_hint`
- `source`
可用于:
- 标签
- 时间
- 状态
- 优先级
- 自定义结构化元数据
---
## 6. DocumentBody 与 Block
### 6.1 为什么以 Block 为正文主模型
因为从既有 `mnote` / `mnote-next` 的稳定人层语义看,真正稳定的是:
- 单块编辑
- 在某块后插入块
- 删除块
- 重排块
- 缩进层级
- 页面引用与块引用占位
- 复杂块占位
这说明“块”比“整篇富文本 JSON”更接近系统真实操作单元。
### 6.2 Block 建议字段
- `block_id`
- `workspace_id`
- `page_id`
- `parent_block_id`
- `prev_block_id`
- `next_block_id`
- `sort_key`
- `block_type`
- `content`
- `props`
- `annotations`
- `status`
- `created_at`
- `updated_at`
- `created_by`
- `updated_by`
- `revision`
### 6.3 BlockType 建议
最小集合建议:
- `paragraph`
- `heading`
- `bulleted_list_item`
- `numbered_list_item`
- `todo`
- `quote`
- `code_block`
- `divider`
- `callout`
- `page_reference`
- `block_reference`
- `embed_asset`
- `embed_view`
- `embed_onlyoffice`
- `embed_mindmap`
- `embed_table`
- `system_placeholder`
原则:
- 类型应描述语义,不描述某个前端组件名
- `embed_*` 表示“外部能力挂件”,不是事实层自己变成那个系统
### 6.4 Block.content
建议:
- 文本类块使用结构化 inline span 序列
- 避免只存单纯 HTML
- 避免直接依赖第三方编辑器私有 schema
建议形态:
```json
{
"spans": [
{ "type": "text", "text": "hello" },
{ "type": "page_ref", "page_id": "page_xxx", "title": "项目计划" },
{ "type": "text", "text": " world" }
]
}
```
### 6.5 Block.props
只放结构化、有限、可校验字段,例如:
- `level`
- `checked`
- `collapsed`
- `indent`
- `language`
- `align`
- `width`
- `height`
- `asset_id`
- `view_id`
避免把任意 UI 状态塞进 props。
---
## 7. Asset
`Asset` 是所有非正文主块树内容的统一挂载对象。
### 7.1 Asset 范围
包括:
- 图片
- PDF
- docx
- xlsx
- pptx
- 音频
- 视频
- 导出文件
- OCR 中间产物
- 结构提取结果
### 7.2 建议字段
- `asset_id`
- `workspace_id`
- `storage_key`
- `original_name`
- `mime_type`
- `size_bytes`
- `checksum`
- `origin`upload / import / generated / synced
- `asset_kind`image / pdf / office / audio / video / binary / extracted_text
- `created_at`
- `updated_at`
- `created_by`
- `status`
- `latest_version_id`
### 7.3 AssetVersion
附件应支持版本化。
建议:
- `asset_version_id`
- `asset_id`
- `version_no`
- `storage_key`
- `checksum`
- `derived_from_version_id`
- `created_at`
- `created_by`
- `change_reason`
这样才能支撑:
- OnlyOffice 编辑回写
- OCR 重跑
- 导入转换
- AI 修改资产派生内容
---
## 8. Reference
`Reference` 是知识型系统的关键对象,不能只做正文里的临时 token。
### 8.1 Reference 范围
- 块引用块
- 页面引用
- 资产片段引用
- PDF 页码引用
- Office 书签/段落锚点引用
- URL 引用
- 检索结果引用
### 8.2 建议字段
- `reference_id`
- `workspace_id`
- `source_object_type`
- `source_object_id`
- `target_object_type`
- `target_object_id`
- `anchor`
- `label`
- `snippet`
- `confidence`
- `created_at`
- `created_by`
- `ref_kind`
### 8.3 Anchor
`anchor` 建议作为独立结构:
```json
{
"kind": "pdf_page",
"page": 12,
"bbox": null,
"text_quote": "..."
}
```
或:
```json
{
"kind": "office_bookmark",
"bookmark": "Heading_3",
"text_quote": "..."
}
```
原则:
- 锚点是系统对象,不是编辑器私有游标
- 锚点失效时要能标记 stale,而不是静默消失
---
## 9. SelectionAnchor
`SelectionAnchor` 不是长期事实主对象,但在 Agent / 编辑器桥接中有价值。
它表示:
- 当前选区
- 当前光标
- 当前活动页
- 当前资产中的定位点
建议定位为:
- **短生命周期上下文对象**
- 可存入 AgentSession / UI Session
- 默认不直接作为主事实落库
因为:
- 它变化太频繁
- 容易和编辑器宿主耦合
- 更适合作为命令输入上下文
---
## 10. Task
`Task` 既是用户任务,也是 AI 工作单元。
### 10.1 建议字段
- `task_id`
- `workspace_id`
- `title`
- `description`
- `task_type`
- `status`
- `priority`
- `assignee_actor_id`
- `source_page_id`
- `source_block_id`
- `input_payload`
- `output_payload`
- `created_at`
- `updated_at`
- `due_at`
- `completed_at`
### 10.2 为什么纳入领域模型
因为 AI 长期介入后,很多能力不是即时编辑,而是:
- 导入任务
- 提取任务
- OCR 任务
- 摘要任务
- 索引重建任务
- 同步任务
这些都应进入系统内核,而不是散在外部脚本里。
---
## 11. AgentSession
`AgentSession` 表示某次 AI 介入的上下文单元。
### 11.1 建议字段
- `agent_session_id`
- `workspace_id`
- `provider`
- `model`
- `initiator_actor_id`
- `status`
- `started_at`
- `updated_at`
- `ended_at`
- `tool_policy`
- `confirmation_policy`
- `summary`
### 11.2 不应存什么
不应把整份聊天 UI 状态直接当作领域模型核心。
建议分开:
- 领域层只保留会话元信息、工具调用摘要、事件链路
- 详细消息可作为附属日志或导出工件保存
---
## 12. CommandLog 与 Event
### 12.1 CommandLog
代表一次显式操作请求。
建议字段:
- `command_id`
- `workspace_id`
- `command_name`
- `actor`
- `source`
- `target`
- `payload_hash`
- `reason`
- `refs`
- `idempotency_key`
- `dry_run`
- `status`
- `requested_at`
- `finished_at`
- `error_code`
- `error_message`
### 12.2 Event
代表命令执行后产生的事实变化。
建议字段:
- `event_id`
- `workspace_id`
- `command_id`
- `event_type`
- `object_type`
- `object_id`
- `before_revision`
- `after_revision`
- `payload`
- `created_at`
原则:
- 命令与事件分离
- 先有意图,再有变化
- 审计必须能回答“谁因为什么改了什么”
---
## 13. View / Derived Object
以下对象建议明确标注为派生对象,不直接当真相:
- TOCView
- BacklinkView
- SearchHit
- KnowledgeGraphView
- MindmapProjection
- OnlyOfficeProjection
- PagePreview
- DailyDigest
它们的共同特点:
- 来自事实层投影
- 可以缓存
- 可以失效
- 可以重建
---
## 14. Mindmap 的正确定位
从既有设计看,Mindmap 很适合走结构化 `ops` 协议。
但在 `mnote-rust` 中,建议将其定义为:
- `Asset``ViewProjection` 的一种
-`Page / Block / Reference / Asset` 投影生成或承载
- 支持独立存储其节点视图结构
- 但不让它成为全系统唯一知识主模型
建议:
- 若是“页面内导图块”,则作为 `embed_mindmap` block 挂载
- 若是“独立导图资产”,则作为 `Asset(kind=mindmap)`
- AI 修改导图时,走 `MindmapOp[]`,但最终仍写入系统命令日志
---
## 15. OnlyOffice 的正确定位
OnlyOffice 不应被建模为“Page 本体”,应被建模为:
- `Asset(kind=office)`
- `AssetVersion`
- `OnlyOfficeAdapterSession`
- `SelectionAnchor(kind=office_*)`
- `Reference(anchor=office_*)`
也就是说:
- Office 文档是系统资产
- OnlyOffice 是该资产的一种编辑适配器
- 编辑产生新版本与事件
- AI 可以通过插件/适配器读当前选区、插入内容、替换内容
- 但系统真相依然在你的领域模型与资产版本链里
---
## 16. v0 必须避免的错误
### 16.1 不要把前端 store 当领域模型
例如:
- 当前页面 UI 选中态
- 面板展开态
- 聊天输入框草稿
这些不应进入核心领域。
### 16.2 不要把第三方编辑器 JSON 当系统法典
否则未来:
- 替换编辑器会非常痛苦
- AI 只能学编辑器私有结构
- CLI 难以稳定操作
### 16.3 不要把全文字符串替换当主修改方式
必须保留结构化修改能力。
### 16.4 不要把向量库结果当事实对象
向量结果是检索派生物,不是知识真相。
---
## 17. 一句话结论
`mnote-rust` 的领域模型应以 **Workspace / Page / Block / Asset / Reference / Task / AgentSession / CommandLog / Event** 为核心;
Mindmap、OnlyOffice、搜索结果、AI 面板都只是围绕这些核心对象形成的投影、适配器或任务系统,而不能反客为主。