Files
orbit/docs/api/task-source.md

10 KiB
Raw Permalink Blame History

任务直接来源查询

状态:已实现普通任务、人工重试、计划和流程任务来源的服务端、独立SDK及示例。接口只读,使用已有模型和索引,不增加数据库外键或迁移。证据见P3报告和流程来源报告。

1. 请求与权限

GET /v1/tasks/{task_id}/source

请求头:Authorization: Bearer <token>。不需要请求体、Content-Type或Idempotency-Key,不接受任何查询参数。task_id为32位小写十六进制ID;认证决定命名空间,URL不能覆盖它。

权限为tasks:read及任务所属队列范围。每次请求验证当前凭据;不存在、跨命名空间或不在队列范围的任务均返回404。只获得来源引用,不因此获得计划/流程定义、其他步骤、管理或执行权限。读取完整计划及固定版本仍需schedules:read;读取流程需workflows:read及全部固定队列范围,读取流程定义需workflows:definitions:read。只拥有一个队列任务权限时,仍能读取该任务的有限流程来源。

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或WORKFLOW
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
workflow object / 仅WORKFLOW 此任务创建时的流程来源,字段见第5节

DIRECT表示普通任务受理,包括单项和批量受理;任务内部的自动安全重试仍属于同一个任务,不改变来源。MANUAL_RETRY表示人工重试创建的新任务,只返回直接父任务,不递归遍历整条重试链。父任务继续保留原历史。

SCHEDULE中的版本来自触发记录,不能用计划最新版本替换。COALESCED对应区间[scheduled_at, covered_until);合并范围内由计划定义确定原始时间点,不承诺每个自然时间单位都有一次触发。SKIPPED未创建任务,因此不会成为任务来源。

普通受理的完整响应:

{
  "request_id":"44444444444444444444444444444444",
  "data":{
    "task_id":"11111111111111111111111111111111",
    "kind":"DIRECT"
  }
}

人工重试的完整响应:

{
  "request_id":"44444444444444444444444444444444",
  "data":{
    "task_id":"11111111111111111111111111111111",
    "kind":"MANUAL_RETRY",
    "retry_of":"22222222222222222222222222222222"
  }
}

计划合并触发的完整响应:

{
  "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. 关联校验与错误

平台采用无外键设计,本接口在认证事务内显式核对命名空间、任务、队列、计划或流程、不可变版本和创建记录。人工重试父任务须为同队列中已失败/取消的独立任务。流程来源核验实例门控、版本摘要与冻结策略、固定步骤处理器契约、任务与激活或轮次的双向关系;恢复父任务还须属于同一步骤的前一激活,POLL须为同一业务轮。

无所有者但存在触发/激活/轮次反向记录、同时关联不同来源种类、悬空父任务、缺失版本、错误队列或未知所有者均拒绝,不能把关联损坏降级成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调用与场景

func (c *Client) GetTaskSource(ctx context.Context, taskID string) (TaskSource, error)

下方为已有客户端与taskID的调用片段;完整初始化、错误处理和可运行程序见后面的示例。

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)
case "WORKFLOW":
    fmt.Println("所属流程", source.Workflow.WorkflowID,
        "步骤", source.Workflow.StepKey,
        "激活代次", source.Workflow.ActivationSequence,
        "业务查询轮次", source.Workflow.PollRound)
}

SDK的ScheduleVersion为uint64,JSON仍为字符串;时间使用time.Time。SDK验证响应task_id一致性、来源种类与字段组合、ID、版本及时间范围;未知kind或矛盾数据返回错误,不返回可误用的部分来源。协议新增来源种类时,接入方需升级SDK再使用。

场景 完整源码 运行说明与新增输出
单项受理、取消后读取来源 tasks SDK初始化,source_kind=DIRECT
按计划找到触发任务,再反查来源 schedules 计划场景,source_kind=SCHEDULE、fixed_version=1
人工核查后创建关联新任务 recovery 恢复场景,source_kind=MANUAL_RETRY、parent=原任务ID
并行流程完成后核对每项任务来源 workflow 流程场景,workflow_sources_verified=3、fixed_version=v1

上述示例均运行在隔离回归中;新发布计划/流程版本后老任务仍指向原版本,人工恢复后旧激活仍可追溯。WORKFLOW是新增来源种类,旧SDK可能按未知种类返回错误;应升级SDK,不能降级为DIRECT。不更改Task/TaskSummary或Worker消息结构,不增加权限配置;新客户端调用旧节点的404/503同样不能理解为DIRECT。

5. 流程固定来源

以下字段位于data.workflow,只适用于TASK和POLL产生的业务任务:

字段 类型 / 必填 含义
workflow_id string / 是 所属流程实例
definition_id string / 是 稳定定义身份
definition_version_id string / 是 实例固定的版本记录身份
version string / 是 固定发布版本名,不解析最新版本
step_id / step_key string / 是 逻辑步骤身份及固定名称
step_kind string / 是 TASK或POLL
activation_id string / 是 创建此任务的历史激活身份
activation_sequence uint32 / 是 步骤激活代次,从1开始;并非当前激活代次
poll_round uint32 / 仅POLL 首次查询开始累计的业务轮编号;人工恢复同轮不重置
poll_round_id string / 仅POLL 唯一关联该任务的轮次记录身份
retry_of string / 恢复任务可有 同步骤、前一激活的直接旧任务;POLL还要求同一业务轮

TASK第二代及之后必有workflow.retry_of;POLL只在恢复激活的首轮任务上带此关联,后续正常业务轮没有retry_of。顶层retry_of仍只用于MANUAL_RETRY;流程恢复任务的kind一直是WORKFLOW。自动尝试重试不创建新任务,也不改变来源。

第二代激活重查原第2业务轮的完整响应:

{
  "request_id":"44444444444444444444444444444444",
  "data":{
    "task_id":"11111111111111111111111111111111",
    "kind":"WORKFLOW",
    "workflow":{
      "workflow_id":"22222222222222222222222222222222",
      "definition_id":"33333333333333333333333333333333",
      "definition_version_id":"55555555555555555555555555555555",
      "version":"v1",
      "step_id":"66666666666666666666666666666666",
      "step_key":"query","step_kind":"POLL",
      "activation_id":"77777777777777777777777777777777",
      "activation_sequence":2,
      "poll_round":2,
      "poll_round_id":"88888888888888888888888888888888",
      "retry_of":"99999999999999999999999999999999"
    }
  }
}

流程暂停、结束、发布新版本或后续人工恢复,都不会把旧任务的来源改指向新激活。要取得当前步骤需另调GetWorkflowStep,不要用来源中的历史代次向当前等待发送信号。完整激活/轮次历史分页仍按开发计划推进,本接口一次只查一个任务的直接来源。