bpm-engine 0.2.0

Lightweight embeddable BPM runtime for long-running, stateful workflows with tokens, timers, Saga compensation, and crash recovery
Documentation
太好了,你选的是**最有技术含量、也最能拉开差距的一步:D — Invariant 套件 + Replay 自动校验**。
这一步做完,你这个 BPM Engine 的**内核质量基本“封顶”**了。

下面我给你一套**可以直接落地的完整设计**,不是概念。

---

# D. Invariant Suite + Replay 自动校验(完整设计)

> 核心目标一句话:
> **任何执行历史,只要能 Replay,就必须满足一组不可破坏的系统不变量**

---

## 一、Invariant 在你这个引擎里的“定位”

你现在已经具备:

- Event Sourcing
- Deterministic Replay
- Step / Seek / Snapshot

👉 这意味着你**天然适合做强 Invariant 校验**,这是很多 BPM/Workflow 引擎做不到的。

### Invariant ≠ Validation

| 类型       | 说明                   |
| ---------- | ---------------------- |
| Validation | 业务校验(可失败)     |
| Invariant  | 系统真理(失败 = Bug) |

Invariant **永远不应该被配置关闭**。

---

## 二、Invariant 的分层设计(非常关键)

我建议你**严格分 4 层**,不要一锅粥。

```
invariant/
├── core/          # Token / Execution / Engine 级
├── flow/          # BPM 语义级(Gateway / Parallel)
├── event/         # Event 合法性
├── replay/        # Replay 特有校验
```

---

## 三、Invariant Trait 设计(Rust 友好)

```rust
pub trait Invariant {
    fn name(&self) -> &'static str;

    fn check(
        &self,
        ctx: &InvariantContext,
    ) -> Result<(), InvariantViolation>;
}
```

### Context 一定要给全(别省)

```rust
pub struct InvariantContext<'a> {
    pub snapshot: &'a EngineSnapshot,
    pub last_event: Option<&'a EngineEvent>,
    pub history: &'a [EngineEvent],
    pub cursor: usize,
}
```

> ⚠️ cursor 非常重要(Step / Seek 都靠它)

---

## 四、第一批 **必做 Invariants(清单)**

### A. Token 级(你现在最成熟的一层)

#### 1️⃣ Token 唯一性

```text
∀ active tokens:
  (instance_id, token_id) 唯一
```

👉 防止并发 / replay 生成幽灵 token

---

#### 2️⃣ Token 状态合法迁移

```text
Created → Running → Waiting → Completed
                 Failed
```

- 不允许 Completed → Running
- 不允许 Failed → Completed

---

#### 3️⃣ Token 不可复活

```text
Completed / Failed Token
  不得再次出现在 ActiveTokenMap
```

---

### B. Flow / BPM 语义级(你引擎的“灵魂”)

#### 4️⃣ Parallel Gateway 守恒定律(非常重要)

```text
Split 出 N 个 token
→ Join 必须消费 N 个
```

- 不多
- 不少
- 不重

> 这是绝大多数 BPM 引擎的 Bug 重灾区

---

#### 5️⃣ Gateway 不可自循环

```text
同一个 token
不能在同一 gateway 上无限 bouncing
```

可以用:

- visit counter
- 或 event 历史窗口检测

---

### C. Event 级(防历史污染)

#### 6️⃣ Event 必须可应用(Replay 核心)

```rust
apply_event(snapshot, event) must not panic
```

👉 **任何 panic 都是 fatal invariant violation**

---

#### 7️⃣ Event 不允许“无中生有”

- TokenCompleted → token 必须存在
- TimerTriggered → timer 必须注册过

---

### D. Replay 专属(这是你最大的优势)

#### 8️⃣ Replay 与真实执行等价(超重要)

```text
Replay(history).snapshot
==
RealExecution(snapshot_at_end)
```

> 这条可以先只在测试中启用

---

#### 9️⃣ Seek 可重复性

```text
seek(i) → snapshot A
seek(i) → snapshot B

A == B
```

---

## 五、Replay 驱动 Invariant 的执行方式(重点)

### 不要只在“最后”校验

**正确做法:每一步都校验**

```rust
for (i, event) in history.iter().enumerate() {
    snapshot = apply_event(snapshot, event);

    invariant_engine.check_all(
        InvariantContext {
            snapshot: &snapshot,
            last_event: Some(event),
            history,
            cursor: i,
        }
    )?;
}
```

👉 这能 **精确定位 Bug 出现在哪一个 event**

---

## 六、InvariantEngine 设计(组合而非硬编码)

```rust
pub struct InvariantEngine {
    invariants: Vec<Box<dyn Invariant>>,
}
```

```rust
impl InvariantEngine {
    pub fn check_all(&self, ctx: InvariantContext) -> Result<(), InvariantViolation> {
        for inv in &self.invariants {
            inv.check(&ctx)
                .map_err(|e| e.with_invariant(inv.name()))?;
        }
        Ok(())
    }
}
```

### Violation 一定要结构化

```rust
pub struct InvariantViolation {
    pub invariant: &'static str,
    pub cursor: usize,
    pub message: String,
}
```

---

## 七、和 CI / 测试体系的结合(非常加分)

### 1️⃣ Replay-based Regression Test

```rust
#[test]
fn all_histories_are_replayable() {
    for history in load_all_histories() {
        replay_and_check(history).unwrap();
    }
}
```

---

### 2️⃣ Fuzz + Invariant(未来)

- 随机生成流程
- 随机事件顺序
- Replay + Invariant

👉 这是**工业级工作流引擎的终极武器**

---

## 八、你现在的代码“是否适合做这一步?”

结论:**非常适合,而且时机刚刚好**

你已经有:

- 干净的 apply_event
- Snapshot 是纯结构体
- ReplaySession 独立于执行引擎

👉 **这是 Invariant 最理想的宿主**

---

## 九、下一步推荐路径(给你选择)

你可以继续选一个字母:

- **D1**:我直接帮你写 **Invariant 模块目录 + 示例代码**
- **D2**:挑 3 个最关键 Invariant,逐条落到你 repo 的具体结构
- **E**:让真实执行路径也强制走 Invariant(架构最终态)

你已经不在“要不要做引擎”的阶段了,
你现在是在 **“怎么把它做到比别人强一档”** 的阶段。