Skip to content

[finding] once #14080's refusal lands, three prose sites still describe $gt: null as the one null comparand the contract accepts, and the TYPE door's undefined sentence prescribes a now-refused spelling at the ordering positions #14426

Description

@os-musk

Blocked-by: #14080

Filed unassigned by the #14080 dev (session session_0112hMx9hjJ9BgB28X97DS68, PR #14425). Recording only — an observation, no severity asserted; routing is triage's. (The refusal lands as PR #14425 under issue #14080; until it lands every sentence below is still true.)

What becomes stale

PR #14425 executes the 2026-09-01 ruling on #14080: a null comparand of $gt / $gte / $lt / $lte is refused at parseFilterAST (INVALID_FILTER / 400). The census that gated that PR (its body carries the method and the table) found the shape written down in prose at three places that describe the PRE-ruling state and are outside the PR's fence (packages/drivers/driver-memory/src/** other than the new pin was ⛔ for that card):

  1. packages/drivers/driver-memory/src/memory-matcher.ts line ~342 — the driver-memory's reference matcher puts a NULL-VALUED row inside $gt/$gte/$lt/$lte on a NUMERIC column — null coerces to 0, and the live mingo path excludes it #13553 guard's comment: "$gt: null is the one null-comparand position the contract still ACCEPTS (measured at parseFilterAST) … Deciding it in an operator arm would pick a camp the platform declined to pick". After feat(spec): refuse a null comparand in the ordering positions — $gt / $gte / $lt / $lte (#14080) #14425 the position is refused, so the comment's reason for excluding a no-value COMPARAND from the guard changes from "no ruling covers it" to "ruled unreachable — refused at the door" (the exclusion itself stays right: ⛔ 不单独修 matcher).
  2. packages/drivers/driver-memory/src/memory-matcher-null-value-and-comparand.test.ts line ~63 — the header's "⚠️ A null COMPARAND in an ORDERING position ({$gte: null}) is absent … and it is the one such position the contract still ACCEPTS". The absence stays deliberate; the sentence's reason moves to "refused, ruled 2026-09-01" — the same wording the file already uses for the list positions.
  3. packages/spec/src/data/filter-comparand-type.tsundefinedComparandRefusal's prescription "Write null for the null predicate, or omit the key" is position-agnostic. At an ordering position ({ $gt: undefined }) an author who follows it writes { $gt: null } and meets the new refusal, which then names $eq: null / $ne: null. Two loud hops, right final answer — but the first hop's advice is wrong for four of the positions it is emitted at. Not a defect in the door; a wording drift worth one sentence ("the null predicate is $eq: null / $null: true", which is position-safe).

Not in this finding

The test fixtures that exercise the shape in consumers OUTSIDE the door (service-analytics's normalizer on the object-form where, formula's matchesFilter) are boundary pins, not stale prose — PR #14425's body records them as the honest boundary the refusal does not widen.

Suggested shape

One small PR after #14425 lands: the two driver-memory comments re-pointed at the ruling, and the TYPE door's undefined sentence made position-safe, with the existing message pins in filter-comparand-type.test.ts updated if they quote it. No behaviour change.

Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions