做 #5324 / #5328 (normalizeFilterCondition 的「该拒收没拒收」)时,顺手清点同包第三个 过滤面,发现这一处不属于那两单范围的缺陷。按 Prime Directive #10 单独记在这里,unassigned。
位置
packages/plugins/driver-memory/src/memory-analytics.ts 的 normalizeFilters → flattenFilterCondition(约 :415)。它把 query.where 摊平成 cube 风格的 { member, operator, values } 列表。
现象:两处静默 continue
// $or / $not are not yet supported in the cube pipeline; ignore so
// a partial query still runs rather than failing entirely.
if ( key === '$or' || key === '$not' ) continue ;
const cubeOp = this . mongoOperatorToCubeOperator ( opKey ) ;
if ( ! cubeOp ) continue ; // ← 无映射 = 整条谓词消失
mongoOperatorToCubeOperator(:475)只映射 11 个 算子:$eq $ne $gt $gte $lt $lte $in $nin $contains $notContains $exists。
spec FILTER_OPERATORS 声明 15 个,因此下面这些在 analytics 面上一律无声消失 :
被丢的
备注
$between
仪表盘日期区间最常用的写法
$startsWith / $endsWith
$null
$regex
plugin-auth 的 ObjectQL adapter 会产出
$or 整条
注释写明「暂不支持,忽略以便部分查询仍能跑」
$not 整条
CEL !expr 的 RLS scope 就是这个形状
为什么是 bug
方向是放大,不是缩小。 丢掉一条约束 = 少过滤 = 多返回行 。A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948 立的规矩正是针对这个方向:「无法编译的过滤器必须响亮拒收,不能被跳过」。{$or: […]} 被丢掉后,一个本应只统计两类记录的部件会统计全表 。
$not 落在权限路径上。 cel-to-filter.ts 把 CEL !expr 编译成 {$not: {…}},这是 RLS read scope 的常规产物。在 analytics 面上它被 continue 掉,等于 scope 没生效 —— 聚合数字里混进了调用方无权读的行。这不是「数字不准」,是越权读。
注释把它当成了 feature。 「ignore so a partial query still runs rather than failing entirely」正是 [P2] data: QueryAST declares 12 members no executor runs — the liveness ledger governs metadata types, not the request surface #4286 / ADR-0078 在 objectql 的 having 上判定为错误的那条理由 —— 那里的结论写得很直白:忽略一个算子会「silently return UNFILTERED aggregates」,所以 having 改成了抛错。
为什么至今没被测出来
memory-analytics.test.ts 没有一条用例断言「一个带 $or / $between 的 where 在 analytics 面上仍然生效」。#5324 修好的一致性表穿透(FILTER_LOGIC_CASES → InMemoryDriver.find)只覆盖 find,不覆盖 analytics 面 —— 这个包有三个过滤面,现在有两个被同一个门禁盯住了,第三个没有。
建议(不代裁决)
与 objectql 的 having 取齐:无法编译的算子/组合子 抛 INVALID_FILTER (同包 filter-refusal.ts 的 unsupportedFilterError 直接可用),而不是 continue。这条最省事也最一致。
或者把 cube 管线补齐到 FILTER_OPERATORS 全集 + $or/$not。工作量大得多,且 $or 在 cube 模型里未必表达得出来。
无论走哪条,都建议让 analytics 面也进 FILTER_LOGIC_CASES 的穿透范围。
未验证的部分
我没有量化今天有多少仪表盘部件的 where 真的带 $between / $or;也没有测 service-analytics 的对应面(它是另一套编译器,#5325 在飞)。严重度请 PM 按 triage 定,不代表我判断它低。
关联:#5324 / #5328 (同包 find 面的同一形状,已修)、#3948 (no-silent-drop)、#4286 / ADR-0078(having 的同款裁决)、#5239 (一致性表)。
做 #5324 / #5328(
normalizeFilterCondition的「该拒收没拒收」)时,顺手清点同包第三个过滤面,发现这一处不属于那两单范围的缺陷。按 Prime Directive #10 单独记在这里,unassigned。位置
packages/plugins/driver-memory/src/memory-analytics.ts的normalizeFilters→flattenFilterCondition(约 :415)。它把query.where摊平成 cube 风格的{ member, operator, values }列表。现象:两处静默
continuemongoOperatorToCubeOperator(:475)只映射 11 个算子:$eq $ne $gt $gte $lt $lte $in $nin $contains $notContains $exists。spec
FILTER_OPERATORS声明 15 个,因此下面这些在 analytics 面上一律无声消失:$between$startsWith/$endsWith$null$regex$or整条$not整条!expr的 RLS scope 就是这个形状为什么是 bug
{$or: […]}被丢掉后,一个本应只统计两类记录的部件会统计全表。$not落在权限路径上。cel-to-filter.ts把 CEL!expr编译成{$not: {…}},这是 RLS read scope 的常规产物。在 analytics 面上它被continue掉,等于 scope 没生效 —— 聚合数字里混进了调用方无权读的行。这不是「数字不准」,是越权读。QueryASTdeclares 12 members no executor runs — the liveness ledger governs metadata types, not the request surface #4286 / ADR-0078 在objectql的having上判定为错误的那条理由 —— 那里的结论写得很直白:忽略一个算子会「silently return UNFILTERED aggregates」,所以having改成了抛错。为什么至今没被测出来
memory-analytics.test.ts没有一条用例断言「一个带$or/$between的 where 在 analytics 面上仍然生效」。#5324 修好的一致性表穿透(FILTER_LOGIC_CASES→InMemoryDriver.find)只覆盖find,不覆盖 analytics 面 —— 这个包有三个过滤面,现在有两个被同一个门禁盯住了,第三个没有。建议(不代裁决)
objectql的having取齐:无法编译的算子/组合子 抛INVALID_FILTER(同包filter-refusal.ts的unsupportedFilterError直接可用),而不是continue。这条最省事也最一致。FILTER_OPERATORS全集 +$or/$not。工作量大得多,且$or在 cube 模型里未必表达得出来。FILTER_LOGIC_CASES的穿透范围。未验证的部分
我没有量化今天有多少仪表盘部件的
where真的带$between/$or;也没有测service-analytics的对应面(它是另一套编译器,#5325 在飞)。严重度请 PM 按 triage 定,不代表我判断它低。关联:#5324 / #5328(同包
find面的同一形状,已修)、#3948(no-silent-drop)、#4286 / ADR-0078(having的同款裁决)、#5239(一致性表)。