Skip to content

driver-memory's two filter faces disagree when a SCALAR comparand meets a stored ARRAY value — == joins the array to "a,b" in the matcher (a false positive) while the live mingo path reads membership #16838

Description

@os-musk

Found while landing #16810 (the array/Date comparand arm of the same function). Filed unassigned; the fix is deliberately NOT in that PR, which pins the behaviour below as unchanged so the refusal it lands could not move it by accident.

What is measured

driver-memory's two filter faces disagree when a scalar comparand meets a stored array value — and they disagree in BOTH directions, one widening and one narrowing. Measured on this repo's own driver, one row per case, with a firing control (a scalar comparand that legitimately matches answered true / 1 row and its negative twin false / 0 rows in the same run).

filter stored value reference matcher (memory-matcher.ts) live query path (InMemoryDriver.find, mingo)
{ tags: 'a' } ['a','b'] false 1 row
{ tags: 'a,b' } ['a','b'] true 0 rows
{ tags: 'a' } ['a'] true 1 row

Both rows of the disagreement come from one line, checkCondition's return value == condition: == converts the stored ARRAY to a primitive, so ['a','b'] becomes the string "a,b". The matcher therefore answers a query nobody wrote — a row whose tags joins to "a,b" matches a comparand of "a,b" — while missing the membership reading ('a' against ['a','b']) that mingo gives on the live path.

The second row is the sharper one: it is a false positive, so a filter meant to narrow returns a row it should not. On an RLS read scope that direction is a permission concern rather than a degraded filter.

Why it is a card rather than a note

It is the #5240 / #5328 / #5347 / #13549 shape exactly — one filter, one package, two answers, nothing reported — and the package's whole refusal architecture exists to remove that class. It is not covered by #16810: that card's cell is the COMPARAND side (an array in the comparand position, now refused), while this is the VALUE side, which the spec's comparand door does not judge at all.

What is NOT decided here

Which face is right. The live path's membership reading is MongoDB's array semantics; the matcher's string-join reading is an accident of ==. A third answer (refuse, as the comparand side was ruled) is also available. The spec's comparand door rules comparands, so it does not settle a stored-array VALUE, and no ruling in the tree names this cell — which is why this is filed rather than fixed.

Repro

Both faces, same row, no build needed beyond the driver's own dependency closure:

import { match } from '@objectstack/driver-memory/src/memory-matcher.js';
match({ tags: ['a', 'b'] }, { tags: 'a,b' });   // true  — the false positive
match({ tags: ['a', 'b'] }, { tags: 'a' });     // false — while the live path returns the row

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions