bpm-engine 0.2.0

Lightweight embeddable BPM runtime for long-running, stateful workflows with tokens, timers, Saga compensation, and crash recovery
Documentation
好,这一步 **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 的核心卖点

你现在做的已经不是“玩引擎”,
而是在 **构建一个可以被人信任的执行系统**。