Files
ai_site/松离线-dbsync方案-最终版.md
whm b04b180d30 feat: harden loose-offline sync for user JWT, schema, and console ops
Enable Binding-scoped agent push/pull, empty-table schema ensure, SyncPage inspect/drop-table, default module import, and agent-bound publish docs from the 宇恒联调意见.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 09:47:35 +08:00

293 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 松离线 + dbsync 方案 · 最终版(冻结)
> **效力**:本文为双方协商后的**唯一开工依据**。
> 原方案稿 / 审评追加 / 决议草稿见同目录 `松离线-dbsync方案.md`(过程稿,不再改口径)。
> 冻结日期2026-07-31
---
## 0. 一句话
**技术**:松离线写本地 B最终 UUID+ 本机 agent 经 dbsync 幂等对齐线上 A冲突自动 LWW落败入**平台超级管理员**覆盖日志。
**产品**:宇恒**默认零影响**;仅显式 `local_dbsync` + **表白名单** opt-inM4 前不对全员切默认。
---
## 1. 硬约束(不可回退)
| # | 约束 |
|---|------|
| H1 | 未开通同步的用户:保存 / 自增表 / 插件行为与现网一致(行为 diff = 0 |
| H2 | 默认**不得**自动升为 `local_dbsync` |
| H3 | agent 停运**不得**导致 `apply_write` / 保存失败;心跳只驱动 UI |
| H4 | 默认建表模板**保持自增**;仅「同步表模板」为 UUID |
| H5 | 旧 `online_primary` 客户**禁止静默关双写**;仅新客或显式迁移客评估切换 |
| H6 | 未开通用户**零同步文案**(无强制状态条、无「已自动合并」) |
---
## 2. 架构定案
| 项 | 定案 |
|----|------|
| 离线 | **松离线**:开通且白名单表 → 本地正式库落最终 UUID立刻可见 |
| 同步 | **接法① dbsync**(非 HTTP 双写主路径) |
| 通道形态 | **B 本机 sync agent**(平台不直连用户 SQLite |
| 首期方向 | **B→A**;双向另开,默认关 |
| 冲突 | **自动 LWW**;首期投递以 **outbox 单调 version** 为主,偏源(本机推上)时覆盖目标并记审计 |
| 落败追溯 | **LWW 自动覆盖日志****仅平台超级管理员**;公司 top **403**;建议 TTL 90 天 |
| 终端冲突 UI | **不做** |
| 形态 A / CRDT / 对账替代 UUID | **不做** |
```text
[开通 + 白名单表]
业务写 ──► BUUID──► outbox ──► 本机 agent ──► Aingest/upsert
UI仅本机 / 待同步 / 已同步(不影响保存成败)
[未开通]
现网路径不变local_only 或 online_primary
```
---
## 3. 模式三分(宇恒)
| 模式 | 含义 | 默认 |
|------|------|------|
| `local_only` | 只写本地 | 可作默认之一 |
| `online_primary` | 现网 HTTP 双写 / 离线 pending | **已上线客户保留** |
| `local_dbsync` | 松离线 + agent | **仅显式配置** |
- 未配置 / 未识别 → **不得**当成 `local_dbsync`
- **互斥粒度(定案 O6****同一张表**不可同时走 `online_primary` 双写与 `local_dbsync`**同一库**允许部分表白名单走 `local_dbsync`,其余表保持该库原模式写本地(不强制整库切模式)。
---
## 4. 白名单(定案 O1 / O2
| 项 | 定案 |
|----|------|
| 源 of truth | **智建通道配置** |
| 宇恒 | 本地缓存副本;拉取前 / 拉失败 → **视为未开通同步表**(走原写路径) |
| 谁可改 | **公司 top**(现「数据同步」权限);终端只读 |
| 闭包 | 父子外键须同进白名单,否则保存通道失败 |
| 入通道 | 非 `id TEXT` UUID → **拒绝** |
---
## 5. 写路径规则
### 5.1 未进白名单或未开 `local_dbsync`
与现网完全一致:可不传 `id`、允许自增;`online_primary` 语义不变。
### 5.2 已开 `local_dbsync` 且表在白名单
1.`id`**网关自动补 UUID**(规范化小写带连字符),响应带回 `id` / `inserted_id`;并记 auto_id 日志。
2. **始终写 B 正式表**;成功即返回成功。
3. 写 outboxagent 异步推 A。
4. agent 未装 / 停运:本地成功 + UI「待同步」**不失败保存**(定案 R1
5. deleteoutbox `delete` 或墓碑agent **按 outbox id 保序**;失败**禁止换新 UUID**。
**开通判定(写路径)**`mode=local_dbsync` **且** 白名单缓存命中该表;否则走 §5.1。
### 5.3 M1 无 agent 时「开通」含义(定案 O3
允许只落 B + 积压待同步;文案须写明 **「需 agent 才上云」**,不得暗示已上云。
---
## 6. 建表(定案 R2
| 模板 | 主键 |
|------|------|
| 默认 create双端 | **仍自增**(不动) |
| 同步表模板(双端新增) | `id TEXT` UUID + 建议 `updated_at` / 配合 outbox version |
禁止「全局默认改成 UUID」。
---
## 7. version / LWW定案 O5
| 项 | 定案 |
|----|------|
| 主序 | **outbox 单调 id / version** |
| 辅 | 客户端 `updated_at`(不对时**不挡保存** |
| 落败 | 写超管覆盖日志(两边 payload、胜出策略、tenant、表、pk、时间 |
| 弱提示「已自动合并」 | 默认**关**;仅开通用户可配置打开(定案 R4 |
---
## 8. 绑定与迁移
| 项 | 定案 |
|----|------|
| Binding API | **P1不挡 M2**(定案 O4M2 可用现网派生 `online_db_id` |
| 存量 `online_primary``local_dbsync` | **另附迁移手册**(定案 O7加白名单 → 装 agent → 观察 → 关 HTTP**禁止静默迁移**(定案 R5/H5 |
---
## 9. 本机 agent 最小集
- 可选装;未开通可不装。
-`_ajz_sync_outbox`(或等价),保序推 A复用/扩展智建 ingest
- 凭证:最小权限、按 `online_db_id` 隔离、可吊销outbox 敏感列脱敏或加密。
- 心跳 → UI only。
---
## 10. 智建平台交付
| 优先级 | 项 |
|--------|-----|
| **P0** | 入通道 UUID + 闭包校验ingest 认客户端 UUID 幂等;模式/开通说明(默认客户无感) |
| **P0** | M3超管 LWW 审计 API/存储与租户「数据同步」菜单分离TTL |
| **P1** | Binding 登记/查询;限流 reconcile设置页「同步修复」可选、不弹窗强打断 |
| **P1** | 表白名单 B→A / 双向开关(默认 B→A |
| **P2** | 超管按快照回滚单行 |
公司 top配通道、白名单、对账现权
平台超管:覆盖日志。
终端:无冲突台。
---
## 11. 分期与退出标准
| 阶段 | 目标 | 退出标准 |
|------|------|----------|
| **M0** | 同步表模板 + 入通道校验 + 模式三分文档 | 自增表无法误入通道 |
| **M1** | 宇恒旁路:`local_dbsync` + 白名单松离线写 B可无 agent | **未开通用户自动化回归 = 现网**;开通用户离线可见;文案不承诺上云 |
| **M2** | agent 最小 B→A + ingest 幂等 | 同 UUID 上云、不双行agent 停仍可本地保存 |
| **M3** | 超管 LWW 日志 + 限流 reconcile | top 403双入口覆盖可查 |
| **M4** | 扩白名单;**仅**评估新客或显式迁移客是否默认 `local_dbsync` | 旧客默认不变 |
**M4 前禁止全员切默认、禁止一刀切关 HTTP 双写。**
---
## 12. 验收清单(冻结)
### 不影响原有
- [ ] 未开 `local_dbsync`:现网回归通过(自增不传 id、原 `online_primary`)。
- [ ] 非白名单:不强制 UUID入通道拒绝。
- [ ]`local_dbsync` / 清白名单缓存:行为回原模式。
- [ ] 未开通:无强制 agent、无同步文案、保存不因同步失败。
- [ ] 同表未同时走双路径;关同步后无双写放大。
### 开通后(白名单)
- [ ] 离线写 B 可见;联网同 UUID 上 A幂等不双行。
- [ ] agent 停:可保存 + 待同步;恢复后追上。
- [ ] delete/重放保序;不换新 UUID。
- [ ] LWW 落败在超管日志;公司 top 不可见。
- [ ] 闭包不完整无法保存通道。
- [ ] M1 文案不暗示已上云。
---
## 13. 双方承诺(冻结)
### 宇恒
1. M0/M1模式三分 + 白名单进网关;旧分支零行为 diff附回归清单
2. agent 非安装强依赖;同步 UI 仅开通后出现。
3. M2 起对接 ingest不自造第二套写语义。
4. 合入门禁:未开通用户行为 diff ≠ 0 → 不予合入。
### 智建
1. M0入通道 UUID + 闭包校验。
2. M2 前ingest 幂等认客户端 UUID。
3. M3超管审计与租户控制台分离。
4. 发版说明写明:默认客户无感;`local_dbsync` 为增值开通。
5. 书面确认R1R4 全部按上文硬约束执行(已并入本文 §1/§5/§6/§7/§8
---
## 14. 对第二轮「待拍板」的最终答复
| 编号 | 决议 |
|------|------|
| R1 | **确认**:心跳只驱动 UI永不因 agent 拒绝保存 |
| R2 | **确认**:只加同步模板,默认建表仍自增 |
| R3 | **确认**:不一刀切关旧客 HTTP 双写 |
| R4 | **确认**:弱提示默认关,仅开通用户可配 |
| O1 | **通道为源**;宇恒缓存;无名单=未开通 |
| O2 | **公司 top 改白名单**;终端只读 |
| O3 | **M1 可无 agent**;文案「需 agent 才上云」 |
| O4 | **Binding 不挡 M2** |
| O5 | **outbox 单调 version 为主**;不对时不挡保存 |
| O6 | **同表互斥**(非同库整库互斥) |
| O7 | **迁移手册 + 禁止静默迁移** |
---
## 15. 最终口径
**旁路 opt-in 的松离线 + 本机 agent + dbsyncLWW 自动、超管可追溯;默认不影响宇恒现网。按 M0→M4 开工,本文冻结。**
---
## 宇恒追加-实施钉死项(摘录已并入 §16
见过程稿全文;下列 §16 为智建复核后的**采纳与补钉**(不回退 H1H6
---
## 16. 实施钉死项 · 智建复核结论2026-07-31
> 宇恒 N1N6 **总体可采纳**,与最终版无冲突。下列为「写死值 + 防坑补钉」。
### 16.1 对 N1N6 的定案
| # | 宇恒建议 | 智建结论 | 写死值 |
|---|----------|----------|--------|
| N1 | 无 id → 网关自动补 UUID | **采纳** | 自动生成并在响应带回 `id`(可用 `inserted_id` 作别名兼容);**同时打日志「auto_id」** 便于排障 |
| N2 | 开通判定 | **采纳并收紧** | **硬条件**`mode=local_dbsync` **且** 白名单缓存命中该表。租户「同步开通」标志若存在,作为**下发白名单的前提**(无标志则不下发名单),**不**再在写路径上增加第三道易漂移条件 |
| N3 | 按表分支 + 单测 | **采纳** | M1 必测:同库表白名单 / 非白名单分叉 |
| N4 | outbox未开通不建表 | **采纳并优选** | **优先与 permanent 同库** `_ajz_sync_outbox`(触发器简单);未开通库**不创建**该表;首次开通时迁移创建 |
| N5 | UUID 小写带连字符 | **采纳** | 双端入库前 `normalize`(小写 + 标准 8-4-4-4-12 |
| N6 | 旧字段保留、新字段仅开通附加 | **采纳** | 未开通响应形状与现网一致(可多不可少成功语义) |
### 16.2 宇恒反馈有没有问题?
**没有方向性问题。** N1N6 都是在落实「不影响原有」,应进 M0/M1。
需注意的不是反对项,而是**执行时的灰区**(见下补钉)。
### 16.3 仍不完善 · 建议补钉P0/P1
| # | 缺口 | 建议 | 优先级 |
|---|------|------|--------|
| P0-a | 白名单缓存过期 | 定 TTL如 515 min+ 开通/改名单后**主动推送或下次启动强制拉**;过期失败 → 未开通(已定),避免长期用过期「仍在名单」误走同步 | P0 |
| P0-b | 同表从 HTTP 双写迁入白名单 | 迁移手册写死:先停该表 `online_primary` 双写 → 再进白名单 / `local_dbsync`;禁止「边双写边进名单」 | P0并入 O7 手册) |
| P0-c | 未开通回归套件归属 | 宇恒维护基线用例;智建提供「通道拒绝非 UUID」用例M1 退出双方签字 | P0 |
| P1-a | M1 无 agent 积压 | outbox/待同步条数告警阈值(如 >N 提示装 agent**仍不挡保存** | P1 |
| P1-b | agent 凭证续期 | ingest Token 刷新与吊销流程写入 agent 最小集;过期只影响上云,不影响本地写 | P1 |
| P1-c | 多 `local_database_id` | 回归:同一 user 多库绑定不串 `online_db_id`;名单按库缓存 | P1 |
| P2-a | 自动补 UUID 掩盖调用方漏传 | 开通路径 metrics`auto_id_count`;超管/日志可查,不挡业务 | P2 |
### 16.4 不采纳 / 不回潮
- 不因「自动补 UUID」改为默认 `local_dbsync`
- 不恢复「写前 agent 心跳失败则拒绝保存」。
- 不把 LWW 审计开放给公司 top。
### 16.5 复核一句话
**宇恒钉死项应采纳;再补缓存 TTL、迁表白名单与双写互斥顺序、回归归属M0/M1 即可干净开工。**
---
## 17. 变更记录(落地)
| 日期 | 范围 | 说明 |
|------|------|------|
| 2026-07-31 | M0 智建 | 通道保存 / `PrepareChannel`UUID TEXT PK + FK 闭包校验(`platform/internal/dbsync/validate.go`);文档 `docs/同步表约定.md`SyncPage 表白名单提示 |
| 2026-07-31 | M2 智建 | `PushToRemote` + `/api/v1/agent/sync/.../push|whitelist`agent JWT 需「数据同步」);**宇恒本机 agent / 写网关由宇恒侧自行落地,智建不改对方仓库** |
| 2026-07-31 | 约定 | M1宇恒 `local_dbsync` 旁路)与 agent 消费 outbox以本文 + `docs/同步表约定.md` 为接口约定,不在智建仓改宇恒代码 |
| 2026-07-31 | M3 智建 | LWW 覆盖审计 `lww_overrides.json``GET /api/v1/platform/dbsync/lww-overrides`(仅超管);公司 conflicts API 403对账限流默认 `lww_source`SyncPage 去冲突台 |
| 2026-07-31 | M4 智建 | Binding API开通说明/O7 迁移手册;中间件文档 LWW 对齐SyncPage「同步修复」+ opt-in 文案apidef 补全。**全员默认 / 宇恒灰度由对方与运营决策,本仓不改宇恒** |