Skip to content

driver-memory's reference matcher still answers $notContains / $nin the pre-ruling way on a no-value row — the #5499 freeze that excused it dissolved 2026-08-11, so the divergence is now unexcused and untracked #13166

Description

@os-litant

Split out of #13089 per its triage instruction ("若测出 HAVING 分歧在解冻后仍真实存在 ⇒ 停下,把它立成 domain:engine 的独立卡"). #13089 remains open and is not addressed by this card; that card's PR delivers only its sites 1-2 (the two pm-dispatch reference files).

The measurement

Run on branch claude/issue-13089-freeze-residue at merge-base 56470d86b, by execution (not by reading), against packages/drivers/driver-memory/src/memory-matcher.ts match(). Fixture: rows 1 (name: 'alpha-one'), 2 (name: 'beta'), 3 (no value) — measured under BOTH readings of "no value", name: null and the key absent. The platform-settled answer (#5146 extended by #5298 option A, re-affirmed after the #5299 reversal was costed and withdrawn) is that a no-value row SATISFIES a negation-carrying operator, i.e. ['2','3']:

Operator name: null key absent ruling answer
$ne: 'alpha-one' ['2','3'] OK ['2','3'] OK ['2','3']
$nin: ['alpha-one'] ['2','3'] OK ['2'] DIVERGENT ['2','3']
$notContains: 'one' ['2'] DIVERGENT ['2'] DIVERGENT ['2','3']

Three of six cells diverge. This reproduces exactly the cell already recorded in the filter-logic-conformance.ts measurement table ("driver-memory reference matcher | no — DIVERGENT, frozen (#5499) | MATCH on a null value, no on a missing key"), so the divergence is real, unchanged, and now unexcused.

Mechanism

Two independent causes in memory-matcher.ts checkCondition:

  1. The pre-switch guard if (value === undefined && op !== '$exists' && op !== '$ne' && op !== '$null') return false; — its allowlist names $ne but not $nin / $notContains, so a MISSING key short-circuits to "no match" for those two before their arms ever run.
  2. The $notContains arm itself: if (typeof value !== 'string' || value.includes(target)) return false; — a null value is not a string, so it fails on the type test rather than on the predicate. That is the name: null half, which the guard above does not reach.

driver-mongodb is NOT part of this: translateFieldOperators maps $nin straight through and compiles $notContains to { $not: { $regex } }, both of which match a missing/null field in MongoDB. It already agrees with the ruling.

Why this is now a card rather than a comment

Two in-code annotations still spell the freeze as LIVE and use it to excuse the divergence — they are #13089's sites 3-4 and were deliberately left untouched:

Both are wrong on two counts now: the freeze dissolved on 2026-08-11 (corroborated in the head note of packages/spec/src/data/aggregation-conformance.ts), and the driver-mongodb half of the claim was never true for this operator family. Rewriting them to past tense without deciding the divergence would have converted an actionable defect into settled-looking prose, which is what the #13089 triage forbade.

What a fix owes

  • Decide the direction first: align driver-memory's reference matcher to the ruling (the include direction), or record a measured, dated exemption. Note this matcher is the reference the SQL family was aligned TO for $not 的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {} 一个是 TRUE 一个是 FALSE #5146, so moving it is not local.
  • $exists is a separate, still-open cell — filter-logic-conformance.ts records that driver-memory's live mingo path and driver-mongodb both still read key-presence rather than has-value, which is why no $exists row can be enrolled yet. Not necessarily this card's scope, but it is the neighbouring cell.
  • Enrolment, not a row: the head note of filter-logic-conformance.ts is explicit that a row added ahead of a backend is just a red gate, and that the DEBT ledger in scripts/check-driver-conformance.mjs is per (driver x case-set), with no spelling for "fails one row of thirty-six".

Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions