Commit 8fec76a
Part of #20822
Clause-②: no
#5930 step 4, group 1 of 3 (`driver-memory`), under ruling 5902355785
(D4 (b), D6) and ADR-0053 D-D1 as amended. **Two of the three faces land
here: F5 and F4. F3 is stopped and reported.** Its deletion moves
answers on a production direct caller and on engine-seamed reads,
measured below. The card says "If routing it changes an answer, that
face stops and is reported". This PR does not touch `memory-driver.ts`'s
four whole-day sites.
## What lands
| Face | Change | Typed column reader |
|:--|:--|:--|
| **F5** `MemoryAnalyticsService` (cube face) | `lteUpperBound` (the
filter copy, 2 of the design's 5 call sites) is deleted. The `lte` row
on both exits (mingo `$match` and SQL echo) now compiles the comparison
it is handed. The `storageForm` hand-over that existed only for that
derived bound is deleted too. | **None, by design.** The step-3 door
(`normalizeFilters`) calls `lowerFilterCondition` with no
`isDatetimeColumn`, so rules 1 and 2 apply type-blind on every column.
That is the reading the deleted copy gave, so no bound is lost. |
| F5, `dateRange` window | **Kept** (the other 3 call sites). It is not
a `where`. The lowering never sees it (ADR-0053 D-D1 item 8, review
5903065983 item 8), and removing it would drop the explicit-end whole
day. | n/a (a window, not a filter) |
| **F4** reference matcher | `memory-matcher.ts` is deleted (ruling D6).
`match()` had no production caller and was never exported. The one
production import from the module, `getValueByPath` in
`memory-driver.ts`, moves into `memory-driver.ts` byte for byte. The 21
importing test files keep every assertion; the table below lists where
each one lives now. | No `datetime` column to bound. The matcher's
`$lte` compared raw and applied no whole-day rule. |
The changeset is `.changeset/20822-driver-memory-face-copies.md`,
`@objectstack/driver-memory` **patch**. **Measured: retiring `match`
removes no export.** The built `dist/index.d.ts` export list has the
same 33 names before and after. `function match(` was never in `dist`:
`index.ts` does not reach it, so the bundler dropped it. The positive
control is `function getValueByPath`, which is present in both builds.
## Why F3 stops — measured before and after its deletion
The four sites were measured with the deletion applied (commit
`6df6dcfab1`, never pushed; patch kept), against built `dist`, through
`examples/embed-objectql`'s dependency graph. Rows: `at` =
`2026-07-27T10Z` / `07-28T00Z` / `07-28T10Z` / `07-29T10Z` / null;
`note` (text) = `2026-07-27` / `2026-07-28` / `2026-07-28 late` / `zzz`
/ null.
**Engine (the seamed path), `engine.find`; `engine.count` agrees:**
| Column the filter names | `$lte '2026-07-28'` before → after | `$lte
'9999-12-31'` before → after |
|:--|:--|:--|
| declared `datetime` | r1,r2,r3 → r1,r2,r3 (unchanged) | r1..r4 →
r1..r4 (unchanged) |
| declared `date` | r1,r2,r3 → r1,r2,r3 (unchanged) | unchanged |
| declared `text` holding ISO text | r1,r2,r3 → **r1,r2** | r1..r4 →
**r1,r2,r3** |
| column not declared on a registered object | r1,r2,r3 → **r1** | — |
| object not in the registry | r1,r2,r3 → **r1** | — |
The seam's reader is `declaredDatetimeLowering` in `engine.ts`. It
lowers only a declared `datetime`. An object with no field map reads no
column as `datetime`, and its comment says the rule is then "left to the
faces, as it was". ADR-0053 D-D1 item 7 says a seam that cannot read the
declared type "applies the rewrite type-blind". Deleting F3 exposes that
gap.
**Direct callers (no seam):**
- **`@objectstack/metadata` `DatabaseLoader.queryHistory({ until })`.**
It builds `recorded_at: { $lte: until }` and calls the driver directly
whenever the loader holds a driver and no engine (`setDatabaseDriver`).
Probe: two history rows recorded today, then `until` set to today's bare
day. Before: 2 rows. After: **0 rows**. `since` and `until` both set to
today: 2 → **0**.
- The face's own suites call the driver directly. With the four sites
deleted, **43 tests go red**: `memory-temporal-conformance` 25,
`memory-driver-calendar-day-upper-bound` 5,
`memory-analytics-20661-lte-whole-day-first` 5,
`memory-datetime-storage` 4, `memory-temporal-storage-form` 2 and
`memory-analytics-shared-lowering-door` 2. So H2 ("dead on the seamed
path, suites green") is falsified as stated.
- In-repo production direct callers grepped for an upper-bound filter:
`packages/cli` (`secret-reference-union`, `migrate/duplicates`,
`secret/orphans`, `storage-driver`), `metadata-protocol` migrations,
`runtime`, `plugin-dev` and `examples/embed-objectql` have **0**.
`plugin-auth`'s adapter has `$lte` but goes through the engine.
`metadata`'s `queryHistory` is the one hit, measured above.
Options and a recommendation are in the report on #20822
(`open_questions`).
## F4 — the 21 importing test files, and where each assertion lives
| File | Disposition | The matcher's assertions now live in |
|:--|:--|:--|
| `memory-matcher-or-semantics.test.ts` (36) | deleted |
`memory-driver-filter-logic-conformance.test.ts`, the
`InMemoryDriver.find` describe: one `it` per `FILTER_LOGIC_CASES` case,
same names, same `expected`, plus a whole-table sweep |
| `memory-matcher-not-null-safe.test.ts` (17) | deleted |
`memory-driver-document-not.test.ts`. 15 of the cells were already
asserted there, live, same fixture, both readings. The `$not $nin` (both
readings), `$not $exists` and `$exists` = `$null: false` cells were
added, and `$not $notContains` now runs on both readings |
| `memory-driver-filter-logic-conformance.test.ts` | edited | the
live-vs-matcher sweep became live vs the table's `expected`, in one
assertion |
| `memory-20041-like-nul-pattern.test.ts` | edited | the matcher's
refusals → `assertFilterConditionShape` called directly (the gate
`match()` ran); the controls → live vs `formula`'s rows |
| `memory-20143-like-code-point.test.ts` | edited | the matcher door →
spec `matchesLikePattern` (what `match()` evaluated) |
| `memory-20444-empty-operator.test.ts` | edited | the leaf by-value
cells → spec `isEmptyFilterValue`. The combinator by-value half →
`formula`'s `matches-filter-empty-operator.test.ts` (same rows, same
answers). Non-boolean refusal → the gate, plus a live `$or` identity
case |
| `memory-driver-document-not.test.ts` | edited | the live-vs-reference
columns → literal expectations on the live path |
| `memory-empty-field-constraint.test.ts` | edited | refusals → the
gate; record-independence → the live path over one-row and empty tables;
row answers and the nested-object comparison → the live path |
| `memory-exists-has-value-faces.test.ts` | edited | the matcher exit →
its literal answers (the ruling's), held on the live path; the composed
cells' oracle → the literals measured on the matcher at `9905e61ca2`;
the analytics face is checked against both |
| `memory-filter-text-conformance.test.ts` | edited | the 19 per-case
matcher `it`s → the query path's per-case `it`s (same names, same
`expected`) plus a one-sweep assertion; refusals → the gate; the #14079
cell → the live path |
| `memory-filter-vocabulary-refusal.test.ts` | edited | refusals → the
gate (same message as the live path); record-independence → the live
path over one-row and empty tables |
| `memory-icontains.test.ts` | edited | → spec
`asciiCaseInsensitiveContains`; the `$contains` case-exact pin → the
live path; refusal → the gate |
| `memory-like-pattern.test.ts` | edited | the agreement check → spec
`matchesLikePattern` for `$like` / `$ilike`; the `$contains` control →
the live path |
| `memory-matcher-array-and-date-comparand.test.ts` | edited, name kept
| every cell → the live path. The Date cells use a declared `datetime`
column (the case they were written for); the array refusals go through
the live path's gate |
| `memory-matcher-no-value-negated-operators.test.ts` | edited, name
kept (`spec`'s `filter-logic-conformance.ts` points at it) | every cell
→ the live path, both readings |
| `memory-matcher-null-value-and-comparand.test.ts` | edited, name kept
| the matcher half of each cell dropped (the live half was already
literal); the matcher-only comparisons → the live path |
| `memory-matcher-scalar-comparand-array-value.test.ts` | edited, name
kept | 7 per-case matcher `it`s → the live per-case `it`s plus a
whole-matrix sweep; the card's three rows, OR-over-elements and `$ne`'s
per-row complement → the live path, a row at a time |
| `memory-null-comparand-refusal.test.ts` | edited | refusals → the
gate; answers → the live path. **`$exists: 'yes'` is the one divergence
found**: the matcher answered `['1']` (it read the flag by truthiness),
the live path answers `['2']` (`val === true` → `$ne: null`, anything
else → `$eq: null`). The live answer is pinned and the divergence
reported |
| `memory-null-list-member-unreachable.test.ts` | edited | the
compile-then-`match` pipeline → compile-then-`find` |
| `memory-null-ordering-comparand-unreachable.test.ts` | edited | the
same |
| `memory-operator-key-clobber.test.ts` | edited | the matcher lines
dropped; each cell's literal was already asserted on the live path |
Counts: the 21 files go from 560 to 483 `it`s, and the package from 1490
to 1413. The 77 are 62 duplicate per-case `it`s (36 + 19 + 7), each
still asserted by case name on the live path with the same `expected`,
plus the 17 not-null-safe `it`s folded into `document-not` (+2 there).
## Hypotheses (dispatch zone 2)
- **H1** held for F5 (reached only through step 3's door) and was
falsified for F3 (see above).
- **H2** was falsified. F3's sites are dead only for declared `datetime`
columns and are equivalent on `date`. They are alive for text,
undeclared and unregistered columns on the engine path, for every direct
caller, and for 43 of the face's own tests.
- **H3** held. Each F5 filter site was ablated on its own at
`9905e61ca2`: 1490 of 1490 green, twice. The window arm stays.
- **H4** held, with two corrections: 21 importing test files, not 20;
and the module had one production import (`getValueByPath`), which
moved. Every mention outside `driver-memory` is a comment, not an
import.
- **H5**: stale comments are listed under Acceptance notes. Nothing
outside `driver-memory` is edited.
## Tests and gates (at `94616a955f`, after merging `origin/main`
`4edb61449b`)
- `pnpm --filter @objectstack/driver-memory exec vitest run
--maxWorkers=2`: **1413 passed / 0 failed**, 65 files.
- `pnpm --filter @objectstack/driver-memory typecheck` (`tsc --noEmit &&
tsc --noEmit -p tsconfig.typecheck.json`): exit 0. `--listFiles` shows
all 65 test files in the program.
- `node scripts/pm/dispatch-gates.mjs --commands` derived 60 families,
all 60 run. `--ran` with exit codes: "60 derived, 58 run, 2
NOT-MEASURED, 0 UNRUN". That includes `check:driver-conformance` (OK, 50
covered cells, 0 DEBT), `check:driver-memory-census` (OK),
`check:engine-double-contract`, `check:nul-bytes`,
`check:published-files`, `check:adr-0087-registration`,
`check:changeset-no-major` and `check:empty-changeset`, all exit 0.
- **NOT MEASURED:** `check:dual-build-cjs-loads` (exit 3, PREREQUISITE
NOT MET: it reads every workspace package's `dist`). Narrowed probe:
`driver-memory`'s CJS entry loads, with 23 runtime exports. **NOT
MEASURED:** `check:type-check-debt` (exit 3, PREREQUISITE NOT MET: 16
unbuilt dependencies of ledgered packages). `driver-memory` has no
ledger row, and its own typecheck is green.
- Lint, narrowed: `eslint --no-inline-config --format json` over the 22
changed `driver-memory` files: 22 files, 0 errors, 0 warnings. All 22
are inside the config (`--print-config` reads each). The config enables
no type-aware linting (`eslint.config.mjs` states it has no
`parserOptions.project`), so this diff cannot move a verdict on an
untouched file. The full `pnpm lint` is CI's.
- Consumers: the public surface is byte-identical (33 exports), so no
downstream suites are owed.
## Ablations
Every leg below went through `scripts/ablation-replace.mjs` (anchor hit
on disk, restore proven by blob == HEAD).
- F5 mongo `lte` row replaced by a plain `$lte` at `9905e61ca2`:
1490/1490 green. The same for the F5 SQL echo `lte` row: 1490/1490
green.
- Control, after the deletion: the door's
`lowerFilterCondition(admitted)` removed → **14 red**, including the
20661 whole-day cells, the echo's half-open `$lte`, `9999-12-31` and the
shared-lowering door. The bound now lives in the door.
- F4, the moved row assertions bind the live path: the live `$exists`
arm reverted to mingo key presence → **19 red** across document-not,
exists-has-value, null-value-and-comparand, null-comparand-refusal,
operator-key-clobber and filter-logic.
- F4, the moved refusals bind the gate: the gate's `$null` non-boolean
check disabled → **9 red** (the gate-direct cells in
null-comparand-refusal).
- F3 metadata-history leg: patch applied, `driver-memory` rebuilt, then
restored and rebuilt. The `dist` preflight shows the marker back and the
tree clean, and the probe reads 2 again.
## Acceptance notes
- **Stale comments outside `driver-memory` that name the retired
matcher** (not edited: the spec lane's or another package's;
`.claude/**` is governed):
- `packages/spec/src/data/filter-logic-conformance.ts:15`: the backend
table's "In-memory matcher / `memory-matcher`" row. It ships in
`dist/data/index.d.ts`. It should read `InMemoryDriver.find` (mingo).
- `packages/formula/src/matches-filter-not-null-safe.test.ts:17`,
`service-analytics` `filter-normalizer-not-null-safe.test.ts:50` and
`read-scope-not-null-safe.test.ts:43` point at the deleted
`memory-matcher-not-null-safe.test.ts`. The cells now live in
`memory-driver-document-not.test.ts`.
- `service-storage` `attachment-read-visibility.test.ts:13` and `:326`
cite `memory-matcher.ts` and `memory-matcher-or-semantics.test.ts`.
- `plugin-security` `claim-seed-ownership.ts:91` attributes
`InMemoryDriver`'s `id IN (…)` scan to `memory-matcher.ts`. It was
always mingo's, and this was already wrong before this PR.
- `service-analytics` `objectql-strategy.ts:1929` and
`objectql-contains-canonical-operator.test.ts` describe the matcher's
`$regex` arm in the present tense (that arm was retired by #5702).
- `.claude/skills/pm-dispatch/references/compile-surfaces.md:16` names
`memory-matcher.ts:134` as a live surface.
- `docs/design/predicate-compilation-convergence.md` F4 row.
- `spec` `filter-logic-conformance.ts:492` still points at a live file,
which kept its name for it.
- Runtime refusal texts in `filter-refusal.ts` (`malformedBetweenError`,
`nonBooleanNullComparandError`, `arrayComparandError`) mention the
reference matcher in the past tense. They stay true and are untouched.
- Four migrated files keep their `memory-matcher-` names so that no
pointer dangles. Each header says so.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent da93a8b commit 8fec76a
26 files changed
Lines changed: 865 additions & 1642 deletions
File tree
- .changeset
- packages/drivers/driver-memory/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
13 | | - | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
319 | 320 | | |
320 | 321 | | |
321 | 322 | | |
322 | | - | |
323 | | - | |
| 323 | + | |
| 324 | + | |
324 | 325 | | |
325 | 326 | | |
326 | 327 | | |
| |||
350 | 351 | | |
351 | 352 | | |
352 | 353 | | |
353 | | - | |
354 | | - | |
| 354 | + | |
| 355 | + | |
355 | 356 | | |
356 | 357 | | |
357 | 358 | | |
358 | 359 | | |
359 | 360 | | |
360 | | - | |
361 | | - | |
| 361 | + | |
| 362 | + | |
362 | 363 | | |
363 | 364 | | |
364 | 365 | | |
365 | 366 | | |
366 | 367 | | |
367 | 368 | | |
368 | | - | |
| 369 | + | |
369 | 370 | | |
370 | 371 | | |
371 | 372 | | |
| |||
409 | 410 | | |
410 | 411 | | |
411 | 412 | | |
412 | | - | |
413 | | - | |
414 | | - | |
415 | | - | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
416 | 417 | | |
417 | 418 | | |
418 | 419 | | |
| |||
638 | 639 | | |
639 | 640 | | |
640 | 641 | | |
641 | | - | |
642 | | - | |
643 | | - | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
644 | 646 | | |
645 | 647 | | |
646 | 648 | | |
| |||
804 | 806 | | |
805 | 807 | | |
806 | 808 | | |
807 | | - | |
| 809 | + | |
808 | 810 | | |
809 | 811 | | |
810 | 812 | | |
| |||
845 | 847 | | |
846 | 848 | | |
847 | 849 | | |
848 | | - | |
849 | | - | |
| 850 | + | |
| 851 | + | |
850 | 852 | | |
851 | 853 | | |
852 | 854 | | |
| |||
Lines changed: 17 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
24 | 27 | | |
25 | 28 | | |
26 | 29 | | |
27 | 30 | | |
28 | | - | |
| 31 | + | |
29 | 32 | | |
30 | 33 | | |
31 | 34 | | |
| |||
81 | 84 | | |
82 | 85 | | |
83 | 86 | | |
84 | | - | |
| 87 | + | |
| 88 | + | |
85 | 89 | | |
86 | | - | |
| 90 | + | |
87 | 91 | | |
88 | 92 | | |
89 | 93 | | |
90 | | - | |
| 94 | + | |
91 | 95 | | |
92 | 96 | | |
93 | 97 | | |
| |||
120 | 124 | | |
121 | 125 | | |
122 | 126 | | |
123 | | - | |
124 | | - | |
| 127 | + | |
| 128 | + | |
125 | 129 | | |
126 | 130 | | |
127 | 131 | | |
| |||
130 | 134 | | |
131 | 135 | | |
132 | 136 | | |
133 | | - | |
| 137 | + | |
134 | 138 | | |
135 | 139 | | |
136 | 140 | | |
| |||
141 | 145 | | |
142 | 146 | | |
143 | 147 | | |
144 | | - | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
145 | 152 | | |
146 | 153 | | |
147 | 154 | | |
148 | | - | |
149 | | - | |
| 155 | + | |
150 | 156 | | |
151 | 157 | | |
152 | 158 | | |
Lines changed: 12 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
21 | 26 | | |
22 | 27 | | |
23 | 28 | | |
24 | 29 | | |
25 | | - | |
| 30 | + | |
26 | 31 | | |
27 | 32 | | |
28 | 33 | | |
| |||
71 | 76 | | |
72 | 77 | | |
73 | 78 | | |
74 | | - | |
| 79 | + | |
75 | 80 | | |
76 | 81 | | |
77 | 82 | | |
78 | 83 | | |
79 | 84 | | |
80 | | - | |
81 | | - | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
82 | 90 | | |
83 | 91 | | |
84 | 92 | | |
0 commit comments