feat: add Yuheng ticket bind, trial SMS off, shared bindings

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>
This commit is contained in:
whm
2026-08-05 15:09:49 +08:00
parent f90245db9c
commit d195aa4804
19 changed files with 957 additions and 96 deletions

View File

@@ -10,6 +10,9 @@
> 再修订2026-08-05智建落实 **Z9e/Z9f、Z8g、Z11a/b**Z12/Z13 仍待)
> 再修订2026-08-05智建落实 **Z12a/b/c/e、Z13ac-1** APIZ12c-2/Z13de 待宇恒;绑定码控制台 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`
@@ -26,7 +29,7 @@
| **Z9 模块导入默认能力(方案 A** | **已落实(含存量)** | Z9af启动扫库 + 一键开启导入 + 读路径内存兜底;见 §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** | **智建 API 已落实;宇恒待接** | Z12a/b/c/e、Z13 绑定码+同号确认 API 已合入**Z13d/e 待宇恒**;见 §5.95.10**Z12d 通道 ID 可复制已有** |
| **Z12/Z13 开通/绑定 UX** | **智建已基本落实;宇恒待接** | 含凭票、试运行关短信(`RequireForBind=false`)、绑定码 UI**Z13d/e 待宇恒**;见 §5.95.10 |
| **仍建议关注** | 性能/运维 | SQLite remote **高并发 push** 易锁agent 宜单库串行 drain属宇恒用法非平台硬改 |
宇恒对照脚本:
@@ -66,7 +69,7 @@
| **生产 · 宇信达** | 公司管理 / 同号绑定联调 | **`13531041944`**(密码由开通时设定) | **专用联调号**;智建须在「宇信达」成员/管理员中写入此手机;宇恒 `YXD_SYNC_LOGIN_PHONE` 与 Z13 同号确认均以此号为准 |
| 平台超管(禁止联调) | 控制台「填入超管手机」 | `13531041945` / `ljk_admin` | **最顶级管理员**seed `ensureDevPhone(ljk_admin)`**禁止**用作同步登录 / 绑定联调,会与公司账号冲突 |
> **智建待办**:生产「宇信达」写入成员手机 **`13531041944`**公司管理员即可),勿再把同步联调绑到 `13531041945`。
> **智建待办**:生产「宇信达」写入成员手机 **`13531041944`**成员管理「绑定」即可;勿绑到超管 `13531041945`
---
@@ -452,7 +455,7 @@ POST .../schema
| **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** | **公司共享库** | 支持标记 `shared=true` `online_db_id` | **智建** |
| **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 用公司级默认 | **智建已落实**(控制台引导文案可再收) |
@@ -489,45 +492,57 @@ POST .../schema
├─ 已绑定本公司 → 静默换票;本机库选「同步」即可用
└─ 未绑定
├─ 读取当前宇恒账号手机号(若有)
└─ 智建侧已存在「相同手机号」的公司成员/管理员账号
→ 【必弹】「检测到手机号 xxx 在「宇信达」已有账号,是否绑定到该账号?」
├─ 确认 → 绑定到该已有账号(继承公司 + 默认同步落点)
│ └─ 取消 → 改走绑定码 / 换号
├─ 无同号 或 用户选择手动
→ 表单:【绑定码】或【手机号
· 填绑定码 → redeem
· 填手机号 → 若命中已有账号,同样走「是否绑定」确认,禁止静默绑错人
├─ 宇恒已绑手机号,且智建侧同号有成员
→ 【必弹】「检测到手机号 xxx 在「宇信达」已有账号,是否绑定?」
├─ 确认 → 绑定(免短信)
└─ 取消 → 改走绑定码 / 换号
├─ 表单:【绑定码】或【手机号】(宇恒未绑手机时也允许直接填手机号)
│ · 填绑定码 → redeem
· 填手机号
├─ 【试运行】短信未接入:一律免短信,仅弹「是否绑定」确认
├─ 【正式】宇恒已绑号且输入=已绑号 → 免短信 → lookup → 确认
│ ├─ 【正式】宇恒未绑 / 异号 → 须短信 → lookup → 确认
│ └─ 智建无此成员 → 提示引导用绑定码
└─ 都没有 → 明确提示联系管理员要绑定码
→ 绑定成功后:本机选「同步」→ 直接 drain零通道配置
```
**同号规则(产品硬约束)**
> **试运行约定(当前)**:尚未接入短信平台 → `SMS.RequireForBind=false`(或 `Provider=off`**关闭绑定短信验证**;仍须用户确认「是否绑定」。接入短信后改为 `RequireForBind=true`,启用下表正式规则。
**同号 / 验证规则(正式目标;试运行短信步骤可跳过)**
| 场景 | 行为 |
|------|------|
| 宇恒手机号 = 公司内已有成员手机号 | **必须先询问**「已有账号,是否绑定」;不得静默绑定 |
| **试运行 / 短信未接入** | **不校验短信**;填手机号 + 确认即可绑定 |
| 宇恒**未**绑定手机号 | **仍可直接输入手机号**;正式环境须短信 |
| 宇恒已绑手机,输入 = 已绑号 | **免短信**;命中智建账号仍弹「是否绑定」 |
| 宇恒已绑手机,输入 ≠ 已绑号 | 正式环境**必须短信** |
| 宇恒/输入手机 = 公司内已有成员 | **必须先询问**「已有账号,是否绑定」;不得静默绑定 |
| 用户点确认 | 绑定到该已有账号;智能体 host_key 挂到该公司 |
| 用户点取消 | 不绑定;可改输绑定码或其他手机号 |
| 手机号在公司不存在 | 提示无此成员;引导用绑定码 |
| 生产联调样例 | 宇恒号与智建均为 **`13531041944`** → 应弹出「宇信达已有账号是否绑定」**勿用** `13531041945` |
| 生产联调样例 | 宇恒号与智建均为 **`13531041944`** → 弹确认**勿用** `13531041945` |
| 编号 | 优先级 | 诉求 | 说明 | 负责 |
|------|--------|------|------|------|
| **Z13a** | **P0** | **后台生成绑定码** | `POST/GET/DELETE /api/v1/admin/bind-codes`(需「数据同步」);关联默认通道;过期/次数/撤销 | **智建已落实**API;控制台 UI 可后补 |
| **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 }`**不**直接绑定 | **智建已落实** |
| **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** | **安全** | 路由级限速已有确认绑定写审计lookup 不回明文全名 | **智建部分落实**(防枚举可再加强) |
| **Z13f** | **P1** | **安全** | 试运行关短信;正式后异号强制短信;确认绑定写审计 | **智建开关已落实** |
**验收**
1. 管理员生成绑定码 → 用户填码 → 绑定成功 → 库选「同步」→ 数据进该公司线上库。
2. **宇恒与智建同号 `13531041944`** → 必须弹出「已有账号是否绑定」;确认后绑定,取消则不绑(禁止用超管号 `13531041945` 测)。
3. 未绑定用户:先同号探测,再绑定码/手机表单;**不**出现 DSN/通道 ID
4. 他人绑定码 / 未确认的同号,不能把数据绑进别的公司或别人账号。
3. **宇恒未绑手机** → 仍可直接输入手机号;**试运行免短信**,正式后须短信
4. **试运行**`RequireForBind=false` 时不带 `sms_code` 也可 confirm仍须 `confirm=true``GET /auth/bind/policy` 返回 `trial_mode=true`
5. 未绑定用户:先同号探测,再绑定码/手机表单;**不**出现 DSN/通道 ID。
6. 他人绑定码 / 未确认的同号,不能把数据绑进别的公司或别人账号。
7. **正式且启凭票**:同号走 `ticket-exchange`,不再依赖明文 `attested_same_phone`
---
## 6. 联系与附件