ADR-0022:双层一致性(工作区 ↔ 版本 ↔ 实库),禁止自动双向同步
- 状态:✅ 已接受(2026-08-04;人工拍板主题,Vision 5m 循环按此迭代)
- 决策者:项目维护者
- 相关:ADR-0016(体验优先主线)、ADR-0009(presence/增量 sync)、ADR-0008(数据源隔离)
背景
产品同时存在三个「真相」:内存中的 projectJSON(正在编辑)、已保存的版本(db_change)、以及活的数据库 schema。此前 UI 把它们混在一起表达:
- 顶栏只有「已保存 / 保存中 / 保存失败」,讲的是落库,不回答「与最新版本比是否有未提交改动」
- 实库一致性靠
db_version(人写的版本号字符串)比较,compareStringVersion对空段/非法输入返回NaN,不可判时表现为「一致」 - 版本基线取自分页列表的
versions[0],翻页/排序变化即基线漂移 - 卸载时
closeSocket无条件Save.saveProject,结果被丢弃 → 静默覆盖 + 静默失败
用户心智已被 Git 训练过:status 看有没有改动、pull 拿别人的、push 推自己的。产品应借用它,而不是发明新词。
决策
| 议题 | 决策 |
|---|---|
| 分层 | A 层(工作区):内存 projectJSON ↔ 最新版本;B 层(实库):模型 ↔ 活库 schema。两层分开呈现,不合并成一个「同步状态」 |
| A 层呈现 | 实时 dirty chip:干净 / 有改动(含变更计数)/ 落库失败;语义与顶栏保存状态合并,不制造重复反馈 |
| A 层基线 | 独立查询最新版本(专用接口或 size:1 排序查询),禁止用分页列表的 versions[0] |
| B 层呈现 | 五态:一致 / 模型领先 / 库领先 / 双向分叉 / 未知;每态给可行动文案,未知态给 4 路出口 |
| B 层判据 | 实测 schema 指纹(表/列/索引规范化后哈希);db_version 降级为提示,不再作为一致性真相 |
| B 层触发 | 设计器顶栏进页可自动探一次(水合 DB 胶囊);版本页/其它入口仍可显式重探。禁止定时轮询与自动双向同步(拉/推仍须用户发起) |
| 动作隐喻 | 「拉取」= 从实库反向解析 → 保存为版本;「推送」= 同步 DDL → 成功后钉基线。二者都是用户显式动作 |
| 红线 | 禁止自动双向同步(无论定时、进页面、还是「顺手帮用户」)。模型与实库之间的写操作必须由用户发起并可预览 |
| 不可判即未知 | 版本号/指纹无法比较时必须显示「未知」,禁止默认「一致」 |
| 分享访客 | 只读分享不暴露 B 层(无凭证、不代表访客探库) |
| 并发 | project 保存走乐观锁(冲突 → 可行动提示,不静默覆盖);db_change.version 加唯一约束(Flyway);卸载不做盲存,仅脏时落库且结果可见 |
| 北极星口径 | 「有版本保存」= 非空 diff 的版本;空 changes 的保存不计入指标 |
后果
- 正面:状态不再骗人(未知就是未知);并发下不丢数据;用户用 Git 习惯即可理解;实库操作永远由人拍板
- 代价:多一次「最新版本」查询;schema 指纹 需按方言规范化(首期 P0 四库),实现量大于比字符串;顶栏进页自动探一次以水合状态(仍禁止定时双向同步)
- 明确不做:定时/自动的双向同步;把实库状态塞进 projectJSON;用
db_version继续当一致性真相;替访客探库