feat: add durable safe retries with uncertainty tracking and SDK examples

This commit is contained in:
2026-09-17 15:43:44 +08:00
parent 9e3d4be5cc
commit d65cf96ea7
44 changed files with 1300 additions and 77 deletions

View File

@@ -83,6 +83,7 @@
- 查询轮次与任务分别唯一;轮次、单轮尝试预算和截止时间独立保存。
- 内部事件与外部订阅交付有去重键,租用状态要求有效租用字段。
- 终态必须有结束时间,待执行任务不能伪造有效执行租约。
- effect_uncertain=true 不能和成功、失败、取消终态共存;尝试/证据 retryable=true 只允许明确失败。
## 5. 仍需服务层事务保证的条件
@@ -110,4 +111,6 @@ P2 服务端已新增会话隔离、两步派发、租约、并发许可、结
000004_execution_history 为已有证据表增加 `(namespace_id, attempt_id, created_at, id)` 分页索引,不新增实体或字段,不回改已有迁移。尝试列表复用执行代次唯一索引。查询 DTO 与持久化模型分离,只在详情读取业务正文,不返回执行秘密或内部摘要;接口见 [执行历史](api/execution-history.md)。
000005_safe_retry 增加任务 EffectUncertain、尝试与证据 Retryable 字段,Go 与 SQL 均附中文说明;实体数仍为 32。跨尝试的不确定效果必须保留,历史 NEEDS_REVIEW 保守回填 true,旧状态、版本和冻结策略不变。回执增加 `(namespace_id, attempt_id, operation, request_digest)` 索引,换消息身份重报结果也返回原已应用回执。字段语义、升级策略启用与限制见 [安全重试](api/retries.md),升级回归见 [阶段报告](reports/phase-3-safe-retry.md)。
命令与验证记录见 [开发计划](08-development-plan.md)、[基础阶段验证报告](reports/phase-0.md) 与 [执行历史回归](reports/phase-2-execution-history.md)。

View File

@@ -27,7 +27,7 @@
| P0 基础与数据模型 | Go workspace、独立 SDK module、公共/Pulse/Weave 模型、MySQL 迁移、配置、健康检查 | 编译/竞态/静态检查;真实 MySQL 建表、隔离、唯一性、预算、不可变版本、迁移失败防护;SDK 健康调用 | 已完成并通过阶段回归(见 P0 报告) |
| P1 任务受理闭环 | 应用凭据与作用域权限、队列、幂等提交、查询、待执行取消、受理事件;SDK 客户端 | API/SDK 创建查询闭环;重复键与内容冲突、响应未确认重报、跨命名空间拒绝、参数边界 | 已完成并通过阶段回归(见 P1 报告;真实断路/提交 ACK 丢失注入留后续故障阶段) |
| P2 Worker 执行闭环 | WSS 接入、能力/容量、领取与开始、租约、续租、结果确认、排空与恢复;Worker SDK | 真 Worker 执行;失联/重连/重复确认/旧尝试拒绝;业务效果未知转核查;节点故障接管 | 已完成并通过阶段回归:服务端、自动 SDK、历史查询、完整 Schema 与真实进程强杀恢复;测试拓扑和剩余生产故障矩阵见 P2 验收报告 |
| P3 Pulse 完整能力 | 安全重试、延时、Cron/固定间隔、漏跑策略、计划版本、并发许可、配额/背压、可靠结果订阅 | 多实例领取/触发竞争、重试上限、取消竞争、队列隔离、积压恢复、事件重复交付 | 待开发 |
| P3 Pulse 完整能力 | 安全重试、延时、Cron/固定间隔、漏跑策略、计划版本、并发许可、配额/背压、可靠结果订阅 | 多实例领取/触发竞争、重试上限、取消竞争、队列隔离、积压恢复、事件重复交付 | 进行中:安全重试、退避、未知效果保留、升级回填及 SDK 场景已通过回归;Cron/固定间隔、计划管理、运行中取消、配额/背压和订阅待开发 |
| P4 Weave 主流程 | SDK 定义构建与发布校验、实例、顺序/分支/结构化并行汇合、信号/定时等待、版本绑定 | 重启接续、早到/重复信号、并行竞争、内部事件重放、发布版本兼容;端到端通用流程 | 待开发 |
| P5 Weave 恢复与查询 | 有界查询、暂停继续、取消、人工核查恢复、控制门控与审计 | 轮次与尝试独立预算;暂停/开始/回执/超时竞争;恢复保留预算;未知结果不盲目重复 | 待开发 |
| P6 生产验证与运维 | 完整监控、凭据轮换、备份恢复、滚动升级、运行手册、可复现负载和故障工具 | 对照生产验收清单执行,报告环境/指标/遗留项;容量、RPO/RTO 按真实拓扑验证 | 待开发 |
@@ -41,6 +41,7 @@ SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最
| --- | --- | --- |
| 健康检查 | 创建客户端 → 存活/就绪查询 | P0 |
| 文件转换任务 | 提交幂等任务 → Worker 处理 → 查询结果 | P1–P2 |
| 有限安全重试 | 明确失败 → 持久化退避 → 新尝试 → 查询完整历史 | P3,已实现完整示例 |
| 定期数据同步 | 发布计划 → 触发任务 → 暂停/恢复 → 查看触发记录 | P3 |
| 并行生成结果 | 发布流程 → 并行执行 → 汇合 → 完成 | P4 |
| 审核与外部回执 | 提交流程 → 等待信号 → 授权信号注入 → 继续 | P4 |
@@ -75,6 +76,7 @@ SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最
- [P0 验证记录](reports/phase-0.md)
- [P1 验证记录](reports/phase-1.md)
- [P2 阶段验收与进程故障记录](reports/phase-2.md)
- [P3 安全重试阶段记录](reports/phase-3-safe-retry.md)
- [P2 服务端阶段记录](reports/phase-2-server.md)
- [P2 SDK 协议阶段记录](reports/phase-2-sdk-protocol.md)
- [P2 自动 Worker 运行器记录](reports/phase-2-worker-runtime.md)

View File

@@ -2,7 +2,7 @@
- 文档版本:0.2
- 整理日期:2026-09-17
- 阶段:P0–P2 已通过阶段验收,包括协议 Schema 与真实进程故障恢复;P3–P7 继续推进,尚未完成全平台生产验收
- 阶段:P0–P2 已通过阶段验收,包括协议 Schema 与真实进程故障恢复;P3 安全重试已通过回归,P3–P7 继续推进,尚未完成全平台生产验收
- 适用范围:Orbit 统一平台,包含 Pulse 任务调度模块、Weave 流程编排模块与公共能力。平台面向多个业务系统复用。
## 阅读顺序
@@ -79,4 +79,4 @@ Orbit 不内置特定行业规则;Pulse 管理执行,Weave 管理步骤依
2026-09-17,文档更新至 0.2:用户确认研发定义发布流程及 Weave 首版功能,明确纳入有界重复查询和暂停/继续等生产管理能力。技术细则与参数继续按建议或待定标注,未将功能确认等同于实现或验收通过。
持续开发目标已建立。当前实现和验收进度以 [开发计划](08-development-plan.md)、[P0 报告](reports/phase-0.md)、[P1 报告](reports/phase-1.md) 与 [P2 阶段验收](reports/phase-2.md) 为准;早期设计稿中的“未实现”描述保留其编写阶段语境。
持续开发目标已建立。当前实现和验收进度以 [开发计划](08-development-plan.md)、[P0 报告](reports/phase-0.md)、[P1 报告](reports/phase-1.md) 与 [P2 阶段验收](reports/phase-2.md) 和 [P3 安全重试报告](reports/phase-3-safe-retry.md) 为准;早期设计稿中的“未实现”描述保留其编写阶段语境。

View File

@@ -14,6 +14,7 @@
| `/v1/queues` 创建与查询 | P1 已实现 | [队列接口](queues.md) |
| 凭据校验与初始化 CLI | P1 已实现 | [鉴权](authentication.md) |
| `/v1/worker/connect` | P2 已实现并通过阶段验收 | [Worker 契约](worker.md)、[Schema](worker-schema.md) |
| 任务冻结策略与 Worker `retryable` | P3 安全重试已实现 | [安全重试](retries.md) |
| `/v1/schedules` 与计划管理 | 计划 P3,尚未实现 | [协议草案](../03-http-websocket-protocol.md) |
| `/v1/events/connect` | 计划 P3,尚未实现 | [协议草案](../03-http-websocket-protocol.md) |
| `/v1/workflows` 与流程管理 | 计划 P4–P5,尚未实现 | [流程接口规划](workflows.md) |

View File

@@ -6,7 +6,7 @@
任务代表一次受理的工作,尝试代表其中一次执行代次。领取后未获开始授权就过期的尝试也会保留,并消耗尝试预算。重连恢复同一次尝试,不创建新的尝试记录。
证据保存 Worker 的迟到、未开始或冲突报告。`outcome=SUCCEEDED` 只是原报告的声明;`disposition=EVIDENCE_ONLY` 表示采集时仅作为证据保存,不能解释为任务成功。权威任务状态与结果使用 [GetTask](tasks.md) 查询。当前接口不执行人工裁定、恢复或重试。
证据保存 Worker 的迟到、未开始或冲突报告。`outcome=SUCCEEDED` 只是原报告的声明;`disposition=EVIDENCE_ONLY` 表示采集时仅作为证据保存,不能解释为任务成功。权威任务状态与结果使用 [GetTask](tasks.md) 查询。当前只读接口不执行人工裁定、恢复或重试。平台自动重试保留每次失败结果;其规则及任务 effect_uncertain 见 [安全重试](retries.md)。
四个接口均要求 `Authorization: Bearer <token>`、`tasks:read` 动作及任务所属队列范围。凭据确定命名空间,每次调用都重新校验有效性。ID 是 32 位小写十六进制字符串,全部父子关系都校验;知道尝试 ID 或证据 ID 不代表有权读取。
@@ -60,6 +60,7 @@ curl "$ORBIT_URL/v1/tasks/$TASK_ID/attempts/$ATTEMPT_ID/evidence/$EVIDENCE_ID" \
| authorized_at | UTC RFC 3339 / 否 | 获批开始时间;省略表示尚未获批,不证明外部效果是否发生 |
| ended_at | UTC RFC 3339 / 否 | 尝试结束时间;尚未结束时省略 |
| error_code | string / 否 | 稳定错误分类,无错误时省略 |
| retryable | bool / 否 | Worker 对明确失败的可安全重试声明,false 时省略;不证明平台实际安排了重试 |
| created_at、updated_at | UTC RFC 3339 / 是 | 创建和最近更新时间,最多微秒精度 |
| session_id、worker_instance_id | string / 仅详情 | 当前或最后归属会话及进程身份;会话可能因恢复改变,不是完整归属变更日志 |
| result | JSON / 仅详情,可省略 | 此尝试已保存的结果;无结果时省略,与追加证据分开 |
@@ -108,6 +109,7 @@ curl "$ORBIT_URL/v1/tasks/$TASK_ID/attempts/$ATTEMPT_ID/evidence/$EVIDENCE_ID" \
| id、task_id、attempt_id | string / 是 | 证据及已校验的父资源标识 |
| outcome | string / 是 | 原报告的 SUCCEEDED、FAILED 或 UNKNOWN |
| error_code | string / 否 | 原报告的错误分类,空时省略 |
| retryable | bool / 否 | 原失败报告的重试声明,false 时省略;证据不触发执行 |
| reason | string / 是 | LEASE_LOST(授权失效)、NOT_STARTED(未获批开始)、RESULT_CONFLICT(与已保存结果冲突) |
| disposition | string / 是 | 固定 EVIDENCE_ONLY,描述采集处理方式 |
| created_at | UTC RFC 3339 / 是 | 首次保存时间,相同身份的重复报告不刷新 |

117
docs/api/retries.md Normal file
View File

@@ -0,0 +1,117 @@
# 安全重试 v1
状态:P3 的安全重试已实现。使用现有任务提交与 Worker 结果接口,无独立的自动重试路由;Cron、运行中取消、人工恢复与可靠结果订阅继续按 [计划](../08-development-plan.md) 开发。验证证据见 [本阶段报告](../reports/phase-3-safe-retry.md)。
## 1. 三种不同的重报
| 行为 | 身份与效果 |
| --- | --- |
| HTTP 受理结果不明,重报提交 | 保留原 Idempotency-Key 和内容,返回原任务,不增加尝试 |
| WS 控制结果不明,重报 start/finish | 保留尝试和 message_id,返回原回执,不再次调用处理器 |
| 执行失败或租约失效,平台安排重试 | 保留 Task.ID;到期后领取新 AttemptID/Epoch,重新消耗一次预算 |
HTTP 调用方自行用原身份核对受理结果,自动 Worker 管理第二类控制确认,平台根据持久化事实决定第三类。业务效果是否可重复执行,需要处理器自身保证,不能只靠连接重试。
## 2. 冻结策略与结果分类
提交时设置 `retry.max_attempts / initial_delay_ms / max_delay_ms / attempt_timeout_ms`、`recovery_safety`、任务总时限及可选最晚开始时间。默认 max_attempts=1、recovery_safety=RECONCILE_ONLY,不自动增加业务尝试。字段范围见 [任务接口](tasks.md)。
Worker 的 `task.finish` 新增可选布尔字段 `retryable`,省略为 false;只有 outcome=FAILED 才能为 true。SUCCEEDED/UNKNOWN 携带 true 返回 INVALID_ARGUMENT。该字段表示明确失败可安全再试,**不能用于把未知效果伪装成明确失败**。
以下均要求报告仍有当前有效授权;无授权的报告只保存证据:
| 当前事实 | 有剩余预算、截止允许时 | 不能重试时 |
| --- | --- | --- |
| SUCCEEDED | 完成任务,清除 effect_uncertain | 同左 |
| FAILED,retryable=false | 不安排重试 | 无历史不确定效果则 FAILED,否则 NEEDS_REVIEW |
| FAILED,retryable=true | 退避后 WAITING | 无历史不确定效果则 FAILED,否则 NEEDS_REVIEW |
| UNKNOWN 或已获开始授权后租约到期;RECONCILE_ONLY | NEEDS_REVIEW | NEEDS_REVIEW |
| UNKNOWN 或已获开始授权后租约到期;IDEMPOTENT | effect_uncertain=true,退避后 WAITING | NEEDS_REVIEW,保留不确定性 |
| 领取后未获开始授权即到期 | 退避后 WAITING,仍消耗已领取次数 | 无历史不确定效果则 FAILED,否则 NEEDS_REVIEW |
存在历史不确定效果时,只有 IDEMPOTENT 允许继续自动尝试。此声明要求跨进程、跨尝试、并发及迟到执行都具有业务幂等保障,例如持久化操作键、唯一约束、返回原结果及资源侧执行代次校验。Task.ID 在自动重试中稳定;AttemptID 会变化,不能作为跨尝试的业务幂等键。平台不会因为新尝试开始而保证旧进程已经停止。
`effect_uncertain` 是任务详情、摘要和派发任务中的可选布尔字段,false 时省略。只要曾发生尚未核实的授权执行,后来的明确失败、未开始即到期、总时限耗尽或取消都不会把该事实抹掉。当前有效尝试成功可自动清除;迟到成功证据不能清除。人工核查解除的管理接口尚未实现。
## 3. 退避、预算与持久化
消费第 n 次领取预算后,退避上限为:
```text
base = min(initial_delay_ms × 2^(n-1), max_delay_ms)
delay_ms ∈ [ceil(base/2), base]
available_at = 本次结束事务中的数据库时间 + delay_ms
```
抖动使用 AttemptID 生成确定值,同一尝试重复计算不改变时间;指数倍增先检查上限,避免溢出。返回 WAITING 表示下一次最早可领取时间,实际派发还受容量、队列暂停、流程门控、凭据、扫描与服务可用性影响,不承诺准点开始。
- 领取就消耗次数,包含未获开始授权的尝试。重连、ACK 重报和取消都不重置次数。
- 下一次 available_at 必须严格早于总截止和最晚开始时间;等于截止也不能安排重试。领取/开始仍重新核验数据库时间。
- attempt_timeout_ms 从获批开始计算;每次尝试重新计时,任务总截止保持不变。
- 尝试结果、retryable、任务状态、不确定标记、下一时间、许可释放、事件及结果操作审计/回执在同一事务提交。后台租约回收保存包含节点身份与决策的持久化事件。
- 等待重试期间不占执行许可,任务不带 result/completed_at;上一尝试的错误和结果保留在 [执行历史](execution-history.md)。NEEDS_REVIEW 也没有 completed_at。
- 重启后以数据库 available_at 继续领取,不依赖某一节点内存中的定时器。
`task.requeued` 等持久化事件记录 decision、delay_ms、next_available_at、attempt_count、max_attempts、effect_uncertain。decision 包括 RETRYABLE_FAILURE、IDEMPOTENT_UNKNOWN、UNSTARTED_LEASE_EXPIRED、ATTEMPT_LIMIT、DEADLINE_EXCEEDED、RECONCILIATION_REQUIRED 等。当前可靠事件订阅尚未开放。
当前取消接口仅支持不归属流程的 WAITING 任务。若等待的是历史效果不明后的重试,取消会记录意图、停止后续尝试并进入 NEEDS_REVIEW,不能宣称此前业务已撤销;其他 WAITING 任务可进入 CANCELLED。运行中取消仍未开放。
## 4. SDK 接入
生产者在提交请求中设置有限策略,以下为函数片段:
```go
accepted, err := client.SubmitTask(ctx, operationKey, orbit.SubmitTaskRequest{
QueueID: queueID,
Handler: "text.normalize", HandlerVersion: "v1", PayloadVersion: "v1",
Payload: json.RawMessage(`{"text":" safe retry "}`),
RecoverySafety: orbit.RecoveryReconcileOnly,
TaskTimeoutMS: 60000,
Retry: orbit.RetryPolicy{
MaxAttempts: 3, InitialDelayMS: 500, MaxDelayMS: 2000,
AttemptTimeoutMS: 10000,
},
})
if err != nil { return err }
fmt.Println(accepted.Task.ID)
```
处理器在已确认可安全重试的失败分支返回:
```go
return nil, &orbit.WorkerFailure{
Code: "TEMPORARY_REJECTION",
Retryable: true,
Details: json.RawMessage(`{"reason_ref":"diagnostics/temporary"}`),
}
```
WorkerFailure 默认 Retryable=false;普通 error、panic、runtime.Goexit 和非法结果仍上报 UNKNOWN,由冻结的 recovery_safety 决定是否允许新尝试。不要把任意网络超时转成 Retryable=true。底层 SDK 对应 `WorkerFinish.Retryable`;其消息结构见 [Worker 契约](worker.md)。
## 5. 运行完整示例
源码:[examples/retry/main.go](../../sdk/go/examples/retry/main.go)。先运行当前迁移、服务,并按 [Worker 初始化步骤](../sdk/worker.md#1-可运行的文本规范化示例) 准备具有提交、读取、消费权限的专用凭据。从仓库根目录执行:
```sh
cd sdk/go
ORBIT_URL=http://127.0.0.1:8080 \
ORBIT_CREDENTIALS_FILE=../../.local/worker.credentials.json \
go run ./examples/retry
```
输入为 `{"text":" safe retry "}`。第一次处理在产生效果前模拟明确拒绝,第二次执行真正的文本规范化;重试等待由平台持久化安排。
```text
task=<任务ID> state=SUCCEEDED attempts=2 first_retryable=true result={"text":"SAFE RETRY"}
worker_drained=true started=2 confirmed=2 running=0
```
confirmed 统计已确认的尝试报告,包含第一次失败,不是成功任务数。示例通过尝试查询核对第一次 FAILED/retryable=true 与第二次 SUCCEEDED。每次运行生成独立操作和处理器身份,不连接外部业务系统;正常结束排空,历史保留。内存计数只用于注入一次故障,不能用作生产幂等存储。错误时非零退出并尝试有界收尾。
## 6. 回执与升级兼容
同 message_id 改变 retryable 或其他内容返回 IDEMPOTENCY_CONFLICT。保持同一结果而换 message_id 时,返回第一次已应用的结果回执;即使任务已在后续尝试成功,旧失败回执仍可能写着 WAITING。当前状态用 GetTask 查询;CommandResult.lease 仍只描述旧尝试当前是否有权执行。
迁移 000005 增加 effect_uncertain、尝试/证据 retryable 以及结果回执索引。已有 NEEDS_REVIEW 保守回填 effect_uncertain=true,保留原状态、版本和策略。已有结果摘要在 retryable 省略或 false 时保持兼容;true 属于不同语义。
本开发版本首次启用已冻结的多次尝试策略:旧的未结束任务若声明 max_attempts>1 / IDEMPOTENT,升级后也会按本页规则执行;不重写策略、不重置预算、不自动恢复已有 NEEDS_REVIEW。旧服务不识别新 Schema 版本,应停止旧节点后迁移并启动统一版本;尚未验证混合版本滚动升级或向旧二进制回退。SDK 尚未发布版本,自研 Worker 使用 retryable 前应升级服务端。

View File

@@ -1,8 +1,8 @@
# 任务接口 v1
状态:P1 已实现并通过真实 MySQL 与 SDK 回归。当前提供受理、查询、分页和待执行取消;P2 已提供 Worker 执行与历史查询,策略重试在 P3 实现。
状态:P1 已实现并通过真实 MySQL 与 SDK 回归。当前提供受理、查询、分页和待执行取消;P2 已提供 Worker 执行与历史查询,P3 已接入 [安全重试](retries.md)。
P2 进展:服务端执行内核、WSS 与独立自动 Worker SDK 已可产生 LEASED、RUNNING、SUCCEEDED、FAILED、NEEDS_REVIEW 等状态,见 [Worker 契约](worker.md);[尝试与证据查询](execution-history.md) 可核对每次执行,自动策略重试尚未完成。
P2 进展:服务端执行内核、WSS 与独立自动 Worker SDK 已可产生 LEASED、RUNNING、SUCCEEDED、FAILED、NEEDS_REVIEW 等状态,见 [Worker 契约](worker.md);[尝试与证据查询](execution-history.md) 可核对每次执行;重试以冻结策略、明确失败分类及未知效果安全声明为依据。
## 公共约定
@@ -28,7 +28,7 @@ P2 进展:服务端执行内核、WSS 与独立自动 Worker SDK 已可产生
| start_deadline_at | time / null | 无独立限制 | 必须晚于当前时间,不早于 available_at,不晚于总截止时间 |
| task_timeout_ms | uint64 | 0 或省略为 86400000 | 从受理时计算的总时限,最大 30 天(2592000000 毫秒) |
| recovery_safety | string | RECONCILE_ONLY | 也可为 IDEMPOTENT;必须基于处理器真实的幂等能力声明 |
| retry | object | 见下表 | 受理时冻结策略;P2 已执行尝试预算及时限,策略退避重试在 P3 实现 |
| retry | object | 见下表 | 受理时冻结策略;包含首次领取的有限尝试、退避及每次时限,见 [安全重试](retries.md) |
处理器及版本名称以 ASCII 字母或数字开头,后续只能包含字母、数字、`_ . : -`。时间年份为 2000–9998。
@@ -90,7 +90,7 @@ curl -i "$ORBIT_URL/v1/tasks" \
}
```
可选响应字段:start_deadline_at、result、cancel_requested_at、completed_at、business_key,未设置时省略。终态有 completed_at。状态集合为 WAITING / LEASED / RUNNING / NEEDS_REVIEW / SUCCEEDED / FAILED / CANCELLED;P1 仅主动产生 WAITING 和 CANCELLED。
可选响应字段: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。
### 幂等与失败处理
@@ -164,7 +164,7 @@ curl "$ORBIT_URL/v1/tasks/$TASK_ID:cancel" \
expected_revision 必填,为非零 uint64 的十进制字符串;reason 必填且不能全为空白,最多 1024 个 Unicode 字符。服务端以版本和状态条件更新,只有一个竞争请求能成功。
200 的 data 为 `{"task":<完整最新任务>,"deduplicated":false}`。示例任务会变为 state=CANCELLED、revision="2",写入 cancel_requested_at/completed_at,并同事务追加取消事件及审计。首次取消之后使用相同键、原因和**原 expected_revision**重报,返回原取消回执且 deduplicated=true。换键重报已取消任务返回 409。
200 的 data 为 `{"task":<完整最新任务>,"deduplicated":false}`。示例任务会变为 state=CANCELLED、revision="2",写入 cancel_requested_at/completed_at,并同事务追加取消事件及审计。首次取消之后使用相同键、原因和**原 expected_revision**重报,返回原取消回执且 deduplicated=true。换键重报已取消任务返回 409。若 WAITING 来自未知效果后的自动重试,取消会保存 cancel_requested_at 并转 NEEDS_REVIEW,保留 effect_uncertain=true、completed_at 为空,停止后续尝试但不宣称此前效果已经撤销。
SDK:`Client.CancelTask(ctx, taskID, key, request)`。此操作不承诺回滚任何已发生的外部业务效果。
@@ -186,4 +186,4 @@ SDK:`Client.CancelTask(ctx, taskID, key, request)`。此操作不承诺回滚
{"request_id":"44444444444444444444444444444444","error":{"code":"IDEMPOTENCY_CONFLICT","message":"idempotency key was used with different content"}}
```
批量受理、人工重试和运行中取消尚未实现。[执行历史](execution-history.md) 已提供尝试与证据的列表和详情。完整可运行程序见 [SDK 任务示例](../../sdk/go/examples/tasks/main.go),已验证异常场景见 [P1 回归报告](../reports/phase-1.md)。SDK 忽略未知响应字段;更改已有字段语义需升级契约和回归。
有限自动重试已实现,具体分类、退避、回执和升级边界见 [安全重试](retries.md)。批量受理、人工重试和运行中取消尚未实现。[执行历史](execution-history.md) 已提供尝试与证据的列表和详情。完整可运行程序见 [SDK 任务示例](../../sdk/go/examples/tasks/main.go),已验证异常场景见 [P1 回归报告](../reports/phase-1.md)。SDK 忽略未知响应字段;更改已有字段语义需升级契约和回归。

View File

@@ -30,9 +30,10 @@ ACK 的 body 根据原请求选择:worker.capacity → welcome,worker.resume
- epoch、sequence、revision 为规范十进制字符串:范围 1–18446744073709551615,无前导零和正号。SDK 使用 uint64,线上使用字符串。
- instance_secret/lease_secret 必须是 32 字节秘密的严格无填充 base64url,长度 43;知道资源 ID 无法替代秘密。
- 结果为非 null JSON;FAILED/UNKNOWN 必须携带稳定 error_code;SUCCEEDED 省略或使用空 error_code。
- finish.retryable 为可选布尔值,默认 false;true 仅允许 FAILED,Schema 与服务端都校验。派发任务可带 effect_uncertain=true,表示此前授权执行的效果尚未核实。业务重试条件见 [安全重试](retries.md)。
- 返回时间使用 UTC RFC 3339,最多微秒精度。验证器应开启 format 断言以校验实际日历日期。
本轮收紧了开发期此前允许的省略控制字段、非规范计数字符串和数组内字段别名。当前 SDK 一直发送完整规范字段,不需迁移;其他自研客户端应按本文件更新。SDK 尚未发布首个版本。将来扩展消息类型或改变字段语义必须同时更新 Schema、服务端、SDK 与兼容回归。
P2 收紧了开发期此前允许的省略控制字段、非规范计数字符串和数组内字段别名。当前 SDK 一直发送完整规范字段,不需迁移;其他自研客户端应按本文件更新。SDK 尚未发布首个版本。将来扩展消息类型或改变字段语义必须同时更新 Schema、服务端、SDK 与兼容回归。
## Schema 与运行时的边界

View File

@@ -88,9 +88,9 @@ capacity 使用单调递增序号和覆盖值。同序号同内容可重复,
| --- | --- | --- |
| task.start | AttemptRef | 领取后再次检查当前会话、尝试、租约、队列暂停及流程门控,保存 RUNNING 后确认 |
| task.renew | AttemptRef | 只续期当前 RUNNING;新消息可续租,重复原消息只取回原回执 |
| task.finish | ref: AttemptRef、outcome、result、error_code | 保存明确结果或未知效果;返回操作回执和当前授权 |
| task.finish | ref: AttemptRef、outcome、result、error_code、可选 retryable | 保存明确结果或未知效果;返回操作回执和当前授权 |
outcome 为 SUCCEEDED / FAILED / UNKNOWN。result 为非 null JSON 或证据引用。FAILED/UNKNOWN 必须带 1–64 字符的稳定 error_code;SUCCEEDED 不带错误分类。当前 FAILED 成为任务失败终态,UNKNOWN 转 NEEDS_REVIEW;策略化自动重试在 P3 实现。
outcome 为 SUCCEEDED / FAILED / UNKNOWN。result 为非 null JSON 或证据引用。FAILED/UNKNOWN 必须带 1–64 字符的稳定 error_code;SUCCEEDED 不带错误分类。retryable 省略为 false,只有 FAILED 可设 true,表示此次明确失败可安全重试。FAILED/true 受次数与时限限制进入退避;UNKNOWN 仅在冻结策略为 IDEMPOTENT 且预算允许时自动恢复,其他情况转 NEEDS_REVIEW。历史不确定效果不会被后续失败覆盖;完整规则见 [安全重试](retries.md)。
开始、续租和结束返回 CommandResult:
@@ -120,7 +120,7 @@ receipt 是原操作的不可变确认;lease 是本次重新核验的执行权
- 原 start 回执可能仍表示 RUNNING,但当前 lease.active=false;旧 ACK 不能让已过期、已结束或已被替换的尝试复活。
- 有效期不超过租约窗口、会话期限、任务总截止和从 authorized_at 起计算的单次尝试时限。
- 客户端需要扣除请求总耗时及安全余量,用本地单调时钟停止执行,不能假定本地墙上时钟与数据库一致。
- 同结果重报不重复释放许可或产生成功事件。同消息不同内容返回 IDEMPOTENCY_CONFLICT。
- 同结果重报不重复释放许可或产生成功事件;即使换 message_id,也返回该结果第一次已应用的回执,不借用后续尝试的状态。同消息改变内容或 retryable 返回 IDEMPOTENCY_CONFLICT。
- 迟到、未开始或与已保存结果冲突的内容,经过身份和秘密验证后保存为 EVIDENCE_ONLY;不改变任务权威结果,不当作新的执行授权。
- 断线后原结果仍保留稳定消息 ID,可在新会话恢复/核对后重报。不会因重连创建第二次业务执行。
@@ -132,12 +132,12 @@ receipt 是原操作的不可变确认;lease 是本次重新核验的执行权
每个节点每 2 秒发起一批恢复扫描,每批最多 100 项:
- 未批准开始即到期:有剩余次数和截止时间时,延后 1 秒重派;否则 FAILED。
- 已批准开始后失联:NEEDS_REVIEW,尝试 UNKNOWN,不能因网络超时断定业务未生效。
- WAITING 超过总截止或最晚开始时间:FAILED。
- 未批准开始即到期:有剩余次数和截止时间时按冻结策略退避;否则无历史不确定效果为 FAILED,有未核实效果为 NEEDS_REVIEW。
- 已批准开始后失联:尝试 UNKNOWN;RECONCILE_ONLY 转 NEEDS_REVIEW,IDEMPOTENT 仅在次数及时限允许时退避重试,并保留 effect_uncertain。
- WAITING 超过总截止或最晚开始时间:无历史不确定效果为 FAILED,否则 NEEDS_REVIEW。
- 回收再次锁定队列、任务、尝试并读取数据库时间,与续租/完成串行化;多个节点不能重复释放许可或重复终结事件。
此处的安全回收不是完整重试引擎;IDEMPOTENT 的自动重试和策略退避仍是 P3 工作。
安全重试已使用同一决策逻辑处理有效结果与租约回收;下一执行时间持久化在任务中,重启不丢失等待。当前不提供人工重试或核查解除接口。
## 6. 连接保护与当前边界

View File

@@ -45,6 +45,15 @@
- 异常:迟到或矛盾内容不能覆盖权威结果;缺少动作权限返回 403,错配父资源、跨命名空间或队列越权返回 404,详见 [接口](../api/execution-history.md)。示例不调用外部业务系统。
- 清理:正常结束确认排空并关闭连接;任务及证据保留用于追踪,不硬删除。重复运行创建独立任务。
## 已可运行:有限安全重试
- 源码:[重试示例](../../sdk/go/examples/retry/main.go),[完整启动命令与结果](../api/retries.md#5-运行完整示例)。
- 前置:当前迁移及服务,具有提交、读取和 Worker 权限的专用凭据。
- 步骤:第一次在产生效果前模拟明确拒绝 → 平台持久化退避 → 第二次处理文本 → 查询两次尝试 → 排空。
- 预期:SUCCEEDED、attempts=2、first_retryable=true、结果 `{"text":"SAFE RETRY"}`;Worker started=2、confirmed=2、running=0。
- 异常:次数/时限耗尽停止重试;未知效果默认转核查。`Retryable=true` 需要明确的安全依据,不能用于普通网络超时。
- 清理:示例不调用外部业务系统,每次创建独立任务并保留历史;失败时非零退出并有界收尾。
## 计划场景与验收标准
| 场景 | 输入与步骤 | 预期结果 | 必测异常 | 状态 |

View File

@@ -0,0 +1,74 @@
# P3 安全重试阶段记录
验证日期:2026-09-17。此报告只完成 P3 中的安全重试部分;P3 及总目标继续进行,不代表全平台生产验收通过。
## 1. 本轮实现
- 按受理时冻结的次数、退避及截止时间决定是否新建下一次执行;同一 Task.ID、不同 AttemptID/Epoch,领取时消耗预算。
- 明确失败通过 WorkerFailure.Retryable / task.finish.retryable 显式声明可安全再试,默认 false;未知效果只有 IDEMPOTENT 且预算允许时重试。
- 保存跨尝试的 effect_uncertain,预算耗尽、后续失败、未获开始授权即到期、等待取消或截止都不会将旧未知效果伪装成普通终态。
- 抖动退避写入数据库 available_at;与尝试结果、许可释放、事件、结果审计及操作回执原子提交。后台回收保留含节点身份与决策的事件。
- 结果换消息身份重报也取原已应用回执;过滤早到的证据回执,避免借用后续尝试状态。
- 前向迁移 000005 增加三个中文注释字段和结果回执索引;实体仍为 32 个,旧迁移未改动。
- 同步独立 SDK、Worker Schema、查询字段、[接口手册](../api/retries.md) 与 [完整示例](../../sdk/go/examples/retry/main.go)。
## 2. 环境与执行命令
本机 macOS、Go 1.27.1、Docker 内单个 MySQL 8.4.10。隔离数据库使用随机密码和回环随机端口,测试结束删除临时容器;不连接业务数据库。升级测试在同一临时实例内另建独立数据库,从迁移 1–4 重建旧结构再运行正式 Migrate。
```sh
make fmt
make test vet build
make integration
cd sdk/go
GOWORK=off go test -race ./...
GOWORK=off go vet ./...
```
全部通过。根模块、SDK 的 make test 使用竞态检测;不设置集成环境时数据库测试跳过,真实数据库证据以 make integration 的记录为准。
最终完整集成输出:存储包 13.464 秒(含 v4 升级 3.19 秒、安全重试事务用例 1.51 秒);SDK 连接/运行器包 62.580 秒(含安全重试 7.80 秒);真实进程故障包 11.263 秒。时间为一次测试运行耗时,**不是吞吐或延迟基准**。此前还完成过一轮主链路回归,本次因新增升级、约束、早到证据回执及示例,再运行完整集成。
## 3. 实际覆盖的安全行为
| 层次 / 用例 | 核对结果 |
| --- | --- |
| 纯决策 | 默认失败不重试、显式失败重试、未知效果安全分流、预算耗尽、旧不确定性保留、总截止/最晚开始、等于截止拒绝 |
| 退避 | 256 个尝试身份的有界分布、同身份确定值、指数上限、1 毫秒边界、Duration 溢出保护 |
| MySQL 事务 | 失败 → WAITING → 新尝试成功;等待期间不占许可、无任务结果;旧尝试保留分类与结果 |
| 原回执 | 同消息重报不改变下一时间;改 retryable 冲突;后续成功后重报旧失败仍返回原 WAITING 回执 |
| 早到证据 | 同一结果在开始前作为证据、开始后正式应用,新身份再次重报取 APPLIED 回执 |
| 未知效果 | IDEMPOTENT 可有限重试,RECONCILE_ONLY 直接转核查;后续失败/未知/预开始失效/取消/截止均不抹掉旧不确定性 |
| 恢复竞争 | 两个执行存储节点并发回收同一尝试,只安排一次重试、一次许可释放和一次 requeued 事件 |
| 暂停与迟到 | 队列暂停阻止重试领取;迟到成功只保存证据,不清除不确定性;恢复队列后新有效尝试可成功 |
| 原子回滚 | 用测试触发器使结果审计写入失败,任务、许可、事件与回执整体回滚;原消息重报后提交成功 |
| 数据库约束 | 运行中的尝试不能声明 retryable,UNKNOWN 证据不能声明 retryable,终态不能标为 effect_uncertain |
| 升级兼容 | v4 NEEDS_REVIEW 回填 true,其他状态默认 false;状态、revision、冻结策略不变;重复迁移无二次修改 |
| 摘要兼容 | 省略或 false 的 retryable 与旧 finish 摘要结构完全一致 |
| SDK 真服务 | 明确失败后成功、幂等未知后成功、默认未知转核查、可重试失败耗尽;查询次数、Epoch 和历史分类 |
| SDK ACK 故障 | 第一次失败已提交并进入退避后丢失 finish ACK,运行器重连核对原回执,未重置预算或重复调用同一尝试 |
| 协议 | Schema 与实际校验接受 FAILED/true,拒绝 UNKNOWN/true;既有 WSS 消息契约回归通过 |
部分服务层用例将受控测试任务的 available_at/lease_until 改为到期值,以触发竞争路径;它们不用于证明真实计时精度。SDK 用例没有此类时间改写,核对第二次领取不早于第一次结束后的最小退避。
原 P0–P2 存储、权限、精确 JSON、历史、大载荷、WSS、自动续租及实际进程强杀回归同步通过。真实进程测试仍是原 P2 用例,验证同次 ACK 恢复及 RECONCILE_ONLY 的未知转核查;本轮未额外宣称已完成“新重试等待期间强杀所有服务节点”测试。
## 4. 六个可运行示例
make integration 已运行 health、tasks、worker-protocol、worker、history、retry。新增 retry 的实际输出(任务 ID 略):
```text
task=<任务ID> state=SUCCEEDED attempts=2 first_retryable=true result={"text":"SAFE RETRY"}
worker_drained=true started=2 confirmed=2 running=0
```
第一次故障在产生业务效果前注入,第二次执行文本规范化。Worker 的 confirmed=2 统计尝试报告,不代表完成两个任务。例子每次使用独立操作和处理器身份;正常排空,记录与审计保留。没有外部业务系统副作用。
## 5. 当前边界与后续
- 自动重试不提供端到端 exactly-once;声明 IDEMPOTENT 的处理器需在真实资源侧验证幂等和迟到执行隔离。Go 不能终止忽略 context 的旧 goroutine。
- Cron/固定间隔、计划版本与漏跑、运行中取消、rate_per_second/配额背压、可靠事件订阅尚未完成。人工核查/恢复入口也尚未实现。
- 本次只验证停旧服务后升级结构;未验证混合版本滚动升级、向旧二进制回退、大表在线 DDL 或生产数据库故障切换。升级会为旧的未结束多次尝试任务启用已冻结策略,详见 [兼容说明](../api/retries.md#6-回执与升级兼容)。
- 本地单 MySQL 与回环多进程不能代替跨主机高可用、复制确认、备份恢复、RPO/RTO 或容量验收。并发与重试风暴负载将在 P6 固定拓扑和负载模型后测量。
下一步继续 P3 的调度与控制闭环,按 [持续计划](../08-development-plan.md) 同步实现、SDK、示例与实际回归。

View File

@@ -1,6 +1,6 @@
# Go SDK 接入说明
已实现健康查询、任务提交/查询/分页/待执行取消、队列创建/查询、[自动 Worker](worker-runtime.md)、[Worker 底层协议 SDK](worker.md) 和 [执行历史查询](../api/execution-history.md)。流程构建等后续接口按 [开发计划](../08-development-plan.md) 继续实现。
已实现健康查询、任务提交/查询/分页/待执行取消、队列创建/查询、[自动 Worker](worker-runtime.md)、[Worker 底层协议 SDK](worker.md) 和 [执行历史查询](../api/execution-history.md)。[安全重试](../api/retries.md) 已提供有限策略、WorkerFailure.Retryable、历史查询及完整例子。流程构建等后续接口按 [开发计划](../08-development-plan.md) 继续实现。
module:`git.yuntuo.hk/k2safe/orbit/sdk/go`。SDK 使用独立 go.mod,依赖 Go 标准库与 `github.com/coder/websocket v1.8.15`,不引用服务端模型或数据库实现。
@@ -97,7 +97,7 @@ if err != nil {
fmt.Println(cancelled.Task.State)
```
取消重报必须保留第一次取消时的 expected_revision,不能将响应中的新版本塞回同一幂等请求。当前只取消 WAITING 且不归属流程的任务。
取消重报必须保留第一次取消时的 expected_revision,不能将响应中的新版本塞回同一幂等请求。当前只取消 WAITING 且不归属流程的任务。若此前存在未知效果,取消结果为 NEEDS_REVIEW,不能解释为已撤销外部效果。
业务错误返回 *orbit.APIError,支持 errors.As 转为 *orbit.HTTPError 查看状态。健康错误或无法识别的网关错误使用 *orbit.HTTPError,不带未知响应正文。网络/上下文错误按标准库错误返回;超时并不能证明服务端没有提交。
@@ -176,4 +176,4 @@ task=<任务ID> state=SUCCEEDED attempts=1 evidence=1 reason=RESULT_CONFLICT dis
## 7. 后续交付
P2 已提供底层协议、[自动 Worker 运行器及示例](worker-runtime.md) 与历史查询,并通过 [Schema 及进程故障阶段验收](../reports/phase-2.md);P3 提供安全重试、计划与结果订阅;P4–P5 提供流程构建发布、信号、等待、轮询与人工恢复。每项随实现同步交付可运行示例及异常回归。
P2 已提供底层协议、[自动 Worker 运行器及示例](worker-runtime.md) 与历史查询,并通过 [Schema 及进程故障阶段验收](../reports/phase-2.md);P3 已实现 [安全重试与示例](../api/retries.md),计划和结果订阅继续开发;P4–P5 提供流程构建发布、信号、等待、轮询与人工恢复。每项随实现同步交付可运行示例及异常回归。

View File

@@ -65,7 +65,7 @@ SDK 的 Run 每个实例只能调用一次,包括已经正常退出的实例
| ReconnectMinDelay | 200 毫秒;至少 10 毫秒 | 连接失败退避起点 |
| ReconnectMaxDelay | 5 秒;不小于起点,最多 30 秒 | 指数退避上限,区间抖动;不增加任务尝试次数 |
`Run(ctx)` 正常排空返回 nil;同实例再次运行返回 ErrWorkerAlreadyRun。`Stats()` 返回进程内 Running、Started、RenewalsConfirmed、ReportsConfirmed、EvidenceConfirmed、Reconnects、DroppedEvents 快照,不是持久化业务成功率。
`Run(ctx)` 正常排空返回 nil;同实例再次运行返回 ErrWorkerAlreadyRun。`Stats()` 返回进程内 Running、Started、RenewalsConfirmed、ReportsConfirmed、EvidenceConfirmed、Reconnects、DroppedEvents 快照,不是持久化业务成功率。ReportsConfirmed 包含已确认的失败/未知尝试报告,一条重试任务可能计多次。
`Events()` 是 128 条缓冲的只读本地通知流,不读取也不会阻塞运行;缓冲满时丢弃并计数,Run 返回时关闭。通知只含类型、任务/尝试 ID 与稳定分类,没有业务结果或秘密。它不能代替审计、可靠结果订阅或任务查询。
@@ -78,15 +78,16 @@ SDK 的 Run 每个实例只能调用一次,包括已经正常退出的实例
- 重连复用实例和尝试身份,在开启容量之前 Resume 所有已知尝试;待确认 start/renew/finish 使用原消息 ID。认证、权限及协议错误终止 Run;临时服务/连接错误按退避核对。
- 结果未确认或处理器失去租约时,运行器降低对外声明的容量。仍未返回的函数占用真实并发槽;已在传输中的预占会有界接收,已知尝试最多为配置容量的两倍。
Run 的重连是控制消息恢复,不是业务自动重试。当前明确失败仍成为 FAILED;策略化业务重试在 P3 实现。
Run 的重连恢复同一次控制操作。平台的 [安全重试](../api/retries.md) 使用同一 Task.ID 派发新的 AttemptID/Epoch,运行器按新的有效授权调用处理器,预算与退避由服务端持久化管理。
## 5. 结果与错误分类
| 处理器结果 | SDK 报告 / 服务端行为 |
| --- | --- |
| 合法非 null JSON + nil error | SUCCEEDED |
| `&orbit.WorkerFailure{Code:"OBJECT_REJECTED", Details:...}` | FAILED;仅用于能确认的业务失败,Details 为空时为 `{}` |
| 普通 error | UNKNOWN / HANDLER_ERROR,进入 NEEDS_REVIEW |
| `&orbit.WorkerFailure{Code:"OBJECT_REJECTED", Details:...}` | FAILED;默认 Retryable=false,Details 为空时为 `{}` |
| `&orbit.WorkerFailure{Code:"TEMPORARY_REJECTION", Retryable:true}` | 明确失败可安全重试,平台仍核验次数、总时限和历史不确定效果 |
| 普通 error | UNKNOWN / HANDLER_ERROR;默认转核查,仅 IDEMPOTENT 且预算允许时可自动重试 |
| panic / runtime.Goexit | UNKNOWN / HANDLER_PANIC 或 HANDLER_ABORTED |
| 非法 JSON、超限结果、不支持数值或嵌套 | UNKNOWN / INVALID_HANDLER_RESULT |
| 处理器因租约取消返回错误 | UNKNOWN / LEASE_LOST |
@@ -94,7 +95,7 @@ Run 的重连是控制消息恢复,不是业务自动重试。当前明确失
原始异常和 panic 内容不写入结果或通知。Result/Details 最多 256 KiB,应优先使用对象引用。SDK 保留精确数字文本,并按服务端数字范围及完整帧的嵌套上限预检,避免产生永远无法接受的报告。
普通网络错误不能证明副作用未发生,因此不会自动解释为明确失败。UNKNOWN 和迟到证据的后续核查由平台管理能力与业务对账共同处理。
普通网络错误不能证明副作用未发生,因此不会自动解释为明确失败。UNKNOWN 由服务端结合真实幂等声明决定是否重试;迟到证据不能清除历史不确定效果。完整规则及可运行示例见 [安全重试](../api/retries.md)。
## 6. 退出与故障边界
@@ -102,7 +103,7 @@ Run 的重连是控制消息恢复,不是业务自动重试。当前明确失
ShutdownTimeout 从取消信号发生时开始计时,包含网络等待和处理器收尾。到期后取消控制请求及处理器 context,返回 `*WorkerRunError`;可用 `errors.Is(err, orbit.ErrWorkerShutdownTimeout)` 判断,Unconfirmed 和 Running 给出终止收尾时的快照。其他致命错误也带这些计数,便于识别未核对的执行。
Go 无法杀死忽略 context 的 goroutine。Run 可以有界返回,但这类函数可能仍在进程中执行;不得把返回错误解释为副作用已停止,也不要在同一进程盲目重建 Worker 叠加执行。服务端对失联执行按租约回收并进入核查。
Go 无法杀死忽略 context 的 goroutine。Run 可以有界返回,但这类函数可能仍在进程中执行;不得把返回错误解释为副作用已停止,也不要在同一进程盲目重建 Worker 叠加执行。服务端对失联执行按租约回收,依冻结安全策略决定重试或进入核查。
身份、尚未确认的结果和消息 ID 当前保存在内存,支持同进程重连;进程被强杀会失去这些内容。持久化租约、操作回执、最终结果和审计在 MySQL 中,业务外部效果仍需要业务幂等或查询核对。这一边界不等于端到端 exactly-once。

View File

@@ -28,6 +28,8 @@ task=<32位任务ID> final_state=SUCCEEDED attempts=1 result={"text":"HELLO ORBI
[完整源码](../../sdk/go/examples/worker-protocol/main.go) 实际提交 `{"text":" Hello Orbit "}`,领取并取得开始授权,执行去首尾空格/转大写,报告结果、查询当前状态并排空。没有生成文件或调用外部服务。每次运行创建独立任务与临时处理器名,避免消费前次示例遗留任务;历史和审计保留,不硬删除。
安全重试可通过 `WorkerFinish.Retryable=true` 声明明确失败允许再试,仅适用于 WorkerFailed;省略为 false。新尝试由服务端决定,详见 [安全重试](../api/retries.md)。
示例最多对开始/结果操作重连重报两次,保持原进程身份、尝试证明、消息 ID 和内容;业务函数只在首次取得有效开始确认后调用一次。网络结果仍不明时非零退出,不能推断服务端回滚。身份与待确认结果只保留在当前进程内,进程被终止后的未知结果由服务端回收进入核查。本示例处理很短的纯计算,不演示长任务自动续租。
## 2. 创建进程身份与连接
@@ -105,7 +107,7 @@ defer cancel()
| Finish 请求结果不明 | 保留原 finishID、Outcome、Result、ErrorCode,重连核对后原样重报 |
| 明确业务成功 | `WorkerSucceeded`,不带 ErrorCode |
| 可确认的业务失败 | `WorkerFailed`,带稳定 ErrorCode |
| 超时、失联或不能确认外部效果 | `WorkerUnknown`,带稳定 ErrorCode,进入核查 |
| 超时、失联或不能确认外部效果 | `WorkerUnknown`,带稳定 ErrorCode;默认转核查,仅 IDEMPOTENT 且预算允许时自动重试 |
| `Disposition=EVIDENCE_ONLY` | 内容已留作证据;不能当作任务成功,另查 GetTask 的权威状态 |
Result 是不超过 256 KiB 的非 null JSON,可传对象引用;原始异常不应写入稳定错误分类。SDK 拒绝非法 Unicode、重复键和过深 JSON,保留数字文本;数值范围仍由服务端最终校验。