出自 #6125 的实施(PR 见下)。#6125 的裁决只派了 read-scope-sql 一格;实施中在同一个包的另一扇门上实测出同一形状的两种新读法,按 Prime Directive #10 另立单、不指派、不扩大那张 PR 的 diff。
⚠️ 这两格不在 #6125 正文的五面表里。 那张表把 service-analytics 记作一行(read-scope-sql),但本包有两扇独立的门:调用方自己写的 where 走 strategies/filter-normalizer.ts(INVALID_FILTER / 400,#5352),平台编译出来的 read scope 走 read-scope-sql.ts(READ_SCOPE_COMPILE_FAILED / 500)。#6125 只实测了后者。
实测(origin/main @ d8e8d9cbc,直接调 normalizeAnalyticsFilterTree({ where }))
| where |
归一化结果 |
读法 |
{d: undefined} |
null |
整条 where 被丢弃 → 查询无过滤器运行 |
{stage:'won', d: undefined} |
只剩 stage equals 'won' |
d 这一合取项静默消失 |
{$not: {d: undefined}} |
NOT (d set),即 d IS NULL |
作者没写过的谓词 |
{d: {$eq: undefined}} |
d equals [null] |
值比较(零行),不是 $eq: null 的 notSet |
{d: {$gt: undefined}} |
d gt [null] |
同上 |
{d: {$in: [undefined]}} |
d in [null] |
同上 |
{d: {$ne: undefined}} |
d notSet OR d notEquals [null] |
同上 |
对照组(null 比较数)正常:{d: null} / {$eq: null} → notSet,{$ne: null} → set。
为什么这是缺陷而不是「可接受的读法」
前两行的方向是加宽,不是 fail-closed:
#6050 于 2026-08-07 裁 B 案:比较数位置的 undefined 一律拒收。实施面按裁决只覆盖 driver-sql / driver-turso;#6125 的裁决又把 read-scope-sql 一格推平(PR 见下)。这扇 where 门从未被任何一次裁决点名,它今天的处置(丢键 + 归一成 null)与 B 案相反。
要不要把 B 案推到这扇门上,是本单要问的;信封现成(INVALID_FILTER / 400,与 #6050 同码同状态,因为这扇门收的确实是调用方输入)。⚠️ 但前四行里至少「$eq: undefined vs $eq: null 含义不同」这一条与 #5526 / #5332 的既有决定相邻,改动前请确认不会顺手改掉那两次裁决的结论。
触达性(判严重度需要的那一半)
undefined 过不了 JSON,所以 REST 的 /analytics/dataset/query 请求体带不进来 —— 这一格只能从进程内调 AnalyticsService.query({ where }) 的代码来。要找的形状与 #6050 完全同款:
await analytics.query({ where: { owner_id: ctx.user?.id } }); // id 缺失 → 整条 where 消失
本单未证实今天有生产调用方这样拼 where(未逐个审 dashboard / report 编译路径)。如实记的是「未证实触达」,不是「不会发生」。⚠️ 但请注意方向差异:#6125 那一格是 fail-closed(匹配零行),这一格是加宽(前两行),与 #6050 判 P0 的那格同向。严重度请分诊自己判 —— #5347 的先例是立单时的定级两个方向都不可靠。
关联
会话:session_01WyvqvKMG6asi9aXjKE6xtx(#6125 实施过程中发现,未指派)
出自 #6125 的实施(PR 见下)。#6125 的裁决只派了
read-scope-sql一格;实施中在同一个包的另一扇门上实测出同一形状的两种新读法,按 Prime Directive #10 另立单、不指派、不扩大那张 PR 的 diff。service-analytics记作一行(read-scope-sql),但本包有两扇独立的门:调用方自己写的where走strategies/filter-normalizer.ts(INVALID_FILTER/ 400,#5352),平台编译出来的 read scope 走read-scope-sql.ts(READ_SCOPE_COMPILE_FAILED/ 500)。#6125 只实测了后者。实测(
origin/main@d8e8d9cbc,直接调normalizeAnalyticsFilterTree({ where })){d: undefined}null{stage:'won', d: undefined}stage equals 'won'd这一合取项静默消失{$not: {d: undefined}}NOT (d set),即d IS NULL{d: {$eq: undefined}}d equals [null]$eq: null的notSet{d: {$gt: undefined}}d gt [null]{d: {$in: [undefined]}}d in [null]{d: {$ne: undefined}}d notSet OR d notEquals [null]对照组(
null比较数)正常:{d: null}/{$eq: null}→notSet,{$ne: null}→set。为什么这是缺陷而不是「可接受的读法」
前两行的方向是加宽,不是 fail-closed:
buildNode第一行就是if (raw === undefined) continue;—— 键被丢掉,不留任何痕迹。同一个函数往下几十行的注释自己写着(针对未映射算子):
丢一个
undefined值的键,就是这段注释禁止的那件事,发生在它自己文件的入口。第三行(
{$not: {d: undefined}}→d IS NULL)更绕:$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 的空值安全改写先把叶子拆成{d:{$null:false}} AND {d: undefined},buildNode丢掉后一半,只剩前一半被取反 —— 于是从一个被丢弃的叶子里凭空长出一条谓词。第四行起是另一回事:
comparand()把undefined归一成null(analytics 的string[]值往返对字符串比较数也有损:{code: {$eq: '007'}}绑成数字7、'null'绑成真 NULL、'true'绑成1—— 文本列静默取到错行 #5526 / 分析查询的{field: {$eq: null}}/{$ne: null}编译成col = ''/col != '',与同文件里{field: null}的IS NULL自相矛盾 #5332 的既有决定),但判「这是空值谓词还是值比较」的分支用的是严格wrapper[opKey] === null,在归一之前。所以$eq: undefined与$eq: null在这扇门上含义不同,而 分析查询的{field: {$eq: null}}/{$ne: null}编译成col = ''/col != '',与同文件里{field: null}的IS NULL自相矛盾 #5332 的注释明确说这两者在契约里是同一条谓词。与 #6050 裁决的关系
#6050 于 2026-08-07 裁 B 案:比较数位置的
undefined一律拒收。实施面按裁决只覆盖driver-sql/driver-turso;#6125 的裁决又把read-scope-sql一格推平(PR 见下)。这扇where门从未被任何一次裁决点名,它今天的处置(丢键 + 归一成 null)与 B 案相反。要不要把 B 案推到这扇门上,是本单要问的;信封现成(⚠️ 但前四行里至少「
INVALID_FILTER/ 400,与 #6050 同码同状态,因为这扇门收的确实是调用方输入)。$eq: undefinedvs$eq: null含义不同」这一条与 #5526 / #5332 的既有决定相邻,改动前请确认不会顺手改掉那两次裁决的结论。触达性(判严重度需要的那一半)
undefined过不了 JSON,所以 REST 的/analytics/dataset/query请求体带不进来 —— 这一格只能从进程内调AnalyticsService.query({ where })的代码来。要找的形状与 #6050 完全同款:本单未证实今天有生产调用方这样拼⚠️ 但请注意方向差异:#6125 那一格是 fail-closed(匹配零行),这一格是加宽(前两行),与 #6050 判 P0 的那格同向。严重度请分诊自己判 —— #5347 的先例是立单时的定级两个方向都不可靠。
where(未逐个审 dashboard / report 编译路径)。如实记的是「未证实触达」,不是「不会发生」。关联
undefined比较数在仓内有五种读法 —— #6050 只在 driver-sql/turso 落了拒收,formula 读作「键缺失」、read-scope-sql 编成= NULL、driver-memory 读作 null #6125(本单的出处;其裁决只覆盖read-scope-sql)undefined比较数被发射器读作 null、却被守卫/校验读作「值」—— turso local 抛裸 knex 错、remote 静默答 IS NULL(实测,origin/main) #6050(比较数undefined一律拒收的 B 案裁决 + driver-sql/turso 实施)$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299(formula的「键缺失 vs 值为 null」,同族未决)string[]值往返对字符串比较数也有损:{code: {$eq: '007'}}绑成数字7、'null'绑成真 NULL、'true'绑成1—— 文本列静默取到错行 #5526 / 分析查询的{field: {$eq: null}}/{$ne: null}编译成col = ''/col != '',与同文件里{field: null}的IS NULL自相矛盾 #5332(comparand()归一与$eq: null的空值谓词分支,改动前必读)会话:
session_01WyvqvKMG6asi9aXjKE6xtx(#6125 实施过程中发现,未指派)