61 KiB
联调后修改意见 · 宇恒松离线(形态 B)
初稿:2026-08-01 · 修订至 2026-08-06 18:10(Z22 绑定表单失败不再冒泡 Expecting value)
焦点:§0.2(按负责方);改代码前须先写入本意见(见 §0.0)
来源:宇恒yuhengyihao_client↔ 智建(生产aisite.yuxindazhineng.com)
依据:松离线-dbsync方案-最终版.md、宇恒-松离线数据同步使用文档.md
0. 当前结论
| 级别 | 状态 | 说明 |
|---|---|---|
| 硬改(阻塞接口) | 无 | 契约不变;剩生产 pull + 配号/Secret |
| 生产栈 | 已恢复 | SMS.Provider: "off" 须引号;health platform/ai=true |
| Z1–Z9 / Z11–Z13 | 已落实 | 绑定/凭票/模块导入等;见 §5 |
| Z10 + Z10d | 代码齐;生产 pull | 空表 ensure;已存在表 ADD COLUMN(§5.7) |
| Z12 默认同步 | 已落实(含 Z12h) | 启用即通道;误删 → Ensure+重挂(§5.9) |
| 数据恢复 | 智建已落实 | SyncPage「数据恢复」:同步成功后打线上快照,可恢复上次/上一代(§5.13) |
| Z14 + Z14c | 代码齐;生产 pull | fingerprint;智能体挂载 online_db_id(§5.11) |
| Z14d 双向补齐 | 宇恒已接 | 误删任一侧 → 心跳/选同步按主键并集 pull↔push(§5.11) |
| Z15 建站配置/绑定优先 | 宇恒已改完;智建无需 | 新机缺 config.json 自动生成;无权限先绑定再鉴权(§5.12) |
| Z16 禁硬编码联调手机 | 宇恒已改完;智建无需 | 代码/默认值不得内置 13531041944;凭票手机仅来自 env 或用户绑定(§5.14) |
| Z17 鉴权失败可换绑 | 宇恒已改完;智建无需 | 鉴权/换票失败门闸提供「绑定或更换绑定」:清本机同步落点后重走绑定表单(§5.15) |
| Z18 模块操作换绑按钮 | 宇恒已改完;智建无需 | 「模块操作」表单底部独立按钮「换绑」(非下拉选项);点后强制清落点并弹绑定表单(§5.16) |
| Z19 禁默认登记先绑定 | 宇恒已改完;智建无需 | 无绑定不得静默 agent/register;登记失败引导绑定;修 sendFrom 漏 components(§5.17) |
| Z20 失败即弹绑表单 | 宇恒已改完;智建无需 | 启动失败不只发文字,直接 force_rebind 弹绑定/换绑表单(§5.18) |
| Z21 绑定接口空体安全解析 | 宇恒已改完;智建无需 | sync_bind_flow 空响应/Expecting value 不再打断 prepare;配合 Z19/Z20(§5.19) |
| Z22 绑定表单失败可重试 | 宇恒已改完;智建无需 | prepare 只做非交互检测;ask_fields/Expecting value 不冒泡成「绑定引导失败」;必弹绑定表单(§5.20) |
| 仍关注 | 用法 | 通道断了靠智建自愈;表数据靠宇恒双向指纹 / 数据恢复;不是「再点启动」 |
0.0 修改流程(冻结)
- 先写本意见(§0.2 分 A/B″,§5 写清诉求/分工/验收)→ 双方对齐。
- 再改代码:宇恒只改
yuhengyihao_client;智建只改ai建站;互不代改。 - 接口/产品有变 → 回写 §1 契约与验收;禁止口头改完再补意见。
0.1 绑定产品冻结(Z13 · 试运行)
| 约定 | 内容 |
|---|---|
| 联调号 | 13531041944(宇信达);禁止超管 13531041945 |
| 换票 | 凭票:YXD_YUHENG_TICKET_SECRET + YXD_HOST_KEY + 手机;勿手写登录密码 |
| 短信 | 试运行关:RequireForBind=false;Provider: "off"(YAML 必须加引号) |
| 同号 | 命中成员 → 必须弹窗;confirm=true 才绑 |
| 通道 | 默认同步通道 默认运行中;开通不依赖用户先点「启动」 |
0.2 待改清单(按负责方)
分工冻结:宇恒只改
yuhengyihao_client;智建只改ai建站;互不代改对方仓。
智建轨迹:d195aa4凭票 →4222667SMS 引号 →cb76824默认启动 →4fae7aeZ10d+fingerprint →bb59bb5Z14c。生产:bash ./restart.sh --pull。
A. 宇恒 · 已完成
| 编号 | 内容 |
|---|---|
| Z10c | ensure_before_drain(选同步 force + drain 前 TTL) |
| Z13b–e | 绑定码 / 同号确认 / policy / 凭票 / 选「同步」即 Binding+drain |
| Z14a | 表级指纹慢心跳(原:不一致 → ensure + full-push) |
| Z14d | 双向并集补齐(2026-08-06):不一致 → 先 pull 灌本机(不进 outbox)再 ensure+full-push;选「同步」同样先 pull 再 push;默认不做 prune(YXD_SYNC_PRUNE_EXTRAS=1 才清线上多余)。覆盖:线上误删 / 本地误删 / 缺表 |
| Z15 | 建站技能 config 自生成 + 无权限先绑定(2026-08-06 · 宇恒已改完):ai-site-agent-api 缺 config.json 自动写默认模板;绑定优先;门闸含「绑定账号或手机号」。改动:config_loader.py / host_bootstrap.py / forms.py / forms_ui.py / forms_ui.json / reference.md。智建无需改 |
| Z16 | 去掉凭票硬编码手机号(2026-08-06 · 宇恒已改完):_sync_phone() 不再回落 13531041944;仅读 YXD_SYNC_LOGIN_PHONE / YXD_ZHIJIAN_PHONE;绑定成功则回写该 env。联调号仍可作人工验收样例(§0.1),但禁止写进产品默认值。智建无需改 |
| Z17 | 鉴权失败门闸:绑定或更换绑定(2026-08-06 · 宇恒已改完):pending_auth 选项含「绑定或更换绑定」;点选后 clear_local_sync_bind + force_rebind 再弹绑定表单,避免因 agents/me 已绑而跳过。智建无需改 |
| Z18 | 模块操作页底部「换绑」按钮(2026-08-06 · 宇恒已改完):sendFrom 增加 showRebind/rebindText;ask_module_action 点按钮 → FormRebindInterrupt → force_rebind。鉴权门闸同步改为按钮而非下拉选项。智建无需改 |
| Z19 | 禁静默默认登记 + 修表单 components(2026-08-06 · 宇恒已改完):未绑定先弹绑定;build_sendfrom_form 补回 components。智建无需改 |
| Z20 | 启动失败直接弹绑定/换绑表单(2026-08-06 · 宇恒已改完)。智建无需改 |
| Z21 | 绑定 HTTP 空体安全 JSON(2026-08-06 · 宇恒已改完):_safe_http_json 替代裸 r.json(),避免 Expecting value 打断启动。智建无需改 |
| Z22 | 绑定表单失败可重试(2026-08-06 · 宇恒已改完):启动检测改为非交互;表单推送/JSON 解析失败不向用户暴露 Expecting value,捕获后重试弹表。智建无需改 |
A′. 宇恒 · 配合注意(非阻塞新开发)
| 优先级 | 内容 |
|---|---|
| P0 | 端到端见 §0.3:绑定 → 库选「同步」→ drain;线上表只反映已 push/ensure/pull |
| P0 | online_db_id:生产 pull Z14c 后,智能体 JWT 用 ticket/agents/me 的 {channel}_uN;apply_bind_success 勿再长期吞掉 _uN |
| P1 | 通道失效(push/agents/me 报通道不存在)→ 重新换票,用返回的新 channel_id 覆盖 env(根因自愈见智建 Z12h) |
| P1 | Binding 带可读名;同库串行 drain;指纹优先 schema/fingerprint,404 回退 row_count |
B. 智建 · 已完成(仓内)
| 项 | 说明 |
|---|---|
| gateway 公开 bind / ticket | 生产 health 已通 |
SMS.Provider: "off" |
YAML 布尔踩坑已修 |
| 默认同步通道默认启动 | 创建即 enabled;重启自动 Start |
| Z14c ACL | 智能体 JWT 放行挂载 online_db_id(agentOwnsOnlineDB) |
| Podman DNS / 脚本可执行位 | compose aliases;restart.sh 100755 |
| Z12h / Z12h-1 / Z12h-2 | HealTenantSyncBind:Ensure 默认通道 + 重挂 Agent/Binding;默认同步可删,删后立即 Ensure+heal;列表/换票/agents/me/ensure Binding 触发;见 §5.9 |
| 数据恢复(§5.13) | 成功同步后线上库快照(latest+previous);SyncPage「数据恢复」;GET …/checkpoint + POST …/restore |
产品一句:账号已绑定 ⇒ 落点信息固定;通道没了平台自动补,终端无需手填 channel_id。
B″. 智建 · 仍待办(开发 · 本仓改)
| 优先级 | 编号 | 项 | 说明 |
|---|---|---|---|
| — | — | 无阻塞开发项 | Z12h、数据恢复已合入;后续仅生产 pull 与运维配号 |
B‴. 智建 · 本次明确不改(Z15)
| 项 | 说明 |
|---|---|
新机缺 config.json |
宇恒技能本地文件问题;平台不负责生成终端 config |
| 「模块操作 / 等待授权」表单 | 宇恒 sendFrom + yxd_skill_ty_host;智建已有 bind/register/token API,契约够用 |
| pending 待管理员启用 | 仍靠控制台赋「生成发布」;不为此新开接口 |
结论:Z15 智建无需开发;仅当后续要把「绑定失败错误码 / pending 文案」标准化进 API 时再开智建项并先改本意见。
B′. 智建 · 仍待办(仅运维)
| 优先级 | 项 | 说明 |
|---|---|---|
| P0 | 生产 pull 本批 | Z10d + fingerprint + Z14c + Z12h + 数据恢复;bash ./restart.sh --pull |
| P0 | 成员手机 | 「宇信达」绑 13531041944;勿超管号 |
| P0 | 通道表白名单 | 勿「填入测试默认」;形态 B 可空 |
| P1 | 凭票 Secret | YuhengTicket.Secret ≡ YXD_YUHENG_TICKET_SECRET |
| P1 | DefaultRemoteDSN |
生产 Postgres;空则 sqlite 联调文件 |
| P2 | 正式短信 | Provider≠"off" 且 RequireForBind=true |
C. 双方已否决
| 项 | 说明 |
|---|---|
| 终端冲突台 | 仅平台超管 |
| 智建代改宇恒仓 / 宇恒代改智建仓 | 分工冻结 |
| 平台 push 租约 | version 幂等 + 宇恒串行 drain |
| 用户手填 channel_id / DSN 为开通终态 | 已否决;见 Z12 / Z12h |
0.3 双方怎么配合(开通 → 看到线上表)
【智建运维】
1. bash ./restart.sh --pull(health platform=true)
2. 成员绑 13531041944;通道勿测试表白名单;配凭票 Secret
3. 启用智能体 → 默认同步通道应「运行中」
4.(可选)生成绑定码
【智建 · Z12h / 数据恢复 已落实】
误删默认同步 / Binding 挂死 → SyncPage / 换票 / agents/me 自动 Ensure+重挂
误删线上数据 → SyncPage「数据恢复」按上次同步快照恢复线上(本机靠下次 pull)
【宇恒终端】
5. 未绑定 → policy → lookup/confirm 或 redeem 或 ticket-exchange
6. 本机库选「同步」→ Binding + pull + ensure + drain/full_push(Z13e / Z14d)
7. 慢心跳指纹(Z14d):本地缺←pull,线上缺→push;缺列依赖生产已 pull 的 Z10d
8. 若仍报通道不存在 → 再换票刷新 channel_id(生产须已 pull Z12h)
【智建验收】
9. 「查看线上表」刷新 → 应有业务表(含曾 0 行空表)
10. 仍空 = 终端尚未 push/ensure/pull,不是再点一次「启动」就能灌表
11. 删默认同步后:换票或打开 SyncPage / agents/me → 通道自动回来
12. 同步成功后误删线上行 →「数据恢复」可选最近一次/上一代快照恢复
| 角色 | 负责 | 不负责 |
|------|------|--------|
| **智建** | 落点/通道自愈/鉴权/ensure/fingerprint API;控制台看线上表 | 不替宇恒灌本机历史行;不改宇恒仓 |
| **宇恒** | 绑定 UX、选同步、outbox、drain、**双向**指纹修复、full-push/pull | 不以手填 DSN/通道为产品终态;不改智建仓 |
| **共同** | 联调号 `13531041944`;Secret 对齐;试运行免短信仍须确认 | 不用超管号 |
宇恒对照脚本:`smoke_zhijian_e2e.py` / `smoke_offline_concurrent.py` / `smoke_zhijian_gateway_burst.py` / `smoke_stress_sync.py`。
复测摘要:gateway 串行 60/60、`eof502=0`;同 version → `skipped`;并发 SQLite 须重试 / 客户端串行。
---
## 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`;**Z10d** 部署后已存在表亦 `ADD COLUMN`)
5. `POST .../schema/fingerprint`(**Z14** · 建议部署)
- Body:`{ "online_db_id": "..." }`
- 返回各表 `name/row_count/content_hash/pk_column`;与宇恒本机指纹算法对齐(见 §5.11)
- **未部署时**宇恒回退只用 `schema.row_count` 对账(可发现行数差,不能发现「同 count 不同内容」)
6. 鉴权:管理员 JWT(含「数据同步」)、智能体 Token,或**登录用户 JWT**(须本人 Binding + `online_db_id`)均可调 agent API
7. 错误体优先带可读 `message`;忙/上游失败宜带 `retryable: true`(502/503)
**联调账号(写入约定,勿再用错租户 / 勿用超管号)**
| 环境 | 用途 | 登录 | 说明 |
|------|------|------|------|
| 本机 / 演示公司 | 租户 1 演示 | `13800000001` / `demo123` | 仅本机联调;**不要**拿去推生产「宇信达」通道 |
| **生产 · 宇信达** | 公司管理 / 同号绑定联调 | **`13531041944`**(密码由开通时设定) | **专用联调号**;智建须在「宇信达」成员/管理员中写入此手机;宇恒 `YXD_SYNC_LOGIN_PHONE` 与 Z13 同号确认均以此号为准 |
| 平台超管(禁止联调) | 控制台「填入超管手机」 | `13531041945` / `ljk_admin` | **最顶级管理员**;seed `ensureDevPhone(ljk_admin)`;**禁止**用作同步登录 / 绑定联调,会与公司账号冲突 |
> **智建待办(运维)**:见 **§0.2 B′**(生产 pull、成员手机、Secret、通道勿测试表白名单)。Z14c/Z10d/fingerprint 属「已合入待 pull」,不在待开发。
---
## 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(`sync_schema`) |
| **Z11** | **P1** | **蓝图增字段迁库** | 见 §5.8 | **智建已落实** ALTER + 导入提示 |
### 建议验收(智建改后)
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` 可读 |
| — | 经典 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 |
### 现象与建议(运维)
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)已打通**。
- **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 已通**;空表 **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` 绑定)。
> **进展**:Z8a–g 已通(本机镜像、绑库、SyncPage 模块展示);空表 **Z10c 宇恒已接**(`sync_schema`)。
**目标数据流**
```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「智能体/模块」勿长期「未绑模块」;「查看线上表」列出本机已有业务表(含 模块·…);模块列优先展示名 |
智建已落实(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 / 宇恒接 2026-08-05)
现象
本机有空表(如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 | 宇恒已接(2026-08-05):sync_schema.ensure_before_drain;选「同步」force ensure;drain 前 TTL 节流 |
契约摘要
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 建空表。Z10d(2026-08-05 晚):已存在表不再整表跳过,缺列则 ALTER TABLE … ADD COLUMN(platform/internal/dbsync/ensure_table.go)。合入后生产 ./restart.sh --pull 生效;未 pull 时对「先按 _row_id 建表、后 push 注入 id」会持续 has no column named id。
验收
- 本机空表 ensure 后,智建「查看线上表」可见同名 0 行表。
- 换机 / 仅线上:schema → 本机建空表 → bootstrap 灌行。
- 有数据的表仍走原 push;ensure 不替代 outbox 行同步。
- Z10d:ensure 带新增列名时,已存在表补列成功,随后 push 含新列不再 503。
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 · 已落实含 Z12h】账号绑定即落点,禁止手填通道(2026-08-05 / 修订 2026-08-06)
产品约定(生产 · 冻结意向)
- 一个登录账号 / 一个智能体 ↔ 本公司同步落点:绑定后,该账号的本机库只进自己的(或公司共享的)线上库,不会接到别人账号的库。
- 用户不应:手抄
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 | 启用即自动绑通道 | 创建/启用智能体 → EnsureAndStartSystemDefaultChannel(IsSystemDefault,表白名单可空,默认运行中);写回 channel_id+online_db_id;DSN 见 DBSync.DefaultRemoteDSN |
智建已落实(含默认启动 cb76824) |
| Z12c-1 | P0 | 账号级隔离 | Binding / push:用户 JWT 只能写本人 Binding;智能体 JWT 另按挂载落点(Z14c 已修) | 智建已落实 |
| 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 用公司级默认 | 智建已落实 |
| Z12g | P1 | 默认同步通道默认启动 | 创建 Enabled=true;重启对 IsSystemDefault 自动 Start;控制台保存后自动启动 |
智建已落实(cb76824) |
| Z12h | P0 | 通道误删自愈 | HealTenantSyncBind:Ensure 默认通道 + 重挂挂死 Agent/Binding;触发:列表/换票/agents/me/ensure Binding/删通道后 |
智建已落实 |
| Z12h-1 | P1 | 禁删或删后重建默认同步 | is_system_default 可删;删后立即 Ensure+heal 重建并重挂 |
智建已落实 |
| Z12h-2 | P1 | orphan Binding heal | 列表 / Ensure Binding 时通道缺失 → 静默改挂默认通道 | 智建已落实 |
缺口说明(2026-08-06 · 已关闭)
- 原:SyncPage「通道已删除」、
ensureAgentSyncBind见非空channel_id即跳过。 - 现:列表/换票等路径先 heal;默认同步可删并立即重建。生产须 pull 后验收。
宇恒已做(2026-08-05)
- 当前生产通道已写入 env(过渡缓存;Z12c/Z12h 落地后应由换票/自愈自动获得)。
resolve_sync_channel_id():env → Binding → 租户仅 1 通道自动选用。- 不把「手建通道 + 手抄 ID」当作产品终态;通道失效时配合再换票(根因仍靠 Z12h)。
验收
- 新公司:只「启用智能体」,不点新建通道,换票已带
channel_id,宇恒可 drain。 - 账号 A 的本机库 push 不会出现在账号 B 的线上库;共享库仅在显式共享时可见。
- 用户全程无需 F12、无需手填
YXD_SYNC_CHANNEL_ID。 - (Z12h) 删除默认同步通道 → 立即重建并重挂;Binding 挂死 ID 时打开 SyncPage / 换票亦自愈。
5.10 【双方代码已接 Z13】绑定流程简化:绑定码 / 手机号(2026-08-05)
端到端体验(生产目标)
- 后台已绑定(或用户用绑定码/手机号完成绑定)→ 宇恒本机库选 「同步」 → 直接可用(自动通道、自动 Binding、agent drain),无第二套配置。
- 绑定流程必须比「登记智能体 + 抄通道 ID + 填 env」简单。
- 生产·宇信达联调账号:
13531041944(公司侧专用;禁止用超管号13531041945)。- 试运行:未接短信平台 → 关闭短信验证;正式接入后再开(见下)。
推荐交互
公司管理员(智建后台)
→ 生成「绑定码」→ 发给终端用户
宇恒 · AI建站智能体
→ 检测绑定状态
├─ 已绑定 → 静默换票;本机库选「同步」即可用
└─ 未绑定
├─ 宇恒已有手机,且智建同号有成员
│ → 弹「已有账号是否绑定?」→ 确认 / 取消
├─ 表单:【绑定码】或【手机号】
│ · 宇恒未绑手机 → 仍可直接填手机号
│ · 试运行:不校验短信,仅确认绑定
│ · 正式:同号可免短信/凭票;异号须短信
└─ 无成员 / 无码 → 提示联系管理员
→ 绑定成功 → 选「同步」→ drain,零通道配置
试运行 vs 正式
| 项 | 试运行(当前) | 正式(接短信后) |
|---|---|---|
| 配置 | SMS.RequireForBind=false(或 Provider=off) |
RequireForBind=true |
| 探测 | GET /api/v1/auth/bind/policy → trial_mode=true,sms_required_unless_attested=false |
trial_mode=false |
| 填手机号 | 可绑;不校验短信 | 异号 / 宇恒未绑号须 sms_code |
| 同号免验 | 可不传 sms_code;确认即可 |
同号 attested 或走 ticket-exchange |
| 必保留 | 「已有账号是否绑定」确认;confirm=true |
同左 |
同号 / 验证规则
| 场景 | 行为 |
|---|---|
| 试运行 / 短信未接入 | 不校验短信;填号 + 确认即可 |
| 宇恒未绑手机 | 仍可直接输入手机号;正式环境须短信 |
| 宇恒已绑且输入 = 已绑号 | 正式可免短信 / 凭票;命中成员仍弹确认 |
| 宇恒已绑且输入 ≠ 已绑号 | 正式必须短信 |
| 命中公司成员 | 必须询问「已有账号是否绑定」;不得静默 |
| 用户确认 | 绑定该账号 + 公司默认同步落点 |
| 用户取消 | 不绑;可改绑定码 / 换号 |
| 公司无此号 | 提示用绑定码 |
| 联调样例 | 13531041944;勿用 13531041945 |
| 编号 | 优先级 | 诉求 | 说明 | 负责 |
|---|---|---|---|---|
| Z13a | P0 | 后台生成绑定码 | POST/GET/DELETE /api/v1/admin/bind-codes;SyncPage「绑定码」区 |
智建已落实 |
| Z13b | P0 | 绑定码兑换 | POST /api/v1/auth/bind-code/redeem(host_key + code) |
双方已接(智建 API + 宇恒表单) |
| Z13c | P0 | 手机号探测 | POST /api/v1/auth/bind/phone-lookup(不直接绑定) |
智建已落实 |
| Z13c-1 | P0 | 确认绑定 | POST /api/v1/auth/bind/phone-confirm(confirm=true) |
双方已接(宇恒弹窗 + 本机 API) |
| Z13c-2 | P0 | 验证策略 / 凭票 / policy | 试运行关短信;GET /auth/bind/policy;正式异号短信;凭票 ticket-exchange |
双方已接(生产须对齐 Secret + 部署 gateway) |
| Z13d | P0 | 未绑定检测与提示 | sync_bound=false → lookup / 表单 |
宇恒已接 ensure_sync_bound_interactive |
| Z13e | P0 | 选同步即用 | 库选「同步」→ Binding + drain;零手填通道 | 宇恒已接 POST /database/sync/mode |
| Z13f | P1 | 安全 | 试运行关短信;正式强制异号短信;审计 | 智建开关已落实 |
验收
- 绑定码 → 填码 → 绑定成功 → 库选「同步」→ 数据进公司线上库。
- 同号
13531041944→ 弹「已有账号是否绑定」;确认绑、取消不绑;禁用13531041945。 - 宇恒未绑手机 → 仍可直接填号;试运行免短信,正式须短信。
- 试运行:无
sms_code也可 confirm;GET /auth/bind/policy→trial_mode=true。 - 未绑定用户只见绑定码/手机表单,不见 DSN/通道 ID。
- 未确认 / 他人绑定码,不能绑进别的公司或别人账号。
- 正式且启凭票:同号走
ticket-exchange,不再依赖明文attested_same_phone。
5.11 【Z14 · 2026-08-05 / 修订 2026-08-06】表级指纹对账 + 双向补齐 + 联调踩坑
诉求
- 仅靠 agent「推送心跳」发现不了「本机 96 行、线上 0 行」或「同 count 不同内容」。
- 已绑定账号后:线上误删或本地误删,都应能从另一侧按主键并集补回;心跳检测到不一致即自动修。
- 通道被删不属于表级修复,见 Z12h(智建已落实;须生产 pull)。
| 编号 | 优先级 | 诉求 | 现状 |
|---|---|---|---|
| Z14a | P0 | 宇恒本机指纹 + 对比线上 | 宇恒已接:sync_fingerprint.py;TTL 600s;POST /database/sync/fingerprint |
| Z14d | P0 | 双向并集修复 | 宇恒已接(2026-08-06):sync_pull.py;本地缺/更少 → pull(直写 B、不进 outbox);线上缺/更少 → ensure+full_push;hash/count 冲突 → 先 pull 再 push。选「同步」同路径;默认不 prune |
| Z14b | P1 | 智建 content_hash |
已合入,生产 pull:POST .../schema/fingerprint |
| Z10d | P0 | ensure 已存在表补列 | 已合入,生产 pull;未 pull 时 填土高度 缺 id 仍 503 |
| Z14c | P0 | 智能体 online_db_id ACL |
已合入,生产 pull:agentOwnsOnlineDB;人类 Binding 规则不变 |
契约摘要(Z14b)
POST /api/v1/agent/sync/channels/{id}/schema/fingerprint
{ "online_db_id": "..." }
→ result.tables[]: { name, pk_column, row_count, content_hash }
哈希:sha256( table+"|"+pk+"|" + Σ json(row, sort_keys=True)+"\n" ) 前 32 hex。
下行(宇恒已用,智建保持兼容):
POST /api/v1/agent/sync/channels/{id}/pull
{ "mode": "bootstrap", "table": "...", "after_pk": "", "limit": 200, "online_db_id": "..." }
→ result: { columns, items[], has_more, next_after_pk, pk_column }
分工
| 方 | 做什么 |
|---|---|
| 宇恒 | 本机指纹;404 回退 row_count;Z14d 双向 pull↔push;选同步默认不 prune |
| 智建 | 生产 pull:fingerprint + Z10d + Z14c + Z12h + 数据恢复;保持 pull/bootstrap 契约 |
联调踩坑(宇信达 · 已回写)
- 行数对不上:开通同步不会自动灌历史 → full-push / 指纹修复;Z14d 后本地缺也会 pull。
填土高度缺id:push 默认 pk=id;须 Z10d 补列或删表重建后再推(宇恒mark_error堵队已修)。_uN曾 403:智能体 JWTuser_id=agent_id,Binding 在人类成员上 → Z14c 放行挂载 online。- 删通道后再同步仍空:Binding「通道已删除」→ 生产 pull Z12h 后打开 SyncPage/换票自愈;表级指纹无法重建通道。
验收
- 改本机行数后 ≤TTL 检出 mismatch 并排队修复。
- 未 pull fingerprint:仅 count 对账仍可用。
- pull 后:同 count 不同内容 →
hash_mismatch→ 双向修。 - Z10d pull 后:
填土高度可推满。 - (Z14d) 线上删某表行 → 心跳后本机行仍在且线上恢复;本机删某表行(未 prune)→ 心跳后本机从线上拉回。
- (Z12h) 见 §5.9 验收第 4 条。
5.12 【Z15 · 2026-08-06】建站技能 config 自生成 + 无权限先绑定
状态:宇恒已改完(2026-08-06);智建不改 / 无需开发。
落地文件(宇恒仓 .yxd/skills/user/ai-site-agent-api/):scripts/config_loader.py(ensure_config)、scripts/host_bootstrap.py(绑定优先)、scripts/forms.py、scripts/forms_ui.py、forms_ui.json、reference.md。
现象(另一台电脑)
- 克隆后无
config.json(gitignore 密钥文件)→默认登记失败: [Errno 2] No such file or directory: '.../config.json' - 随后卡在鉴权/换票,而不是先走绑定码/手机号
诉求
- 缺
config.json→ 自动写默认模板(可再填绑定码/手机号等),禁止裸抛 Errno 2 - 启动顺序:绑定优先 → 再鉴权/换票;未绑定或无权限时门闸选项含「绑定账号或手机号」
- 聊天表单仍走宇恒
sendFrom/yxd_skill_ty_host(与税务 TY 插件无关,但是同一套交互壳)
分工
| 方 | 做什么 |
|---|---|
| 宇恒 | ai-site-agent-api:config_loader.ensure_config、host_bootstrap 绑定优先、forms/forms_ui 门闸选项 |
| 智建 | 不改;bind / register / ticket 已够用(见 §0.2 B‴) |
宇恒落地(已改完)
ensure_config():缺config.json写默认模板,不再 Errno 2host_bootstrap:绑定码/手机号优先于鉴权换票pending_auth等门闸选项含「绑定账号或手机号」
验收
- 新机无 config → 首次跑技能生成模板文件,无 Errno 2
- 未绑定 → 弹绑定相关表单,而非只提示换票失败
- 已绑定有权限 → 原鉴权/模块操作路径不变
5.13 【智建已落实 · 2026-08-06】数据恢复(恢复上次同步)
状态:智建已落实(须生产 bash ./restart.sh --pull)。
诉求:10:00 同步成功 → 11:00 误删 → 可恢复到最后一次成功同步(含自动 push/drain)时的线上库状态。
| 项 | 说明 |
|---|---|
| 快照 | 成功同步后对通道 Remote 线上库 打滚动快照(latest + previous,约 30s 防抖) |
| 触发 | agent push/batch(有 applied)、通道 drain 有变更、控制台「同步修复」成功 |
| UI | SyncPage 通道操作「数据恢复」;可选最近一次 / 上一代 |
| API | GET /api/v1/admin/sync/channels/{id}/checkpoint;POST …/restore(confirm=true,which=latest|previous) |
| 恢复范围 | 仅线上库;本机靠宇恒下次同步/指纹 pull 从线上补回 |
| 注意 | 误删后若又自动同步成功,最新快照可能已含删除态 → 选「上一代」或尽快恢复 |
验收
- 同步成功约 30s 后,checkpoint 有
latest。 - 删线上若干行 →「数据恢复」选最近一次 → 「查看线上表」行数/内容回到快照。
- 无快照时按钮提示「尚无成功同步快照」,不静默失败。
5.14 【Z16 · 2026-08-06】禁止硬编码联调手机号
状态:宇恒已改完;智建无需开发。
诉求
sync_token._sync_phone()不得默认13531041944(否则别的用户未配 env 时会误用联调号换票)。- 手机号来源:用户绑定表单 / 显式
YXD_SYNC_LOGIN_PHONE(或YXD_ZHIJIAN_PHONE);绑定成功后回写本机 env。 - §0.1 联调号仅作人工联调约定,不是代码默认值。
验收
- 未配置手机号 → 凭票返回「未配置 YXD_SYNC_LOGIN_PHONE」,不静默用联调号。
- 用户用自己的号绑定成功 → env 写入该号,后续换票用该号。
- 源码检索无产品路径硬编码
13531041944(文档/联调说明除外)。
5.15 【Z17 · 2026-08-06】鉴权失败可绑定或更换绑定
状态:宇恒已改完;智建无需开发。
现象
- 鉴权/换票失败后只像「重新检查授权」,虽有绑定项,但本机/线上仍判已绑时
ensure_sync_bound_interactive直接 skipped,无法换绑。
诉求
- 鉴权失败门闸选项含 「绑定或更换绑定」(优先于仅重新检查)。
- 点选后:清空本机
CHANNEL_ID/ Token /ONLINE_DB_ID/LOGIN_PHONE,再强制弹出绑定码/手机号表单。 - 不要求智建提供解绑 API(本机强制重绑即可联调;线上旧 Binding 由新绑定覆盖/复用)。
验收
- 鉴权失败 → 表单可见「绑定或更换绑定」。
- 即使 env 里仍有旧 channel,点选后仍弹出绑定方式表单。
- 绑定成功后可再「重新检查授权状态」。
5.16 【Z18 · 2026-08-06】模块操作页「换绑」为独立按钮
状态:宇恒已改完;智建无需开发。
诉求
- 「模块操作」第一屏底部提供独立按钮「换绑」,不是操作下拉里的选项。
- 鉴权失败门闸同样用按钮触发换绑,避免与「重新检查」混在同一枚举里。
- 点击后:
clear_local_sync_bind+ 强制绑定表单(同 Z17)。
改动面
- 前端
sendFrom.vue:showRebind/rebindText yxd_skill_ty_host:表单载荷 +FormRebindInterrupt- 建站技能:
forms.py/host_bootstrap.py/forms_ui.json
验收
- 已登录看到「模块操作」时,底部有「换绑」按钮(与退出、提交并列)。
- 点击后弹出绑定码/手机号表单,不依赖下拉选「换绑」。
- 操作下拉里无「换绑」项。
5.17 【Z19 · 2026-08-06】禁止静默默认登记;未绑定先绑;修表单无组件
状态:宇恒已改完;智建无需开发。
现象
- 「默认登记失败:Expecting value…」→ 旧路径静默
POST /auth/agent/register,空响应 JSON 解析失败。 - 紧接「会话异常:chat form has no components」→
build_sendfrom_form漏写components字段。
诉求
- 无公司同步绑定时:先绑定,禁止把「默认登记」当登录。
- 登记/换票失败文案引导绑定,不暴露原始 JSON 解析堆栈给用户为主路径。
- sendFrom 表单必须带
components。
5.18 【Z20 · 2026-08-06】启动失败直接弹绑定表单
状态:宇恒已改完。启动未完成时禁止只 notify 文字;立即 force_rebind 推绑定表单(底部换绑同源)。门闸异常时同样回落弹表。
另:ty_host≥1.2.33 禁止用 DONE 永久跳过 bootstrap(旧日志「已自动登记过,跳过」会导致选中会话再也不弹表)。失败后 ACTIVE 清掉,可再拉起。
5.19 【Z21 · 2026-08-06】绑定接口空体安全解析
状态:宇恒已改完;智建无需开发。
现象
- 日志
prepare_runtime failed: Expecting value: line 1 column 1→ 某绑定/鉴权 HTTP 返回空体,裸r.json()抛错,启动被打断;再选会话若被旧 DONE 跳过则只剩文字。
诉求 / 改动
yxd/app_fastapi/sync_bind_flow.py:_safe_http_json,空体/非 JSON 返回{},不抛。- 与 Z19/Z20 配合:解析失败不阻断 → 走「未绑定」→ 直接弹绑定表单。
5.20 【Z22 · 2026-08-06】绑定表单失败可重试
状态:宇恒已改完;智建无需开发。
现象
- 聊天出现「绑定引导失败:Expecting value: line 1 column 1」且下方无绑定表单。
- 根因:
prepare内过早走交互绑定;ask_fields/宿主回包空体 JSON 解析异常冒泡。
诉求
prepare_runtime仅非交互检测是否已绑(channel/token),不在此处ask_fields。- 交互绑定集中在
_try_bind_interactive;失败捕获、友好文案、重试弹表,禁止把Expecting value原文给用户。 config.json空/坏档自动重建;宿主 command 回包空体安全解析。
6. 联系与附件
- 待改清单(优先看):§0.2(A′ 宇恒配合 · B′ 智建运维;开发项已清空);改代码前先写本意见(§0.0);配合见 §0.3
- 方案:
松离线-dbsync方案-最终版.md(含 2026-08-01 联调建议落地记录) - 宇恒使用说明:
宇恒-松离线数据同步使用文档.md(含 Z10 schema/ensure、Z13 绑定) - 开通说明:
docs/数据同步-开通说明.md(含 数据恢复) - 绑定策略探测:
GET /api/v1/auth/bind/policy(trial_mode/require_for_bind) - 本意见如与冻结方案冲突,以冻结方案为准;§5.1–5.13 为产品增量与复测记录,不推翻 H1–H6 默认无感约束。
- 分工:宇恒只改宇恒仓;智建只改智建仓;互不代改。