From 3ba971f1b6bcaf233670da2ba6e25c3ac89d65da Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 09:31:25 +0000 Subject: [PATCH 1/6] docs: comments outside driver-memory name what replaced the retired reference matcher driver-memory's reference matcher (memory-matcher.ts) was retired by commit 8fec76a2b. Sentences outside that package that still named it as a live surface now name what carries the semantics today, or say it is retired: driver-memory's query path (normalizeFilterCondition, then mingo), formula's matches-filter.ts, or the driver-memory suites that hold the deleted test files' cells (memory-driver-document-not.test.ts, memory-driver-filter-logic-conformance.test.ts). Historical sentences are unchanged. Comment and docblock prose only. Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp Co-authored-by: Claude --- .../predicate-compilation-convergence.md | 6 ++--- .../src/matches-filter-not-null-safe.test.ts | 12 ++++++---- .../src/claim-seed-ownership.ts | 6 +++-- .../filter-normalizer-not-null-safe.test.ts | 8 ++++--- ...jectql-contains-canonical-operator.test.ts | 24 +++++++++++-------- .../src/strategies/objectql-strategy.ts | 9 +++---- .../src/attachment-read-visibility.test.ts | 9 ++++--- .../spec/src/data/filter-comparand-shape.ts | 12 ++++++---- .../spec/src/data/filter-logic-conformance.ts | 2 +- 9 files changed, 53 insertions(+), 35 deletions(-) diff --git a/docs/design/predicate-compilation-convergence.md b/docs/design/predicate-compilation-convergence.md index 5b043683de4..c74836bfc3f 100644 --- a/docs/design/predicate-compilation-convergence.md +++ b/docs/design/predicate-compilation-convergence.md @@ -41,7 +41,7 @@ Vocabulary is counted against the 19 field operators an author can write. That i | F1 | `driver-sql` | `SqlDriver.applyFilters` → `compileFilters` → `applyFilterCondition` (`sql-driver.ts:16350` / `16462` / `16789`) | `find`/`findOne`, `count`, `aggregate`, `distinct`, `updateMany`, `deleteMany`, `findWithWindowFunctions`, `analyzeQuery` on PostgreSQL, MySQL and SQLite. `driver-sqlite-wasm` and `driver-turso` local/replica mode inherit it (`extends SqlDriver`). | 19/19, plus `$and`/`$or`/`$not` and `$field` | none (relies on the engine seam); calls `reduceFilterVerdict`, `isAcceptedFilterComparand`, `expandEmptyOperator` | | F2 | `driver-turso` remote | `TursoDriver.toRemoteFilter` (`turso-driver.ts:2566`, a lowering pass) → `RemoteTransport.buildWhereSQL` / `compileWhereSQL` (`remote-transport.ts:2852` / `2896`) | remote-mode `find`, `count`, `aggregate`, `distinct`, `updateMany`, `deleteMany` | 18/19 (`$between` is lowered before the transport) | none; calls `isAcceptedFilterComparand`, `expandEmptyOperator` | | F3 | `driver-memory` query path | `InMemoryDriver.convertToMongoQuery` (`memory-driver.ts:1334`) → mingo `Query`, behind `assertFilterConditionShape` (`filter-refusal.ts`) | `find`/`findOne`, `count`, `distinct`, `aggregate`, `updateMany`, `deleteMany` | 19/19 | none (relies on the engine seam) | -| F4 | `driver-memory` reference matcher | `match()` (`memory-matcher.ts:43`) | **no production caller.** It is not exported from the package index, and 20 test files import it. | 19/19 | none | +| F4 | `driver-memory` reference matcher | `match()` (`memory-matcher.ts:43`) | **no production caller.** It was not exported from the package index, and 20 test files imported it. **Retired** under D6 by commit `8fec76a2b`: `memory-matcher.ts` is deleted, and the tests that imported it assert on F3, the shared gate or the spec predicate. | 19/19 | none | | F5 | `driver-memory` cube face | `MemoryAnalyticsService.query` / `generateSql` → `normalizeFilters` (`memory-analytics.ts:910` / `1389` / `1501`) → mingo `$match` and echo SQL | the published `@objectstack/driver-memory` export. No in-repo door constructs it (recorded on #20661). | 12/19 (no `$between`, `$startsWith`, `$endsWith`, `$null`, `$empty`, `$like`, `$ilike`); `$and` is its only combinator | **none** — see §2.5 | | F6 | `driver-mongodb` | `translateFilter` (`mongodb-filter.ts:805`); the aggregation `$match` reuses it (`mongodb-aggregation.ts:556`) | all CRUD verbs and `aggregate` | 17/19 (no `$like`/`$ilike`) | none (relies on the engine seam) | | F7 | `formula` | `matchesFilterCondition` (`matches-filter.ts:322`) | the RLS `check` on a write's post-image (`security-plugin.ts:3215`), the tenant check (`:3519`), the explain engine (`explain-engine.ts:964`), and F8's scalar comparisons (`having-filter.ts:1155`) | 19/19 | none (the RLS compile seam runs the doors first) | @@ -53,7 +53,7 @@ Vocabulary is counted against the 19 field operators an author can write. That i | F10c | ↳ engine hand-off | `filterNodeToCondition` (`objectql-strategy.ts:1602`) | a `FilterCondition` handed back to the engine, which F1, F3 or F6 then compile a second time | (the tree's) | — | | F11 | `service-analytics` draft preview | `evaluateAnalyticsQueryOverRows` → `matchesWhere` (`preview-evaluator.ts:640` / `325`) | the draft-preview branch of `queryDataset` (`analytics-service.ts:1819`), reached through REST `?preview=` (`rest-server.ts:5872`) | 10/19 (`$eq $ne $gt $gte $lt $lte $between $in $nin $contains`); the rest are refused | both, through `normalizeWhereComparands` (`preview-evaluator.ts:665`) | -The 15 source files are `sql-driver.ts`, `turso-driver.ts`, `remote-transport.ts`, `memory-driver.ts`, `filter-refusal.ts`, `memory-matcher.ts`, `memory-analytics.ts`, `mongodb-filter.ts`, `matches-filter.ts`, `having-filter.ts`, `read-scope-sql.ts`, `filter-normalizer.ts`, `native-sql-strategy.ts`, `objectql-strategy.ts` and `preview-evaluator.ts`. §2 uses this list as its "face files". +The 15 source files are `sql-driver.ts`, `turso-driver.ts`, `remote-transport.ts`, `memory-driver.ts`, `filter-refusal.ts`, `memory-matcher.ts` (deleted since, by commit `8fec76a2b`), `memory-analytics.ts`, `mongodb-filter.ts`, `matches-filter.ts`, `having-filter.ts`, `read-scope-sql.ts`, `filter-normalizer.ts`, `native-sql-strategy.ts`, `objectql-strategy.ts` and `preview-evaluator.ts`. §2 uses this list as its "face files". ### 1.2 Against the card's table @@ -355,7 +355,7 @@ A ruling that adds a new predicate *kind* or a dialect construct still costs one | F1 `driver-sql` | the engine seam | no | polarity quartet (67 lines), `assertDefinedComparands`, the `calendarDay*Rewrite` calls (5 sites) — only under D4 (b) | `FILTER_LOGIC`, `FILTER_TEXT`, `TEMPORAL`, `FILTER_COMPARAND_TYPE` on SQLite, plus the PostgreSQL/MySQL live matrix | direct callers (D4); a 21,103-line file | | F2 `driver-turso` remote | the engine seam, then `toRemoteFilter` | no | `toRemoteFilter`'s `$between` / whole-day arms (3 sites), the transport's polarity copy (69 lines) | turso filter-logic (local and remote), local/remote NULL parity | a live remote server was NOT MEASURED here | | F3 `driver-memory` query | the engine seam | no | whole-day calls (8 sites) | memory filter-logic, temporal, text | — | -| F4 reference matcher | — (no production caller) | no | keep as test oracle, or retire (D6) | 20 test files | — | +| F4 reference matcher | — (no production caller) | no | **retired** (D6, commit `8fec76a2b`): `memory-matcher.ts` is deleted, and the tests that imported it keep their assertions on F3, the shared gate or the spec predicate | 20 test files | — | | F5 cube face | the new `normalizeFilters` door | **yes**: doors + lowering, and widen `$or` / `$not` / `$null` | whole-day calls (5 sites) | its own suites; not in `check:driver-conformance` | an accept-set widening, so a changeset with its Clause-② line | | F6 `driver-mongodb` | the engine seam | no | whole-day calls (4 sites) | mongodb filter-logic, text, temporal, comparand-type | the server answer was NOT MEASURED here | | F7 `formula` | the RLS compile seam (policies); the engine (via F8) | no | **retired** (#21242): `lteBound` and its 2 sites are deleted; a bound that reaches F7 unlowered is compared as written (D-D1 item 5) | matches-filter not-null-safe, or-semantics, temporal | — | diff --git a/packages/formula/src/matches-filter-not-null-safe.test.ts b/packages/formula/src/matches-filter-not-null-safe.test.ts index 7213d924c4d..0283545352f 100644 --- a/packages/formula/src/matches-filter-not-null-safe.test.ts +++ b/packages/formula/src/matches-filter-not-null-safe.test.ts @@ -14,8 +14,10 @@ * * These cases are therefore a PIN on the reference behaviour, mirrored id-for-id * by `driver-sql`'s `sql-driver-not-null-safe.test.ts` and `driver-memory`'s - * `memory-matcher-not-null-safe.test.ts`. Moving an expectation here silently - * re-opens the divergence. + * `memory-driver-document-not.test.ts` (its query path; it holds the cells of + * `memory-matcher-not-null-safe.test.ts`, deleted with the reference matcher in + * commit `8fec76a2b`). Moving an expectation here silently re-opens the + * divergence. * * `cel-to-filter.ts` is why this matters in practice: a CEL `!expr` in a * permission rule lowers to exactly these `$not` shapes. @@ -116,8 +118,10 @@ describe('[#5146] matchesFilterCondition — $not over records with no value', ( it('$not of $notContains does NOT match them — the mirror case', () => { // A value-less field satisfies `$notContains` here, so the negation - // rejects it. `driver-sql` follows this answer; `driver-memory`'s - // REFERENCE matcher answers the opposite for a null-valued field. + // rejects it. `driver-sql` follows this answer, and so does + // `driver-memory`'s query path (`memory-driver-document-not.test.ts`). + // Its REFERENCE matcher answered the opposite for a null-valued field + // until PR #13356, and commit `8fec76a2b` has since retired it. // // ⚠️ [#5299, 2026-08-10] A ruling that morning would have reversed this // direction; it was WITHDRAWN the same day and include re-affirmed. See diff --git a/packages/plugins/plugin-security/src/claim-seed-ownership.ts b/packages/plugins/plugin-security/src/claim-seed-ownership.ts index 6d34e418a50..1c9fa5b8aa2 100644 --- a/packages/plugins/plugin-security/src/claim-seed-ownership.ts +++ b/packages/plugins/plugin-security/src/claim-seed-ownership.ts @@ -87,8 +87,10 @@ * * ⚠️ The order is not cosmetic. Paging unconditionally was measured 13× SLOWER * on the sizes every real install has: an `id IN (…)` page is evaluated by - * `InMemoryDriver` as a linear scan of the id list PER ROW - * (`memory-matcher.ts`, `target.includes(value)`), so a paged claim is + * `InMemoryDriver` as a linear scan of the id list PER ROW (it hands `$in` to + * mingo, whose `$in` hashes the whole list again for each row — not the + * reference matcher `memory-matcher.ts` this once named, which had no + * production caller and which commit `8fec76a2b` retired), so a paged claim is * quadratic there, where the natural predicate is linear. 5 000 rows: 528 ms * whole-set versus 5 865 ms always-paged, same engine, same driver, same row * set. The page is therefore what the engine's refusal buys, not the default. diff --git a/packages/services/service-analytics/src/__tests__/filter-normalizer-not-null-safe.test.ts b/packages/services/service-analytics/src/__tests__/filter-normalizer-not-null-safe.test.ts index 73d4f67be51..6d920609dfb 100644 --- a/packages/services/service-analytics/src/__tests__/filter-normalizer-not-null-safe.test.ts +++ b/packages/services/service-analytics/src/__tests__/filter-normalizer-not-null-safe.test.ts @@ -47,9 +47,11 @@ * Measured, not reasoned: the fixture is row-for-row `driver-sql`'s * `sql-driver-not-null-safe.test.ts`, and every id set below is the answer that * file, `formula/src/matches-filter-not-null-safe.test.ts`, - * `driver-memory/src/memory-matcher-not-null-safe.test.ts` and this package's - * own `read-scope-not-null-safe.test.ts` assert for the same filter. Moving an - * expectation here re-opens the divergence #5146 closed. + * `driver-memory/src/memory-driver-document-not.test.ts` (which holds the cells + * of `memory-matcher-not-null-safe.test.ts`, deleted with the reference matcher + * in commit `8fec76a2b`) and this package's own `read-scope-not-null-safe.test.ts` + * assert for the same filter. Moving an expectation here re-opens the divergence + * #5146 closed. * * `sql.js` (pure WASM) is the engine, for the reason spelled out at the top of * `native-sql-filter-logic-conformance.test.ts`: a native binding is loadable diff --git a/packages/services/service-analytics/src/__tests__/objectql-contains-canonical-operator.test.ts b/packages/services/service-analytics/src/__tests__/objectql-contains-canonical-operator.test.ts index e5f63692592..d404cb9acce 100644 --- a/packages/services/service-analytics/src/__tests__/objectql-contains-canonical-operator.test.ts +++ b/packages/services/service-analytics/src/__tests__/objectql-contains-canonical-operator.test.ts @@ -28,8 +28,9 @@ * `FilterCondition` consumer and fails closed on an operator it cannot * compile, so it THREW on the filter this strategy produced. * 3. **The row set depends on which driver answers.** A backend that evaluates - * `$regex` as a real regex (driver-memory's `memory-matcher.ts`: - * `new RegExp(target, condition.$options || '')`, `catch { return false }`) + * `$regex` as a real regex (driver-memory's `memory-matcher.ts` was one: + * `new RegExp(target, condition.$options || '')`, `catch { return false }`, + * until #4706 retired `$regex` and commit `8fec76a2b` retired the matcher) * reads `a.b` as "a, any character, b" and reads `50% (+)` as a SyntaxError → * zero rows, in silence. `driver-sql` compiles the same `$regex` to a * substring LIKE. One dashboard, two row sets. @@ -100,13 +101,15 @@ const query = (where: unknown): AnalyticsQuery => /** * An engine face that evaluates the four LIKE operators the way - * `driver-memory`'s `memory-matcher.ts` does, `$regex` arm included. + * `driver-memory`'s `memory-matcher.ts` did, `$regex` arm included (#4706 + * retired `$regex`, and commit `8fec76a2b` retired that matcher). * * Mirrored rather than imported: `service-analytics` does not depend on any - * driver (see its `package.json`), and the point is not "driver-memory is - * broken" — driver-memory's `$regex` arm is deliberate and serves a real - * producer (plugin-auth's ObjectQL adapter, see `filter-refusal.ts`'s - * `SUPPORTED_FIELD_OPERATORS` note). The point is that a filter carrying + * driver (see its `package.json`), and the point was never "driver-memory is + * broken" — driver-memory's `$regex` arm was deliberate while it served a real + * producer (plugin-auth's ObjectQL adapter; the `[#5702]` note over + * `filter-refusal.ts`'s `SUPPORTED_FIELD_OPERATORS` records that producer's move + * to `$contains`). The point is that a filter carrying * `$regex` MEANS something different on a regex-evaluating face than the * substring the analytics author wrote, and this strategy has no business * choosing between those readings on the author's behalf. @@ -115,8 +118,9 @@ function matchesLikeFamily(row: (typeof FIXTURE)[number], cond: Record { /** * Row ids the ObjectQL path returns when the engine reads the LIKE family - * the way `memory-matcher.ts` does. + * the way the retired `memory-matcher.ts` did. */ const ids = async (where: unknown): Promise => { const ctx = { diff --git a/packages/services/service-analytics/src/strategies/objectql-strategy.ts b/packages/services/service-analytics/src/strategies/objectql-strategy.ts index 76c5d3a4950..550f8c417f0 100644 --- a/packages/services/service-analytics/src/strategies/objectql-strategy.ts +++ b/packages/services/service-analytics/src/strategies/objectql-strategy.ts @@ -1684,7 +1684,7 @@ export class ObjectQLStrategy implements AnalyticsStrategy { * OR ABSORBER, so a `null` branch makes the whole disjunction unconstrained * instead of collapsing it to its surviving branches (#5325). FALSE is handed * to the engine as `{$not: {}}`, the spelling `driver-sql`, `formula` and - * `driver-memory`'s matcher all already pin as the zero-row filter (#5134) — + * `driver-memory`'s query path all already pin as the zero-row filter (#5134) — * this strategy invents no second one. */ private filterNodeToCondition( @@ -2052,9 +2052,10 @@ export class ObjectQLStrategy implements AnalyticsStrategy { // filter tree no longer travelled between two consumers of the same // contract sitting in the same directory. // 3. On a backend that reads `$regex` as a real regex — driver-memory's - // `memory-matcher.ts` does, deliberately, for plugin-auth's adapter - // — an unescaped comparand changes what the author asked for: - // `a.b` also matched `axb`, and `50% (+)` did not compile at all, so + // `memory-matcher.ts` did, deliberately, for plugin-auth's adapter, + // until #4706 retired `$regex` (commit `8fec76a2b` has since retired + // the matcher too) — an unescaped comparand changes what the author + // asked for: `a.b` also matched `axb`, and `50% (+)` did not compile at all, so // the `catch { return false }` answered zero rows in silence. // `driver-sql` meanwhile compiles `$regex` to a substring LIKE, so // the same widget returned different row sets per driver. diff --git a/packages/services/service-storage/src/attachment-read-visibility.test.ts b/packages/services/service-storage/src/attachment-read-visibility.test.ts index 3065556b9c0..84a14e954eb 100644 --- a/packages/services/service-storage/src/attachment-read-visibility.test.ts +++ b/packages/services/service-storage/src/attachment-read-visibility.test.ts @@ -10,8 +10,9 @@ const silentLogger = () => ({ info: vi.fn(), warn: vi.fn(), debug: vi.fn() }); * Filter Protocol evaluator for the fake engine below — real `$and` / `$or` / * `$not` semantics, not a shape check. * - * Mirrors `driver-memory`'s `memory-matcher.ts` and `formula`'s - * `matches-filter.ts`: **every key inside one filter object ANDs**, a `$or` + * Mirrors `formula`'s `matches-filter.ts` (as `driver-memory`'s + * `memory-matcher.ts` did until commit `8fec76a2b` retired it): **every key + * inside one filter object ANDs**, a `$or` * array ORs its branches, and a branch's own contents still AND. * * This used to understand neither logical operators nor anything but `$in`, @@ -323,7 +324,9 @@ describe('installAttachmentReadVisibility', () => { * Who tests the test double? The row assertions above are only worth as much * as the matcher evaluating them, so it gets the same 2x2 fixture and the same * expectations as the three production backends: - * `driver-memory/memory-matcher-or-semantics.test.ts`, + * `driver-memory/memory-driver-filter-logic-conformance.test.ts` (which holds + * the cases of `memory-matcher-or-semantics.test.ts`, deleted with the + * reference matcher in commit `8fec76a2b`), * `formula/matches-filter-or-semantics.test.ts`, and * `driver-sql/sql-driver-or-filter.test.ts`. If this harness ever drifts from * them, a read scope it declares safe would not be safe in the engine. diff --git a/packages/spec/src/data/filter-comparand-shape.ts b/packages/spec/src/data/filter-comparand-shape.ts index a639231c230..c225b1ffede 100644 --- a/packages/spec/src/data/filter-comparand-shape.ts +++ b/packages/spec/src/data/filter-comparand-shape.ts @@ -124,8 +124,9 @@ * agree, and the SQL family's `NOT IN` answer is unconditional), and the * maintainer ruled the divergence constructively unreachable rather than * reconciled — ⛔ no cross-backend alignment; #5299 stays declined, and the - * matcher's own answers for these shapes are sealed behind this refusal, not - * repaired. "Equals X or has no value" has an explicit spelling — + * matcher's own answers for these shapes were sealed behind this refusal, never + * repaired, until commit `8fec76a2b` retired the matcher itself. "Equals X or + * has no value" has an explicit spelling — * `$or: [{$in: […]}, {$null: true}]` — and the refusal text prescribes it. * #5041's question (ISO date strings as legitimate `$between` bounds) and * #5234's (object members on the `driver-sql` face) stand untouched. @@ -139,10 +140,11 @@ * had recorded it in writing as one "no ruling covers", and `driver-memory`'s * two faces answered it differently — the live path reads two absences as * EQUAL (so `$gte: null` admits the no-value row and `$gt: null` does not), - * the reference matcher compares through JS coercion (`5 > null` is + * the reference matcher compared through JS coercion (`5 > null` is * `5 > 0`). Ruled 2026-09-01 (option A): refused at this door, same envelope, - * so the divergent cells are constructively unreachable — ⛔ the matcher is - * not repaired (dead code once refused), ⛔ no ordering-vs-null semantics is + * so the divergent cells are constructively unreachable — ⛔ the matcher was + * not repaired (dead code once refused; commit `8fec76a2b` has since retired + * it), ⛔ no ordering-vs-null semantics is * defined anywhere (the live path's reading needs a strictness rule, "two * absences compare equal", that no ruling states), ⛔ no cross-backend * alignment. `null` is not ordered; the refusal text prescribes the ruled diff --git a/packages/spec/src/data/filter-logic-conformance.ts b/packages/spec/src/data/filter-logic-conformance.ts index 24f71a62dfc..6e141f47570 100644 --- a/packages/spec/src/data/filter-logic-conformance.ts +++ b/packages/spec/src/data/filter-logic-conformance.ts @@ -12,7 +12,7 @@ * | Backend | Where | * |---|---| * | SQL compiler | `driver-sql` `applyFilterCondition` | - * | In-memory matcher | `driver-memory` `memory-matcher` | + * | In-memory query path | `driver-memory` `normalizeFilterCondition`, then mingo (the reference matcher `memory-matcher` held this row until commit `8fec76a2b` retired it) | * | Record-at-a-time evaluator | `formula` `matchesFilterCondition` (RLS write-side `check`) | * | Read-scope SQL lowering | `service-analytics` `read-scope-sql` | * | MongoDB query translator | `driver-mongodb` `translateFilter` | From f1d77a9e4152700765249c9f97a1b3a8f62b23e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 09:46:36 +0000 Subject: [PATCH 2/6] docs(changeset): spec and service-analytics ship the corrected comments The table row in spec's filter-logic conformance docblock reaches data/index.d.ts and data/index.d.mts, and both ObjectQLStrategy comments reach service-analytics' JavaScript output (one also its declaration file), so each package takes a patch entry. formula and service-storage changed test files only, and plugin-security's rewritten docblock is not in its dist. Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp Co-authored-by: Claude --- .changeset/20822-retired-matcher-pointers.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .changeset/20822-retired-matcher-pointers.md diff --git a/.changeset/20822-retired-matcher-pointers.md b/.changeset/20822-retired-matcher-pointers.md new file mode 100644 index 00000000000..23423b020aa --- /dev/null +++ b/.changeset/20822-retired-matcher-pointers.md @@ -0,0 +1,15 @@ +--- +'@objectstack/spec': patch +'@objectstack/service-analytics': patch +--- + +Published comments that named `driver-memory`'s retired reference matcher as a live filter backend now name what replaced it + +Clause-②: no + +`driver-memory`'s reference matcher (`memory-matcher.ts`) was retired in commit `8fec76a2b`. Two published packages still described it as a live surface in text that ships: + +- `@objectstack/spec`: the backend table in the filter-logic conformance docblock, which ships in `data/index.d.ts` and `data/index.d.mts`, now lists the in-memory backend as `driver-memory`'s query path (`normalizeFilterCondition`, then mingo) where it listed `memory-matcher`, and says the matcher held that row until commit `8fec76a2b` retired it. +- `@objectstack/service-analytics`: two comments in `ObjectQLStrategy`, which ship in the JavaScript output (the first also in `index.d.ts` / `index.d.cts`), changed. The first names `driver-memory`'s query path, not its matcher, as a face that pins `{$not: {}}` as the zero-row filter. The second says in the past tense that `memory-matcher.ts` read `$regex` as a real regex, until `$regex` was retired and commit `8fec76a2b` retired the matcher too. + +Comment only: no export, type, error code, status, message text or runtime behaviour changes. From 68ca26224f07b856a99ac66aa8372800c27167f1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 10:58:12 +0000 Subject: [PATCH 3/6] docs(service-analytics): read-scope not-null-safe pin names the driver-memory suite that holds the deleted matcher file's cells The docblock pointed at memory-matcher-not-null-safe.test.ts, deleted with driver-memory's reference matcher in commit 8fec76a2b. It now names memory-driver-document-not.test.ts, which holds those cells on the query path, and keeps the historical note that the suites were run while these cases were written. Comment only. Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp Co-authored-by: Claude --- .../src/__tests__/read-scope-not-null-safe.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/services/service-analytics/src/__tests__/read-scope-not-null-safe.test.ts b/packages/services/service-analytics/src/__tests__/read-scope-not-null-safe.test.ts index ac060ea2275..37056bdfe1b 100644 --- a/packages/services/service-analytics/src/__tests__/read-scope-not-null-safe.test.ts +++ b/packages/services/service-analytics/src/__tests__/read-scope-not-null-safe.test.ts @@ -39,10 +39,13 @@ * Measured, not reasoned: the fixture is row-for-row the one in * `driver-sql`'s `sql-driver-not-null-safe.test.ts`, and every id set below is * the answer that file and the two JS-backend pins - * (`formula/src/matches-filter-not-null-safe.test.ts`, - * `driver-memory/src/memory-matcher-not-null-safe.test.ts`) assert for the same + * (`formula/src/matches-filter-not-null-safe.test.ts` and + * `driver-memory/src/memory-driver-document-not.test.ts`) assert for the same * filter — all three suites were run against this fixture while writing these - * cases. Moving an expectation here re-opens the divergence #5146 closed. + * cases, the `driver-memory` one as `memory-matcher-not-null-safe.test.ts`, + * whose cells `memory-driver-document-not.test.ts` holds since commit + * `8fec76a2b` deleted it with the reference matcher. Moving an expectation here + * re-opens the divergence #5146 closed. * * `sql.js` (pure WASM) is the engine, for the reason spelled out at the top of * `read-scope-sql-conformance.test.ts`: a native binding cannot be relied on to From 1bf9b24f262d83c1493828e69f4844426125791b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 11:00:51 +0000 Subject: [PATCH 4/6] wip(group 4 round 1): engine-lane matcher pointers Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp Co-authored-by: Claude --- .../drivers/driver-mongodb/src/mongodb-filter.ts | 6 ++++-- .../src/remote-transport-boolean-identity.test.ts | 7 +++++-- .../src/remote-transport-not-operator.test.ts | 4 +++- packages/formula/src/matches-filter.ts | 4 +++- packages/objectql/src/having-filter.test.ts | 5 +++-- packages/objectql/src/having-filter.ts | 12 +++++++----- .../src/number-comparand-declared-type-door.ts | 5 +++-- packages/objectql/src/validation/record-validator.ts | 9 +++++---- packages/objectql/test-typecheck-debt.json | 2 +- packages/objectql/tsconfig.test.json | 5 +++-- 10 files changed, 37 insertions(+), 22 deletions(-) diff --git a/packages/drivers/driver-mongodb/src/mongodb-filter.ts b/packages/drivers/driver-mongodb/src/mongodb-filter.ts index 0db26f8d9f8..9cdd4dc4edc 100644 --- a/packages/drivers/driver-mongodb/src/mongodb-filter.ts +++ b/packages/drivers/driver-mongodb/src/mongodb-filter.ts @@ -1148,8 +1148,10 @@ interface LoweredWrite { * write becomes its own `$and` branch on the same field, where both constraints * survive. That is exactly the guard #13195 landed for `$exists` alone, * generalised to every writer rather than restated once per operator. - * `driver-memory`'s reference matcher loops the operators and therefore cannot - * express this defect at all; it is the oracle both drivers agree with. + * `driver-memory`'s reference matcher looped the operators and therefore could + * not express this defect at all; it was the oracle both drivers agreed with + * until commit `8fec76a2b` retired it, and `driver-memory`'s + * `memory-operator-key-clobber.test.ts` keeps its answers as literal row sets. * * ## Why rank, and not author order * diff --git a/packages/drivers/driver-turso/src/remote-transport-boolean-identity.test.ts b/packages/drivers/driver-turso/src/remote-transport-boolean-identity.test.ts index 0820c063d75..84140bd7a6f 100644 --- a/packages/drivers/driver-turso/src/remote-transport-boolean-identity.test.ts +++ b/packages/drivers/driver-turso/src/remote-transport-boolean-identity.test.ts @@ -40,8 +40,11 @@ import { markFilterSubtreeProvenance } from '@objectstack/spec/data'; * them correctly rather than refuse them. Framework's `matchesFilterCondition` * (`packages/formula/src/matches-filter.ts`) already evaluates them this way and * pins it — `expect(m(rec, { $or: [] })).toBe(false) // empty OR matches - * nothing` — as does `driver-memory`'s matcher (`.some()` over an empty array). - * These tests hold the remote transport to the same table. + * nothing` — as does `driver-memory`'s query path, which runs + * `FILTER_LOGIC_CASES`' "empty $or is FALSE" case + * (`memory-driver-filter-logic-conformance.test.ts`); its reference matcher + * (`.some()` over an empty array) answered the same until commit `8fec76a2b` + * retired it. These tests hold the remote transport to the same table. * * The other half of the fix is that "compiles to nothing" now has exactly ONE * cause. An element that is not a filter NODE (null, a scalar, an array, a diff --git a/packages/drivers/driver-turso/src/remote-transport-not-operator.test.ts b/packages/drivers/driver-turso/src/remote-transport-not-operator.test.ts index 06af9dc2e14..cc2f6113162 100644 --- a/packages/drivers/driver-turso/src/remote-transport-not-operator.test.ts +++ b/packages/drivers/driver-turso/src/remote-transport-not-operator.test.ts @@ -37,7 +37,9 @@ import { lowerFilterCondition, markFilterSubtreeProvenance } from '@objectstack/ * — declared in the same object literal as the two that WERE implemented. And * it is a shape real rules produce: `SqlDriver.applyFilterCondition` compiles it * with `whereNot`/`orWhereNot` (framework#2704, added to close this same - * silent-filter-bypass family), `driver-memory`'s matcher and + * silent-filter-bypass family), `driver-memory`'s query path + * (`memory-driver-document-not.test.ts`; its reference matcher did too until + * commit `8fec76a2b` retired it) and * `matchesFilterCondition` both evaluate it, and CEL `!expr` in a permission / * RLS read scope lowers to `{ $not: {…} }` (`formula/src/cel-to-filter.ts`). So * one RLS scope answered correctly on a local SqlDriver and broke on Turso diff --git a/packages/formula/src/matches-filter.ts b/packages/formula/src/matches-filter.ts index f963059621f..75a7881362b 100644 --- a/packages/formula/src/matches-filter.ts +++ b/packages/formula/src/matches-filter.ts @@ -726,7 +726,9 @@ function evalOp( /** * [#6520] `$contains`' case-INSENSITIVE twin, folding ASCII case and nothing * else — `asciiCaseInsensitiveContains` is the spec's shared definition, the - * same one `driver-memory`'s matcher and objectql's `having` call. + * same one objectql's `having` calls. `driver-memory`'s reference matcher + * called it too until commit `8fec76a2b` retired it; `driver-memory`'s + * query path folds through its pattern twin, `asciiCaseInsensitiveRegexSource`. * * NOT `actual.toLowerCase().includes(v.toLowerCase())`, which is the obvious * line and the wrong one: it folds the whole Unicode range, so an RLS diff --git a/packages/objectql/src/having-filter.test.ts b/packages/objectql/src/having-filter.test.ts index 840278604bc..3b98ed4efbc 100644 --- a/packages/objectql/src/having-filter.test.ts +++ b/packages/objectql/src/having-filter.test.ts @@ -5,7 +5,8 @@ * * The namespace is the aggregated row's own columns (aggregation aliases + * groupBy projections); operator semantics follow the Filter Protocol, with two - * deliberate divergences from driver-memory's matcher: an unknown operator + * deliberate divergences from driver-memory's reference matcher (retired since by + * commit `8fec76a2b`): an unknown operator * throws — ignoring one would silently return unfiltered aggregates, the exact * silently-inert failure (#4286, ADR-0078) enforcement exists to end — and the * negation-carrying operators are NULL-safe per #5298 (see the grid at the @@ -57,7 +58,7 @@ describe('applyHaving', () => { expect(applyHaving(ROWS, { total: { $between: [600, 1300] } }).map((r) => r.customer_id)) .toEqual(['c2', 'c3']); expect(applyHaving(ROWS, { region: { $null: true } }).map((r) => r.customer_id)) - .toEqual(['c1', 'c2', 'c3']); // absent folds into null, like the memory matcher + .toEqual(['c1', 'c2', 'c3']); // absent folds into null, as driver-memory's query path reads `$null` }); it('multiple keys on one condition AND together, like `where`', () => { diff --git a/packages/objectql/src/having-filter.ts b/packages/objectql/src/having-filter.ts index e2c5fc94ee5..a999c0e0482 100644 --- a/packages/objectql/src/having-filter.ts +++ b/packages/objectql/src/having-filter.ts @@ -20,11 +20,11 @@ // ordinary FilterCondition over those columns: implicit equality, the // comparison / set / null / existence / string operators, and `$and` / `$or` / // `$not` composition. Operator semantics follow the Filter Protocol, with TWO -// deliberate divergences from driver-memory's matcher — the face this module -// was originally written against: +// deliberate divergences from driver-memory's reference matcher — the face this +// module was originally written against, which commit `8fec76a2b` retired: // -// 1. AN UNKNOWN OPERATOR THROWS. The memory matcher ignores operators it does -// not know; here an ignored operator would silently return UNFILTERED +// 1. AN UNKNOWN OPERATOR THROWS. The memory matcher ignored operators it did +// not know when this module was written; here an ignored operator would silently return UNFILTERED // aggregates — the precise failure mode (#4286, ADR-0078) this module exists // to end. The rejection names the operator and the supported set. // @@ -2061,7 +2061,9 @@ function checkCondition( // at query time (SQLSTATE 42883), and `driver-memory`'s reference // matcher failed both polarities. The maintainer ruled the cell on // 2026-09-05 (option A, type-gate) and `FILTER_TEXT_CASES`' `score` rows - // pin it on every face: the reference matcher answers the predicate, and + // pin it on every face: the record-at-a-time faces (`formula`, this + // walker) answer the predicate, as `driver-memory`'s reference matcher did + // until commit `8fec76a2b` retired it, and // the SQL compilers emit a type-gated constant for a column whose // declared type is in `NON_TEXT_STORED_VALUE_TYPES`. This arm was already // on the ruled side; nothing here moved. diff --git a/packages/objectql/src/number-comparand-declared-type-door.ts b/packages/objectql/src/number-comparand-declared-type-door.ts index cc4b1198938..84e9a0c96c6 100644 --- a/packages/objectql/src/number-comparand-declared-type-door.ts +++ b/packages/objectql/src/number-comparand-declared-type-door.ts @@ -43,8 +43,9 @@ * | `having` on `sum(amount)`: `$gt "abc"` | 200, no group | 200, no group | 200, no group | * * One client mistake, three answers, one of them a server fault; and a numeric - * string read two ways (the memory matcher compares `12 > "12"` without - * coercing it, the SQL backends bind it with numeric affinity or input). + * string read two ways (`InMemoryDriver`'s query path hands the comparison to + * mingo, which compares `12 > "12"` without coercing it; the SQL backends bind + * it with numeric affinity or input). * * ## The door's two answers * diff --git a/packages/objectql/src/validation/record-validator.ts b/packages/objectql/src/validation/record-validator.ts index 7455fdd0c4c..627681bcb71 100644 --- a/packages/objectql/src/validation/record-validator.ts +++ b/packages/objectql/src/validation/record-validator.ts @@ -529,10 +529,11 @@ function isMultiValueField(def: FieldDef): boolean { * * ## Why derived and not `key.startsWith('$')` * - * The repo already carries five hand-rolled `keys.some(k => k.startsWith('$'))` - * shape tests (`having-filter.ts`, `driver-memory`'s matcher and - * `filter-refusal.ts`, `driver-mongodb`'s `mongodb-filter.ts`, `driver-turso`'s - * `remote-transport.ts`). None of them is exported, and none is reachable from + * The repo already carries hand-rolled `keys.some(k => k.startsWith('$'))` + * shape tests: five when this was written (`having-filter.ts`, `driver-memory`'s + * matcher and `filter-refusal.ts`, `driver-mongodb`'s `mongodb-filter.ts`, + * `driver-turso`'s `remote-transport.ts`), four since commit `8fec76a2b` retired + * the matcher. None of them is exported, and none is reachable from * this package without inverting the layering — `@objectstack/objectql` depends * on no driver. Writing a sixth `startsWith('$')` here is the accident #5659 * names: one question, N private answers, and the day one of them changes only diff --git a/packages/objectql/test-typecheck-debt.json b/packages/objectql/test-typecheck-debt.json index 4be34455546..fb48290e375 100644 --- a/packages/objectql/test-typecheck-debt.json +++ b/packages/objectql/test-typecheck-debt.json @@ -1,6 +1,6 @@ { "_comment": "Per-file tsc error debt of the @objectstack/objectql TEST layer (#5286). `tsconfig.test.json` compiles `src/**/*.test.ts` — which `tsconfig.json` excludes and therefore no gate ever read — and every file below still carries errors from before that gate existed. THIS FIELD IS GENERATED: every regeneration rewrites it from scripts/check-test-typecheck.mts, and the EXACT ratchet below requires a regeneration on every repair — so an edit made here is gone by the next one. Anything true of THIS package goes in the sibling `_note` field, which is authored, is preserved verbatim, and is never written by the generator (#12624). This comment states NO cause for the errors, deliberately: the classes differ per package and per file, they move as the debt is paid down, and a cause written here is rewritten verbatim into every ledger by every regeneration — so it outlives its own repair and cannot be corrected in the file where it is read. Measure instead, before repairing anything: `tsc --noEmit --pretty false -p tsconfig.test.json` in the package prints the real classes with their TS codes. Each entry maps a file to its per-SIGNATURE error counts, never to a bare total, because a total can hold while the errors underneath it are replaced wholesale: a signature is the TS code plus the diagnostic message with structural type blobs collapsed, and it carries NO line or column — so the pin survives edits that move code around, and only stops matching when the error itself becomes a different error. EXACT ratchet, judged by re-running tsc: a file that gains errors is red, a file that loses them is red until its number is re-recorded, a file that reaches zero is red until its entry is deleted, a signature that ARRIVES or VANISHES is red even when the file total is unchanged, and a file NOT listed here may have no errors at all. Regenerate with: pnpm --filter @objectstack/objectql gen:test-typecheck-debt", - "_note": "SEEDED at 2a181174a6 by the PR that first put this layer in front of tsc (#13676), workspace closure built first. All 242 errors across 44 files are PRE-EXISTING: that PR edits no test file, and every one of these would have been reported on origin/main had this program ever existed. ⛔ Not one `any` and not one `@ts-expect-error` was added anywhere to open the gate — bulk-suppressing this residue is the exact shape the card was filed to prevent, and it would turn a real gate into a phantom one. The residue is NOT annotation debt to sweep: 14 of the 17 in src/datasource-mapping.test.ts and the bulk of the TS2345 population are one story — a driver double that does not satisfy IDataDriver — and src/engine.test.ts alone carries 102 of the 242, so read the classes before touching anything (`tsc --noEmit --pretty false -p tsconfig.test.json`). ⚠️ src/engine-filter-array-lowering.test.ts is DELIBERATELY ABSENT and must stay absent: it is the #13357 point-3 negative pin proving a refused filter shape cannot reach the reference matcher, it reports zero errors, and an unledgered file is red on its first error. ⚠️ But its zero is a weak guarantee today and must not be read as 'that pin is type-checked': the file holds `const driver: any` and `interface SeenRead { ast: any }` at the very seam the proof reads, so a drifted IDataDriver signature would not redden it. Repairing that is tracked separately and is not a licence to loosen tsconfig.test.json. ⬇ AMENDED at #16319: four files GRADUATED and their entries are deleted here, so the seed counts above ('242 errors across 44 files') are the 2a181174a6 record and no longer this file's totals — read `entries`. The eight errors were one story, not annotation debt: src/protocol-recorded-by-null, -registry-shadow and -save-meta-repo-path-real-engine each built a `sys_metadata` fixture whose `metadata` field declared `type: 'longtext' as const`, and src/registry-invalidate built one declaring `type: 'id'` / `type: 'string'` — five spellings the `FieldType` enum has never contained. The `as const` / `as` casts they needed to get past tsc WERE the recorded errors. #16319 made the registration door refuse such a declaration outright, so the fixtures were corrected to the members the platform's own sys_metadata object uses (`textarea` / `text`) and the casts stopped straining.", + "_note": "SEEDED at 2a181174a6 by the PR that first put this layer in front of tsc (#13676), workspace closure built first. All 242 errors across 44 files are PRE-EXISTING: that PR edits no test file, and every one of these would have been reported on origin/main had this program ever existed. ⛔ Not one `any` and not one `@ts-expect-error` was added anywhere to open the gate — bulk-suppressing this residue is the exact shape the card was filed to prevent, and it would turn a real gate into a phantom one. The residue is NOT annotation debt to sweep: 14 of the 17 in src/datasource-mapping.test.ts and the bulk of the TS2345 population are one story — a driver double that does not satisfy IDataDriver — and src/engine.test.ts alone carries 102 of the 242, so read the classes before touching anything (`tsc --noEmit --pretty false -p tsconfig.test.json`). ⚠️ src/engine-filter-array-lowering.test.ts is DELIBERATELY ABSENT and must stay absent: it is the #13357 point-3 negative pin proving a refused filter shape cannot reach a driver (it was written against driver-memory's reference matcher, which commit 8fec76a2b has since retired), it reports zero errors, and an unledgered file is red on its first error. ⚠️ But its zero is a weak guarantee today and must not be read as 'that pin is type-checked': the file holds `const driver: any` and `interface SeenRead { ast: any }` at the very seam the proof reads, so a drifted IDataDriver signature would not redden it. Repairing that is tracked separately and is not a licence to loosen tsconfig.test.json. ⬇ AMENDED at #16319: four files GRADUATED and their entries are deleted here, so the seed counts above ('242 errors across 44 files') are the 2a181174a6 record and no longer this file's totals — read `entries`. The eight errors were one story, not annotation debt: src/protocol-recorded-by-null, -registry-shadow and -save-meta-repo-path-real-engine each built a `sys_metadata` fixture whose `metadata` field declared `type: 'longtext' as const`, and src/registry-invalidate built one declaring `type: 'id'` / `type: 'string'` — five spellings the `FieldType` enum has never contained. The `as const` / `as` casts they needed to get past tsc WERE the recorded errors. #16319 made the registration door refuse such a declaration outright, so the fixtures were corrected to the members the platform's own sys_metadata object uses (`textarea` / `text`) and the casts stopped straining.", "entries": { "src/build-probes.test.ts": { "TS2493: Tuple type '…' of length '0' has no element at index '1'.": 1 diff --git a/packages/objectql/tsconfig.test.json b/packages/objectql/tsconfig.test.json index c6114bb363a..032de0cb785 100644 --- a/packages/objectql/tsconfig.test.json +++ b/packages/objectql/tsconfig.test.json @@ -18,8 +18,9 @@ // // ⭐ WHY THIS PACKAGE, AND WHY NOW. `src/engine-filter-array-lowering.test.ts` // is the negative pin required by maintainer ruling #13357 execution point 3 -// (landed in #13673): it proves a refused filter shape CANNOT REACH the -// reference matcher. A pin whose whole job is a security-relevant +// (landed in #13673): it proves a refused filter shape CANNOT REACH a driver +// (it was written against `driver-memory`'s reference matcher, which commit +// `8fec76a2b` has since retired). A pin whose whole job is a security-relevant // unreachability guarantee was type-checked by nothing at all. // // What differs from the build config, and what deliberately does NOT: From 57d675df102e49099b7bd974ea3f30c527769a3e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 11:11:17 +0000 Subject: [PATCH 5/6] wip(group 4 round 1): services- and spec-lane matcher pointers Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp Co-authored-by: Claude --- .../bootstrap-declared-capabilities.test.ts | 6 +++-- .../src/bootstrap-system-capabilities.test.ts | 6 +++-- .../src/rls-check-stored-form.ts | 2 +- .../src/strategies/filter-normalizer.ts | 4 +++- .../spec/src/data/filter-text-conformance.ts | 5 ++-- packages/spec/src/data/filter.zod.ts | 24 +++++++++++-------- packages/spec/src/ui/view.zod.ts | 7 +++--- 7 files changed, 33 insertions(+), 21 deletions(-) diff --git a/packages/plugins/plugin-security/src/bootstrap-declared-capabilities.test.ts b/packages/plugins/plugin-security/src/bootstrap-declared-capabilities.test.ts index 6380f2bae0b..89f4b88b1e7 100644 --- a/packages/plugins/plugin-security/src/bootstrap-declared-capabilities.test.ts +++ b/packages/plugins/plugin-security/src/bootstrap-declared-capabilities.test.ts @@ -36,8 +36,10 @@ function makeQl(declared: any[] = []) { if (object !== 'sys_capability') return []; const where = q?.where ?? {}; // [#8470] A `null` comparand is IS NULL, not `=== null`: `driver-sql` - // compiles `{ field: null }` to `IS NULL`, `driver-memory`'s matcher uses - // `value == condition`, and MongoDB matches null-or-missing — none of them + // compiles `{ field: null }` to `IS NULL`, `driver-memory`'s query path + // matches null-or-missing through mingo (its reference matcher, retired by + // commit `8fec76a2b`, used `value == condition`), and MongoDB matches + // null-or-missing — none of them // is strict equality against an ABSENT key. `bootstrapSystemCapabilities` // (called by several cases below) scopes its curated lookup with // `organization_id: null`, which strict `===` would make unsatisfiable diff --git a/packages/plugins/plugin-security/src/bootstrap-system-capabilities.test.ts b/packages/plugins/plugin-security/src/bootstrap-system-capabilities.test.ts index e1f66feaa6f..581d4d1669a 100644 --- a/packages/plugins/plugin-security/src/bootstrap-system-capabilities.test.ts +++ b/packages/plugins/plugin-security/src/bootstrap-system-capabilities.test.ts @@ -26,8 +26,10 @@ import { buildExistingByName } from './seed-name-lookup.js'; * unrelated to ownership. Insertion order is what the double used to model, * and it models nothing. * 2. **A `null` comparand matches a null OR absent value.** `driver-sql` - * compiles `{ field: null }` to `IS NULL`; `driver-memory`'s matcher uses - * `value == condition`; MongoDB matches null-or-missing. Strict `===`, which + * compiles `{ field: null }` to `IS NULL`; `driver-memory`'s query path + * matches null-or-missing through mingo (its reference matcher, retired by + * commit `8fec76a2b`, used `value == condition`); MongoDB matches + * null-or-missing. Strict `===`, which * this double used, matches NONE of them and would have made * `organization_id: null` unsatisfiable here while working in production. * 4. **`$in` membership**, because the real engine has it (`security-plugin.ts` diff --git a/packages/plugins/plugin-security/src/rls-check-stored-form.ts b/packages/plugins/plugin-security/src/rls-check-stored-form.ts index 9abd7ba5f83..13157285af8 100644 --- a/packages/plugins/plugin-security/src/rls-check-stored-form.ts +++ b/packages/plugins/plugin-security/src/rls-check-stored-form.ts @@ -37,7 +37,7 @@ * - every comparand of the value comparisons on that column (`$eq`, `$ne`, the * four orderings, `$in`, `$nin`, `$between`, and implicit equality), because * the read compares the stored value against the comparand in that form - * (`driver-sql`'s `coerceFilterValue`, `driver-memory`'s matcher, objectql's + * (`driver-sql`'s `coerceFilterValue`, `driver-memory`'s query path, objectql's * `having` walker all pair the two). Putting only the image into the form * would refuse a write the read shows whenever a policy spells its comparand * another way: `record.start_time == '09:00'` against a stored `'09:00:00'`. diff --git a/packages/services/service-analytics/src/strategies/filter-normalizer.ts b/packages/services/service-analytics/src/strategies/filter-normalizer.ts index 564c80f58c5..492d5ce73f1 100644 --- a/packages/services/service-analytics/src/strategies/filter-normalizer.ts +++ b/packages/services/service-analytics/src/strategies/filter-normalizer.ts @@ -447,7 +447,9 @@ * Row-result cover: `filter-operator-coverage.test.ts` for the operator * vocabulary, `native-sql-filter-logic-conformance.test.ts`, which runs the * SHARED combinator table (`FILTER_LOGIC_CASES`, #3774) that the SQL compiler, - * the in-memory matcher, `formula` and `read-scope-sql` are already held to, + * `driver-memory`'s query path (its in-memory reference matcher, which ran the + * table too, was retired by commit `8fec76a2b`), `formula` and `read-scope-sql` + * are already held to, * `filter-normalizer-not-null-safe.test.ts` for the two squares that table * deliberately does not carry (NULL handling, boolean identities), * `filter-array-lowering.test.ts` for the array door (#5334), diff --git a/packages/spec/src/data/filter-text-conformance.ts b/packages/spec/src/data/filter-text-conformance.ts index ccdbd09499a..d45bd0f3021 100644 --- a/packages/spec/src/data/filter-text-conformance.ts +++ b/packages/spec/src/data/filter-text-conformance.ts @@ -339,8 +339,9 @@ export const FILTER_TEXT_CASES: readonly FilterTextCase[] = [ // `$regex` and that driver answers them too. #6682\'s second half then took // the same flag off driver-memory\'s query path and off the rule its analytics // face borrows, which was the last folding face on the platform. (`formula` - // and driver-memory\'s reference matcher measured case-exact both then and - // now — they are what the other faces were moved onto.) + // measured case-exact both then and now, and driver-memory\'s reference + // matcher did until commit `8fec76a2b` retired it — they are what the other + // faces were moved onto.) { name: '$contains is case-SENSITIVE — a lower-case comparand misses the upper-case row', filter: { name: { $contains: 'acme' } }, diff --git a/packages/spec/src/data/filter.zod.ts b/packages/spec/src/data/filter.zod.ts index 0ec404b85bf..47cc9375123 100644 --- a/packages/spec/src/data/filter.zod.ts +++ b/packages/spec/src/data/filter.zod.ts @@ -407,8 +407,10 @@ const ORDERING_COMPARAND_DESCRIPTION = * one form the platform's own date-macro path can never hand them. This is the * declaration aligning to a contract the rest of the stack already keeps, not * a new capability: every evaluation surface ALREADY compares strings - * (`driver-sql` binds `>`/`>=`/`<`/`<=`, `formula`'s `matchesFilter` and - * `driver-memory`'s matcher fall through to the JS operators). + * (`driver-sql` binds `>`/`>=`/`<`/`<=`, `formula`'s `matchesFilter` falls + * through to the JS operators, and `driver-memory`'s query path hands the + * comparison to mingo, which orders strings; its reference matcher, retired by + * commit `8fec76a2b`, fell through to the JS operators). * * ## Why a BARE string, and not an ISO-shaped refinement (#5685 rider ①) * @@ -937,7 +939,7 @@ export const RangeOperatorSchema = lazySchema(() => z.object({ * | `driver-sql` | ANSWERS both rows | its own `case '$icontains'`, folding through the same emitter that carries the escaping | * | `driver-sqlite-wasm` | ANSWERS both rows | INHERITED — `SqliteWasmDriver extends SqlDriver`; this package carries no text case arm of its own, on a different ENGINE | * | `driver-turso` | ANSWERS both rows, on BOTH transports | local inherits `SqlDriver`; the remote transport compiles independently and has its own arm | - * | `driver-memory` — query path, reference matcher, analytics face | ANSWERS both rows | #6520; the pattern faces take {@link asciiCaseInsensitiveRegexSource}, the matcher {@link asciiCaseInsensitiveContains} | + * | `driver-memory` — query path, analytics face | ANSWERS both rows | #6520; both take {@link asciiCaseInsensitiveRegexSource} (its reference matcher took {@link asciiCaseInsensitiveContains} until commit `8fec76a2b` retired it) | * | `driver-mongodb` | ANSWERS both rows | #6520; an ASCII-only `$regex`, never `$options: 'i'` | * | objectql `having` | ANSWERS both rows | #6520; {@link asciiCaseInsensitiveContains} over the aggregated row | * | `formula` `matchesFilterCondition` | ANSWERS both rows | #6520; the same helper, on the RLS write-side `check` | @@ -1194,16 +1196,17 @@ const ASCII_CASE_DELTA = 0x20; // 'a' - 'A' * * ## Why this is in the spec and not four times in four packages * - * `$icontains` has six JS evaluation faces (`driver-memory`'s query path, - * reference matcher and analytics face, `driver-mongodb`, objectql's `having`, - * `@objectstack/formula`'s `matchesFilterCondition`) plus three SQL compilers in + * `$icontains` has five JS evaluation faces (`driver-memory`'s query path and + * analytics face, `driver-mongodb`, objectql's `having`, + * `@objectstack/formula`'s `matchesFilterCondition`; a sixth, `driver-memory`'s + * reference matcher, was retired by commit `8fec76a2b`) plus three SQL compilers in * `service-analytics`. Every one of them needs the same fold, and this repo has * already measured what happens when such a rule is written out per package: * *"a list written out here would agree with the spec on the day it was typed * and never again"* (`driver-memory/src/filter-refusal.ts`, on the operator * vocabulary) — the #3948 shape, reached through the fold instead of the word * list. One definition means a fold that is wrong is wrong everywhere at once, - * which is the only way six faces can be held to one answer. + * which is the only way these faces can be held to one answer. * * ## Why not `toLowerCase()` * @@ -1239,7 +1242,8 @@ export function foldAsciiCase(value: string): string { * [#6520] Does `haystack` contain `needle`, ignoring ASCII case only? * * The `$icontains` predicate for every face that can compare two JS strings - * directly — the reference matcher, objectql's `having`, `formula`. The fold + * directly — objectql's `having` and `formula` (and `driver-memory`'s reference + * matcher until commit `8fec76a2b` retired it). The fold * runs on BOTH sides, which is the half that is easy to get wrong: folding only * the comparand compares a folded needle against a raw haystack and matches just * the rows that were already lower-case. `FILTER_TEXT_CASES`' first row (an @@ -3108,7 +3112,7 @@ export const FilterArraySchema: z.ZodType = z.lazy(() * |---|---| * | `driver-sql` (and `driver-sqlite-wasm`, which inherits its compiler) | ANSWERS — `LIKE` / `GLOB` per dialect, caller-bound wildcards | * | `driver-turso` — local (inherits `SqlDriver`) and remote (its own compiler) | ANSWERS on both transports | - * | `driver-memory` — query path and reference matcher | ANSWERS — it widens its own `SUPPORTED_FIELD_OPERATORS` by hand, the way `driver-turso`'s remote transport has carried `$icontains` since #5702. It is the in-memory DOUBLE: an app whose tests run there and whose production runs SQL must not get a 400 for a filter that works | + * | `driver-memory` — query path (its reference matcher answered too until commit `8fec76a2b` retired it) | ANSWERS — it widens its own `SUPPORTED_FIELD_OPERATORS` by hand, the way `driver-turso`'s remote transport has carried `$icontains` since #5702. It is the in-memory DOUBLE: an app whose tests run there and whose production runs SQL must not get a 400 for a filter that works | * | `@objectstack/formula` `matchesFilterCondition` | ANSWERS — {@link matchesLikePattern}, so a write-side `check` agrees with the read-side SQL | * | `driver-mongodb`, `objectql` `having`, `service-analytics` | REFUSE, loudly, in the ADR-0112 `INVALID_FILTER` envelope — they derive acceptance from THIS array, which does not name the operator | * @@ -3139,7 +3143,7 @@ export const FilterArraySchema: z.ZodType = z.lazy(() * | `driver-turso` remote transport | the declared row, through the resolver `TursoDriver` wires | * | `driver-memory` query path, `driver-mongodb` | the declared row, from `syncSchema` | * | `service-analytics` — `where` and read-scope SQL | the declared row, from the host's `sourceFieldMeta` | - * | `driver-memory` reference matcher, objectql `having`, `@objectstack/formula` `matchesFilterCondition` | by VALUE — null, `''` and `[]` are empty (they hold no declarations) | + * | objectql `having`, `@objectstack/formula` `matchesFilterCondition` (and `driver-memory`'s reference matcher until commit `8fec76a2b` retired it) | by VALUE — null, `''` and `[]` are empty (they hold no declarations) | * | `driver-memory` analytics (cube) face | REFUSES — `INVALID_FILTER` / 400, as it refuses `$null` | * * A declared-row face asked about a column it holds NO declaration for refuses diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index 6dcb6d567bd..f988699edb4 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -602,9 +602,10 @@ const VIEW_FILTER_TEXT_COMPARAND_OPERATOR = 'icontains' satisfies ViewFilterOper * `false` (an array is none of the six accepted comparand types — * `isAcceptedFilterComparand`, `filter-comparand-type.ts`), and the comparand * is refused with the withheld `INVALID_FILTER` / 400 envelope. - * - **`driver-memory` REFUSES** the same shape in the same envelope — `match()` - * runs `assertFilterConditionShape`, whose implicit-equality arm throws on an - * array (`filter-refusal.ts`). That refusal first shipped in + * - **`driver-memory` REFUSES** the same shape in the same envelope — its query + * path's `convertToMongoQuery` runs `assertFilterConditionShape`, whose + * implicit-equality arm throws on an array (`filter-refusal.ts`); the reference + * matcher's `match()` ran it too until commit `8fec76a2b` retired the matcher. That refusal first shipped in * `@objectstack/driver-memory@17.4.0`; published 17.3.0 returned the row * stored as `['a']` (run in this change's review; which other rows it * selected is NOT MEASURED). From 54c8e70e88fb043f30ee71ab376726a3a7829528 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 11:43:51 +0000 Subject: [PATCH 6/6] docs(changeset): formula and objectql ship the corrected comments too; spec's entry names the new docblocks Measured with three legs at 57d675df10: the round's rewritten lines reach spec's filter declaration chunk, data/index.js and the shipped src/**/*.zod.ts, formula's index.js/.mjs and objectql's index and core bundles. driver-mongodb and service-analytics changed source maps only, and driver-turso and plugin-security not at all, so they take no new entry. Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp Co-authored-by: Claude --- .changeset/20822-retired-matcher-pointers.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.changeset/20822-retired-matcher-pointers.md b/.changeset/20822-retired-matcher-pointers.md index 23423b020aa..18263e6811f 100644 --- a/.changeset/20822-retired-matcher-pointers.md +++ b/.changeset/20822-retired-matcher-pointers.md @@ -1,15 +1,23 @@ --- '@objectstack/spec': patch '@objectstack/service-analytics': patch +'@objectstack/formula': patch +'@objectstack/objectql': patch --- Published comments that named `driver-memory`'s retired reference matcher as a live filter backend now name what replaced it Clause-②: no -`driver-memory`'s reference matcher (`memory-matcher.ts`) was retired in commit `8fec76a2b`. Two published packages still described it as a live surface in text that ships: +`driver-memory`'s reference matcher (`memory-matcher.ts`) was retired in commit `8fec76a2b`. Four published packages still described it as a live surface in text that ships: -- `@objectstack/spec`: the backend table in the filter-logic conformance docblock, which ships in `data/index.d.ts` and `data/index.d.mts`, now lists the in-memory backend as `driver-memory`'s query path (`normalizeFilterCondition`, then mingo) where it listed `memory-matcher`, and says the matcher held that row until commit `8fec76a2b` retired it. +- `@objectstack/spec`: + - The backend table in the filter-logic conformance docblock, which ships in `data/index.d.ts` and `data/index.d.mts`, now lists the in-memory backend as `driver-memory`'s query path (`normalizeFilterCondition`, then mingo) where it listed `memory-matcher`, and says the matcher held that row until commit `8fec76a2b` retired it. + - `src/data/filter.zod.ts` ships as source. In it, the `$icontains` implementation table lists `driver-memory`'s query path and analytics face, both on `asciiCaseInsensitiveRegexSource`. The `$like` / `$ilike` and `$empty` tables keep the matcher only in a note that commit `8fec76a2b` retired it. The `foldAsciiCase` docblock counts five JS evaluation faces where it counted six. The `asciiCaseInsensitiveContains` docblock names objectql's `having` and `formula` as its callers. The string-ordering note says `driver-memory`'s query path hands the comparison to mingo. Of these, the `foldAsciiCase`, `asciiCaseInsensitiveContains` and `FILTER_OPERATORS` docblocks also ship in the filter declaration chunk (`filter.zod-*.d.ts` / `.d.mts`). + - `src/ui/view.zod.ts` ships as source. It now says that `driver-memory`'s query path runs `assertFilterConditionShape` through `convertToMongoQuery`, where it said `match()` did. + - A comment inside `FILTER_TEXT_CASES` ships in `data/index.js` / `.mjs` and `browser/data/index.js` / `.mjs`. It now says the reference matcher measured case-exact until commit `8fec76a2b` retired it. - `@objectstack/service-analytics`: two comments in `ObjectQLStrategy`, which ship in the JavaScript output (the first also in `index.d.ts` / `index.d.cts`), changed. The first names `driver-memory`'s query path, not its matcher, as a face that pins `{$not: {}}` as the zero-row filter. The second says in the past tense that `memory-matcher.ts` read `$regex` as a real regex, until `$regex` was retired and commit `8fec76a2b` retired the matcher too. +- `@objectstack/formula`: the comment over the `$icontains` arm in `matches-filter.ts` ships in `index.js` / `index.mjs`. It now names objectql's `having` as the other caller of `asciiCaseInsensitiveContains`. It says `driver-memory`'s reference matcher called it until commit `8fec76a2b` retired it, and that `driver-memory`'s query path folds through `asciiCaseInsensitiveRegexSource`. +- `@objectstack/objectql`: the comment over the `having` walker's `$notContains` arm in `having-filter.ts` ships in `index.js` / `index.mjs` and `core.js` / `core.mjs`. It now says the record-at-a-time faces (`formula` and this walker) answer the predicate on a stored value that is not a string, as `driver-memory`'s reference matcher did until commit `8fec76a2b` retired it. Comment only: no export, type, error code, status, message text or runtime behaviour changes.