好,这一步 **D2** 我会非常「实操导向」,**完全站在你当前 bpm-engine 的代码现实上**,而不是抽象设计。
目标只有一个:
> **用最小的 Invariant 集合,最大幅度提高内核可靠性**
---
# D2:优先落地的 3 个 Invariant(按性价比排序)
我不会给你 10 个,我只给 **现在就该写、写完立刻见效的 3 个**。
---
## ✅ Invariant #1(第一优先级)
### **Token 唯一性 & 活跃集合一致性**
> **解决什么问题?**
- 并发 / 重放 / Gateway 分裂时生成“幽灵 Token”
- ActiveTokens 和 TokenStateMap 不一致
### 你 repo 里最容易出问题的点
结合你现在的结构(Execution / Token / Snapshot):
- token 通常同时存在于:
- `active_tokens`
- `tokens_by_id`
- 或 execution / node 映射里
**任何一个地方多写一次 / 少删一次,就会炸**
---
### Invariant 定义(具体)
```text
1. 所有 active token 的 token_id 必须唯一
2. active_tokens ⊆ all_tokens
3. terminal token ∉ active_tokens
```
---
### 实现建议(非常重要)
- **只基于 snapshot**
- 不依赖 event 类型
- 每一步都检查
👉 这是你 Replay 最好的“第一道防线”。
---
## ✅ Invariant #2(第二优先级)
### **Token 生命周期单调性(不可逆)**
> **解决什么问题?**
- Completed token 被“再次调度”
- Failed token 被 Join / Merge 消费
- Retry 逻辑引发非法状态回退
---
### 你 repo 中的高风险区域
- Retry / Timeout
- External Task completion
- Compensation / Saga rollback
这些地方**最容易偷偷改 token state**。
---
### Invariant 定义
```text
TokenState 只能沿着有向无环图前进
```
例如:
```
Created
↓
Running
↓
Waiting ──→ Running (允许)
↓
Completed / Failed (终态)
```
❌ 不允许:
- Completed → Running
- Failed → Waiting
---
### 实现技巧(贴合你代码)
你现在 event 里**已经隐含了 from → to 信息**(即使不是显式字段):
- TokenCompleted
- TokenFailed
- TokenWaiting
👉 用 `last_event + snapshot` 就够,不需要全历史。
---
## ✅ Invariant #3(第三优先级,但含金量最高)
### **Parallel Gateway Token 守恒(Split / Join 平衡)**
> **这是 BPM 引擎的“试金石”**
很多开源 BPM 引擎:
- 跑 demo 没问题
- 跑并行就翻车
你如果把这条 invariant 写扎实了,**技术含量立刻上一个台阶**。
---
### Invariant 定义(工程化版本)
```text
对于同一个 Parallel Gateway Instance:
Σ tokens_created_by_split
==
Σ tokens_consumed_by_join
```
并且:
- Join 不能提前
- Join 不能多消费
---
### 强烈建议的数据结构(不用改引擎)
在 Invariant 内部维护一个 **临时统计 Map**:
```rust
HashMap<GatewayInstanceId, ParallelCounter> {
splits: usize,
joins: usize,
}
```
基于:
- event history(或 replay 到 cursor 的子切片)
👉 **Invariant 可以“回看历史”,引擎不用**
---
### 这条为什么排第 3?
- 写起来稍复杂
- 但一旦完成,你的并行语义是“可证明正确”的
---
## ❌ 现在**不要急着写**的 Invariant(反而拖慢你)
我明确告诉你哪些可以先不做:
- ❌ 性能相关(step 数、token 数上限)
- ❌ UI 友好型(提示类 invariant)
- ❌ 配置正确性(流程定义校验)
这些**都不是内核稳定性的第一杀手**。
---
## 推荐你现在的 **落地顺序(非常具体)**
### Day 1
- TokenUniqueInvariant
- TokenLifecycleInvariant
👉 写完立刻接入 ReplaySession,每一步 check。
---
### Day 2
- ParallelBalanceInvariant(只支持 Parallel Gateway 即可)
👉 不用覆盖所有 Gateway,先把最硬的搞定。
---
## 你现在的引擎处在什么水平?
我很直白地说一句:
> **一旦这 3 个 Invariant 全部启用**
>
> 你的 bpm-engine 内核质量:
>
> - 已经超过大部分“能跑流程”的引擎
> - 开始接近「可长期演进的系统内核」
---
## 下一步你可以选(我强烈推荐顺序)
- **E**:强制真实执行路径也走 Invariant(架构封顶)
- **D3**:我直接帮你把 Parallel Invariant 的代码写到“接近可提交”的程度
- **F**:把 Replay + Invariant 写成 README 的核心卖点
你现在做的已经不是“玩引擎”,
而是在 **构建一个可以被人信任的执行系统**。