Files
ai_site/联调后修改意见-宇恒松离线.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

434 lines
31 KiB
Markdown
Raw 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.
# 联调后修改意见 · 宇恒松离线(形态 B
> 初稿2026-08-01
> 修订2026-08-03对照智建已合入改动 + 宇恒压测结果回写)
> 再修订2026-08-03 下午(用户自助 JWT / Binding / 库级策略复测,见 §5.3
> 再修订2026-08-04§5.6 Z9 模块导入默认能力 · 方案 A
> 再修订2026-08-04 晚§5.5 Z8f 本机镜像已通§5.5.2 Z8g SyncPage
> 再修订2026-08-04§5.7 **Z10 空表双侧建齐**§0/§1/§5.4 现状回写)
> 再修订2026-08-05§5.6 **Z9e/Z9f 存量 import 必须扫库**`coerce_fields` 复现)
> 来源:宇恒客户端 `yuhengyihao_client` ↔ 本机智建 `127.0.0.1:8180`gateway/ `:8888`platform
> 依据:`松离线-dbsync方案-最终版.md`、`宇恒-松离线数据同步使用文档.md`
---
## 0. 当前结论
| 级别 | 状态 | 说明 |
|------|------|------|
| **硬改(阻塞宇恒上线)** | **无**(松离线主路径) | Z1Z7、**Z9**、**Z10a/b智建 API** 已落实;主路径可上线 |
| **原建议项 2.12.5** | **智建侧基本已落实** | 见 §2 对照表;接口请保持兼容 |
| **用户自助 Z1Z7** | **已落实** | Z2 宇恒复测通过Z7 Binding 可读名已合入(需宇恒 ensure 时带 `database_name`/`display_name` |
| **Z8 模块↔本机库+智能体库** | **大部分落实** | Z8a/b/c/e 智建;**Z8f 宇恒本机 `模块·名·资源` 已可管+可 sync****Z8d 路径已通****Z8g** 展示增强仍待智建P1非硬阻塞见 §5.5.2 |
| **Z9 模块导入默认能力(方案 A** | **新建已落实;存量未闭环** | Z9ad 发布时兜底;**Z9e/Z9f 必须做**:旧模块未再发布仍会 `operation import not allowed`2026-08-05 再复现),见 §5.6 |
| **Z10 空表双侧建齐** | **智建 API 已落实;宇恒待接** | `schema/ensure` + `schema` + pull `columns`**Z10c 待宇恒** sync 周期调用,见 §5.7 |
| **Z11 蓝图↔库列一致** | **待智建** | 增字段未迁 Postgres → 导入 42703见 §5.8 |
| **仍建议关注** | 性能/运维 | SQLite remote **高并发 push** 易锁agent 宜单库串行 drain属宇恒用法非平台硬改 |
宇恒对照脚本:
- `yxd/app_fastapi/smoke_zhijian_e2e.py`
- `yxd/app_fastapi/smoke_offline_concurrent.py`
- `yxd/app_fastapi/smoke_zhijian_gateway_burst.py`(连续 push / stats
- `yxd/app_fastapi/smoke_stress_sync.py`(高并发 + gateway 压测)
复测摘要改后gateway 串行 60/60、`eof502=0`;同 version → `skipped``pushed_applied` / `pushed_skipped` 有值;并发 push 加重试可压满(锁冲突靠重试消化)。
---
## 1. 已验证可用(请保持兼容)
请**勿破坏**下列契约(宇恒 agent 已按此对接):
1. `GET /api/v1/agent/sync/channels/{id}/whitelist`
- 返回含 `tables``pk_columns``conflict_policy`
2. `POST /api/v1/agent/sync/channels/{id}/push`
- Body`table, op, row_pk, row/rows, version, client_outbox_id`(用户自助另加 `online_db_id`
- `op``insert|update|update_by_id|delete`(平台侧 insert/update 归一为 upsert 可接受)
- 成功:`{ success: true, result: { ok, applied, skipped, conflict, applied_version, message } }`
- **同 `(table,row_pk,version)` 已落地 → `skipped=true` 且 HTTP 2xx**
3. `POST .../pull``POST .../bootstrap`
- 下行灌库;响应含 `columns`(空表也返回列,便于本机建表)
4. `POST .../schema``POST .../schema/ensure`**Z10**
- 拉线上表结构 / 本机空表结构推到线上(`CREATE IF NOT EXISTS`,无需 outbox 行)
5. 鉴权:管理员 JWT含「数据同步」、智能体 Token或**登录用户 JWT**(须本人 Binding + `online_db_id`)均可调 agent API
6. 错误体优先带可读 `message`;忙/上游失败宜带 `retryable: true`502/503
演示账号联调可用:`phone=13800000001` / `demo123`(超管快捷号见控制台;另见 seed `13531041945`)。
---
## 2. 原建议项落实对照2026-08-03
| 编号 | 原建议 | 智建现状 | 是否还要改 |
|------|--------|----------|------------|
| **2.1 P1** | gateway 偶发 502 EOF超时与可重试 JSON | `ProxyTimeoutSec` 已生效sync 路径加长超时502 体含 `retryable`SQLite DSN 规范化 + `busy_timeout`remote IO → 503+retryable见方案 changelog 2026-08-01 | **否(已落实)**。若生产仍偶发 EOF按运维排障不升格为接口变更 |
| **2.2 P2** | 文档写明多 agent / version 幂等 | `docs/数据同步-中间件.md` 已写「允许重复 push / 客户端 drain 锁优化」 | **否** |
| **2.3 P3** | `pushed_applied` / `pushed_skipped` | `ChannelStats` 已有burst 联调可见 | **否**。OpenAPI 已标注 applied/skipped示例可再补一句即可非必须 |
| **2.4 P4** | Windows `file:` DSN 示例 | 开通/同步表约定已补正斜杠示例;平台规范化反斜杠 | **否** |
| **2.5 P5** | 503/502 + retryable | 已按表落实 | **否** |
### 2.6 【新增·知会】高并发 push 与 SQLite remote
**现象**(宇恒 `smoke_stress_sync`):对同一通道 remote本机 SQLite**并行** push 时,大量 `database is locked` / 失败,串行则稳定;加重试后可最终成功,但 p95 延迟明显升高。
**边界**
- 正确性仍靠 **version 幂等**,不要求平台做 push 租约。
- **推荐用法**:宇恒 agent **按库串行 drain**(勿多线程齐推同一 SQLite A
- 若线上 A 为 MySQL/Postgres并发能力通常好于 SQLite文档可一句带过「SQLite remote 不适合高并发多 writer」。
**智建是否必须改****否**。可选更低优remote 打开时统一更长 `busy_timeout`、或文档强调 SQLite 并发限制(已有 busy_timeout 则足够)。
---
## 3. 明确不需要智建改的部分
| 项 | 负责方 | 说明 |
|----|--------|------|
| 本机 drain 互斥锁 / 串行 drain | 宇恒 | 减重复 HTTP 与 SQLite 锁风暴 |
| 写网关 / outbox / UUID 补齐 | 宇恒 | 未开通零感;仅白名单表同步 |
| 「agent 停仍可保存」 | 宇恒 | H3 |
| 终端冲突处理台 | 双方已否决 | 冲突只在平台超管侧 |
| 改宇恒仓库代码 | 禁止由智建代改 | 分工已冻结 |
---
## 4. 联调回归清单(改后复测)
| 项 | 宇恒侧结果 |
|----|------------|
| whitelist 与通道一致 | ✅ |
| push insert → A 同 UUID | ✅ |
| push update / delete | ✅ |
| 同 version → skippedA 不增行 | ✅ |
| gateway 连续 push ≥20 无 EOF | ✅burst 25、stress 串行 60 |
| stats `pushed_applied` / `pushed_skipped` | ✅ |
| 并发 pushSQLite | ⚠️ 需重试;建议客户端串行 |
未在每次脚本覆盖:无「数据同步」权限 → 403旧 version + `lww_source` 全矩阵——以平台单测/文档为准即可。
---
## 5. 宇恒侧说明(知会)
- 测试已收紧mock 对齐 skipped、双 drain 断言 applied==N、update/delete 保序、压测脚本带锁重试。
- **开通仍是可选**`YXD_SYNC_MODE=local_dbsync`;用户自助以 Binding 为准(**Z4**:不按通道表白名单拒收)。
- **空表**sync 周期须调 **Z10** `schema/ensure`(见 §5.7),否则本机空表不会出现在线上。
- 原「若采纳 2.1 则稳定性提升」:**已验证提升**;本意见可归档为「历史建议 + 落实对照」,无需再开一轮同名改造。
---
## 5.1 【新增·2026-08-03】用户自助库级三态宇恒已开做
产品目标:用户登录智能体后,用**自己的全部权限**管理名下数据库/网页;可对每个库选择:
| 产品态 | 含义 | 宇恒引擎映射 |
|--------|------|--------------|
| **仅本地** `local_only` | 只写本机 | `local_only` |
| **仅线上** `online_only` | 以线上为主 | `online_primary` |
| **同步** `sync` | 本机落库 + outbox 上云 | `local_dbsync` |
宇恒已落地(本机):
- `POST/GET /database/sync/mode``GET /database/sync/policies`
- 策略键:`(tenant_id, user_id, local_database_id, database_name)`
- 写网关按命名库策略覆盖全局 `YXD_SYNC_MODE`(默认仍不静默升 sync
- 数据库区卡片「同步设置」可视化三态;鉴权优先**登录用户 JWT**`app.client.access_token`
- 选「同步」后可拉起本机 agentpush 带头里的用户 Bearer
---
## 5.2 【请智建配合改 / 兼容】用户态同步
> 不破坏现有管理员通道 + agent Token 联调路径;以下为**用户自助开通**所需平台能力。
| 编号 | 优先级 | 诉求 | 说明 | 智建现状2026-08-04 |
|------|--------|------|------|------------------------|
| **Z1** | **P0** | **登录用户 JWT 可调 agent sync API** | whitelist / push / batch 允许已登录普通用户(本人资源范围)。 | **已落实** |
| **Z2** | **P0** | **权限范围 = 用户自己的库** | 带 `online_db_id` 时一律校验本人 Binding异库 → 403含有「数据同步」的人类。 | **已落实** |
| **Z3** | **P1** | **Binding 支持用户自助登记** | `POST/GET .../bindings` 认用户 JWT强制本人 `user_id`。 | **已落实** |
| **Z4** | **P1** | **用户选 sync 时白名单策略** | **冻结②**不按通道表白名单拒收Binding 库内任意表可 push可自动建表。 | **已落实** |
| **Z5** | **P2** | **仅线上 / 下行补齐** | A→B / pull / bootstrap。 | **已落实** |
| **Z6** | **P2** | **控制台文案** | Binding 即权限;管理员不负责维护表白名单。 | **已落实** |
| **Z7** | **P1** | **SyncPage / Binding 展示「可读库名 + 映射」** | 通道「线上」列优先可读名;副文案 online_db_id / 落库路径;统计标注为 push 次数Binding 列表「本地库名 ↔ 线上库名」。 | **已落实2026-08-04**Binding 增 `database_name`/`display_name`SyncPage 主标题优先本地 `database_name`,↑ 标注「推送次数 / 非表数」。宇恒 ensure 须带可读名,否则仍会回退到通道名或 driver |
| **Z8** | **P0** | **模块数据:本机库可管理 + 按智能体上云** | 见 §5.5:本机 B 管理模块表push 到智能体绑定的 A废弃默认双轨 | **大部分落实**Z8a/b/c/d/e/f**Z8g 展示增强待智建**P1 |
| **Z10** | **P0** | **空表两侧建齐** | 见 §5.7:仅靠 outbox 不会建空表 | **智建 API 已落实****Z10c 待宇恒**接 ensure/schema |
### 建议验收(智建改后)
1. 演示账号登录拿用户 JWT**无**单独「数据同步」管理员权)→ `GET .../channels/{id}/whitelist` → 2xx。
2. 同 JWT → `POST .../push` 写入本人 Binding 库 → A 出现同 UUID。
3. 同 JWT 推他人 `online_db_id` → 403。
4. 原管理员 / 智能体 Token 路径回归仍通过(不破坏联调结论)。
### 宇恒 ↔ 智建分工(本项)
| 方 | 负责 |
|----|------|
| 宇恒 | 库级三态 UI/API、写路径分流、outbox、用登录态调 push**默认不按通道表白名单过滤****Z10c** 调 schema/ensure |
| 智建 | 用户 JWT 鉴权、Binding 校验、任意表 push**SyncPage/Binding 按可读名展示Z7****Z10a/b** 空表 API |
---
## 5.3 【复测·2026-08-03 下午】智建改后 × 宇恒库级策略
> 环境:`127.0.0.1:8180` + `:8888`;演示账号 `13800000001` / `demo123`
> 脚本:`yuhengyihao_client/yxd/app_fastapi/smoke_zhijian_e2e.py`、`smoke_user_jwt_policy.py`
> 说明:直连智建验收(当时本机 8080 未起push 遇 gateway **502 EOF / 503** 时按 `retryable` 重试后成功。
### 对照表
| 编号 | 项 | 结果 | 备注 |
|------|----|------|------|
| — | 登录拿用户 JWT | ✅ | `token_len=225``/api/v1/auth/me` 可读 |
| — | 经典 e2eB→outbox→push→A 同 UUID | ✅ **PASSED** | 首次 push 常 502/503约 24 次重试后成功;二次 insert 同行数≥2 |
| **Z1** | 用户 JWT 拉 whitelist | ✅ | HTTP 200`tables=['orders']` |
| **Z1** | 用户 JWT 直推本人库 | ✅ | 带 `online_db_id`A 可见同 UUID重试后 200或同 version → `skipped` |
| **Z3** | 用户 JWT 自助 Binding | ✅ | `POST .../admin/sync/bindings` → 200返回 `user_id/local/online/channel_id` |
| — | 库级策略 `sync` → 写网关 `local_dbsync` | ✅ | 全局未强制 `YXD_SYNC_MODE=local_dbsync` 时,命名库策略仍进 outbox |
| — | 登录态 agent drain 上云 | ✅ | drain 重试后 `pushed=1`A 行 title=`policy-sync` |
| **Z2** | 推他人 `online_db_id` → 403 | ✅ **宇恒复测通过2026-08-04** | `403 无权访问该 online_db_id非本人 Binding``smoke_user_jwt_policy.py` PASSED |
| **Z4Z6** | 白名单 / 下行 / 文案 | ✅ | Z4 冻结②已落实(任意表+自动建表Z5 pullZ6 SyncPage |
### 现象与建议(运维)
1. **Gateway 偶发 502 EOF / 503 Request Timeout**,体带 `retryable: true`;正确性仍靠 version 幂等(重试后常见 `skipped=already applied`)。
2. **推荐**:宇恒 agent / 联调脚本对 push **串行 + 按 retryable 重试**(已在上述 smoke 加重试与 error→pending 恢复)。
3. **Z2****2026-08-04 宇恒复测通过**——异库返回 `403 无权访问该 online_db_id非本人 Binding`
4. 前端「数据库区 → 同步设置」需本机服务起来后人工点验;本次后端契约已通。
### 再测摘要2026-08-03 17:53
| 脚本 | 结果 |
|------|------|
| `smoke_zhijian_e2e.py` | ✅ PASSED首推即成功无 502 |
| `smoke_user_jwt_policy.py` | ✅ 主路径 PASSED**当时 Z2 WARN** 异库仍 applied |
### 再测摘要2026-08-04 · 智建 Z2/Z4/Z6 重启后 · 宇恒确认)
| 脚本 | 结果 |
|------|------|
| `smoke_user_jwt_policy.py` | ✅ **PASSED****Z2 OK**(异库 403直推 + 库级 sync drain 上云均通过 |
### 结论
- **用户自助主路径Z1 + Z2 + Z3 + 宇恒库级 sync已打通**。
- **Z22026-08-04**:异库 `online_db_id`**403**(智建已修,宇恒已确认)。
- **Z4 已冻结并落地**push 不按通道表白名单拒收Binding + 用户 JWT 即权限;可自动建表。
### 宇恒澄清2026-08-04 · 对照截图)
| 现象 | 说明 | 责任方 |
|------|------|--------|
| SyncPage `↑37` | **不是表数**,是通道 `pushed_ok`push 成功次数);与线上 `orders` 行数巧合接近 | 智建 Z7 已改列名为「推送次数」并加副文案 |
| 显示 `ai_site_….db` 而非「AI建站智能体API」 | SyncPage 曾用落库 DSN 当主展示应对齐本地库名id/路径作副文案 | **智建 Z7**(主标题优先 Binding.`database_name`);宇恒 ensure 带 `database_name`/`display_name` |
| Binding `local_*``online_*`「名字对不上」 | **id 本就可不同**(映射);烟雾联调造的占位 id。真实库 `6a3df5…` 已补可读名「AI建站智能体API」默认隐藏 smoke Binding | 智建已清烟雾数据 + UI 过滤;宇恒正式 ensure 带可读名 |
| 本地多表 vs 线上曾只有 `orders` | **当时** remote 仅 `orders``accounts` / `填土高度` 尚未 push**不是**映射错库 | 后续 `填土高度` 已 push 对齐;空表仍需 **Z10 ensure**(见 §5.7 |
| 弹窗 4 表「0 条记录 / 0 字段」 | 卡片统计不准;库内仍有数据 | **宇恒前端**(智建 SyncPage 无此卡片);可用数据预览或打开 remote db |
| **要看线上表内容验同步** | SyncPage 原先只显示通道名/推送次数 | **智建已加**:「查看线上表」→ 表名/行数/字段 + 行预览;**可「删表」**(仅当前侧,需确认;不同步 DDL |
| **图1「线上表」≠ 图2 宇恒「数据表」** | 见 §5.4:一边是线上 A一边是本机 B | **不是串库**;未 push / 未 ensure 的表不会出现在线上 |
| **模块数据按智能体进库** | 模块与 dbsync 原无关联 | **§5.5 Z8 大部分已通**;剩余 **Z8g** 展示与 **Z10c** 空表 |
同步链路本身可用;展示层勿把「通道文件名 + 推送次数」当成「本地库名 + 表行数」。
### 5.4 图1 vs 图2表名「对不上」说明2026-08-04 · 现状已回写)
| | 图1 智建「查看线上表」 | 图2 宇恒「数据库管理 · 数据表」 |
|--|------------------------|--------------------------------|
| **看的是哪边** | 通道 **线上 A**(例:`ai_site_1785779755.db` | 本机正式库 **B**「AI建站智能体API」 |
| **联调当时(早)** | 仅 `orders`(约 37 行)+ `_ajz_*` | `accounts``orders``填土高度…` 等 |
| **回写后(已 push** | `orders`1+ **`填土高度(6标一工区)`96**outbox 全 `done` | 同行数对齐;`accounts` 仍 0 行 |
| **仍可能不一致** | 空表(如 `accounts`)未 ensure → 线上无此表 | 本机有空表;须走 **Z10** `schema/ensure`,单靠 outbox 不会建空表 |
| **如何验同步** | 「查看线上表」预览行;或对照 `_ajz_sync_meta` | 宇恒「数据预览」看本机真实行 |
**结论**:表名集合不同 = **本机有、线上尚未 push/ensure**,不是 Binding/通道指错库。有行的表走 drain push**空表必须走 Z10 ensure**。
`_ajz_sync_*` 是中间件系统表(版本/outbox**不是业务模块表**控制台「查看线上表」默认隐藏Z8e
### 5.5 【进展 Z8】模块数据 ↔ 本机库 + 智能体库
> **产品原则2026-08-04 确认)**
> 1. **模块数据也要能在本机库 B 里管理**(宇恒数据库区:建表/增删改查/导入),与普通业务表同一套体验,不是只能在聊天「模块操作」或线上 Postgres 里改。
> 2. 本机改完后,经松离线 **push → 该智能体绑定的线上库 A**;换机/仅线上可用 **pull/bootstrap** 灌回本机。
> 3. **哪个智能体的模块 → 进哪个智能体的库**(通道/`online_db_id` 绑定)。
> **进展**Z8f 本机镜像已通;智建绑库/筛通道已通;剩余主要是 **Z8g SyncPage 展示** 与空表 **Z10c**。
**目标数据流**
```text
宇恒本机库 B含模块表可本地管理
│ sync / agent drain push
智能体绑定的线上库 A与通道 remote 一致)
│ 智能体 / 控制台「查看线上表」核对
(可选)模块页 CRUD 读同一 A或本机 B 为唯一写入口 + 同步
```
| 编号 | 优先级 | 诉求 | 说明 | 现状 |
|------|--------|------|------|------|
| **Z8f** | **P0** | **模块表可在本机库管理** | 模块实体表出现在宇恒「AI建站智能体API」类本机库中数据预览/录入/导入与业务表一致;开通 sync 后进 outbox | **宇恒已通2026-08-04**`local_module_db` 镜像;表名 **`模块·{模块名}·{资源}`**无下划线列含「所属模块」「模块slug」写网关 → outbox → push |
| **Z8a** | **P0** | **统一「一份表」叙事** | 废弃「模块一套库、松离线又一套」双轨并存为默认;默认:**本机 B 为模块数据管理面**A 为同步副本(或同库) | **已补开通说明**`docs/数据同步-开通说明.md` |
| **Z8b** | **P0** | **智能体选库 / 选通道** | 该智能体绑定 `channel_id` + `online_db_id`;宇恒可选「同步到哪个智能体库」 | **智建已落实**:用户管理可绑通道/线上库/落库名SyncPage 按智能体筛通道。宇恒开通 sync 时会 `PUT` 通道 `agent_id`/`app_slug`(读 `YXD_SYNC_*` |
| **Z8c** | **P1** | **线上与本机对齐** | 智能体读模块数据时优先本机 B离线或已同步的 A发布蓝图在本机建表UUID PK再 sync避免只在平台 schema 建一份 | **部分**:智能体新建发布若设 `database_name``database_per_app` 落该库;本机建表靠宇恒 Z8f |
| **Z8d** | **P1** | **本地上传 / 编辑 → 上云** | 本机模块表变更经 Binding + push 进该智能体 A「查看线上表」可核对 | **路径已通**:本机改 `模块·…` 表 → drain → 「查看线上表」应见同表;若仍缺行 = 未 push / 未 drain |
| **Z8e** | **P2** | **查看线上表默认藏 `_ajz_*`** | 减少「表名不对」误解 | **智建已做默认隐藏**2026-08-04 |
| **Z8g** | **P1** | **本机可见 = 线上可见(展示闭环)** | SyncPage「智能体/模块」勿长期「未绑模块」;「查看线上表」列出本机已有业务表(含 `模块·…`);模块列优先展示名 | **待智建**,见 §5.5.2 |
**分工建议**
| 方 | 负责 |
|----|------|
| 智建 | Z8a 文案Z8b 智能体↔通道/库绑定;**Z8g SyncPage 模块展示名**Z10a/b API开通说明 |
| 宇恒 | **本机库管理模块表Z8f 已通)**;开通/全量推送时挂通道 `agent_id`/`app_slug`**Z10c** sync 周期调 schema/ensure |
**验收Z8 完成后)**
1. ✅ 在宇恒本机库能看到并编辑模块相关表Z8f
2. ✅ 本机改模块行 → sync 后智建「查看线上表」出现同表同行(有数据路径;空表见 Z10
3. ✅ 智能体 X 只落到绑定库管理员可按智能体筛通道验数Z8b
4. ✅ 文档写明:本机有、线上无 = 尚未 push/ensure不是串库。
5. ⬜ SyncPage「智能体/模块」显示智能体名 + 模块**展示名**Z8g目前多显示 slug /「未绑模块」)。
### 5.5.1 【联调补丁·2026-08-04】编辑发布撞 page id
**现象**:编辑模块 `mode=add_pages` 时报
`page id already exists: record_list`(模型复用了已有页 id实为要改 API `import`)。
**智建已改**`platform/internal/blueprint/merge.go` — 同 page id **合并 actions**(不再 400同 path resource **并集 operations**;仅有 API/按钮更新也视为可发布(`UpdatedResources`/`UpdatedPages`)。需**重启 platform** 后生效。
**与 Z9**Z9 默认带 import 后,此类「仅为开导入而编辑」会减少;本补丁仍保留给「改已有页能力」用。
### 5.5.2 【待智建 Z8g】本机可见 ↔ 线上可见(展示增强 · 非硬阻塞)
> **联调现象(对照两图)**
> - 宇恒「数据表」已有:`orders`、`填土高度(6标一工区)`、**`模块·测·Sheet1`**(及业务表)。
> - 智建 SyncPage 通道行若未写 `agent_id`/`app_slug` 会显示 **「未绑模块」**;有 slug 时目前多直接显示 slug未解析模块**展示名**。
> - 期望:本机库卡片能看见的业务表,同步后「查看线上表」也应能看见(行数允许短暂滞后;**空表走 Z10**)。
> - **有数据表对齐**靠 push**空表对齐**靠 Z10不单靠 Z8g。
| 编号 | 优先级 | 诉求 | 说明 | 负责 |
|------|--------|------|------|------|
| **Z8g-1** | **P1** | **SyncPage 模块列可读** | 「智能体/模块」:有 `app_slug` 时解析已发布模块**展示名**如「测」slug 作副文案;仅缺绑定才显示「未绑模块」。`agent_id` 有值但 agents 列表未加载时仍显示 `#id`,勿整列空白 | **智建** `web/src/SyncPage.tsx` |
| **Z8g-2** | **P1** | **通道绑定可被宇恒写入** | 保持 `PUT /api/v1/admin/sync/channels/{id}` 可写 `agent_id``app_slug`;列表 API 原样返回。宇恒已在 sync/mode、full-push 调 `ensure_channel_module_link`(环境 `YXD_SYNC_AGENT_ID` / `YXD_SYNC_APP_SLUG` / `YXD_SYNC_MODULE_NAME` | **智建保持契约**;宇恒已接 |
| **Z8g-3** | **P1** | **查看线上表 = 本机业务表子集** | inspect 列出 remote 全部业务表(含中文/含 `模块·`);继续默认藏 `_ajz_*`。缺表文案明确:「本机有、这里没有 = 尚未 push」 | **智建**(展示已基本具备;验收与文案强化) |
| **Z8g-4** | **P2** | **白名单列勿误导** | Z4 已允许任意表 push若通道 `tables` 仍只配 `["orders"]`UI 勿暗示「只能同步 orders」。可标「策略表参考」或「整库同步中」 | **智建** SyncPage/配置抽屉 |
**验收**
1. 通道已挂 `agent_id=1``app_slug=mismatch_test2` → SyncPage 不再显示「未绑模块」,模块列可见「测」或至少 slug。
2. 本机 `模块·测·Sheet1` 全量 push 后,「查看线上表」出现同名表且行数对齐(幂等 upsert
3. 本机 `填土高度(6标一工区)` / `orders` 与线上一致。
---
### 5.6 【已落实 Z9】模块导入默认能力方案 A · 2026-08-04
> **联调现象**
> 宇恒绑定账号已具备 `row.import`(导入数据),智能体调用
> `POST /api/v1/apps/{slug}/{resource}/import` 仍返回
> `HTTP 400: operation import not allowed`。
> 根因(已修):平台在角色权限之外,还校验蓝图 `apis.resources[].operations` 是否含 `import`
> 旧生成默认只有 `list/get/create/update/delete`。现已默认带 import且 publish 兜底补全Z9
> 编辑补丁时模型仍可能只改部分表;**再发布**或依赖 Z9d/`EnsureDefaultImportExport` 即可。
> 另:同 page id 编辑曾 400见 §5.5.1merge 已改)。
**产品结论:采用方案 A**
绑定宇恒账号解决「谁可以调导入接口」;方案 A 解决「业务表是否声明允许导入」,避免「有权限仍 400」。
不采用「仅有 `row.import` 就跳过 `hasOp`」(方案 B作主路径以免列表页无导入入口、前后端能力不一致。
| 编号 | 优先级 | 诉求 | 说明 | 现状 |
|------|--------|------|------|------|
| **Z9a** | **P0** | **生成默认带 import** | 业务 `apis.resources[].operations` 默认含 `list,get,create,update,delete,**import**,**export**`(除非需求明确禁止) | **已落实**`generation_rules` + `prompt-contract.md` |
| **Z9b** | **P0** | **列表 actions 对齐** | list 页 `actions` 默认含 **`import`**(及可选 `export` | **已落实**:发布兜底补 list actions |
| **Z9c** | **P0** | **改 prompt / 文档** | `prompt-contract.md`:由「提到导入再加」改为「默认加;明确不要再去掉」 | **已落实** |
| **Z9d** | **P0** | **publish/merge 兜底** | 发布或编辑合并时:业务 resource 缺 `import` 则自动补上(可配置,默认开) | **已落实**`EnsureDefaultImportExport`;但**只在 publish 路径触发** |
| **Z9e** | **P0升格** | **存量模块一次性扫库补齐** | **禁止**依赖用户「碰巧再发布一次」。平台启动或管理接口:遍历租户已发布蓝图,对可写业务 resource 缺 `import/export` 的执行与 Z9d 相同补齐并落库(可干跑+确认)。目标:**任意旧模块** `POST .../import` 不再因缺 ops 400 | **待智建**2026-08-05 `coerce_fields/items` 仍只有 list/create/… 无 import联调第三次踩同一坑 |
| **Z9f** | **P1** | **读路径也兜底(双保险)** | `GetBlueprint` / `ResolveResource` / `ImportRows` 入口:若可写 resource 缺 import**内存补齐后再校验**或返回明确引导「请点修复导入能力」。避免「UI 能点导入、API 仍 400」 | **待智建**;宇恒侧已在导入前自动 replace 补 ops不能替代平台扫存量 |
**存量复现(必须消灭)**
```text
POST /api/v1/apps/coerce_fields/items/import
→ HTTP 400: operation import not allowed
蓝图 ops 实为list, create, get, update, delete ← 无 import
Z9d 已合入后新建模块正常;未再发布的旧模块仍坏)
```
**智建交付标准Z9e 验收)**
1. 不手工编辑、不触发「编辑模块」的情况下,对租户内**全部**已发布可写业务 resource 执行扫库后:`hasOp("import")==true`
2. 回归:任取 3 个「合入 Z9 之前发布」的旧 slug直接 `POST .../import` **不再**出现 `operation import not allowed`
3. 控制台可选:「数据同步 / 模块管理 → 一键开启全部业务表导入」按钮,调用同一扫库逻辑。
4. 文档写明Z9d≠存量修复上线 Z9 后**必须跑一次 Z9e**,否则用户会反复以为「绑定/权限坏了」。
**分工**
| 方 | 负责 |
|----|------|
| **智建** | **Z9e 扫存量P0**、Z9f 读路径兜底;保持 Z9ad错误文案区分「蓝图未开 import」vs「账号无 row.import」 |
| 宇恒 / 智能体 | 导入前尽力自动补齐(已做);表单选对 slug/resource**不能**代替平台扫全租户 |
**验收(含存量)**
1. 新建模块需求**不提**「导入」→ 发布后 `POST .../import` 不再因 `operation import not allowed` 失败。
2. **旧模块**Z9 合入前发布、之后未再发布)在跑完 Z9e 后同样不再 400。
3. 用户明确写「禁止导入」的表可不含 `import``disable_default_import` 或表级标记)。
4. 400 文案可读:指出是蓝图 operations 缺 import而不是笼统「操作失败」。
### 5.7 【智建已落实 Z10】空表也要两侧建齐2026-08-04
> **现象**
> 本机有空表(如 `accounts` 0 行)时,线上「查看线上表」没有该表;
> 仅靠 outbox 行 push 时,**没有行就没有建表事件**。同理,线上空表下行时本机若缺表,旧 pull 只给空 `items`、不给列,本机无法建表。
| 编号 | 优先级 | 诉求 | 现状 |
|------|--------|------|------|
| **Z10a** | **P0** | 本机空表 → 线上建空表 | **智建已落实** `POST /api/v1/agent/sync/channels/{id}/schema/ensure` |
| **Z10b** | **P0** | 线上空表 → 本机建空表 | **智建已落实** `POST .../schema``pull`/`bootstrap` 响应带 `columns` |
| **Z10c** | **P0** | 宇恒 sync 周期调用上述 API | **待宇恒**drain **前**对本机业务表(含 0 行ensurebootstrap **前** schema + 本地 `CREATE IF NOT EXISTS` |
**契约摘要**
```http
POST .../schema/ensure
{ "online_db_id": "...", "tables": [{ "table": "accounts", "pk_column": "id", "columns": ["id","username",...] }] }
POST .../schema
{ "online_db_id": "..." }
result.tables[].name / columns / row_count
```
列一律按 TEXT + 指定 PK 建空表;已存在幂等跳过。详例见 `宇恒-松离线数据同步使用文档.md`「表结构同步」。
**验收**
1. 本机空表 ensure 后,智建「查看线上表」可见同名 **0 行**表。
2. 换机 / 仅线上schema → 本机建空表 → bootstrap 灌行。
3. 有数据的表仍走原 pushensure **不替代** outbox 行同步。
### 5.8 【待智建 Z11】蓝图增字段须迁 Postgres2026-08-04
> **联调现象**
> 模块 `record` 蓝图后来带了 `title/content/status`,但 Postgres 表仍只有 `col_0/col_1/col_2`。
> 导入 Excel 若含「标题/内容/状态」→ `pq: 关系 "record" 的 "title" 字段不存在 (42703)`,整批 skipped。
> 反过来蓝图把这些标成非空、Excel 又没有时,会先报 `missing required field: title`。
| 编号 | 优先级 | 诉求 | 负责 |
|------|--------|------|------|
| **Z11a** | **P1** | 编辑/发布增字段后 **自动 ALTER** 业务表补列(或发布失败并提示未迁库) | **智建** |
| **Z11b** | **P2** | 导入时Excel 列映射到蓝图有、库无的字段 → 明确报「请先迁库」,勿只堆 28 条 pq 错误 | **智建** |
**宇恒临时绕过**:导入 Excel 只保留库里已有列对应表头(填土:`序号/断面里程/填土高度` → col_0/1/2
---
## 6. 联系与附件
- 方案:`松离线-dbsync方案-最终版.md`(含 2026-08-01 联调建议落地记录)
- 宇恒使用说明:`宇恒-松离线数据同步使用文档.md`(含 Z10 schema/ensure
- 开通说明:`docs/数据同步-开通说明.md`
- 本意见如与冻结方案冲突,**以冻结方案为准**§5.15.7 为产品增量与复测记录,不推翻 H1H6 默认无感约束。