Repository navigation
Commit 53ed3d1
fix(driver-mongodb): MongoDBDriver compiles the whole-day comparison it is handed; formula's copy stops on a measured answer move (#21105)
Part of #20822
Clause-②: no
Group 3a of #20822 (#5930 step 4, the per-face deletions), under ruling
5902355785 (D4 (b): "drivers receive the seams' lowered input; no
permanent local guards on faces that bypass a seam") and ADR-0053 D-D1
as amended. This PR does not close the card: group 3b (F8 `having`,
serial on `having-filter.ts`) does.
## What this PR does
- **F6 `driver-mongodb`: deleted.** `translateFieldOperators`
(`mongodb-filter.ts`) no longer applies the whole-day upper bound
itself. `MongoDBDriver` compiles the comparison it is handed.
- **F7 `formula` `lteBound`: stopped and reported, not deleted.**
Deleting it moves an answer on a seam-fed path (the card's stop rule).
The measurements are below, and the decision is asked in the report.
`matches-filter.ts` has no net change in this PR.
- **#20987's engine faces move to group 3b** (triage pointer
5922592744). These are the mongodb `$contains` `$regex`, formula's `case
'$contains'` and the `engine.ts` delete-probe docblock. The shared
membership module (`json-membership-sql.ts` in `@objectstack/core`) is
not on `origin/main`: at `e07566b73`, `git ls-tree -r` names 0 paths
matching `json-membership`. Also, `@objectstack/formula` depends on
`@objectstack/spec` alone, so an import from core would add a package
edge.
## H1: the sites, at `origin/main` `6f578888a`
- **F6.** `nextUtcCalendarDay` occurs 4 times in `mongodb-filter.ts`:
the import (`:27`), the `translateFieldOperators` docblock (`:1123`),
and 2 call sites. The call sites are the `$lte` arm (`:1211`) and the
`$between` arm (`:1305`). The card's "4 sites" are these 4 occurrences.
All 4 are gone. The `@objectstack/core` import is gone with them, and
the clobber-class table in `assembleLoweredWrites`' docblock loses its
`$lt` row.
- **F7.** `lteBound` has 2 call sites, the `$lte` arm (`:668`) and the
`$between` arm (`:673`), plus the function itself (`:867`). All 3 are
kept.
## H2: direct callers, with their answers before and after
- **F6.** No in-repo production caller hands `MongoDBDriver` a bare-day
`$lte` or `$between` outside a seam:
- the CLI's `secret-reference-union.ts` (`where: { type: 'datasource'
}`, an equality) and `secret orphans`;
- objectql `LifecycleService` archive (`$lt` an ISO instant);
- `DatabaseLoader.queryHistory` in driver mode, which has been its own
seam since group 1b.
Their answers do not move. The answer that moves belongs to a
hypothetical direct call such as `driver.find('task', { where: {
created_at: { $lte: '2026-07-28' } } })` on a declared `datetime`
column:
- before: `$lt` 2026-07-29 midnight, so the whole day was kept;
- after: `$lte` 2026-07-28 midnight, the comparison as written (item 5).
Through the engine seam the answer is `$lt` the next day both before and
after.
- **F7.** Its callers were measured with the copy deleted (`d045f1d6c`),
then the copy was restored (`394df163a`):
- **The RLS write `check`** (`security-plugin.ts`, step 3.6). It is fed
by the RLS compile seam, which lowers only the guard's `datetime`
columns (item 7). **Its answer moves**, as the table below shows.
- **The explain engine** (`explain-engine.ts`). Its filters come from
`computeLayeredRlsFilter`, so they too are compiled through the RLS
seam. It evaluates the STORED record, where a `date` column holds day
text. No answer move was found.
- **The tenant check** (`security-plugin.ts`, step 3.7). It evaluates
the organization wall only, with no temporal bound, so its answer is
unchanged.
- **F8's cross-field comparison** (`having-filter.ts`,
`compareWithReference`). It hands F7 `{ $field }` comparands, which the
lowering never rewrites. **Its answer moves** for a referent that
resolves to a bare day, as the second finding below shows.
- **Any external caller of the published `matchesFilterCondition`.**
Under item 5 it would get the comparison it wrote.
### F7: the measured answer move
The probe is temporary and not committed. It runs through the public
insert door: ObjectQL, SecurityPlugin and `SqlDriver` on SQLite. The
object has `due_on: date` and `signed_on: datetime`. One policy carries
`using` and `check`, both `record.due_on` at most `'2026-01-05'`. The
typed RLS seam leaves the `date` column as written, `{ due_on: { $lte:
'2026-01-05' } }`, as `rls-shared-lowering-seam.test.ts` pins. A member
inserts each value. The same value is also written by the system and
read back by the member.
| post-image `due_on` | stored | the member's read of the stored row |
insert, F7 copy kept | insert, F7 copy deleted |
|:--|:--|:--|:--|:--|
| `'2026-01-05'` | `2026-01-05` | visible | admitted | admitted |
| `'2026-01-05T10:00:00.000Z'` | `2026-01-05` | visible | admitted |
**refused `PERMISSION_DENIED` / 403** |
| `new Date('2026-01-05T10:00:00Z')` | `2026-01-05` | visible | admitted
| **refused `PERMISSION_DENIED` / 403** |
| `'2026-01-06'` (control) | `2026-01-06` | not visible | refused 403 |
refused 403 |
The record validator accepts both non-day spellings for a `date` field:
a `Date`, and a string with a leading `YYYY-MM-DD`. The driver stores
them as that day. The `check` judges the raw image before the driver
converts it. `lteBound` is what reads the bound as the whole day there,
on a column the typed seam does not rewrite. With it deleted, the write
is refused although the read shows the stored row. ADR-0053 D-D1 item 9
says a copy is "idempotent on lowered input". That premise does not hold
for this face.
A second answer moves too. A `$lte` against a `{ $field, addDays }`
reference whose referent resolves to a bare day gets the whole day only
from `lteBound`. The lowering keeps any `{ $field }` comparand as
written, so no seam carries this rule.
`matches-filter-field-reference-offset.test.ts` pins it ("a calendar day
stays a calendar day, so a `$lte` still covers the whole shifted day"),
and that pin goes red with the copy deleted.
**Existing suites with the F7 copy deleted** (`d045f1d6c`, formula
rebuilt):
- formula: 25 of 1241 red. Of these, 24 are direct-call cells that could
be routed through the lowering: 20 in
`matches-filter-temporal-conformance.test.ts` and 4 in
`matches-filter.test.ts`. The 25th is the `$field` offset pin above,
which cannot be routed.
- plugin-security: 153 files, 3287 passed, 23 skipped. No suite there
pins the `date` column case, so only the probe shows it.
- objectql `having` / aggregate (`src/having`, `src/engine-aggregate`):
15 files, 573 passed.
## F6: what moves, and what does not
- **Seam-fed reads do not move.** The engine's `where`,
`aggregations[i].filter` and the RLS compile seam hand this driver `$lt`
the next day, a split `$between`, and `$null: false` on `9999-12-31`.
The deleted copy answered that input identically, as ablations A1 and A2
below show. A `date` column answers as before, because its stored day
text orders the same either way.
- **Two answers converge on `SqlDriver`'s** (item 7's scope; seat answer
5915193659: "declare it in the changeset; it is not a decision"). On a
registered object, a bare-day `$lte` or `$between` maximum on a column
that is not declared `datetime` is now compared as written. That covers
a `text` column holding ISO instant text and an undeclared column.
- **A direct call compares as written** (item 5). The changeset gives
the one-line way to keep the seam's reading:
`lowerFilterCondition(where, { isDatetimeColumn })`.
- **Real mongod: NOT MEASURED.** There is no `mongod` binary in this
container, and `OS_TEST_MONGODB_MEMORY_SERVER_ENABLED` is off, as the
card's recorded gaps state. The gated suites that call `driver.find`
directly are routed through a typed seam helper, as the sibling drivers'
suites are: `mongodb-temporal-conformance.test.ts` (both
`TEMPORAL_CASES` and `TEMPORAL_TIME_CASES`, tokens resolved first) and
`mongodb-datetime-storage.test.ts` (its 3 bare-day cells). Neither ran
here.
## Pins, routed and flipped
- **`mongodb-filter.test.ts`.** The calendar-day block is split in two.
The first half is the direct call, compiled as written: a bare-day
`$lte`, a bare-day `$between`, the authored array form, and the last
supported day for both. The second half is the seam-fed control, every
former cell through `lowerFilterCondition` with `created_at` declared
`datetime`, with the same documents as before.
- **`mongodb-operator-key-clobber.test.ts`.** The enumeration rows
change: a bare-day `$lte` now writes `$lte`, a bare-day `$between` now
writes `$gte` and `$lte`, and the `$lt` contested row is removed. The
"fourth member" cell is now two-sided:
- a direct `$lte` beside `$lt` is uncontested;
- seam-fed, the lowering's `$lt` arrives as its own conjunct, and both
bounds survive in either key order.
- **`mongodb-filter-shape-refusal.test.ts`.** A bare-day range is pinned
as written, plus the seam-fed control.
- **`mongodb-time-storage.test.ts`.** A comment only.
## Ablations
Each ablation runs on the committed head through
`scripts/ablation-replace.mjs` in WRAP mode, nested: the import anchor
first, then the arm. The driver-mongodb suite imports
`./mongodb-filter.js` from `src`, so no dist is involved.
- **A1, the `$lte` arm restored:** 5 red, exactly the direct-call `$lte`
cells (3 in `mongodb-filter.test.ts`, 2 in the clobber file). Every
seam-fed cell stayed green.
- **A2, the `$between` arm restored:** 4 red, exactly the direct-call
`$between` cells (2 in `mongodb-filter.test.ts`, 1 in the shape-refusal
file, 1 clobber row). Every seam-fed cell stayed green.
- **Restore, both legs:** the blob equals HEAD `de35f1ee395c` and `git
diff HEAD` is empty.
- **The first attempt was a no-op.** Its import replacement contained
its own anchor, and the tool refused it: "the anchor count moved 1 ->
1". It was rerun with a corrected anchor.
## Compile surfaces
- **F6 `driver-mongodb` `translateFieldOperators`:** the whole-day copy
is deleted. It compiles as handed, and seam-fed answers are unchanged
(A1, A2).
- **F7 `formula` `matchesFilterCondition`:** unchanged. The stop is
reported, and `lteBound` stays until the decision.
- **`engine.ts` delete-probe docblock (`referenceProbeFilter`):**
unchanged. It moves to group 3b with the membership items.
## Tests
The full figures are in the report comment on #20822, measured at
`d1137d7ea`.
- driver-mongodb: 30 files passed, 5 skipped (the gated real-mongod
suites); 675 tests passed, 172 skipped. At the base: 671 passed, 172
skipped.
- `pnpm --filter @objectstack/driver-mongodb typecheck`: exit 0,
including `check:test-typecheck`.
- `check:driver-conformance`, before and after: OK, 50 covered cells, 0
DEBT, 0 exempt.
- Narrowed lint: `eslint --no-inline-config --format json` over the 8
changed `.ts` files gives 8 files, 0 errors and 0 warnings.
`eslint.config.mjs` never enables type-aware linting, so no untouched
file's verdict can move. The full `pnpm lint` is CI's.
## Acceptance notes (noted, not filed)
- **`@objectstack/core` is now test-only in `driver-mongodb`.** Its last
`src` import was the deleted `nextUtcCalendarDay` one, and the remaining
importer is `mongodb-temporal-conformance.test.ts`. The manifest still
lists it under `dependencies`. Moving it to `devDependencies` is a
manifest and lockfile edit outside this card. Carrier: none.
- **Stale pointers that name F1, not F6 / F7, are not carried here:**
`filter.zod.ts` around `:434`, `:734` and `:750` names
`SqlDriver.calendarDayUpperBoundRewrite` / `calendarDayBetweenRewrite`,
and the case title in `read-scope-shared-lowering-seam.test.ts` concerns
the RLS change of group 2. Release 5924791165 places them with the last
group PR, so the carrier is group 3b.
- **`query-syntax.mdx`'s sentence on direct calls names only the
`SqlDriver` family.** It reads: "A filter handed directly to `SqlDriver`
or a driver built on it … is compared as written". `MongoDBDriver` (this
PR) and `InMemoryDriver` (group 1b) now answer the same way. The
sentence is incomplete but not false. Carrier: group 3b.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Ujdtvqs7ree7WyQmEDwEnG)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 12fbb2f commit 53ed3d1
9 files changed
Lines changed: 195 additions & 103 deletions
File tree
- .changeset
- packages/drivers/driver-mongodb/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 | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
32 | 49 | | |
33 | 50 | | |
34 | 51 | | |
| |||
52 | 69 | | |
53 | 70 | | |
54 | 71 | | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
| 72 | + | |
60 | 73 | | |
61 | 74 | | |
62 | 75 | | |
| |||
96 | 109 | | |
97 | 110 | | |
98 | 111 | | |
99 | | - | |
| 112 | + | |
100 | 113 | | |
101 | 114 | | |
102 | 115 | | |
| |||
116 | 129 | | |
117 | 130 | | |
118 | 131 | | |
119 | | - | |
| 132 | + | |
| 133 | + | |
120 | 134 | | |
121 | 135 | | |
122 | 136 | | |
| |||
133 | 147 | | |
134 | 148 | | |
135 | 149 | | |
136 | | - | |
| 150 | + | |
137 | 151 | | |
138 | 152 | | |
139 | 153 | | |
140 | 154 | | |
141 | 155 | | |
142 | 156 | | |
143 | 157 | | |
144 | | - | |
| 158 | + | |
145 | 159 | | |
146 | | - | |
| 160 | + | |
147 | 161 | | |
148 | 162 | | |
149 | 163 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
| 77 | + | |
77 | 78 | | |
78 | 79 | | |
79 | 80 | | |
| |||
164 | 165 | | |
165 | 166 | | |
166 | 167 | | |
167 | | - | |
168 | | - | |
169 | | - | |
170 | | - | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
171 | 173 | | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
172 | 177 | | |
173 | 178 | | |
174 | 179 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
47 | | - | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
48 | 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 | + | |
49 | 88 | | |
50 | 89 | | |
51 | 90 | | |
52 | 91 | | |
53 | 92 | | |
54 | | - | |
| 93 | + | |
55 | 94 | | |
56 | 95 | | |
57 | 96 | | |
58 | 97 | | |
59 | 98 | | |
60 | | - | |
| 99 | + | |
61 | 100 | | |
62 | 101 | | |
63 | | - | |
| 102 | + | |
64 | 103 | | |
65 | 104 | | |
66 | 105 | | |
67 | 106 | | |
68 | | - | |
69 | | - | |
| 107 | + | |
| 108 | + | |
70 | 109 | | |
71 | 110 | | |
72 | 111 | | |
73 | 112 | | |
74 | 113 | | |
75 | | - | |
76 | | - | |
77 | | - | |
| 114 | + | |
78 | 115 | | |
79 | 116 | | |
80 | 117 | | |
81 | | - | |
| 118 | + | |
82 | 119 | | |
83 | 120 | | |
84 | 121 | | |
85 | 122 | | |
86 | 123 | | |
87 | 124 | | |
88 | | - | |
89 | | - | |
| 125 | + | |
90 | 126 | | |
91 | | - | |
| 127 | + | |
92 | 128 | | |
93 | 129 | | |
94 | 130 | | |
95 | | - | |
| 131 | + | |
96 | 132 | | |
97 | 133 | | |
98 | 134 | | |
99 | 135 | | |
100 | 136 | | |
101 | | - | |
| 137 | + | |
102 | 138 | | |
103 | 139 | | |
104 | | - | |
| 140 | + | |
105 | 141 | | |
106 | 142 | | |
107 | 143 | | |
108 | 144 | | |
109 | | - | |
110 | | - | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
111 | 151 | | |
112 | | - | |
113 | 152 | | |
| 153 | + | |
114 | 154 | | |
115 | 155 | | |
116 | 156 | | |
| |||
0 commit comments