Repository navigation
Commit d3f88fa
Fixes #20661
Clause-②: no
The memory analytics (cube) face now applies the whole-day rule for a
bare-day `$lte` BEFORE it converts the bound to the field's storage
form, as ADR-0053 D-E3 orders. On a declared `datetime` field, `where {
created_at: { $lte: '2026-07-28' } }` now answers the same rows as
`find()`, and the SQL echo shows the bound that runs.
## The change
`packages/drivers/driver-memory/src/memory-analytics.ts`:
- New module function `lteUpperBound(authored, comparand, storageForm)`.
It asks `nextUtcCalendarDay` about the AUTHORED value and returns one of
three decisions: `before` (a bare day: the next day's midnight,
converted to storage form, exclusive), `unbounded` (`9999-12-31`, which
has no next day), or `through` (anything else: the authored comparand in
storage form, inclusive).
- Both `lte` rows, the mingo `$match` row
(`CUBE_OPERATOR_TO_MONGO_PREDICATE`) and the SQL echo row
(`CUBE_OPERATOR_TO_SQL_PREDICATE`), now only render that one decision.
They used to call `nextUtcCalendarDay(comparands[0])`, and `comparands`
is already in storage form. On a declared `datetime` field that value is
the instant `2026-07-28T00:00:00.000Z`, which the helper correctly
refuses to widen.
- The storage-form conversion still has one home. `comparandsFor` became
`storageFormFor(cube, member)`, which returns the per-member conversion
(still `driver.filterComparandStorageForm`). Each exit maps its
comparands through it and passes the same function to its builder as
`storageForm`, for the one bound a builder derives instead of receiving.
`comparands` is still computed for every operator, so every conversion
(and any refusal it raises) happens exactly as before.
- `nextUtcCalendarDay` (`packages/spec` `calendar-day.ts`) is untouched.
The driver's own filter path (`memory-driver.ts`) was already in this
order (`nextUtcCalendarDay(val)`, then `store(nextDay)`) and is
untouched.
The landing site is the one the claim expected. The producer is the
emitter, not `filterComparandStorageForm`: ADR-0053 D-D1 keeps the
storage-form conversion operator-blind and puts the operator-sensitive
rewrite on the emitters.
## Before and after, measured at the published export
Built `@objectstack/driver-memory` `dist/index.mjs`
(`MemoryAnalyticsService`, `InMemoryDriver`). Before = `origin/main` at
`fa0a4b6610` (this branch's base; `e4e5222b7` plus one commit that does
not touch this package). After = this branch. Rows `r28` `created_at:
2026-07-28T10:00:00.000Z` and `r27` `2026-07-27T10:00:00.000Z`.
"Declared" means `syncSchema` with `created_at: datetime` and `made_on:
date`.
| field | `where` | `find()` | cube `query()` before | echo before |
cube `query()` after | echo after |
|:--|:--|:--|:--|:--|:--|:--|
| `created_at` undeclared | `$lte '2026-07-28'` | r27, r28 | r27, r28 |
`created_at < '2026-07-29'` | r27, r28 | unchanged |
| `created_at` declared `datetime` | `$lte '2026-07-28'` | r27, r28 |
**r27** | **`created_at <= '2026-07-28T00:00:00.000Z'`** | r27, r28 |
`created_at < '2026-07-29T00:00:00.000Z'` |
| `made_on` declared `date` (control) | `$lte '2026-07-28'` | r27, r28 |
r27, r28 | `made_on < '2026-07-29'` | r27, r28 | unchanged |
| `created_at` declared `datetime` | `$lte` a `Date` at
`2026-07-28T00:00Z` (control) | r27 | r27 | `created_at <=
'2026-07-28T00:00:00.000Z'` | r27 | unchanged |
| `created_at` declared `datetime` | `$lte '9999-12-31'`, rows `c26`
`2026-07-15T14:00Z`, `mid` `9999-12-31T10:00Z`, `none` | c26, mid, none
| **c26, none** | **`created_at <= '9999-12-31T00:00:00.000Z'`** | c26,
mid, none | `created_at IS NOT NULL` |
| `created_at` undeclared | `$lte '9999-12-31'`, same rows | c26, mid,
none | c26, mid, none | `created_at IS NOT NULL` | same | unchanged |
In the last-day rows, `none` was written with `created_at: null`, and
the driver's `create` stamps its `created_at` default in its place, so
that row has a value. The pinned test uses no such row.
## Dispatch hypotheses
- **H1, held.** At `fa0a4b6610`, both `lte` rows called
`nextUtcCalendarDay(comparands[0])` (`:273`, `:468`). `comparands` came
from `comparandsFor` (`:1555`, called at `:886` and `:1367`), and the
builder context also carried `raw`. The fix widens from `raw[0]` and
converts only the widened bound, through the same conversion the
comparands take. It is one function that both rows render, so the two
exits cannot drift.
- **H2, held.** `$between` is not in `MONGO_TO_CUBE_OPERATOR`. Measured
on both exits with a declared `datetime` field and a bare-day maximum:
`INVALID_FILTER` / 400 ("declared by the Filter Protocol but cannot be
compiled by driver-memory's analytics (cube) face"). No row added. The
new test pins that refusal on the date shape, so widening the table to
take `$between` turns it red and points at `lteUpperBound`.
- **H3, held.** A `timeDimensions[].dateRange` end widens the raw `end`
string before it builds the string and `Date` bounds (`:1005`), so it
already follows D-E3. On a declared `datetime` field, `['2026-07-01',
'2026-07-28']` answers r27, r28 before and after, the same as `find()`
with `$gte` / `$lte`. Pinned, not changed.
- **H4, held.** On a declared `date` field the storage form of a bare
day is that bare day, and the answer and echo are byte-identical before
and after (table above). Pinned.
- **H5, held.** #20643's `isUnboundedAbove` path still answers "has a
value". Its existing pins in
`memory-driver-calendar-day-upper-bound.test.ts` (undeclared field) are
green. On a DECLARED `datetime` field the last day was also broken by
this card's order (`<= '9999-12-31T00:00:00.000Z'`, dropping `mid`). It
now answers `IS NOT NULL` / `$ne: null` and is pinned.
## Verification
Tests: new
`packages/drivers/driver-memory/src/memory-analytics-20661-lte-whole-day-first.test.ts`,
7 cases. Every case compares the cube's rows with `find()` and with a
literal id list, and checks the echo's `WHERE`:
- the card's pair, undeclared and declared `datetime`;
- a full-timestamp `$lte` (stays inclusive, in storage form);
- the declared `date` control;
- the declared-`datetime` last day and the day before;
- the `dateRange` sibling;
- the `$between` refusal on both exits, asserting the envelope `code`
and `status`.
- `pnpm --filter @objectstack/driver-memory test`: 63 files, 1456 tests
passed.
- `pnpm --filter @objectstack/driver-memory typecheck` (`tsc --noEmit &&
tsc --noEmit -p tsconfig.typecheck.json`): exit 0. `--listFiles` on the
first program lists the new test file.
Reverse verification. The fix was committed first (HEAD `cb155edf6c`).
The subject is loaded from `src` through the test's relative import, so
no `dist` is involved. The mutation went through
`scripts/ablation-replace.mjs` under a `trap` restoring `HEAD`: the
anchor `nextUtcCalendarDay(authored)` was replaced with
`nextUtcCalendarDay(comparand)`, which is the old order. On disk the
anchor went 1 to 0, the replacement 0 to 1, and the blob `014497594cc6`
became `a41c37fac411`. Run over the new file and
`memory-driver-calendar-day-upper-bound.test.ts`: 2 failed, 15 passed.
The two failures were the declared-`datetime` pair (`expected [ 'r27' ]
to deeply equal [ 'r27', 'r28' ]`) and the declared last day (`expected
[ 'c26', 'prev' ] …`). The undeclared, `date`, instant, `dateRange`,
`$between` and #20600 cases stayed green, which is the expected
direction. Restore: the blob equals the `HEAD` blob `014497594cc6` and
`git diff HEAD` is empty.
Gates, at `8cae2ae398`:
- `node scripts/pm/dispatch-gates.mjs --commands`: 60 commands, all exit
0. `check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:type-check-debt` first exited 3 (PREREQUISITE NOT MET, no built
closure). They were re-run green after `pnpm exec turbo run build
--filter='./packages/*' --filter='./packages/*/*'` (71 of 71 tasks).
- `--ran` reconciliation: "60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN".
Lint, narrowed and not the repo-wide run:
- `eslint --no-inline-config --format json` over the 2 touched `.ts`
files: 2 files, 0 errors, 0 warnings.
- Both files are in eslint's own population: `--print-config` resolves a
config for each, with 6 and 5 rules.
- `eslint.config.mjs` enables no type-aware linting (no
`parserOptions.project`; its own header says so at `:327`), so this diff
cannot move the verdict for any untouched file.
The `Clause-②: no` line above is the claim's.
## Acceptance notes
- **Reach unchanged.** No in-repo door constructs
`MemoryAnalyticsService`: `git grep 'new MemoryAnalyticsService('` finds
0 non-test hits against 31 in test files (the control).
`service-analytics` `plugin.ts:245` mentions it in a comment as a
registered fallback "from dev-plugin", but no code in this repo
registers it.
- **No other analytics or preview face has this order, by reading.**
- `service-analytics` `native-sql-strategy.ts:1244` widens `values[0]`
and then coerces `nextDay`.
- `objectql-strategy.ts:548` widens the raw `$lte` bound.
- The preview evaluator `lteBound` is type-blind and converts nothing.
- `driver-mongodb` `mongodb-filter.ts:1165` widens `value` and then
stores `nextDay`.
- None of these was measured at runtime.
- **Behind `origin/main` by 2 commits.** They were `9b384f63ae`
(service-storage docs) and `5757463712` (platform-objects translations),
with no path in `driver-memory`, `spec` or `core`. The branch was not
merged forward; CI's merge ref covers them.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5757463 commit d3f88fa
3 files changed
Lines changed: 271 additions & 19 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 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
157 | | - | |
| 157 | + | |
158 | 158 | | |
159 | 159 | | |
160 | 160 | | |
| |||
204 | 204 | | |
205 | 205 | | |
206 | 206 | | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
207 | 215 | | |
208 | 216 | | |
209 | 217 | | |
210 | 218 | | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
211 | 266 | | |
212 | 267 | | |
213 | 268 | | |
| |||
269 | 324 | | |
270 | 325 | | |
271 | 326 | | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
276 | 334 | | |
277 | 335 | | |
278 | 336 | | |
| |||
326 | 384 | | |
327 | 385 | | |
328 | 386 | | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
329 | 392 | | |
330 | 393 | | |
331 | 394 | | |
| |||
464 | 527 | | |
465 | 528 | | |
466 | 529 | | |
467 | | - | |
468 | | - | |
469 | | - | |
470 | | - | |
471 | | - | |
472 | | - | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
473 | 539 | | |
474 | 540 | | |
475 | 541 | | |
| |||
628 | 694 | | |
629 | 695 | | |
630 | 696 | | |
631 | | - | |
| 697 | + | |
632 | 698 | | |
633 | 699 | | |
634 | 700 | | |
| |||
882 | 948 | | |
883 | 949 | | |
884 | 950 | | |
| 951 | + | |
885 | 952 | | |
886 | | - | |
| 953 | + | |
887 | 954 | | |
| 955 | + | |
888 | 956 | | |
889 | 957 | | |
890 | 958 | | |
| |||
1362 | 1430 | | |
1363 | 1431 | | |
1364 | 1432 | | |
| 1433 | + | |
1365 | 1434 | | |
1366 | 1435 | | |
1367 | | - | |
| 1436 | + | |
1368 | 1437 | | |
| 1438 | + | |
1369 | 1439 | | |
1370 | 1440 | | |
1371 | 1441 | | |
| |||
1537 | 1607 | | |
1538 | 1608 | | |
1539 | 1609 | | |
1540 | | - | |
1541 | | - | |
1542 | | - | |
| 1610 | + | |
| 1611 | + | |
| 1612 | + | |
| 1613 | + | |
| 1614 | + | |
| 1615 | + | |
| 1616 | + | |
1543 | 1617 | | |
1544 | 1618 | | |
1545 | 1619 | | |
| |||
1552 | 1626 | | |
1553 | 1627 | | |
1554 | 1628 | | |
1555 | | - | |
| 1629 | + | |
1556 | 1630 | | |
1557 | 1631 | | |
1558 | | - | |
| 1632 | + | |
1559 | 1633 | | |
1560 | 1634 | | |
1561 | 1635 | | |
| |||
0 commit comments