# Weave 有界查询的人工恢复 状态:内部 `RetryStep` 已支持 POLL 新激活,保留跨激活的业务轮次、同轮剩余尝试和绝对截止;附属最终信号等待随新激活换代。[核查后PENDING接续和独立等待重建](18-workflow-wait-recovery.md)已接入内部服务,公共实例/恢复 HTTP、独立 SDK 和可运行接入示例继续按[开发计划](08-development-plan.md)交付。验证范围见[阶段报告](reports/phase-5-poll-recovery.md)。 ## 1. 业务轮次与请求尝试 查询成功且业务结果为 PENDING 时,后台按原间隔开启下一业务轮。请求失败、未知或结果不符合契约时,进入核查;人工恢复重查同一业务轮,不能通过换激活获得新的请求预算。 例如固定定义最多查询 3 轮,每轮最多尝试 3 次: | 工作 | 激活 | 业务轮次 | 本任务尝试上限 | 沿用的截止 | | --- | --- | --- | --- | --- | | 原查询请求失败,已用 1 次 | 1 | 1 | 3 | 第一轮原任务截止、原查询总截止 | | 人工恢复同轮 | 2 | 1 | 2 | 同上,人工操作不重新计时 | | 恢复请求成功返回 PENDING,间隔到达 | 2 | 2 | 3 | 新业务轮的任务截止受原查询总截止截短 | | 第二轮请求失败,已用 1 次,再人工恢复 | 3 | 2 | 2 | 第二轮第一次任务的绝对截止、原查询总截止 | 恢复前核验真实尝试行数、任务计数、原处理器/版本/策略/输入、业务键、互斥键、所有者、连续激活及 `retry_of` 链。每一业务轮的任务总数也不超过原单轮尝试上限,防止反复取消未开始任务而无限制造激活。一次成功查询已返回 PENDING 的轮次不能人工重试;轮次耗尽须核查最终事实或取消。若 UNKNOWN 经 ReviewTask 被核实为成功 PENDING,须等待结果消费,再通过[ContinuePoll](18-workflow-wait-recovery.md)显式恢复原等待;不能借同轮 RetryStep 绕过间隔或次数。 ## 2. 恢复前提和事务 沿用[人工核查约定](16-workflow-recovery.md):显式 `workflows:recover`、最新实例/步骤版本、当前激活、稳定操作键、原因、证据和 `safe_to_retry=true`。只有 NEEDS_REVIEW 实例中待核查、失败或取消的 POLL 可恢复。已有停止意图、实例/查询/原请求截止到达、次数耗尽或历史执行未知时拒绝。 UNKNOWN 必须先核实全部外部执行结束,经 ReviewTask 保存确定结果,并消费其新版本 Inbox 后再恢复。声明不能替代服务端的历史归属、执行、许可和未消费/隔离来源检查。 新激活、新 Pulse 任务、同轮记录、预算、最终信号等待、时钟、额度、审计和幂等回执同事务提交。新任务受当前应用及队列配额约束;满额、唯一性冲突或审计失败全部回滚。相同操作键并发重报只返回一个冻结回执,仍核验当前权限。 恢复后实例仍是 NEEDS_REVIEW,执行门控关闭;继续核查通过后显式 Continue,原 paused 为 true 时还需 RESUME。恢复不会撤销原取消/失败停止目标。 ## 3. 数据与历史 [迁移 000022](../internal/storage/mysql/migrations/000022_workflow_poll_recovery.up.sql)为 `PollState` 新增: | 字段 | 含义 | | --- | --- | | first_round | 当前激活保存的首个业务轮次。人工恢复沿用旧激活的最后业务轮编号 | | first_round_attempts_used | 该轮在此前激活已用的请求尝试数,从原单轮预算扣除 | `used_rounds` 仍是从首次激活起的业务轮次编号,不是当前激活行数。当前激活轮次必须完整覆盖 `[first_round, used_rounds]`;正常进入下一轮时继续 `used_rounds+1`。查询总截止基于首次激活的创建时间,当前激活创建时间不成为新起点。同轮的新任务以 `retry_of` 关联前一任务,沿用该轮第一次任务的绝对截止。 旧激活状态、输出、任务结果、执行尝试、轮次及信号证据保留;旧查询预算关闭在途/后续检查点并保留原结清时间。来源事件仍固定原激活和任务,旧激活结果只记录 STALE_ACTIVATION,不能完成新任务。停止结清核验所有历史任务,轮次归属按各自的 `first_round` 和 `used_rounds` 检查。 迁移将历史数据补为 `first_round=1`、已用历史尝试为 0,不更改旧轮次、原截止、预算、状态版本及时间。新增两个 Go 字段和 SQL 列均有中文注释。当前共 35 个持久化实体、305 个直接字段声明;无外键、无级联删除。 升级需停止旧写入节点,再执行正式迁移并启动新版。旧节点不了解跨激活轮次与截止传递,不能在新节点创建恢复激活后继续混跑。 ## 4. 最终信号的换代 POLL 带最终信号时,恢复创建同契约、同精确关联键、同原截止的新等待,目标激活代次加一。旧已消费信号及旧 winner 保留,不能自动成为新等待赢家。旧代次再次受理的信号保存为 REJECTED / STALE_ACTIVATION;新代次须重新提交有证据的信号。 当前代次仍有 PENDING 信号时,先消费这些事实,恢复拒绝直接丢弃。已有赢家时核对其受理证明与关联后保留历史。最终信号原截止已过时不重新开始等待。 这些规则覆盖 POLL 附属等待;独立 SIGNAL/TIMER 使用[RearmWait](18-workflow-wait-recovery.md),公共接口仍待交付。 ## 5. 内部调用场景 | 场景 | 调用顺序 | | --- | --- | | 已知请求失败且仍有预算 | 读取当前实例/步骤 → RetryStep → 核对新任务及剩余预算 → Continue → Worker 执行 → 原查询分类逻辑接续 | | 请求 UNKNOWN | 核实外部执行结束 → ReviewTask → 等待 Inbox 消费 → 读取当前版本 → RetryStep → Continue | | 恢复后收到旧结果 | 内部 Inbox 消费只保存旧激活处置,不推进当前激活 | | 恢复后收到新最终回执 | 用新激活代次发送同契约信号 → 按原受理时间裁决并结清当前查询 | | 原轮次/请求预算或截止耗尽 | 拒绝新查询;按证据 ResolveStep 或继续取消/停止结清 | 实现入口是[内部恢复服务](../weave/recovery/service.go)和[POLL 存储事务](../internal/storage/mysql/workflow_poll_retry.go)。这些场景尚不是公共 SDK 用法;公共接口、可运行示例和生产拓扑验收仍须交付。