# 提交后回执丢失:核查、暂停与有证据恢复 [完整源码](../../examples/workflow-recovery/main.go)使用真实Orbit、自动Worker和本机HTTP渠道夹具。夹具先保存外部提交结果,再直接关闭连接,确保Worker实际遇到传输错误。流程进入NEEDS_REVIEW后,程序在受控夹具中模拟一个已经核实事实的管理员,逐项提交恢复决定。 这是一份可运行的SDK接入与故障示例。夹具数据仅在本次进程内保存;真实系统需要持久证据和真实授权,不能把下面的演示核查逻辑接成任意业务的自动放行。 ## 1. 启动 需要Go 1.27.1、MySQL 8.4+,设置专用`ORBIT_MYSQL_DSN`。仓库根目录执行: ```sh go run ./cmd/orbit-migrate up mkdir -p .local chmod 700 .local go run ./cmd/orbit-admin bootstrap \ --application workflow-recovery-example --namespace default --environment development \ --queue workflows --workflow-access --workflow-run-access --worker-access \ --workflow-control-access --workflow-recovery-access --workflow-audit-access \ --output .local/workflow-recovery.credentials.json go run ./cmd/orbit ``` 初始化一次;原凭据文件不会被覆盖。另开终端,从仓库根目录运行: ```sh cd examples ORBIT_URL=http://127.0.0.1:8080 \ ORBIT_CREDENTIALS_FILE=../.local/workflow-recovery.credentials.json \ go run ./workflow-recovery ``` 开发凭据包含定义、启动、Worker、控制、恢复及审计权限,便于单命令演示。正式接入应使用HTTPS/WSS,并为业务Worker与管理员分别签发所需权限;Worker执行权不自动授予恢复权。 ## 2. 故障与完整调用顺序 流程包含publish和record两个顺序任务。输入为原操作身份及精确序号9007199254740993;publish只允许一次请求尝试,安全策略为RECONCILE_ONLY。record只在前一步被核实成功并解除控制门控后处理已确认回执。 1. 创建定义、发布v1并启动;Worker向本机渠道POST一次。渠道保存结果,然后关闭连接,不返回HTTP响应。 2. 处理器返回普通error。Orbit保留UNKNOWN尝试,任务和流程进入NEEDS_REVIEW,不重发POST。 3. 查询当前步骤、任务及尝试,确认唯一尝试已结束、Worker处理器已退出且结果报告已确认,再显式PAUSE流程。 4. 用原操作身份GET渠道证据,核对operation_id、状态CONFIRMED及精确序号。夹具能证明这是唯一一次提交,且提交处理已经结束;真实系统必须另行证明历史执行已结清,仅有一次GET成功不够。 5. `ReviewWorkflowTask`提交执行结论:当前实例版本、任务版本、execution_settled=true、结果及证据引用。使用原键原命令再调用一次,确认同一审计回执。 6. 等待核查结果被内部消费,重新读取步骤及实例版本;`ResolveWorkflowStep`确认逻辑步骤成功,保留原激活。 7. `ContinueWorkflow`声明核查事项已解决。此时仍为paused,record步骤保持BLOCKED,没有任务被提前建立。 8. 查询三条管理审计,核对操作人、原因和同一证据引用;再单独调用`ControlWorkflow(RESUME)`释放暂停。 9. Worker完成record,流程成功;查询原尝试仍是UNKNOWN,原实例截止未变,最后排空Worker。 每个管理决定都有独立稳定操作键。核查使用实例revision,控制使用control_revision,两者不可互换。读到新事实后重新核查再形成下一条命令;不能自动把STATE_CONFLICT改为最新版本重报。 ## 3. SDK关键字段 完整程序使用[六类恢复API中的三个方法](../api/workflow-recovery.md)。执行核查示意: ```go command := orbit.ReviewWorkflowTaskRequest{ WorkflowRecoveryDecision: orbit.WorkflowRecoveryDecision{ ExpectedRevision: currentStep.Revision, Reason: "已核实原操作、结果和所有相关执行已结束", EvidenceRef: evidenceRef, }, TaskID: task.ID, ExpectedTaskRevision: task.Revision, ExecutionSettled: true, Outcome: orbit.TaskSucceeded, Result: verifiedResult, } receipt, err := client.ReviewWorkflowTask(ctx, workflowID, reviewKey, command) if err != nil { return err } // receipt是管理事务的冻结回执,下一项决定前应读取新的当前事实。 ``` 示例证据引用形如`evidence:local-fixture/`,明确表示本地受控夹具,不能冒充持久业务证据。平台只保存受控引用及审计,不请求该地址。真实证据应由接入方按权限、保留及审计要求持久保存。 ## 4. 输出与历史保护 程序先打印可查询的操作身份和workflow ID,最终应输出: ```text recovery_verified=true state=SUCCEEDED submissions=1 unknown_history_preserved=true review_deduplicated=true pause_preserved=true audits=3 sequence=9007199254740993 worker_drained=true ``` 这些标志来自实际API和Worker统计:只发生1次外部POST;同键核查返回同一audit_id;task-review、step-resolve、continue三条审计具有原因、主体和证据;恢复没有解除PAUSE;旧UNKNOWN尝试和结束时间没有改写;两项业务任务均已结束并确认。 最终任务的权威结果可以是SUCCEEDED,同时原尝试保留UNKNOWN,两者并不矛盾。核查增加新的决定和证据,不能抹去当时无法确认结果的事实。 ## 5. 失败及重跑边界 本次运行最多45秒,Worker排空最多5秒。任何身份、证据、结果或版本不符都会非零退出;失败不会自动调用RetryWorkflowStep、换键重发外部POST或解除核查。 重新运行命令创建新的独立演示流程,不是对上一流程的恢复。生产接入方必须持久保存每条命令和幂等键;响应未知时使用原命令原键核对,旧版本冲突需要重新核查。示例退出时关闭本机HTTP夹具与Worker,平台流程和历史保留;若中途失败,夹具停止后不能再用其内存结果作为后续人工恢复依据。 `make integration`运行此示例以及独立SDK中的六类恢复回归;另有节点强杀、恢复代次和原预算验证。记录见[P4/P5核对](../reports/phase-4-5.md)。这不替代真实业务的幂等、权限、证据和生产故障恢复验收。