Skip to content

drivers: undefined 比较数被发射器读作 null、却被守卫/校验读作「值」—— turso local 抛裸 knex 错、remote 静默答 IS NULL(实测,origin/main) #6050

Description

@os-zhuang

出自 #5903 的实施(PR #6047)—— 给 remote 侧写 NULL-safe 极性表时,为把每一格对齐到「守卫必须匹配它自己的发射器」这条 #5298 不变量,逐格复核了 undefined 比较数,发现一族与 #5903 同类但成因不同的分叉。范围外,按 Prime Directive #10 记录,不指派。

事实(origin/main @ efedd289f,同一个 TursoDriver,共享 fixture:1-2 行 d 有值、3-4 行 d 为 NULL)

filter turso LOCAL(继承 SqlDriver) turso REMOTE(RemoteTransport)
{ 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 谓词):

case '$ne':
  if (coerced == null) (builder)[…'whereNotNull'](field);

但同文件的两张守卫表读的是严格相等:

  • 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 比较数应当:

B 案与本仓 contract-first 的方向更一致,但会改变 remote 今天已经在答的行为,且要先量一遍仓内有没有依赖 A 案的调用方 —— 请裁决方拍。

落点

关联

#5903 / PR #6047(同族,不同成因:那单是裁决没落到独立发射器,本单是守卫没对上自己的发射器)、#5298(NULL/无值语义族的裁决入口与「守卫匹配发射器」不变量的出处)、#5146、#5347 / #5369(比较数按声明拒收的先例)、#1116 / #4436(ADR-0112 信封,毛病 A 的清点来源)。

会话:session_01WyvqvKMG6asi9aXjKE6xtx

Activity

  1. claude commented on Aug 6, 2026

    @claude
    Contributor

    分诊:needs-user-decision + domain:drivers

    落点锚定(域按落点判,⛔ 不按标题词汇猜):packages/drivers/driver-sql/src/sql-driver.ts —— nullValueSatisfiesOperator(:1054)、operatorIsNullTotal(:1105)、两者的汇总处(:1142-1143)与 $ne 发射器的宽松相等(:6626 if (coerced == null) … whereNotNull);另一半在 packages/drivers/driver-turso/src/remote-transport.ts。两个包都在 domain:drivers 域内。

    过时前提检查(origin/main 9e3709a,立单时记的是 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 座位的两条硬约束(裁决落地后适用)

    查重:三仓 open issue/PR 搜过 undefined 比较数 / $ne / driver 无值语义族 —— #5903(同族,成因不同,正文已互链)、#5299 / #5905 / #5346(无值语义家族的其它面)、#5907(信封面)均非重复;本问题在三仓无第二个派发入口。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-zhuang commented on Aug 7, 2026

    @os-zhuang
    ContributorAuthor

    裁决落档(2026-08-07,维护者在 PM 会话批复「按建议继续」;否决窗口:评论/重开可推翻):B 案 —— undefined 比较数一律响亮拒收(INVALID_FILTER / 400,#5347-A 同族信封)。

    理由(三透镜,rc.4 语境):

    实施要点:闸落在比较数进入任何发射器/守卫之前,两个毛病(A:三格裸 knex 错无信封;B:driver-sql 守卫与自家发射器分裂)同闸消灭;A 案行为(remote 现行 IS NULL)按裁决废止,先量仓内调用方再落地(issue 自provision 的要求);driver-memory/mongodb 冻结面照 #5499 只 pin 不改。

    赶 rc.4:即刻派修。


    Generated by Claude Code

  3. self-assigned this
    on Aug 7, 2026
  4. os-zhuang commented on Aug 7, 2026

    @os-zhuang
    ContributorAuthor

    认领: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

  5. os-zhuang commented on Aug 7, 2026

    @os-zhuang
    ContributorAuthor

    复核:ACCEPT → PR #6142(PM 实测复核):

    rc.4 判定:纳入。行为变更(静默/崩溃 → 400)在 rc 阶段落地正当,零生产依赖已举证,升级说明已写。CI 清零后转 ready 入队。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions