Repository navigation
driver-memory 的实时查询路径根本不支持 $not —— mingo 抛无 code 的 MingoError,CEL !expr 降下来的 RLS scope 在该驱动上直接 500 #5324
Description
修 #5240(四后端对 { field: {} } 拒收)时,为了确认 driver-memory 那一行的现场而实测了驱动的真实查询路径,发现这一处不属于 #5240 范围的缺陷。按 Prime Directive #10 单独记在这里,unassigned。
现象
InMemoryDriver.find 并不走 memory-matcher.ts 的 match() —— 它只从那个文件 import 了 getValueByPath,过滤实际由 convertToMongoQuery() → mingo 完成。而 mingo 是 MongoDB 语义的实现,MongoDB 没有文档级 $not,于是:
WHERE {"$not":{"stage":"won"}} => THROW MingoError | code= undefined | status= undefined | unknown top level operator: $not
WHERE {"$or":[{"$not":{"stage":"won"}}]} => THROW MingoError | code= undefined | status= undefined | unknown top level operator: $not
WHERE {"$and":[{"$not":{"stage":"won"}}]} => THROW MingoError | code= undefined | status= undefined | unknown top level operator: $not
(实测原文,InMemoryDriver + syncSchema('deal') + 两行数据,worktree 基于 26e1029f5。)
normalizeFilterCondition 对 $not 只是递归归一化后原样透传 (result[key] = ...),没有任何 De Morgan 改写,所以 $not 在任何位置都失败,不只是顶层。
为什么是 bug
$not是已声明的协议算子。 specLOGICAL_OPERATORS声明它;driver-sql 实现它([driver-sql] 未知查询 operator(is/is_null)静默透传返整表,应报错;缺 $null 处理 #2704 加的分支,$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 又把它改成 NULL-safe);driver-mongodb 专门有一层翻译(mongodb-filter-logic-translation.test.ts)正是为了绕开「MongoDB 没有文档级$not」;memory-matcher.match自己也实现了它。四个后端里只有 driver-memory 的实时路径没有。- 它落在权限路径上。
cel-to-filter.ts把 CEL!expr编译成{ $not: {...} },这是 RLS read scope 的常规产物。driver-memory 是默认的开发/测试驱动 —— 一条带否定的 scope 在它上面不是「多返回/少返回几行」,而是整个查询抛异常。 - 异常逃出了 ADR-0112 信封。
MingoError没有code也没有status,于是mapDataError走 default 分支,客户端拿到的是 500 形状的裸{ error }—— data: unsupported-filter-operator refusal ships without error.code and leaks the [sql-driver] prefix #4436 为这个驱动建立的INVALID_FILTER/ 400 envelope(见memory-filter-refusal-envelope.test.ts)对这一类完全没覆盖到。
为什么至今没被测出来
FILTER_LOGIC_CASES 对 driver-memory 只经由 memory-matcher(memory-matcher-or-semantics.test.ts)跑,那是参考实现,不是驱动的实时路径;而 driver-sql / driver-sqlite-wasm / driver-mongodb 三家的同名 conformance 都是穿过真驱动跑的。表里第 $not ANDs with its sibling keys inside a branch 条(filter-logic-conformance.ts:172)因此从未在 InMemoryDriver.find 上执行过一次。
即:一致性表覆盖了这个后端的一半,而没覆盖的那一半正好是用户真正用到的那一半。
建议(不代裁决)
两条路都要维护者/spec 车道拍板方向,故只列不选:
- 实现它 —— 在
normalizeFilterCondition里对$not做 De Morgan 下推(driver-mongodb 已有先例可抄),顺带把$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 的 NULL-safe 极性一并对齐(否则会新增一处与 driver-sql 的分叉,参见 read-scope-sql 的$not有两处与 SQL 驱动分叉:非 NULL-safe(#5146 后的最后一个异类),且{ $not: {} }编译成空 → RLS 整表放行 #5297 在 service-analytics 上的同款)。 - 响亮拒收 —— 在归一化阶段抛
INVALID_FILTER/ 400,至少把 500 形状变成 catalogued 400;但这等于宣布 driver-memory 不支持一个已声明算子,与「参考实现」的定位冲突。
另外无论走哪条,建议补一条 FILTER_LOGIC_CASES 穿过 InMemoryDriver.find 的 conformance 套件 —— 覆盖缺口本身比这个 $not 更值钱(可与 #5239 同批考虑)。
未验证的部分:我没有测 driver-mongodb 的翻译层是否已经覆盖全部 $not 形状,也没有量化今天有多少 RLS 规则真的编译出 $not。严重度请 PM 按 triage 定,不代表我判断它低。
关联:#5240(实测出这一条的那一单)、#5146(driver-sql 的 NULL-safe $not)、#5297(service-analytics 的 $not 分叉)、#5239(一致性表)、#4436(driver-memory 的 refusal envelope)。
Activity
os-zhuang commentedon Aug 4, 2026
补一条实测:这个缺陷不止
$not—— 任何未知$op都从 ADR-0112 信封里逃出去做 #5158 第 2 步(engine Door 2 下沉 + 四驱动数组方言删除)时实测到,本 issue 描述的机制比标题窄:
normalizeFilterCondition的default: result[op] = val是通用透传,$not只是它的一个实例。实测原文(worktree 基于
0f1711470,InMemoryDriver+ 两行数据):对照
driver-sql同一输入:即:字段级未知算子与本 issue 记的文档级
$not是同一条缝的两个出口,拒收都发生了(不是静默),丢的都是信封。所以本 issue 的第二条建议(「在归一化阶段抛INVALID_FILTER/ 400」)如果落地,建议按未知$op通用来写,而不是只加一条$not分支 —— 否则$sounds_like这一半仍然是 500 形状。为什么以前没暴露:这个输入过去由数组路径接住(
convertConditionToMongo的Unsupported filter operator "…",带信封),而数组路径正是 #5158 删掉的那条。memory-filter-ast-vocabulary.test.ts里对应用例已迁到声明路径并按现状放宽为.rejects.toThrow(),注释指向本 issue —— 本 issue 修好后请一并收紧回带信封的断言。另外同一
switch里还有一条吞掉 arm(连异常都没有)的近邻:形状错误的$between静默匹配 0 行,已另立 #5328(机制不同:透传 vs 吞掉,症状不同:无信封异常 vs 无声错答案),建议同批看。未验证:我没有清点
default分支还会放行多少个 mingo 不认识的$op,也没有测$regex族在形状错误时的行为。Generated by Claude Code
os-zhuang commentedon Aug 4, 2026
认领 + 派发,与 #5328 合并为一次派发(engine/services 车道 PM,会话
session_01Pbu27iNUfQCHeuS551Rqo7)分支
claude/issue-5324-memory-filter-refuse-what-it-cannot-evaluate,专用 worktree/home/user/objectstack-issue-5324,域domain:engine,标target:v17(收紧类,窗口在 GA 前)。为什么与 #5328 合并
核过现场:两条缺陷在同一个函数的同一个 switch 里 ——
packages/plugins/driver-memory/src/memory-driver.ts:878的normalizeFilterCondition,#5328 是case '$between':(:964)的有条件写入,本单是default:(:997)的通用透传。分两单派意味着第二个 agent 去改第一个刚重写过的 switch,必然返工。一个 PR 同时Fixes #5324与Fixes #5328。两条其实是同一个形状
normalizeFilterCondition对它无法求值的东西不拒收,而是二选一地静默处理:$op(default:通用透传)code/无status的MingoError,逃出 ADR-0112 信封(500 形状)INVALID_FILTER/ 400$between(comparand 非二元数组)find返回[]INVALID_FILTER/ 400一个静默变 500、一个静默变空结果集,方向相反,病因相同:该拒收的没拒收。#4436 / #3948 已确立「无法编译的过滤器必须响亮拒收,不能被跳过或改写」,本单是把这条补进 driver-memory 的实时查询路径。
$not只是 #5324 正文举的例子 —— 修法要按未知$op通用来写,不是加一条$not分支(这一点由 #5158 的实现方实测确认:{name:{$sounds_like:'alpha'}}同样逃逸,code/status皆undefined)。门禁的洞(这是本单最值得处理的部分)
FILTER_LOGIC_CASES对 driver-memory 只经参考匹配器跑,没穿过真驱动 —— 其余三家都穿了。所以一致性表全绿,而这些算子在真驱动上根本不工作。修实现的同时要把这个洞补上,否则下一条同类缺陷照样查不出来。这比修两个 case 更要紧。已确立、不必重查的事实
$not(MongoDB 无此算子),CEL!expr降下来的 RLS scope 在该驱动上直接报错。filter-refusal.ts({ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240 落地),unsupportedFilterError与 driver-sql 的孪生保持逐字一致的INVALID_FILTER/ 400 信封 —— 复用它,不要另造。#5328的$between现状此前由数组路径的守卫接住,而数组路径已被 [engine] driver-sql 编译 spec 未声明的「数组 where 方言」—— 与 Turso remote 的拒收分叉,需一次定调(接纳进 spec 或响亮弃用) #5158 / PR fix(objectql,driver-sql,driver-memory,driver-mongodb)!:FilterArray在 engine 门下沉,四驱动数组方言删除 (#5158 拍板 C 第 2 步) #5329 删除;对象路径从未有过守卫。相关用例目前按现状钉在resolves.toEqual([]),修复时一并改。⛔
packages/spec/**一行不改 —— 若认为FILTER_LOGIC_CASES本身需要补 null/畸形 case,那归 spec 车道(#5239),本单只补 driver-memory 侧的穿透真驱动。⛔
content/docs/releases/**一行不改。Generated by Claude Code
fix(driver-memory): `$options` without `$regex` is refused too — the …
fix(driver-memory): refuse the filters the live query path cannot eva…
fix(driver-memory): the analytics face stops round-tripping comparand…
fix(service-analytics): escape LIKE comparands in all three SQL compi…
fix(objectql): put `having`'s operator refusals inside the ADR-0112 e…
fix(driver-mongodb): refuse malformed $between, undeclared node-level…