Entity Graph 治理收敛与历史债务清理方案(2026-06)
目的
本文档定义 Kototoro 当前 entity graph / source / metadata / tracking 相关模型的治理收敛方案。
目标不是继续局部修补,而是系统性解决以下问题:
- 实体身份、阅读投影、追踪元数据、缓存/候选、历史兼容字段长期混写;
- repair 诊断将不同层级的问题混为一类,导致噪音过大;
- entity 级 metadata source 被批量镜像到 local manga,持续制造新污染;
- backup / restore 与旧迁移逻辑继续传播 legacy 语义;
- 新旧版本残留字段仍参与运行时主决策,维护成本持续上升。
本方案遵循以下原则:
- KISS:先收口语义,再做结构演进;
- YAGNI:不引入新图数据库、不引入复杂远程同步模型;
- DRY:运行时只能存在一套主 metadata authority 语义;
- SOLID:identity、projection、metadata authority、cache 各自单一职责。
同时明确范围边界:
- 当前跨设备数据主链以
backup / restore / WebDAV为准; - 仓库中遗留的
sync/与旧kotatsu sync源码不作为本方案的当前 authoritative 输入; - 因此文档中关于“同步隔离”“旧语义回流”的约束,默认都落在当前 backup / restore 主链上。
现状结论
当前主要污染链路已经明确:
entity_preferences保存 entity 级 metadata source。ContentDataRepository.setEntityMetadataSourceSelection()支持mirrorLocalMangaIds,会把 entity 级选择镜像写入多个 local manga prefs。EntityGraphRepository.inspectRepairIssues()将以下不同语义混入 tracking suspect:- entity-level tracking binding
- per-manga metadata source mismatch
tracking_site_links缓存漂移
- backup / restore 与历史 migration 会继续保留 legacy 结构,并把历史语义带回当前运行时。
结果是系统中同时存在两套“metadata source 真相”:
- entity 级:
entity_preferences - manga 级:
preferences.metadata_source_*
这正是概念冗余和持续污染的根源。
治理目标
将运行时语义收敛为一句话:
Entity 只负责身份聚合,Local Manga 只负责阅读投影,Tracking Source 只负责元数据权威,缓存和局部覆盖全部降级为附属物。
对应目标如下:
- entity 成为唯一的跨源身份中心;
- 阅读源绑定不再反向定义 entity 身份;
- entity metadata source 成为唯一默认 metadata authority;
- per-manga metadata source 降级为显式 override;
tracking_site_links降级为 cache / audit / suggestion history;- repair 分类按问题层级拆分,不再混报;
- backup / restore 不再把 legacy 数据重新提升为当前真相。
概念分级
一级:核心模型(保留并继续演进)
以下概念保留为主模型:
entityentity_bindingentity_preferencesrelation
要求:
- 运行时主流程必须优先依赖这些结构;
- 新功能不得绕开这些结构另建平行语义;
- repair、details、merge、bind 的主决策必须落在这一层。
二级:附属模型(降级,不再承载主真相)
以下概念保留,但明确降级:
tracking_site_linkspreferences.metadata_source_*preferences.ignoredTrackingSuggestion*
要求:
- 只能作为缓存、局部覆盖、候选历史或抑制状态存在;
- 不得继续被当作 entity 真相或 tracking binding 真相;
- 不得参与默认 metadata authority 计算。
三级:历史兼容模型(只读兼容,逐步退役)
以下语义视为历史债务:
- 由旧
preferences迁移生成的 entity metadata 影子语义; - restore 时恢复回来的
LEGACY/SYNCbinding; - 旧 relation 或历史 merge/mirror 残留;
- 新旧版本 backup 中残存但无法证明仍是当前真相的数据。
要求:
- 可读取、可修复、可清理;
- 不得默认参与当前运行时主决策;
- 不能在新写路径中被重新扩散。
目标边界模型
Entity
职责:
- 表示同一作品/人物/组织的聚合身份;
- 持有 primary name、aliases;
- 持有 entity-level tracking bindings;
- 持有 entity-level metadata preference;
- 持有 preferred local projection。
非职责:
- 不负责具体阅读入口;
- 不负责 per-manga 级别 override;
- 不负责缓存性质的 tracking match history。
Entity Binding
职责:
- 表示 entity 与 source-native item 的绑定;
- 区分来源类型,如 reading / tracking / local / manual;
- 提供 provenance:
sourceKind、state、createdBy、updatedAt。
规则:
MANUAL不可被自动匹配覆盖;REJECTED阻止同 key 自动回流;LEGACY可读但不默认视为强真相;- candidate/cache 不得直接提升为 confirmed。
Entity Preferences
职责:
- 保存 entity 级展示与默认选择;
- 表达默认 metadata authority;
- 表达 preferred local projection。
规则:
- entity details 默认只读 entity preferences;
- manga details 仅在显式 override 存在时才覆盖 entity 默认值;
- preference 应逐步从 raw service/remote id 演进为 binding 引用。
Local Manga
职责:
- 表示一个本地可读的 source projection;
- 负责打开、阅读、更新等执行行为;
- 在需要时可被 entity 选为 preferred local projection。
非职责:
- 不定义 entity 级 tracking identity;
- 不默认承载 entity metadata authority。
Tracking Site Links
职责:
- 记录 per-manga tracking match cache;
- 记录 suggestion history / audit / suppression state。
非职责:
- 不作为 entity 真相;
- 不作为 repair 中的 tracking binding corruption 主证据。
Per-Manga Metadata Source
职责:
- 仅作为单 manga 的显式 metadata override。
规则:
- 默认不写;
- 默认不继承 entity metadata source;
- 不允许批量镜像成为 entity 默认值的影子副本。
代码与数据现状映射
核心结构
entity/entity_binding/relation: EntityGraphEntities.ktentity_preferences: EntityPrefsRecord.kt- DAO 读写: EntityGraphDao.kt
主要污染入口
- entity metadata source 镜像写入: ContentDataRepository.kt
- 调用方:
repair 噪音入口
- repair 扫描逻辑: EntityGraphRepository.kt
当前分支已明确包含:
entity_tracking_bindingentity_metadata_sourcemanga_metadata_sourcetracking_site_link
历史传播入口
entity_preferences首次迁移: Migration47To48.kt- restore 逻辑: BackupRepository.kt
保留 / 降级 / 删除决策
保留
entityentity_bindingrelationentity_preferences
原因:
- 已经构成当前 graph 主模型;
- 已有 FK、state、origin 等演进基础;
- 可以在不推翻现有架构的前提下继续收敛。
降级
preferences.metadata_source_kindpreferences.metadata_source_servicepreferences.metadata_source_remote_idtracking_site_links
降级后语义:
preferences.metadata_source_*:单 manga 显式 override onlytracking_site_links:cache / audit / suggestion history only
退役目标
以下能力需要逐步退役:
- entity metadata source 批量 mirror 到全部 local manga
- 将 per-manga metadata mismatch 计入
SUSPECT_TRACKING_BINDING - 将
tracking_site_links视作 entity binding 失败证据 - 让 raw service/remote id 长期直接承担 preference 主引用语义
分阶段治理方案
Phase 1:止血与诊断去噪
目标:
- 停止继续制造新污染;
- 让 repair 结果重新可信;
- 不先做高风险 schema 改造。
实施项:
- 修正
inspectRepairIssues()分类:entity_tracking_binding->SUSPECT_TRACKING_BINDINGmanga_metadata_source->SUSPECT_METADATA_SOURCEtracking_site_link-> 新增或单列为 stale cache/link 问题
- repair UI 和计数同步调整;
setEntityMetadataSourceSelection()的调用方默认不再传多项mirrorLocalMangaIds;- 仅保留“用户明确对单个 projection 执行 override”时的单项写入。
验收:
- suspect tracking count 显著下降,且接近真实 entity binding 问题规模;
- 新操作不再把 entity metadata source 批量复制到 local manga。
Phase 2:语义收口
目标:
- 将 entity metadata authority 与 per-manga override 彻底区分;
- 保留兼容读能力,但运行时只认一套默认真相。
实施项:
- 统一读取优先级:
- entity details 默认读取
entity_preferences - manga details 先看 explicit override,否则回退 entity default
- entity details 默认读取
- 所有自动绑定/合并流程仅写 entity-level metadata source;
- per-manga metadata source 不再由 entity bind / merge 自动生成;
- 明确
tracking_site_links不再影响 entity identity 判定。
当前工作树已完成一项基础收口:
EntityGraphRepository.findEntityIdsByAnyMangaIds(...)已移除基于tracking_site_links的 owner fallback;- 公共 owner helper 现在只依赖
local_mangaconfirmed binding; - cache / audit 数据不再通过通用解析入口反向定义 entity。
DetailsViewModel的TrackingItem入口也已改为 entity-first anchor 解析;- legacy 无 entity 场景下才允许从 link cache 里选择本地 projection anchor。
BindTrackingToEntitiesUseCase在 preview 阶段已优先复用 confirmed entity binding;- 同一个 work/entity 已经存在的 tracking binding 不再强制重新搜索。
验收:
- details、repair、bind 三条主链路都能区分 entity default 与 manga override;
- 运行时没有新的“双真相”写入路径。
Phase 3:偏好绑定化
目标:
- 将 preference 从裸
serviceId + remoteId演进到 binding 引用; - 降低 orphan/stale raw ids 的长期维护成本。
建议做法:
优先采用兼容式增量设计,而不是一次性重做表结构。
推荐新增字段:
metadata_binding_sourcemetadata_binding_external_id- 可选:
preferred_local_binding_sourcepreferred_local_binding_external_id
说明:
- 当前
entity_binding的主键就是source + external_id; - 先复用现有主键语义,比引入 surrogate binding id 风险更低;
- 旧
metadata_source_service/remote_id保留为兼容回读字段。
验收:
- 新写入优先写 binding reference;
- 旧字段仅兼容读取,不再作为长期主写入口。
Phase 4:历史数据清理
目标:
- 清理历史镜像污染;
- 控制 backup/restore 的旧语义回流;
- 让 legacy 数据可见、可修、不可继续扩散。
实施项:
- 扫描并清理被 mirror 污染的
preferences.metadata_source_*; - 清理明显不兼容的 per-manga metadata source;
- 清理 stale
tracking_site_links; - 只对可确定错误的数据做自动清理;
- 不能确定的降级为
LEGACY/SUSPECT/CANDIDATE,进入 repair 流程。
验收:
- 自动清理不误伤明显可能仍有用户意图的数据;
- legacy 数据不再参与默认主流程决策。
Phase 5:兼容字段退役
目标:
- 将旧字段从“运行时主逻辑依赖项”降为“迁移兼容资产”;
- 为后续移除历史包袱创造条件。
实施项:
- 停止新写入旧 raw metadata 字段;
- 在至少一个稳定周期后,把它们降级为只读兼容;
- 最终评估是否移除或只保留数据库历史兼容。
验收:
- 不再有新代码依赖这些旧字段做主决策;
- 兼容字段即便存在,也不会继续污染新数据。
repair 体系重构
推荐分类
repair 结果建议拆分为以下类别:
ORPHAN_PREFERRED_LOCALORPHAN_METADATA_SOURCESUSPECT_TRACKING_BINDINGSUSPECT_METADATA_SOURCECONFLICTING_READING_BINDINGSUSPECT_MISMERGED_LOCAL_WORKSTALE_LEGACY_RELATION- 可选:
STALE_TRACKING_CACHE_LINK
分类原则
SUSPECT_TRACKING_BINDING
只包含:
- entity-level tracking binding 与 entity/local evidence 冲突
不包含:
- per-manga metadata source mismatch
tracking_site_links漂移
SUSPECT_METADATA_SOURCE
包含:
- entity metadata source 与 entity aliases / local projections 不兼容
- per-manga explicit metadata override 不兼容
STALE_TRACKING_CACHE_LINK
包含:
tracking_site_links中陈旧、漂移或与当前本地标题不兼容的缓存匹配
它是缓存质量问题,不是 entity identity corruption。
repair 动作拆分
建议提供独立动作:
- 拒绝 suspect entity tracking binding
- 清理 entity metadata source
- 清理 per-manga metadata override
- 清理 stale tracking cache/link
- 拆分或 detach suspect local projection
Backup / Restore 治理规则
目标
- backup 允许保存历史数据;
- restore 不允许把历史数据重新提升为当前真相;
- 跨设备同步不继续放大旧污染。
规则
- restore
entity_binding时保留state/createdBy/updatedAt语义; LEGACY/SYNC不自动提升为CONFIRMED/MANUAL;- restore
entity_preferences时不得反向回写 manga prefs; - 评估是否将
tracking_site_links从核心备份资产降级为可选缓存资产; - backup 中出现的旧 raw metadata 结构只做兼容导入,不得重建 blind mirror。
数据清理策略
可自动清理
- entity metadata source 指向不存在 binding;
- per-manga metadata source 与本地标题完全不兼容;
- stale
tracking_site_links; - orphan preference / orphan relation。
只降级不自动删除
- restore 回来的 legacy binding;
- 兼容性存疑但无法确定错误的 metadata source;
- 历史 relation 但仍可能有参考价值的数据。
必须人工确认
- 疑似误合并 local work;
- 多 tracking source 互相冲突且均可能合理的 entity;
- 用户可能主动设置过的跨语种特殊 metadata override。
PR 拆分建议
PR-1:repair 分类止血
范围:
EntityGraphRepository.inspectRepairIssues()- repair report / viewmodel / panel 文案与计数
特点:
- 风险低;
- 收益直接;
- 不动 schema。
PR-2:停止 blind mirroring
范围:
ContentDataRepository.setEntityMetadataSourceSelection()- 所有传入
mirrorLocalMangaIds的调用方
目标:
- entity metadata source 默认只写 entity;
- per-manga override 仅保留显式单项写入。
PR-3:preference 绑定化
范围:
entity_preferencesmigration- DAO / repository 读写优先级
目标:
- 引入 binding reference 字段;
- 新写路径优先写 binding reference。
PR-4:历史清理与 restore 降噪
范围:
- backup / restore
- stale data cleanup
- 可选:
tracking_site_links备份范围调整
目标:
- 阻断旧语义跨设备回流;
- 对历史污染执行温和但明确的收敛。
验收标准
治理完成后,系统应满足以下条件:
SUSPECT_TRACKING_BINDING基本只反映真实 entity-level tracking identity 问题;- entity metadata source 不再自动污染所有 local manga prefs;
tracking_site_links明确降级为 cache / audit 数据;- entity / local / tracking / metadata 四层语义在代码和 repair 中都能明确区分;
- restore 不会重新制造 mirror 型污染;
- 旧 raw metadata 字段即便仍存在,也不再主导运行时主逻辑;
- merge / bind / details / repair 四条主链路都有一致的边界语义。
非目标
本方案不包含以下内容:
- 引入远程实体中心或 graph backend;
- 重写整个详情页 UI;
- 一次性删除所有 legacy 数据;
- 通过模糊标题匹配自动决定最终身份真相;
- 为未来未落地场景预建额外抽象层。
推荐执行顺序
推荐按以下顺序推进:
- repair 分类止血;
- 停止 blind mirroring;
- 统一 entity default 与 manga override 读取语义;
- preference 绑定化;
- 历史数据清理;
- 兼容字段退役。
这样可以先解决“持续制造脏数据”和“诊断噪音失真”,再进入 schema 与迁移层面的收敛工作,风险最低,回报最高。