mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
docs: add developer docs scaffold + architecture section
This commit is contained in:
49
docs/README.md
Normal file
49
docs/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# ST-BME 开发者文档
|
||||
|
||||
这里是 ST-Bionic-Memory-Ecology(ST-BME)的**开发者/架构文档**。面向想理解内部原理、算法、或参与维护的人。
|
||||
|
||||
普通用户的安装、面板、配置、排障说明在仓库根目录的 [`README.md`](../README.md),本目录不重复。
|
||||
|
||||
## 文档地图
|
||||
|
||||
### architecture/ — 架构与控制平面
|
||||
跨文件的结构、数据路径、不变量。这些内容变化慢,是理解"为什么这样组织"的入口。
|
||||
|
||||
- [`overview.md`](architecture/overview.md) — 子系统地图 + 写入/读取/安全三条数据路径
|
||||
- [`control-plane.md`](architecture/control-plane.md) — 身份解析、持久化状态机、必须维持的不变量
|
||||
- [`storage-and-formats.md`](architecture/storage-and-formats.md) — 存储分层、快照契约、向前兼容纪律
|
||||
- [`server-integration.md`](architecture/server-integration.md) — 三档 Authority 集成、自动降级/升级、能力探测
|
||||
|
||||
### algorithms/ — 算法原理
|
||||
核心算法"怎么算的":参数、公式、阈值。基于真实代码编写,并标注关键文件位置。
|
||||
|
||||
- [`retrieval.md`](algorithms/retrieval.md) — 三层混合检索:向量预筛 + 图扩散 + LLM 精排 + 多意图/DPP/残差
|
||||
- [`extraction.md`](algorithms/extraction.md) — LLM 提取管线:消息 → 结构化 → 图谱写入 → 时序边
|
||||
- [`diffusion-and-dynamics.md`](algorithms/diffusion-and-dynamics.md) — 图扩散(PEDSA)+ 混合评分 + 访问强化/衰减
|
||||
- [`consolidation-and-compression.md`](algorithms/consolidation-and-compression.md) — 记忆整合/去重 + 压缩遗忘 + 分层总结
|
||||
- [`vector-and-embedding.md`](algorithms/vector-and-embedding.md) — 向量空间身份 + 批量 embedding + 维度门禁
|
||||
|
||||
### features/ — 功能解析
|
||||
每个面向用户的功能"做什么、怎么用、边界在哪"。
|
||||
|
||||
- [`memory-model.md`](features/memory-model.md) — 节点类型、关系类型、主客观分层、故事时间线
|
||||
- [`recall-cards.md`](features/recall-cards.md) — 持久召回卡片
|
||||
- [`history-safety.md`](features/history-safety.md) — 历史变动恢复、渲染限制保护、Restore Lock
|
||||
- [`hide-and-render.md`](features/hide-and-render.md) — 隐藏旧楼层与渲染限制
|
||||
- [`ena-planner.md`](features/ena-planner.md) — ENA Planner
|
||||
- [`native-acceleration.md`](features/native-acceleration.md) — Native/WASM 灰度加速
|
||||
|
||||
### contributing/ — 参与维护
|
||||
怎么开发、怎么测、必须遵守的约定。
|
||||
|
||||
- [`development.md`](contributing/development.md) — 构建、测试、检查命令;分支工作流
|
||||
- [`testing.md`](contributing/testing.md) — 测试分类、ratchet 防线、依赖注入守卫
|
||||
- [`conventions.md`](contributing/conventions.md) — 注入式控制器模式、必须保持的不变量
|
||||
|
||||
## 维护原则(重要)
|
||||
|
||||
文档最大的敌人是腐烂。本目录遵守三条防腐铁律:
|
||||
|
||||
1. **离代码越近,腐烂越慢。** 单个函数的 API 细节留在模块头注释里(改代码自然会改它),不抄进这里。本目录只写"跨文件的算法原理、不变量、功能行为"。
|
||||
2. **不写一改就过期的内容。** 避免"某函数第几行做什么"这种描述;算法文档引用文件位置时,描述的是"哪个算法在哪个文件",而非逐行。
|
||||
3. **改了行为就更新对应文档。** 算法参数、不变量、功能边界发生变化时,更新这里;纯重构(不改行为)通常不需要动文档。
|
||||
101
docs/architecture/control-plane.md
Normal file
101
docs/architecture/control-plane.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# 控制平面:身份、持久化、不变量
|
||||
|
||||
这是 ST-BME 最关键的一块。过去反复出现的 bug——"提取卡住"、"未进入聊天"、"reroll 乱召回"、"一致性审计永远说有漂移"——几乎全部源于这块的状态管理。本文档记录其结构和**必须维持的不变量**。
|
||||
|
||||
## 根本问题(历史背景)
|
||||
|
||||
早期实现里,**身份、持久化确认、加载状态、图谱可写性、向量脏标记、召回复用,全都从多个异步事件路径读写同一份模块级可变状态**(`currentGraph`、`graphPersistenceState`、pending 标志、commit marker)。
|
||||
|
||||
没有单一事实源,也没有把"做决定"和"写入"分开。每个修复都是在这些接缝上打补丁,于是每修一个就冒出下一个。
|
||||
|
||||
解决方向:把控制平面抽成**纯逻辑/注入式模块**,让整类 bug 在结构上不可能发生。
|
||||
|
||||
## 身份解析
|
||||
|
||||
聊天身份是一切持久化的主键。问题从来不是"chatId 这个键选错了",而是"有好几个来源都自称是当前身份,还互相偷偷顶替"。
|
||||
|
||||
身份核心在 `runtime/identity-resolver.js`,把身份明确拆成**四条独立通道**,绝不互相顶替:
|
||||
|
||||
| 通道 | 含义 | 唯一来源 |
|
||||
| --- | --- | --- |
|
||||
| **active identity** | 当前宿主活动聊天 | 只来自宿主上下文(context integrity / hostChatId 的已知别名 / hostChatId) |
|
||||
| **graph-owner identity** | 图谱自带的所属身份 | 图谱 meta,只用于校验/恢复 |
|
||||
| **queued identity** | 排队持久化的身份 | 持久化队列状态,只用于校验 |
|
||||
| **marker identity** | commit marker 的身份 | commit marker,只用于校验/恢复 |
|
||||
|
||||
**核心不变量:**
|
||||
|
||||
> active identity 只能来自宿主上下文。graph-owner / queued / marker 身份只能用于校验和恢复,**绝不能"偷偷"变成当前聊天**。
|
||||
|
||||
这正是"未进入聊天"那类 bug 的根:旧代码用一个"优先级抽奖"函数接受十几个竞争来源,结果某个非活动身份被当成了活动聊天。现在 `resolveActiveIdentity` / `resolveGraphOwnerIdentity` / `resolveQueuedIdentity` / `resolveMarkerIdentity` 是分开的入口,不给聚合入口诱导污染。
|
||||
|
||||
> 身份是每次操作解析一次、显式传递的,不是从全局随用随取。
|
||||
|
||||
## 持久化确认状态机
|
||||
|
||||
持久化确认逻辑收敛在 `sync/persistence-reducer.js`,是**纯函数**:无 IO、无图谱变更、无 UI 副作用。
|
||||
|
||||
它把"这批记忆到底存好了没"变成 `(身份, 存储层 tier, 版本 revision, 证据)` 的纯函数。核心不变量写死进 reducer,而不是到处打补丁:
|
||||
|
||||
```
|
||||
已确认版本 >= 排队版本
|
||||
且 同一身份
|
||||
且 是规范 tier(canonical:authority-sql / indexeddb / opfs / luker-chat-state)
|
||||
⟹ pendingPersist 必须为 false
|
||||
```
|
||||
|
||||
**派生不变量:**
|
||||
|
||||
> recovery-only tier(`shadow` / `metadata-full` / `runtime-recovery` 等)永远不能推进确认状态。它们只用于灾难恢复,不能被当作"数据已安全落地"的证据。
|
||||
|
||||
> 当 `lastAcceptedRevision >= max(batchRevision, queuedPersistRevision)` 且排队聊天与当前聊天一致时,陈旧的 `pendingPersist` 标志自动清除。
|
||||
|
||||
这条解决了"SQL 已确认 rev=2、但 pendingPersist 赖着不走、把提取一直卡死"的 bug。reducer 让"陈旧 pending 卡住提取"在结构上不可能发生。
|
||||
|
||||
历史上的语义修复(Phase 2 引入不变量、Phase 5 把调用点改为显式事件)都保留在该文件头注释里。
|
||||
|
||||
## 图谱可写性门禁
|
||||
|
||||
`sync/graph-mutation-gate.js` 决定"现在能不能改图谱",避免在加载中/恢复中/未进入聊天时误写。
|
||||
|
||||
关键判定(注入式 impl):
|
||||
|
||||
- `ensureGraphMutationReady` — 操作前的总门禁
|
||||
- `getGraphMutationBlockReason` — 给用户的暂停原因文案
|
||||
- `assertRecoveryChatStillActive` — 异步恢复过程中,校验聊天没被切走(切走则抛 abort)
|
||||
- `getGraphPersistenceLiveState` — 把内部状态投影成面板/调试可读形态
|
||||
|
||||
## 向量门禁与 reroll 边界
|
||||
|
||||
- `vector/vector-gate.js` — 向量准备/修复前置门禁,决定 skip / repair / blocked / sync。
|
||||
- `runtime/reroll-transaction-boundary.js` — reroll 召回复用事务边界。
|
||||
|
||||
**reroll 不变量:**
|
||||
|
||||
> reroll 助手楼层时,若上方用户楼层未变且存在可复用的持久召回记录,则跳过预召回历史恢复和新向量检索;但被 reroll 的助手楼层的**图谱回滚必须保留**(走既有 `onReroll` 路径)。
|
||||
|
||||
换句话说:召回注入可以复用,但图谱状态该回滚还得回滚。两者不能混为一谈——这是"reroll 乱召回"修复的核心。
|
||||
|
||||
## 副本一致性模型
|
||||
|
||||
Authority 场景下有三处存储,它们**不是平级的版本副本**:
|
||||
|
||||
| 存储 | 角色 |
|
||||
| --- | --- |
|
||||
| Authority SQL | **规范主源**(canonical primary) |
|
||||
| Blob checkpoint | 备份副本(backup replica) |
|
||||
| Trivium | 搜索副本(search replica) |
|
||||
|
||||
**不变量:**
|
||||
|
||||
> 只有 Authority SQL 有可靠的图谱版本。当 SQL rev > Blob/Trivium rev 时,状态是"副本待同步",**不是**"数据漂移"。SQL 领先时不建议从 checkpoint 恢复(那会用旧数据覆盖新数据)。
|
||||
|
||||
> checkpoint 生成时,若 SQL 是主存储层,必须以 Authority SQL 快照为源;SQL 导出失败/为空时,checkpoint 生成失败(`authority-sql-checkpoint-source-empty`),绝不回退到可能陈旧的运行时图谱。
|
||||
|
||||
> 副本同步动作(checkpoint 写入、Trivium/向量同步)相互独立执行,一个失败不阻塞其余。
|
||||
|
||||
## 依赖注入接缝
|
||||
|
||||
控制平面模块通过一个 `runtime` 对象拿到所有依赖,由 `index.js` 的 `create*Runtime()` builder 提供。这有个隐患:模块"期望"的 `runtime.X` 必须全部被 builder 提供,否则运行时(尤其 fallback 路径)才炸。
|
||||
|
||||
`tests/runtime-deps-completeness.mjs` 守住这条线。详见 [`../contributing/conventions.md`](../contributing/conventions.md) 和 [`../contributing/testing.md`](../contributing/testing.md)。
|
||||
87
docs/architecture/overview.md
Normal file
87
docs/architecture/overview.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# 架构总览
|
||||
|
||||
ST-BME 是一个 SillyTavern 第三方前端扩展:在聊天进行时,把对话提炼成一张**记忆图谱**(节点 + 关系),并在生成前把相关记忆召回、注入回提示词。
|
||||
|
||||
`manifest.json` 指向 `index.js`(主入口/编排层)和 `style.css`。
|
||||
|
||||
## 子系统地图
|
||||
|
||||
```
|
||||
index.js 主入口:事件钩子、设置管理、流程调度、依赖注入组合根
|
||||
│
|
||||
├── graph/ 记忆图谱数据结构、节点/关系 schema、快照、认知/区域/时间线状态
|
||||
├── maintenance/ 写入链路:提取、整合、压缩、分层总结、智能触发
|
||||
├── retrieval/ 读取链路:混合检索、图扩散、评分、增强、注入格式化
|
||||
├── vector/ 向量索引、直连 embedding、向量空间身份、维度门禁
|
||||
├── sync/ 持久化与同步:IndexedDB/OPFS 存储、快照契约、控制平面模块
|
||||
├── prompting/ 提示词构建、任务预设、正则/世界书/EJS 任务模式
|
||||
├── llm/ LLM 请求封装
|
||||
├── runtime/ 运行时状态、设置默认值、身份解析、注入式控制器/工厂
|
||||
├── host/ SillyTavern 宿主适配(事件绑定、上下文、原生渲染)
|
||||
├── ui/ 面板、图谱渲染、消息级 UI、召回卡片
|
||||
├── ena-planner/ ENA Planner(独立规划子系统)
|
||||
└── native/ Native/WASM 加速(灰度,fail-open 回退 JS)
|
||||
```
|
||||
|
||||
`index.js` 在这次重构后正在收敛成**组合根(composition root)**:它持有少数模块级运行时状态,并通过 `create*Runtime()` 把依赖显式注入给抽出的控制器模块。详见 [`control-plane.md`](control-plane.md) 和 [`../contributing/conventions.md`](../contributing/conventions.md)。
|
||||
|
||||
## 三条数据路径
|
||||
|
||||
ST-BME 的运行可以归纳为三条相对独立的链路。
|
||||
|
||||
### 写入链路(对话 → 记忆图谱)
|
||||
|
||||
助手回复落地后触发,把新对话提炼进图谱。
|
||||
|
||||
```
|
||||
助手消息落层
|
||||
→ 自动提取计划(够不够触发?智能触发?)
|
||||
→ 构建结构化提取输入(过滤 think/analysis 等)
|
||||
→ LLM 提取 → 规范化操作(create/update/delete/link)
|
||||
→ 写入图谱节点与关系(含时序边)
|
||||
→ 后处理:整合去重 → 分层总结 → 反思 → 睡眠遗忘 → 压缩
|
||||
→ 向量同步(为新节点生成 embedding)
|
||||
→ 持久化到耐久存储层
|
||||
```
|
||||
|
||||
算法细节见 [`../algorithms/extraction.md`](../algorithms/extraction.md) 和 [`../algorithms/consolidation-and-compression.md`](../algorithms/consolidation-and-compression.md)。
|
||||
|
||||
### 读取链路(用户输入 → 注入提示词)
|
||||
|
||||
生成前触发,把相关记忆召回并注入。
|
||||
|
||||
```
|
||||
解析召回输入(override / 发送意图 / 聊天尾部用户楼层)
|
||||
→ 可复用的持久召回记录?命中则跳过新检索
|
||||
→ 向量预筛(多意图拆分 + 多查询)
|
||||
→ 图扩散(PEDSA 扩散激活)
|
||||
→ 混合评分(图 + 向量 + 词法 + 重要度 × 时间衰减)
|
||||
→ 认知边界过滤 + 可选 DPP 多样性 + 可选残差召回
|
||||
→ 可选 LLM 精排
|
||||
→ 访问强化 + 可选概率召回
|
||||
→ 注入格式化(按 POV/区域分桶成表格)
|
||||
```
|
||||
|
||||
算法细节见 [`../algorithms/retrieval.md`](../algorithms/retrieval.md) 和 [`../algorithms/diffusion-and-dynamics.md`](../algorithms/diffusion-and-dynamics.md)。
|
||||
|
||||
### 安全链路(保护已有记忆不被误删/误覆盖)
|
||||
|
||||
横跨写入和读取,确保宿主的各种异常状态(只渲染最近 N 条、reroll、切换聊天、历史被编辑)不会误清空或覆盖记忆图谱。
|
||||
|
||||
```
|
||||
历史变动检测 → 必要时历史恢复(replay 或全量重建)
|
||||
渲染切片识别 → 暂停破坏性恢复
|
||||
Restore Lock → 恢复期间阻断图谱变更
|
||||
持久化身份校验 → 防止把别的聊天身份当成当前聊天
|
||||
```
|
||||
|
||||
细节见 [`control-plane.md`](control-plane.md) 和 [`../features/history-safety.md`](../features/history-safety.md)。
|
||||
|
||||
## 控制平面 vs 数据平面
|
||||
|
||||
这次重构的核心理念是把**"做决定"(控制平面)和"执行副作用"(数据平面)分开**:
|
||||
|
||||
- **控制平面**:身份解析、持久化确认状态机、图谱可写性门禁、向量门禁、reroll 边界。这些是纯逻辑/策略,已抽成可独立测试的注入式模块。
|
||||
- **数据平面**:实际的 IndexedDB/OPFS/Authority/Luker 读写。仍在编排层,由控制平面的决定驱动。
|
||||
|
||||
这条分界是过去大量 bug(陈旧 pending、未进入聊天、reroll 乱召回、一致性漂移)的修复基础。详见 [`control-plane.md`](control-plane.md)。
|
||||
74
docs/architecture/server-integration.md
Normal file
74
docs/architecture/server-integration.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# 服务器集成(Authority / st-doa)
|
||||
|
||||
ST-BME 可以独立运行(纯前端),也可以在检测到 st-doa/Authority 服务器时自动升级到更稳定的数据平面操作。本文档说明这套自动集成。
|
||||
|
||||
## 核心原则
|
||||
|
||||
1. **BME 独立运行是第一公民。** 没有 st-doa 时,BME 必须完整可用,优雅降级,不报错。纯前端模式显示"纯前端模式",不是错误状态。
|
||||
2. **第三方自定义 URL embedding 是主流路径。** OpenAI 兼容 `/v1/embeddings`、one-api、new-api、litellm、vLLM、llama.cpp、Ollama 桥接等,都是一等公民。embedding 默认在客户端执行(`embeddingExecution` 默认 `client`)。
|
||||
3. **集成是自动的,不需要用户配置。** 能力探测驱动的升级/降级,没有面向用户的"服务器模式"开关。既要优雅降级(无 st-doa),也要优雅升级(有 st-doa),全程零用户干预。
|
||||
|
||||
> Authority 是**增强层**,不是硬依赖。它不生成 embedding——只通过 Trivium 存储/搜索向量,并要求调用方提供向量。
|
||||
|
||||
## 三档集成模式
|
||||
|
||||
| 模式 | 含义 | embedding | 存储 |
|
||||
| --- | --- | --- | --- |
|
||||
| **Mode A** | 纯前端(基线) | 前端生成 | 浏览器本地(IndexedDB/OPFS) |
|
||||
| **Mode B** | Authority 存储增强 | 前端仍生成 | Authority 负责可靠 SQL/Trivium 持久化与诊断 |
|
||||
| **Mode C** | 可选服务器端 embedding | 服务器端(**仅 opt-in**) | Authority |
|
||||
|
||||
Mode A 必须完全稳定(它是基线)。Mode C 是可选的、必须显式开启。
|
||||
|
||||
## 自动升级状态机
|
||||
|
||||
ST-BME 维护一个派生的 `authorityUpgradeState`,由能力探测驱动:
|
||||
|
||||
```
|
||||
standalone → probing → shadow → candidate → enhanced → degraded
|
||||
```
|
||||
|
||||
- **standalone**:没有 Authority,纯前端模式。
|
||||
- **probing**:检测到可能的 Authority,正在探测能力。
|
||||
- **shadow / candidate**:逐步确认 Authority 各项能力。
|
||||
- **enhanced**:Authority 各项能力就绪,使用增强数据平面。
|
||||
- **degraded**:Authority 部分能力不可用,自动回退到仍可用的层。
|
||||
|
||||
面板显示当前自动模式,但不提供手动切换开关。
|
||||
|
||||
## 能力探测
|
||||
|
||||
ST-BME 从 Authority 的 `/probe` 和 session 响应里读取 BME 专属能力标志:
|
||||
|
||||
- `bmeVectorManifestReady`
|
||||
- `bmeVectorApplyReady`
|
||||
- `bmeVectorApplyJobsReady`
|
||||
- `bmeServerEmbeddingProbeReady`
|
||||
- `bmeCandidateSearchReady`
|
||||
- `bme.protocolVersion`
|
||||
|
||||
旧版/无 Authority 时这些默认全 false,自动走 fallback。这保证了对老 Authority 探针的向后兼容。
|
||||
|
||||
## 任务类型协商
|
||||
|
||||
Authority 的后台 job 系统只支持特定 job 类型。ST-BME 不能盲目提交。
|
||||
|
||||
> `shouldUseAuthorityJobs` 校验具体 job 类型是否在服务器声明的 registry 中(从 `jobs.builtinTypes` / `jobs.registry.jobTypes` / `core.health.jobRegistrySummary.jobTypes` 读取)。服务器没声明支持的 job 类型不提交,直接走直连同步。
|
||||
|
||||
> 空的 `supportedJobTypes: []` 表示"服务器明确不支持任何 job";缺失/默认则表示"未知",保持对旧探针的向后兼容。
|
||||
|
||||
## 向量应用端点
|
||||
|
||||
`POST /bme/vector-apply`:客户端生成向量后,由 Authority 执行 Trivium 批量 upsert + link。
|
||||
|
||||
> Authority 不碰 embedding API key。它只接收客户端生成好的向量。
|
||||
|
||||
> 该端点按批校验 `vectorSpaceId` / `observedDim` 一致性,拒绝混合维度,返回带类型的校验错误。
|
||||
|
||||
向量空间身份和维度门禁的算法见 [`../algorithms/vector-and-embedding.md`](../algorithms/vector-and-embedding.md)。
|
||||
|
||||
## Authority SQL 图谱存储选择
|
||||
|
||||
> Authority SQL 图谱持久化由 `storagePrimaryReady`(SQL + session + permission)门控,**不是** `serverPrimaryReady`(SQL + Trivium + Jobs + Blob)。
|
||||
|
||||
这意味着 Blob/Jobs/Trivium 降级不会禁用 SQL 持久化——SQL 图谱持久化只需要 SQL + 会话 + 权限。这条避免了"某个增强能力不可用就连主存储都不写"的过度联动。
|
||||
78
docs/architecture/storage-and-formats.md
Normal file
78
docs/architecture/storage-and-formats.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# 存储分层与数据格式
|
||||
|
||||
ST-BME 的图谱数据可能存在多种位置,取决于宿主环境和服务器能力。本文档说明分层策略、快照契约,以及保证"以后改格式不用大迁移"的向前兼容纪律。
|
||||
|
||||
## 存储分层
|
||||
|
||||
| 层 | 用途 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| **Authority SQL** | 规范主源 | 有 st-doa/Authority 时的权威存储;唯一有可靠图谱版本 |
|
||||
| **Luker chat-state** | 宿主当前聊天主存储 | Luker 宿主下作为当前聊天状态的主写入目标 |
|
||||
| **IndexedDB** | 浏览器本地主存储 | 普通 SillyTavern 下的默认本地存储 |
|
||||
| **OPFS** | 浏览器本地存储(替代) | Origin Private File System sidecar |
|
||||
| **Blob checkpoint** | 备份副本 | Authority 场景的备份,非主源 |
|
||||
| **Trivium** | 搜索副本 | 向量搜索存储,非主源 |
|
||||
| **metadata-full / shadow / runtime-recovery** | 仅恢复用 | 灾难兜底,**永远不能**推进持久化确认状态 |
|
||||
|
||||
存储层的选择是能力探测驱动的,不需要用户手动配置。Authority 是增强层,缺席时优雅降级。详见 [`server-integration.md`](server-integration.md)。
|
||||
|
||||
**关键设计:** Luker 宿主下,浏览器全图镜像默认关闭(`cacheStorageTier = none`),避免把大图谱重复写进 IndexedDB/OPFS。只有用户显式"重建本地缓存"才写浏览器缓存。
|
||||
|
||||
## 快照契约
|
||||
|
||||
耐久快照的顶层结构被**冻结**为固定的六个键(实现见 `sync/graph-snapshot-schema.js`):
|
||||
|
||||
```
|
||||
{
|
||||
schemaVersion, // 顶层快照布局版本
|
||||
meta, // 图谱元信息(含 meta.schemaVersion 等)
|
||||
nodes,
|
||||
edges,
|
||||
tombstones,
|
||||
state // lastProcessedFloor / extractionCount 等运行状态
|
||||
}
|
||||
```
|
||||
|
||||
> **不变量:顶层这六个键永不增减。** 所有未来演进都放进 `meta` / `state` / 各记录字段里——这些层级已经容错(保留未知字段)。
|
||||
|
||||
## 向前兼容纪律
|
||||
|
||||
这是保证"ST-BME 以后不需要再做 v4/v5 大迁移"的核心机制。它**不是**一个信封框架或预留字段,而是一条解析纪律:
|
||||
|
||||
### 1. 宽容解析(保留未知字段)
|
||||
|
||||
> 读取方遇到不认识的字段,必须保留、不报错、不丢弃。
|
||||
|
||||
具体现状:
|
||||
- 节点 / 边 / tombstone 记录:整对象克隆,未知字段天然保留。
|
||||
- `meta`:展开保留,已有 `meta.schemaVersion`。
|
||||
- 顶层:冻结为六键 + 保留未知顶层字段。
|
||||
|
||||
原理:如果读取代码遇到不认识的字段就崩或就丢,那么**任何**格式改动都会逼出一次迁移;如果遇到不认识的字段就忽略并原样保留,那么以后所有改动都是**加法**,老版本读新数据照样不崩,永远不需要换命名空间、不需要大搬家。这是 protobuf 这类协议几十年验证过的做法。
|
||||
|
||||
### 2. 只加不减
|
||||
|
||||
> 新字段一律可选,永不删字段、永不改已有字段的含义。
|
||||
|
||||
这样新版写的数据老版仍能读,反之亦然。
|
||||
|
||||
### 3. 就地升级(upgrade-on-read)
|
||||
|
||||
实现见 `sync/graph-snapshot-upgrade.js`,接入真实加载路径 `buildGraphFromSnapshot`。
|
||||
|
||||
> 读到旧 `schemaVersion` 时,在内存里逐级升级一格再用,下次保存时顺手写成新版。单调、幂等、读到比当前更新的数据绝不向下改写。
|
||||
|
||||
当前快照布局版本是第一版,升级链为空,但框架和铁律已立住——以后改格式只是"加一个升级步骤",不是搬家。
|
||||
|
||||
### 关于 Luker sidecar
|
||||
|
||||
Luker checkpoint 存的是完整序列化图谱(`serializeGraph`),节点/边的未知字段被保留——所以图谱正文通过 Luker 是容错的。sidecar 上的信封元数据(manifest 统计、checkpoint 元信息)用白名单规范化是**有意为之**:那些是可重算的运行指标,不是图谱本身,丢了能重建。
|
||||
|
||||
## 图谱内容版本 vs 快照布局版本
|
||||
|
||||
注意区分两个版本号:
|
||||
|
||||
- **`GRAPH_VERSION`**(`graph/` 内):图谱**内容结构**的版本,有自己的 v2→v9 迁移链,管节点/关系语义的演进。
|
||||
- **`schemaVersion`**(快照顶层):耐久快照**布局**的版本,管"存进磁盘的信封形状"的演进。
|
||||
|
||||
两者独立。本文档的向前兼容纪律针对的是后者(快照布局)。
|
||||
Reference in New Issue
Block a user