cmx-rulesengine 设计与功能方案

一个用 Rust 打造、可独立部署的决策规则引擎微服务。对标全球最先进的规则引擎(GoRules ZEN / Camunda DMN / Drools / IBM ODM),采用 cmx-flowengine / cmx-report 的**「一芯多壳」架构范式,是 CMX 平台 metaKind 分类学的第五元:RULE(决策规则)**。

  • 文档性质:源码级设计与功能方案(图文并茂,SVG 以 base64 内嵌)
  • 对应源码cmx-rulesengine/(6 crate + 前端 6 页 + 5 存储表)
  • 技术栈:Rust · axum · tokio-postgres · Rhai 1.25 · 自研 FEEL 引擎
  • 部署端口:独立微服务 :8094(或内嵌门户 / 反代壳三形态)

目录

  1. 产品定位与设计目标
  2. 总体架构:一芯多壳
  3. 领域模型:决策集一体三态
  4. 决策表:声明式内核主体
  5. FEEL 表达式引擎
  6. 决策图:JDM 拓扑编排
  7. 脚本能力:Rhai 过程式逃生舱
  8. 完整性分析:gap / overlap
  9. 可解释性:求值主流程与 trace
  10. 多租户:db-per-tenant
  11. 存储层:五张表
  12. API 全景
  13. 前端工作台
  14. 里程碑与质量保障

一、产品定位与设计目标

cmx-rulesengine 是 CMX 平台 metaKind 分类学继 DCT(字典)/ DOC(单据)/ FLX(流程表单)/ RPT(报表) 之后的第五元:RULE(决策规则)。它对齐当前 Rust 生态唯一生产级业务规则引擎 GoRules ZEN 的形态(决策图 + 决策表 + 表达式 + 逃生舱、纯 Rust、嵌入式微秒级),并在两处谋求超越

图 1 · metaKind 分类学第五元 RULE 与决策集一体三态

1.1 三种规则范式的选型

业界规则引擎可归为三大范式,本引擎的取舍如下:

范式代表适用本引擎取舍
① 产生式(Rete/PHREAK)Drools、CLIPS、IBM ODM推理链 / 事实累积 / CEP / 专家系统后置可选(少数场景)
② 决策表(DMN 标准)Camunda DMN、OpenL Tablets无状态「输入→决策」(定价/资格/合规/风控)✅ 内核主体
③ 决策图(JDM,JSON 化)GoRules ZEN、DecisionRules编排多决策 + 可视化 + 嵌入式高性能✅ 顶层编排

引擎以 ②决策表为内核主体③决策图为顶层编排,绝大多数业务(信贷审批、风控、定价、资格、合规)是无状态的「输入事实 → 输出决策」,决策表 + 决策图足以覆盖;产生式推理作为后置可选能力。

1.2 相对 ZEN 的两处超越

  1. 表达式走 FEEL/DMN 标准 —— ZEN 用私有 business-first 方言(与 DMN 工具链不互通),本引擎自研 FEEL 引擎拿到「世界级」标准的可信度、可移植性、业务可读性。
  2. 补齐 ZEN 缺失的两块世界级能力
    • 决策表 gap / overlap 完整性分析(空隙与重叠检测)——ZEN 与多数引擎缺此;
    • 决策 trace 记录失败归因(ZEN 的 trace 不显示「哪个节点导致失败、为什么」)。

1.3 设计纪律

  • 永不 panic:一切错误(表达式错、脚本错、结构错)以 Result 返回并落 trace 的 failure,绝不穿透宿主。
  • 确定性 / wasm 友好:核心引擎不引 Instant、不做 IO,求值可复现(脚本沙箱用操作数计数而非 wall-clock 超时)。
  • 判定侧永远 FEEL:脚本能力只在输出/计算侧,判定侧(决策表输入列)永远是可静态分析的 FEEL,保 gap/overlap 完整性分析始终有效。

二、总体架构:一芯多壳

参照 cmx-flowengine(S0→S6)与 cmx-report 的架构范式:中立核零框架依赖,薄壳承载部署形态。整个引擎由 6 个 crate 分层组成,依赖严格自上而下(壳 → 核)。

图 2 · 总体架构 · 一芯多壳(One-Core-Multi-Shell)

2.1 六 crate 职责

crate层次职责依赖
cmx-rule-model中立领域核HitPolicy / DecisionDef / DecisionBody / DecisionTable / DecisionGraph / ScriptBody / TraceNode / CoverageReportserde
cmx-rule-feel表达式 / 脚本自研 FEEL 引擎(tokenizer+Pratt+eval+~25 内置)+ Constraint 区域推理 + Rhai 脚本沙箱model, rhai
cmx-rule-engine求值引擎evaluate_with(决策表/图/脚本分派)+ graph.rs(Kahn 拓扑)+ analyze.rs(gap/overlap)feel, model
cmx-rule-store-pg持久化PgDecisionStore(实现 DecisionStore trait)+ 5 张表 DDLmodel
cmx-rule-app中立应用核26 条 REST 路由(rule_routes::<S> 泛型)+ handlers + tenancy + stats + 求值即落审计engine, feel, model, store, axum
cmx-rule-server部署壳独立 bin,chassis 装配,监听 :8094app, axum

依赖倒置纪律cmx-rule-model 是中立核,只依赖 serde,不知道 HTTP、不知道 PostgreSQL、不知道 Rhai;求值引擎 cmx-rule-engine 只认领域模型与 FEEL/脚本内核,不认存储与 Web。这使得引擎可被内嵌、可独立部署、可多租户,无框架锁定。

2.2 三种部署形态

  • 独立微服务cmx-rule-server bin 监听 :8094,自持 PostgreSQL 连接,RULE_AUTH_MODE/RULE_TENANCY 控认证与租户。
  • 门户内嵌cmx-rule-apprule_routes::<S> 泛型路由挂进门户,同进程共享装配。
  • 门户反代壳cmx-rule-api(proxy-only)在门户侧反代到独立 :8094,内嵌 ↔ 独立仅切 urls.rules 一处配置,字节级 parity。

三、领域模型:决策集一体三态

一个决策集DecisionDef)是引擎的基本单元,拥有跨版本稳定的 key、展示 nameversion 与决策体 body。决策体 DecisionBodyinternally taggedtag="kind",与 GoRules JDM 节点标注同构)的三态枚举:

#[serde(tag = "kind")]
pub enum DecisionBody {
    #[serde(rename = "decisionTable")] DecisionTable(DecisionTable), // 决策表
    #[serde(rename = "graph")]         Graph(DecisionGraph),         // 决策图
    #[serde(rename = "script")]        Script(ScriptBody),           // 脚本决策
}

序列化时 body flatten 进顶层,即 {key, name, version, kind, ...}kind:"decisionTable"hitPolicy/inputs/outputs/ruleskind:"graph"nodes/edgeskind:"script"lang/script

3.1 版本 · 发布 · 激活生命周期

阶段说明
草稿cmx_rule_definition设计器保存,含结构校验 + 脚本语法预检
发布cmx_rule_release产不可变 release,version+1
激活cmx_rule_release.active求值按 key 装载 active 版本

求值有两条路径:POST /decisions/{key}/evaluate(按 key 装载激活版本)与 POST /evaluate(内联 definition 试算,不落库、先 validate)。


四、决策表:声明式内核主体

决策表是引擎的招牌能力:一张二维表,输入列 × 规则行 × 输出列 + 命中策略。判定侧(输入列)永远是 FEEL unary test,输出侧(输出列)是 FEEL 表达式 / 字面量 / =rhai: 脚本。

图 3 · 决策表解剖 · 输入判定(FEEL)× 输出赋值
  • 输入列(InputClause):绑定一个 FEEL 表达式(如 scoreincome * rate),单元格是 unary test(> 700[600..700)"north","south"- 通配)。
  • 输出列(OutputClause):命名输出字段,单元格是 FEEL 表达式 / 字面量 / =rhai: 脚本。
  • 规则行(DecisionRule)inputEntries[] 对齐输入列、outputEntries[] 对齐输出列。
  • 命中策略(HitPolicy):多行同时命中时如何裁决。

4.1 十一种命中策略

对齐 DMN 标准,分单命中 / 多命中 / 聚合三族,共 11 种:

图 4 · 11 种命中策略(Hit Policy)· 对齐 DMN 标准
代码名称语义
单命中UUnique唯一——多命中即冲突报错(最严格,默认)
AAny任意——多命中须输出相同,否则报错
PPriority优先级——按输出值优先序取第一
FFirst首个——按规则行序取第一命中
多命中CCollect收集——所有命中输出成列表
RRuleOrder规则序——按行序收集
OOutputOrder输出序——按输出优先序收集
聚合C+CollectSum求和
C<CollectMin最小
C>CollectMax最大
C#CollectCount计数

HitPolicy::is_multi() 判定 C/R/O 返回数组;is_aggregate() 判定 C+/C</C>/C# 返回单一聚合值。


五、FEEL 表达式引擎

因离线环境无法拉取 dsntk-rs,本引擎自研 FEEL(DMN 标准表达式语言)引擎:一条 tokenizer → Pratt 解析器 → 求值器 三段流水线,配 ~25 个内置函数。这是相对 ZEN 私有方言的关键超越——拿到 DMN 标准的可移植性与业务可读性。

图 5 · FEEL 表达式引擎 · 自研三段流水线

5.1 三段流水线

  1. Tokenizer:词法扫描,识别数字、字符串、标识符、运算符、区间 [a..b]
  2. Pratt Parser:按最小绑定力(binding power)解析,infix 左结合、** 右结合,构造 AST。
  3. Evaluator:递归求值 AST → serde_json::Value数值统一 f64(与脚本引擎归一一致)。

三个入口覆盖不同载体:

  • eval_unary_test(cell, value, ctx) —— 决策表输入格判定(> 700 / [18..65) / contains(?, "vip"))。
  • eval_output(src, ctx) —— 决策表输出格 / 图 expression 节点求值。
  • eval_expression(src, ctx) —— 完整表达式(/feel/expression 端点)。

5.2 内置函数库(~25,逐字对齐)

floor ceil round abs modulo sqrt min max sum avg upperCase lowerCase substring contains startsWith endsWith concat string number trim len sort append not coalesce

5.3 区域推理(Constraint / NumRange)

FEEL 引擎额外提供 parse_constraint(cell),把单元格解析为区间 / 集合 / 通配Constraint,供决策表 gap/overlap 分析做区域相交推理(而非逐点求值)——见第八章。含 not() / != 的列判为「未知」,不误报。


六、决策图:JDM 拓扑编排

决策图(JDM,JSON Decision Model)是顶层编排能力:一张 JSON DAG,数据从 Input 左→右流至 Output,中间穿过多类节点。引擎按 Kahn 拓扑排序逐节点求值,前序节点输出 merge 回累积上下文,传递后继。

图 6 · 决策图 · JDM 拓扑编排

6.1 六类节点

节点 type作用
input输入事实入口
output决策输出出口
decisionTable内嵌决策表求值
expressionFEEL 表达式映射({key: expr} 若干)
scriptRhai 脚本节点(SC1)
decision引用另一决策集(子决策,可递归)

6.2 拓扑求值与子决策路由

  • Kahn 拓扑:拓扑序逐节点,读累积上下文 → 求值 → 输出 merge 回上下文 → 传递后继。成环在求值期报错并落 trace(后端不校验环,仅求值期检出)。
  • 子决策路由decision 节点按主决策维度(org / 字典)BFS 预载解析器(build_resolver)→ 递归求值,MAX_DEPTH 防失控。每层子决策的逐节点 trace 并入父 trace,可解释性穿透。

七、脚本能力:Rhai 过程式逃生舱

当声明式的决策表 / 单表达式 FEEL 表达不动(阶梯累进、多步过程、带状态迭代)时,规则作者用一段受控、沙箱、可审计的 Rhai 脚本兜底。脚本能力分 SC0(接缝)+ SC1-SC4(四载体),全部复用同一求值内核,主流程零改动(均在既有 match 分派点加分支)。

图 7 · 脚本能力 · Rhai 过程式逃生舱(SC0–SC4)

7.1 四载体

载体形态实现接缝
SC1 · 决策图 Script 节点graphtype:"script" 节点graph.rs"script"=> 分支,累积上下文→对象 merge
SC2 · 决策表脚本单元格输出格 =rhai: 前缀eval_outputeval_scripted 一行 seam 换
SC3 · 脚本函数库cmx_rule_script_function发布后注册进引擎,三载体皆可 name(args) 调用
SC4 · 脚本决策kind:"script" 整决策DecisionBody::Script,可被图 decision 节点引用

唯一接缝是 eval_scripted(lang, src, ctx)feel/"" → 现有引擎(零回归)、rhai → 脚本内核,且 sniff =rhai: 前缀。

7.2 沙箱与安全

Rhai 沙箱内核(script.rs)每次求值新建引擎,脚本间不共享可变状态,四道闸门:

闸门限额防护
max_operations100,000死循环 / CPU 耗尽
max_call_levels32递归爆栈
max_string_size64 KiB字符串内存爆炸
max_array_size / max_map_size10,000集合内存爆炸
  • 确定性:用操作数计数而非 wall-clock 超时(不引 Instant,保 wasm 友好),求值可复现。
  • Don’t-Panic:Rhai 官方保证脚本永不 panic 宿主,错误以 Result 返回 → 落 FeelError::Script(带行号)。
  • 数值归一:Rhai 有独立 i64/f64(1+1→整数 2),递归归一为 f64(2.0),与 FEEL 算术一致,防 Number(2) != Number(2.0) 破坏。

7.3 架构约束:判定侧永远 FEEL

脚本只在输出 / 计算侧,决策表判定侧(inputEntries)永远 FEEL → gap/overlap 完整性分析对决策表判定始终有效,脚本黑盒天然跳过。脚本决策(SC4)为黑盒analyze 显式返回「不参与完整性分析」(非静默假 complete)——诚实披露。


八、完整性分析:gap / overlap

这是相对 ZEN 的世界级超越能力:对决策表做区域推理(非逐点穷举),检出「空隙」(gap,某输入组合未被任何规则覆盖)与「重叠」(overlap,多规则覆盖同一区域,单命中策略下潜在冲突)。

图 8 · 完整性分析 · gap(空隙)/ overlap(重叠)

8.1 两种检测机制

检测方法要点
overlap 重叠Constraint::intersects 对每对规则逐列判相交not()/!= 的列判「未知」→ 不误报;U/A/P 等单命中策略下重叠即潜在冲突
gap 空隙每列按边界切分 → 取代表点 → 笛卡尔积(里程表编码)遍历组合 → 逐组合过真实 any_rule_matches 查覆盖用真实求值器验证,非近似;笛卡尔积上限保护,超出则显式告知

gap 检测的巧妙在于:不穷举无穷输入空间,而是按规则边界把每列切成有限段,每段取一个代表点,代表点的笛卡尔积就是「有区分意义的输入组合」的有限集,逐一过真实求值器即可确定覆盖性。

8.2 应用价值

设计器(designer.js / graph-designer.js)在 property 区实时展示 gap/overlap 徽标,规则作者保存前即可发现「score < 600 没有任何规则处理」这类完整性缺陷——这是金融/合规规则不可或缺的质量门。POST /decisions/{key}/analyze 端点消费 CoverageReport{gaps, overlaps}


九、可解释性:求值主流程与 trace

每次求值都是无状态的(输入事实 → 输出决策),并产出逐节点 trace(含失败归因)与审计日志。这是相对 ZEN 的另一处超越——ZEN 的 trace 不显示「哪个节点导致失败、为什么」。

图 9 · 求值主流程 · 无状态决策 + 可解释 trace

9.1 主流程

  1. POST /evaluate/decisions/{key}/evaluate —— 提交 input 事实(或内联 definition 试算)。
  2. 装载定义 —— 按 key 取 cmx_rule_release active 版本;内联走 validate
  3. build_resolver —— 决策图含 decision 节点时 BFS 递归预取被引用决策。
  4. with_functions —— load_published_functionsthread_local RAII 注入已发布脚本库(全程同步无 await,无跨请求泄漏)。
  5. evaluate_with —— 分派求值:决策表 row_matches / 图 Kahn 拓扑 / 脚本 Rhai。
  6. 组装响应 + append_log —— 返回 {output, trace[], timingUs, failure} 并落审计日志。

9.2 TraceNode 结构

每个节点产一个 TraceNode,构成完整的可解释链:

字段含义
nodeId / nodeKind节点标识与类型(input/decisionTable/script/…)
matchedRules[]命中的规则行号
output该节点输出快照
timingUs微秒级时延
failure?失败原因(脚本错带行号

失败归因不误标:脚本节点失败时 nodeKind 保持 "script"(而非被误标为 "graph"),审计中心(logs.js)与仿真台(simulator.js)据此下钻,红标定位失败节点。


十、多租户:db-per-tenant

多租户采用 db-per-tenant 物理隔离(R3,镜像 flow S2)。single 模式单库零回归;multi 模式按租户派生 rule_<tenant> 库并懒备。

图 10 · 多租户 · db-per-tenant 物理隔离
  • 派生tenancy.rs):singleRULE_DB_ID(默认 rule_pg);multirule_<tenant>(小写),由 RULE_TENANT_DB_URL_TEMPLATE{tenant} 占位派生。
  • 懒备库ensure_current_ready 按租户选 db_id + 懒建库建表。规则引擎无长驻运行态,故比流程 S2 更简单——只需「选库 + 懒备」,无 per-tenant 缓存引擎。
  • 认证RULE_AUTH_MODE=off 单租户 default 放行零回归;开启则验 JWT claim(HS256/RS256)+ scoped 租户上下文(task_local)。

十一、存储层:五张表

PgDecisionStore 实现 DecisionStore trait,5 张表覆盖草稿 ↔ 发布 ↔ 日志 ↔ 用例 ↔ 脚本库。无 RU(工作内存)表——决策是无状态的,不需要产生式引擎的事实工作内存。

图 11 · 存储层 · 5 张表
职责
cmx_rule_definition决策集草稿(key PK · body JSONB · published 标记)
cmx_rule_release不可变发布版本(key+version · active 激活位)
cmx_rule_decision_log决策审计日志(input/output/trace/timing/failure
cmx_rule_test_case测试用例(决策集绑定 · 期望输出 · 覆盖率)
cmx_rule_script_function脚本函数库(name PK · params · body · published

决策集稳定 key 贯穿五表:发布产不可变 release,求值装载 active 版本,每次求值落 log。tokio-postgres 并行连接池,日志表按 decision_key / created_at / failure 建索引支持审计下钻。


十二、API 全景

全部 26 条 REST 路由挂在 /api/rules/v1 前缀下(rule_routes::<S> 泛型,内嵌/独立同源),按功能分八组:

端点
定义 · 设计GET /definitions · POST /definitions/draft · POST /definitions/validate · GET,DELETE /definitions/{key}
发布 · 版本POST /definitions/{key}/publish · GET /definitions/{key}/versions · POST /definitions/{key}/versions/{version}/activate
求值POST /decisions/{key}/evaluate · POST /evaluate(内联试算)
仿真 · 分析POST /decisions/{key}/simulate · POST /decisions/{key}/analyze(gap/overlap)
测试用例GET,POST /decisions/{key}/tests · POST /decisions/{key}/tests/run · DELETE /decisions/{key}/tests/{id}
审计日志GET /decisions/{key}/logs · GET /logs/{id}
FEELPOST /feel/eval · POST /feel/expression · POST /feel/validate · GET /feel/functions
脚本 · 监控GET,POST /functions · POST /functions/draft · GET,DELETE /functions/{name} · POST /functions/{name}/publish · POST /script/eval · GET /stats

统一响应信封 {code, msg, data}code:0 成功,业务错 code:1 带可读 msg


十三、前端工作台

前端是门户 native 四区工作台explorer(决策集列表)/ content(编辑)/ property(属性/分析/trace)。共 6 个页面——3 个列表工作台 + 3 个设计器,均为 shadow DOM 自包含 ES 模块,适配 light/dark 双主题。

图 12 · 前端 · 门户 native 四区工作台
页面角色
design-workbench决策设计工作台 · 总入口(决策集浏览 + 图态只读预览)
designer决策表设计器 · 可编辑网格 + fx 向导 + 实时 gap/overlap
graph-designer决策图设计器 · 手搓 SVG DAG 编辑(增删节点/连线防环/内嵌决策表)
sim-workbench决策应用工作台 · facts 表单 → 求值 → 输出 + trace
simulator决策仿真台 · 用例集批跑 + 通过率 + 覆盖率
logs决策审计中心 · 日志列表 + 全量 trace 归因下钻(失败红标)

多实例 openWorkNode 开成 Tab;native 页经 /api/native-pages 供给,门户 F3 反代与独立 :8094 同源。设计器 / 仿真台对标报表的 designer / applier 双台形态。


十四、里程碑与质量保障

14.1 演进里程碑

里程碑目标状态
R0决策表内核 · 11 命中策略 · FEEL 子集 · trace · 4 表
R1全 FEEL 表达式引擎(tokenizer+Pratt+eval+~25 内置)
R2 / R3JDM 决策图拓扑编排 / db-per-tenant 多租户
F1–F5前端:发布版本 · 决策表设计器 · 决策图设计器 · 仿真台 · 审计中心
SC0–SC4脚本能力:Rhai 接缝 + 四载体 + 函数库

14.2 质量保障

维度断言数结果
单元测试(feel + engine + model)63✅ 全绿
脚本专项 API 测试(qa-script.sh55✅ 全绿
后端功能回归(qa-backend-1/2.sh146✅ 全绿
clippy 静态分析✅ 零告警

全部核心能力有真机黑盒(HTTP API 断言)+ 单元测试双重覆盖;脚本沙箱三闸门、数值 f64 归一、判定侧 FEEL 保护均经端到端验证。详见 docs/脚本能力测试报告-2026-08-19.mddocs/测试报告-2026-08-17.md


更多推荐