Skip to content

Commit 41a3c8d

Browse files
docs: comments outside driver-memory name what replaced the retired reference matcher (#20822 group 4) (#21395)
Fixes #20822 Clause-②: no #20822 group 4, the card's last group: the comments and docblocks outside `driver-memory` that still named its retired reference matcher (`memory-matcher.ts`, retired by commit `8fec76a2b`) as a live surface. This is the carry group 1's ACCEPT put on the card's last group PR. Claim: the PM's `Claim:` comment 5948997842 (branch `claude/issue-20822-retired-matcher-pointers`). Cross-lane declarations: spec seat post (5949027331) and services seat post (5949038856). **The seat confirms `Fixes` at ACCEPT.** Hypothesis H1 (the site list is complete outside `driver-memory`) is falsified: 28 more sites outside the claim's file surface still name the matcher as live (25 comments and docblocks, one JSON ledger note, and 2 string literals in code). They are not edited here. They are listed under "Sites outside the claim's surface" below, and the route is the open question in the `os-dev-report` on #20822. Base `11905a4f8b`; `origin/main` `db0cf2231b` merged once (merge `ff241ad71a`, no conflict, no file in this diff). Head `ff241ad71a`. Net diff against `main`: 10 files, +68 / -35. Not governed. ## What changes Comment and docblock prose only. A sentence that named the matcher as a live surface now names what carries the semantics today, measured per site, or says the matcher is retired. Historical sentences stay. | Site | Reading at base | Action | |---|---|---| | spec `filter-logic-conformance.ts:15` | live: the backend table's "In-memory matcher / `memory-matcher`" row | now "In-memory query path / `driver-memory` `normalizeFilterCondition`, then mingo", with the retirement in the same row (H3) | | spec `filter-comparand-shape.ts:127` | live: "the matcher's own answers ... are sealed behind this refusal" | past tense, plus the retirement | | spec `filter-comparand-shape.ts:142` and `:144` | live: "compares through JS coercion", "the matcher is not repaired" | past tense, plus the retirement | | service-analytics `objectql-strategy.ts:1687` | live: "`driver-memory`'s matcher ... pin" `{$not: {}}` | now `driver-memory`'s query path (`memory-driver-document-not.test.ts` pins it) | | service-analytics `objectql-strategy.ts:2055` (the unlock read it at `:2014`) | live: `memory-matcher.ts` "does" read `$regex` | past tense, until `$regex` and then the matcher were retired | | service-analytics `filter-normalizer-not-null-safe.test.ts:50` | live: points at the deleted `memory-matcher-not-null-safe.test.ts` | now `memory-driver-document-not.test.ts`, which holds its cells | | service-analytics `objectql-contains-canonical-operator.test.ts:31`, `:103`-`:110`, `:118`, `:305` | live: the mirror evaluates "the way `memory-matcher.ts` does", and "`driver-memory`'s `$regex` arm is deliberate and serves a real producer" | past tense; the producer's move to `$contains` is pointed at in `filter-refusal.ts`. `:118` and `:305` are in the same file but not in the unlock's list | | service-storage `attachment-read-visibility.test.ts:13` | live: "Mirrors `memory-matcher.ts` and `formula`'s `matches-filter.ts`" | now mirrors `formula`'s `matches-filter.ts`; the matcher is past tense | | service-storage `attachment-read-visibility.test.ts:326` | live: points at the deleted `memory-matcher-or-semantics.test.ts` | now `memory-driver-filter-logic-conformance.test.ts`, which holds its cases | | plugin-security `claim-seed-ownership.ts:91` | live, and wrong before the retirement: the `id IN (...)` scan attributed to `memory-matcher.ts` | now mingo's `$in`, which `InMemoryDriver` hands the list to (measured below) | | formula `matches-filter-not-null-safe.test.ts:17` | live: points at the deleted `memory-matcher-not-null-safe.test.ts` | now `memory-driver-document-not.test.ts` | | formula `matches-filter-not-null-safe.test.ts:120` | live: the matcher "answers the opposite" | the query path answers the same as this face (`memory-driver-document-not.test.ts` pins `['1']`); the matcher answered the opposite until PR #13356 and is retired | | `docs/design/predicate-compilation-convergence.md:44`, `:56`, `:358` | census rows anchored at `3711e0b763` | past tense plus the retirement, as PR #21336 did for the F7 row | Read and left as they are, because each is already historical or not a claim about a live matcher: spec `filter-logic-conformance.ts:184`, `:211` (a measurement table dated by its commits), `:244`, `:480`; `:492` names `memory-matcher-no-value-negated-operators.test.ts`, which still exists under that name and holds the live path's cells; `:503` and `:509` are string literals in the past tense; `filter-comparand-shape.ts:122`-`:124` (the reason for the 2026-08-31 ruling); formula `matches-filter-icontains.test.ts:91` ("what the reference matcher was moved onto"); the design doc's `:510` (the D6 decision row) and `:570` (a commit-table row). ## Measurements **H3, what `driver-memory` evaluates a filter with today.** `InMemoryDriver.find`, `count`, `updateMany`, `deleteMany` and the others call `convertToMongoQuery` (`memory-driver.ts:1421`). It runs `assertFilterConditionShape` (`filter-refusal.ts`), then `normalizeFilterCondition` (`memory-driver.ts:1600`), and hands the result to mingo's `Query`. `memory-driver-filter-logic-conformance.test.ts` runs `FILTER_LOGIC_CASES` through `InMemoryDriver.find`, and `check:driver-conformance` holds it. So the spec table's in-memory row names the query path. **`claim-seed-ownership.ts`'s `id IN (...)` sentence.** `normalizeFieldOperators`' `$in` arm (`memory-driver.ts:1862`) passes `$in` through. mingo 7.2.4's `$in` predicate (`operators/_predicates.js`) is built once per query and called once per document; each call runs `intersection([values, list])` (`util/_internal.js`), which fills a hash map from the whole list. So the sentence's "linear scan of the id list PER ROW" holds, through mingo, and the attribution to the matcher was wrong. **Code-token guard (PR #21357's two readings), base `11905a4f8b` against the working tree at head, TypeScript 6.0.3.** Reading 1 is the parser's leaf nodes from a `forEachChild` walk, so comments are trivia and JSDoc is never visited; a leaf that is not a token is re-scanned with trivia skipped. Reading 2 is the token stream from a `getChildren` walk, with JSDoc nodes skipped. Identifiers and string, template and numeric literals are compared in full. - Real run over all 8 touched `.ts` files: 26,645 base tokens (reading 2), **0 files with a token change** (exit 0). - Comment control ("invents no second one" to "invents NO second one", `objectql-strategy.ts`): 0 files changed (exit 0). - Positive control, an identifier (`filterNodeToCondition` to `filterNodeToConditionX`, `objectql-strategy.ts`): DIFFER on both readings (exit 1). - Positive control, a string literal (a `FILTER_LOGIC_CASES` `name` gains an `X`, `filter-logic-conformance.ts`): DIFFER on both readings (exit 1). - Positive control, a numeric literal (`MAX_BULK_PER_ROW_HOOK_ROWS / 2` to `/ 3`, `claim-seed-ownership.ts`): DIFFER on both readings (exit 1). Each mutation went through `scripts/ablation-replace.mjs` in wrap mode (anchor 1 to 0). Each restore was proven: blob equal to `HEAD` and `git diff HEAD` empty. **`dist` reach (H4), three legs plus a determinism leg.** The five packages' `dist` files were hashed after each build. Every build exited 0 and ran under the shared verify lock. All four legs ran at `3ba971f1b6`; the later commits change no file in the five packages. - Leg 1: a turbo build of the five packages and their closure (20 tasks, all five cache misses). - Leg 2: the 8 changed files of the five packages back at their base blobs (8 of 8 proven equal), then each package's own `build`. - Leg 3: the 8 files restored (8 of 8 equal to their `HEAD` blob, `git diff HEAD` empty), then the same five builds. - Leg 4: `@objectstack/spec`'s own `build` again. It equals leg 3 in all 230 files, so that build path is deterministic. | Package | Changed | Added non-test lines found verbatim in `dist` | Leg 2 against leg 3 | Changeset | |---|---|---|---|---| | `@objectstack/spec` | 2 src files | 1 of 8: the table row, in `data/index.d.ts` and `data/index.d.mts` | 34 files differ: `data/index.d.ts` / `.d.mts`, 30 source maps (line offsets), 2 build-input hashes | `patch` | | `@objectstack/service-analytics` | 1 src + 2 test files | 5 of 5, in `index.js` / `index.cjs` (one also in `index.d.ts` / `index.d.cts`) | 6 of 6 differ | `patch` | | `@objectstack/plugin-security` | 1 src file | 0 of 4 | only `index.js.map` and `index.mjs.map` differ: line offsets, because the docblock grew by two lines; the maps carry no `sourcesContent` | none | | `@objectstack/formula` | 1 test file | none | 0 differ | none | | `@objectstack/service-storage` | 1 test file | none | 0 differ | none | `.changeset/20822-retired-matcher-pointers.md` therefore declares `patch` for `@objectstack/spec` and `@objectstack/service-analytics`, comment text only, with `Clause-②: no`. Each changeset sentence maps to a diff line: the spec table row at `filter-logic-conformance.ts:15`, and the two `ObjectQLStrategy` comments at `objectql-strategy.ts:1687` and `:2055`. ## Gates and tests (head `ff241ad71a`) - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `ff241ad71a` (10 paths against merge base `db0cf2231`) derived 89 commands. All 89 ran, each exit code captured before any pipe. 87 exited 0 on the first run. `check:dual-build-cjs-loads` and `check:i18n` exited 3 (PREREQUISITE NOT MET: unbuilt workspace packages), not a measurement. Both exited 0 after a whole-workspace build (`turbo run build --filter=!@objectstack/docs`, 72 tasks, VERDICT command-exit 0). `--ran` reports "89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN" and exits 0. - **Tests, under the verify lock, `vitest run --maxWorkers=2`:** - spec `--project local`: 598 files, 17,529 passed, 1 todo; - spec `--project repo`: 48 files, 849 passed; - formula: 43 files, 1,257 passed; - service-analytics: 167 files, 3,786 passed, 83 skipped; - service-storage: 41 files, 629 passed; - plugin-security: 159 files, 3,479 passed, 23 skipped. - **Typecheck:** `pnpm --filter ... typecheck` exits 0 for spec (with `check:scripts-typecheck` and `check:test-typecheck`), formula, service-analytics, service-storage and plugin-security. Formula, service-storage and plugin-security also run `check:test-typecheck`, and service-analytics' `tsc --listFiles` program holds 164 of its `__tests__` files, both touched ones included. - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 8 touched `.ts` files plus `service-analytics/dist/index.js` as a control gives 9 results, 0 errors and 1 warning: the control's ignore notice. None of the 8 is reported ignored, and each resolves under `--print-config`. `eslint.config.mjs` never enables type-aware linting (its lines 327-328 say so), so a comment edit cannot move the verdict on an untouched file. The repo-wide `pnpm lint` is CI's run. - **Bytes:** `pnpm check:nul-bytes` exits 0. A control-byte scan over the 10 changed files finds none. ## Sites outside the claim's surface (round 0; superseded by patch round 1 below) Read at base `11905a4f8b` with every spelling: `memory-matcher`, `reference matcher`, `in-memory matcher`, `memory matcher`, `match()`, the `memory-matcher-*` test-file names, and "`driver-memory`'s matcher". `driver-memory`, `CHANGELOG.md` and `content/docs/releases/**` are excluded. Each of these still names the matcher as a live surface: - `service-analytics` `read-scope-not-null-safe.test.ts:43`: points at the deleted `memory-matcher-not-null-safe.test.ts`. It is on group 1's list and inside the ACCEPT's "service-analytics test docblocks", but not in the unlock's list or the claim. - `driver-mongodb` `mongodb-filter.ts:1151` ("it is the oracle both drivers agree with"). - `driver-turso` `remote-transport-boolean-identity.test.ts:43` and `remote-transport-not-operator.test.ts:40`. - `formula` `matches-filter.ts:729` (`asciiCaseInsensitiveContains` is "the same one `driver-memory`'s matcher ... call[s]"). - `objectql`: - `having-filter.ts:23`, `:26` and `:2064`; - `having-filter.test.ts:8` and `:60`; - `number-comparand-declared-type-door.ts:46` ("the memory matcher compares"; which face it means is ambiguous); - `validation/record-validator.ts:533` ("five hand-rolled shape tests", one of them the matcher); - `tsconfig.test.json:22` and `test-typecheck-debt.json:3` (a JSON string). - `plugin-security`: - `bootstrap-declared-capabilities.test.ts:39` and `bootstrap-system-capabilities.test.ts:29`; - `rls-check-stored-form.ts:40`, written after the retirement, so it probably means the query path. - `service-analytics` `strategies/filter-normalizer.ts:450` ("the in-memory matcher ... already held to" the table). - `spec`: - `filter.zod.ts:411`, `:940` and `:3111` (live implementation-status tables), `:1198`, `:1242` (the exported `asciiCaseInsensitiveContains` docblock) and `:3142` (the `$empty` table); - `filter-text-conformance.ts:342` ("both then and now"); - `ui/view.zod.ts:605` ("`match()` runs `assertFilterConditionShape`"). - **String literals**, outside this group's form: - spec `filter.zod.ts:470`, the author-facing refusal for a `null` ordering comparand. Measured on the published schema door: `FieldOperatorsSchema.safeParse({ $gt: null })` answers "... its reference matcher compares through JS coercion ...", in the present tense. - spec `filter-operator-vocabulary.test.ts:86`, an assertion message that prescribes editing the reference matcher. The governed `.claude/skills/pm-dispatch/references/compile-surfaces.md:16` stays on the seat post's protocol observation, as the ACCEPT placed it. 34 more hits outside the surface are historical (past tense, dated measurements, or string literals in the past tense). The `os-dev-report` on #20822 lists them. ## Patch round 1 (head `54c8e70e88`) Section added by the `domain:engine#1` seat, from the dev's patch-round report (5952834398 on #20822). - **The seat's answer to round 0's open question:** A, minus the two string literals (claim amendment 5950842658). The round corrects the 26 comment, docblock and ledger-note sites listed there, under the same rule. - `read-scope-not-null-safe.test.ts:43` comes first, in its own commit `68ca26224f`. It is the site inside group 1's ACCEPT carry that the seat's unlock had dropped, so **every site that ACCEPT carried is now corrected, and `Fixes #20822` stands.** - The added surfaces are declared on the spec (5950854566) and services (5950863357) seat posts. - **Not edited:** - the two string literals `filter.zod.ts:470` (the author-facing null-ordering refusal) and `filter-operator-vocabulary.test.ts:86` (an assertion message). They are #21397's (spec lane), so this PR stays comment-only; - `filter.zod.ts:3106` ("that driver's matcher", generic staging reasoning with an ambiguous referent); - the governed `compile-surfaces.md`. - **Commits:** - `1118eebbcd` merges `origin/main` `56238d890d` once, with no conflict. #21372's hunks moved no listed line; - then `68ca26224f`, `1bf9b24f26`, `57d675df10`, and `54c8e70e88` (the changeset). The net diff against `main` is 28 files, +152/−81, not governed. - **Measured per site:** each rewritten sentence names what carries the semantics today, or says commit `8fec76a2b` retired the matcher. The probes behind the sentences that state a behaviour: - mingo's string ordering for `filter.zod.ts:411`; - mingo over the declared-number table for `number-comparand-declared-type-door.ts:46`; - `InMemoryDriver`'s null-or-missing match for the two `plugin-security` bootstrap tests; - the callers of `asciiCaseInsensitiveContains`. Historical sentences are untouched. - **Code-token guard** (both readings, base = merge base `56238d890`, so it covers the whole PR): - all 24 touched `.ts` files: **0 files changed**; - the two JSON files through `ts.parseJsonText`, with `test-typecheck-debt.json`'s `_note` masked: 0 changed. That `_note` is the text this round edits by design; an `entries` value control still DIFFERS under the mask; - controls (identifier, string, numeric, and JSON value) each DIFFER. - **`dist` reach** (three legs; the legs agree byte for byte in 276 of 276 files): | package | rewritten lines in `dist` | entry | |---|---|---| | `@objectstack/spec` | 12 of 21: 9 in the filter declaration chunk and 3 in the data bundles; `filter.zod.ts` and `view.zod.ts` also ship as source | `patch`, extended | | `@objectstack/formula` | 3 of 3 | `patch`, added | | `@objectstack/objectql` | 3 of 19 | `patch`, added | | `@objectstack/service-analytics` | its round-0 entry stands; this round's line reaches only the source maps | unchanged | | `driver-mongodb`, `plugin-security` | maps only, or nothing | none | | `driver-turso` | test files only | none | - **Tests and typecheck** at `54c8e70e88`: - the suites of `spec`, `formula`, `objectql`, `driver-turso`, `driver-mongodb`, `plugin-security` and `service-analytics` are green. `driver-mongodb`'s real-mongod suites are not measured here: there is no `mongod`; - every touched package's typecheck exits 0; - gates: 95 derived, 95 run, 0 not measured. ## Acceptance notes - **Build path, not this diff.** Leg 1 (turbo) and leg 3 (each package's own `build`) are both at the same text. They differ in 14 `@objectstack/spec` declaration files (`api`, `automation`, `contracts`, `marketplace`, `system` and two `node-executor.zod` chunks, `.d.ts` / `.d.mts`). Leg 3 equals leg 4 byte for byte, so each path is deterministic and the rewrite's effect was read on legs 2 and 3, which share a path. The difference between the paths is not explained here. - **`main` moved after the merge.** Seven more commits landed after `db0cf2231b` (to `69a12a0952` when this was written); none touches a file here. The derivation's only stale input among them is `scripts/sdui-manifest.record.json`. CI judges the merge ref. - **Contract review** is owed at tier: the path limb is `packages/spec/src/**`, non-test. The seat runs it. --- _Generated by [Claude Code](https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fa7b565 commit 41a3c8d

28 files changed

Lines changed: 152 additions & 81 deletions
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/service-analytics': patch
4+
'@objectstack/formula': patch
5+
'@objectstack/objectql': patch
6+
---
7+
8+
Published comments that named `driver-memory`'s retired reference matcher as a live filter backend now name what replaced it
9+
10+
Clause-②: no
11+
12+
`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:
13+
14+
- `@objectstack/spec`:
15+
- 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.
16+
- `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`).
17+
- `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.
18+
- 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.
19+
- `@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.
20+
- `@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`.
21+
- `@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.
22+
23+
Comment only: no export, type, error code, status, message text or runtime behaviour changes.

‎docs/design/predicate-compilation-convergence.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Vocabulary is counted against the 19 field operators an author can write. That i
4141
| 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` |
4242
| 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` |
4343
| 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) |
44-
| 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 |
44+
| 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 |
4545
| 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 |
4646
| 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) |
4747
| 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
5353
| 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) | — |
5454
| 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`) |
5555

56-
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".
56+
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".
5757

5858
### 1.2 Against the card's table
5959

@@ -355,7 +355,7 @@ A ruling that adds a new predicate *kind* or a dialect construct still costs one
355355
| 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 |
356356
| 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 |
357357
| F3 `driver-memory` query | the engine seam | no | whole-day calls (8 sites) | memory filter-logic, temporal, text | — |
358-
| F4 reference matcher | — (no production caller) | no | keep as test oracle, or retire (D6) | 20 test files | — |
358+
| 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 | — |
359359
| 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 |
360360
| 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 |
361361
| 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 | — |

‎packages/drivers/driver-mongodb/src/mongodb-filter.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,8 +1148,10 @@ interface LoweredWrite {
11481148
* write becomes its own `$and` branch on the same field, where both constraints
11491149
* survive. That is exactly the guard #13195 landed for `$exists` alone,
11501150
* generalised to every writer rather than restated once per operator.
1151-
* `driver-memory`'s reference matcher loops the operators and therefore cannot
1152-
* express this defect at all; it is the oracle both drivers agree with.
1151+
* `driver-memory`'s reference matcher looped the operators and therefore could
1152+
* not express this defect at all; it was the oracle both drivers agreed with
1153+
* until commit `8fec76a2b` retired it, and `driver-memory`'s
1154+
* `memory-operator-key-clobber.test.ts` keeps its answers as literal row sets.
11531155
*
11541156
* ## Why rank, and not author order
11551157
*

‎packages/drivers/driver-turso/src/remote-transport-boolean-identity.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,11 @@ import { markFilterSubtreeProvenance } from '@objectstack/spec/data';
4040
* them correctly rather than refuse them. Framework's `matchesFilterCondition`
4141
* (`packages/formula/src/matches-filter.ts`) already evaluates them this way and
4242
* pins it — `expect(m(rec, { $or: [] })).toBe(false) // empty OR matches
43-
* nothing` — as does `driver-memory`'s matcher (`.some()` over an empty array).
44-
* These tests hold the remote transport to the same table.
43+
* nothing` — as does `driver-memory`'s query path, which runs
44+
* `FILTER_LOGIC_CASES`' "empty $or is FALSE" case
45+
* (`memory-driver-filter-logic-conformance.test.ts`); its reference matcher
46+
* (`.some()` over an empty array) answered the same until commit `8fec76a2b`
47+
* retired it. These tests hold the remote transport to the same table.
4548
*
4649
* The other half of the fix is that "compiles to nothing" now has exactly ONE
4750
* cause. An element that is not a filter NODE (null, a scalar, an array, a

‎packages/drivers/driver-turso/src/remote-transport-not-operator.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ import { lowerFilterCondition, markFilterSubtreeProvenance } from '@objectstack/
3737
* — declared in the same object literal as the two that WERE implemented. And
3838
* it is a shape real rules produce: `SqlDriver.applyFilterCondition` compiles it
3939
* with `whereNot`/`orWhereNot` (framework#2704, added to close this same
40-
* silent-filter-bypass family), `driver-memory`'s matcher and
40+
* silent-filter-bypass family), `driver-memory`'s query path
41+
* (`memory-driver-document-not.test.ts`; its reference matcher did too until
42+
* commit `8fec76a2b` retired it) and
4143
* `matchesFilterCondition` both evaluate it, and CEL `!expr` in a permission /
4244
* RLS read scope lowers to `{ $not: {…} }` (`formula/src/cel-to-filter.ts`). So
4345
* one RLS scope answered correctly on a local SqlDriver and broke on Turso

‎packages/formula/src/matches-filter-not-null-safe.test.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
*
1515
* These cases are therefore a PIN on the reference behaviour, mirrored id-for-id
1616
* by `driver-sql`'s `sql-driver-not-null-safe.test.ts` and `driver-memory`'s
17-
* `memory-matcher-not-null-safe.test.ts`. Moving an expectation here silently
18-
* re-opens the divergence.
17+
* `memory-driver-document-not.test.ts` (its query path; it holds the cells of
18+
* `memory-matcher-not-null-safe.test.ts`, deleted with the reference matcher in
19+
* commit `8fec76a2b`). Moving an expectation here silently re-opens the
20+
* divergence.
1921
*
2022
* `cel-to-filter.ts` is why this matters in practice: a CEL `!expr` in a
2123
* permission rule lowers to exactly these `$not` shapes.
@@ -116,8 +118,10 @@ describe('[#5146] matchesFilterCondition — $not over records with no value', (
116118

117119
it('$not of $notContains does NOT match them — the mirror case', () => {
118120
// A value-less field satisfies `$notContains` here, so the negation
119-
// rejects it. `driver-sql` follows this answer; `driver-memory`'s
120-
// REFERENCE matcher answers the opposite for a null-valued field.
121+
// rejects it. `driver-sql` follows this answer, and so does
122+
// `driver-memory`'s query path (`memory-driver-document-not.test.ts`).
123+
// Its REFERENCE matcher answered the opposite for a null-valued field
124+
// until PR #13356, and commit `8fec76a2b` has since retired it.
121125
//
122126
// ⚠️ [#5299, 2026-08-10] A ruling that morning would have reversed this
123127
// direction; it was WITHDRAWN the same day and include re-affirmed. See

‎packages/formula/src/matches-filter.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,9 @@ function evalOp(
726726
/**
727727
* [#6520] `$contains`' case-INSENSITIVE twin, folding ASCII case and nothing
728728
* else — `asciiCaseInsensitiveContains` is the spec's shared definition, the
729-
* same one `driver-memory`'s matcher and objectql's `having` call.
729+
* same one objectql's `having` calls. `driver-memory`'s reference matcher
730+
* called it too until commit `8fec76a2b` retired it; `driver-memory`'s
731+
* query path folds through its pattern twin, `asciiCaseInsensitiveRegexSource`.
730732
*
731733
* NOT `actual.toLowerCase().includes(v.toLowerCase())`, which is the obvious
732734
* line and the wrong one: it folds the whole Unicode range, so an RLS

‎packages/objectql/src/having-filter.test.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
*
66
* The namespace is the aggregated row's own columns (aggregation aliases +
77
* groupBy projections); operator semantics follow the Filter Protocol, with two
8-
* deliberate divergences from driver-memory's matcher: an unknown operator
8+
* deliberate divergences from driver-memory's reference matcher (retired since by
9+
* commit `8fec76a2b`): an unknown operator
910
* throws — ignoring one would silently return unfiltered aggregates, the exact
1011
* silently-inert failure (#4286, ADR-0078) enforcement exists to end — and the
1112
* negation-carrying operators are NULL-safe per #5298 (see the grid at the
@@ -57,7 +58,7 @@ describe('applyHaving', () => {
5758
expect(applyHaving(ROWS, { total: { $between: [600, 1300] } }).map((r) => r.customer_id))
5859
.toEqual(['c2', 'c3']);
5960
expect(applyHaving(ROWS, { region: { $null: true } }).map((r) => r.customer_id))
60-
.toEqual(['c1', 'c2', 'c3']); // absent folds into null, like the memory matcher
61+
.toEqual(['c1', 'c2', 'c3']); // absent folds into null, as driver-memory's query path reads `$null`
6162
});
6263

6364
it('multiple keys on one condition AND together, like `where`', () => {

‎packages/objectql/src/having-filter.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@
2020
// ordinary FilterCondition over those columns: implicit equality, the
2121
// comparison / set / null / existence / string operators, and `$and` / `$or` /
2222
// `$not` composition. Operator semantics follow the Filter Protocol, with TWO
23-
// deliberate divergences from driver-memory's matcher — the face this module
24-
// was originally written against:
23+
// deliberate divergences from driver-memory's reference matcher — the face this
24+
// module was originally written against, which commit `8fec76a2b` retired:
2525
//
26-
// 1. AN UNKNOWN OPERATOR THROWS. The memory matcher ignores operators it does
27-
// not know; here an ignored operator would silently return UNFILTERED
26+
// 1. AN UNKNOWN OPERATOR THROWS. The memory matcher ignored operators it did
27+
// not know when this module was written; here an ignored operator would silently return UNFILTERED
2828
// aggregates — the precise failure mode (#4286, ADR-0078) this module exists
2929
// to end. The rejection names the operator and the supported set.
3030
//
@@ -2061,7 +2061,9 @@ function checkCondition(
20612061
// at query time (SQLSTATE 42883), and `driver-memory`'s reference
20622062
// matcher failed both polarities. The maintainer ruled the cell on
20632063
// 2026-09-05 (option A, type-gate) and `FILTER_TEXT_CASES`' `score` rows
2064-
// pin it on every face: the reference matcher answers the predicate, and
2064+
// pin it on every face: the record-at-a-time faces (`formula`, this
2065+
// walker) answer the predicate, as `driver-memory`'s reference matcher did
2066+
// until commit `8fec76a2b` retired it, and
20652067
// the SQL compilers emit a type-gated constant for a column whose
20662068
// declared type is in `NON_TEXT_STORED_VALUE_TYPES`. This arm was already
20672069
// on the ruled side; nothing here moved.

‎packages/objectql/src/number-comparand-declared-type-door.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@
4343
* | `having` on `sum(amount)`: `$gt "abc"` | 200, no group | 200, no group | 200, no group |
4444
*
4545
* One client mistake, three answers, one of them a server fault; and a numeric
46-
* string read two ways (the memory matcher compares `12 > "12"` without
47-
* coercing it, the SQL backends bind it with numeric affinity or input).
46+
* string read two ways (`InMemoryDriver`'s query path hands the comparison to
47+
* mingo, which compares `12 > "12"` without coercing it; the SQL backends bind
48+
* it with numeric affinity or input).
4849
*
4950
* ## The door's two answers
5051
*

0 commit comments

Comments
 (0)