Files
story/docs/device-api-reference.md
Codex Local 0c2151ace9
ci / verify (push) Canceled after 0s
chore: checkpoint 第一版
2026-09-06 17:04:11 +08:00

577 lines
23 KiB
Markdown
Raw Permalink 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.
# 设备接口文档
> 适用范围:平台设备管理、EchoEar/ESP32-S3 设备接入、火山引擎硬件对话智能体。
> 契约基线:当前工作区实际路由、处理器与 `openapi/openapi.yaml`,更新日期 2026-09-05。
> 生产 API:`https://121.41.46.238`(正式域名启用后只替换 Base URL,不改变路径)。
## 1. 接入模型
设备同时使用两套彼此独立的身份:
| 身份/密钥 | 用途 | 生成与保存位置 | 是否下发设备 |
|---|---|---|---|
| 平台 `device_id` | 标识平台登记设备 | 平台生成;设备保存公开编号 | 是 |
| 平台 Device Secret | 调用 `/device/*` 时做 HMAC 鉴权 | 平台逐设备生成;平台加密保存,设备安全区保存 | 是,只在登记/轮换时显示一次 |
| 火山 `instance_id` / `product_key` | 标识火山实例和产品 | 平台配置 | 可随预配响应返回,非秘密 |
| 火山 Product Secret | 调用 DynamicRegister 的产品级秘密 | 只保存在平台 KMS/Secret | **否** |
| 火山 `device_name` / `device_secret` | 设备直连火山时使用的一机一密 | 平台代设备动态注册;平台加密保存,设备安全区保存 | 是,只返回给已认证的对应设备 |
| Bot ID | 选择硬件对话智能体 | 平台按设备绑定 | 会话响应返回,非秘密 |
核心原则:设备固件不内置 Product Secret。设备先用平台 Device Secret 认证,由平台代设备调用火山动态注册,再把该设备专属的火山 Device Secret 返回给设备。
### 1.1 火山 License 的准确含义
火山硬件对话智能体的 License 是产品侧可用额度。平台调用 DynamicRegister 时,火山自动消耗当前产品的一份有效额度并返回一机一密;设备不接收一段可安装的 License 明文,也不需要向平台提交 License 安装回执。
因此,当前有效契约中:
- 没有 `/device/v3/hardware-agent-license:ack` 路由;
- 没有设备端 `license` 字段;
- 没有 `AVAILABLE → ALLOCATED → ACTIVE` 的设备 License 安装状态机;
- 平台可以维护额度监控或影子账本,但不能用它替代火山的真实额度,也不能阻断已正确取得一机一密的设备。
火山返回额度耗尽、过期等错误时,平台统一转换为 `HARDWARE_AGENT_PROVISIONING_FAILED`,同时通过内部日志和请求编号保留供应商错误供运维定位。
## 2. 通用响应格式
成功响应:
```json
{
"request_id": "req-...",
"data": {}
}
```
失败响应:
```json
{
"request_id": "req-...",
"error": {
"code": "VALIDATION_FAILED",
"message": "请求参数不正确",
"retryable": false,
"field_errors": [],
"details": {}
}
}
```
排障时应记录 `request_id`、HTTP 状态码和稳定错误码,不记录任何 Secret、Token、完整人设或完整对话正文。
## 3. 设备 HMAC 鉴权
所有 `/device/v2/*` 和 `/device/v3/*` 请求必须携带:
| 请求头 | 说明 |
|---|---|
| `X-Device-Id` | 平台设备公开 UUID |
| `X-Device-Timestamp` | RFC3339 UTC 时间,默认允许前后 5 分钟 |
| `X-Device-Nonce` | 本次请求唯一的 16—128 字符随机值 |
| `X-Device-Content-SHA256` | 原始 HTTP 正文的 SHA-256 小写十六进制;空正文也要计算 |
| `X-Device-Signature` | HMAC-SHA256 结果的无填充 Base64URL |
签名原文必须逐字使用实际请求 URI(含查询字符串):
```text
UPPERCASE_HTTP_METHOD
REQUEST_URI_WITH_EXACT_QUERY_STRING
X-Device-Timestamp原值
X-Device-Nonce原值
X-Device-Content-SHA256原值
```
伪代码:
```text
bodyHash = hexLower(SHA256(rawBody))
canonical = methodUpper + "\n" + requestURI + "\n" + timestamp + "\n" + nonce + "\n" + bodyHash
signature = base64UrlNoPadding(HMAC_SHA256(platformDeviceSecret, canonical))
```
注意事项:
- JSON 签名后不得重新格式化或改变字段顺序;服务端校验的是收到的原始字节。
- 每次网络重试都生成新的 `timestamp`、`nonce` 和签名。
- 需要幂等的业务请求保留原 `Idempotency-Key` 和原正文。
- 时间未同步时先通过安全时间源校时;不能关闭时间窗或重放保护。
## 4. 设备激活状态机
主流程:
```text
FACTORY → CLAIMABLE → BOUND → PROVISIONING → ACTIVE
│
├─→ LOST
└─→ REVOKED
```
| 状态 | 含义 | 允许的设备操作 |
|---|---|---|
| `FACTORY` | 只有出厂身份,等待质检放行 | 不允许设备 API 鉴权 |
| `CLAIMABLE` | 可生成/消费绑定码 | 启动、配置、命令、遥测;不能通话 |
| `BOUND` | 已绑定家庭和使用人 | 可申请火山一机一密;不能创建会话 |
| `PROVISIONING` | 正在注册或恢复火山运行凭据 | 可重复预配;不能创建会话 |
| `ACTIVE` | 平台身份、绑定和运行凭据就绪 | 全部设备接口 |
| `LOST` | 设备挂失 | 设备鉴权和新会话被拒绝 |
| `REVOKED` | 永久吊销 | 终态,不可恢复 |
代码允许的恢复/解绑边包括:`BOUND → CLAIMABLE`、`PROVISIONING → BOUND`、`ACTIVE → CLAIMABLE/PROVISIONING/LOST`、`LOST → CLAIMABLE/ACTIVE`;任意非 `REVOKED` 状态均可受控转为 `REVOKED`。状态变更必须由平台业务或运维接口执行,设备不能自行声明已激活。
每次变更增加 `activation_revision` 并保存审计历史。进入 `ACTIVE` 时,硬件智能体设备必须已经绑定且存在有效火山一机一密。
## 5. 首次激活完整流程
1. 运维调用 `POST /api/v1/ops/devices` 登记设备,取得一次性平台 Device Secret。
2. 工厂烧录/安全注入 `device_id`、平台 Device Secret、API Base URL 和稳定 `hardware_id`;不得烧录 Product Secret。
3. 运维将设备从 `FACTORY` 放行到 `CLAIMABLE`,并生成绑定码。
4. 用户 App/Web 调用 `POST /api/v1/device-claims` 消费绑定码,平台把设备变为 `BOUND`。
5. 设备使用平台 HMAC 调用 `POST /device/v3/hardware-agent-credentials:provision`。
6. 平台把状态推进到 `PROVISIONING`,自动建立产品默认 Bot/传输/最低 SDK 绑定,再使用服务端 Product Secret 调用火山 DynamicRegister。
7. 火山消耗产品 License 额度并返回该 `device_name` 对应的 `device_secret`。
8. 平台加密保存火山 Device Secret,并通过 `no-store` 响应返回给同一设备,然后自动完成到 `ACTIVE` 的迁移。
9. 设备把火山一机一密写入 NVS 加密分区/安全芯片;以后重复预配会返回已保存的同一凭据,不重复注册。
10. 每次用户发起聊天时,设备先创建平台硬件智能体会话,注入本次人设和上下文后,再直连火山。
## 6. 设备端接口总览
### 6.1 启动、配置、命令和固件(v2 公共能力)
| 方法 | 路径 | 状态要求 | 幂等键 | 作用 |
|---|---|---|---|---|
| `GET` | `/device/v2/bootstrap` | `CLAIMABLE/BOUND/PROVISIONING/ACTIVE` | 否 | 上报启动版本并取得绑定和兼容性 |
| `GET` | `/device/v2/config` | 同上 | 否 | 拉取期望配置;支持 `If-None-Match` |
| `GET` | `/device/v2/commands` | 同上 | 否 | 拉取待执行命令 |
| `POST` | `/device/v2/commands/{id}/ack` | 同上 | 否 | 上报命令阶段或终态 |
| `POST` | `/device/v2/telemetry:batch` | 同上 | 是 | 批量上报设备遥测 |
| `GET` | `/device/v2/firmware-releases/{id}/download` | 同上 | 否 | 下载受控固件制品 |
`bootstrap` 示例:
```http
GET /device/v2/bootstrap?firmware_version=1.0.0&board_rev=echoear-v1&rtc_sdk_version=1.0.0&codec=pcm_s16le&sample_rate=16000&device_time=2026-09-03T08%3A00%3A00Z&network=wifi
```
配置拉取建议:首次不带 `If-None-Match`;保存响应 `ETag`,后续携带该值。HTTP `304` 表示继续使用本地有效配置。
### 6.2 旧 RTC 通话(v2,仅兼容旧设备)
| 方法 | 路径 | 说明 |
|---|---|---|
| `POST` | `/device/v2/conversations` | 创建旧 RTC 通话 |
| `POST` | `/device/v2/conversations/{id}/activate` | 入房成功后激活供应商任务 |
| `GET` | `/device/v2/conversations/{id}` | 查询通话权威状态 |
| `POST` | `/device/v2/conversations/{id}/heartbeat` | 通话心跳 |
| `POST` | `/device/v2/conversations/{id}/stop` | 停止通话 |
| `POST` | `/device/v2/conversations/{id}/turns:batch` | 补传最终字幕 |
新 EchoEar 固件的 `voice_runtime=volc_hardware_agent` 时不得调用这组 RTC 创建/激活接口。
### 6.3 录音上传(v2 公共媒体能力)
| 方法 | 路径 | 作用 |
|---|---|---|
| `POST` | `/device/v2/conversations/{id}/upload-batches` | 创建分片上传批次 |
| `GET` | `/device/v2/upload-batches/{id}` | 查询上传校验状态 |
| `POST` | `/device/v2/upload-batches/{id}/complete` | 通知平台分片已上传 |
| `POST` | `/device/v2/media-segments/{id}/local-delete-ack` | 回报本地密文已删除 |
只有 `ACTIVE` 设备且本次隐私策略允许时才能使用;没有录音授权不得采集、缓存或上传 PCM。
## 7. 火山硬件智能体接口(v3)
### 7.1 申请火山一机一密
```http
POST /device/v3/hardware-agent-credentials:provision
Idempotency-Key: 2ee3b675-59c0-4d93-a189-6db7f8db3750
Cache-Control: no-store
Content-Type: application/json
```
状态要求:`BOUND`、`PROVISIONING` 或 `ACTIVE`。
请求:
```json
{
"hardware_id": "echoear-b43a451634a8"
}
```
`hardware_id` 必须来自稳定硬件身份(如 eFuse/Chip ID 的规范化值),设备重启、升级和恢复出厂后保持不变。
成功响应:
```json
{
"request_id": "req-...",
"data": {
"instance_id": "<instance-id>",
"product_key": "<product-key>",
"device_name": "echoear-...",
"hardware_id": "echoear-b43a451634a8",
"device_secret": "<volc-device-secret>",
"credential_ref": "<non-secret-ref>",
"credential_version": 1,
"credential_source": "platform_dynamic_registration",
"issued_at": "2026-09-03T08:00:00Z"
}
}
```
安全要求:
- 响应带 `Cache-Control: no-store` 和 `Pragma: no-cache`。
- `device_secret` 只写入设备安全存储,不进入普通配置文件、串口日志、崩溃转储或平台遥测。
- `credential_source=platform_stored` 表示幂等返回平台已保存的凭据,不代表重新消耗 License。
- `hardware_id` 与已注册记录不一致时返回冲突,不允许覆盖原凭据。
常见错误:
| HTTP | 错误码 | 处理 |
|---|---|---|
| `400` | `VALIDATION_FAILED` | 修正 `hardware_id` 或幂等键 |
| `409` | `HARDWARE_AGENT_BINDING_REQUIRED` | 先在平台绑定 Bot/产品配置 |
| `409` | `HARDWARE_AGENT_PRODUCT_MISMATCH` | 运维核对实例、产品和设备绑定 |
| `409` | `HARDWARE_AGENT_HARDWARE_MISMATCH` | 停止自动重试,核对硬件身份 |
| `409` | `DEVICE_ACTIVATION_REQUIRED` | 先完成用户绑定,进入 `BOUND` |
| `503` | `HARDWARE_AGENT_PROVISIONING_UNAVAILABLE` | 平台未配置动态注册;联系运维 |
| `503` | `HARDWARE_AGENT_PROVISIONING_FAILED` | 使用原幂等键、原正文和新签名退避重试;由 `request_id` 查火山原始错误 |
### 7.2 创建本次硬件智能体会话
```http
POST /device/v3/agent-sessions
Idempotency-Key: d7f08e7a-9e1d-455f-b8ec-a745a34d8790
Content-Type: application/json
```
状态要求:仅 `ACTIVE`。每次聊天创建一个新会话,不复用上一通响应。
请求示例:
```json
{
"device_time": "2026-09-03T08:05:00Z",
"monotonic_ms": 123456,
"capabilities": {
"hardware_agent": true,
"sdk_version": "1.0.0-echoear-platform.1",
"transports": ["low_load"],
"input_audio_format": "pcm_s16le_16000_mono",
"output_audio_format": "pcm_s16le_16000_mono",
"server_event_callback": false,
"transcript_upload": true,
"dynamic_session_config": true,
"credential_rotation": true,
"near_pcm_recording": false,
"far_pcm_recording": false
},
"network": {
"type": "wifi",
"rssi": -52
},
"requested_goal": "open_conversation",
"config_revision": 1
}
```
约束:
- `transports` 只能包含 `low_load`、`high_quality`,至少一个。
- `transcript_upload`、`dynamic_session_config`、`credential_rotation` 必须为 `true`。
- EchoEar 当前使用 `low_load`;`server_event_callback=false` 时设备事件和最终字幕上传是权威回流。
- `config_revision` 必须使用最近一次 `/device/v2/config` 返回的版本。
成功响应核心字段:
```json
{
"request_id": "req-...",
"data": {
"conversation_id": "<uuid>",
"status": "SESSION_READY",
"runtime_type": "volc_hardware_agent",
"runtime": {
"provider": "volcengine",
"bot_id": "<bot-id>",
"transport": "low_load",
"provider_session_ref": "<provider-session-ref>",
"session_nonce": "<memory-only-secret>",
"config_digest": "<sha256-hex>",
"wait_for_session_update": true
},
"session_config": {
"dialog_id": "<uuid>",
"system_prompt": "<本次冻结的人设提示>",
"persona_tone": "<语气>",
"authorized_context": "<已授权背景/近期摘要/长期记忆>",
"prompt_bundle_version": "<version>"
},
"privacy": {
"retention_mode": "transcript_only",
"record_near": false,
"record_far": false,
"upload_allowed": false
},
"session": {
"engine_mode": "volc_hardware_agent",
"heartbeat_interval_sec": 10,
"allocation_expires_at": "2026-09-03T08:10:00Z"
}
}
}
```
设备处理顺序:
1. 只在运行内存保存 `session_nonce`。
2. 使用预配取得的一机一密连接响应指定的 Bot 和传输模式。
3. 以 `dialog_id` 作为本次业务对话标识。
4. 在开启麦克风前通过 SDK 动态配置或 WebSocket `session.update` 注入 `system_prompt`、`persona_tone` 和 `authorized_context`。
5. 收到火山配置已应用确认后,再上报 `session_started`。
`tool_scope_token` 如存在也只能保存在运行内存,并且仅用于响应指定的 `tool_url` 和本次会话。
### 7.3 上报会话事件
```http
POST /device/v3/agent-sessions/{conversation_id}/events
Content-Type: application/json
```
开始事件:
```json
{
"event_id": "evt-start-0001",
"sequence_no": 1,
"event_type": "session_started",
"occurred_at": "2026-09-03T08:05:03Z",
"provider_session_ref": "<response-runtime-provider-session-ref>",
"session_nonce": "<response-runtime-session-nonce>",
"provider_connection_ref": "<actual-volc-connection-ref>",
"bot_id": "<response-runtime-bot-id>",
"transport": "low_load",
"config_digest": "<response-runtime-config-digest>",
"dynamic_config_applied": true
}
```
后续事件类型:
- `session_updated`:按 `heartbeat_interval_sec` 上报存活;`sequence_no` 严格递增。
- `session_ended`:正常结束。
- `session_failed`:异常结束,可附加稳定、脱敏的 `error_code`。
响应:
```json
{
"request_id": "req-...",
"data": {
"conversation_id": "<uuid>",
"status": "ACTIVE",
"action": "continue",
"duplicate": false,
"accepted_at": "2026-09-03T08:05:03Z"
}
}
```
`action=stop_session` 时,设备必须立即关闭麦克风和火山连接,然后补报 `session_ended` 或 `session_failed`。相同 `event_id` 重放时必须保持原正文一致。
### 7.4 批量上传最终话轮
```http
POST /device/v3/agent-sessions/{conversation_id}/turns:batch
Idempotency-Key: 1710b8cc-9bf2-45e8-a16d-f32c3b80cbbe
Content-Type: application/json
```
请求:
```json
{
"turns": [
{
"turn_key": "user-final-0001",
"sequence_no": 1,
"speaker": "subject",
"start_at": "2026-09-03T08:05:10Z",
"end_at": "2026-09-03T08:05:12Z",
"final_text": "今天天气怎么样?",
"interrupted": false
},
{
"turn_key": "agent-final-0002",
"sequence_no": 2,
"speaker": "agent",
"start_at": "2026-09-03T08:05:12Z",
"end_at": "2026-09-03T08:05:15Z",
"final_text": "我来帮您看看。",
"interrupted": false
}
]
}
```
只上传最终字幕;不得上传增量识别结果。单批 1—500 条,`speaker` 只能为 `subject` 或 `agent`。同一 `turn_key`/`sequence_no` 的重试必须保持正文完全一致。
成功响应:
```json
{
"request_id": "req-...",
"data": {
"conversation_id": "<uuid>",
"accepted_count": 2,
"duplicate_count": 0
}
}
```
## 8. App/Web 的设备接口
以下接口使用用户 OIDC Bearer Token,不使用设备 HMAC:
| 方法 | 路径 | 作用 |
|---|---|---|
| `POST` | `/api/v1/device-claims` | 消费绑定码并绑定设备 |
| `GET` | `/api/v1/devices` | 列出当前用户可管理设备 |
| `GET` | `/api/v1/devices/{id}` | 查看设备详情 |
| `GET` | `/api/v1/devices/{id}/binding-impact` | 变更绑定前评估影响 |
| `POST` | `/api/v1/devices/{id}/binding-change` | 解绑或改绑 |
| `GET` | `/api/v1/devices/{id}/presence` | 查看在线状态 |
| `GET` | `/api/v1/devices/{id}/twin` | 查看设备影子 |
| `PATCH` | `/api/v1/devices/{id}/twin` | 修改期望配置;必须携带 `If-Match` |
| `GET` | `/api/v1/devices/{id}/commands` | 查看命令历史 |
| `POST` | `/api/v1/devices/{id}/commands` | 下发设备命令 |
绑定码由平台/运维为 `CLAIMABLE` 设备生成,App 只消费,不自行生成。解绑会影响设备激活状态和火山运行凭据,必须先调用 `binding-impact` 展示影响并要求用户确认。
## 9. 平台运维设备接口
所有接口要求 OIDC Bearer Token 和有效的 `X-Tenant-Id: <tenant-uuid>`,且仅独立运维账号可调用。
| 方法 | 路径 | 作用 |
|---|---|---|
| `GET` | `/api/v1/ops/devices` | 设备列表 |
| `POST` | `/api/v1/ops/devices` | 登记设备并一次性返回平台 Device Secret |
| `GET` | `/api/v1/ops/device-assignment-options` | 可选家庭/长者列表 |
| `PATCH` | `/api/v1/ops/devices/{id}` | 修改设备;必须携带 `If-Match` |
| `DELETE` | `/api/v1/ops/devices/{id}` | 吊销设备;必须携带 `If-Match` |
| `POST` | `/api/v1/ops/devices/{id}/credentials/rotate` | 轮换平台 Device Secret,只返回一次明文 |
| `POST` | `/api/v1/ops/devices/{id}/claims` | 生成一次性绑定码 |
| `POST` | `/api/v1/ops/devices/{id}/activation-transitions` | 人工迁移激活状态 |
| `GET` | `/api/v1/ops/devices/{id}/activation-transitions` | 查询激活迁移历史 |
### 9.1 登记硬件智能体设备
```json
{
"model": "echoear",
"hardware_revision": "esp32-s3",
"minimum_security_version": "1",
"minimum_firmware_version": "1.0.0",
"household_id": "",
"subject_id": "",
"voice_runtime": "volc_hardware_agent",
"activation_state": "FACTORY"
}
```
Instance ID、Product Key、默认 Bot、传输模式和最低 SDK 由平台产品配置统一持有。设备第一次预配时自动生成绑定,运维创建设备或设备自身都不能覆盖这些字段。
响应中的 `data.credential.secret` 只显示一次。运维系统应立即通过受控注入工具写入设备安全区,不得复制到工单、聊天记录或普通数据库字段。
### 9.2 人工迁移激活状态
```json
{
"target_state": "CLAIMABLE",
"reason": "工厂质检通过",
"expected_revision": 1
}
```
并发控制:列表/详情返回的设备版本对应 `ETag`;修改和删除使用 `If-Match`。激活迁移单独使用 `expected_revision`。版本不一致返回 `412 VERSION_CONFLICT` 或 `409 DEVICE_ACTIVATION_CONFLICT`,刷新后再决定,不能强制覆盖。
## 10. 幂等、重试和本地持久化
| 数据 | 本地策略 |
|---|---|
| 平台 Device Secret | 安全区长期保存;轮换时原子替换 |
| 火山 Device Secret | 安全区长期保存;不要每次启动都预配 |
| `session_nonce` | 仅本次通话运行内存 |
| `tool_scope_token` | 仅本次通话运行内存 |
| 最终字幕待上传队列 | 授权允许时加密持久化,成功确认后删除 |
| 增量字幕/原始音频 | 默认不长期保存 |
重试规则:
- `Idempotency-Key` 长度 16—128 字符。
- HTTP 超时或 `retryable=true`:原业务正文 + 原幂等键 + 新 HMAC nonce/signature,指数退避。
- `401`:先检查平台 Device Secret、时钟、请求 URI、正文摘要和 Base64URL 编码,不要盲目轮换密钥。
- `409 IDEMPOTENCY_KEY_REUSED`:同一幂等键配了不同正文;这是客户端错误。
- `HARDWARE_SESSION_PROOF_INVALID`:丢弃当前会话内存数据,重新创建新会话,不尝试其他设备的 nonce。
- `action=stop_session`:优先停止音频和供应商连接,再做结束确认及本地队列收尾。
## 11. EchoEar 固件接入清单
出厂只写入:
- 平台 `device_id`;
- 平台 Device Secret;
- `hardware_id`;
- API Base URL 和服务器信任锚;
- 固件签名信任锚。
首次绑定后设备取得并安全保存:
- 火山 `instance_id`;
- 火山 `product_key`;
- 火山 `device_name`;
- 火山 Device Secret;
- `credential_version`。
绝不写入设备:
- 火山 Product Secret;
- 平台数据库主密钥/KMS 密钥;
- 其他设备的 Device Secret;
- 固定的会话 nonce 或工具 Token;
- 人设、长期记忆和聊天记录的永久明文副本。
会话侧必须做到:
- 每通先请求平台会话配置,再连接火山;
- 人设、近期摘要和长期记忆以本次 `session_config` 为准;
- 动态配置确认前不打开麦克风;
- 设备事件和最终字幕可靠回流平台;
- 平台下发 `stop_session` 时可在一个心跳周期内停止。
## 12. 当前实现边界
- 当前 API 已实现平台 HMAC、激活状态机、火山动态注册、v3 会话控制面、事件回流和最终字幕上传。
- 设备音频是设备直连火山,平台不代理每一帧音频;平台通过设备事件和字幕回流维护会话权威状态。
- Windows/EchoEar 客户端仍需按目标 SDK 完成真实音频 HAL、火山协议、断线重连和安全存储验收。
- `/device/v3/hardware-agent-license:ack` 处理器代码虽曾存在,但没有路由、没有生产契约,也不应由设备调用。
- 完整字段定义以 [`openapi/openapi.yaml`](../openapi/openapi.yaml) 为机器可读基准;本文件是设备研发和联调的实施说明。
## 13. 联调最小验收用例
1. 错误平台签名返回 `401`,日志不泄露设备是否存在。
2. 同 nonce 重放被拒绝;相同业务幂等键配新 nonce 可安全重试。
3. `CLAIMABLE` 设备不能预配,`BOUND` 设备可以预配但不能创建会话。
4. 首次预配成功取得火山一机一密;重复预配不重复注册。
5. Product Secret 在设备固件、NVS、日志和网络响应中均不存在。
6. `ACTIVE` 设备创建 v3 会话并得到正确 Bot ID、人设和授权上下文。
7. 未确认动态配置的 `session_started` 被拒绝。
8. 正常开始、心跳、最终字幕、结束全部回流平台。
9. 平台返回 `stop_session` 后设备关闭连接并补报结束。
10. 火山 License 额度耗尽时预配失败,扩容后使用原幂等请求可恢复;设备无需安装 License 或 ACK。