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