# 智建修改意见（松离线 + UUID 主键一致性）

> 配套文档：同目录 `database_fastapi修改意见.md`（宇恒客户端写网关 / 双轨写）  
> 目标：离线写入不破坏联网后的**数据一致 + 主键一致**；对账可兜底。  
> 对照能力：本仓库 `docs/数据同步-中间件.md`（dbsync outbox / ingest / reconcile / 冲突队列）。

---

## 一、结论摘要

| 项 | 结论 |
|----|------|
| 对账引擎 | **不必从零重做**；dbsync 已有按主键 upsert、ingest、reconcile、冲突队列 |
| 真正要改 | **表主键约定（UUID）** + **写入口语义（认客户端 id / upsert）** + **与宇恒绑定对齐** |
| 推荐模型 | **松 + UUID**：离线本地先按最终主键落库；联网同主键幂等 upsert 到线上 |
| 禁止 | 同步键依赖两端各自 `INTEGER AUTOINCREMENT` |

宇恒侧（客户端）负责：补 UUID、松离线本地落库 + pending、回放幂等。  
智建侧负责：线上按 UUID upsert、通道/对账/冲突、表结构与发号规则。

---

## 二、与宇恒文档的分工

| 端 | 文档 | 职责 |
|----|------|------|
| 宇恒客户端 | `database_fastapi修改意见.md` | `apply_write`、pending、binding、`online_primary` |
| 智建平台 | **本文** | 线上主库语义、dbsync、表约定、对账 API 对业务可用 |

接法二选一（长期建议 ①）：

| 接法 | 含义 | 智建改动量 |
|------|------|------------|
| **① 走 dbsync** | 宇恒本地 = B；松离线写 B（带 UUID）；通道 B↔A | 表结构 + 配置为主，代码改少 |
| **② HTTP 双写** | 宇恒直打 `/database/permanent/...` | 线上 insert **必须**变 upsert，且强制客户端 `id` |

---

## 三、表结构约定（A / B 两端）

1. 参与同步的业务表主键为 **`id TEXT`（UUID 字符串）**，不用自增整数当同步键。  
2. 建议列：`updated_at` / `version`（与 dbsync 版本跳过、冲突策略对齐）。  
3. 通道 `pk_columns`：非默认 `id` 时显式配置。  
4. 存量自增表：迁移生成 UUID，或暂不纳入同步白名单，仅新表用 UUID。  
5. dbsync 触发器已 `CAST(pk AS TEXT)`，字符串 UUID **原生兼容**；改的是建表习惯，不是中间件内核。

---

## 四、接法 ①：走 dbsync（推荐）

### 已有、可直接用

- 本地变更 → `_ajz_sync_outbox` → worker 推线上  
- `POST .../ingest`：按主键 upsert（示例已是 `"id": "c-001"`）  
- `POST .../reconcile`：主键集合补缺  
- 冲突队列 + LWW / queue 策略  
- 双向通道约每分钟自动对账  

### 建议补强

| 优先级 | 项 | 说明 |
|--------|----|------|
| P0 | 文档/控制台规范 | 写死：同步表白名单必须 UUID 主键；松离线以客户端已带 `id` 为准 |
| P0 | 写入口拒无 id | ingest / 业务写：无 `id` → 400，或服务端生成 UUID **并回写响应**（松离线仍优先客户端生成） |
| P1 | 库绑定对齐 | 宇恒 `online_db_id` 与智建租户库 / 通道 remote 可查询、可登记；避免仅本地 hash 对不上真实库 |
| P1 | 业务可调对账 | 现 sync API 偏管理员；提供租户内用户/agent 代理调用 ingest/reconcile，或网关代调 |
| P2 | 同 PK 内容对账 | 当前 reconcile 偏主键集合；可选 version/hash 不一致 → 冲突队列 |

### 松离线数据流（①）

```text
离线：宇恒本地 B 按最终 UUID 落库（可标未同步）+ 可选 pending
联网：触发器/回放 → 同 PK upsert → A
兜底：reconcile 补主键集合差；冲突进 conflicts
```

---

## 五、接法 ②：HTTP 双写（短期兼容宇恒网关）

宇恒当前：`online_primary` 下同 body 先写线上再写本地，**不会**把线上自增 `inserted_id` 回填本地。

智建线上 permanent 写接口必须：

1. **`insert` = upsert by `id`**（有则更新，无则插入）；请求**必须带 `id`**。  
2. 响应带回 **`id` + `version`（可选）**，便于客户端校验。  
3. `update` / `delete` 统一按同一 `id`；禁止「无 id 靠自增」。  
4. 建表默认主键模板改为 UUID，不再默认 `INTEGER AUTOINCREMENT`。  
5. （可选）按 `online_db_id` + 表暴露 reconcile，或复用 dbsync reconcile。

未改以上语义时，即使客户端用 UUID，线上若仍自增，双轨主键仍会漂。

---

## 六、落地顺序（智建）

1. **规范 + 迁移**：同步表白名单，主键 UUID。  
2. **选定接法**：优先 ①；若短期 ②，先改 permanent 写为 upsert。  
3. **写入口强制带 `id`**（缺则 400 或服务端生成并返回）。  
4. **绑定**：租户/用户 ↔ 线上库 ID 可查，与宇恒 `cache/db_bindings` 对齐。  
5. **对账**：业务可读 API 或网关代调；冲突走现有 conflicts。  
6. **文档**：离线写入 = 最终主键；联网只做同 PK 幂等，不重新发号。

---

## 七、验收标准

- 离线插入一行（客户端 UUID）→ 联网后线上与本地 **同一 `id`**，无重复行。  
- 同一 UUID 重复提交 / 回放 → **幂等**，不多行。  
- 断网窗口结束回放后，主键集合对账无持续缺口（或缺口可一键 reconcile 清掉）。  
- 并发改同 PK → 进冲突队列或按约定 LWW，不静默改主键。  
- 未纳入白名单的自增旧表：不同步或明确排除，避免误配通道。

---

## 八、勿做事项

- 不要用「两端自增 + 事后对账」充当主路径。  
- 不要只靠触发器异步 outbox 充当客户端主写路径却仍让 HTTP insert 自增发号（两套语义打架）。  
- 不要在回放失败时换新主键重试。  
- 不要让管理员专用 sync API 成为唯一对账入口却要求终端用户自愈（需代理或网关）。

---

## 九、相关路径

| 项 | 路径 |
|----|------|
| 本文 | `E:\project\ai建站\智建修改意见.md` |
| 宇恒写网关意见 | `E:\project\ai建站\database_fastapi修改意见.md` |
| dbsync 说明 | `E:\project\ai建站\docs\数据同步-中间件.md` |
| dbsync 实现 | `platform/internal/dbsync/` |
| 宇恒网关实现 | `yuhengyihao_client/yxd/app_fastapi/db_write_gateway.py` 等 |

---

## 十一、模块导入默认能力（方案 A）

> **正式条目已写入联调意见，请以此为准：**  
> `E:\project\ai建站\联调后修改意见-宇恒松离线.md` → **§5.6 Z9（方案 A）**  
> （§0 结论表已挂「Z9 模块导入默认能力」）

摘要：生成/发布时业务 resource **默认**带 `import`（及 `export`），列表 `actions` 对齐；publish/merge 可兜底。绑定权限解决「谁能调」，方案 A 解决「表是否允许导入」。

---

## 十二、一句话（同步 + 导入）

松离线要对齐的是 **UUID 主键 + upsert**；模块灌数要对齐的是 **绑定权限 + 蓝图默认开放 import（方案 A / Z9）**——细则见联调意见 §5.6。
