From c28172bd6648155fcd5d033a9898ef0a160277ff Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 11:46:20 +0000 Subject: [PATCH 1/2] fix(spec): the null ordering-comparand refusal and the vocabulary assertion name only faces that exist The schema-door refusal for a null $gt/$gte/$lt/$lte comparand gave, as its example of the evaluation faces disagreeing, driver-memory's reference matcher, which has been deleted. The parenthesis now names two faces that exist and were measured to disagree: driver-memory's query path admits a stored-null row for {"$gte": null}; driver-sql compares against SQL NULL and admits no row. The first sentence, the prescription and the ruling sentence are unchanged. The filter-operator vocabulary assertion message no longer lists the deleted reference matcher among driver-memory's faces. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude --- .../21397-null-ordering-message-faces.md | 20 +++++++++++++++++++ .../data/filter-operator-vocabulary.test.ts | 4 ++-- packages/spec/src/data/filter.zod.ts | 5 +++-- 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 .changeset/21397-null-ordering-message-faces.md diff --git a/.changeset/21397-null-ordering-message-faces.md b/.changeset/21397-null-ordering-message-faces.md new file mode 100644 index 00000000000..30c31629294 --- /dev/null +++ b/.changeset/21397-null-ordering-message-faces.md @@ -0,0 +1,20 @@ +--- +'@objectstack/spec': patch +--- + +fix(spec): the null ordering-comparand refusal names only evaluation faces that exist + +Clause-②: no + +`FieldOperatorsSchema` and `ComparisonOperatorSchema` refuse a `null` comparand of `$gt` / `$gte` / +`$lt` / `$lte` with a pointed message. Its example of the evaluation faces disagreeing named +driver-memory's reference matcher, which has been deleted, so an author or agent reading the +refusal went looking for a face that no longer exists. The example now names two faces that exist +and were measured to disagree: driver-memory's query path reads a stored `null` as equal to the +comparand, so `{"$gte": null}` admits that row, while driver-sql compares against SQL `NULL` and +admits no row. + +Text only: the first sentence (`null is not a valid $gt comparand.` and its siblings), the prescription +(`{"$eq": null}` / `{"$ne": null}`) and the ruling sentence are unchanged, and the schemas accept +and refuse exactly the same filters. A client or log filter that matches the old parenthesis needs +the new spelling. diff --git a/packages/spec/src/data/filter-operator-vocabulary.test.ts b/packages/spec/src/data/filter-operator-vocabulary.test.ts index 65714f0887c..14c3f3183fb 100644 --- a/packages/spec/src/data/filter-operator-vocabulary.test.ts +++ b/packages/spec/src/data/filter-operator-vocabulary.test.ts @@ -83,8 +83,8 @@ describe('the declaration surface and the enforcement surface', () => { + 'so declaring ahead of the arms is the correct staging. To stage one: declare it in ' + 'FieldOperatorsSchema, add it to the array THIS assertion compares against, and note on ' + 'FILTER_OPERATORS which issue implements it. To clear one: implement it on EVERY face ' - + 'in ONE PR — spec word list, driver-memory (query path, reference matcher, analytics ' - + 'face), driver-mongodb, service-analytics (3 compilers), objectql `having`, formula — ' + + 'in ONE PR — spec word list, driver-memory (query path, analytics face), ' + + 'driver-mongodb, service-analytics (3 compilers), objectql `having`, formula — ' + 'then empty this list. #6520 is the worked example of the clearing direction.', ).toEqual(STAGED_AHEAD_OF_BACKENDS); }); diff --git a/packages/spec/src/data/filter.zod.ts b/packages/spec/src/data/filter.zod.ts index 0ec404b85bf..b56e7c59e2c 100644 --- a/packages/spec/src/data/filter.zod.ts +++ b/packages/spec/src/data/filter.zod.ts @@ -466,8 +466,9 @@ const ORDERING_COMPARAND_DESCRIPTION = function nullOrderingComparandMessage(op: string): string { return ( `null is not a valid ${op} comparand. null is not ordered, and no two evaluation faces ` - + 'agree on what an ordering against it matches (driver-memory\'s live path reads two ' - + 'absences as equal; its reference matcher compares through JS coercion). State absence ' + + 'agree on what an ordering against it matches (driver-memory\'s query path reads a ' + + 'stored null as equal to it, so {"$gte": null} admits that row; driver-sql compares ' + + 'against SQL NULL and admits no row). State absence ' + 'with the null predicate instead: {"$eq": null} is "has no value", {"$ne": null} is ' + '"has a value". Ruled 2026-09-01: a null ordering comparand is refused at the validation ' + 'entrance.' From 80b111c86e8053aab31bfda5e772e50ecb7ce083 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 12:51:22 +0000 Subject: [PATCH 2/2] fix(spec): both null ordering-comparand refusals say the evaluation faces do not agree Measured, driver-sql and formula agree on an ordering against null (both admit no row), so "no two evaluation faces agree" was false read pairwise. The schema-door message (nullOrderingComparandMessage) and its runtime twin (nullOrderingComparandError) now both say "the evaluation faces do not agree"; every other sentence of each is unchanged. The vocabulary assertion's every-face list now also names driver-sql and driver-turso's remote transport, which clearing $empty edited. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude --- .changeset/21397-null-ordering-message-faces.md | 15 ++++++++++----- packages/spec/src/data/filter-comparand-shape.ts | 2 +- .../src/data/filter-operator-vocabulary.test.ts | 5 +++-- packages/spec/src/data/filter.zod.ts | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.changeset/21397-null-ordering-message-faces.md b/.changeset/21397-null-ordering-message-faces.md index 30c31629294..e96c007002b 100644 --- a/.changeset/21397-null-ordering-message-faces.md +++ b/.changeset/21397-null-ordering-message-faces.md @@ -2,7 +2,7 @@ '@objectstack/spec': patch --- -fix(spec): the null ordering-comparand refusal names only evaluation faces that exist +fix(spec): the null ordering-comparand refusals name only evaluation faces that exist, and say only what was measured Clause-②: no @@ -14,7 +14,12 @@ and were measured to disagree: driver-memory's query path reads a stored `null` comparand, so `{"$gte": null}` admits that row, while driver-sql compares against SQL `NULL` and admits no row. -Text only: the first sentence (`null is not a valid $gt comparand.` and its siblings), the prescription -(`{"$eq": null}` / `{"$ne": null}`) and the ruling sentence are unchanged, and the schemas accept -and refuse exactly the same filters. A client or log filter that matches the old parenthesis needs -the new spelling. +That refusal and its runtime twin, the `parseFilterAST` refusal for the same comparand +(`Operator "$gt" on field "…" does not accept a null comparand …`), both said "no two evaluation +faces agree" on what an ordering against `null` matches. Measured, two faces do agree (driver-sql +and formula both admit no row), so both now say "the evaluation faces do not agree". + +Text only: each message's first sentence, its prescription (`{"$eq": null}` / `{"$ne": null}`), the +schema door's ruling sentence and the runtime door's "NOT applied" sentence are unchanged, and both +doors accept and refuse exactly the same filters. A client or log filter that matches the old +wording needs the new spelling. diff --git a/packages/spec/src/data/filter-comparand-shape.ts b/packages/spec/src/data/filter-comparand-shape.ts index a639231c230..b4282015e25 100644 --- a/packages/spec/src/data/filter-comparand-shape.ts +++ b/packages/spec/src/data/filter-comparand-shape.ts @@ -703,7 +703,7 @@ function nullOrderingComparandError( return invalidFilterComparandError( context, `Operator "${op}" on field "${field}" does not accept a null comparand (at ${path}). ` + - `null is not ordered; no two evaluation faces agree on what it matches. State absence ` + + `null is not ordered; the evaluation faces do not agree on what it matches. State absence ` + `with the null predicate: {"$eq": null} is "has no value", {"$ne": null} is "has a value". ` + `Authoring spellings: ${spellings.join(', ')}. The filter was NOT applied, and an ` + `unapplied filter would have returned the UNFILTERED result set.`, diff --git a/packages/spec/src/data/filter-operator-vocabulary.test.ts b/packages/spec/src/data/filter-operator-vocabulary.test.ts index 14c3f3183fb..c68a6bcfefb 100644 --- a/packages/spec/src/data/filter-operator-vocabulary.test.ts +++ b/packages/spec/src/data/filter-operator-vocabulary.test.ts @@ -83,8 +83,9 @@ describe('the declaration surface and the enforcement surface', () => { + 'so declaring ahead of the arms is the correct staging. To stage one: declare it in ' + 'FieldOperatorsSchema, add it to the array THIS assertion compares against, and note on ' + 'FILTER_OPERATORS which issue implements it. To clear one: implement it on EVERY face ' - + 'in ONE PR — spec word list, driver-memory (query path, analytics face), ' - + 'driver-mongodb, service-analytics (3 compilers), objectql `having`, formula — ' + + 'in ONE PR — spec word list, driver-memory (query path, analytics face), driver-sql, ' + + 'driver-turso (remote transport), driver-mongodb, service-analytics (3 compilers), ' + + 'objectql `having`, formula — ' + 'then empty this list. #6520 is the worked example of the clearing direction.', ).toEqual(STAGED_AHEAD_OF_BACKENDS); }); diff --git a/packages/spec/src/data/filter.zod.ts b/packages/spec/src/data/filter.zod.ts index b56e7c59e2c..7b580111352 100644 --- a/packages/spec/src/data/filter.zod.ts +++ b/packages/spec/src/data/filter.zod.ts @@ -465,7 +465,7 @@ const ORDERING_COMPARAND_DESCRIPTION = */ function nullOrderingComparandMessage(op: string): string { return ( - `null is not a valid ${op} comparand. null is not ordered, and no two evaluation faces ` + `null is not a valid ${op} comparand. null is not ordered, and the evaluation faces do not ` + 'agree on what an ordering against it matches (driver-memory\'s query path reads a ' + 'stored null as equal to it, so {"$gte": null} admits that row; driver-sql compares ' + 'against SQL NULL and admits no row). State absence '