M3(1/5): @sfeel —— S-FEEL 子集的解析与求值,前后端同源

- lexer / parser(递归下降)/ evaluate / static-check,四文件纯函数,零 IO 零外部调用
- 子集封顶(ADR-006):比较 · 逻辑 · 算术 · 区间(开闭四态)· 列表与 in ·
  封闭内建函数集(字符串 / 数字 / 日期);未知函数一律拦下
- 单元格谓词(DMN 一格):`-` 任意 / 比较 / 区间 / 逗号析取
- staticCheck 给「画图即校验」用:语法 + 未知函数 + 引用路径清单
- app 侧以它替下 passthroughEvaluator:监听内容条件真求值,求值异常按不命中处置并留痕

32 个 sfeel 测试;全仓 182 个测试通过,门禁五道全绿

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
JCAI Main Agent
2026-07-27 01:21:45 +08:00
co-authored by Claude Opus 5
parent f0444f1417
commit 146e9bbfad
13 changed files with 1048 additions and 26 deletions
+16 -3
View File
@@ -7,6 +7,7 @@
*/
import { Pool } from 'pg';
import { drizzle } from 'drizzle-orm/node-postgres';
import { evaluateCondition } from '@sfeel';
import type { Db } from '@shared/db.ts';
import type { EventService, TriggerMatcher } from '@modules/event';
@@ -83,8 +84,20 @@ const emptyInFlightProbe: InFlightWorkProbe = {
/** 触发器判定 —— trigger(L7)随 M5 落地,当下恒不命中(docs/debt.md · M1-1) */
const noTriggerMatcher: TriggerMatcher = { match: async () => [] };
/** S-FEEL 求值 —— @sfeel 随 M3 落地;在此之前监听只用类型 + 关联键过滤 */
const passthroughEvaluator = (): boolean => true;
/**
* 监听内容条件的求值 —— @sfeel 的求值语义**前后端同源**(§8.3)。
* 表达式写坏了不该让事件流停摆:求值异常按「不命中」处置并留痕。
*/
const sfeelEvaluator = (expression: string, scope: Readonly<Record<string, unknown>>): boolean => {
try {
return evaluateCondition(expression, scope);
} catch (error) {
process.stderr.write(
`[listener] 条件求值失败,按不命中处置:${expression} —— ${error instanceof Error ? error.message : String(error)}\n`,
);
return false;
}
};
/** 第一方工具实现 —— 由 app 注册进 ToolRuntime;活动只见调用契约 */
export const firstPartyToolHandlers = new Map<string, ToolHandler>();
@@ -133,7 +146,7 @@ export async function createContainer(config: AppConfig): Promise<Container> {
store: createEventStore(db, ids),
clock: systemClock,
ids,
evaluate: passthroughEvaluator,
evaluate: sfeelEvaluator,
triggers: noTriggerMatcher,
messages: message,
});