Workspace、Update 与 Merge
Workspace 模型
一个 Runtime Database 可以对应任意数量的 Workspace。Workspace 无需向数据库注册,可以通过 SDK、CLI、Web UI、普通文件复制或其他外部方式创建。
所有 Workspace 都属于同一逻辑层级:它们直接从 Runtime Database 获取数据,也直接向 Runtime Database 提交。Workspace 之间不能派生、互相 Merge 或形成 Branch。
持有 Workspace 不代表拥有查询、Update 或 Merge 权限。
完整与 Scoped Workspace
Workspace 可以包含完整数据库数据,也可以只包含按照 Collection、查询条件、UUID 集合或物理分区选择的 Record。后者称为 Scoped Workspace,适合人或 Agent 在一次任务中只获取需要理解和修改的有界上下文。
Collection Scope 明确选择的 Collection 即使没有任何 Record,也必须在 Workspace 中创建对应目录、导出 _schemas/{collection}.md,并把 Schema 写入 Based Metadata。空 Collection Workspace 因此仍然可以表达允许新增的数据结构,而不是只包含不可见的 .mddb/。
Query Scope 执行完整 Query AST,包括 where、orderBy、limit、cursor 与 select。Snapshot Cursor 必须与 Query 摘要及当前 Catalog Version 匹配;导出不能只执行 where 后忽略范围和顺序约束。
Based Workspace 的 Manifest 必须保存可验证的 Scope 描述、导出时实际包含的 UUID 集合、Runtime Version 与摘要。Scope 是 Merge 删除语义的一部分:
- Scope 之外没有出现的 Record 永远不能解释为删除;
- 只有导出基线中实际存在且属于 Scope 的 Record,在 Workspace 中消失时才能成为删除候选;
- 查询条件发生变化不能把不再匹配的 Record 自动解释为删除;
- 扩大或缩小 Scope 必须通过新的 Update 刷新 Manifest 和 Base;
- Runtime 与 Workspace 可以使用不同物理分区或文件投影,但 UUID、Record Version 和 Schema 语义不变。
Unbased Workspace 没有可信 Scope,因此不能自动推断任何删除。
Based 与 Unbased Workspace
Based Workspace
通过数据库 Update 创建或维护,包含经过完整性保护的 .mddb/:
workspace/
├── 用户可见 Markdown
└── .mddb/
├── manifest
├── base/
└── signature- Manifest 记录 Database ID、Schema、数据范围、文件、Record UUID、Runtime Version 与摘要;
- Base 保存三方比较所需的基线内容或等价差异;
- Signature 保护 Manifest 与 Base 的完整性。
数据库不保存 Workspace 注册信息。Workspace 自己携带合并基线,并可以被复制到其他位置。
当前格式版本 1 使用三个内部文件:.mddb/manifest.json 保存 Database ID、Workspace ID、Runtime Version、Scope、实际导出 Record Entry 与摘要;.mddb/base/records.json 保存三方比较所需的 Record、物理路径和 Schema 基线;.mddb/signature 保存二者规范化内容的 HMAC-SHA256。签名密钥只保存在 Runtime 内部状态,不能进入 Workspace。任一文件缺失、Database ID 不符、Base 摘要不符或签名不符时,Workspace 被标记为 WORKSPACE_BASE_INVALID,并且不能解释任何删除。
Unbased Workspace
直接复制 Markdown 而没有有效 .mddb/ 的目录属于 Unbased Workspace。它可以提交,但数据库必须明确提示:
- 不能安全推断删除;
- 不能自动完成完整三方合并;
- 相同 UUID 需要选择更新、跳过或手动比较;
- 缺少 UUID 的 Record 需要交互式生成或映射;
- 成功提交或 Update 后可以建立新的 Based Workspace 基线。
Update
Update 将 Runtime Database 当前数据合并到 Workspace,不修改 Runtime Database,也不能静默覆盖 Workspace 修改。
Based Workspace 使用:
Base:上次 Update 或 Merge 成功时的数据
Runtime:当前数据库数据
Workspace:当前本地文件Update 自动应用不存在冲突的 Runtime 变化,并列出冲突。用户可以针对冲突选择:
- 保留 Workspace;
- 接受 Runtime;
- 手动提供结果;
- 对同类冲突批量应用选择。
Update 会传播 Runtime-only 的新增、修改、删除与 Schema 变化,同时保留 Workspace-only 修改。只有没有冲突的对象才会进入新的 Base 与 Manifest;尚未解决的 Record 或 Schema 冲突继续保留旧 Base,必须在后续 Status、Update 或 Merge 中保持可观察,不能被误判成可以直接 Merge 的正常状态。Table 与 Records Layout 按 Collection 汇总目标 Record 后一次重建受限文件,不能因为更新其中一条 Record 丢失同文件中的其他 Record。
Merge Plan
Merge 首先只生成 Plan,不直接修改 Runtime Database。Plan 包含:
- 新增、修改、删除、移动和重命名的 Record;
- 文件、Layout、Codec 和 Markdown 解析诊断;
- Schema 变化及数据迁移影响;
- UUID、类型、required、nullable、唯一与未知字段检查;
- Reference、反向引用、删除与级联影响;
- Runtime 与 Workspace 并发冲突;
- 只读系统表与越权变化;
- 将要修改的文件、分区和索引;
- 所需权限与最终提交摘要。
Plan 另外绑定生成时的 Workspace 扫描摘要。摘要覆盖可见 Record、Schema、物理路径与诊断;Apply 会重新读取 metadata、重新扫描 Workspace 并比较摘要。Plan 生成后的正文、Frontmatter、Schema 或文件路径变化都会返回 PLAN_STALE,不能使用旧 Plan 忽略后续编辑。
移动和重命名通过同一 UUID 的 Base Path 与当前 Path 比较产生 pathChanges。它们用于审阅 Workspace 的物理变化,不会生成逻辑 Record 更新,也不会改变 Runtime 的 UUID、Collection 或分区策略。
用户可以从候选变化中排除 Record、修复 Workspace、选择冲突结果或调整删除策略,然后重新生成 Plan。最终 Apply 的 Plan 必须整体合法。
冲突
冲突按照逻辑数据处理,而不是简单采用文件级冲突:
- Properties 和结构化 Record 使用字段级冲突;
- Table 与 Records Layout 使用 Record 级和字段级冲突;
- Body 使用文本块或行级冲突;
- Schema 使用结构级冲突;
- 删除与修改形成 delete/modify 冲突;
- 同一 UUID 的不同新增内容形成 identity conflict。
可用选择包括:
- 使用 Runtime 值;
- 使用 Workspace 值;
- 手动提供目标值;
- 保留对象并取消删除;
- 确认删除和已经展示的级联影响;
- 对同类冲突批量应用规则。
覆盖 Runtime 新数据必须明确选择,不能采用默认 last-write-wins。
Schema Merge
Workspace 可以自由修改 Schema。Plan 使用目标 Schema 检查目标数据,并允许 Schema 与相关 Record Changeset 在同一事务中提交。
如果 Schema 变化需要字段转换、默认值、重命名或删除,Plan 必须列出每条受影响 Record。无法满足目标 Schema 的数据会阻止整个 Plan 提交。
Apply
Apply 需要有效 Credentials,并重新检查:
- Plan 完整性和 Database ID;
- 调用者当前权限;
- Runtime Record 与 Schema Version;
- 所有硬性约束与冲突结果;
- 目标 Changeset 的完整性。
检查通过后,全部 Changeset 交给 Runtime Transaction Engine。同一个 Writer Host 内的 Merge 与 Init 进入独立串行队列,避免两个 Workspace 操作交错发布。Apply 在提交前检查所有目标目录是否可以写入,并把 Runtime Version、操作描述和所有受管 Workspace 文件原像写入 Runtime 内部的 .mddb/wal/workspace-operation.json。Journal 使用 prepared、committed、rollback 三个阶段:
- Runtime WAL 提交前中断时,启动恢复还原 Apply 开始前的 Workspace;
- Runtime WAL 已提交时,启动恢复幂等完成 Record、Schema 和 Based Metadata 发布;
- 同步错误触发补偿且补偿本身中断时,
rollback阶段保存的 Runtime 快照会在下次启动继续恢复双方状态。
正常调用中的 Workspace 正文或 Based Metadata 发布失败会立即恢复原 Runtime 状态与原 Workspace 文件,再返回失败。Init 使用同一持久化恢复协议,并且不会删除初始化前已经存在的 .mddb 文件。Runtime 打开顺序固定为先恢复事务 WAL,再恢复 Workspace Operation Journal,使任意已覆盖的进程崩溃点最终只能收敛到完整操作前或完整操作后。
提交成功后,当前 Workspace 更新 Base、Manifest 和 Signature。其他 Workspace 不受修改,但下一次 Update 或 Merge 会看到新的 Runtime 状态。
隐藏目录损坏
Merge 与 Update 必须验证 .mddb/ 的结构、摘要和签名。损坏或缺失时停止基于原 Base 的自动合并,并提供:
- 从 Runtime 重新建立基线,同时保留用户 Markdown;
- 将目录作为 Unbased Workspace 检查;
- 选择对象重新建立 UUID 与路径映射;
- 导出完整诊断;
- 放弃损坏元数据,但不删除 Workspace 内容。
数据库不能把损坏的隐藏目录解释成用户删除了大量 Record。
初始化已有 Markdown
现有 Obsidian Vault 或混合型 Markdown 知识库通过一次性的 scan → preview → apply 进入数据库。Scan 和 Preview 默认不写入知识库:
- Scan 递归统计 Markdown、Frontmatter 字段、已有身份和资源,按照目录与字段相似度建议 Collection、Schema 与 include/exclude Config;报告与 Config 只有在用户指定知识库外部的输出目录时才写入磁盘;
- Preview 使用用户审阅后的 Config 形成明确的 eligible 与 pending 集合,生成 UUIDv7、缺省 title 和确切 Frontmatter Patch,并把 Plan 绑定到全部 Markdown 路径与字节摘要;
- Apply 不重新推断分类,只提交 Preview 冻结的 eligible 集合;Plan 之后任何 Markdown 变化都会返回
PLAN_STALE; - pending 在 Apply 前已经排除,不会造成事务内部分成功;eligible 的 Runtime、Schema、Workspace 正文和 Based Metadata 仍然整体成功或整体恢复。
Apply 在知识库外部创建受影响文件的原始字节备份与 Journal,使用临时 Runtime 完成 Schema、Record 和完整性检查,再发布目标数据库。它只在缺失时加入 id、collection 和 title,保留已有 Frontmatter、正文、路径和文件名;资源文件只统计,不导入数据库。最终 Config、报告与 pending 清单保存到被扫描器忽略的 .mddb/init/。
已有结构化 Markdown 的兼容 Init Plan 继续负责:
- 识别 Document、Table 和 Records 候选布局;
- 建议 Collection 与 Schema;
- 为缺少 UUID 的 Record 生成 UUID;
- 报告无法解析、重复、歧义和不支持的内容;
- 展示将要修改的 Markdown 和创建的数据库结构;
- 在用户确认后原子创建 Runtime Database,并将原目录建立为 Based Workspace。
Init Plan 会根据候选 Record 推断字段名称、类型、required 与 Layout,明确返回建议 Schema;确认之后 Schema 和 Record 通过同一个 Runtime Transaction 提交,并把 Schema 写入 Workspace 的 _schemas/。推断结果不能在确认前静默写入文件。
受管 Markdown 边界
迁移 Config 是一次性决策输入,不参与迁移后的日常 Merge。迁移成功后,Signed Manifest Entry 保存受管 Record 的 UUID、Collection 与路径,并成为 Based Workspace 的事实源:
- Manifest 已有路径必须解析,并参与修改与删除判断;
- 其他 Markdown 携带合法
collection时才作为显式新增候选;已有id时沿用该身份,缺少id时 Merge Plan 使用 Workspace ID、路径、内容摘要与文件时间确定性生成 UUIDv7,并从文件名推导缺省title,Apply 成功后把身份字段写回文件; - 携带已有受管 UUID 的新路径用于识别移动与重命名;
- 没有
collection加入标记的模板、绘图、报告和普通 Markdown 保持未受管,不产生 Parse 诊断,也不能被解释为删除; - Unbased Workspace 与旧版缺少 Entry 的 Workspace 保持完整扫描行为。
这个边界允许 Obsidian Vault 长期混合数据库 Record 与普通文件,同时不让一次性目录规则成为常驻事务语义。