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

666 lines
12 KiB
Markdown
Raw Permalink 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.
# 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 面板都只是围绕这些核心对象形成的投影、适配器或任务系统,而不能反客为主。