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

12 KiB
Raw Blame History

人工核查、人工重试与任务操作审计

状态:P3 已实现服务端、Go SDK 和可运行场景。与自动安全重试不同,人工操作需要专门权限、原因、证据引用和当前版本。验证范围见阶段报告。

1. 公共规则

  • HTTPS Authorization: Bearer <token>,写入带 Content-Type: application/json 与稳定 Idempotency-Key;查询不带幂等键。
  • 权限分别是 tasks:review、tasks:retry、tasks:audit:read,均校验任务当前命名空间和队列范围。默认提交及 Worker 凭据不包含它们;开发初始化显式 --recovery-access 才添加。真实生产应分离执行与管理身份。
  • ID 为32位小写十六进制;expected_revision 是非零十进制字符串。正文最多512 KiB,拒绝未知字段、重复键、大小写别名及非允许的null。
  • 写操作幂等作用域:命名空间 + task.review / task.retry + 原任务ID + key。同键不同内容409;当前鉴权通过后优先返回原回执,不因任务已终结或额度已满重新执行。幂等保留约定与任务受理相同。
  • 网络/提交结果不明时持久保留原键、原版本和完整正文核对;不要换键。回执是操作当时快照,最新状态用 GetTask。SDK不自动生成身份或自动重试。
  • reason:非空白UTF-8,1–1024个Unicode字符,无NUL。evidence_ref:1–512个Unicode字符,无空白/控制字符;使用受控记录引用,禁止放入凭据秘密。平台只保存引用,不请求地址或自动判定证据真实性。

2. POST /v1/tasks/{task_id}:review

权限 tasks:review。仅 NEEDS_REVIEW 且版本匹配、无当前租约、无活跃尝试和未释放许可时允许。普通独立任务可核查;合法计划任务需同事务核验计划与触发记录归属。流程所属任务或有门控的任务拒绝,恢复须经对应上层控制器。

字段 类型/要求 含义
expected_revision string,必填 当前待核查任务版本
reason / evidence_ref string,必填 上述原因和证据边界
execution_settled bool,必须显式true 声明已核实全部历史执行已结束、不会继续产生效果
outcome string,必填 仅SUCCEEDED或FAILED
result JSON,必填非null 核查结论或业务结果引用,最多256 KiB,保留精确数值

先收集执行历史、迟到证据及外部系统凭证,核实所有旧执行和实际效果,再提交结论。仅“数据库租约已过期”不足以声明执行已经物理停止;平台无法杀死远程goroutine,也无法证明外部系统没有副作用。

请求示例,原状态为 NEEDS_REVIEW/revision=4:

{
  "expected_revision":"4",
  "reason":"已核实旧执行全部结束,操作未完成",
  "evidence_ref":"evidence:reconciliation-001",
  "execution_settled":true,
  "outcome":"FAILED",
  "result":{"confirmed":true,"sequence":9007199254740993}
}

首次及去重均返回HTTP200;完整示意响应:

{
  "request_id": "44444444444444444444444444444444",
  "data": {
    "task": {
      "id": "22222222222222222222222222222222",
      "namespace_id": "33333333333333333333333333333333",
      "queue_id": "11111111111111111111111111111111",
      "state": "FAILED",
      "revision": "5",
      "handler": "object.prepare",
      "handler_version": "v1",
      "payload_version": "v1",
      "payload": {
        "object_ref": "objects/input",
        "sequence": 9007199254740993
      },
      "business_key": "object:operation-001",
      "available_at": "2026-09-17T00:00:00Z",
      "deadline_at": "2026-09-17T00:01:00Z",
      "recovery_safety": "RECONCILE_ONLY",
      "retry": {
        "max_attempts": 1,
        "initial_delay_ms": 0,
        "max_delay_ms": 0,
        "attempt_timeout_ms": 10000
      },
      "attempt_count": 1,
      "result": {
        "confirmed": true,
        "sequence": 9007199254740993
      },
      "completed_at": "2026-09-17T00:00:20Z",
      "created_at": "2026-09-17T00:00:00Z",
      "updated_at": "2026-09-17T00:00:20Z"
    },
    "audit_id": "55555555555555555555555555555555",
    "deduplicated": false
  }
}

事务内保存新终态/result、清除effect_uncertain、revision加一、设置completed_at,释放一次未终结额度;同时保存幂等回执、不可变审计及新的结果事件。失败整体回滚,不能只更新状态后补写审计。旧尝试及UNKNOWN结果不改写,迟到报告仍只追加证据;已有needs_review结果事件保持冻结,订阅会再收到更高版本的最终结果。串行计划可以在核查终结后推进后续触发。

SDK:ReviewTask(ctx, taskID, key, orbit.ReviewTaskRequest) 返回 ReviewTaskResult。

3. POST /v1/tasks/{task_id}:retry

权限 tasks:retry。只接受没有上层所有者的独立任务,原状态FAILED或CANCELLED,effect_uncertain=false,版本匹配,无租约/活跃尝试/未释放许可。NEEDS_REVIEW先核查,SUCCEEDED不能重试;计划任务和流程任务均拒绝直接生成后继。

字段 类型/要求 含义
expected_revision string,必填 原终态任务的当前版本
reason / evidence_ref string,必填 重执行原因与安全依据
safe_to_retry bool,必须显式true 声明已确认旧执行结束,按原业务身份重新执行安全
task_timeout_ms uint64,必填1–2592000000 新任务从受理起的总预算,最多30天;至少覆盖冻结的attempt_timeout_ms
available_at time/null,可选 新任务最早可执行时间,默认当前数据库时间
start_deadline_at time/null,可选 新任务最晚开始时间,默认无独立限制,受新总截止时间约束

时间采用UTC RFC3339、微秒精度、年份2000–9998。available_at只允许最多1秒过去容差,必须早于新总截止;start_deadline_at必须晚于现在、不早于available_at、不晚于新总截止,详见受理时间约束。不继承原任务已经过期的绝对时间。

{
  "expected_revision":"5",
  "reason":"已核实旧执行不会继续产生效果,允许重新执行",
  "evidence_ref":"evidence:safe-retry-001",
  "safe_to_retry":true,
  "task_timeout_ms":60000
}

首次HTTP201,原键重报HTTP200且deduplicated=true。完整示意响应:

{
  "request_id": "77777777777777777777777777777777",
  "data": {
    "source_task_id": "22222222222222222222222222222222",
    "task": {
      "id": "66666666666666666666666666666666",
      "namespace_id": "33333333333333333333333333333333",
      "queue_id": "11111111111111111111111111111111",
      "state": "WAITING",
      "revision": "1",
      "handler": "object.prepare",
      "handler_version": "v1",
      "payload_version": "v1",
      "payload": {
        "object_ref": "objects/input",
        "sequence": 9007199254740993
      },
      "business_key": "object:operation-001",
      "available_at": "2026-09-17T00:00:30Z",
      "deadline_at": "2026-09-17T00:01:30Z",
      "recovery_safety": "RECONCILE_ONLY",
      "retry": {
        "max_attempts": 1,
        "initial_delay_ms": 0,
        "max_delay_ms": 0,
        "attempt_timeout_ms": 10000
      },
      "attempt_count": 0,
      "created_at": "2026-09-17T00:00:30Z",
      "updated_at": "2026-09-17T00:00:30Z",
      "retry_of": "22222222222222222222222222222222"
    },
    "audit_id": "88888888888888888888888888888888",
    "deduplicated": false
  }
}

新任务保留原队列、处理器与输入版本、payload、business_key、concurrency_key、priority、recovery_safety和冻结重试策略;新ID、revision=1、attempt_count=0和新时间预算,retry_of指回原任务。原任务终态、版本、结果和尝试次数均保持不变。

每个原任务最多一个直接重试后继;不同键竞争时只有一个成功,其余409,继续人工重试应沿失败/取消的后继任务操作。同键重报返回同一子任务和audit_id。唯一索引 (namespace_id,retry_of) 兜底,关联存在与归属由事务校验,无数据库外键。

新任务占用原队列与应用额度,仍受背压/暂停/并发/限速/资源键控制;满额不创建子任务、不遗留幂等身份。人工声明不能替代业务幂等:业务操作身份仍须保持稳定,新taskID不能直接用作外部业务的去重身份。

task.retry审计记在原任务上,after包含retry_task_id;子任务的task.accepted事件包含retry_of和audit_id。任务详情、单项回执和Worker分配中的retry_of为新增可选字段;任务摘要列表没有该字段。

SDK:RetryTask(ctx, taskID, key, orbit.RetryTaskRequest) 返回 RetryTaskResult。

4. 查询不可变操作审计

权限 tasks:audit:read,与普通tasks:read分开;当前凭据与队列范围逐次校验,不返回其他任务的审计或秘密。

GET /v1/tasks/{task_id}/operations?limit=20&cursor=...:limit默认20,1–100,cursor最长1024;参数只能出现一次且不能传未知筛选。按(created_at DESC,id DESC)分页;游标绑定命名空间、任务和查询种类。不是跨页快照,新的并发操作需从首页重新查询。

{
  "request_id":"44444444444444444444444444444444",
  "data":{
    "items":[{
      "id":"88888888888888888888888888888888",
      "task_id":"22222222222222222222222222222222",
      "operation_id":"99999999999999999999999999999999",
      "actor_id":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "action":"task.retry",
      "created_at":"2026-09-17T00:00:30Z"
    }],
    "next_cursor":"<服务端返回的不透明游标>"
  }
}

GET /v1/tasks/{task_id}/operations/{audit_id}:200详情在上述摘要基础上增加reason、可选evidence_ref、before和after;不存在或不属该任务返回404。

{
  "request_id":"44444444444444444444444444444444",
  "data":{
    "id":"88888888888888888888888888888888",
    "task_id":"22222222222222222222222222222222",
    "operation_id":"99999999999999999999999999999999",
    "actor_id":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "action":"task.retry",
    "created_at":"2026-09-17T00:00:30Z",
    "reason":"已核实旧执行不会继续产生效果,允许重新执行",
    "evidence_ref":"evidence:safe-retry-001",
    "before":{"state":"FAILED","revision":"5"},
    "after":{
      "source_task_id":"22222222222222222222222222222222",
      "retry_task_id":"66666666666666666666666666666666",
      "safe_to_retry":true,
      "task_timeout_ms":60000
    }
  }
}

核查after包含state、revision、effect_uncertain=false、execution_settled=true及result_digest。摘要不复制完整业务输入;详情也不返回租约/凭据秘密。核查/重试的actor_id由实际认证凭据确定,不能在正文自报。审计可含受理、取消、执行结果等其他动作;查询操作记录不等于获得任务执行权。

SDK:ListTaskOperations(ctx, taskID, HistoryPageRequest)、GetTaskOperation(ctx, taskID, auditID)。

5. 错误与兼容

HTTP/错误码 处理
400 INVALID_ARGUMENT 修正字段、显式确认、预算、JSON或游标,不触及任务
401 UNAUTHENTICATED 核对凭据到期/撤销与所属应用启用情况
403 PERMISSION_DENIED 当前凭据未获该管理/审计权限
404 NOT_FOUND 任务/操作不存在,或命名空间/队列/任务归属不匹配
409 STATE_CONFLICT 状态/版本/所有者/门控不允许,或已有直接重试后继;先读当前状态和审计
409 IDEMPOTENCY_CONFLICT 同操作身份正文变化,核对已保存原请求
429 QUEUE_FULL / APPLICATION_FULL 新任务受理背压,释放额度后原请求重试;错误携带retry_after_ms提示
503 UNAVAILABLE 或网络错误 操作可能已提交,保持原身份核对

完整通用错误边界与请求大小限制见任务接口。新接口调用旧服务返回404,不静默改成重新SubmitTask。旧SDK忽略retry_of响应字段;使用additionalProperties=false的旧版严格Schema验证器需更新Schema。需要恢复接口与关联追踪时升级当前SDK。Worker协议仍为v1,retry_of是可选新增字段。

迁移000015不新增模型字段:明确中文retry_of语义,将相同列的历史非唯一索引替换为唯一索引,并增加禁止自关联CHECK。停止旧写入节点后迁移并统一升级;迁移遇到历史非法重复或自关联应失败,不能自动删除数据。跨版本混合负载和生产滚动发布尚未验收。