feat: persist workflow signals and arbitrate poll completion

This commit is contained in:
2026-09-18 03:52:50 +08:00
parent e2baffbe37
commit 7d5ebfcb09
34 changed files with 1958 additions and 113 deletions

View File

@@ -1,6 +1,6 @@
# Weave 流程编排功能与设计
> 版本 0.4 · 2026-09-18。首版功能范围已确认。定义结构、发布校验与不可变版本已实现,见[定义格式](api/workflow-definition-format.md)和[发布API](api/workflow-definitions.md);[实例受理](11-workflow-admission.md)、[结果消费与根推进](12-workflow-runtime.md)、[TIMER与POLL时钟](13-workflow-clock.md)已接入。信号、完整管理及实例公共API/SDK仍待P4–P5实现验收。
> 版本 0.5 · 2026-09-18。首版功能范围已确认。定义结构、发布校验与不可变版本已实现,见[定义格式](api/workflow-definition-format.md)和[发布API](api/workflow-definitions.md);[实例受理](11-workflow-admission.md)、[结果消费与根推进](12-workflow-runtime.md)、[TIMER与POLL时钟](13-workflow-clock.md)已接入。[信号等待及最终查询竞争](14-workflow-signals.md)已接入内部运行器;完整管理及实例公共API/SDK仍待P4–P5实现验收。
## 1. 职责与边界

View File

@@ -75,7 +75,7 @@
| weave | `InboxEvent` | `weave_inbox` |
| weave | `PendingAdvance` | `weave_pending_advances` |
`orbit_events` 保存原始状态事件,`orbit_result_events` 使用相同事件 ID 保存队列、提交序号和冻结快照,`orbit_event_streams` 串行分配命名空间内的结果序号;对外投递使用 `orbit_deliveries`,内部流程结果原子写入 `weave_inbox`及其独立冻结快照,已接入[实例消费推进](12-workflow-runtime.md)。`weave_pending_advances` 保存根步骤续接和TIMER/POLL时钟检查点。
`orbit_events` 保存原始状态事件,`orbit_result_events` 使用相同事件 ID 保存队列、提交序号和冻结快照,`orbit_event_streams` 串行分配命名空间内的结果序号;对外投递使用 `orbit_deliveries`,内部流程结果原子写入 `weave_inbox`及其独立冻结快照,已接入[实例消费推进](12-workflow-runtime.md)。`weave_pending_advances` 保存根步骤续接和TIMER/SIGNAL/POLL时钟检查点。
## 4. 已落实的数据库约束
@@ -199,4 +199,8 @@ Wait新增SignalName和PayloadVersion两个中文注释字段;correlation_key
## 等待时钟回填(000019)
复用PendingAdvance保存ROOT、TIMER、POLL检查点;新增内部JSON结构字段均有中文注释,不新增持久化列或实体,仍为35个实体/295个直接持久化字段。TIMER/POLL检查点固定激活ID,实际推进核验同命名空间归属、原截止及剩余预算。迁移只回填历史有效等待的唤醒身份和时间,保留已有检查点;无外键、无级联,见[时钟运行约定](13-workflow-clock.md)。
复用PendingAdvance保存ROOT、TIMER、POLL检查点;新增内部JSON结构字段均有中文注释,不新增持久化列或实体,截至该迁移为35个实体/295个直接持久化字段。TIMER/POLL检查点固定激活ID,实际推进核验同命名空间归属、原截止及剩余预算。迁移只回填历史有效等待的唤醒身份和时间,保留已有检查点;无外键、无级联,见[时钟运行约定](13-workflow-clock.md)。
## 信号身份与赢家增量(000020)
Signal新增payload_version、target_activation_sequence、evidence_ref、disposition、available_at;Wait新增winner_signal_id。当前35个实体、含公共Record的301个直接持久化字段声明,全部中文注释。新信号精确关联最多512字节,存储兼容历史764字节;过期检查退避不改变expires_at。赢家唯一约束只防重复,实例/等待/激活/信号归属及不可变受理证明仍由事务验证,外键保持0。历史不完整身份隔离和升级回填见[信号约定](14-workflow-signals.md)。

View File

@@ -28,14 +28,22 @@
| P1 任务受理闭环 | 应用凭据与作用域权限、队列、幂等提交、查询、待执行取消、受理事件;SDK 客户端 | API/SDK 创建查询闭环;重复键与内容冲突、响应未确认重报、跨命名空间拒绝、参数边界 | 已完成并通过阶段回归(见 P1 报告;真实断路/提交 ACK 丢失注入留后续故障阶段) |
| P2 Worker 执行闭环 | WSS 接入、能力/容量、领取与开始、租约、续租、结果确认、排空与恢复;Worker SDK | 真 Worker 执行;失联/重连/重复确认/旧尝试拒绝;业务效果未知转核查;节点故障接管 | 已完成并通过阶段回归:服务端、自动 SDK、历史查询、完整 Schema 与真实进程强杀恢复;测试拓扑和剩余生产故障矩阵见 P2 验收报告 |
| P3 Pulse 完整能力 | 安全重试、延时、Cron/固定间隔、漏跑策略、计划版本、运行中取消、并发许可、配额/背压、可靠结果订阅、批量受理、独立任务核查/人工重试、查询筛选与事务Outbox接入 | 多实例领取/触发竞争、重试上限、取消竞争、队列隔离、积压恢复、事件重复交付 | 已完成平台功能阶段验收:[原范围逐项核对](reports/phase-3.md)、[本轮完整回归](reports/phase-3-delay-recovery.md)。补齐相对延迟、改参数关联示例和四种重试全停恢复场景;20个可运行示例全部通过。全平台生产门槛及SDK首版交付剩余项仍按P6–P7推进 |
| P4 Weave 主流程 | SDK 定义构建与发布校验、实例、顺序/分支/结构化并行汇合、信号/定时等待、版本绑定 | 重启接续、早到/重复信号、并行竞争、内部事件重放、发布版本兼容;端到端通用流程 | 定义格式/词法类型校验、不可变发布、独立SDK及六类节点定义例子已实现,已通过[子项验收](reports/phase-4-definitions.md);已补[有限决策器及内部结果交付](reports/phase-4-engine-core.md);[实例受理及首批事务](reports/phase-4-admission.md)已实现;[结果消费和根接续](reports/phase-4-runtime.md)已接入后台,顺序/分支/并行及真实进程恢复已验证;[TIMER与后续POLL轮次](13-workflow-clock.md)已接入;信号、完整控制和实例公共API/SDK继续开发 |
| P5 Weave 恢复与查询 | 有界查询、暂停继续、取消、人工核查恢复、控制门控与审计 | 轮次与尝试独立预算;暂停/开始/回执/超时竞争;恢复保留预算;未知结果不盲目重复 | 有界查询轮次/尝试独立预算及原截止恢复已接入内部运行器;信号竞争、完整控制/人工恢复及公共API/SDK继续开发 |
| P4 Weave 主流程 | SDK 定义构建与发布校验、实例、顺序/分支/结构化并行汇合、信号/定时等待、版本绑定 | 重启接续、早到/重复信号、并行竞争、内部事件重放、发布版本兼容;端到端通用流程 | 定义格式/词法类型校验、不可变发布、独立SDK及六类节点定义例子已实现,已通过[子项验收](reports/phase-4-definitions.md);已补[有限决策器及内部结果交付](reports/phase-4-engine-core.md);[实例受理及首批事务](reports/phase-4-admission.md)已实现;[结果消费和根接续](reports/phase-4-runtime.md)已接入后台,顺序/分支/并行及真实进程恢复已验证;[TIMER与后续POLL轮次](13-workflow-clock.md)已接入;[信号消费和最终查询竞争](14-workflow-signals.md)已接入内部运行器;完整控制和实例公共API/SDK继续开发 |
| P5 Weave 恢复与查询 | 有界查询、暂停继续、取消、人工核查恢复、控制门控与审计 | 轮次与尝试独立预算;暂停/开始/回执/超时竞争;恢复保留预算;未知结果不盲目重复 | 有界查询轮次/尝试独立预算及原截止恢复已接入内部运行器;最终信号竞争及在途查询结清已接入;完整控制/人工恢复及公共API/SDK继续开发 |
| P6 生产验证与运维 | 完整监控、凭据轮换、备份恢复、滚动升级、运行手册、可复现负载和故障工具 | 对照生产验收清单执行,报告环境/指标/遗留项;容量、RPO/RTO 按真实拓扑验证 | 待开发 |
| P7 首版交付核对 | SDK 兼容与发布准备、完整 API 手册、场景示例、全量回归及交付清单 | 新接入者按文档跑通全部示例;所有首版能力可追溯到实现与测试;报告实际生产边界 | 待开发 |
SDK、文档与示例随 P1–P5 同步开发,P7 是全量核对,不是最后才开始补文档。
P4/P5定时等待与后续查询轮次的实现及回归记录见[时钟报告](reports/phase-4-clock.md)。实例公共入口继续与信号/控制能力同步交付,不能将内部存储链路作为完整SDK验收。
P4/P5定时等待与后续查询轮次的实现及回归记录见[时钟报告](reports/phase-4-clock.md)。外部信号与最终查询竞争见[信号报告](reports/phase-4-signals.md)。实例公共入口继续与控制能力同步交付,不能将内部存储链路作为完整SDK验收。
### P4/P5 下一步实施顺序
1. 完成FAIL_AFTER_SETTLED与显式取消的分支结清:持久取消意图、复用Pulse取消、收集已发出查询/任务结果、等待全部已激活工作结清后才进入失败或取消终态。效果未知仍保留核查。
2. 完成实例暂停/继续/取消内部操作,以control_revision、操作幂等身份、明确原因及审计保护控制决定;暂停不延长原截止,继续不覆盖核查或取消状态。
3. 完成人工核查恢复与旧激活隔离,保留原预算、结果和证据,不能通过换激活/新键绕过未知效果或执行上限。
4. 同步开放实例/步骤/信号/来源/审计HTTP、独立SDK和端到端场景,覆盖原键恢复、权限、版本冲突、竞争与进程全停;公共能力逐项记录实际验收结果。
5. 转入P6/P7的生产故障、容量、运维、发布与全部文档核对。内部存储阶段通过不会提前勾选公共接口或生产验收。
### P3 功能范围核对

View File

@@ -74,7 +74,7 @@ v16及以前的未处理记录缺少冻结来源,升级后统一标为QUARANTI
## 4. 后续存储适配必须满足的条件
以下保留存储运行器的完整要求。第1项已按[受理报告](reports/phase-4-admission.md)验证;第2–4项结果消费和根接续已接入[实际运行器](12-workflow-runtime.md),第5项的TIMER/POLL已接入[时钟运行器](13-workflow-clock.md),其余信号、完整控制、接口和SDK仍继续实现:
以下保留存储运行器的完整要求。第1项已按[受理报告](reports/phase-4-admission.md)验证;第2–4项结果消费和根接续已接入[实际运行器](12-workflow-runtime.md),第5项的TIMER/POLL已接入[时钟运行器](13-workflow-clock.md),第5项的信号等待已接入[信号运行器](14-workflow-signals.md),完整控制、接口和SDK仍继续实现:
1. 启动时认证、固定不可变版本/输入和策略、校验全部队列、计算实例截止,并在同事务创建实例、门控、首批激活/任务及审计回执。相同身份返回原实例。
2. 消费者领取内部事件后核对命名空间、来源任务版本、激活/轮次及当前步骤版本;消费确认、步骤变化、后续任务或持久化待续记录同事务提交。

View File

@@ -1,6 +1,6 @@
# Weave 实例受理与首批工作
状态:内部Go服务与MySQL存储适配已实现实例Start/Get/Steps、固定版本、首批决策落库和可靠结果交付。[后台Inbox消费和根待续扫描](12-workflow-runtime.md)已接通。**尚未开放实例HTTP路由、启动SDK或命令行入口;信号运行器和完整控制仍继续开发。** Weave相关公开SDK目前仅支持定义发布。[TIMER和后续POLL轮次](13-workflow-clock.md)已接入,TASK及组合节点已可自动推进,不能将此解释成全部等待/控制能力已实现。
状态:内部Go服务与MySQL存储适配已实现实例Start/Get/Steps、固定版本、首批决策落库和可靠结果交付。[后台Inbox消费和根待续扫描](12-workflow-runtime.md)已接通。**尚未开放实例HTTP路由、启动SDK或命令行入口;完整控制仍继续开发;[信号运行器](14-workflow-signals.md)已接入。** Weave相关公开SDK目前仅支持定义发布。[TIMER和后续POLL轮次](13-workflow-clock.md)已接入,TASK及组合节点已可自动推进,不能将此解释成全部等待/控制能力已实现。
## 1. 受理事务
@@ -49,9 +49,9 @@ Get返回当前实例视图;Start重报返回受理时冻结的原始视图,
| 活跃实例 | 每个namespace最多10000个RUNNING/WAITING/CANCELLING/NEEDS_REVIEW实例;终态不计入;满额仍可恢复原受理回执 |
| 信号关联 | 新受理最多512字节;数据库兼容旧191字符最长764字节,升级不截断历史 |
| 截止 | 数据库时间生成;任务、查询与等待均不超过实例总截止 |
| 自动运行 | Inbox消费、根待续、TIMER到期及后续POLL轮次已接入;信号消费及完整控制仍待实现 |
| 自动运行 | Inbox消费、根待续、TIMER到期及后续POLL轮次已接入;SIGNAL与最终查询竞争已接入,完整控制仍待实现 |
迁移000018为Wait增加signal_name和payload_version两个带中文注释的可空字段;NULL仅表示TIMER或尚未核实的历史元数据,未来消费者不得把历史NULL自动解释为有效的新信号等待。correlation_key转为精确字节存储;实例business_key改为utf8mb4_0900_bin。35个实体、含公共Record的295个直接持久化字段声明,全部有中文注释,继续保持零外键。
迁移000018为Wait增加signal_name和payload_version两个带中文注释的可空字段;NULL仅表示TIMER或尚未核实的历史元数据,未来消费者不得把历史NULL自动解释为有效的新信号等待。correlation_key转为精确字节存储;实例business_key改为utf8mb4_0900_bin。该迁移时为35个实体、含公共Record的295个直接持久化字段声明,全部有中文注释,继续保持零外键。
## 5. 下一步接入

View File

@@ -1,6 +1,6 @@
# Weave 结果消费与自动推进
状态:内部实例的TASK结果消费、顺序、分支、有限并行汇合和根检查点接续已接入Orbit后台运行器。[定时等待和后续查询轮次](13-workflow-clock.md)也已接入。实例公共HTTP/SDK仍未开放,信号、失败后的分支取消结清及人工恢复继续开发,不能将本阶段等同于完整Weave交付。
状态:内部实例的TASK结果消费、顺序、分支、有限并行汇合和根检查点接续已接入Orbit后台运行器。[定时等待和后续查询轮次](13-workflow-clock.md)也已接入。[信号等待与最终查询竞争](14-workflow-signals.md)已接入。实例公共HTTP/SDK仍未开放,失败后的分支取消结清及人工恢复继续开发,不能将本阶段等同于完整Weave交付。
## 1. 后台运行和事务边界
@@ -45,7 +45,7 @@ flowchart LR
暂停、关闭门控或停用应用/命名空间时,继续收集已经落库的结果,但不激活新步骤;若还有后继,将其保存为待续。重新允许推进后,从持久记录继续。测试通过直接设置控制状态验证该行为;暂停/继续公共接口尚未交付。
已过实例总截止时不创建后继,未能全部完成则进入核查。最后的有效任务结果在截止前已由Pulse接受,而消费发生较晚时,可以整理完整结果并完成实例。暂停或重启不延长截止。TIMER与POLL的时钟已接入;SIGNAL及其超期裁决仍需后续实现。
已过实例总截止时不创建后继,未能全部完成则进入核查。最后的有效任务结果在截止前已由Pulse接受,而消费发生较晚时,可以整理完整结果并完成实例。暂停或重启不延长截止。TIMER与POLL的时钟已接入;[SIGNAL及其超期/最终查询裁决](14-workflow-signals.md)已接入。
## 4. POLL与失败策略的当前边界
@@ -56,7 +56,7 @@ flowchart LR
- 轮次耗尽、查询截止、未知/非法值或单轮请求失败:进入核查,不新建轮次绕过Pulse重试预算。
- 业务明确失败:按固定实例失败策略处理;REVIEW进入核查,FAIL_AFTER_SETTLED进入CANCELLING并关闭门控。
下一轮查询派发及到期扫描已实现,详见[时钟、预算与恢复](13-workflow-clock.md)。**尚未实现FAIL_AFTER_SETTLED的剩余分支取消/收集/失败终态判定。** CANCELLING保留未结清事实,不提前宣称FAILED。信号抢先完成时与当前在途查询的结清竞争,也属于后续必须实现的功能。
下一轮查询派发及到期扫描已实现,详见[时钟、预算与恢复](13-workflow-clock.md)。**尚未实现FAIL_AFTER_SETTLED的剩余分支取消/收集/失败终态判定。** CANCELLING保留未结清事实,不提前宣称FAILED。信号抢先到达时保留当前查询,实际结果结清及竞争见[信号运行约定](14-workflow-signals.md)。
## 5. 回归与后续工作
@@ -64,4 +64,4 @@ flowchart LR
进程回归由`make integration`构建二进制后显式执行。缺少一次性数据库或`ORBIT_TEST_SERVER_BINARY`时测试会跳过;跳过不构成进程恢复证据。验证记录见[本阶段报告](reports/phase-4-runtime.md)。
下一步仍包括持久化信号等待及查询最终信号竞争、完整控制与人工恢复、实例/来源公共API、独立SDK和通用端到端场景。P4整体及P5–P7继续按[完整计划](08-development-plan.md)执行。
持久化信号等待及查询最终信号竞争已按[信号运行约定](14-workflow-signals.md)实现。下一步仍包括完整控制与人工恢复、实例/来源公共API、独立SDK和通用端到端场景。P4整体及P5–P7继续按[完整计划](08-development-plan.md)执行。

View File

@@ -1,10 +1,10 @@
# Weave 持久化定时等待与查询轮次
状态:TIMER到期推进和POLL后续业务轮次已接入Orbit后台运行器。复用MySQL和既有待续索引,不增加外部调度服务或数据库外键。实例公共HTTP/SDK、SIGNAL消费、查询最终信号竞争以及完整控制/人工恢复仍未交付。
状态:TIMER到期推进和POLL后续业务轮次已接入Orbit后台运行器。复用MySQL和既有待续索引,不增加外部调度服务或数据库外键。[SIGNAL消费与查询最终信号竞争](14-workflow-signals.md)已进一步接入;实例公共HTTP/SDK以及完整控制/人工恢复仍未交付。
## 1. 时钟和恢复身份
`weave_pending_advances`同时保存ROOT、TIMER、POLL三类检查点。唯一范围为命名空间、实例和步骤;时钟检查点额外固定当前激活ID,避免旧激活唤醒新工作。扫描只提供候选,实际推进仍须持锁核验固定版本、父记录、步骤/激活状态、任务所有者、门控和原始预算。
`weave_pending_advances`同时保存ROOT、TIMER、SIGNAL、POLL四类检查点。唯一范围为命名空间、实例和步骤;时钟检查点额外固定当前激活ID,避免旧激活唤醒新工作。扫描只提供候选,实际推进仍须持锁核验固定版本、父记录、步骤/激活状态、任务所有者、门控和原始预算。
| 检查点 | 到期时间来源 | 推进结果 |
| --- | --- | --- |
@@ -50,7 +50,7 @@ TIMER由待续表直接唤醒,不创建Pulse业务任务,也不占Worker执
当前任务仍在途且查询总截止已到时,实例转NEEDS_REVIEW并关闭新执行门控;步骤保持ACTIVE、轮次保持当前任务引用,继续收集Pulse结果。时钟不释放执行许可,也不假定外部请求已经停止。Pulse恢复将失联执行记录为UNKNOWN后,内部消费再结清当前查询记录,保留核查状态。
最终信号与在途查询的竞争尚未实现。定义可以描述`final_signal`并建立等待,但当前只能通过查询结果结清;不能据此承诺外部信号已经可以消费。
最终信号与在途查询的竞争已按[信号运行约定](14-workflow-signals.md)接入:保存赢家后等待Pulse查询结清,不能提前释放许可或覆盖未知效果。实例及信号公共HTTP/SDK尚未开放。
## 5. 升级和验证
@@ -60,4 +60,4 @@ TIMER由待续表直接唤醒,不创建Pulse业务任务,也不占Worker执
[存储回归](../internal/storage/mysql/workflow_clock_integration_test.go)覆盖到期、竞争、预算、暂停/满额、回滚、归属损坏与未知结果;[v18升级回归](../internal/storage/mysql/workflow_clock_upgrade_integration_test.go)验证回填及原预算保留;[真实进程回归](../internal/storage/mysql/workflow_runtime_process_integration_test.go)覆盖全部控制进程退出后的定时和查询接续。执行记录见[阶段报告](reports/phase-4-clock.md)。
下一阶段继续SIGNAL早到/重复/超期处理、信号与查询竞争、失败分支结清、公共控制及实例SDK,完整范围见[持续计划](08-development-plan.md)。
SIGNAL早到/重复/超期处理及最终查询竞争已接入;下一阶段继续失败分支结清、公共控制及实例SDK,完整范围见[持续计划](08-development-plan.md)。

View File

@@ -0,0 +1,68 @@
# Weave 外部信号与最终查询结果
状态:内部 Go 信号受理/查询、SIGNAL 等待、早到保存、截止处理及 POLL 最终信号竞争已接入 MySQL 和后台运行器。**实例与信号的公共 HTTP/独立 SDK 尚未开放**;完整控制、人工恢复和失败分支结清继续按[持续计划](08-development-plan.md)实现。定义发布 SDK 不等于实例运行 SDK。
## 1. 受理身份与权限
[内部契约](../weave/signal/service.go)为 `Send(token, workflowID, idempotencyKey, input)` 与 `Get(token, workflowID, signalID)`。输入绑定固定定义中的信号名称、内容版本、步骤键、显式目标激活序号和精确操作关联,附内容、受理原因与可选证据引用。
| 输入 | 限制及语义 |
| --- | --- |
| name / payload_version | 定义中冻结的名称/内容契约;ASCII,最多128/32字符 |
| target_step_key | 固定定义的目标步骤,最多64字符,必须为SIGNAL或带最终信号的POLL |
| target_activation_sequence | 显式正数;首次和早到信号为1,旧信号不能跟随人工恢复后的新激活;尚不存在的未来代次拒绝 |
| correlation_key | 1至512字节UTF8,精确匹配,保留尾部空格;不由调用者随意省略 |
| payload | 固定类型的精确JSON,最多256KiB;显式null与缺失不同;POLL最终信号不能是PENDING |
| reason / evidence_ref | 必填原因最多1024字符;可选证据引用最多512字符、不含空白/控制符;平台不访问引用地址 |
提交要求显式 `workflows:signal` 权限;查询要求 `workflows:read`。两者都核对当前凭据、命名空间、实例固定版本及全部相关队列权限。actor来自认证凭据,不能请求自报。现有bootstrap的定义发布授权不会自动授予信号操作权限。
提交按认证 → 应用额度 → 原幂等身份 → 排序队列 → 实例/门控/步骤持锁。首次受理将信号、检查点、事件、审计和冻结回执同事务写入。审计包含原请求摘要、受理时间和总截止;运行器会核对受理证明,参数被替换时转核查,数据库连接/SQL错误则保留原来源并退避重试。
同一实例最多保存1024条信号,包含拒绝及终结历史;原幂等身份恢复先于上限检查。同键同内容返回首次冻结回执,同键不同内容冲突。重报仍校验当前权限。受理回执中的PENDING不跟随消费变化,当前状态用Get读取。
## 2. 早到、迟到与消费
目标步骤尚未激活时,信号保持PENDING;步骤激活会创建立即到期的检查点扫描早到信号。每个等待固定名称、版本、操作关联、激活序号和原截止,消费选择按受理时间、信号ID排序的第一条匹配记录。
- 信号必须在等待截止之前受理,等于截止不算按时。按时受理但控制进程稍后恢复,可以继续收集该事实。
- 暂停或应用停用仍可收集结果;后继工作受原门控、额度及实例总截止约束。
- 已激活时的关联不符、旧代次、已结清步骤或实例会保存明确拒绝原因;内容类型/定义契约无效的请求直接拒绝,不产生有效信号。
- 同一等待只记录一个winner_signal_id;其余同代次信号保存WAIT_SETTLED或QUERY_SETTLED等原因。所有关联由事务核验,不使用外键。
- SIGNAL在没有按时匹配信号的情况下到期,将等待标为EXPIRED、步骤和实例转NEEDS_REVIEW,并关闭新执行门控。
信号的expires_at固定为首次受理时的实例总截止。尚未匹配的早到信号通过单独到期索引结清;available_at仅表示下次检查时间,故障退避不能改变expires_at、accepted_at或等待截止。若实例已过总截止且仍需新工作,保留已收到事实并进入核查。
消费确认、等待赢家、步骤结果、后继任务或根检查点及审计同事务。审计失败不会留下“信号已消费但后继未保存”的部分状态。
## 3. 最终信号与POLL查询竞争
查询请求由Pulse执行,最终信号和查询结果由同一个实例事务裁决。PENDING只是一次成功查询观察,不争夺最终结果;请求失败也不允许创建新业务轮次绕过Pulse尝试预算。
| 条件 | 处理 |
| --- | --- |
| 最终查询结果早于信号受理,且在原查询截止内 | 查询结果胜出,信号保存QUERY_SETTLED;精度相同的时间优先查询 |
| 已有最终信号,当前无在途查询 | 结清POLL,不再创建下一轮查询 |
| 最终信号先到,查询尚未开始 | 保存信号赢家,使用Pulse取消规则结清未开始任务,再收集其Inbox结果 |
| 最终信号先到,查询正在执行 | 保存赢家和取消意图;保留当前任务、ACTIVE步骤及执行许可,等待Pulse实际结果 |
| 在途查询随后返回PENDING、相同最终结果或已知失败/取消 | 收集该查询结清后采用信号结果,不新增查询轮次 |
| 在途查询效果未知、结果不合法或返回不同最终结果 | 保留信号和查询证据,转NEEDS_REVIEW;不能让信号覆盖未知事实 |
| 原查询总截止已到,查询仍在途 | 关闭新执行门控,保留当前任务等待结清;后续结果不能自动清除已建立的核查状态 |
`CONSUMED`表示信号已被等待采用,不表示整个实例成功。带在途查询时,即使等待记录为SATISFIED,步骤仍保持ACTIVE直到Pulse确认结束。取消复用Pulse既有许可释放逻辑,不在Weave中实现另一套释放计数。
控制时钟发现查询结果已落库但未消费时,读取原Inbox事件参与裁决;不由扫描器先后决定赢家,也不绕过Inbox故障退避。结果、信号和许可预算均在MySQL持久保存,进程重启不重新选择已保存的赢家。
## 4. 存储升级和运行边界
[迁移000020](../internal/storage/mysql/migrations/000020_workflow_signal_identity.up.sql)增加信号契约/代次/证据/处置/到期检查时间,以及等待的赢家引用。名称扩至128字符,关联保持精确字节,并兼容历史764字节值。35个实体、301个直接持久化字段声明均提供中文说明;数据库无外键、无级联删除。
历史信号没有足够身份和受理证明:旧PENDING标记REJECTED/LEGACY_UNVERIFIED,保留原载荷和时间;已消费历史不被重新解释。旧NULL等待元数据不能推测成有效新契约。迁移为当前SIGNAL等待回填原截止的检查点,保留已有检查点,排除终态实例及旧激活。
升级仍采用停止旧写入节点 → 应用全部迁移 → 启动新版;新版信号字段和运行语义不支持与旧节点混跑。后台扫描三类候选:结果Inbox、根/等待检查点和未匹配信号到期;每类最多100项,最终批次最多100项,每个候选短事务处理。
## 5. 验证与后续
[受理回归](../internal/storage/mysql/workflow_signals_integration_test.go)、[消费回归](../internal/storage/mysql/workflow_signal_runtime_integration_test.go)、[真实v19升级](../internal/storage/mysql/workflow_signal_upgrade_integration_test.go)和[实际进程恢复](../internal/storage/mysql/workflow_runtime_process_integration_test.go)对应受理/并发/故障/升级边界,实际运行结果记录在[阶段报告](reports/phase-4-signals.md)。
下一步完成失败分支取消结清、实例暂停/继续/取消/人工核查恢复,再交付实例与信号HTTP、独立SDK及完整场景。当前内部存储测试不替代尚未开放的公共接口验收,也不替代生产MySQL切换与容量测试。

View File

@@ -0,0 +1,49 @@
# P4/P5 外部信号与最终查询竞争回归
日期:2026-09-18。实现和验收范围为内部信号服务与持久化运行器。实例/信号公共HTTP和独立SDK、完整控制与人工恢复仍待交付,持续目标未结束。
## 变化与边界
- 固定目标步骤、激活代次、信号名称/契约、精确操作关联、原因及证据,显式信号授权;每实例历史总上限1024。
- 原键冻结回执,参数冲突拒绝;受理证明与不可变审计核对,不接受修改后的信号参数授权后继。
- SIGNAL早到、等待、按时受理延迟消费、重复与截止处理;暂停收集并持久保存根接续。
- POLL最终信号与查询按原事实时间竞争,PENDING不作为最终结果;在途查询先请求取消并保留许可,未知或冲突转核查。
- Pulse取消逻辑提取为公共内部事务函数,原公共取消权限、幂等和审计边界保留;信号调用前验证流程所有权。
- 迁移000020增加5个Signal字段和1个Wait字段。当前35个实体、301个直接持久化字段声明,中文注释、零外键及禁止级联约束继续执行。
详细规则及升级顺序见[信号文档](../14-workflow-signals.md)。此处CONSUMED表示等待采用了信号,不代表流程成功或外部查询已结束。
## 验证记录
环境:darwin/arm64、Go 1.27.1、Docker MySQL 8.4.10;临时隔离数据库,未访问业务数据库或生产数据库。
| 检查 | 结果 |
| --- | --- |
| 基础格式、单元、竞态、静态检查、构建 | `make fmt test vet build`通过,退出0,包含服务端、独立SDK和业务示例module |
| 信号受理与运行器专项 | 受理4组、运行器20组,连同TIMER/POLL、原流程运行器、Pulse取消、新建结构和v7/v9升级,`-race`通过,51.926秒 |
| 真实v19升级 | 全量存储阶段已通过,3.90秒;原764字节关联、精确内容、已消费证据保留,旧PENDING隔离,当前SIGNAL在原截止回填 |
| 全量隔离MySQL与真实进程/SDK/示例 | 已通过,脚本退出0;全存储172.485秒、流程进程20.127秒、Worker SDK 94.716秒、SDK进程故障93.118秒;19个SDK示例和2个独立业务示例全部通过 |
专项入口:
```sh
go test -race -count=1 -run '^TestIntegration(WorkflowSignalAdmission|WorkflowSignalRuntime|WorkflowSignalUpgrade|WorkflowClock|WorkflowRuntime|WorkerCancellation|SchemaAndConstraints|NoForeignKeysUpgrade|ApplicationResourcesUpgrade)$' -v ./internal/storage/mysql
go build -o /tmp/orbit-signal-test-server ./cmd/orbit
ORBIT_TEST_SERVER_BINARY=/tmp/orbit-signal-test-server go test -race -count=1 -run '^TestIntegrationWorkflowRuntimeProcess$' -v ./internal/storage/mysql
```
必须设置`ORBIT_TEST_MYSQL_DSN`为一次性数据库。未配置环境变量导致跳过时不能记作通过。[全量脚本](../../scripts/test-mysql.sh)创建隔离MySQL并显式构建二进制、再运行实际进程测试。
存储专项覆盖12个并发受理只生成一条信号、12个并发消费者只创建一个后继、早到保存、暂停后接续、按时受理晚消费、真实等待超时、审计故障回滚、原回执冻结、载荷/actor/受理时间篡改进入核查、过期退避不改截止,以及最终信号在轮次间/未开始/执行中到达。执行中的查询分别返回取消、PENDING、相同最终值、未知、已知请求失败及冲突最终值;核查和许可释放按实际结果核验。查询较早完成时,先运行时钟或先运行Inbox都保留查询胜出。
真实进程用例进一步扩展了早到信号在全停期间保存、两个新控制进程激活消费,以及最终信号选中后再强杀全部进程、查询结清入库后由两个新进程恢复赢家的场景。该用例已由实际Orbit二进制执行通过,20.127秒;不是仅重建Go对象或内存快照的模拟。
数据库直接核验外键0、缺少COMMENT的业务字段0;代码301个直接持久化字段声明均有中文注释。文档检查870个本地链接、53个JSON代码块,错误0,业务项目标识检查通过;`git diff --check`通过。v7升级中70个历史外键全部移除,323项原索引组件保留;v20的两个信号索引和赢家唯一索引另行核验定义。
## 仍须完成
1. FAIL_AFTER_SETTLED分支取消与完整结清、实例暂停/继续/取消/人工核查恢复。
2. 实例/步骤/来源/信号/审计HTTP、独立SDK和可运行的端到端通用示例。
3. P6–P7的监控、完整故障矩阵、性能容量、MySQL切换/备份恢复和SDK发布核对。
这些测试不等同于生产高可用、容量或RPO/RTO验收;运行器采用单MySQL持久事务,生产边界继续单独验证。