Files
ai_site/松离线-dbsync方案.md
whm 76cdcd760e feat: ship loose-offline dbsync (validate, agent push, LWW audit)
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>
2026-07-31 17:54:14 +08:00

612 lines
30 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`](./松离线-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 = Bsqlite该用户库文件
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 投递 Aupsert 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. 总评
同意宇恒总评:主线可立,**必须白名单灰度**,不可默认切全库。
智建侧补充:现网 dbsyncoutbox / 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 403TTL建议 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 停:本地仍可保存 +「待同步」;恢复后追上。
- [ ] 双入口同 PKLWW 符合首期策略(偏本机或按 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→AM4 前不谈全员默认 | **接受** |
| 分期 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. 宇恒侧本期承诺(换智建确认 R1R4
1. M0/M1模式三分 + 白名单判断进 `apply_write`**旧分支零行为 diff**(附回归清单)。
2. 不把 agent 做成安装强依赖。
3. 同步 UI 与开关仅在开通后出现。
4. M2 起才对接 ingest协议以智建文档为准客户端不自造第二套写语义。
### 5. 建议智建本期承诺
1. 入通道 UUID + 闭包校验M0
2. ingest 幂等认客户端 UUIDM2 前就绪)。
3. 超管审计 API 与租户控制台分离M3
4. 发版说明写明:**默认客户无感**`local_dbsync` 为增值开通。
### 6. 第二轮协商一句话
**决议版方向已收束;宇恒同意开工。请智建确认 R1R4并拍板 O1O7尤其 O6 同表互斥 vs 同库互斥)后,即可冻结决议修订版进入 M0。**
---
## 最终冻结说明2026-07-31
R1R4、O1O7 已全部拍板并写入 **[`松离线-dbsync方案-最终版.md`](./松离线-dbsync方案-最终版.md)**。
此后改需求只改最终版并备注变更记录;本过程稿不再作为实施口径。