Repository navigation
Description
PR #6004(#5977 / #5298 第二批)实施途中的范围外观察,按 Prime Directive #10 记录,不指派。观察类:今天没有任何用户会撞到它,谓词逐行等价,执行计划不变 —— 故挂 finding、不挂 pm:queue,严重度交分诊轮判。
事实
packages/services/service-analytics/src/strategies/filter-normalizer.ts
#5146 为 $not 引入了 nullSafeNegationOperand → guardFieldEntry → nullGuardForFieldSpec,后者对一个字段 spec 给三种判定:none / requireValue / allowNull。
PR #6004 之后,fieldLeaves 在叶子层自己发守卫,条件恰好就是 nullValueSatisfiesOperator(op, v) && !operatorIsNullTotal(op, v) —— 也就是 nullGuardForFieldSpec 的 allowNull 判定本身。于是 allowNull 这一支变成冗余的第二层:
{$not: {stage: {$ne: 'won'}}}
→ NOT ((stage IS NULL OR (stage IS NULL OR stage != $1)))
^^^^^^^^^^^^^^^ guardFieldEntry 的 allowNull 支
^^^^^^^^^^^^^^^ fieldLeaves 的叶子守卫
X OR (X OR Y) ≡ X OR Y,所以行为完全等价(PR #6004 实测钉住:$not of $ne / $nin 仍答 ['1'],极性未被拓宽)。#5146 的 header 本来就把「引擎路径守卫两次、幂等」写成已接受的代价,这条只是把同一笔代价又叠了一层。
requireValue 支不受影响,仍然必需(正向比较、以及多算子 spec 里只要有一个算子不接受 NULL 的情况)。
为什么可能值得清
allowNull 支能到达的算子,恰好只有 $ne(非 null 比较数)/ $nin(非空)/ $notContains —— 其余要么 total($null / $exists / $eq: null / $ne: null / 空 $nin)走 none,要么正向走 requireValue。也就是说这三个算子现在每一个都自带守卫,allowNull 支再没有独占的落点。
去掉它(改为 none)在多算子 spec 上也成立,靠分配律:
- 现在:
(c IS NULL OR (X AND Y)) - 去掉后:
(c IS NULL OR X) AND (c IS NULL OR Y)
两者逐行等价。
为什么 PR #6004 没有顺手做
- 它改的是
$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 的守卫代码,不在 [#5298 第二批] filter-normalizer(Cube 面)NULL-safe 对齐:先实测既有 NULL 行为,分叉则改写;N1 入表按后落地者规则 #5977 的验收范围内(「scope = the issue」); - 删肢需要按 fixture 三分诊重新逐条判
filter-normalizer-not-null-safe.test.ts里$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 的钉子 —— 其中至少一条($notof$ne的生成 SQL 断言)会跟着变形,值得单独一轮而不是搭车; - 收益是可读性 / SQL 噪音,不是正确性。
关联
- PR fix(analytics): $ne / $nin / $notContains 在 Cube 面保留无值行 (#5298 第二批) (#5977) #6004([#5298 第二批] filter-normalizer(Cube 面)NULL-safe 对齐:先实测既有 NULL 行为,分叉则改写;N1 入表按后落地者规则 #5977):引入叶子层守卫的那次改动,本观察的出处;
$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 / PR fix(driver-sql):$not取反前先把操作数编译成全域谓词,NULL 行不再被静默排除 (#5146) #5296:$notNULL-safe 与allowNull支的来源;- 非否定路径上的
$ne/$nin/$notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了$not) #5298:非否定路径的同族裁决; - [finding] 仓内存在 5 个独立的过滤器→谓词编译器,每次语义裁决成本 ×5 —— 值得立「谓词编译收敛」调查程序(#5298 成本清单副产品) #5930:仓内 5 个独立谓词编译器的收敛调查 —— 若那条程序起了,这条应并进去一起判,而不是单独动手。
Activity
claude commentedon Aug 6, 2026
发现分诊轮(#4949 纪律):持有(留
finding),域维持domain:services。9e3709a):出处 PR fix(analytics): $ne / $nin / $notContains 在 Cube 面保留无值行 (#5298 第二批) (#5977) #6004([#5298 第二批] filter-normalizer(Cube 面)NULL-safe 对齐:先实测既有 NULL 行为,分叉则改写;N1 入表按后落地者规则 #5977 / 非否定路径上的$ne/$nin/$notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了$not) #5298 第二批)已 MERGED ⇒ 叶子层守卫已在 main 上,allowNull支的冗余是现状(不是对在飞 PR 的预测)。前提成立。X OR (X OR Y)≡X OR Y;多算子 spec 上靠分配律(c IS NULL OR (X AND Y))≡(c IS NULL OR X) AND (c IS NULL OR Y)),且 PR fix(analytics): $ne / $nin / $notContains 在 Cube 面保留无值行 (#5298 第二批) (#5977) #6004 已实测钉住极性未被拓宽。而成本不为零:删肢要按 fixture 三分诊重新逐条判filter-normalizer-not-null-safe.test.ts里$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 的钉子,其中至少一条($notof$ne的生成 SQL 断言)会跟着变形。零正确性收益 + 需要动别人的钉子,这个比例不该占派发位。$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 header 本就把「引擎路径守卫两次、幂等」写成已接受的代价,本条是同一笔代价又叠一层),而且requireValue支仍然必需这一点需要留档 —— 关掉后下一个人删肢时很可能连requireValue一起判错。guardFieldEntry/nullGuardForFieldSpec⇒ 在那个 PR 里顺带清掉(那时 fixture 本来就要重判,边际成本≈0)。$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 / PR fix(driver-sql):$not取反前先把操作数编译成全域谓词,NULL 行不再被静默排除 (#5146) #5296(allowNull支来源)、非否定路径上的$ne/$nin/$notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了$not) #5298(非否定路径同族裁决)、[finding] 仓内存在 5 个独立的过滤器→谓词编译器,每次语义裁决成本 ×5 —— 值得立「谓词编译收敛」调查程序(#5298 成本清单副产品) #5930(收敛调查)为关联。同车道本轮另有 finding(service-automation): engine.ts 的 resumeInternal 把驱动错误插进 message —— #5737 修完后,这条 resume 路径上仅剩的一处外来 cause 拼接 #5912 / [finding]isMissingSourceError仍把 Postgres 的「缺列」措辞column "c" of relation "t" does not exist判为「缺源」——文档明说不该,今天靠读路径不产生该措辞而无害 #6035 晋级,与本条文件不相交。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
claude commentedon Aug 7, 2026
Findings triage round (#4949 discipline): HOLD maintained (
findingkept), domain staysdomain:services. Stale-premise check @origin/main80f7dc6— no new information since the 08-06 17:01Z grading, stated explicitly so the next round can skip re-measuring it.packages/services/service-analytics/src/strategies/filter-normalizer.tssince 2026-08-06T13:00Z returns exactly one commit:de70b421([#5298 第二批] filter-normalizer(Cube 面)NULL-safe 对齐:先实测既有 NULL 行为,分叉则改写;N1 入表按后落地者规则 #5977 / PR fix(analytics): $ne / $nin / $notContains 在 Cube 面保留无值行 (#5298 第二批) (#5977) #6004, 14:37:13Z) — i.e. this issue's own source PR, which predates the grading (17:01Z) and is already fully accounted for in it. Nothing has touched the file since.main:NullGuard = 'none' | 'requireValue' | 'allowNull'(:712),nullGuardForFieldSpec(:803, returningallowNullat:822), andguardFieldEntry's consumption at:851. TheallowNullbranch is therefore still the redundant second layer the body proves equivalent (X OR (X OR Y)≡X OR Y; multi-operator specs by distribution).git log -- <path>reports the graft boundary as if it created every file — these readings are from the REST commits API againstmain, not from the local history (Operational notes 6, fourth bullet).Verdict: HOLD — restart conditions unchanged
guardFieldEntry/nullGuardForFieldSpecfor another reason ⇒ clean the limb inside that PR, where the fixture re-triage the body describes is being paid for anyway (marginal cost ≈ 0).Still zero correctness benefit and non-zero cost (the
$notof$negenerated-SQL pin infilter-normalizer-not-null-safe.test.tsdeforms with the change), which is why it does not take a dispatch slot; and still not closable, because therequireValuebranch remains necessary and that distinction is exactly what the next person deleting the limb would get wrong.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-project-manager commentedon Aug 8, 2026
Findings sweep (maintainer-authorized one-off, 2026-08-07 — registered on #6015): closing, not planned. Predicate-equivalent redundant guard layer — one extra paren pair in SQL, zero behavioral difference by the body's own measurement. The cleanup rides the next
filter-normalizertouch (noted for the services lane). Veto window applies.Generated by Claude Code