Repository navigation
driver-memory 的两个过滤面对**比较值的种类**给出相反答案:{f: /re/}(正则)与 {f: ['x']}(数组)各差一个方向 —— 实测 #5354
Description
修 #5324 / #5328 时给两个过滤面装上共用的形状门,顺手把一批比较值种类喂给两个面做对读,发现两处语义分叉(形状门不碰,也不该碰)。按 Prime Directive #10 单独记在这里,unassigned。
实测原文
单行数据 { id: '1', stage: 'won', score: 10 },InMemoryDriver.find vs memory-matcher.match,worktree 基于 c7406b0ec + #5324 的修复:
live(mingo) ref(matcher)
{ stage: /WON/i } ["1"] [] >>> 分叉
{ stage: ['won'] } [] ["1"] >>> 分叉
两条方向相反,而且都不涉及 null / 缺字段(那一族是 #5299)。
机制
1. RegExp 比较值 —— mingo 当正则匹配,匹配器当结构相等
mingo 按 MongoDB 语义把 {field: /re/} 读作正则匹配。memory-matcher.checkCondition 的分派只豁免 Date 和 Array:
if (typeof condition !== 'object' || condition === null ||
condition instanceof Date || Array.isArray(condition)) {
return value == condition; // ← RegExp 不在这里
}
const keys = Object.keys(condition); // RegExp 的自有可枚举键是 []
const isOperatorObject = keys.some(k => k.startsWith('$')); // false
return JSON.stringify(value) === JSON.stringify(condition); // 'won' vs '{}' → false于是 RegExp 掉进结构相等分支,JSON.stringify(/WON/i) 是 {},永远不匹配。
顺带记录:memory-driver.ts 的 normalizeFilterCondition 明确把 RegExp 排除在算子对象之外并原样传给 mingo(!(value instanceof RegExp)),所以实时路径这一侧是有意支持的。
2. 数组比较值 —— 匹配器靠 JS 松散相等意外匹配
Array.isArray(condition) → return value == condition;'won' == ['won'] 在 JS 里是 true(数组被 toString() 成 'won')。所以匹配器把 {stage: ['won']} 读成了「等于 'won'」。mingo 按 MongoDB 语义读作「字段等于该数组,或字段是包含该元素的数组」,标量字段 'won' 都不满足 → []。
匹配器这一侧几乎肯定不是有意设计的 —— ['won','lost'] 会因为 'won' == ['won','lost']('won,lost')而不匹配,而 ['won'] 匹配;这是 toString() 的巧合,不是任何一种「数组比较值」语义。
为什么是 bug
- 同一个包的两个面,同一个 filter,两个相反答案。
{ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240 的裁决理由原话:「a backend whose two halves disagree about what a filter MEANS is exactly the divergence the ruling closes」。driver-memory 的实时查询路径根本不支持$not—— mingo 抛无 code 的 MingoError,CEL!expr降下来的 RLS scope 在该驱动上直接 500 #5324 已经把形状统一到一个门里,但语义仍是两份实现。 - 匹配器是
FILTER_LOGIC_CASESheld 的那一面,也是formula对照的那一面;实时路径是用户跑的那一面。哪一面「对」需要裁决,但两面同时对两个答案都言之凿凿是明确的缺陷。 {f: ['x']}这种写法在 AI 生成的元数据里很容易出现(作者想写$in却写成了裸数组)。今天它在两个后端上得到两个答案,且都不报错。
为什么 #5324 没有一并修
那一单加的是形状门(词汇表、$between 元数、组合子操作数、{field:{}}),对语义一律不碰 —— 它的实现注释里写明了「It decides SHAPE, never MEANING」。这两条是同一个比较值在两个求值引擎里的不同读法,修它要先裁决哪个读法是 canonical,那是一次协议裁决而不是 shape gate 能顺手带走的。
建议(不代裁决)
RegExp比较值:FilterConditionSchema没有声明它($regex才是声明的写法)。可选:(a) 匹配器补上condition instanceof RegExp分支,与实时路径取齐;(b) 两个面都拒收裸RegExp,要求写{$regex: …}—— 与 driver-memory 的实时查询路径根本不支持$not—— mingo 抛无 code 的 MingoError,CEL!expr降下来的 RLS scope 在该驱动上直接 500 #5324 「未声明的一律拒收」同向,但会打断今天在实时路径上能用的写法(需先清点是否有生产者)。- 数组比较值:匹配器的
value == condition松散相等几乎肯定该改。可选:(a) 与 mingo 取齐(等于数组,或字段数组包含该元素);(b) 两个面都拒收裸数组、要求$in(formula的evalField已经是这个态度:if (Array.isArray(spec)) return false,即 fail-closed)。注意formula在这一条上是第三个答案。
无论走哪条,建议与 #5299 / #5298 / #5239 一起看 —— 它们是同一族「一个 filter,N 个后端 N 个答案」的裁决。
未验证的部分
我没有测 driver-sql / driver-sqlite-wasm / driver-mongodb 在这两个输入上的答案(driver-sql 对裸数组比较值会走 coerceFilterValue → knex 绑定,预期报错但没实测);也没有清点仓内是否真有生产者产出裸 RegExp 或裸数组比较值。严重度请 PM 按 triage 定,不代表我判断它低。
关联:#5324 / #5328(实测出这两条的那一单;形状门已统一,语义未动)、#5299(同包两面在「字段没有值」上的三处分叉)、#5298、#5239(一致性表)、#5240(两面必须对 filter 的含义一致)。
Activity
os-zhuang commentedon Aug 5, 2026
分诊(backlog sweep,主 backlog 代扫):挂⚠️ 与 #5442(
pm:queue+domain:engine。理由:落点packages/plugins/driver-memory的两个过滤面(driver-*属 engine 域);实测证据具体,符合 auto-queue 判据。flattenFilterCondition摊平数组)可能同缝,认领前先核是否合并为一单;与 #5433 / #5440 / #5444 同文件族,严格跨轮串行。不构成认领。会话:session_01N3uGFF8teXbpgtbEJ1aYXuGenerated by Claude Code
os-zhuang commentedon Aug 5, 2026
转
pm:on-hold(2026-08-05,维护者裁决,锚点 #5499):driver-memory 缺陷修复投入冻结(撤销今晨入队分诊)。重启条件见锚点;若影响 CI 判绿正确性,按 restore-invariant 例外升级。不派发、不催。会话:session_01N3uGFF8teXbpgtbEJ1aYXuGenerated by Claude Code
os-zhuang commentedon Aug 6, 2026
按 #5499 重启条件 + #5704 收口关闭(not planned):维护者 2026-08-06 裁定 driver-memory 的测试职能由 sqlite
:memory:替代,迁移已完成(PR #5715 / #5797 / #5806);本包处于投入冻结(#5499),两过滤面的比较值语义分叉不再计划修复。备注:本单建议里「与 #5299/#5298/#5239 一起看」的跨后端 filter 语义族裁决不受本次关闭影响——那些单独立于 driver-memory 存续。若维护者后续裁定包本体保留并解冻,可重开。Generated by Claude Code