Files
orbit/docs/api/tasks.md

192 lines
12 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.
# 任务接口 v1
状态:P1 已实现并通过真实 MySQL 与 SDK 回归。当前提供受理、查询、分页和待执行取消;P2 已提供 Worker 执行与历史查询,P3 已接入 [安全重试](retries.md)。
P2 进展:服务端执行内核、WSS 与独立自动 Worker SDK 已可产生 LEASED、RUNNING、SUCCEEDED、FAILED、NEEDS_REVIEW 等状态,见 [Worker 契约](worker.md);[尝试与证据查询](execution-history.md) 可核对每次执行;重试以冻结策略、明确失败分类及未知效果安全声明为依据。
## 公共约定
业务接口需要 `Authorization: Bearer <token>`;写入需要 `Content-Type: application/json` 和稳定的 `Idempotency-Key`。凭据决定命名空间,请求体不能覆盖它。详见 [鉴权与初始化](authentication.md)。
请求体上限 512 KiB。拒绝未知字段、大小写别名、重复 JSON 键、无效 UTF-8、孤立 Unicode 代理项和超过 32 层的嵌套。除可空时间外,字段不能显式传 null。请求追踪 ID 由服务端生成,与幂等键分开。
响应使用 `{"request_id":"…","data":…}`;X-Request-ID 与正文一致。revision 是十进制字符串;ID 是 32 位小写十六进制字符串。时间使用 RFC 3339,返回 UTC,最多微秒精度。
## POST /v1/tasks
权限:tasks:submit,并有目标队列的访问范围。首次提交返回 201,同键相同内容返回 200。两者均只表示**数据库已受理**,不表示业务处理完成。
| 字段 | 类型 | 必填 / 默认 | 含义与限制 |
| --- | --- | --- | --- |
| queue_id | string | 必填 | 同一命名空间内的队列 ID |
| handler | string | 必填 | 处理器名称,1–128 字符 |
| handler_version | string | 必填 | 固定处理器版本,1–64 字符 |
| payload_version | string | 必填 | 输入契约版本,1–32 字符 |
| payload | JSON | 必填 | 非 null JSON,最大 256 KiB;平台不解释业务字段 |
| business_key | string | 可选 | 最多 191 个 Unicode 字符,无 NUL/换行;用于关联,不提供业务幂等 |
| available_at | time / null | 受理时数据库时间 | 最早可执行时间;必须早于总截止时间,仅允许最多 1 秒的过去时间容差 |
| start_deadline_at | time / null | 无独立限制 | 必须晚于当前时间,不早于 available_at,不晚于总截止时间 |
| task_timeout_ms | uint64 | 0 或省略为 86400000 | 从受理时计算的总时限,最大 30 天(2592000000 毫秒) |
| recovery_safety | string | RECONCILE_ONLY | 也可为 IDEMPOTENT;必须基于处理器真实的幂等能力声明 |
| retry | object | 见下表 | 受理时冻结策略;包含首次领取的有限尝试、退避及每次时限,见 [安全重试](retries.md) |
处理器及版本名称以 ASCII 字母或数字开头,后续只能包含字母、数字、`_ . : -`。时间年份为 2000–9998。
| retry 字段 | 默认与限制 |
| --- | --- |
| max_attempts | 0 或省略为 1;范围 1–100,包含首次尝试 |
| attempt_timeout_ms | 0 或省略为 30000;必须不超过任务总时限 |
| initial_delay_ms | 多次尝试时 0 或省略为 1000;必须为正且不超过 max_delay_ms;单次尝试默认 0 |
| max_delay_ms | 多次尝试时 0 或省略为 30000;单次尝试默认 0;最大 9223372036854,整体仍受总截止时间限制 |
这些值是当前接口保护边界,不代表已经验证相应吞吐或并发容量。
业务 JSON 以经过校验的文本持久化,保留大整数和小数的精确数字内容。不要在接入方先转成 float64;Go 使用 json.RawMessage、结构体中的精确类型或业务约定的字符串。单个数字最长 256 字符,显式指数和去除尾零后的指数均需在 -308 至 308 内,数值不能超出有限 double 的幅度;这只是可接受范围检查,摘要不使用浮点近似值。
请求示例,队列 ID 替换为初始化结果:
```sh
curl -i "$ORBIT_URL/v1/tasks" \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: conversion-operation-001' \
--data '{
"queue_id":"11111111111111111111111111111111",
"handler":"file.convert",
"handler_version":"v1",
"payload_version":"v1",
"payload":{"source_ref":"objects/input.txt","format":"pdf","sequence":9007199254740993},
"business_key":"conversion-001"
}'
```
首次受理的完整响应示例,ID 和时间为示意值:
```json
{
"request_id":"44444444444444444444444444444444",
"data":{
"deduplicated":false,
"task":{
"id":"22222222222222222222222222222222",
"namespace_id":"33333333333333333333333333333333",
"queue_id":"11111111111111111111111111111111",
"state":"WAITING",
"revision":"1",
"handler":"file.convert",
"handler_version":"v1",
"payload_version":"v1",
"payload":{"source_ref":"objects/input.txt","format":"pdf","sequence":9007199254740993},
"business_key":"conversion-001",
"available_at":"2026-09-17T00:00:00Z",
"deadline_at":"2026-09-18T00:00:00Z",
"recovery_safety":"RECONCILE_ONLY",
"retry":{"max_attempts":1,"initial_delay_ms":0,"max_delay_ms":0,"attempt_timeout_ms":30000},
"attempt_count":0,
"created_at":"2026-09-17T00:00:00Z",
"updated_at":"2026-09-17T00:00:00Z"
}
}
}
```
可选响应字段:start_deadline_at、result、cancel_requested_at、completed_at、business_key,未设置时省略。effect_uncertain=true 表示存在尚未核实的授权执行效果,false 时省略;详情、摘要和 Worker 派发均可返回。等待重试时 result/completed_at 省略,历史结果在尝试中保留。终态有 completed_at。状态集合为 WAITING / LEASED / RUNNING / NEEDS_REVIEW / SUCCEEDED / FAILED / CANCELLED;P1 仅主动产生 WAITING 和 CANCELLED。
### 幂等与失败处理
- 键为 1–128 个可打印 ASCII 字符,不含空格;作用域为命名空间 + 操作类型,取消额外包含任务 ID。不同凭据在同一命名空间共享该身份,重复请求仍需通过当前权限校验。
- 固定默认值、规范化 UTC 时间、排序 JSON 对象键、精确规范化十进制数字,再计算版本化 SHA-256 摘要。1 与 1.0 等价;数组顺序有意义。内部算法名 orbit-json-v1,不宣称采用 RFC 8785。
- 同键不同内容返回 409 IDEMPOTENCY_CONFLICT;不同键即使 business_key 相同,也可以产生两个任务。
- 同一事务保存任务、受理事件、提交者审计、幂等身份与原始回执;验证或写入失败整体回滚。提交审计记录凭据与处理器/队列,不复制业务参数。
- 同键重报返回**首次操作的回执快照**;任务之后取消也不会改写该回执。获取最新状态调用 GET。
- 网络中断、客户端超时或 503 时,不能认定提交未发生;保持原键和原内容查询或重报。SDK 不自动换键或自动重试写操作。
- 幂等记录设置 90 天保留截止时间;当前尚无清理任务,存在的记录持续生效。后续清理不得缩短已承诺窗口。
SDK:`Client.SubmitTask(ctx, key, request)`。
## GET /v1/tasks/{task_id}
权限:tasks:read 和任务所在队列范围。200 的 data 直接为上方 task 对象,返回当前状态和完整参数/已保存结果。不存在、跨命名空间或不在该凭据队列范围时返回 404。
```sh
curl "$ORBIT_URL/v1/tasks/$TASK_ID" -H "Authorization: Bearer $ORBIT_TOKEN"
```
SDK:`Client.GetTask(ctx, taskID)`。
## GET /v1/tasks
权限:tasks:read。查询参数均只能出现一次:
| 参数 | 要求 |
| --- | --- |
| queue_id | 必填,有权限的队列 ID |
| state | 可选,取上述状态之一 |
| limit | 可选,默认 20;1–100 |
| cursor | 可选,上一页返回值;必须保持 queue_id/state 不变,最长 1024 字符 |
按 (created_at DESC, id DESC) 游标分页,不使用大 offset。不承诺跨页快照:读取过程中状态可能变化。列表省略 payload/result/retry 等大字段,返回摘要。
```sh
curl "$ORBIT_URL/v1/tasks?queue_id=$ORBIT_QUEUE_ID&state=WAITING&limit=20" \
-H "Authorization: Bearer $ORBIT_TOKEN"
```
```json
{
"request_id":"44444444444444444444444444444444",
"data":{
"items":[{
"id":"22222222222222222222222222222222",
"queue_id":"11111111111111111111111111111111",
"state":"WAITING","revision":"1",
"handler":"file.convert","handler_version":"v1","business_key":"conversion-001",
"available_at":"2026-09-17T00:00:00Z","deadline_at":"2026-09-18T00:00:00Z",
"attempt_count":0,"created_at":"2026-09-17T00:00:00Z","updated_at":"2026-09-17T00:00:00Z"
}]
}
}
```
还有下一页时返回 data.next_cursor。空结果为 items: []。SDK:`Client.ListTasks(ctx, request)`。
## POST /v1/tasks/{task_id}:cancel
权限:tasks:cancel 和队列范围。支持独立任务和计划生成任务的 WAITING、LEASED、RUNNING、NEEDS_REVIEW;流程所属任务仍由后续流程控制管理。运行中只记录取消意图,返回 202,最终状态等待有效结果或超时核查。完整规则及 SDK 示例见 [取消契约](cancellation.md)。
```sh
curl "$ORBIT_URL/v1/tasks/$TASK_ID:cancel" \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: conversion-operation-001-cancel' \
--data '{"expected_revision":"1","reason":"source object was replaced"}'
```
expected_revision 必填,为非零 uint64 的十进制字符串;reason 必填且不能全为空白,最多 1024 个 Unicode 字符。服务端以版本和状态条件更新,只有一个竞争请求能成功。
200/202 的 data 为 `{"task":<本次取消的任务快照>,"deduplicated":false}`。上方等待态示例会变为 state=CANCELLED、revision="2",写入 cancel_requested_at/completed_at,并同事务追加取消事件及审计。首次取消之后使用相同键、原因和**原 expected_revision**重报,返回原取消回执且 deduplicated=true;原回执为 RUNNING 时仍返回 202,最新状态另行 GetTask。换键重报已有取消意图或终态任务返回 409。WAITING/LEASED 若承接历史未知效果,取消转 NEEDS_REVIEW,保留 effect_uncertain=true、completed_at 为空;已在核查的任务保持核查。
SDK:`Client.CancelTask(ctx, taskID, key, request)`。此操作不承诺回滚任何已发生的外部业务效果。
## 错误与回归
| HTTP / code | 处理 |
| --- | --- |
| 400 INVALID_ARGUMENT | 修正字段、时间、游标或幂等键 |
| 401 UNAUTHENTICATED | 检查凭据、到期/撤销和应用/命名空间状态 |
| 403 PERMISSION_DENIED | 检查动作或提交/列表所需队列范围 |
| 404 NOT_FOUND | 资源不存在或单资源读取范围不匹配 |
| 409 IDEMPOTENCY_CONFLICT | 原键已对应其他内容,不应盲目换键重试 |
| 409 STATE_CONFLICT | 当前状态或版本不允许操作,先查询 |
| 413 BODY_TOO_LARGE | 减小请求或改为传递业务对象引用 |
| 415 UNSUPPORTED_MEDIA_TYPE | 使用 application/json |
| 429 APPLICATION_FULL | 应用下所有命名空间共享额度已满;按 Retry-After 退避并保留原身份,详见 [应用配额](application-resources.md) |
| 429 QUEUE_FULL | 本次未受理,按 Retry-After 有限退避并保留原身份;已受理的原回执不受当前满额限制 |
| 503 UNAVAILABLE | 结果可能未知;保留原幂等身份,有限退避后核对 |
```json
{"request_id":"44444444444444444444444444444444","error":{"code":"IDEMPOTENCY_CONFLICT","message":"idempotency key was used with different content"}}
```
有限自动重试已实现,具体分类、退避、回执和升级边界见 [安全重试](retries.md)。[运行中取消](cancellation.md) 已实现;批量受理和人工重试尚未实现。[执行历史](execution-history.md) 已提供尝试与证据的列表和详情。完整可运行程序见 [SDK 任务示例](../../sdk/go/examples/tasks/main.go),已验证异常场景见 [P1 回归报告](../reports/phase-1.md) 及 [取消回归](../reports/phase-3-cancellation.md)。SDK 忽略未知响应字段;更改已有字段语义需升级契约和回归。