feat: add scoped task source tracing and SDK examples

This commit is contained in:
2026-09-17 22:40:42 +08:00
parent 7641423f9d
commit 2b8213a4f7
26 changed files with 760 additions and 9 deletions

View File

@@ -9,6 +9,7 @@
| `GET /health/live` | 已实现 | [健康检查](health.md) |
| `GET /health/ready` | 已实现,验证数据库、迁移记录及零外键要求 | [健康检查](health.md) |
| `/v1/tasks` 提交、查询、分页与待执行取消 | 已实现,P3扩展业务键与创建时间筛选 | [任务接口](tasks.md) / [查询契约](task-queries.md) |
| `GET /v1/tasks/{task_id}/source` | P3已实现普通、人工重试与计划固定版本来源 | [来源查询](task-source.md) |
| `POST /v1/tasks:batch` | P3 已实现逐项受理、独立幂等、部分结果与补交 | [批量任务](batch-tasks.md) |
| `/v1/tasks/{task_id}:review` / `:retry` 与 `/operations` | P3 已实现带证据核查、关联新任务与操作审计 | [人工恢复](task-recovery.md) |
| `/v1/tasks/{task_id}/attempts` 与尝试详情 | P2 已实现 | [执行历史](execution-history.md) |

View File

@@ -165,6 +165,8 @@ ListSchedules 需要 queue_id;计划列表和 occurrences 均可用 limit(
FIRED 表示已创建单点任务;COALESCED 表示范围已合并到 task_id;SKIPPED 没有 task_id。它们都不是业务执行结果,继续 GetTask 查询,再通过 [尝试与证据接口](execution-history.md) 核对执行。
从任务反查计划使用[GetTaskSource](task-source.md),返回来源计划、触发记录、当时固定版本和合并边界。该只读引用需要tasks:read,完整计划/版本内容仍需schedules:read;发布新版本不改变老任务来源。
## 6. SDK 与完整示例
所有方法首参数为 context.Context,写操作需要稳定 key。ListSchedules 的参数为 `(ctx, queueID, SchedulePageRequest)`;ListScheduleOccurrences 为 `(ctx, scheduleID, SchedulePageRequest)`。
@@ -185,7 +187,9 @@ ORBIT_URL=http://127.0.0.1:8080 \
前置数据库与目录步骤见 [SDK 接入](../sdk/README.md)。源码:[完整调度示例](../../sdk/go/examples/schedules/main.go)。示例创建暂停的每秒计划,恢复后让自动 Worker 返回对象输入快照,查询两次完成,再暂停并排空。计划本身有十秒结束边界,任务与历史保留;重复运行生成独立身份,不调用外部业务系统。
```text
source_kind=SCHEDULE fixed_version=1
schedule=<计划ID> occurrence=<触发ID> task=<任务ID> version=1 state=SUCCEEDED
source_kind=SCHEDULE fixed_version=1
schedule=<计划ID> occurrence=<触发ID> task=<任务ID> version=1 state=SUCCEEDED
paused=true completed_tasks=2 current_version=1
worker_drained=true running=0

131
docs/api/task-source.md Normal file
View File

@@ -0,0 +1,131 @@
# 任务直接来源查询
状态:P3已实现服务端、独立SDK及普通任务、计划、人工恢复示例。接口只读,使用已有模型和索引,不增加数据库外键或迁移。验证证据见[回归报告](../reports/phase-3-task-source.md)。
## 1. 请求与权限
`GET /v1/tasks/{task_id}/source`
请求头:`Authorization: Bearer <token>`。不需要请求体、Content-Type或Idempotency-Key,不接受任何查询参数。task_id为32位小写十六进制ID;认证决定命名空间,URL不能覆盖它。
权限为`tasks:read`及任务所属队列范围。每次请求验证当前凭据;不存在、跨命名空间或不在队列范围的任务均返回404。只获得来源引用,不因此获得计划定义、模板、管理或执行权限;读取完整计划及固定版本仍需`schedules:read`,见[计划手册](schedules.md)。
```sh
curl "$ORBIT_URL/v1/tasks/$TASK_ID/source" \
-H "Authorization: Bearer $ORBIT_TOKEN"
```
## 2. 响应字段
HTTP 200,使用`request_id`和`data`信封;X-Request-ID与正文一致。下表字段均位于data。
| 字段 | 类型 / 必填 | 含义 |
| --- | --- | --- |
| task_id | string / 是 | 已授权查询的任务ID |
| kind | string / 是 | DIRECT、MANUAL_RETRY或SCHEDULE |
| retry_of | string / 仅MANUAL_RETRY | 同命名空间、同队列的直接原任务ID |
| schedule | object / 仅SCHEDULE | 触发该任务的固定关联,字段如下 |
| schedule.schedule_id | string / 是 | 来源计划ID |
| schedule.schedule_version | 十进制字符串 / 是 | 实际触发时的非零uint64版本,发布新版本不改写它 |
| schedule.occurrence_id | string / 是 | 唯一关联该任务的触发记录ID |
| schedule.scheduled_at | UTC RFC3339 / 是 | 原计划时间,最多微秒精度,与实际受理/执行时间分开 |
| schedule.decision | string / 是 | FIRED单点触发或COALESCED合并触发 |
| schedule.covered_until | UTC RFC3339 / 仅COALESCED | 合并范围的排除上界,严格晚于scheduled_at |
DIRECT表示普通任务受理,包括单项和批量受理;任务内部的自动安全重试仍属于同一个任务,不改变来源。MANUAL_RETRY表示[人工重试](task-recovery.md)创建的新任务,只返回直接父任务,不递归遍历整条重试链。父任务继续保留原历史。
SCHEDULE中的版本来自触发记录,不能用计划最新版本替换。COALESCED对应区间`[scheduled_at, covered_until)`;合并范围内由计划定义确定原始时间点,不承诺每个自然时间单位都有一次触发。SKIPPED未创建任务,因此不会成为任务来源。
普通受理的完整响应:
```json
{
"request_id":"44444444444444444444444444444444",
"data":{
"task_id":"11111111111111111111111111111111",
"kind":"DIRECT"
}
}
```
人工重试的完整响应:
```json
{
"request_id":"44444444444444444444444444444444",
"data":{
"task_id":"11111111111111111111111111111111",
"kind":"MANUAL_RETRY",
"retry_of":"22222222222222222222222222222222"
}
}
```
计划合并触发的完整响应:
```json
{
"request_id":"44444444444444444444444444444444",
"data":{
"task_id":"11111111111111111111111111111111",
"kind":"SCHEDULE",
"schedule":{
"schedule_id":"22222222222222222222222222222222",
"schedule_version":"1",
"occurrence_id":"33333333333333333333333333333333",
"scheduled_at":"2026-09-17T00:00:00Z",
"decision":"COALESCED",
"covered_until":"2026-09-17T00:03:00.000001Z"
}
}
}
```
单点触发返回decision=FIRED并省略covered_until。所有不适用的可选字段省略,不用null占位。来源不包含任务payload/result、计划模板、策略、凭据或执行租约秘密。任务当前状态另用GetTask读取。
## 3. 关联校验与错误
平台采用无外键设计,本接口在认证事务内显式核对命名空间、任务、队列、计划、不可变版本与触发记录。人工重试父任务须为同队列中已失败/取消的独立任务。无所有者但存在触发记录、悬空父任务、缺失版本、错误队列、未知所有者或当前未支持的门控均拒绝,不能把关联损坏降级成DIRECT。
| HTTP / code | 处理 |
| --- | --- |
| 400 INVALID_ARGUMENT | 检查task_id;删除不受支持、重复或格式错误的查询参数 |
| 401 UNAUTHENTICATED | 检查凭据、过期、撤销及应用/命名空间状态 |
| 403 PERMISSION_DENIED | 需要tasks:read动作权限 |
| 404 NOT_FOUND | 任务不存在或不在当前命名空间/队列范围;旧版本节点也可能没有本路由 |
| 503 UNAVAILABLE | 数据库或来源关联不能可靠确认;不返回部分来源,不推测DIRECT |
读取不改变任务状态、版本、额度、尝试或审计。网络错误可以在有限预算内重新读取;SDK不自动重试。持续503应排查数据关联和服务日志,不能通过删除来源字段“修复”,也不能根据来源查询失败重新提交任务。
## 4. SDK调用与场景
```go
func (c *Client) GetTaskSource(ctx context.Context, taskID string) (TaskSource, error)
```
下方为已有客户端与taskID的调用片段;完整初始化、错误处理和可运行程序见后面的示例。
```go
source, err := client.GetTaskSource(ctx, taskID)
if err != nil { return err }
switch source.Kind {
case "DIRECT":
fmt.Println("普通受理", source.TaskID)
case "MANUAL_RETRY":
fmt.Println("直接原任务", *source.RetryOf)
case "SCHEDULE":
fmt.Println("来源计划", source.Schedule.ScheduleID,
"固定版本", source.Schedule.ScheduleVersion,
"触发记录", source.Schedule.OccurrenceID)
}
```
SDK的ScheduleVersion为uint64,JSON仍为字符串;时间使用time.Time。SDK验证响应task_id一致性、来源种类与字段组合、ID、版本及时间范围;未知kind或矛盾数据返回错误,不返回可误用的部分来源。协议新增来源种类时,接入方需升级SDK再使用。
| 场景 | 完整源码 | 运行说明与新增输出 |
| --- | --- | --- |
| 单项受理、取消后读取来源 | [tasks](../../sdk/go/examples/tasks/main.go) | [SDK初始化](../sdk/README.md),source_kind=DIRECT |
| 按计划找到触发任务,再反查来源 | [schedules](../../sdk/go/examples/schedules/main.go) | [计划场景](schedules.md#6-sdk-与完整示例),source_kind=SCHEDULE、fixed_version=1 |
| 人工核查后创建关联新任务 | [recovery](../../sdk/go/examples/recovery/main.go) | [恢复场景](../examples/recovery.md),source_kind=MANUAL_RETRY、parent=原任务ID |
上述示例均运行在隔离回归中;新发布计划版本后老任务仍指向旧版本,由真实数据库与SDK集成测试验证。Weave流程来源随P4–P5实现,目前没有WORKFLOW响应。此接口为新增只读路由,不更改Task/TaskSummary或Worker消息结构,不增加权限配置;新客户端调用旧节点的404不能理解为DIRECT。

View File

@@ -116,6 +116,8 @@ curl "$ORBIT_URL/v1/tasks/$TASK_ID" -H "Authorization: Bearer $ORBIT_TOKEN"
SDK:`Client.GetTask(ctx, taskID)`。
任务直接来源独立通过`GET /v1/tasks/{task_id}/source`读取,SDK为`GetTaskSource(ctx, taskID)`;可定位普通受理、人工重试父任务、来源计划/触发记录/固定版本。使用相同的tasks:read和队列范围,详情见[来源查询](task-source.md)。来源未嵌入Task或列表摘要,读取完整计划仍需计划权限。
## GET /v1/tasks
权限:tasks:read。查询参数均只能出现一次: