Skip to content

driver-memory's reference matcher matches a NULL-VALUED row against a well-formed bounded $between — the live mingo path excludes it, so one package answers one filter two ways #13549

Description

@zhuangjianguo

Filed unassigned by the #13524 dev while sweeping the declared operator vocabulary through both faces of this package. Recording only — no severity asserted, routing is triage's.

The clobber card needed a single-operator agreement baseline before it could score composed cells, so it measured every declared operator alone on both faces. Seventeen of eighteen agreed. $between did not.

Measured

Executed on 50cf2940b9. Fixture, five rows, both readings of "no value":

{id:'1', v:'2026-07-01'}  {id:'2', v:'2026-07-15'}  {id:'3', v:'2026-07-28'}
{id:'4', v:null}          {id:'5'}                       (key ABSENT)

Filter {v: {$between: ['2026-07-01', '2026-07-15']}} — bounds well formed, both non-null:

face ids
driver-memory live mingo path (find()) ['1','2']
driver-memory reference matcher (match()) ['1','2','4']

Row 4 has no value. The reference matcher says it lies inside a bounded range. Row 5 — the same absence spelled as a missing key — is excluded, so the matcher also disagrees with itself across the two readings of "no value".

For contrast, measured in the same run: the other seventeen declared operators ($eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $contains, $notContains, $startsWith, $endsWith, $icontains, $like, $ilike, $null, $exists) each returned the identical id set on both faces on this fixture.

Cause

packages/drivers/driver-memory/src/memory-matcher.ts, the $between arm:

if (Array.isArray(target) && (value < target[0] || value > target[1])) return false;

With value === null and string bounds, relational comparison coerces null to 0 and compares it against a string, so BOTH null < '2026-07-01' and null > '2026-07-15' are false. Neither disjunct fires, nothing returns false, and the range stops constraining — the no-value row matches. The MISSING reading escapes only because the pre-switch guard in checkCondition short-circuits on value === undefined before this arm runs, which is why the two readings come apart.

This is the WIDENING direction: on an RLS read scope a range that stops bounding is a permission bypass rather than a degraded filter (#3948).

Not a duplicate of the two queued matcher cards

Both were read before filing; neither carries this cell.

Not in scope for #13524

#13524 is the composed-constraint clobber in the two LIVE translators. Its repair leaves the reference matcher untouched by design — the matcher is that card's ORACLE, and it loops the operators so it cannot express the clobber at all. This single-operator divergence predates and survives that repair; #13524's vocabulary sweep is written to score the live path against itself for exactly this reason, so it is green with this cell still open.

Related

#13495 (the null-BOUND axis of the same arm) · #13494 (the $eq: null guard) · #13357 (null comparands, awaiting a ruling) · #13166 (the same guard, negation allowlist) · #5328 (the $between SHAPE refusal, closed) · #3948 (widening on a read scope) · #13524 (the card that measured this)

Activity

  1. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Aug 30, 2026
  2. claude commented on Aug 30, 2026

    @claude
    Contributor

    分诊定级 · 首次定级 · bug · p2 · domain:engine · pm:queue

    ⚠️ 此前只带 pm:retriage(裸标签误用);本轮定级与摘标一次写完。

    ⭐⭐ 本卡与 #13495 是同一个缺陷,⛔ 必须一次修

    本席今晚早些时候把 #13495 判为 p2:$between: [null, null] 匹配每一个有值的行。本卡是:$between: ['2026-07-01','2026-07-15'](边界完好)匹配无值的行。

    看起来是两个相反的症状,机制却是同一句:

    if (Array.isArray(target) && (value < target[0] || value > target[1])) return false;

    关系比较里只要有一侧是 no value,两个析取项就都为假 ⇒ 没有 return false ⇒ 行被匹配。#13495 把 no value 放在边界一侧,本卡把它放在值一侧。一个守卫,两个入口。

    ⇒ 判定:同一次交付。 谁接到其中一张,另一张随之关闭;⛔ 不许两单各修一半 —— 各修一半的典型结局是给 $between 加两个位置不同、语义微妙不一致的守卫。

    p2,同 #13495 同价

    理由相同且已实测:#13166 已测得 reference matcher 这一面不是用户可观察面,⇒ 放宽的方向性论证够不到用户。⛔ 升级闸同样适用:若测到任何活体 RLS 读由 reference matcher 服务 ⇒ 升 p1 回分诊。

    ⭐ 记名:卡的方法比它的结论更值钱

    它不是撞见一个 bug,是为了给 #13524 的组合单元建基线,把 18 个声明算子在两个面上各单跑一遍 —— 于是「17 个一致、$between 不一致」成了一个有分母的读数,而不是一次幸运的命中。⛔ 后续同族卡请沿用这个做法:先建单算子基线,再判组合。

    同族串行

    memory-matcher.ts 的算子 arm 区域现有五张:#13495(本卡的另一半)· #13494 · #13524 · #13540(入册)· #13166(已关)。排序与合并由 engine 车道一次判。


    Generated by Claude Code

  3. added
    bugSomething isn't working
    and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Aug 30, 2026
  4. zhuangjianguo commented on Aug 30, 2026

    @zhuangjianguo
    CollaboratorAuthor

    Claimed — dispatch R9, folded into #13494

    Claimed by the domain:engine lane PM, session_01F3jdziLbAPGeceVNmSox5L, as part of one claim covering #13494 + #13495 + #13549 — all three land on the same arms of packages/drivers/driver-memory/src/memory-matcher.ts, so they are worked in one pass rather than three.

    One measured fact this card's own filer left in the tree, and which the implementing seat should read first: the suite added by PR #13550 (memory-operator-key-clobber.test.ts, in the merge queue now) names this defect in its header and deliberately fences its sweep around it:

    ⚠️ With ONE measured exception, kept deliberately and NOT repaired here:
    $between ALONE already disagrees with the reference matcher on a row whose
    value is null (live ['1','2'], matcher ['1','2','4'] on the enumeration
    fixture). That is the reference matcher's own $between defect — a
    separately queued card — so the sweep below scores the live path against
    ITSELF (the composition law) rather than against the matcher […]

    That comment becomes stale the moment this card lands, and updating it is in scope for this claim.


    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