Device 离线投递与 Mailbox
Device Mailbox 让调用方在设备离线、重连或暂时不可路由时,把一次设备命令保存为 durable operation;设备恢复后主动拉取并执行。它适合构建、同步、采集等可以异步完成的结构化任务。
Mailbox 不是消息推送、FIFO 队列或 WebSocket 的替代品:网关不会通过 APNs/FCM 唤醒设备,也不会把所有实时调用失败自动改成入队。命令作者先声明交付能力,调用方再为每次调用显式选择策略。
两层 delivery 不能混用
Section titled “两层 delivery 不能混用”~help 中每条设备命令的 delivery 是能力声明:
命令 delivery |
含义 |
|---|---|
realtime |
只允许当前 WebSocket 实时调用;也是缺省值 |
mailbox |
只允许持久化入队 |
both |
同时允许实时与 Mailbox |
调用时的 --delivery 是本次调用策略:
| 调用策略 | 行为 |
|---|---|
realtime |
只尝试实时调用;也是缺省值 |
mailbox |
不尝试实时调用,直接创建 operation |
fallback |
对 both 命令先尝试实时调用;只有网关能确定“尚未派发”时才入队,避免重复执行副作用 |
fallback 不是“遇到任意 5xx 就再发一次”。如果调用可能已经发送但结果未知,网关会返回不可盲目重试的错误,不会再创建 operation。
1. 声明可离线投递的结构化命令
Section titled “1. 声明可离线投递的结构化命令”CLI 的 Shell 与文件入口仍是实时能力。要使用 Mailbox,应通过 strict JSON command profile 暴露固定 executable 与 argv,并在命令上声明 mailbox 或 both:
{ "version": 1, "path": "ops", "description": "构建机维护任务", "commands": [ { "name": "sync-repository", "description": "同步一个已授权仓库", "executable": "/usr/local/bin/sync-repository", "argv": [ { "input": "repository", "flag": "--repository", "required": true } ], "effect": "write", "delivery": "both" } ]}以前台进程运行:
tb connect \ --device-id build-01 \ --path device/build-01 \ --no-shell \ --command-profile ./device-ops.jsonLinux 上也可以把同一 profile 交给 tb daemon install。CLI/daemon 只在暴露面确实包含 mailbox 或 both 命令时启用 Mailbox processor;首次连接和每次重连进入 ready 后各 drain 一次,不启动固定轮询 timer。
连接后先确认运行时契约:
tb help device/build-01/ops/sync-repository --json检查返回命令的完整 path、arguments schema 和 delivery,不要只凭本页示例拼接路径。
2. 调用并取得 operation
Section titled “2. 调用并取得 operation”设备在线时优先实时执行、明确未派发时再入队:
tb call device/build-01/ops/sync-repository \ --arg repository=acme/app \ --delivery fallback \ --ttl 900 \ --idempotency-key sync-acme-app-20260901直接入队,不尝试实时调用:
tb call device/build-01/ops/sync-repository \ --arg repository=acme/app \ --delivery mailbox \ --ttl 900 \ --idempotency-key sync-acme-app-20260901实际入队返回 HTTP 202;CLI 人类输出形如 queued dop_... (queued)。--ttl 是 operation 的总有效期,必须是正整数秒。--idempotency-key 是同一 caller owner 范围内的创建重试键:同一业务动作重试时复用它,不要为每次网络重试随机生成新值。
如果命令只声明 mailbox,选择 realtime 会被拒绝;如果命令只声明 realtime,选择 mailbox 或 fallback 也会被拒绝。
3. 查询、筛选与取消
Section titled “3. 查询、筛选与取消”创建者 owner 的其他 SK,或对完整目标路径有 admin 的身份,可以管理 operation:
tb device op ls build-01tb device op ls build-01 --state queued --state claimedtb device op get build-01 <operation-id>tb device op cancel build-01 <operation-id>常见状态:
| 状态 | 含义 |
|---|---|
queued |
已持久化,尚未由设备 claim |
claimed |
设备持有短 lease,可能正在执行 |
succeeded |
设备返回成功结果 |
rejected |
当前设备已不再暴露该命令,或 delivery 契约不再允许 |
failed |
handler 返回失败 |
result_unknown |
执行可能已经开始,但设备重启等原因让终态无法恢复 |
cancelled |
在 queued 阶段取消,handler 未由该 operation 启动 |
expired |
超过总 TTL;若 executionMayHaveOccurred 为真,可能执行过 |
取消 queued operation 可以保证它尚未开始。取消 claimed operation 只是写入协作式取消请求;设备 handler 是否已经产生副作用仍需由业务幂等与补偿机制处理。
4. 离线发现与 Dashboard
Section titled “4. 离线发现与 Dashboard”Presence 只说明实时 WebSocket 当前是否可路由,不决定 Mailbox 能力是否可见。当前身份有 read 时,离线设备及其命令仍保留在 ~tree 和 Dashboard 工作树中;命令面会根据 delivery 提供 realtime、mailbox 或 fallback 选择。
Dashboard 的 Device 页面还提供 Mailbox operation 列表、详情与取消入口。它与 tb device op 使用同一公开 HTTP 管理面,不会绕过 owner/admin 权限。
5. 持久化、安全与恢复边界
Section titled “5. 持久化、安全与恢复边界”- 标准 Node 把 operation 保存在 PostgreSQL 的 MailboxRepository,事务保护配额、幂等与状态迁移。自定义宿主必须显式注入领域仓库,普通 KV CAS 不是替代品。
- arguments 与终态 result/error 使用从部署 encryption keyring 按独立域派生的 Mailbox 专用 AES-256-GCM key 静态加密。网关在 enqueue、claim、get、complete 时仍持有明文,因此这不是端到端加密。
- 密文携带 keyId;新写使用 active key,旧根用于读取存量。通过密钥维护执行重加密与退役,不直接替换或删除根。
- 单次 arguments 与终态结果各有 256 KiB JSON 上限。Mailbox 不承诺 FIFO;依赖顺序的业务应在命令自身建模。
- operation 绑定创建时的设备凭证身份。禁用/吊销该 Device SK 会立即阻止继续 claim/complete;新 keyId 不会自动接管旧 operation。
标准 Node / Docker / Helm 使用同一 PG 领域仓库,沿已应用的清理调度收敛过期记录。备份应同时覆盖 PG、bootstrap keyring 与设备 installation journal。
SDK 宿主需要显式接线
Section titled “SDK 宿主需要显式接线”@tool-bridge/sdk/device 提供 createDeviceMailboxProcessor,但它是 pull-only primitive:不会自行启动 timer、监听 WebSocket ready 或处理进程退出。宿主必须提供 installation-local durable journal,在初次 ready 与每次重连 ready 后调用 drain(),并在停止或 suspend 时 abort 当前 drain。
journal 的 put() 必须在 resolve 前真正持久化。进程在 handler 开始后崩溃时,processor 会保守收敛为 result_unknown,而不是再次执行可能有副作用的 handler。完整 SDK 边界见嵌入现有应用。
-
~help中只有明确设计为异步安全的命令声明mailbox或both; - 设备离线时,受限调用身份仍能发现该命令并以
mailbox入队; - 设备恢复到
ready后 operation 从queued收敛到可解释终态; -
fallback在实时成功时不创建第二条 operation; - caller 只能看到自己的 operation,管理员接管需要完整目标路径的
admin; -
queued与claimed的取消语义、result_unknown与可能执行过的expired已进入业务重试策略; - PostgreSQL、设备 installation journal 与受保护 keyring 都在备份/恢复与轮换计划内。
- 设备身份、Shell/文件白名单与 daemon:阅读Device 反向连接;
- CLI 输出、JSON 模式与脚本化:阅读使用 tb CLI;
- 请求 body、响应头和 operation 路由:阅读运行时 HTTP 参考;
- 上线前逐项验证:生产上线检查清单。