Add UUID/FK channel checks, agent whitelist/push APIs, bindings, super-admin LWW audit with rollback, reconcile rate limits, and sync docs. Default customers stay opt-in; company conflict UI is removed. Co-authored-by: Cursor <cursoragent@cursor.com>
612 lines
30 KiB
Markdown
612 lines
30 KiB
Markdown
# 方案稿:松离线 + 接法①(dbsync)
|
||
|
||
> 状态:**已冻结 → 请以 [`松离线-dbsync方案-最终版.md`](./松离线-dbsync方案-最终版.md) 为准开工**
|
||
> 本文保留为过程稿(正文草稿 + 双方审评 + 协商记录),**口径以最终版为准**。
|
||
> 日期:2026-07-31
|
||
> 配套:`智建修改意见.md`、`docs/数据同步-中间件.md`、宇恒 `db_write_gateway.py`
|
||
|
||
---
|
||
|
||
## 1. 一句话
|
||
|
||
**离线在本地正式库按最终 UUID 落库;联网靠 dbsync 通道把本地 B 与线上 A 同主键幂等对齐。**
|
||
不做 HTTP 双写主路径;不做「两端自增再对账」。
|
||
|
||
---
|
||
|
||
## 2. 角色与库
|
||
|
||
| 端 | 角色 | 说明 |
|
||
|----|------|------|
|
||
| **A** | 智建线上主库 | Postgres(租户/应用库) |
|
||
| **B** | 宇恒本机正式库 | SQLite permanent(按用户绑定的 `local_database_id`) |
|
||
| **绑定** | `(tenant_id, user_id, local_database_id) → online_db_id` | 宇恒本地登记;智建侧可查询/登记同一映射,通道 remote 指向对应 A |
|
||
|
||
通道(管理员配置,或安装时模板下发):
|
||
|
||
```text
|
||
local = B(sqlite,该用户库文件)
|
||
remote = A(该 online_db_id / 租户库 DSN)
|
||
方向 = bidirectional(或先 B→A,稳定后再双向)
|
||
冲突 = LWW(自动)+ 超级管理员可查覆盖日志;非用户 queue
|
||
表白名单 = 仅 UUID 主键业务表
|
||
```
|
||
|
||
```text
|
||
业务写 ──► B(本地正式库,带 UUID)
|
||
│
|
||
├─ 触发器 → _ajz_sync_outbox → worker → A(同 PK upsert)
|
||
│
|
||
└─ A 上变更 ←── outbox 拉回 ── B(双向时)
|
||
对账兜底:reconcile(主键集合差)+ conflicts 队列
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 松离线语义(写路径)
|
||
|
||
| 场景 | 行为 |
|
||
|------|------|
|
||
| **离线写** | 客户端生成 UUID → **直接写 B 正式表** → 成功即对用户可见;同时可标 `sync_status=pending`(可选列) |
|
||
| **联网写** | 同样写 B(带 UUID);触发器进 outbox,近实时推 A |
|
||
| **不做** | 离线只写 pending、正式表空着(那是紧离线,本方案不用) |
|
||
| **不做** | 业务主路径先 HTTP 打智建 permanent 再 mirror(接法②,本方案不用) |
|
||
|
||
Insert 约定:
|
||
|
||
1. 请求/落库行必须带 **`id`(UUID)**,客户端生成。
|
||
2. 禁止依赖 `INTEGER AUTOINCREMENT` 当同步键。
|
||
3. 重复提交同一 `id` → upsert / 忽略,不多行。
|
||
|
||
---
|
||
|
||
## 4. 表结构约定(A/B 同步表白名单)
|
||
|
||
| 项 | 要求 |
|
||
|----|------|
|
||
| 主键 | `id TEXT`(UUID 字符串) |
|
||
| 建议列 | `updated_at`、`version`(配合 LWW / 版本跳过) |
|
||
| 可选列 | `sync_status`(`synced` / `pending` / `conflict`)仅 UI,同步仍靠 outbox |
|
||
| 存量自增表 | **不进白名单**,或先迁移 UUID 再进 |
|
||
| 通道 `pk_columns` | 默认 `id`;复合主键显式配置 |
|
||
|
||
---
|
||
|
||
## 5. 两端职责
|
||
|
||
### 宇恒客户端
|
||
|
||
| 项 | 做什么 |
|
||
|----|--------|
|
||
| UUID | insert 前生成并写入;update/delete 带同一 `id` |
|
||
| 写路径 | 改 `apply_write`:**始终先写本地 B**(松);去掉「离线只 pending 不落库」 |
|
||
| 联网 | 不依赖 HTTP 双写;确保本机 B 上 dbsync 触发器/ agent 可用,或由智建侧连 B 的方式落地(见下「通道落地形态」) |
|
||
| 绑定 | 保留 `BindingStore`;启动时向智建登记/校验 `online_db_id` |
|
||
| UI | 可显示「待同步」;**不展示冲突处理**;以本地正式数据为离线真相 |
|
||
|
||
### 智建平台
|
||
|
||
| 项 | 做什么 |
|
||
|----|--------|
|
||
| dbsync | 通道、outbox、ingest、reconcile、conflicts — **复用现有** |
|
||
| 表规范 | 控制台/文档写死:同步表必须 UUID 主键 |
|
||
| 绑定 API | 提供「登记/查询 online_db_id ↔ 租户库」供宇恒对齐(P1) |
|
||
| 对账入口 | 管理员 sync API 已有;可选网关代调给终端用户「一键对账」(P1) |
|
||
| 线上业务写 | 控制台/网页改 A 时同样走带 UUID 的写;触发器推回 B |
|
||
|
||
---
|
||
|
||
## 6. 通道落地形态:**已选定 B(本机 sync agent)**
|
||
|
||
dbsync worker 默认跑在智建 platform 上时,往往**连不到**用户电脑上的 SQLite B。选定:
|
||
|
||
### 形态 B(已定)
|
||
|
||
```text
|
||
用户电脑:业务写 B → 本机 sync agent(触发器 / outbox)→ 推到线上 A
|
||
← 拉取 A 变更回 B(双向时)
|
||
智建平台:提供线上 A、鉴权、对账/冲突 API、通道元数据;不直连用户 SQLite
|
||
```
|
||
|
||
| 项 | 约定 |
|
||
|----|------|
|
||
| agent 位置 | 宇恒本机(独立进程或嵌入客户端) |
|
||
| 推送目标 | 智建线上库 A(按 `online_db_id` / 租户库) |
|
||
| 平台职责 | A 侧接收(兼容现有 ingest/应用逻辑)、conflicts、reconcile 入口 |
|
||
| 为何不选 A | 用户电脑公网不可达,平台无法直连 B |
|
||
| 为何不选 C | C 仅作过渡;主路径要近实时 outbox,用 B |
|
||
|
||
### 曾对比(归档)
|
||
|
||
| 形态 | 说明 | 结论 |
|
||
|------|------|------|
|
||
| A. 智建连远程 B | 平台 DSN 直连用户库 | 否(公网不可达) |
|
||
| **B. 本机 sync agent** | 本机推/拉 A | **采用** |
|
||
| C. 批量 ingest 过渡 | 联网后打包推 | 仅应急,非主路径 |
|
||
|
||
---
|
||
|
||
## 7. 与当前实现的差异(改造清单)
|
||
|
||
| 现状 | 本方案目标 |
|
||
|------|------------|
|
||
| 宇恒 `online_primary`:离线 pending、不写正式库 | 离线 **写正式库(UUID)** |
|
||
| 宇恒联网:HTTP 先写线上再 mirror | 联网 **写本地 B**,由 dbsync 推 A |
|
||
| 智建 dbsync 偏管理员控制台 | 增加绑定对齐 +(可选)用户侧对账入口 |
|
||
| 业务表可能自增主键 | 同步表白名单强制 UUID |
|
||
|
||
建议改造顺序:
|
||
|
||
1. **规范**:同步表 UUID + 建表模板(智建 + 宇恒)
|
||
2. **宇恒写路径**:松离线落 B + 强制 `id`
|
||
3. **本机 agent**:按形态 B 打通一条演示通道(本机 outbox → A)
|
||
4. **绑定 API**:online_db_id 可查可登记
|
||
5. **对账 + LWW 覆盖日志**:reconcile 沿用;LWW 落败快照仅超级管理员可查
|
||
6. **下线接法②主路径**(HTTP 双写仅作运维/兼容开关)
|
||
|
||
---
|
||
|
||
## 8. 数据流示例
|
||
|
||
### 离线插入
|
||
|
||
```text
|
||
用户点保存
|
||
→ 客户端 uuid = U1
|
||
→ INSERT INTO B.orders (id=U1, ...) ✅ 本地立刻可见
|
||
→ outbox 暂存本地(触发器);无法推 A
|
||
联网后
|
||
→ worker/agent 投递 A:upsert id=U1
|
||
→ A、B 同主键;sync_status=synced(若有)
|
||
```
|
||
|
||
### 联网插入
|
||
|
||
```text
|
||
→ 写 B (id=U2) → outbox → 秒级 upsert A
|
||
```
|
||
|
||
### 冲突
|
||
|
||
```text
|
||
离线改 U1;同时线上也改 U1
|
||
→ LWW 自动留一版,另一版写入「自动覆盖日志」(含两边 payload)
|
||
→ 终端用户无感;仅平台超级管理员可查/导出(可选回滚)
|
||
→ 禁止换新主键重试
|
||
```
|
||
|
||
### 漏数兜底
|
||
|
||
```text
|
||
POST reconcile → 按主键集合补缺 → 不多发号
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 验收标准
|
||
|
||
- [ ] 离线插入(客户端 UUID)→ 本地立刻有行;联网后 A 与 B **同一 `id`**,无第二行。
|
||
- [ ] 同一 UUID 重复写 / 重放 outbox → **幂等**。
|
||
- [ ] 断网窗口结束 + 对账后,白名单表主键集合无持续缺口。
|
||
- [ ] 同 PK 并发 → LWW 自动覆盖,**不改主键**;落败方在超级管理员覆盖日志中可查。
|
||
- [ ] 自增旧表未进白名单,误配会被拒绝或文档明确排除。
|
||
- [ ] 关闭 HTTP 双写主开关后,业务仍仅靠 B + dbsync 对齐。
|
||
|
||
---
|
||
|
||
## 10. 明确不做
|
||
|
||
- 紧离线(只 pending、正式表不落)作为本方案主路径。
|
||
- 接法② HTTP 双写作为主同步。
|
||
- 两端自增 + 事后映射。
|
||
- 回放/投递失败时换新 UUID 重试。
|
||
|
||
---
|
||
|
||
## 11. 评审结论
|
||
|
||
| 项 | 结论 |
|
||
|----|------|
|
||
| 离线模型 | **松离线**(本地正式库落 UUID) |
|
||
| 同步接法 | **① dbsync** |
|
||
| 通道形态 | **B 本机 sync agent** |
|
||
| 冲突默认 | **LWW(自动裁决,终端用户零操作)**;建议按 `updated_at`/`version`,单客户端场景可偏本机(`lww_source`) |
|
||
| 覆盖可追溯 | **LWW 仍自动覆盖,但必须写「自动覆盖日志」**(见下) |
|
||
| 谁可查日志 | **仅平台超级管理员**(非公司 top / 租户管理员) |
|
||
| 兼容开关 | **过渡期保留** `online_primary`(HTTP 双写),灰度后再切默认 |
|
||
|
||
### 自动覆盖日志(相对现网 dbsync 的补强)
|
||
|
||
现网纯 LWW **不会**把落败方写入 `conflicts.json`。本方案要求:
|
||
|
||
| 项 | 约定 |
|
||
|----|------|
|
||
| 何时写 | 每次因版本较旧而跳过(`lww_target`)或因 LWW 覆盖目标旧行(`lww_source`) |
|
||
| 记什么 | `tenant_id`、通道、表、`row_pk`、源/目标 version、**两边 payload 快照**、胜出策略、时间 |
|
||
| 存储 | 独立审计(如 `data/dbsync/lww_audit.json` 或平台审计表),**不要**当成用户待办冲突队列 |
|
||
| 终端用户 | 无入口、无提示、不阻断同步 |
|
||
| 公司 top | **不可见**(与现「数据同步」控制台租户管理员权限分离) |
|
||
| 平台超级管理员 | 可查、可导出;可选「按快照回滚某一行」(P2) |
|
||
|
||
权限口径:
|
||
|
||
```text
|
||
公司 top(租户管理员)→ 配自己公司的通道 / 对账(沿用现权限)
|
||
平台超级管理员 → 看全局 LWW 自动覆盖日志 / 应急回滚
|
||
终端用户 → 不参与冲突处理
|
||
```
|
||
|
||
---
|
||
|
||
## 12. 一句话验收口径
|
||
|
||
**松离线写 B(最终 UUID)+ 本机 agent 走 dbsync;冲突自动 LWW;落败方进超级管理员可查的覆盖日志;主键不漂。**
|
||
|
||
---
|
||
|
||
<!-- =====================================================================
|
||
以下为独立评审意见(追加区),不改写上文方案正文。
|
||
标识:宇恒追加-审评意见 · 2026-07-31
|
||
===================================================================== -->
|
||
|
||
## 宇恒追加-审评意见
|
||
|
||
> **说明**:本节是宇恒侧对上文方案的外部审评意见,**不是**方案正文的修订稿。采纳与否单独决议;未并入 §1–§12。
|
||
> **硬约束(宇恒侧)**:**这边的修改不得影响原有用户操作与现网写库逻辑**——默认行为保持现状;新能力仅 opt-in / 白名单生效。
|
||
|
||
### A. 总评
|
||
|
||
方案主线(松离线写 B + UUID + 本机 agent + dbsync + LWW)与「离线可用且联网后主键不漂」目标匹配,可立。
|
||
但在宇恒落地时必须遵守:
|
||
|
||
| 原则 | 含义 |
|
||
|------|------|
|
||
| **默认不变** | 未开新模式时,等同现网:`local_only` 或现有 `online_primary` 行为与契约不变 |
|
||
| **显式开启** | 仅 `YXD_SYNC_MODE=local_dbsync`(名可再定)+ 同步表白名单 时走松离线/dbsync |
|
||
| **白名单隔离** | 未进白名单的表:仍可自增、仍走原 insert;**禁止**因全库切 UUID/强制 `id` 拖垮旧插件 |
|
||
| **双路径互斥** | 同一库/表不得同时 HTTP 双写与 local_dbsync,避免双写放大 |
|
||
| **可回滚** | 关开关即回到原行为,无需迁数据才能用旧功能 |
|
||
|
||
**因此:正文目标可做,但宇恒改造必须是「旁路增量」,不是改默认写路径。**
|
||
|
||
### B. 同意保留的决策(在不影响原有的前提下)
|
||
|
||
| 项 | 意见 |
|
||
|----|------|
|
||
| 不做接法②作**新**主路径 | 同意;现网 `online_primary` 可作兼容保留,默认可不切走 |
|
||
| 形态 B 本机 agent | 同意;仅对已开通同步的用户/库启用 |
|
||
| 终端零冲突 UI | 同意;且**未开通同步的用户完全无感**(无新状态条、无强制 agent) |
|
||
| 过渡期保留 HTTP 开关 | 同意;**默认勿切** `local_dbsync` |
|
||
|
||
### C. 建议补强(保证「不影响原有」)
|
||
|
||
1. **模式三分,默认旧行为**
|
||
- `local_only`:只写本地(现网兼容默认之一)
|
||
- `online_primary`:现网 HTTP 双写(已上线客户可继续)
|
||
- `local_dbsync`:**新**松离线 + agent(仅显式配置)
|
||
未配置 / 未识别 → **不得**自动升到 `local_dbsync`。
|
||
|
||
2. **强制 UUID / 必带 `id` 仅白名单表**
|
||
网关:白名单表缺 `id` 可补 UUID 或 400;**非白名单表保持原样**(允许自增、允许不传 id)。
|
||
禁止「全局 create_table 改成 UUID」一刀切——可新增「同步表模板」,旧模板不动。
|
||
|
||
3. **`apply_write` 分支,不改旧分支语义**
|
||
- `local_only` / `online_primary`:代码路径与响应字段兼容现网(可多字段,不可少成功语义)
|
||
- `local_dbsync`:才「始终写 B、不依赖离线 pending 空正式表」
|
||
旧调用方继续认 `success`;勿要求全员改认 `pending`。
|
||
|
||
4. **agent 可选装**
|
||
未装 agent / 未开通同步:业务写库与现网一致;**禁止**写前强依赖 agent 心跳导致保存失败。
|
||
「仅本机 / 待同步 / 已同步」仅对白名单且已开通同步的会话展示。
|
||
|
||
5. **外键 / 闭包**
|
||
仅约束「要进同步白名单」的表集;未同步的旧关联表不强制改造。
|
||
|
||
6. **删改幂等、安全、对账**
|
||
仍建议做,但范围限 `local_dbsync` + 白名单;不影响未开通路径。
|
||
|
||
7. **建表模板**
|
||
双端「同步表模板」新增;**默认建表模板保持自增**,避免新用户无感被切 UUID。
|
||
|
||
8. **联网 reconcile**
|
||
仅同步用户;设置页入口可选;默认不弹、不阻断。
|
||
|
||
### D. 对现有用户操作逻辑的影响(按约束重述)
|
||
|
||
| 对象 | 要求结果 |
|
||
|------|----------|
|
||
| 未开 `local_dbsync` 的用户 | **零影响**:保存/查询/自增表/插件与现网一致 |
|
||
| 未进白名单的表 | **零影响**:不强制 UUID、不强制 agent |
|
||
| 已开同步的白名单表 | 预期内变更:离线立刻落 B、带 UUID、可显示待同步 |
|
||
| 聊天 / 选站 / 税务等非 permanent 同步写路径 | **零影响** |
|
||
|
||
若某改造会导致「未开同步也改变保存结果」→ **不予合入**,改回 opt-in。
|
||
|
||
### E. 建议验收追加项(含「不影响原有」)
|
||
|
||
- [ ] 默认配置下:跑现网回归(insert 不传 id 的自增表、offline pending 若仍开 `online_primary`)与改前一致。
|
||
- [ ] 仅打开 `local_dbsync` + 白名单后:松离线 UUID 验收(§9)通过。
|
||
- [ ] agent 停运:未开通用户保存仍成功;开通用户本地可写且待同步,恢复后主键不双行。
|
||
- [ ] 网页与客户端双入口:仅同步表;LWW 符合偏向;超管日志可查。
|
||
- [ ] 非白名单表加入通道:拒绝或警告。
|
||
- [ ] `local_dbsync` 与 `online_primary` 互斥;关 `local_dbsync` 后行为回到原模式。
|
||
|
||
### F. 不建议纳入本方案(避免范围膨胀与误伤)
|
||
|
||
- 用对账替换 UUID 主键策略。
|
||
- 终端用户冲突工作台。
|
||
- 平台直连用户 SQLite(形态 A)。
|
||
- **默认全局改主键 / 默认切 `local_dbsync` / 强制全员装 agent。**
|
||
|
||
### G. 一句话评审口径
|
||
|
||
**方案可立,但宇恒必须旁路增量:默认不影响原有;仅白名单 + 显式 `local_dbsync` 走松离线。先灰度开通,再谈切默认。**
|
||
|
||
---
|
||
|
||
<!-- =====================================================================
|
||
标识:智建追加-审评意见 · 2026-07-31
|
||
角色:方案侧 / 平台侧复核(对照正文 §1–§12 与上文「宇恒追加」)
|
||
===================================================================== -->
|
||
|
||
## 智建追加-审评意见
|
||
|
||
> **说明**:本节是智建/方案侧对正文及「宇恒追加-审评意见」的复核意见,**不是**方案正文修订稿。与宇恒追加并列,采纳单独决议。
|
||
|
||
### A. 总评
|
||
|
||
同意宇恒总评:主线可立,**必须白名单灰度**,不可默认切全库。
|
||
智建侧补充:现网 dbsync(outbox / ingest / reconcile / LWW)够用当引擎;缺口在 **本机 agent 协议面**、**LWW 覆盖审计(超管)**、**绑定 API**,不是再造同步中间件。
|
||
与宇恒追加 **无方向冲突**;下文对 C 条逐项表态,便于开工排序。
|
||
|
||
### B. 同意保留 / 同意宇恒已同意项
|
||
|
||
| 项 | 意见 |
|
||
|----|------|
|
||
| 松离线 + ① + 形态 B + LWW + 超管覆盖日志 | **维持正文定案** |
|
||
| 不做接法②主路径 / 不做形态 A / 终端零冲突 UI | **同意**(与宇恒 B 节一致) |
|
||
| 过渡期保留 `online_primary` | **同意**;切默认前须互斥,禁止双路径同时写 |
|
||
| 白名单灰度 | **同意并升为 P0 开工前提** |
|
||
|
||
### C. 对「宇恒追加 · C 建议补强」的表态
|
||
|
||
| # | 宇恒建议 | 智建意见 | 建议处置 |
|
||
|---|----------|----------|----------|
|
||
| 1 | 外键 / 同步闭包 | **同意** | 写入规范:白名单按闭包进;跨名单外键配置期拒绝 |
|
||
| 2 | agent 未就绪与「立刻可见」 | **同意** | UI 三态(仅本机/待同步/已同步)+ 写前/写后可探测 agent 心跳;属宇恒 P0 |
|
||
| 3 | 双入口吞改 | **同意偏保守** | 双入口表:**先 B→A**,稳定后再双向;LWW 默认可偏本机,但双入口表在超管日志外可加「弱提示已自动合并」(仍不选边) |
|
||
| 4 | delete / 重放顺序 | **同意** | 正文补:墓碑或 outbox `delete`;agent **按 outbox id 保序**;禁止失败换新 UUID |
|
||
| 5 | `YXD_SYNC_MODE=local_dbsync` | **同意** | 三分:`local_only` / `online_primary` / `local_dbsync`;文档与发版说明同步 |
|
||
| 6 | 双端建表模板 | **同意,P0** | 智建 create + 宇恒 create 同时改;入通道校验非 TEXT UUID PK → 拒绝 |
|
||
| 7 | agent 凭证与 outbox 敏感列 | **同意** | 正文加安全节:最小权限、按 `online_db_id` 隔离、可吊销;outbox 敏感列加密或脱敏 at rest |
|
||
| 8 | 联网自动 reconcile | **同意** | 限流自动对账 + 设置页「同步修复」;**不**暴露 conflicts 给终端;公司 top 仍可走现控制台 |
|
||
|
||
### D. 智建侧另补(宇恒未写或平台专属)
|
||
|
||
1. **LWW 审计权限**
|
||
维持:**仅平台超级管理员**;与公司 top「数据同步」菜单分离。实现上独立 API/页,勿挂在租户 sync 控制台下以免误授权。
|
||
|
||
2. **A 侧接收面**
|
||
agent 推送优先复用/扩展现有 ingest(或等价 upsert 接口),保证 **认客户端 UUID、幂等**;避免再开一套与 dbsync 语义不一致的 HTTP 写。
|
||
|
||
3. **时钟与 version**
|
||
LWW 依赖 `updated_at`/`version`:约定客户端单调时钟或服务端授时规则,防止对时错误导致「旧盖新」。建议 version 用 outbox 单调序号为主、时间戳为辅。
|
||
|
||
4. **超管日志保留策略**
|
||
覆盖日志需 TTL/容量上限(如 90 天或按租户配额),避免无限涨;导出权限同超管。
|
||
|
||
### E. 对现有用户操作逻辑的影响(复核)
|
||
|
||
| 对象 | 影响程度 | 说明 |
|
||
|------|----------|------|
|
||
| 终端(白名单表) | **中高** | 同意宇恒:契约带 UUID + 可见性语义变化;用白名单限面 |
|
||
| 终端冲突 | **降低** | 同意;无选边 |
|
||
| 自增插件/脚本 | **高(仅误入白名单时)** | 配置期拒绝可把风险压到「零误配」 |
|
||
| 「保存后线上立刻读到」 | **中** | 同意改为异步;需延迟可观测(agent 状态) |
|
||
| 公司 top | **低~中** | 多「闭包校验 / 单向先」配置纪律;仍无 LWW 审计 |
|
||
| 平台超管 | **新增** | 覆盖日志查询/导出;(P2)按快照回滚 |
|
||
|
||
**结论**:同意宇恒「冲击面用白名单隔离」;智建把住 **入通道校验 + 超管审计隔离 + ingest 幂等** 三条,终端无感冲突可成立。
|
||
|
||
### F. 建议验收追加项(相对 §9 与宇恒 E)
|
||
|
||
- [ ] 采纳宇恒 E 全部四条(agent 停运、双入口 LWW、非白名单拒绝、模式互斥)。
|
||
- [ ] 超管可按 `tenant_id` / 表 / `row_pk` 检索覆盖日志,且公司 top Token **403**。
|
||
- [ ] 白名单闭包:只配父表不配子表时,保存通道失败并给出明确错误。
|
||
- [ ] `local_dbsync` 下业务写不调用 permanent HTTP 双写主路径(开关关闭时无双写流量)。
|
||
|
||
### G. 不建议纳入本方案
|
||
|
||
- 同意宇恒 F:不对账替代 UUID、不做终端冲突台、不做形态 A。
|
||
- 另:**不做字段级自动合并(CRDT)** 作本期范围;需要时另立专项。
|
||
- **不把 LWW 审计对租户管理员开放**(即使只读),避免与「超管」口径混淆。
|
||
|
||
### H. 一句话评审口径
|
||
|
||
**同意宇恒「可立、先白名单再切默认」;正文方向不改。开工顺序建议:双端 UUID 模板与入通道校验 → `local_dbsync` 松离线写 B → agent 最小推送 + 超管 LWW 日志 → 再灰度关 HTTP 双写。**
|
||
|
||
---
|
||
|
||
<!-- =====================================================================
|
||
标识:协商决议版 · 2026-07-31
|
||
合成:正文 §1–§12 + 宇恒追加 + 智建追加 → 可执行默认策略
|
||
===================================================================== -->
|
||
|
||
## 协商决议版(双方意见合成 · 以此为准开工)
|
||
|
||
> **效力**:本节覆盖正文中与「默认切全站 / 写前强依赖 agent」相冲突的表述;技术主线(松离线、①、形态 B、LWW、超管日志)不变。
|
||
> **总原则(采纳宇恒硬约束)**:**未显式开通前,宇恒现网操作与写库逻辑零影响**;新能力仅 `local_dbsync` + 白名单 opt-in。
|
||
|
||
### 1. 定案一览
|
||
|
||
| 维度 | 决议 |
|
||
|------|------|
|
||
| 同步模型 | 松离线写 B(最终 UUID)+ 接法① dbsync |
|
||
| 通道形态 | **B 本机 sync agent**(平台不直连用户 SQLite) |
|
||
| 冲突 | **自动 LWW**(用户零操作) |
|
||
| 落败方可追溯 | **自动覆盖日志**,**仅平台超级管理员**可查(公司 top 不可见) |
|
||
| 宇恒默认模式 | **保持现网**:`local_only` 或已在用的 `online_primary`,**不自动升** `local_dbsync` |
|
||
| 新模式 | 显式 `YXD_SYNC_MODE=local_dbsync` + **表级白名单** 才生效 |
|
||
| 双路径 | `local_dbsync` 与 `online_primary` **互斥**(同库不同时开) |
|
||
| 建表 | **默认模板仍自增**;另增「同步表模板」= UUID TEXT PK;入通道校验非 UUID → 拒绝 |
|
||
| 同步方向(首期) | 白名单表 **先 B→A**;双入口稳定后再开 bidirectional |
|
||
| 范围外 | 不做形态 A、不做终端冲突台、不做 CRDT 字段合并、不对账替代 UUID |
|
||
|
||
### 2. 比原文「更好」的关键修正
|
||
|
||
原文偏「目标态替换默认写路径」;协商后改为 **旁路增量**,避免伤宇恒存量:
|
||
|
||
| 原文风险 | 决议后 |
|
||
|----------|--------|
|
||
| 全库强制 UUID / 改默认 create | 仅同步模板 + 白名单;旧表旧模板不动 |
|
||
| 松离线成为默认写语义 | 仅 `local_dbsync` 分支;旧分支语义冻结 |
|
||
| 写前强依赖 agent 心跳(智建初评 C2) | **作废**:agent 停也不得导致保存失败;仅开通用户显示「待同步」 |
|
||
| 双向一上来 | 首期单向 B→A,降低网页↔客户端互盖 |
|
||
| LWW 无痕 | 超管覆盖日志(两边 payload + 胜出策略 + TTL) |
|
||
|
||
### 3. 模式与写路径(宇恒)
|
||
|
||
```text
|
||
local_only → 只写本地(现网)
|
||
online_primary → HTTP 双写 / 离线 pending(现网已落地,兼容保留)
|
||
local_dbsync → 白名单表:始终写 B(松)+ outbox;非白名单:仍走该模式下的「本地普通写」且不进同步
|
||
未配置 → 不得视为 local_dbsync
|
||
```
|
||
|
||
网关规则:
|
||
|
||
1. **非白名单**:不强制 `id`、允许自增;与现网一致。
|
||
2. **白名单 + local_dbsync**:无 `id` 则客户端补 UUID(或 400);写 B 成功即返回成功;agent 仅异步。
|
||
3. 关 `local_dbsync` → 行为回到原模式(可回滚)。
|
||
|
||
### 4. 智建平台职责(收窄可交付)
|
||
|
||
| P0 | 内容 |
|
||
|----|------|
|
||
| 入通道校验 | UUID TEXT PK + 白名单闭包(父子表一起或拒绝) |
|
||
| ingest/应用面 | 认客户端 UUID,幂等 upsert;供本机 agent 推送 |
|
||
| LWW 超管审计 | 独立存储与 API;公司 top 403;TTL(建议 90 天) |
|
||
| version | outbox 单调序号为主,时间戳为辅 |
|
||
|
||
| P1 | 内容 |
|
||
|----|------|
|
||
| 绑定 API | `online_db_id` 登记/查询,与宇恒 BindingStore 对齐 |
|
||
| 联网限流 reconcile | 仅开通同步的库;设置页「同步修复」可选 |
|
||
| 单向→双向 | 按表白名单开关,默认仍 B→A |
|
||
|
||
| P2 | 内容 |
|
||
|----|------|
|
||
| 超管按快照回滚单行 | |
|
||
| 弱提示「已自动合并」 | 仅开通同步客户端,仍不选边 |
|
||
|
||
### 5. 本机 agent(形态 B)最小集
|
||
|
||
- 嵌入或独立进程;**未开通同步可不装**。
|
||
- 读本地 `_ajz_sync_outbox`(或等价),**按 id 保序**推 A;支持 delete。
|
||
- 凭证:最小权限、按 `online_db_id` 隔离、可吊销;outbox 敏感列脱敏/加密。
|
||
- 心跳仅用于 UI 状态,**不阻断** `apply_write`。
|
||
|
||
### 6. 分期(建议排期)
|
||
|
||
| 阶段 | 目标 | 退出标准 |
|
||
|------|------|----------|
|
||
| **M0** | 规范:同步表模板、入通道校验、模式三分文档 | 误配自增表无法进通道 |
|
||
| **M1** | 宇恒:`local_dbsync` 旁路 + 白名单松离线写 B(仍可无 agent,只落本地) | 未开通用户回归 = 现网;开通用户离线可见 |
|
||
| **M2** | agent 最小推送 B→A + ingest 幂等 | 联网后同 UUID 上云、不双行 |
|
||
| **M3** | 超管 LWW 覆盖日志 + 限流 reconcile | 双入口覆盖可查;公司 top 不可见日志 |
|
||
| **M4** | 灰度扩大白名单;评估是否对**新客**默认 `local_dbsync` | 旧客默认仍不变,除非显式迁移 |
|
||
|
||
**明确:M4 之前不谈「全员切默认」。**
|
||
|
||
### 7. 统一验收(合并 §9 + 双方 E)
|
||
|
||
**不影响原有**
|
||
|
||
- [ ] 默认 / 未开 `local_dbsync`:现网回归通过(含自增不传 id、原 `online_primary` 行为)。
|
||
- [ ] 非白名单表:不强制 UUID;加入通道被拒绝。
|
||
- [ ] 关 `local_dbsync` 后行为回到原模式。
|
||
- [ ] 未开通用户:无强制 agent、无新冲突 UI、保存不因同步失败。
|
||
|
||
**开通后(白名单)**
|
||
|
||
- [ ] 离线写 B 立刻可见;联网同 UUID 上 A,幂等不双行。
|
||
- [ ] agent 停:本地仍可保存 +「待同步」;恢复后追上。
|
||
- [ ] 双入口同 PK:LWW 符合首期策略(偏本机或按 version);落败在超管日志。
|
||
- [ ] 公司 top 查覆盖日志 → 403;超管可检索/导出。
|
||
- [ ] 与 `online_primary` 互斥,无双路径同时写。
|
||
|
||
### 8. 协商一句话
|
||
|
||
**技术上走松离线 + 本机 agent + dbsync + LWW(超管可追溯);产品上必须旁路 opt-in——默认不影响宇恒原有操作,白名单灰度达标前不切默认。**
|
||
|
||
---
|
||
|
||
<!-- =====================================================================
|
||
标识:宇恒追加-继续协商(第二轮)· 2026-07-31
|
||
对照:协商决议版;回应智建追加中仍需对齐处
|
||
===================================================================== -->
|
||
|
||
## 宇恒追加-继续协商(第二轮)
|
||
|
||
> **说明**:在「协商决议版」之上的第二轮协商意见,**不改写** §1–§12 与决议正文;请智建对「待拍板」表态后可并入决议修订。
|
||
> **立场**:决议版 **总原则 + §1/§2/§6/§7「不影响原有」** 已对齐宇恒硬约束 → **宇恒原则上接受决议版开工**。下列为收口与防回潮。
|
||
|
||
### 1. 宇恒对决议版的表态
|
||
|
||
| 决议要点 | 宇恒态度 |
|
||
|----------|----------|
|
||
| 默认不升 `local_dbsync`;旁路 opt-in | **接受(硬条件,不可回退)** |
|
||
| 默认建表仍自增;同步模板另增 | **接受** |
|
||
| 写前不因 agent 停而保存失败 | **接受(硬条件)**;请同步作废智建初评 C2「写前强依赖心跳」表述 |
|
||
| 首期 B→A;M4 前不谈全员默认 | **接受** |
|
||
| 分期 M0→M4 | **接受**;建议 M1 退出标准增加「未开通用户自动化回归套件」 |
|
||
| 超管 LWW 日志 / 公司 top 403 | **接受**(宇恒侧无异议) |
|
||
|
||
**一句话**:可以按决议版排期;宇恒侧合入准则仍是——**未开通用户 diff 行为 = 0**。
|
||
|
||
### 2. 请智建确认(防与初评打架)
|
||
|
||
| # | 议题 | 宇恒主张 | 需要智建回复 |
|
||
|---|------|----------|--------------|
|
||
| R1 | agent 与保存 | 心跳**只驱动 UI**,永不 `apply_write` 失败 | 书面确认作废「写前探测失败则拒绝保存」 |
|
||
| R2 | 双端建表 | 「同时改」= **只加同步模板**,默认模板不动 | 确认智建控制台默认建表仍自增 |
|
||
| R3 | 关 HTTP 双写 | M4 只评估**新客**或**显式迁移客**;旧 `online_primary` 客户保留 | 确认不会「灰度达标后一刀切关双写」 |
|
||
| R4 | 弱提示「已自动合并」 | 仅 `local_dbsync` 开通用户;默认关闭,配置打开 | 避免未开通用户弹任何同步文案 |
|
||
|
||
### 3. 仍待拍板(建议写进决议修订)
|
||
|
||
| # | 缺口 | 建议默认(宇恒提案) | 理由 |
|
||
|---|------|----------------------|------|
|
||
| O1 | **白名单存在哪** | 智建通道配置为源;宇恒本地缓存副本;以通道为准 | 防两端名单漂移;未拉到名单前当未开通 |
|
||
| O2 | **谁有权改白名单** | 公司 top(现「数据同步」);终端用户只读 | 与现权限一致;避免用户误加自增表 |
|
||
| O3 | **M1 无 agent 时「开通同步」含义** | 允许:仅本地松离线 + 待同步积压;产品文案写「需 agent 才上云」 | 方便分期,但不承诺已上云 |
|
||
| O4 | **Binding API 是否挡 M2** | **不挡**:M2 可用现网派生 `online_db_id`;P1 再对齐登记 | 降低串行依赖 |
|
||
| O5 | **version 授时** | 优先 **outbox 单调 id**;客户端 `updated_at` 仅辅助;不对时不挡保存 | 落实「不影响保存」 |
|
||
| O6 | **同库表级互斥** | 决议写「同库互斥」偏严;提案改为:**同表**不可双路径;同库可「部分表 local_dbsync、其余保持原模式写本地」 | 更贴白名单灰度,少逼整库切模式 |
|
||
| O7 | **存量 `online_primary` 迁 `local_dbsync`** | 另附迁移手册:先加白名单→装 agent→双跑观察→关 HTTP;禁止静默迁移 | 保护已上线双写客户 |
|
||
|
||
### 4. 宇恒侧本期承诺(换智建确认 R1–R4)
|
||
|
||
1. M0/M1:模式三分 + 白名单判断进 `apply_write`,**旧分支零行为 diff**(附回归清单)。
|
||
2. 不把 agent 做成安装强依赖。
|
||
3. 同步 UI 与开关仅在开通后出现。
|
||
4. M2 起才对接 ingest;协议以智建文档为准,客户端不自造第二套写语义。
|
||
|
||
### 5. 建议智建本期承诺
|
||
|
||
1. 入通道 UUID + 闭包校验(M0)。
|
||
2. ingest 幂等认客户端 UUID(M2 前就绪)。
|
||
3. 超管审计 API 与租户控制台分离(M3)。
|
||
4. 发版说明写明:**默认客户无感**;`local_dbsync` 为增值开通。
|
||
|
||
### 6. 第二轮协商一句话
|
||
|
||
**决议版方向已收束;宇恒同意开工。请智建确认 R1–R4,并拍板 O1–O7(尤其 O6 同表互斥 vs 同库互斥)后,即可冻结决议修订版进入 M0。**
|
||
|
||
---
|
||
|
||
## 最终冻结说明(2026-07-31)
|
||
|
||
R1–R4、O1–O7 已全部拍板并写入 **[`松离线-dbsync方案-最终版.md`](./松离线-dbsync方案-最终版.md)**。
|
||
此后改需求只改最终版并备注变更记录;本过程稿不再作为实施口径。
|