Files
ordo/docs/usage.md
T
0264408andCursor 8d08a2840b docs: sync package layout and REST with current API
Host examples and README still described the pre-split packages and omitted optional REST.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-20 11:45:23 +08:00

431 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ordo 使用说明
本文档是 Ordo 对外行为的使用说明真源。**新增或变更对外 API、JSON 定义、扩展点、异常或运行时语义时,必须同步更新本文。** 未实现能力写在 [roadmap.md](roadmap.md),不要在这里当已支持功能描述。
版本:`0.0.1-SNAPSHOT`。语言:Java 17+。
## 1. 定位
Ordo 是嵌入宿主进程的审批引擎,入口是 `OrdoEngine`。
做:流程定义、实例推进、待办任务、条件路由、ACTION 副作用、审计事件、分页查询。
产品边界:不做业务表单、用户体系、多租户;不内置设计器 UI。业务字段放在 `ProcessContext`(不可变 `Map<String, Object>`)。可选 HTTP 见 §2.3;独立设计器见 [roadmap.md](roadmap.md)。
## 2. 模块与接入
| 模块 | 何时用 |
|---|---|
| `ordo-api` | 始终:模型与 `OrdoEngine` |
| `ordo-core` | 内存引擎 / 自己装配 `DefaultOrdoEngine` |
| `ordo-storage-jdbc` | JDBC 持久化(PostgreSQL、MySQL;测试可用 H2 PostgreSQL 模式) |
| `ordo-spring-boot-starter` | Spring Boot 自动装配 |
| `ordo-example` | `LeaveRequestExample` 内存演示 |
Java 包(模块未变):
| 包 | 内容 |
|---|---|
| `com.jetlumen.ordo.api` | `OrdoEngine`、`TransactionExecutor` |
| `com.jetlumen.ordo.api.definition` | 图:定义、步骤、边、`when`、PARALLEL |
| `com.jetlumen.ordo.api.runtime` | 实例、任务、事件、ACTION 执行、token、`ProcessRuntime` |
| `com.jetlumen.ordo.api.spi` | `ActionHandler`、`NamedAction`、`RoutingCondition`、`NamedCondition`、`AssigneeResolver`、`OrdoCatalog`、`OrdoEventListener` |
| `com.jetlumen.ordo.api.util` | `Texts`、`Jsons` |
| `com.jetlumen.ordo.api.exception` / `query` / `repository` | 异常、分页查询、存储端口 |
| `com.jetlumen.ordo.core.spi` | `DispatchingActionHandler`、`DispatchingRoutingCondition`、`RegistryOrdoCatalog` |
Starter **不携带** JDBC 驱动。生产按库添加 `org.postgresql:postgresql` 或 `com.mysql:mysql-connector-j`。Spring Boot 4 还需 `spring-boot-starter-flyway`,否则 Flyway 迁移不会执行。
先 `mvn install` 本仓库,宿主再依赖 `0.0.1-SNAPSHOT`。
### 2.1 内存引擎
```xml
<dependency>
<groupId>com.jetlumen</groupId>
<artifactId>ordo-core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
```
```java
OrdoEngine ordo = new InMemoryOrdoEngine();
// 可选:new InMemoryOrdoEngine(clock, assigneeResolver, routingCondition, actionHandler, listeners)
```
进程退出数据即丢失。适合单测和本地试跑。
### 2.2 Spring Boot(JDBC)
```xml
<dependency>
<groupId>com.jetlumen</groupId>
<artifactId>ordo-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
```
需要已有 `DataSource`。`ordo.enabled` 缺省为 `true`。
```yaml
ordo:
enabled: true
jdbc:
dialect: # 可选 postgresql / mysql;空则按 DataSource 探测
definitions:
location: classpath*:ordo/*.json # 启动时对每个 JSON 调用 publish
due:
poll-ms: 0 # >0 时轮询 processDue;默认不调度
rest:
enabled: false
base-path: /ordo
```
启动加载使用 `publish`:图与 latest 相同则不升版本;不同则写入新版本。运行中实例继续锁定发起时所用版本。
宿主用 `@Bean` 覆盖默认扩展点:
| Bean | 默认 |
|---|---|
| `AssigneeResolver` | 候选人字符串即办理人 |
| `NamedCondition` | 可有多个;无 `RoutingCondition` 门面且列表非空时装配 `DispatchingRoutingCondition` |
| `NamedAction` | 可有多个;无 `ActionHandler` 门面且列表非空时装配 `DispatchingActionHandler` |
| `RoutingCondition` | 无 `NamedCondition` 时 `ref` 边恒 true;有则按 key 分发,未知 `ref` 为 false |
| `ActionHandler` | 无 `NamedAction` 时空操作;有则按 key 分发,未知 key 抛 `IllegalArgumentException` |
| `OrdoEventListener` | 可有多个,提交后按 Spring 顺序调用 |
| `OrdoCatalog` | REST 打开时:从 `NamedCondition` / `NamedAction` 投影;皆空则为空列表。`assignees` 仍空 |
未提供 `NamedAction` 且未覆盖 `ActionHandler` 时,ACTION 步骤仍会推进流程,但 handler 什么都不做。不要同时提供门面 Bean 与对应 `Named*`(门面优先,具名 Bean 不参与运行时)。
内存引擎把 `com.jetlumen.ordo.core.spi.DispatchingRoutingCondition.of(...)` / `DispatchingActionHandler.of(...)` 传入 `InMemoryOrdoEngine` 即可。
### 2.3 可选 REST
`spring-boot-starter-web` 对 autoconfigure 为 optional,starter **不**传递 Web。宿主已有 Web 且 `ordo.rest.enabled=true` 时注册 `com.jetlumen.ordo.spring.rest` 下的接口。不包含鉴权;`actor` / `initiator` 放在 JSON 体。OpenAPI:[ordo-rest.openapi.yaml](ordo-rest.openapi.yaml)。
默认前缀 `/ordo`:
| 方法 | 路径 | 引擎 |
|---|---|---|
| POST | `/definitions/parse` | `ProcessDefinitionParser.fromJson` |
| POST | `/definitions` | `publish` |
| GET | `/definitions` | `queryDefinitions` |
| GET | `/definitions/{id}` | `findDefinition`(latest) |
| GET | `/definitions/{id}/versions` | `queryDefinitionVersions` |
| GET | `/definitions/{id}/versions/{version}` | `findDefinition(id, version)` |
| POST | `/instances` | `start` |
| GET | `/instances` | `queryInstances` |
| GET | `/instances/{id}` | `findInstance` |
| POST | `/instances/{id}/withdraw` | `withdraw` |
| POST | `/instances/{id}/cancel` | `cancel` |
| GET | `/instances/{id}/tasks` | `findTasks` |
| GET | `/instances/{id}/tasks/pending` | `findPendingTasksByInstanceId` |
| GET | `/instances/{id}/history` | `queryHistory` |
| GET | `/instances/{id}/action-executions` | `queryActionExecutions` |
| GET | `/tasks` | `queryTasks` |
| GET | `/tasks/pending?assignee=` | `findPendingTasksByAssignee` |
| GET | `/tasks/{id}` | `findTask` |
| POST | `/tasks/{id}/approve` | `approve` |
| POST | `/tasks/{id}/reject` | `reject` |
| POST | `/tasks/{id}/reassign` | `reassign` |
| POST | `/due` | `processDue`(默认 limit 100) |
| GET | `/catalog/conditions\|actions\|assignees` | `OrdoCatalog` |
定义读写 JSON 与 §3.2 相同。HTTP:404 找不到;403 越权;409 已完成/无路由;400 非法参数。
## 3. 流程定义
每个定义有 `id`、引擎分配的 `version`、`name`、步骤列表、转移列表。步骤 id 在定义内唯一。每个步骤必须至少有一条出边(结束用 `to = null`)。同一 `from` 上 `priority` 不能重复。
### 3.1 代码构建
线性(每步无条件进下一步,最后一步结束):
```java
ordo.publish(ProcessDefinition.linear("leave-request", "Leave request", List.of(
ApprovalStep.single("manager", "Manager approval", "maria"),
ApprovalStep.single("hr", "HR approval", "henry")
)));
```
图(条件边 + ACTION):
```java
new ProcessDefinition("leave-request-routed", "Leave request",
List.of(
ApprovalStep.action("notify-submitted", "Notify submitted", "leave-submitted-mail"),
ApprovalStep.single("manager", "Manager approval", "maria"),
new ApprovalStep("hr", "HR approval", List.of("henry"), ApprovalPolicy.ANY),
ApprovalStep.action("notify-approved", "Notify approved", "leave-approved-mail")
),
List.of(
StepTransition.always("notify-submitted", "manager"),
StepTransition.when("manager", "hr", RoutingPredicate.gt("days", 3), 0),
new StepTransition("manager", "notify-approved", null, 1),
StepTransition.always("hr", "notify-approved"),
StepTransition.end("notify-approved")
));
```
`publish`:该 `id` 尚无版本则写入 v1;与 latest 的 id/name/steps/transitions 相同则返回 latest 不插入;否则插入 `latest + 1`。运行中实例不阻止发布。调用方构造的 `ProcessDefinition` 版本为 0;入库后由引擎分配从 1 起的单调版本。JSON 不要写 `version`,出现则忽略。
`start(definitionId)` 使用 latest。实例带 `definitionVersion`;审批、到期、撤回、取消均按该版本取图,不跟随后续 `publish`。
### 3.2 JSON
`ProcessDefinitionParser.fromJson(String|InputStream)` / `toJson(ProcessDefinition)`。Spring 默认扫 `classpath*:ordo/*.json`。`toJson` 写出 `version` 与 `startStep`(当前步骤列表首位);`fromJson` 仍忽略 JSON 里的 `version`。
```json
{
"id": "leave-request-routed",
"name": "Leave request",
"startStep": "notify-submitted",
"steps": [
{
"id": "notify-submitted",
"name": "Notify submitted",
"kind": "ACTION",
"action": "leave-submitted-mail"
},
{
"id": "manager",
"name": "Manager approval",
"candidates": ["maria"],
"policy": "ANY"
},
{
"id": "hr",
"name": "HR approval",
"candidates": ["henry"],
"policy": "ANY"
},
{
"id": "notify-approved",
"name": "Notify approved",
"kind": "ACTION",
"action": "leave-approved-mail"
}
],
"transitions": [
{ "from": "notify-submitted", "to": "manager" },
{ "from": "manager", "to": "hr", "when": { "gt": ["days", 3] }, "priority": 0 },
{ "from": "manager", "to": "notify-approved", "priority": 1 },
{ "from": "hr", "to": "notify-approved" },
{ "from": "notify-approved", "to": null }
]
}
```
字段:
| 字段 | 说明 |
|---|---|
| `startStep` | 可选。指定起始步骤 id;缺省为 `steps` 数组第一项。解析时会把该步旋到列表首位。 |
| `kind` | `APPROVAL`(默认)、`ACTION` 或 `PARALLEL`。 |
| `branches` | 仅 `PARALLEL`。至少 2 条分支;每条含 `id`、`steps`、`transitions`。禁止嵌套 PARALLEL。 |
| `candidates` / `policy` | 仅审批步。`policy` 默认 `ANY`。审批步至少一名候选人,禁止重复。 |
| `action` | 仅 ACTION 步,对应 `ActionHandler.execute` 的 key。审批步禁止带 `action`。 |
| `due` | 仅审批步。可选。`after` 为 ISO-8601 时长;`then` 为 `reassign` / `notify` / `goto`。进入该步时任务 `dueAt = now + after`。 |
| `from` / `to` / `when` / `priority` | `to` 为 `null` 或 JSON `null` 表示结束。`when` 省略则无条件(else)。`priority` 默认 0,**仅在同类边之间越小越先匹配**(条件边一组,无条件边一组)。 |
## 4. 实例与任务
### 4.1 生命周期
```
start → RUNNING
审批步:为每个候选人建 PENDING 任务
PARALLEL 步:为每条分支建立令牌并同时进入分支起点
ACTION 步:事务内记 PENDING 执行记录,提交后调 ActionHandler
approve / 路由结束 → APPROVED
reject(步被否决)→ REJECTED
withdraw(仅发起人)→ WITHDRAWN,剩余 PENDING 任务 → SKIPPED
cancel(任意非空 actor)→ CANCELLED,剩余 PENDING 任务 → SKIPPED
```
```java
ProcessInstance instance = ordo.start("leave-request", "alice",
new ProcessContext(Map.of("requestId", "LEAVE-001", "days", 2)));
List<ApprovalTask> pending = ordo.findPendingTasksByInstanceId(instance.id());
ordo.reassign(pending.get(0).id(), "maria", "diana");
ordo.approve(pending.get(0).id(), "diana", "ok");
ordo.reject(taskId, "maria", "额度不足");
ordo.withdraw(instance.id(), "alice", "计划有变");
ordo.cancel(instance.id(), "admin", "政策变更");
```
`start` 的 `initiator`、办理人 `actor` 均不可空白。
### 4.2 权限
- `approve` / `reject` / `reassign`:`actor` 必须等于该任务当前 `assignee`,否则 `UnauthorizedTaskOperationException`。
- `reassign`:仅 `PENDING` 任务;同一任务 id,办理人改为 `newAssignee`,不推进步骤。`newAssignee` 不可空白、不可等于当前 `assignee`,且同一步不能已有该人的 `PENDING` 任务,否则 `IllegalArgumentException`。不经过 `AssigneeResolver`。人工转派不改 `dueAt`。
- `processDue(limit)`:认领 `dueAt <= now` 的 PENDING 任务(`limit > 0`),按步上 `due.then` 执行:`reassign` 换办理人(`to` 走 `AssigneeResolver`)、`notify` 可选 `ActionHandler`、`goto` 跳过当前步 PENDING 并进入 `to` 步骤(`to` 必须是步骤 id)。每种策略对一张任务最多成功一次(清空 `dueAt`)。引擎无后台线程;Spring 下 `ordo.due.poll-ms > 0` 才轮询。
- 任务非 `PENDING`:`TaskAlreadyCompletedException`。
- `withdraw`:仅 `initiator`,否则 `UnauthorizedInstanceOperationException`;实例非 `RUNNING`:`InstanceAlreadyCompletedException`。
- `cancel`:`actor` 非空即可,**不校验**是否发起人;实例须为 `RUNNING`,否则 `InstanceAlreadyCompletedException`。谁能调用由宿主决定。
没有管理员代批。
### 4.3 状态
实例 `ProcessStatus`:`RUNNING`、`APPROVED`、`REJECTED`、`WITHDRAWN`、`CANCELLED`。
任务 `TaskStatus`:`PENDING`、`APPROVED`、`REJECTED`、`SKIPPED`(同一步其它候选人已决定结果,或实例被撤回/取消)。
`ApprovalTask.action`(`TaskAction`)仍表示**该任务**上的办理人/意见/时间;实例级时间线用 `ProcessEvent`,不要靠拼任务 action 还原流程史。
## 5. 会签 / 或签
一步多个 `candidates` 时由 `ApprovalPolicy` 决定:
| | ANY | ALL |
|---|---|---|
| 通过 | 任一人 `approve` 即过步,其余 PENDING 变 `SKIPPED` | 全部 `approve` 才过步 |
| 驳回 | 所有候选人都 `reject` 才否决实例 | 任一人 `reject` 即否决实例,其余 PENDING 变 `SKIPPED` |
候选人创建任务前会经过 `AssigneeResolver.resolve(candidate, step, runtime)`,例如把角色名解析成用户 id。默认实现原样返回 candidate。`ProcessRuntime` 含 `instanceId`、`definitionId`、`definitionVersion`、`initiator`、当前 `stepId` 和业务 `context`。发起人等引擎元数据不写入 `ProcessContext.variables`。
连续 ACTION 步会在同一次提交后依次执行,上限 32 跳(**每个 PARALLEL 分支各自计数**),超出抛 `IllegalStateException`。
### 5.1 结构化并行(PARALLEL)
主图仍是单线。`kind: PARALLEL` 是一个步骤,块内多条分支同时推进;全部完成后走该步在父图上的出边。不是 BPMN fork/join 网关。
JSON 嵌套写,引擎拍平存储。分支内边 `to: null` 表示**该分支完成并等待 join**,不是实例通过。PARALLEL 步自己的出边 `to: null` 才结束实例。
v1:至少 2 条分支;禁止套娃 PARALLEL;join 固定 ALL;任一分支按现有规则否决则整单 `REJECTED` 并 SKIPPED 其余 PENDING;`due.goto` 只能指向同一分支内的步骤。会签仍用单步 `candidates` + `ANY`/`ALL`。
事件:`PARALLEL_ENTERED`、`BRANCH_COMPLETED`(`detail` 为 branch id)、`PARALLEL_JOINED`。
## 6. 条件路由
离开当前步时,取出全部 `from = 当前步` 的边。**先**按 `priority` 升序匹配带 `when` 的边(谓词为真,或 `ref` 且 `routingCondition.matches` 为真);都未命中再按 `priority` 走无 `when` 的边(else)。无条件边不再与条件边抢数字顺序。
`when` 必须是 JSON **对象**,不能再写字符串 key。谓词闭集:`eq/ne/gt/gte/lt/lte`、`in`、`and/or/not`(深度 ≤ 8、叶子 ≤ 32)。宿主复杂逻辑用 `ref` + `args`,例如 `{ "ref": "amountGt", "args": { "threshold": 50000 } }`。
没有匹配边:`NoRouteFoundException`,**整次 approve/start 事务回滚**(JDBC 下任务/实例都不会半更新)。
无条件边是默认分支;`priority` 只在同类边之间比较(多条条件边之间,或多条无条件边之间)。
`RoutingCondition.matches` 看到 `args` 与 `ProcessRuntime`(含业务 `context`、发起人、定义与当前步)。看不到任务意见。上下文在 `start` 时写入,运行中引擎不会改 context。内置谓词仍只读 `ProcessContext` 变量。
引擎仍只注入**一个** `RoutingCondition`。Spring 下注册多个 `NamedCondition` Bean(`key()` 对应 `when.ref`);无门面且列表非空时装配官方分发器。未知 `ref` 为 false,该边不匹配。重复 `key` 启动失败。也可继续提供单个 `RoutingCondition` 门面自行分发(不要与 `NamedCondition` 混用)。无 `NamedCondition` 时默认仍是恒 true。
## 7. ACTION 步骤
进入 ACTION 步时:
1. 事务内插入 `ActionExecution`,状态 `PENDING`。
2. 按转移进入下一步或结束实例(仍在同一事务)。
3. 事务提交后调用 `ActionHandler.execute(actionKey, runtime)`。
4. 成功 → `SUCCESS` + 事件 `ACTION_SUCCEEDED`;失败 → `FAILED`(`errorMessage`)+ `ACTION_FAILED` + 日志 WARNING。
**失败不回滚已提交的审批,不阻塞后续步骤,引擎不做重试。** 宿主用 `queryActionExecutions` 或 listener 自行补发。分发器遇到未知 `actionKey` 会抛 `IllegalArgumentException`,记为该次 ACTION `FAILED`。
```java
import com.jetlumen.ordo.api.runtime.ProcessRuntime;
import com.jetlumen.ordo.api.spi.NamedAction;
import org.springframework.stereotype.Component;
@Component
public class LeaveApprovedMail implements NamedAction {
@Override
public String key() {
return "leave-approved-mail";
}
@Override
public void execute(ProcessRuntime runtime) {
/* ... */
}
}
```
仍可提供单个 `ActionHandler` 按 key 自行分发;不要与 `NamedAction` 混用。
## 8. 审计与监听
`queryHistory(instanceId, page)`:该实例事件,**发生时间升序**。
`ProcessEventType`:
- `INSTANCE_STARTED` / `INSTANCE_APPROVED` / `INSTANCE_REJECTED` / `INSTANCE_WITHDRAWN` / `INSTANCE_CANCELLED`
- `TASK_CREATED` / `TASK_APPROVED` / `TASK_REJECTED` / `TASK_SKIPPED` / `TASK_REASSIGNED` / `TASK_ESCALATED`
- `ACTION_SUCCEEDED` / `ACTION_FAILED`
字段:`id`、`instanceId`、可选 `taskId`/`stepId`/`actor`/`detail`、`occurredAt`。系统完成类事件 `actor` 可为空。`TASK_REASSIGNED` 的 `actor` 为转出人,`detail` 为转入人。`TASK_ESCALATED` 的 `actor` 为空,`detail` 为新办理人 / notify 的 action key / goto 目标步 id。
`OrdoEventListener.onEvent(ProcessEvent)` 在**事务提交之后**按事件顺序调用(含本轮 ACTION 结果)。单个 listener 抛错只打日志,不影响流程和其他 listener。
短假(提交邮件 → 经理批 → 通过邮件)一类时间线示例:
`INSTANCE_STARTED` → `TASK_CREATED` → `ACTION_SUCCEEDED` → `TASK_APPROVED` → `INSTANCE_APPROVED` → `ACTION_SUCCEEDED`
ACTION 成功事件发生在提交之后,因此排在同轮事务内写入的任务/实例事件后面。
## 9. 查询
`PageRequest.of(page, size)`:`page` 从 0 起,`size > 0`。`Page` 含 `content`、`totalElements`、`page`、`size`,以及 `totalPages()` / `hasNext()`。
| 方法 | 过滤 | 排序 |
|---|---|---|
| `queryTasks(TaskQuery, PageRequest)` | assignee、instanceId、definitionId、status、createdFrom/To | 创建时间降序 |
| `queryInstances(InstanceQuery, PageRequest)` | definitionId、status、initiator、startedFrom/To | 开始时间降序 |
| `queryDefinitions(PageRequest)` | 每个 id 的 latest | 定义 id 升序 |
| `queryDefinitionVersions(id, PageRequest)` | 单 id 全部版本 | version 降序 |
| `queryHistory(instanceId, PageRequest)` | 单实例 | 发生时间升序 |
| `queryActionExecutions(instanceId, PageRequest)` | 单实例 | 开始时间升序 |
`TaskQuery.any().withAssignee("maria").withStatus(TaskStatus.PENDING)` 等 with 方法返回新对象。字段 `null` 表示不按该维过滤。
便捷方法(不分页):`findInstance`、`findTask`、`findTasks`、`findPendingTasksByAssignee`、`findPendingTasksByInstanceId`、`findDefinition(id)` / `findDefinition(id, version)`。
## 10. `OrdoEngine` 一览
| 方法 | 说明 |
|---|---|
| `publish` | 发布不可变图版本(相等则 no-op) |
| `start` | 发起;可选 `ProcessContext` |
| `approve` / `reject` | 办理当前 PENDING 任务 |
| `reassign` | 当前办理人把 PENDING 任务转给他人 |
| `processDue` | 认领并处理已到期 PENDING 任务 |
| `withdraw` | 发起人撤回 |
| `cancel` | 管理员/系统取消(引擎不鉴权角色) |
| `find*` | 按 id / 待办索引读取 |
| `queryTasks` / `queryInstances` / `queryDefinitions` / `queryDefinitionVersions` | 分页列表 |
| `queryHistory` / `queryActionExecutions` | 实例审计与 ACTION 记录 |
## 11. 异常
均继承 `OrdoException`(unchecked)。
| 类型 | 何时 |
|---|---|
| `DefinitionNotFoundException` | `start` 等找不到 latest,或实例锁定的 version 不存在 |
| `InstanceNotFoundException` | 撤回/取消等找不到实例 |
| `InstanceAlreadyCompletedException` | 对非 RUNNING 实例完成/撤回/取消 |
| `TaskNotFoundException` | 任务 id 不存在 |
| `TaskAlreadyCompletedException` | 重复办理或已被 SKIPPED |
| `UnauthorizedTaskOperationException` | actor ≠ 当前 assignee(approve / reject / reassign) |
| `UnauthorizedInstanceOperationException` | 非发起人撤回 |
| `NoRouteFoundException` | 当前步没有匹配转移 |
参数空白等会抛 `IllegalArgumentException`,不属于 `OrdoException`。
## 12. 存储
Flyway 脚本按方言分目录:`db/postgresql/migration`、`db/mysql/migration`。未设置 `spring.flyway.locations` 时,starter 按探测到的方言指向对应目录。已有 `flyway_schema_history` 的开发库按版本追加迁移(PARALLEL 为 `V3__parallel_blocks.sql`)。表包括流程头 `ordo_process`、按 `(id, version)` 存储的定义/步骤(含 `parent_step_id` / `branch_id`)/候选人/转移、实例(含 `definition_version`)、任务、并行令牌 `ordo_instance_token`、`ordo_process_event`、`ordo_action_execution`。
自定义方言:实现 `SqlDialect` 并用 `META-INF/services` 注册,或提供 `SqlDialect` Bean。新增列时每个已支持方言目录各加一条迁移。
多 JVM 共享同一库时,多步写入走 `TransactionExecutor`,完成任务/实例用条件更新(仍 PENDING / 仍 RUNNING 才改),避免双花。
## 13. 未提供能力
独立设计器不进本仓库,等 REST 与目录之后由单独产品消费(见 [roadmap.md](roadmap.md))。
暂不在计划中:多租户、设计器 UI。