feat: complete device onboarding and mobile binding workflows
ci / verify (push) Canceled after 0s

This commit is contained in:
Codex Local
2026-09-12 08:57:29 +08:00
parent 7846f30d0b
commit 98fb2965d0
116 changed files with 15669 additions and 64 deletions
+412
View File
@@ -0,0 +1,412 @@
# 首次接入接口字段清单
由 `node scripts/generate-onboarding-contract.mjs` 根据实际Go请求结构生成。共 31 项操作。完整响应结构见 [OpenAPI](../openapi/onboarding.yaml),流程、签名、错误恢复见 [实施说明](onboarding-implementation.md)。
## C01 校时挑战
`POST /api/v1/device/time-challenges`
调用身份:校时请求,无请求签名;设备必须核验TIME1响应MAC
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| device_id | string | 是 | 生产编号,KP01-加12位大写MAC;不是平台UUID |
| key_version | integer | 是 | 设备认证密钥版本 |
| boot_id | string | 是 | 每次启动生成并保持的UUID v4 |
| client_nonce | string | 是 | 设备生成的16字节随机数,Base64URL无填充 |
响应:根对象直接返回 `{payload_b64,mac_hex}`。
## C02 首次认证和启动注册
`POST /api/v1/device/register`
调用身份:设备DEVREQ1签名
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| challenge_id | string | 是 | C01签名校时返回的一次性挑战 |
| boot_id | string | 是 | 每次启动生成并保持的UUID v4 |
| hardware_model | string | 是 | 生产登记型号,当前为KP01 |
| hardware_revision | string | 是 | 硬件修订号,注册必须与库存一致 |
| firmware_version | string | 是 | 固件版本,用于兼容诊断 |
| sdk_version | string | 是 | SDK版本,用于兼容诊断 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `RegisterResponse.data`。
## C03 创建临时设置会话
`POST /api/v1/device/setup-sessions`
调用身份:设备DEVREQ1签名
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| boot_id | string | 是 | 每次启动生成并保持的UUID v4 |
| local_setup_id | string | 是 | 设备本次设置入口UUID v4;重试保持不变 |
| transport_profile | string | 是 | 固定LOCAL_WEB,表示设备提供本地网页入口 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `SetupResponse.data`。
## C04 获取一次性手机入口
`POST /api/v1/device/handoffs`
调用身份:设备DEVREQ1签名
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| setup_id | string | 是 | 云端15分钟临时设置会话UUID |
| local_setup_id | string | 是 | 设备本次设置入口UUID v4;重试保持不变 |
| code_challenge | string | 是 | SHA256(ASCII verifier)的Base64URL无填充值 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `HandoffResponse.data`。
## C05 查询状态和命令
`POST /api/v1/device/poll`
调用身份:设备DEVREQ1签名
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| setup_id | string | 是 | 云端15分钟临时设置会话UUID |
| boot_id | string | 是 | 每次启动生成并保持的UUID v4 |
| known_revision | integer | 是 | 设备已知状态版本;允许0 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `PollResponse.data`。
## C06 物理按键确认绑定
`POST /api/v1/device/claim-confirmations`
调用身份:设备DEVREQ1签名
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| claim_id | string | 是 | 待物理确认的绑定申请UUID |
| challenge | string | 是 | C05给设备的短时按键挑战,手机不能获取 |
| binding_digest | string | 是 | 云端生成的绑定摘要,按键回报原值 |
| expected_binding_generation | integer | 是 | 提交时预期绑定代数,防止跨解绑或重新绑定重放 |
| press_event_id | string | 是 | 该次真实物理按键事件UUID v4,重试保持不变 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `ConfirmationResponse.data`。
## C07 绑定状态持久化回执
`POST /api/v1/device/state-acks`
调用身份:设备DEVREQ1签名
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| device_revision | integer | 是 | 云端设备状态版本号 |
| binding_generation | integer | 是 | 绑定代数;解绑、重新绑定后旧代数无效 |
| local_generation | integer | 是 | 本次启动的本地持久化序号,不能倒退 |
| state_hash | string | 是 | STATE1规范字符串的SHA256小写十六进制摘要 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `StateAckResponse.data`。
## C08 申请设备执行许可
`POST /api/v1/device/activation-attempts`
调用身份:设备DEVREQ1签名
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| activation_id | string | 是 | 该物理设备唯一的激活任务UUID |
| config_version | integer | 是 | 配置版本,必须与当前云端版本一致 |
| attempt_id | string | 是 | 设备生成的UUID v4,表示一次引擎初始化或首通执行 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `AttemptResponse.data`。
## C09 回报设备执行结果
`POST /api/v1/device/activation-results`
调用身份:设备DEVREQ1签名
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| activation_id | string | 是 | 该物理设备唯一的激活任务UUID |
| attempt_id | string | 是 | 设备生成的UUID v4,表示一次引擎初始化或首通执行 |
| stage | string | 是 | SDK_REGISTER或CONVERSATION_TEST;与C08平台当前阶段一致 |
| sdk_result | integer | 是 | SDK执行返回码;SUCCESS_OBSERVED时必须为0 |
| outcome | string | 是 | SUCCESS_OBSERVED、DEFINITE_FAILURE或UNKNOWN;未知结果不得盲目重试 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `ResultResponse.data`。
## C10 激活结果保存回执
`POST /api/v1/device/activation-acks`
调用身份:设备DEVREQ1签名
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| activation_id | string | 是 | 该物理设备唯一的激活任务UUID |
| local_generation | integer | 是 | 本次启动的本地持久化序号,不能倒退 |
| config_version | integer | 是 | 配置版本,必须与当前云端版本一致 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `ActivationAckResponse.data`。
## C11 关闭临时设置入口
`POST /api/v1/device/setup-close`
调用身份:设备DEVREQ1签名
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| setup_id | string | 是 | 云端15分钟临时设置会话UUID |
| reason | string | 是 | 关闭、轮换或人工处理原因,见对应枚举 |
| final_revision | integer | 是 | 设备关闭设置入口前看到的最新状态版本 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `CloseResponse.data`。
## 手机兑换一次性入口
`POST /api/v1/web/handoffs/exchange`
调用身份:同源网页;一次性token和verifier
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| token | string | 是 | 90秒内只能兑换一次的handoff令牌 |
| verifier | string | 是 | 设备交给手机的PKCE校验值;32字节随机数的Base64URL无填充字符串 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `BrowserResponse.data`。
## 恢复手机设置会话
`GET /api/v1/web/setup/session`
调用身份:设置Cookie + 写请求CSRF
无请求正文。
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `BrowserResponse.data`。
## 关联OIDC登录
`POST /api/v1/web/setup/auth-link`
调用身份:设置Cookie + CSRF + OIDC Bearer
正文:`{}`。
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `BrowserResponse.data`。
## 发送登录短信
`POST /api/v1/web/auth/sms/send`
调用身份:设置Cookie + 写请求CSRF
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| phone_e164 | string | 是 | 含国家区号的手机号,例如+8613800000000 |
| purpose | string | 是 | 固定LOGIN |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `SMSResponse.data`。
## 验证短信并登录
`POST /api/v1/web/auth/sms/verify`
调用身份:设置Cookie + 写请求CSRF
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| sms_challenge_id | string | 是 | 发送短信得到的挑战UUID |
| code | string | 是 | 6位短信验证码;5分钟有效,最多验证5次 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `BrowserResponse.data`。
## 查询可选择的家庭使用者
`POST /api/v1/web/profiles`
调用身份:设置Cookie + 写请求CSRF
正文:`{}`。
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `ProfilesResponse.data`。
## 创建家庭或使用者
`POST /api/v1/web/profiles/create`
调用身份:设置Cookie + 写请求CSRF
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| household_id | string | 条件或可选 | 家庭公开UUID;省略时须提供新家庭名称 |
| household_name | string | 条件或可选 | 新建家庭的名称 |
| preferred_name | string | 是 | 使用者称呼,用于设备人设和家庭展示 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `ProfileResponse.data`。
## 提交绑定申请
`POST /api/v1/web/claims`
调用身份:设置Cookie + 写请求CSRF
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| elder_id | string | 是 | 使用者公开UUID,复用subject_profile |
| expected_binding_generation | integer | 是 | 提交时预期绑定代数,防止跨解绑或重新绑定重放 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `ClaimResponse.data`。
## 取消自己的绑定申请
`POST /api/v1/web/claims/cancel`
调用身份:设置Cookie + 写请求CSRF
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| claim_id | string | 是 | 待物理确认的绑定申请UUID |
| expected_binding_generation | integer | 是 | 提交时预期绑定代数,防止跨解绑或重新绑定重放 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `StateOnlyResponse.data`。
## 查询手机绑定进度
`POST /api/v1/web/setup/status`
调用身份:设置Cookie + 写请求CSRF
正文:`{}`。
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `WebStatusResponse.data`。
## 网页确认收到绑定结果
`POST /api/v1/web/setup/ack`
调用身份:设置Cookie + 写请求CSRF
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| binding_id | string | 是 | 当前已生效的绑定公开UUID |
| binding_generation | integer | 是 | 绑定代数;解绑、重新绑定后旧代数无效 |
| received_revision | integer | 是 | 网页已收到的设备状态版本 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `AckResponse.data`。
## 设置结束后查询我的设备
`GET /api/v1/web/onboarding/devices`
调用身份:账号Cookie;设备列表也支持OIDC Bearer
无请求正文。
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `DevicesResponse.data`。
## 恢复账号会话及CSRF
`GET /api/v1/web/account/session`
调用身份:账号Cookie;设备列表也支持OIDC Bearer
无请求正文。
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `AccountResponse.data`。
## 退出本浏览器登录
`POST /api/v1/web/auth/logout`
调用身份:账号Cookie;设备列表也支持OIDC Bearer
正文:`{}`。
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `LogoutResponse.data`。
## 查询生产设备和激活状态
`GET /api/v1/ops/device-inventory`
调用身份:运维OIDC Bearer + X-Tenant-ID
无请求正文。
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `DevicesResponse.data`。
## 登记生产设备
`POST /api/v1/ops/device-inventory`
调用身份:运维OIDC Bearer + X-Tenant-ID
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| factory_mac | string | 是 | 12位大写十六进制MAC;相同MAC不能创建第二台物理设备 |
| hardware_model | string | 是 | 生产登记型号,当前为KP01 |
| hardware_revision | string | 是 | 硬件修订号,注册必须与库存一致 |
| batch_no | string | 是 | 生产批次,方便追溯 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `InventoryResponse.data`。
## 向工装交付加密密钥
`POST /api/v1/ops/device-inventory/{id}/credential-deliveries`
调用身份:运维OIDC Bearer + X-Tenant-ID
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| enrollment_job_id | string | 是 | 生产登记或密钥轮换任务UUID |
| receiver_public_key_pem | string | 是 | 工装RSA公钥,PKIX PEM,2048至4096位,指数65537 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `DeliveryResponse.data`。
## 确认烧录读回并放行
`POST /api/v1/ops/device-inventory/{id}/release`
调用身份:运维OIDC Bearer + X-Tenant-ID
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| enrollment_job_id | string | 是 | 生产登记或密钥轮换任务UUID |
| evidence_ref | string | 是 | 烧录读回或人工对账的证据引用,不放密钥 |
| expected_key_version | integer | 是 | 交付放行目标版本;轮换时为当前生效版本 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `ReleaseResponse.data`。
## 准备轮换凭据
`POST /api/v1/ops/device-inventory/{id}/credential-rotations`
调用身份:运维OIDC Bearer + X-Tenant-ID
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| reason | string | 是 | 关闭、轮换或人工处理原因,见对应枚举 |
| expected_key_version | integer | 是 | 交付放行目标版本;轮换时为当前生效版本 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `InventoryResponse.data`。
## 人工记录激活对账结论
`POST /api/v1/ops/onboarding-activations/{id}/reconcile`
调用身份:运维OIDC Bearer + X-Tenant-ID
| 字段 | 类型 | 必填 | 用途 |
|---|---|---|---|
| expected_version | integer | 是 | 运维列表中的激活任务版本,防止覆盖并发处理 |
| conclusion | string | 是 | OBSERVED_REGISTERED、CONFIRMED_NOT_REGISTERED或DEVICE_ATTEMPT_STOPPED |
| evidence_ref | string | 是 | 烧录读回或人工对账的证据引用,不放密钥 |
| reason | string | 是 | 关闭、轮换或人工处理原因,见对应枚举 |
| attempt_id | string | 条件或可选 | 设备生成的UUID v4,表示一次引擎初始化或首通执行 |
| device_secret | string | 条件或可选 | 已核实的供应商单设备密钥,仅人工对账写入,加密保存 |
| rtc_app_id | string | 条件或可选 | 供应商RTC应用ID;不填则使用已保存值 |
响应:成功外层 `{ok,code,request_id,server_time_ms,data}`;失败 `{ok,code,request_id,message}`。data结构为 `StateOnlyResponse.data`。