Repository navigation
driver-memory 的实时查询路径根本不支持 $not —— mingo 抛无 code 的 MingoError,CEL !expr 降下来的 RLS scope 在该驱动上直接 500 #5324
Description
Activity
补一条实测:这个缺陷不止
$not—— 任何未知$op都从 ADR-0112 信封里逃出去做 #5158 第 2 步(engine Door 2 下沉 + 四驱动数组方言删除)时实测到,本 issue 描述的机制比标题窄:
normalizeFilterCondition的default: result[op] = val是通用透传,$not只是它的一个实例。实测原文(worktree 基于
0f1711470,InMemoryDriver+ 两行数据):await find({ name: { $sounds_like: 'alpha' } }) => THROW 'unknown query operator $sounds_like' code= undefined status= undefined对照
driver-sql同一输入:await find({ stage: { $sounds_like: 'won' } }) => THROW 'Unsupported filter operator "$sounds_like" on field "stage". Supported operators: …' code= INVALID_FILTER status= 400即:字段级未知算子与本 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
- addedbugSomething isn't workingSomething isn't working
on 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对它无法求值的东西不拒收,而是二选一地静默处理:现在 driver-sql 同输入 未知 $op(default:通用透传)原样交给 mingo → 抛无 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 更要紧。已确立、不必重查的事实
- driver-memory 的实时查询路径不支持文档级
$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
- driver-memory 的实时查询路径不支持文档级
- added a commit that references this issue
on Aug 5, 2026 - added 3 commits that reference this issue
on Aug 6, 2026
修 #5240(四后端对
{ field: {} }拒收)时,为了确认 driver-memory 那一行的现场而实测了驱动的真实查询路径,发现这一处不属于 #5240 范围的缺陷。按 Prime Directive #10 单独记在这里,unassigned。现象
InMemoryDriver.find并不走memory-matcher.ts的match()—— 它只从那个文件 import 了getValueByPath,过滤实际由convertToMongoQuery()→ mingo 完成。而 mingo 是 MongoDB 语义的实现,MongoDB 没有文档级$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 在它上面不是「多返回/少返回几行」,而是整个查询抛异常。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)。