# 方案稿：松离线 + 接法①（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)**。  
此后改需求只改最终版并备注变更记录；本过程稿不再作为实施口径。
