feat: add bounded workflow decisions and durable result inbox

This commit is contained in:
2026-09-18 01:11:42 +08:00
parent 520e98d9d1
commit 0f7ac5203b
20 changed files with 1909 additions and 9 deletions

View File

@@ -1,6 +1,6 @@
# Weave 流程编排功能与设计
> 版本 0.3 · 2026-09-18。首版功能范围已确认。定义结构、发布校验与不可变版本已实现,见[定义格式](api/workflow-definition-format.md)和[发布API](api/workflow-definitions.md);下述实例推进、等待、查询与管理仍待P4–P5实现验收。
> 版本 0.3 · 2026-09-18。首版功能范围已确认。定义结构、发布校验与不可变版本已实现,见[定义格式](api/workflow-definition-format.md)和[发布API](api/workflow-definitions.md);已补[步骤决策与内部结果交付](10-workflow-engine-core.md),下述实例存储推进、等待、查询与管理仍待P4–P5实现验收。
## 1. 职责与边界

View File

@@ -1,6 +1,6 @@
# 数据模型与实现基线
更新:2026-09-17。数据库统一禁止外键。当前已实现任务执行、安全重试、运行中取消、计划调度、队列限速/背压和应用配额;优先级及节点公平领取已实现;可靠订阅核心、HTTP/WSS、管理与消费 SDK 已实现,独立 Inbox 场景已实现,流程编排继续开发。
更新:2026-09-18。数据库统一禁止外键。当前已实现任务执行、安全重试、运行中取消、计划调度、队列限速/背压和应用配额;优先级及节点公平领取已实现;可靠订阅核心、HTTP/WSS、管理与消费 SDK 已实现,独立 Inbox 场景已实现,流程编排继续开发。
## 1. 模块与依赖
@@ -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`。`weave_pending_advances` 保存暂停或分批推进后的续接检查点。
`orbit_events` 保存原始状态事件,`orbit_result_events` 使用相同事件 ID 保存队列、提交序号和冻结快照,`orbit_event_streams` 串行分配命名空间内的结果序号;对外投递使用 `orbit_deliveries`,内部流程结果已原子写入 `weave_inbox`及其独立冻结快照(实例消费推进尚未实现)。`weave_pending_advances` 保存暂停或分批推进后的续接检查点。
## 4. 已落实的数据库约束
@@ -186,3 +186,8 @@ HTTP/SDK创建时间范围使用包含下界、不包含上界;v2游标以固
## 相对延迟的持久化边界
delay_ms是普通/批量提交命令字段,首次事务以同一个数据库时间写入created_at和available_at;不增加表字段、迁移或外键。原时长参与幂等请求摘要,原回执保存初始时间;重报不能重新计时。后续安全重试可以更新当前available_at,首次受理回执及历史尝试仍保留。Go命令字段提供中文说明,见[任务契约](api/tasks.md)和[回归报告](reports/phase-3-delay-recovery.md)。
## P4 内部结果快照
000017_workflow_result_inbox为InboxEvent增加SourceTaskID、SourceActivationID、SourceRevision、Snapshot四个直接字段;当前35个实体、293个直接声明持久化字段,均有中文注释,无外键或级联。有效待处理记录要求来源齐全;历史缺少快照的未处理记录隔离为QUARANTINED,保留旧代次而不重建结果。内部快照独立于对外结果日志保留期,和Pulse权威结果同事务提交。见[执行决策及交付边界](10-workflow-engine-core.md)与[回归进展](reports/phase-4-engine-core.md)。

View File

@@ -1,6 +1,6 @@
# Orbit 持续开发与回归计划
更新:2026-09-17。此计划对应持续开发目标,由开发过程同步维护。数据库已按用户要求采用无外键设计,迁移 000008 移除历史外键,关联一致性由服务端事务保证。
更新:2026-09-18。此计划对应持续开发目标,由开发过程同步维护。数据库已按用户要求采用无外键设计,迁移 000008 移除历史外键,关联一致性由服务端事务保证。
## 1. 最终交付目标
@@ -28,7 +28,7 @@
| 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);实例推进、事件接续与持久化等待仍待实现 |
| P4 Weave 主流程 | SDK 定义构建与发布校验、实例、顺序/分支/结构化并行汇合、信号/定时等待、版本绑定 | 重启接续、早到/重复信号、并行竞争、内部事件重放、发布版本兼容;端到端通用流程 | 定义格式/词法类型校验、不可变发布、独立SDK及六类节点定义例子已实现,已通过[子项验收](reports/phase-4-definitions.md);已补[有限决策器及内部结果交付](reports/phase-4-engine-core.md);实例存储适配、事件消费推进与持久化等待仍待实现 |
| P5 Weave 恢复与查询 | 有界查询、暂停继续、取消、人工核查恢复、控制门控与审计 | 轮次与尝试独立预算;暂停/开始/回执/超时竞争;恢复保留预算;未知结果不盲目重复 | 待开发 |
| P6 生产验证与运维 | 完整监控、凭据轮换、备份恢复、滚动升级、运行手册、可复现负载和故障工具 | 对照生产验收清单执行,报告环境/指标/遗留项;容量、RPO/RTO 按真实拓扑验证 | 待开发 |
| P7 首版交付核对 | SDK 兼容与发布准备、完整 API 手册、场景示例、全量回归及交付清单 | 新接入者按文档跑通全部示例;所有首版能力可追溯到实现与测试;报告实际生产边界 | 待开发 |

View File

@@ -0,0 +1,87 @@
# Weave 执行决策与内部结果交付
状态:P4 内部基础已实现。已接通Pulse结果到Weave持久化Inbox,并提供有限步骤决策器;**实例启动、实例存储适配、Inbox消费推进、定时/信号运行器及对应公共SDK尚未实现**。不能把本页能力解释成流程已经可以端到端执行。
## 1. 为什么结果要独立持久化
任务已执行成功之后,流程节点可能立即退出。依赖进程内回调会丢失后继工作;从可变任务当前状态读取历史结果,也可能把后来的核查决定误当成旧事件。
当前结果事务顺序如下,任何一步失败均回滚:
```mermaid
flowchart LR
A[更新Pulse任务和尝试] --> B[结算执行许可与用量]
B --> C[写入原始状态事件]
C --> D[核验流程激活归属]
D --> E[写入Weave Inbox和冻结快照]
E --> F[锁定对外结果流并写入结果日志]
F --> G[写入审计和执行回执]
G --> H[提交事务]
```
普通任务和计划任务仍使用原有结果链路,不产生Weave Inbox。流程任务使用`owner_kind=workflow_activation`、`owner_id=激活ID`;TASK直接关联激活task_id,POLL通过轮次记录关联。所有关系均限定同一命名空间,核对步骤、实例、门控、固定定义和版本,不使用外键。
已实现的内部交付触发为`task.succeeded`、`task.failed`、`task.cancelled`、`task.needs_review`。任务暂时失败并等待自动重试,不新增流程激活或内部结果事件。相同完成回执重报不产生第二个事件;同一来源任务状态版本还有独立唯一约束。
发布后使用旧激活的在途结果仍保留原激活身份;生产者不自行判定是否推进当前激活。未来消费者须识别旧激活/旧轮次并留痕,不能将其用于当前工作。
生产者在现有结果快照查询中一起读取所有者和关联标记;普通结果不增加一次单独网络查询。流程关联核验使用只读关联查询,不在已持有任务锁后再锁实例、步骤或对外订阅。内部Inbox写入发生在外部结果流行锁之前,保持既有锁顺序。
## 2. 数据模型与升级
迁移`000017_workflow_result_inbox`为`weave_inbox`新增四个中文注释字段:
| 字段 | 含义 |
| --- | --- |
| source_task_id | 来源Pulse任务 |
| source_activation_id | 事件发生时的流程激活 |
| source_revision | 来源任务的状态版本,大于零 |
| snapshot | schema_version=1的独立结果JSON文本,最多512 KiB,保留原始数字精度 |
snapshot包含任务/队列ID、当时的状态与版本、结果、效果不确定标记和当前尝试ID;命名空间及实例由Inbox行固定。快照不依赖对外`orbit_result_events`的保留时间,也不从后来的任务结果重建。
`(namespace_id,event_id)`沿用原去重约束,新增`(namespace_id,source_task_id,source_revision)`唯一键。有效待处理记录必须四项完整且快照为合法JSON;只有迁移前的PROCESSED/QUARANTINED历史记录可以四项均为空。没有外键或级联。
v16及以前的未处理记录缺少冻结来源,升级后统一标为QUARANTINED,原租约清空、代次保留,error_code为LEGACY_RESULT_UNVERIFIED;已处理历史保持原状态。迁移不使用当前任务倒推旧结果,不赋予旧记录新的执行权。
新字段对应`weave/model/InboxEvent`,Go与SQL均有中文注释。原有扫描索引`(state,available_at,namespace_id,id)`保留,后续消费仍按持久化待处理状态和有限批次驱动。
关联丢失或类型异常时拒绝提交权威任务结果,不能悄悄丢掉内部交付后返回成功。此类持续异常需要修复存储关系后原身份核对;本阶段尚未提供自动修复工具。
## 3. 纯步骤决策器
`weave/engine.Compile`校验并独立拷贝已发布的结构版本1定义。Program可并发读取,调用方修改原定义或取出的步骤副本不会改变它。
`Program.Plan(snapshot, limit)`接受事务内读取、已校验归属的当前步骤快照,返回最多1–32个ACTIVATE、COMPLETE或SKIP决策。它不执行HTTP、业务Go函数、数据库写入,也不读取当前时间。绝对截止、许可与执行授权继续由存储及Pulse负责。
| 能力 | 已实现的决策行为 |
| --- | --- |
| 顺序 | 仅消费已成功步骤输出;后继不会提前读取结果 |
| 分支 | 首次按条件选择,保存case索引;再次推进复用选择,未选择路径分批标为SKIPPED |
| 并行 | 分支分别使用进入节点前的数据,全部完成才产生组合输出;兄弟结果通过命名汇合读取 |
| TASK | 解析输入与追踪/资源键,按Pulse运行数据边界检查;返回待持久化任务参数,不授予执行权 |
| SIGNAL | 解析并冻结非空业务关联、名称与参数版本,返回等待命令 |
| TIMER | 返回持久化等待命令,不分配业务Worker |
| POLL | 解析固定查询输入及可选回执关联;成功查询结果有独立分类器,PENDING不等于节点成功 |
| 暂停门控 | AllowNew=false时不激活新业务工作,允许整理已有组合结果;不替代数据库门控校验 |
| 有限推进 | More=true时说明本轮变更预算耗尽,必须由适配层保存待续记录;已有活动分支仍继续校验,不能仅安排内存回调 |
只在已保存的SUCCEEDED投影上读取输出;类型不匹配、未知/失败状态、不可达的执行、已完成组合下仍有未结清子节点、伪造组合输出等都会拒绝整轮决策。不返回可以部分应用的错误结果。
引用与对象/数组构造保留JSON原始数值,输出及累计构造有大小上限;数字条件使用精确有理数比较。相邻大整数、长小数和科学计数法不会先转成float64。规则不提供任意脚本、动态时间或随机数据。
`ClassifyPoll`仅处理“查询请求成功”的业务结果,返回PENDING、SUCCEEDED、FAILED或NEEDS_REVIEW;未知值/类型错误拒绝。即使快照将POLL标为SUCCEEDED,结果仍在处理中、失败或待核查时也不能推进后继。
## 4. 后续存储适配必须满足的条件
此处是尚待实现的要求,不能按纯单测宣称已完成:
1. 启动时认证、固定不可变版本/输入和策略、校验全部队列、计算实例截止,并在同事务创建实例、门控、首批激活/任务及审计回执。相同身份返回原实例。
2. 消费者领取内部事件后核对命名空间、来源任务版本、激活/轮次及当前步骤版本;消费确认、步骤变化、后续任务或持久化待续记录同事务提交。
3. Plan返回值只有存储事务提交后才生效。额度不足或事务失败不能留下孤立激活/许可;重启从持久化快照重新计算,不重复创建已有任务。
4. AllowNew来自权威实例状态、控制版本、取消、总截止和门控。程序参数本身不是权限证明。任务deadline不得超过实例或查询节点绝对截止。
5. 数据库时间驱动真实等待与超时,按可靠受理时间裁决信号/结果,原截止和预算在恢复时保持。单轮重试与查询轮次不能互相补充次数。
6. 完成实例前核对所有活动工作结清和未知效果;失败/取消需要停止新派发并收集在途结果。人工恢复通过新激活保留历史。
7. 增加流程来源查询、实例/步骤/信号/操作HTTP和独立SDK、可运行端到端场景、真实节点强杀和竞争回归。以上均属于P4–P5剩余工作。
当前测试夹具通过SQL构造流程关联,只验证结果事务边界;它不代表公共实例API或运行器已经存在。纯决策器的JSON往返测试验证可从快照重算,也不等同于真实进程恢复验收。

View File

@@ -0,0 +1,60 @@
# P4 进展:步骤决策与内部结果交付
日期:2026-09-18。本次完成纯步骤决策器、Pulse结果原子写入Weave Inbox及独立快照迁移;P4整体尚未完成,实例API、消费推进与真实等待运行器仍待开发。设计与边界见[内部基础](../10-workflow-engine-core.md)。
## 代码与数据变化
- weave/engine提供不可变编译结果、1–32变更预算的顺序/分支/并行决策、任务参数与等待描述、精确数字条件及POLL业务状态分类。不会调用业务处理器或授予执行权。
- 结果事务复用现有任务快照查询读取关联;内部交付先于对外结果流行锁,任务/尝试/许可/原事件/内部快照/外部日志/审计/回执一起提交或回滚。
- 新迁移000017为Inbox增加来源任务、激活、状态版本和独立快照4字段,全部中文注释。35个实体、293个直接声明持久化字段,零外键/级联。旧的未验证记录隔离,不按当前任务重建历史。
- 尚无新的公共运行API或SDK;已有定义发布SDK和其余场景随完整回归验证,执行器不作为业务端可调用的SDK暴露。
## 已完成验证
环境:macOS arm64、Go 1.27.1、隔离Docker MySQL 8.4.10。所有数据库测试使用临时库、随机秘密与回环端口。
| 检查 | 证据 |
| --- | --- |
| make fmt / race test / vet / build | 通过,覆盖服务端、SDK和独立Inbox/Outbox模块 |
| 决策器单测 | 预算1/2/32反复重编译及JSON恢复,不重复产生任务;精确数字、分支固定、并行汇合、暂停、类型/大小/状态拒绝、不可达执行、已完成组合完整性、并发只读、SIGNAL/TIMER/POLL准备与查询分类均通过;新增嵌套并行分支耗尽预算后仍遍历其他在途分支的回归,race测试通过 |
| 初始真实MySQL定向回归 | 修正测试夹具未提交非成功结果error_code后,Schema、结果交付、v16升级连续3轮通过,共22.917秒;此轮尚未加入后续自然租约到期用例 |
| 新结果交付用例 | 四种结果、8个并发同完成回执、暂停期间收集、POLL旧激活归属、内部/外部写入失败完整回滚、缺失owner/门控/激活/定义拒绝;独立快照不依赖外部结果日志;实际数据库租约自然到期后转NEEDS_REVIEW并交付一次,重复恢复不再写入 |
| 升级用例 | 真实v16结构及PENDING/LEASED/PROCESSED/QUARANTINED记录升级,历史状态/代次及空来源检查;缺来源的新PENDING被CHECK拒绝;schema与零外键检查通过 |
| 存储全量及无外键检查 | 重跑后全部存储集成测试通过,116.335秒;v7的70个外键移除至0、323个原索引组件保留,新结果唯一索引另行核对。v9额度回填和v16升级通过;直接查询当前测试库外键为0、业务字段空COMMENT为0 |
| 完整隔离回归 | scripts/test-mysql.sh退出0;19个SDK示例与独立Inbox/Outbox共21个例子执行通过,10个SDK进程故障父用例及Inbox/Outbox各自进程故障通过,临时容器已清理 |
| 模型及文档核对 | 含公共Record的293个直接持久化字段声明均有中文注释;799个本地文档链接、53个JSON块校验通过;git diff --check通过 |
完整脚本中的测试组结果如下(秒):
| 测试组 | 耗时 |
| --- | ---: |
| MySQL存储全量 | 116.335 |
| Worker连接及自动运行SDK | 92.477 |
| 计划 / 队列资源SDK | 5.676 / 4.488 |
| 批量受理 / 任务查询SDK | 1.424 / 1.627 |
| 订阅管理 / 事件SDK | 33.392 / 51.871 |
| 人工恢复 / 定义发布SDK | 3.395 / 1.354 |
| 独立Inbox / Outbox | 34.588 / 34.330 |
| 应用额度SDK | 1.503 |
| SDK真实进程故障 | 93.116 |
复现入口(Go及Docker需已安装):
```sh
make fmt test vet build
make integration
```
本次实际通过GO参数选择Go 1.27.1、DOCKER参数选择本机Docker;脚本自行创建隔离数据库,不连接已有业务数据库。数据库环境变量缺失时普通单测会跳过集成用例,不能代替上述真实integration结果。
测试中的流程关联由受控SQL夹具创建,不能作为实例受理实现的证据。数据库写入失败通过触发器注入,之后原完成身份重报成功;没有修改接口约束以接受不合法回执。
首次完整回归暴露两处旧升级测试未适配v17:v9还原夹具遗漏删除新增Inbox字段,v7索引保留比较未单独核验新增唯一索引。已修正夹具并增加该唯一索引列序/唯一性断言,重跑存储全量通过;没有修改历史迁移或放宽零外键检查。
复查另发现决策器在左侧分支耗尽变更预算后,提前返回另一个已有分支,误报其活动子任务不可达。新增测试先复现,再将预算限制保留在新增变更处,继续遍历已有选中路径;恢复后各任务仍只激活一次,最终输出及完整race检查通过。
## 尚未实现及后续工作
下一步实现实例存储适配与Start/Get/步骤查询:认证、固定版本/输入、实例及门控、首批任务与幂等/审计同事务;之后内部事件消费、有限推进检查点、持久化等待、流程来源与SDK接入。POLL轮次、信号竞争、暂停/取消/核查恢复与预算维护继续按P4–P5交付。
本阶段没有真实流程进程重启回归、持久化轮询或信号/定时消费器。P3已有进程故障测试只证明原任务和订阅链路,不能替代Weave运行器验证。增加关联索引读取与快照写入后的吞吐、存储增长、清理及跨版本上线边界继续在P6–P7验证,不声称生产容量和高可用已达标。