Repository navigation
drivers: undefined 比较数被发射器读作 null、却被守卫/校验读作「值」—— turso local 抛裸 knex 错、remote 静默答 IS NULL(实测,origin/main) #6050
Description
Activity
分诊:
needs-user-decision+domain:drivers落点锚定(域按落点判,⛔ 不按标题词汇猜):
packages/drivers/driver-sql/src/sql-driver.ts——nullValueSatisfiesOperator(:1054)、operatorIsNullTotal(:1105)、两者的汇总处(:1142-1143)与$ne发射器的宽松相等(:6626if (coerced == null) … whereNotNull);另一半在packages/drivers/driver-turso/src/remote-transport.ts。两个包都在domain:drivers域内。过时前提检查(
origin/main9e3709a,立单时记的是efedd289f):上述四处逐一命中且形态未变 —— 期间落在该路径上的#5962(#5298 第二批)与#6006(remote canonical 回填)都没有触及这两张守卫表的相等语义 ⇒ 立单前提成立。常设裁决核对(#5499 投入冻结):冻结面只覆盖
driver-memory/driver-mongodb两族;本单落driver-sql/driver-turso,不在冻结面内 ⇒ 照常分诊,未挂pm:on-hold。为什么进决策箱而不是入队:两案不是实现细节之争 —— B 案(按
FieldOperatorsSchema未声明即拒收)会改变 remote 今天已经在答的行为,并且是在公开 filter 契约上再收一次口;A 案(undefined ≡ null)同样是一次公开语义确认。作者已自陈「请裁决方拍」,PM 不代拍。⚠️ 正文里「['3','4']才是该驱动自身发射器推出的正确答案」是分析,裁决时请当作待核而非既定事实。给 drivers 座位的两条硬约束(裁决落地后适用)
- 串行:在飞 PR fix(driver-turso): remote 模式对齐 NULL-safe 语义 —— $not/$ne/$nin/$notContains 四算子 + $exists 拒收闸 (#5903) #6047(drivers(turso): remote 模式没有继承 #5146 的
$notNULL-safe —— 同一个 TursoDriver,local 返回 NULL 行、remote 不返回(实测,origin/main) #5903)正在改remote-transport.ts的同名两函数 ⇒ 本单实施必须排在其落地之后,开工前重拉origin/main。 - 合并面:毛病 A(裸 knex
Undefined binding(s)无 ADR-0112 信封)与已入队的 drivers(sql,turso):Unsupported aggregate function两面都是裸Error(code/status 皆 undefined),且三个 spec 已声明的聚合函数无任何 SQL 后端编译 #5907(Unsupported aggregate function两面裸Error)落在同一处mapDataError默认分支面 —— 同车道同域,实施时请判一次「合并做还是严格串行」,⛔ 不要两单各修一半留下第三种形态。
查重:三仓 open issue/PR 搜过
undefined比较数 /$ne/ driver 无值语义族 —— #5903(同族,成因不同,正文已互链)、#5299 / #5905 / #5346(无值语义家族的其它面)、#5907(信封面)均非重复;本问题在三仓无第二个派发入口。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- 串行:在飞 PR fix(driver-turso): remote 模式对齐 NULL-safe 语义 —— $not/$ne/$nin/$notContains 四算子 + $exists 拒收闸 (#5903) #6047(drivers(turso): remote 模式没有继承 #5146 的
裁决落档(2026-08-07,维护者在 PM 会话批复「按建议继续」;否决窗口:评论/重开可推翻):B 案 ——
undefined比较数一律响亮拒收(INVALID_FILTER/ 400,#5347-A 同族信封)。理由(三透镜,rc.4 语境):
- 契约:spec 的
FieldOperatorsSchema未声明undefined比较数——declared = enforced,与$null的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347/driver-sql 把**文档级**未声明$op({$where:…}、{$nor:[…]})当成列名编译,静默返回空结果集 —— #5324 的文档级一半在 SQL 侧还在 #5348/drivers(turso): RemoteTransport 条件层的$-算子键被当列名编译成静默空集 —— SqlDriver 已按 #5348 拒收,remote 是唯一剩余面(cloud#1077 移交) #5769 的拒收先例同向;{field: undefined}与「没有这个键」在 JS 不可区分而语义相反,任何「猜含义」的编译都是赌; - 业务/安全:
{ owner_id: ctx.user?.id }在 id 缺失时 remote 静默匹配全环境行——拒收把一个静默越权读变成一个响亮 400; - AI 防错:
undefined只能来自进程内代码(过不了 JSON),即作者侧 bug 的直接指纹——正确答案是把 bug 指给作者,不是替它圆场。
实施要点:闸落在比较数进入任何发射器/守卫之前,两个毛病(A:三格裸 knex 错无信封;B:driver-sql 守卫与自家发射器分裂)同闸消灭;A 案行为(remote 现行 IS NULL)按裁决废止,先量仓内调用方再落地(issue 自provision 的要求);driver-memory/mongodb 冻结面照 #5499 只 pin 不改。
赶 rc.4:即刻派修。
Generated by Claude Code
- 契约:spec 的
- addedbugSomething isn't workingSomething isn't workingand removed
on Aug 7, 2026 认领:PM 循环第 10 轮(drivers 车道,rc.4 冲刺件)
会话:session_01WyvqvKMG6asi9aXjKE6xtx
分支:claude/issue-6050-undefined-comparand-refusal
Worktree:objectstack-issue-6050
域:domain:drivers
文件面:packages/drivers/driver-sql/src/sql-driver.ts(比较数闸 + 两张守卫表 $eq/$ne 臂注释)+packages/drivers/driver-turso/src/remote-transport.ts(同款闸)+ turso parity 套件补 undefined 格 + 实测后视情 read-scope-sql / formula(冻结面 memory/mongodb ⛔ 只 pin 不改)+ changeset
定向在飞检查:本车道零在飞;全仓在飞面(昨日扫描 + 队列 2 单)无 driver-sql/turso 相交。时限:今天赶 rc.4 —— 若调用方清点发现依赖 A 案行为的生产面,停手报告,不强推。
Generated by Claude Code
复核:ACCEPT → PR #6142(PM 实测复核):
- 变更面 6 文件与申报一致;冻结面(driver-memory / mongodb)grep 零触碰;
null语义未动(whereNotNull分支仅把宽松== null收严为=== null,对null输入行为逐字不变,三个文件各带对照块钉住); - 闸位正确且比派发词更完整:除
reduceFilterKey校验走查外,dev 实测发现{d: undefined}进不了走查(typeof undefined !== 'object'构不成 hasMongoOperators)——而这恰是本 bug 最常见拼写,故在applyFilters平铺分支补第二个调用点、两处共用一个函数({ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240 同形状两判的账已付过一次,处置对);turso 侧前置整树走查的理由(逐键编译会先把$not操作数交给守卫)成立; - 前提复核超额:五格矩阵复现之外自量四格($in/$nin/$gt/$or),覆盖面更硬;
- 调用方清点达到停手判据的举证标准:静态 grep + 全仓 build 71/71 + 全仓 test 134/135(唯一失败是 dogfood 满载 beforeAll 超时,单独重跑 5/5 绿,与本改动无关)→ 零依赖旧行为,不触发停手条件;
- 反向验证的分辨力值得单独记:预判「红的机制应有两种」并实测证实(driver-sql 22 红中多数红在裸抛、其余红在「答了」;turso 20 红全部红在「答了」)——由此得出的那条结论应进 SKILL:只断言「它抛了」的测试会在本 issue 所针对的驱动上保持绿色,所以每例必须断言
code+status; - parity 套件补上 fix(driver-turso): remote 模式对齐 NULL-safe 语义 —— $not/$ne/$nin/$notContains 四算子 + $exists 拒收闸 (#5903) #6047 刻意留空的 undefined 列(13 位置 × 2 transport 同码同句);
- 其余三面(formula / read-scope-sql / driver-memory)实测记录后按纪律留 [finding]
undefined比较数在仓内有五种读法 —— #6050 只在 driver-sql/turso 落了拒收,formula 读作「键缺失」、read-scope-sql 编成= NULL、driver-memory 读作 null #6125,不扩 diff 赌时限——判断正确(formula 那格会顺手替 driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 拍板,不能夹带); - 门禁:两包 typecheck + 906/788 测试 + nul-bytes 全绿;changeset 双 patch 带作者侧修法说明。
rc.4 判定:纳入。行为变更(静默/崩溃 → 400)在 rc 阶段落地正当,零生产依赖已举证,升级说明已写。CI 清零后转 ready 入队。
Generated by Claude Code
- 变更面 6 文件与申报一致;冻结面(driver-memory / mongodb)grep 零触碰;
- added a commit that references this issue
on Aug 17, 2026 - added 4 commits that reference this issue
on Oct 7, 2026
出自 #5903 的实施(PR #6047)—— 给 remote 侧写 NULL-safe 极性表时,为把每一格对齐到「守卫必须匹配它自己的发射器」这条 #5298 不变量,逐格复核了
undefined比较数,发现一族与 #5903 同类但成因不同的分叉。范围外,按 Prime Directive #10 记录,不指派。事实(
origin/main@efedd289f,同一个TursoDriver,共享 fixture:1-2 行d有值、3-4 行d为 NULL){ d: undefined }Undefined binding(s) detected when compiling SELECT(knex 裸 Error)['3','4']{ d: { $eq: undefined } }['3','4']{ $not: { d: undefined } }Undefined binding(s) … compiling WHERE['1','2']{ d: { $ne: undefined } }['1','2']✅['1','2']✅{ $not: { d: { $ne: undefined } } }[]['3','4']两个可分开的毛病:
A. 抛出的那三格没有 ADR-0112 信封
LOCAL 抛的是 knex 的
Undefined binding(s) …,code与status都是undefined—— 正是 #1116 / #4436 为这条通路装信封时清点过的形态,只是这一格漏在名单外。mapDataError落默认分支,客户端拿到一个没有code的 500,而它其实是一条 caller 写坏的 filter。B.
$ne: undefined一格,driver-sql 的极性表与它自己的发射器互相矛盾sql-driver.ts的发射器读coerced == null(宽松相等,所以undefined走whereNotNull,编译成IS NOT NULL—— 一条 TOTAL 谓词):但同文件的两张守卫表读的是严格相等:
operatorIsNullTotal('$ne', undefined)→undefined === null→false(判它「不 total」,与发射器矛盾)nullValueSatisfiesOperator('$ne', undefined)→undefined !== null→true(判「NULL 行满足它」,与IS NOT NULL相反)两处一起,
nullGuardForFieldSpec得出allowNull,于是$not的操作数被改写成d IS NULL OR d IS NOT NULL—— 恒真 —— 取反后恒假,答[]。remote 侧(PR #6047 的表,两格都按== null语义写,与它自己的发射器一致)答['3','4']。这正是 #5298 立的那条不变量:「每张极性表钉的是它自己发射器的拼写,不变量是守卫与发射器一致,不是三份源码字面相同」。driver-sql 这一格恰好没有对上自己。⚠️ 因此
['3','4']是按该驱动自身发射器推出的正确答案,[]是错的 —— 但这句话是本单的分析,不是裁决。为什么值得修(不是纯理论)
undefined过不了 JSON 往返,所以只会从进程内代码来 —— 而那正是它最危险的地方:{ owner_id: ctx.user?.id }里 id 缺失时,remote 会把整条谓词静默变成owner_id IS NULL,匹配所有环境级(env-wide)行,而不是响亮地失败。同一条规则在 local 上抛错、在 remote 上放行,是 #5903 刚关掉的那个「按连接模式给两个答案」家族的又一格,只是这次触发键是比较数的形态而不是算子。需要一次裁决(所以本单不自带修法)
undefined比较数应当:null同义,四个发射面统一编译成空值谓词(remote 现行为),守卫表相应读== null;INVALID_FILTER/ 400,同$null的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347-A 的信封),理由是 spec 的FieldOperatorsSchema没有声明undefined这个比较数,「声明即强制」;{ field: undefined }与「没有这个键」在 JS 里不可区分,而两者语义相反(前者是谓词,后者是无约束)。B 案与本仓 contract-first 的方向更一致,但会改变 remote 今天已经在答的行为,且要先量一遍仓内有没有依赖 A 案的调用方 —— 请裁决方拍。
落点
packages/drivers/driver-sql/src/sql-driver.ts:operatorIsNullTotal/nullValueSatisfiesOperator的$eq/$ne两臂(严格 vs 宽松相等),以及$eq: undefined交给 knex 前缺一道闸;packages/drivers/driver-turso/src/remote-transport.ts:同名两函数已按== null写,若裁 B 案需一并改;read-scope-sql.ts、formula/matches-filter.ts、driver-memory、driver-mongodb)未逐格实测,裁决后需按 非否定路径上的$ne/$nin/$notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了$not) #5298 的清点方式补测。关联
#5903 / PR #6047(同族,不同成因:那单是裁决没落到独立发射器,本单是守卫没对上自己的发射器)、#5298(NULL/无值语义族的裁决入口与「守卫匹配发射器」不变量的出处)、#5146、#5347 / #5369(比较数按声明拒收的先例)、#1116 / #4436(ADR-0112 信封,毛病 A 的清点来源)。
会话:
session_01WyvqvKMG6asi9aXjKE6xtx