Ship ticket-exchange and bind/policy for Z13, keep trial binds SMS-free, allow shared company bindings, and align SyncPage plus sync docs. Co-authored-by: Cursor <cursoragent@cursor.com>
41 KiB
联调后修改意见 · 宇恒松离线(形态 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复现)
再修订:2026-08-05(智建落实 Z9e/Z9f、Z8g、Z11a/b;Z12/Z13 仍待)
再修订:2026-08-05(智建落实 Z12a/b/c/e、Z13a–c-1 API;Z12c-2/Z13d–e 待宇恒;绑定码控制台 UI 可后补)
再修订:2026-08-05(宇恒-松离线数据同步使用文档.md、docs/数据同步-开通说明.md对齐 Z12/Z13 生产路径)
再修订:2026-08-05(Z13c-2 同号免验约定;Z12c-2 共享库;绑定码 SyncPage UI;成员管理可代绑手机)
再修订:2026-08-05(宇恒凭票免登录POST /auth/yuheng/ticket-exchange;gateway 放行 bind/ticket)
再修订:2026-08-05(试运行RequireForBind=false;GET /auth/bind/policy;正式+凭票才禁 attested)
来源:宇恒客户端yuhengyihao_client↔ 本机智建127.0.0.1:8180(gateway)/:8888(platform)
依据:松离线-dbsync方案-最终版.md、宇恒-松离线数据同步使用文档.md
0. 当前结论
| 级别 | 状态 | 说明 |
|---|---|---|
| 硬改(阻塞宇恒上线) | 无(松离线主路径) | Z1–Z7、Z9(含存量扫库)、Z10a/b 已落实;主路径可上线 |
| 原建议项 2.1–2.5 | 智建侧基本已落实 | 见 §2 对照表;接口请保持兼容 |
| 用户自助 Z1–Z7 | 已落实 | Z2 宇恒复测通过;Z7 Binding 可读名已合入(需宇恒 ensure 时带 database_name/display_name) |
| Z8 模块↔本机库+智能体库 | 大部分落实 | Z8a–f;Z8g 展示已落实(模块名 + 策略表文案);见 §5.5.2 |
| Z9 模块导入默认能力(方案 A) | 已落实(含存量) | Z9a–f:启动扫库 + 一键开启导入 + 读路径内存兜底;见 §5.6 |
| Z10 空表双侧建齐 | 智建 API 已落实;宇恒待接 | schema/ensure + schema + pull columns;Z10c 待宇恒 sync 周期调用,见 §5.7 |
| Z11 蓝图↔库列一致 | 智建已落实 | 发布自动 ADD COLUMN IF NOT EXISTS;导入 42703 可读提示;见 §5.8 |
| Z12/Z13 开通/绑定 UX | 智建已基本落实;宇恒待接 | 含凭票、试运行关短信(RequireForBind=false)、绑定码 UI;Z13d/e 待宇恒;见 §5.9–5.10 |
| 仍建议关注 | 性能/运维 | SQLite remote 高并发 push 易锁;agent 宜单库串行 drain(属宇恒用法,非平台硬改) |
宇恒对照脚本:
yxd/app_fastapi/smoke_zhijian_e2e.pyyxd/app_fastapi/smoke_offline_concurrent.pyyxd/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 已按此对接):
GET /api/v1/agent/sync/channels/{id}/whitelist- 返回含
tables、pk_columns、conflict_policy
- 返回含
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
- Body:
POST .../pull、POST .../bootstrap- 下行灌库;响应含
columns(空表也返回列,便于本机建表)
- 下行灌库;响应含
POST .../schema、POST .../schema/ensure(Z10)- 拉线上表结构 / 本机空表结构推到线上(
CREATE IF NOT EXISTS,无需 outbox 行)
- 拉线上表结构 / 本机空表结构推到线上(
- 鉴权:管理员 JWT(含「数据同步」)、智能体 Token,或登录用户 JWT(须本人 Binding +
online_db_id)均可调 agent API - 错误体优先带可读
message;忙/上游失败宜带retryable: true(502/503)
联调账号(写入约定,勿再用错租户 / 勿用超管号)
| 环境 | 用途 | 登录 | 说明 |
|---|---|---|---|
| 本机 / 演示公司 | 租户 1 演示 | 13800000001 / demo123 |
仅本机联调;不要拿去推生产「宇信达」通道 |
| 生产 · 宇信达 | 公司管理 / 同号绑定联调 | 13531041944(密码由开通时设定) |
专用联调号;智建须在「宇信达」成员/管理员中写入此手机;宇恒 YXD_SYNC_LOGIN_PHONE 与 Z13 同号确认均以此号为准 |
| 平台超管(禁止联调) | 控制台「填入超管手机」 | 13531041945 / ljk_admin |
最顶级管理员;seed ensureDevPhone(ljk_admin);禁止用作同步登录 / 绑定联调,会与公司账号冲突 |
智建待办:生产「宇信达」写入成员手机
13531041944(成员管理「绑定」即可;勿绑到超管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 → skipped,A 不增行 | ✅ |
| gateway 连续 push ≥20 无 EOF | ✅(burst 25、stress 串行 60) |
stats pushed_applied / pushed_skipped |
✅ |
| 并发 push(SQLite) | ⚠️ 需重试;建议客户端串行 |
未在每次脚本覆盖:无「数据同步」权限 → 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) - 选「同步」后可拉起本机 agent;push 带头里的用户 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;废弃默认双轨 | 已落实(含 Z8g 展示) |
| Z9 | P0 | 模块默认 import + 存量扫库 | 见 §5.6 | 已落实 Z9a–f |
| Z10 | P0 | 空表两侧建齐 | 见 §5.7:仅靠 outbox 不会建空表 | 智建 API 已落实;Z10c 待宇恒接 ensure/schema |
| Z11 | P1 | 蓝图增字段迁库 | 见 §5.8 | 智建已落实 ALTER + 导入提示 |
建议验收(智建改后)
- 演示账号登录拿用户 JWT(无单独「数据同步」管理员权)→
GET .../channels/{id}/whitelist→ 2xx。 - 同 JWT →
POST .../push写入本人 Binding 库 → A 出现同 UUID。 - 同 JWT 推他人
online_db_id→ 403。 - 原管理员 / 智能体 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 可读 |
| — | 经典 e2e(B→outbox→push→A 同 UUID) | ✅ PASSED | 首次 push 常 502/503,约 2–4 次重试后成功;二次 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 |
| Z4–Z6 | 白名单 / 下行 / 文案 | ✅ | Z4 冻结②已落实(任意表+自动建表);Z5 pull;Z6 SyncPage |
现象与建议(运维)
- Gateway 偶发 502 EOF / 503 Request Timeout,体带
retryable: true;正确性仍靠 version 幂等(重试后常见skipped=already applied)。 - 推荐:宇恒 agent / 联调脚本对 push 串行 + 按 retryable 重试(已在上述 smoke 加重试与 error→pending 恢复)。
- Z2:2026-08-04 宇恒复测通过——异库返回
403 无权访问该 online_db_id(非本人 Binding)。 - 前端「数据库区 → 同步设置」需本机服务起来后人工点验;本次后端契约已通。
再测摘要(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)已打通。
- Z2(2026-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 确认)
- 模块数据也要能在本机库 B 里管理(宇恒数据库区:建表/增删改查/导入),与普通业务表同一套体验,不是只能在聊天「模块操作」或线上 Postgres 里改。
- 本机改完后,经松离线 push → 该智能体绑定的线上库 A;换机/仅线上可用 pull/bootstrap 灌回本机。
- 哪个智能体的模块 → 进哪个智能体的库(通道/
online_db_id绑定)。
进展:Z8f 本机镜像已通;智建绑库/筛通道已通;剩余主要是 Z8g SyncPage 展示 与空表 Z10c。
目标数据流
宇恒本机库 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「智能体/模块」勿长期「未绑模块」;「查看线上表」列出本机已有业务表(含 模块·…);模块列优先展示名 |
智建已落实(2026-08-05):模块展示名 + slug 副文案;策略表标「参考/整库同步」 |
分工建议
| 方 | 负责 |
|---|---|
| 智建 | Z8a 文案;Z8b 智能体↔通道/库绑定;Z8g SyncPage 模块展示名;Z10a/b API;开通说明 |
| 宇恒 | 本机库管理模块表(Z8f 已通);开通/全量推送时挂通道 agent_id/app_slug;Z10c sync 周期调 schema/ensure |
验收(Z8 完成后)
- ✅ 在宇恒本机库能看到并编辑模块相关表(Z8f)。
- ✅ 本机改模块行 → sync 后智建「查看线上表」出现同表同行(有数据路径;空表见 Z10)。
- ✅ 智能体 X 只落到绑定库;管理员可按智能体筛通道验数(Z8b)。
- ✅ 文档写明:本机有、线上无 = 尚未 push/ensure,不是串库。
- ✅ 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】本机可见 ↔ 线上可见(展示增强 · 2026-08-05)
联调现象(对照两图)
- 宇恒「数据表」已有:
orders、填土高度(6标一工区)、模块·测·Sheet1(及业务表)。- 智建 SyncPage:有
app_slug时解析已发布模块展示名,slug 作副文案;仅缺绑定才「未绑模块」。- 通道表白名单 UI 标为「策略表(参考)」/「整库同步中」,避免误读为只能 sync 名单内表。
| 编号 | 优先级 | 诉求 | 说明 | 负责 |
|---|---|---|---|---|
| Z8g-1 | P1 | SyncPage 模块列可读 | 展示名 + slug 副文案 | 智建已落实 |
| Z8g-2 | P1 | 通道绑定可被宇恒写入 | PUT 可写 agent_id/app_slug |
智建保持契约;宇恒已接 |
| Z8g-3 | P1 | 查看线上表 = 本机业务表子集 | inspect + 默认藏 _ajz_*;缺表文案 |
智建已具备 |
| Z8g-4 | P2 | 白名单列勿误导 | 「策略表(参考)」/「整库同步中」 | 智建已落实 |
验收
- 通道已挂
agent_id=1、app_slug=mismatch_test2→ SyncPage 不再显示「未绑模块」,模块列可见「测」或至少 slug。 - 本机
模块·测·Sheet1全量 push 后,「查看线上表」出现同名表且行数对齐(幂等 upsert)。 - 本机
填土高度(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.1(merge 已改)。
产品结论:采用方案 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(升格) | 存量模块一次性扫库补齐 | 平台启动扫库 + 管理接口;不依赖用户再发布 | 已落实(2026-08-05):启动 RunBackfill…;POST /api/v1/admin/apps/ensure-import;模块管理「一键开启导入」;超管 …/platform/apps/ensure-import |
| Z9f | P1 | 读路径也兜底(双保险) | Import/Export 入口内存补齐后再校验;可读错误文案 | 已落实:ensureImportOpInMemory + 区分蓝图缺 import vs 权限 |
存量复现(必须消灭)
POST /api/v1/apps/coerce_fields/items/import
→ HTTP 400: operation import not allowed
蓝图 ops 实为:list, create, get, update, delete ← 无 import
(Z9d 已合入后新建模块正常;未再发布的旧模块仍坏)
智建交付标准(Z9e 验收)
- 不手工编辑、不触发「编辑模块」的情况下,对租户内全部已发布可写业务 resource 执行扫库后:
hasOp("import")==true。 - 回归:任取 3 个「合入 Z9 之前发布」的旧 slug,直接
POST .../import不再出现operation import not allowed。 - 控制台可选:「数据同步 / 模块管理 → 一键开启全部业务表导入」按钮,调用同一扫库逻辑。
- 文档写明:Z9d≠存量修复;上线 Z9 后必须跑一次 Z9e,否则用户会反复以为「绑定/权限坏了」。
分工
| 方 | 负责 |
|---|---|
| 智建 | Z9e/Z9f 已落实;保持 Z9a–d;错误文案区分「蓝图未开 import」vs「账号无 row.import」 |
| 宇恒 / 智能体 | 导入前尽力自动补齐(已做);表单选对 slug/resource;重启 platform 或点「一键开启导入」后旧模块即可 import |
验收(含存量)
- 新建模块需求不提「导入」→ 发布后
POST .../import不再因operation import not allowed失败。 - 旧模块(Z9 合入前发布、之后未再发布)在跑完 Z9e 后同样不再 400。
- 用户明确写「禁止导入」的表可不含
import(disable_default_import或表级标记)。 - 400 文案可读:指出是蓝图 operations 缺 import,而不是笼统「操作失败」。
5.7 【智建已落实 Z10】空表也要两侧建齐(2026-08-04)
现象
本机有空表(如accounts0 行)时,线上「查看线上表」没有该表;
仅靠 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 行)ensure;bootstrap 前 schema + 本地 CREATE IF NOT EXISTS |
契约摘要
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「表结构同步」。
验收
- 本机空表 ensure 后,智建「查看线上表」可见同名 0 行表。
- 换机 / 仅线上:schema → 本机建空表 → bootstrap 灌行。
- 有数据的表仍走原 push;ensure 不替代 outbox 行同步。
5.8 【已落实 Z11】蓝图增字段须迁 Postgres(2026-08-05)
联调现象
模块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 业务表补列 | 智建已落实:BuildPostgresAlterDDL + publish 合并执行;字段-only merge 可发布 |
| Z11b | P2 | 导入时:Excel 列映射到蓝图有、库无的字段 → 明确提示迁库 | 智建已落实:42703 行错误附加「请重新发布以自动补列」 |
说明:存量表补列一律可空,避免 NOT NULL 迁库失败。已有库需再发布一次(或编辑发布增字段)才会 ALTER;之后新字段随发布自动补。
5.9 【智建已落实 Z12】生产:账号绑定即落点,禁止手填通道 / 手建通道(2026-08-05)
产品约定(生产 · 冻结意向)
- 一个登录账号 / 一个智能体 ↔ 本公司同步落点:绑定后,该账号的本机库只进自己的(或公司共享的)线上库,不会接到别人账号的库。
- 用户不应:手抄
channel_id、也不应先去「数据同步」里点「新建通道」才能用。- 通道对用户隐藏或自动生成:公司开通 / 智能体启用时,平台自动准备本公司默认同步通道(Postgres 线上 A)并写到智能体/账号绑定上;控制台「数据同步」留给运维改 DSN,不作为开通必经 UI。
- 共享库:仅「公司共享数据库」可被多名成员同步到同一
online_db_id;个人库默认隔离。- 方向默认
local_to_remote(松离线 agent push);「双向」不是用户必选项。- SyncPage 列表须展示可复制 通道 ID(运维用;普通开通流程不依赖)。
| 编号 | 优先级 | 诉求 | 说明 | 负责 |
|---|---|---|---|---|
| Z12a | P0 | 换票带回绑定 | POST /api/v1/auth/token(及登录)响应含 channel_id / online_db_id / database_name / sync_bound |
智建已落实 |
| Z12b | P0 | 智能体自查绑定 | GET /api/v1/agents/me:智能体 Bearer 可读自己的通道/线上库/状态 |
智建已落实 |
| Z12c | P0 | 启用即自动绑通道 | 创建/启用智能体 → EnsureSystemDefaultChannel(IsSystemDefault,表白名单可空);写回 channel_id+online_db_id;DSN 见 DBSync.DefaultRemoteDSN(空则 sqlite 联调文件) |
智建已落实 |
| Z12c-1 | P0 | 账号级隔离 | Binding / push 校验:用户 JWT 只能写本人 online_db_id |
智建已有(自助 Binding) |
| Z12c-2 | P1 | 公司共享库 | Binding shared=true:同租户成员可访问该 online_db_id;仅管理员可设 |
智建已落实 |
| Z12d | P1 | SyncPage 展示通道 ID | 运维可见 + copyable | 智建已落实 |
| Z12e | P1 | 唯一默认通道 | 每公司至多 1 条 is_system_default;复用已有默认 |
智建已落实 + 宇恒「仅 1 条则写入 env」 |
| Z12f | P1 | 开通 UX | 启用智能体即可;DSN 用公司级默认 | 智建已落实(控制台引导文案可再收) |
宇恒已做(2026-08-05)
- 当前生产通道
691575c0-…已写入 env(过渡缓存;Z12c 落地后应由换票/唯一通道自动获得)。 resolve_sync_channel_id():env → Binding → 租户仅 1 通道自动选用。- 不把「手建通道 + 手抄 ID」当作产品终态。
验收
- 新公司:只「启用智能体」,不点新建通道,换票已带
channel_id,宇恒可 drain。 - 账号 A 的本机库 push 不会出现在账号 B 的线上库;共享库仅在显式共享时可见。
- 用户全程无需 F12、无需手填
YXD_SYNC_CHANNEL_ID。
5.10 【智建 API 已落实 / 宇恒待接 Z13】绑定流程简化:绑定码 / 手机号(2026-08-05)
端到端体验(生产目标)
- 后台已绑定(或用户用绑定码/手机号完成绑定)→ 宇恒本机库选 「同步」 → 直接可用(自动通道、自动 Binding、agent drain),无第二套配置。
- 绑定流程必须比「登记智能体 + 抄通道 ID + 填 env」简单。
- 生产·宇信达联调账号:
13531041944(公司侧专用;禁止用超管号13531041945);宇恒侧同号对齐此号做「已有账号是否绑定」联调。
推荐交互
公司管理员(智建后台)
→ 生成「绑定码」(一次性/短时有效,绑定到本公司 + 默认同步落点)
→ 发给终端用户 / 贴在开通单
宇恒 · AI建站智能体
→ 启动 / 换票前检测绑定状态
│
├─ 已绑定本公司 → 静默换票;本机库选「同步」即可用
│
└─ 未绑定
├─ 宇恒已绑手机号,且智建侧同号有成员
│ → 【必弹】「检测到手机号 xxx 在「宇信达」已有账号,是否绑定?」
│ ├─ 确认 → 绑定(免短信)
│ └─ 取消 → 改走绑定码 / 换号
├─ 表单:【绑定码】或【手机号】(宇恒未绑手机时也允许直接填手机号)
│ · 填绑定码 → redeem
│ · 填手机号
│ ├─ 【试运行】短信未接入:一律免短信,仅弹「是否绑定」确认
│ ├─ 【正式】宇恒已绑号且输入=已绑号 → 免短信 → lookup → 确认
│ ├─ 【正式】宇恒未绑 / 异号 → 须短信 → lookup → 确认
│ └─ 智建无此成员 → 提示引导用绑定码
└─ 都没有 → 明确提示联系管理员要绑定码
→ 绑定成功后:本机选「同步」→ 直接 drain,零通道配置
试运行约定(当前):尚未接入短信平台 →
SMS.RequireForBind=false(或Provider=off)关闭绑定短信验证;仍须用户确认「是否绑定」。接入短信后改为RequireForBind=true,启用下表正式规则。
同号 / 验证规则(正式目标;试运行短信步骤可跳过)
| 场景 | 行为 |
|---|---|
| 试运行 / 短信未接入 | 不校验短信;填手机号 + 确认即可绑定 |
| 宇恒未绑定手机号 | 仍可直接输入手机号;正式环境须短信 |
| 宇恒已绑手机,输入 = 已绑号 | 免短信;命中智建账号仍弹「是否绑定」 |
| 宇恒已绑手机,输入 ≠ 已绑号 | 正式环境必须短信 |
| 宇恒/输入手机 = 公司内已有成员 | 必须先询问「已有账号,是否绑定」;不得静默绑定 |
| 用户点确认 | 绑定到该已有账号;智能体 host_key 挂到该公司 |
| 用户点取消 | 不绑定;可改输绑定码或其他手机号 |
| 手机号在公司不存在 | 提示无此成员;引导用绑定码 |
| 生产联调样例 | 宇恒号与智建均为 13531041944 → 弹确认;勿用 13531041945 |
| 编号 | 优先级 | 诉求 | 说明 | 负责 |
|---|---|---|---|---|
| Z13a | P0 | 后台生成绑定码 | POST/GET/DELETE /api/v1/admin/bind-codes;SyncPage「绑定码」区可生成/复制/撤销 |
智建已落实(API + 控制台) |
| Z13b | P0 | 绑定码兑换 API | POST /api/v1/auth/bind-code/redeem(host_key + code)→ 激活并绑定落点 |
智建已落实 |
| Z13c | P0 | 手机号探测 API | POST /api/v1/auth/bind/phone-lookup → { exists, tenant_name, masked_name, need_confirm, sms_required_unless_attested },不直接绑定 |
智建已落实 |
| Z13c-1 | P0 | 同号确认绑定 | POST /api/v1/auth/bind/phone-confirm(confirm=true 才绑);写审计 |
智建已落实 + 宇恒弹窗待接 |
| Z13c-2 | P0 | 验证策略 / 凭票 | 试运行:关短信(RequireForBind=false / Provider=off);GET /auth/bind/policy 可探测。正式:异号 sms_code;同号凭票优先,正式且启凭票才禁 attested |
智建开关+凭票+policy 已落实 + 宇恒对接 |
| Z13d | P0 | 未绑定检测与提示 | sync_bound=false:有手机号先 lookup;命中则弹确认;未命中再出绑定码/手机号表单 |
宇恒技能(智建字段已提供) |
| Z13e | P0 | 选同步即用 | 库选「同步」→ 自动 Binding + drain;零手填通道 | 宇恒(依赖 Z12/Z13) |
| Z13f | P1 | 安全 | 试运行关短信;正式后异号强制短信;确认绑定写审计 | 智建开关已落实 |
验收
- 管理员生成绑定码 → 用户填码 → 绑定成功 → 库选「同步」→ 数据进该公司线上库。
- 宇恒与智建同号
13531041944→ 必须弹出「已有账号是否绑定」;确认后绑定,取消则不绑(禁止用超管号13531041945测)。 - 宇恒未绑手机 → 仍可直接输入手机号;试运行免短信,正式后须短信。
- 试运行:
RequireForBind=false时不带sms_code也可 confirm(仍须confirm=true);GET /auth/bind/policy返回trial_mode=true。 - 未绑定用户:先同号探测,再绑定码/手机表单;不出现 DSN/通道 ID。
- 他人绑定码 / 未确认的同号,不能把数据绑进别的公司或别人账号。
- 正式且启凭票:同号走
ticket-exchange,不再依赖明文attested_same_phone。
6. 联系与附件
- 方案:
松离线-dbsync方案-最终版.md(含 2026-08-01 联调建议落地记录) - 宇恒使用说明:
宇恒-松离线数据同步使用文档.md(含 Z10 schema/ensure) - 开通说明:
docs/数据同步-开通说明.md - 本意见如与冻结方案冲突,以冻结方案为准;§5.1–5.10 为产品增量与复测记录,不推翻 H1–H6 默认无感约束。