Files
orbit/docs/03-http-websocket-protocol.md

181 lines
14 KiB
Markdown
Raw 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.
# Orbit HTTP 与 WebSocket 协议草案
> 版本 0.2 · 2026-09-17。HTTP + WS 已确认。本文的具体路径、消息名、字段及状态码是首版设计建议,业务协议尚未实现,需评审后冻结;健康接口的已实现契约见接口手册。
实现更新:HTTP 任务/队列接口已完成 P1;Worker 服务端已进入 P2,实际帧名、字段、回执与边界见 [Worker 契约](api/worker.md)。本文保留早期完整设计,未实现项目不能据此视为已交付。
## 1. 通用契约
- 生产使用 HTTPS / WSS;HTTP 承担任务提交、流程启动与信号、查询、管理,WS 承担 Worker 双向通信与结果订阅。
- 数据采用 JSON;HTTP 契约使用 OpenAPI 描述,WS 消息使用版本化 JSON Schema 与交互时序描述。
- ID 与执行代次等可能超出跨语言整数精度的值使用字符串。时间使用 RFC 3339 UTC 字符串,时长字段注明单位。
- 请求体、消息、批量条数、查询页大小和发送缓冲都必须有限制;具体上限见待定参数表。
- 鉴权后校验应用、环境、队列和操作权限。请求中的命名空间、Worker ID、任务 ID 不能代替授权。
- SDK 与服务端维护兼容矩阵;不兼容的协议或处理器版本明确拒绝,不静默解释为当前版本。
## 2. HTTP 接口草案
| 方法与路径 | 行为 |
| --- | --- |
| `POST /v1/tasks` | 幂等提交一项任务 |
| `POST /v1/tasks:batchSubmit` | 批量逐项提交,每项独立幂等 |
| `GET /v1/tasks/{task_id}` | 查询权威任务状态与结果摘要 |
| `GET /v1/tasks/{task_id}/attempts` | 分页查询执行尝试 |
| `GET /v1/tasks` | 按有索引的字段分页查询,支持提交幂等键精确查找 |
| `POST /v1/tasks/{task_id}:cancel` | 请求取消,记录原因 |
| `POST /v1/tasks/{task_id}:retry` | 普通任务人工重试;流程关联任务须经 Weave 恢复入口 |
| `POST /v1/schedules` | 创建计划,明确漏跑和重叠策略 |
| `PATCH /v1/schedules/{schedule_id}` | 带预期版本修改计划,冲突时拒绝覆盖 |
| `POST /v1/schedules/{schedule_id}:pause` | 暂停计划的新触发 |
| `POST /v1/schedules/{schedule_id}:resume` | 按漏跑策略恢复计划 |
| `GET /v1/schedules/{schedule_id}/occurrences` | 查询计划触发记录 |
| `GET /health/live`、`GET /health/ready` | 进程存活与接单就绪状态,二者独立 |
应用、凭据、队列、配额和审计管理接口沿用同一鉴权、版本与错误规范,其完整路径清单在接口评审时补齐。就绪检查必须反映关键依赖,数据库不可用时不能继续宣称可安全接单。
Weave 的补充接口建议如下,功能范围已确认,具体技术细则以 [流程设计](06-workflow-spec.md) 为准:
| 方法与路径 | 行为 |
| --- | --- |
| `POST /v1/workflow-definitions/{definition_id}/versions` | 研发通过 SDK 发布不可变定义;同版本同内容幂等,不同内容冲突 |
| `POST /v1/workflows` | 幂等启动流程,固定定义版本 |
| `GET /v1/workflows/{workflow_id}` | 查询实例、定义版本、执行状态、暂停标记与等待摘要 |
| `GET /v1/workflows/{workflow_id}/steps` | 分页查询步骤、输入/结果引用、激活、查询轮次、已用预算与关联任务 |
| `POST /v1/workflows/{workflow_id}/signals` | 幂等受理信号,不等于信号已经被消费 |
| `GET /v1/workflows/{workflow_id}/signals/{signal_id}` | 查询信号受理、匹配与消费结果 |
| `POST /v1/workflows/{workflow_id}:pause` | 持久化暂停门控与审计;不代表在途业务已停止 |
| `POST /v1/workflows/{workflow_id}:resume` | 解除暂停并安排受控推进;不重置预算、核查状态或原版本 |
| `POST /v1/workflows/{workflow_id}:cancel` | 记录取消意图,结清关联工作 |
流程列表、事件查询与人工核查/恢复接口待细化;恢复必须带预期状态版本、稳定操作身份、原因与证据,不提供任意修改状态的通用入口。
暂停和继续使用独立管理权限、预期控制版本、幂等操作身份与原因;同一操作重报返回原确认,过期控制版本不能覆盖后来的操作。建议 `200` 确认门控已提交,后续执行进度通过查询观察;门控失效条件和终态操作错误在协议评审时固定。
### 2.1 受理、幂等与错误
- 首次创建任务、流程或信号记录的建议响应为 `201 Created`,表示资源及幂等记录已可靠提交,不表示业务执行或信号消费完成。
- 相同幂等请求返回 `200` 和原资源 ID,标记 `deduplicated: true`;相同键不同语义请求返回 `409`。
- 单项提交使用 `Idempotency-Key`;批量每项使用 `submission_key`,由 SDK 统一映射为提交幂等标识。
- 普通任务提交幂等作用域为命名空间内的任务提交操作;流程启动使用独立操作作用域,信号按命名空间、实例与信号 ID 去重。其他管理写操作按命名空间、操作和资源界定作用域,避免同一个键在不同 API 间意外冲突。
- HTTP `request_id` 仅用于追踪,与业务幂等键分开。
- 请求超时后,使用原幂等键查询或重试;不能通过换一个键来猜测原请求是否失败。
- 语义哈希排除请求追踪、连接身份等传输字段,明确 JSON 规范化、缺省值和数组顺序;不能依赖普通 map 的序列化顺序。算法在协议冻结时确定。
建议统一响应结构:成功包含 `request_id` 和 `data`;失败包含 `request_id` 和 `error`,其中 `error` 含稳定的 `code`、可读 `message` 和受控的 `details`。
| HTTP 状态 | 用途 |
| --- | --- |
| 200 / 201 | 查询或已确认的操作结果 / 新资源可靠创建 |
| 202 | 取消等操作已记录意图,最终行为仍在推进 |
| 400 / 422 | 协议格式错误 / 语义校验失败 |
| 401 / 403 | 未认证 / 无操作权限 |
| 404 | 不存在或按访问策略隐藏的资源 |
| 409 | 幂等内容冲突、资源版本冲突或操作状态冲突 |
| 429 / 503 | 容量限制 / 服务暂不可用,可提供 `Retry-After` |
批量请求顶层校验通过后可返回 `200`,但每项必须给出索引、任务 ID 或错误。顶层成功不表示每项均被受理。限流或断线后的补交仍使用原逐项幂等键。
## 3. Worker 连接
建议入口:`GET /v1/worker/connect`,使用 WebSocket Upgrade,协商子协议 `pulse.worker.v1`。SDK 在握手中携带应用凭据,不把长期密钥放在 URL 查询参数中。
连接生命周期:鉴权 → 协议协商 → 能力登记 → 恢复核对 → 可接单 → 排空 / 断线。
- `worker_instance_id` 标识一个进程实例,重连保留,重启建议新建;可另设可读名称。
- `session_id` 标识本次连接。新连接需有新的会话身份,旧连接消息不得继续修改新会话的执行权。
- 恢复执行必须校验认证身份、执行凭据和数据库中的当前归属,不能仅凭相同 Worker 名称认领。
- Worker 声明队列、处理器兼容版本及容量。平台授权与配额优先于客户端声明。
## 4. 消息信封
以下为示意,时间与数值不是生产默认参数:
```json
{
"protocol_version": "1",
"type": "task.assign",
"message_id": "msg_example",
"session_id": "session_example",
"task_id": "task_example",
"attempt_id": "attempt_example",
"body": {
"handler": "report.generate",
"handler_version": "1",
"payload_version": "1",
"payload": {"report_ref": "report_example"},
"lease_token": "example-only-not-a-real-secret",
"lease_expires_at": "2026-09-17T08:00:30Z"
}
}
```
响应通过 `reply_to` 关联原消息。需要可靠确认的操作使用稳定消息 ID;跨重连重报时保留操作身份,但使用当前会话认证。传输会话变化不改变操作的语义哈希。
执行凭据属于敏感信息,不进入普通日志、管理查询或指标标签。Task / Attempt / Message / Session 四种 ID 分别表达业务工作、执行尝试、通信操作和连接。
## 5. 消息类型与确认
| 消息 | 方向 | 语义 |
| --- | --- | --- |
| `worker.hello` / `worker.welcome` | 双向 | 协商能力、版本、会话与协议限制 |
| `worker.resume` / `worker.resume.result` | 双向 | 核对断线前的执行尝试,逐项给出续接、停止或核查结果 |
| `worker.capacity` | Worker → 平台 | 更新容量声明;带会话内序号,重复上报不得重复增加额度 |
| `task.assign` | 平台 → Worker | 派发已持久化授权的任务与执行凭据 |
| `task.start` | Worker → 平台 | 确认接单并申请开始;建议获平台确认后再调用处理器 |
| `task.renew` | Worker → 平台 | 续期有效执行租约,可批量但逐项确认 |
| `task.complete` | Worker → 平台 | 提交结果或结果引用,等待持久化确认 |
| `task.fail` | Worker → 平台 | 提交错误分类及已知业务结果,平台按冻结策略决定后续行为 |
| `task.cancel` | 平台 → Worker | 传递已保存的取消意图,不代表副作用被撤销 |
| `task.cancelled` | Worker → 平台 | 报告处理器停止及已知效果;未知效果转核查,不自动成为取消终态 |
| `worker.drain` | 双向 | 停止接收新任务,受控完成已有任务 |
| `ack` / `error` | 双向 | 关联明确操作的确认或错误,不能用连接 Pong 替代 |
建议采用两步派发:平台先保存 `LEASED` 并发送任务;Worker 发 `task.start`,平台原子确认有效租约和未被取消后保存 `RUNNING`,再确认执行。消息丢失时按同一尝试重报或核对,不能未经重新授权自行执行。
如关联执行门控已暂停,尚未获开始授权的 `task.start` 不能被确认可执行;已确认开始的操作重报只补取原确认,不授予第二次执行。具体暂挂、释放预占容量和恢复派发的消息细节在协议冻结时补齐。
`RUNNING` 表示平台已授权开始,不足以证明业务侧已经发生某项效果。租约前后派发与取消的竞争见 [可靠性模型](04-reliability-and-data.md)。
### 5.1 完成确认丢失
1. 平台在同一事务内保存任务终态、尝试结果与待交付事件。
2. 提交成功后回复 `ack`。
3. SDK 未收到确认时,重报相同结果或查询任务。
4. 已确认的同一尝试、同一结果再次上报,返回原确认,即使此时原租约已经到期。
5. 不同结果冲突或过期尝试试图写入新结果时拒绝修改,保留可诊断记录。
先验证权限,再识别已完成操作的幂等重报,最后对新操作校验租约,避免将合法的确认补取误判为任务失败。
SDK 可以在进程存活期间缓存未确认消息;不得宣称内存缓存能跨进程崩溃恢复。业务效果已发生但结果尚未回报的情况,依赖业务幂等、业务侧操作记录或结果查询恢复。
## 6. 心跳、重连与背压
- Ping/Pong 用于连接活性;具体任务的续租必须单独经过授权与持久化确认。[WebSocket 标准](https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.2)
- 任务续租失败后,SDK 不能仅因连接仍在线就继续假定执行权有效。
- SDK 根据服务端确认的租约期限,结合请求耗时、安全余量及本地单调时钟管理停止边界;不直接信任客户端墙上时钟与服务器完全一致。
- 重连使用有上限的指数退避与抖动;支持凭据刷新、重新鉴权和恢复核对。
- 断线后允许在最后确认的有效租约内按策略推进;无法及时恢复有效执行权时,停止推进并尽力取消可取消操作。外部已发请求仍需核查。
- 重连可进入任一平台节点;有效旧尝试经原子校验迁移到新会话,失效尝试不得因重连复活。
- 平台为已派发未确认的任务预占容量。容量消息采用序号和明确的覆盖/计算规则,重复消息不能释放或重复发放额度。
- 单连接读写由独立循环管理;设置消息上限、读写期限和有界发送队列,处理器运行不占网络循环。
- 慢消费者停止追加派发;非关键进度更新可合并,关键状态与结果必须保留恢复路径。
- 负载均衡器支持 Upgrade、长连接超时和排空;网络断开不能触发无条件的业务重试。
## 7. 可靠结果订阅
建议独立使用 WSS 入口 `/v1/events/connect` 与子协议 `orbit.events.v1`,区分 Worker 消费权和结果订阅权。具体路径待评审。
事件包含 `event_id`、资源类型、资源 ID、状态版本、事件类型及结果引用,可表达任务与流程事件。平台先持久化事件与交付状态,再发送 `event.deliver`;接收方完成自身的可靠处理后发送 `event.ack`。
未确认事件可重新发送,订阅方按事件 ID 去重;同一资源按状态版本处理迟到信息,不承诺跨资源全局顺序。事件保留到期、订阅失效与补偿查询行为必须公开。
实时控制台展示可使用合并后的进度通知,但业务流程推进不能依赖只有在线时才能收到的消息。
Weave 消费 Pulse 的内部持久化事件,不经此对外订阅入口;其事务与去重要求见 [流程协作](06-workflow-spec.md#5-pulse-与-weave-的事务协作)。
## 8. 版本与协议验证
HTTP `/v1` 与 WS 子协议分别标识兼容边界,业务处理器版本独立。新增可选字段应兼容旧客户端;未知关键消息、状态或枚举不能被当作成功。
协议测试应覆盖断线、重复、迟到、会话切换、局部批量失败与凭据撤销。OpenAPI 与 WS Schema 要进入版本控制和兼容性检查,禁止服务端与 SDK 各自维护不同的状态解释。