bpm-engine 0.2.0

Lightweight embeddable BPM runtime for long-running, stateful workflows with tokens, timers, Saga compensation, and crash recovery
Documentation
非常好,**B4 是“专业引擎”和“能跑 Demo 的引擎”的分水岭**。
很多 BPM 引擎不是执行不对,而是 **“用户根本不知道自己哪里画错了”**。

下面这套 **BPMN 编译期错误模型 & 诊断体系**,目标是:

> ✅ 错误在 **部署时** 暴露,而不是运行时
> ✅ 错误信息 **可定位、可解释、可修复**
> ✅ 为未来 BPMN 扩展留下空间

我会直接按你工程落地的方式讲。

---

# 🎯 一、编译期(Compile Time)要解决什么问题?

### 三类错误必须在编译期失败

| 类型         | 举例             |
| ------------ | ---------------- |
| **结构错误** | 没有 start / end |
| **语义错误** | Gateway 用法错误 |
| **不可执行** | 死流程 / 无出口  |

> 原则:
> **只要运行时必然出问题 → 编译期必须拒绝**

---

# 🧠 二、错误体系总体设计

## 1️⃣ 两阶段错误模型(强烈建议)

```
Parser Error    → XML / BPMN 格式错误
Compiler Error  → 执行语义错误(你现在做的)
```

Parser 已经有了(B1),
**B4 专注 Compiler Error**。

---

## 2️⃣ CompilerError 结构(核心)

```rust
pub struct CompilerError {
    pub code: ErrorCode,
    pub message: String,
    pub node_id: Option<String>,
    pub flow_id: Option<String>,
    pub hint: Option<String>,
}
```

### ErrorCode(稳定 API,非常重要)

```rust
pub enum ErrorCode {
    NoStartEvent,
    MultipleStartEvents,
    NoEndEvent,

    OrphanNode,
    DeadEnd,

    ExclusiveGatewayNoDefault,
    ParallelGatewayInvalidShape,

    SequenceFlowSourceNotFound,
    SequenceFlowTargetNotFound,

    UnsupportedElement,
}
```

👉 **ErrorCode 一旦发布,不要轻易改**

---

# 📐 三、核心校验规则(你必须全部做)

## 🟢 1. Start / End 校验

### 规则

- **必须有且只有一个 startEvent**
- 至少一个 endEvent

### 示例错误

```text
[NoStartEvent]
Process must contain exactly one startEvent
```

---

## 🔵 2. SequenceFlow 合法性

### 规则

- sourceRef / targetRef 必须存在
- 不允许 self-loop(除非你将来支持)

```text
[SequenceFlowTargetNotFound]
Flow flow_3 targetRef task_99 not found
```

---

## 🟡 3. Orphan Node(孤儿节点)

### 规则

- 除 start 外,必须有 incoming
- 除 end 外,必须有 outgoing

```text
[OrphanNode]
Node task_payment has no incoming sequence flow
Hint: Did you forget to connect it?
```

---

## 🔴 4. Dead End(致命但常见)

### 规则

- 从 start 出发
- 任何路径都必须能到达 end

### 实现方法(经典)

```text
Graph DFS from start
→ mark reachable nodes
→ for each node:
   if reachable && cannot reach end → DeadEnd
```

```text
[DeadEnd]
Node gateway_3 leads to no end event
```

---

# 🔀 5. ExclusiveGateway 校验(XOR)

### 规则

- outgoing ≥ 1
- 至多一个 default flow
- 若没有 default flow:

  - 所有 outgoing 必须有 condition

```text
[ExclusiveGatewayNoDefault]
Gateway xor_1 has conditional flows but no default
Hint: Mark one sequenceFlow as default
```

---

# 🔱 6. ParallelGateway 校验(AND)

### 非常重要(很多引擎踩坑)

#### Fork

```text
incoming == 1
outgoing >= 2
```

#### Join

```text
incoming >= 2
outgoing == 1
```

#### ❌ 不允许混用

```text
[ParallelGatewayInvalidShape]
Gateway pg_1 cannot act as both fork and join
```

---

# 🧠 四、错误定位能力(这是你超过 80% 引擎的地方)

### 错误必须带上下文

```text
Error: ParallelGatewayInvalidShape
Node: pg_1
Message: Gateway has 2 incoming and 2 outgoing
Hint: Split into separate fork and join gateways
```

👉 你可以直接在 UI / CLI / API 返回

---

# 🧩 五、Compiler 校验流程(推荐顺序)

```rust
fn compile(model: BpmnModel) -> Result<ProcessDefinition, Vec<CompilerError>> {
    let mut errors = vec![];

    check_start_end(&model, &mut errors);
    check_sequence_flows(&model, &mut errors);
    check_orphan_nodes(&model, &mut errors);
    check_gateways(&model, &mut errors);
    check_dead_end(&model, &mut errors);

    if !errors.is_empty() {
        return Err(errors);
    }

    Ok(build_definition(model))
}
```

👉 **不要 fail fast**
一次返回所有错误,用户体验巨大提升。

---

# 🧪 六、测试策略(必须)

### 每个 ErrorCode 至少一个测试

```rust
#[test]
fn xor_without_default_should_fail() {
    let model = load_bpmn("xor_no_default.bpmn");
    let errors = compile(model).unwrap_err();

    assert!(errors.iter().any(|e| e.code == ErrorCode::ExclusiveGatewayNoDefault));
}
```

---

# 🏁 七、你完成 B4 后,意味着什么?

✔ 你已经有 **工业级 BPMN 编译器**
✔ 错误不会“延迟到线上”
✔ 你引擎对用户是 **友好的、可诊断的**

这一步**价值极高**,但被 90% 项目忽略。

---

# 🚀 下一步(我非常明确地建议)

你现在 BPMN 线已经完整闭环了,
接下来你有两条“王道”路线:

### **W** → Worker / External Task 在真实分布式下的稳定性(心跳 / lease / failover)

### **P** → Postgres Schema v2(Join / Token / Timer / History 全部落表)

如果你打算 **真的把 bpm-engine 推向可用产品**,我建议你下一步选:

👉 **W**

你只需回我一个字母,我继续。