Repository navigation
Commit 8460592
Part of #20822
Clause-②: no
#5930 step 4, **group 1b: F3** (`driver-memory`'s query path). This
follows the seat's answer B (5915193659 on #20822), read from ADR-0053
D-D1 items 5, 7 and 10 as amended: first route the direct caller, then
make the engine seam type-blind when it has no field map, then delete.
It is one PR in three ordered commits on `main` at `4d0b9cd542`:
| # | Commit | What it does |
|:--|:--|:--|
| 1 | `0bd0e6d4f7` fix(metadata) | `DatabaseLoader.queryHistory` in
driver mode runs `lowerFilterCondition` on its own `where`. The reader
is typed by the history object the loader syncs (`recorded_at` is
`Field.datetime`). The loader becomes a seam (item 5). |
| 2 | `5317b5aa22` fix(objectql) | `declaredDatetimeLowering`'s
absent-map branch drops the reader, so an object with no field map is
lowered type-blind (item 7). An object with a field map keeps the typed
scope byte-identical. |
| 3 | `e15606bae2` refactor(driver-memory) | F3's four whole-day sites
are deleted. The 43 direct-call tests are routed through
`lowerFilterCondition` with the declared-datetime reader. New pins cover
item 5 (one cell per deleted site) and item 7's convergence. |
| 4 | `86ccdc099e` docs(changeset) | The `driver-memory` bullet names
the RLS no-guard path (review 5919688563, FAIL 1), and no longer says
that every seam hands the driver a lowered filter. Changeset text only.
|
13 files against `4d0b9cd542` (+662 / -102 at `e15606bae2`; commit 4
changes one changeset line). The changeset is
`.changeset/20822-f3-route-then-delete.md`: `patch` for
`@objectstack/metadata`, `@objectstack/objectql` and
`@objectstack/driver-memory`, with the (b) convergence stated.
## The answers that move, named
These were measured through the real engine (`ObjectQL` dist,
`engine.find`) on `SqlDriver` (`driver-sqlite-wasm`) and
`InMemoryDriver`. The table was synced through `driver.syncSchema`. The
object was either registered in the engine with its field map
("registered") or not registered ("unregistered").
- Rows: `r1` = `2026-07-28T00:00:00.000Z`, `r2` = `…T12:00:00.000Z`,
`r3` = `…T23:59:59.999Z`, `r4` = `2026-07-29T00:00:00.000Z`. The same
instant is written into `at` (`datetime`), `txt` (`text`) and `extra`
(not declared; memory only). `d` (`date`) holds the calendar day.
- Filter: `{ col: { $lte: '2026-07-28' } }`.
- Columns: BASE = `main` with commit 1 only; c2 = after commit 2; c3 =
after commit 3.
| Object · column | Driver | BASE | c2 | c3 |
|:--|:--|:--|:--|:--|
| registered · `at` (datetime) | both | r1,r2,r3 | r1,r2,r3 | r1,r2,r3 |
| registered · `d` (date) | both | r1,r2,r3 | r1,r2,r3 | r1,r2,r3 |
| registered · `txt` (text, ISO) | sqlite | none | none | none |
| registered · `txt` (text, ISO) | memory | r1,r2,r3 | r1,r2,r3 |
**none** |
| registered · `extra` (undeclared) | memory | r1,r2,r3 | r1,r2,r3 |
**none** |
| unregistered · `at` / `d` | both | r1,r2,r3 | r1,r2,r3 | r1,r2,r3 |
| unregistered · `txt` | sqlite | none | **r1,r2,r3** | r1,r2,r3 |
| unregistered · `txt` / `extra` | memory | r1,r2,r3 | r1,r2,r3 |
r1,r2,r3 |
| any · `at` `$lte '9999-12-31'` | both | r1..r4 | r1..r4 | r1..r4 |
| any · `at` `$between` the day | both | r1,r2,r3 | r1,r2,r3 | r1,r2,r3
|
- **Commit 2 moves one answer, a widening, on `SqlDriver`.** Take an
unregistered object's non-datetime column that holds ISO instant text. A
bare-day `$lte` on it now keeps the whole day (none becomes r1,r2,r3).
That is item 7's reading for a seam that cannot read the declared type:
"applies the rewrite type-blind". The dispatch expected that
`SqlDriver`'s answer for an unregistered object would not move yet,
because its F1 copy still exists (H2). That holds for `datetime` and
`date` columns only. F1 covers the columns the driver itself knows as
`datetime`, and nothing else.
- **Commit 3 moves the (b) cells, both narrowings on `driver-memory`,
onto `SqlDriver`'s answer.** On a registered object:
- a declared `text` column holding ISO text;
- a column the object does not declare.
The typed seam leaves both byte-identical, and the deleted copy used to
widen them. The seat's answer calls this item 7's scope ("it is not a
decision"). It is declared in the changeset. An unregistered object does
**not** narrow on memory, because commit 2 now lowers it at the seam.
- Neither move is a narrowing beyond what item 7 names, so nothing
stopped.
## Commit 1: `queryHistory` becomes a seam (H1: held)
These were measured with a scratch probe over the built `dist` of
`@objectstack/metadata`, `driver-memory` and `driver-sqlite-wasm`. The
mode is driver mode (`new DatabaseLoader({ driver })`), with two saves
on one day and `until` / `since` = that day:
| State | memory `until` | memory `since = until` | sqlite `until` |
sqlite `since = until` |
|:--|:--|:--|:--|:--|
| `main` (F3 present) | 2 / 2 | 2 / 2 | 2 / 2 | 2 / 2 |
| all three commits | 2 / 2 | 2 / 2 | 2 / 2 | 2 / 2 |
| F3 deleted, loader lowering removed (dist ablation) | **0 / 0** | **0
/ 0** | 2 / 2 (F1 still present) | 2 / 2 |
- **Other direct driver callers in `packages/metadata`.** The other one
is `utils/history-cleanup.ts` (`recorded_at: { $lt: cutoffISO }`,
twice). That is an instant `$lt`, which no rule widens, so it is
unaffected. The other `_find` / `_count` filters in the loader are
equality only. No other temporal bound was found.
- **H4.** Group 2 (`driver-sql` F1) meets the same `queryHistory`
caller. Commit 1 lowers it for every driver, so **group 2 has no caller
left to route** in `packages/metadata`. §A below is the answer group 2
must keep once F1 is gone.
- **Pin:** `database-loader-20822-history-whole-day.test.ts`. It fakes
`Date` only.
- §A: the rows on real SQLite in driver mode.
- §B: the `where` the driver's `find` / `count` receive (`recorded_at: {
$lt: next day }`, lower bound kept, instant `until` and other columns
byte-identical).
§B is driver-agnostic. It is the half that goes red when the loader
stops lowering.
- **Not pinned on memory inside `@objectstack/metadata`.** A new test
consumer of `@objectstack/driver-memory` needs a maintainer ruling
(`scripts/driver-memory-census.ledger.json`, `RULED_CEILING = 2`). So
the memory half is covered in two other ways:
- §B (what every driver receives), plus `driver-memory`'s own pin of how
it answers the lowered and the unlowered filter;
- the dist measurement in the table above.
- Engine mode is untouched: the engine's `where` seam lowers it, typed
by the registered history object.
## Commit 2: an object with no field map is lowered type-blind (H2: held
for datetime and date, falsified for text)
The only change is `if (fields === null || typeof fields !== 'object')
return {};`. The typed branch is unchanged. The control in the new pin
(`engine-20822-no-field-map-type-blind-lowering.test.ts`) and the
existing `engine-shared-filter-lowering-seam.test.ts` stay green. The
new pin covers `find`, `findOne`, `count`, `aggregate`'s `where` and the
judge on an unregistered object. `having` has its own aggregated-row
reader (F8, group 3), which is untouched.
## Commit 3: F3 deleted (H3: held)
- **Deleted:**
- the `$lte` and `$between` arms of the FilterCondition translator;
- the less-or-equal and `between` arms of the AST-node translator (`{
type: 'comparison' }`, which no seam emits; only direct callers reach
it).
`nextUtcCalendarDay` / `isUnboundedAbove` are no longer imported by
`memory-driver.ts`. The clobber-class table in `assembleLoweredWrites`'
docblock loses the `$lt` / `$ne` writers the rewrite added. No
driver-local guard is kept.
- **F3's typed reader** is the engine's `declaredDatetimeLowering`. It
is typed when the object has a field map, and type-blind without one
(commit 2). The driver's own `syncSchema` temporal index is not
consulted by any seam.
- **The 43 direct-call tests** are the same 43 that went red with the
deletion alone:
| Suite | Tests |
|:--|:--|
| temporal-conformance | 25 |
| calendar-day-upper-bound | 5 |
| analytics-20661 | 5 |
| datetime-storage | 4 |
| temporal-storage-form | 2 |
| shared-lowering-door | 2 |
Each now hands `find()` what a typed seam hands it:
`lowerFilterCondition` with a reader over the fixture's own declared
field map. In the 20661 file, the `undeclared` reading is lowered
type-blind, which is commit 2's reading. **0 `expect(` lines changed**
in the six routed files.
- **New pin:** `memory-driver-20822-comparison-as-written.test.ts`.
- §A (item 5): a direct call gets the comparison it wrote. There is one
cell per deleted site. On a `datetime` column a bare day takes its
storage form, the midnight instant, so `$lte` keeps the midnight row.
- §B (item 7): the registered-object convergence cells.
- §C: the type-blind reading.
## Ablations: each one committed first, restored and proven by blob
hash, re-run at the final head `e15606bae2`
Every mutation went through `scripts/ablation-replace.mjs` (anchor must
hit, blob verified, restored blob equal to HEAD, `git diff HEAD` empty).
| Commit | Mutation | Red | Green |
|:--|:--|:--|:--|
| 1 | loader lowering removed | 2 of 8 (§B's two bare-day cells) | §A
stays green through `SqlDriver`'s F1 copy |
| 2 | absent-map branch removed | 4 of 19 (the four unregistered
lowering cells) | the control, the instant and the judge cells |
| 3 | `$lte` arm restored (with its import) | 4 of 1424 | the other 1420
|
| 3 | `$between` arm restored | 2 of 1424 | the other 1422 |
| 3 | AST less-or-equal arm restored | 1 of 1424 | the other 1423 |
| 3 | AST `between` arm restored | 1 of 1424 | the other 1423 |
In every commit-3 row, the red cells are the matching cells of the new
pin and nothing else. Each restored copy is idempotent on lowered input
(item 9).
The H1 dist counterfactual (the memory 2 / 2 to 0 / 0 row above) ran
through `ablation-dist-preflight.mjs` for the restore leg: marker absent
from all 30 built files and the tree clean. The mutate leg's arrival in
`dist` is shown by the probe's answer moving.
## Tests, gates and lint, all at `e15606bae2`
- `@objectstack/driver-memory` vitest: 66 files / 1424 passed.
- `@objectstack/metadata` vitest: 56 files / 836 passed.
- `@objectstack/objectql` vitest `--project local`: 348 files / 6807
passed. `--project repo`: 1 / 5 passed.
- The three packages' `typecheck`: exit 0. That covers objectql's
`check:test-typecheck` (OK, 234 errors / 65 signatures held in the
ledger). driver-memory's `tsconfig.json` program lists all 66 test
files.
- `node scripts/pm/dispatch-gates.mjs --commands` (merge base
`4d0b9cd54`) derived 66 families. **66 run, all exit 0.** The `--ran`
verdict: "66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED
(a DERIVED zero …)". This includes:
- `check:driver-conformance`: "OK — 50 covered cell(s), 0 in the DEBT
ledger, 0 exempt".
- `check:driver-memory-census`: "OK — every declaration is ledgered …".
- `check:dual-build-cjs-loads` and `check:type-check-debt`, after a
whole-workspace build.
- `check:query-options-erasure`: back at 236 test sites. My first draft
added one `{ where } as any`, and it is now typed.
- **Lint, narrowed.** `eslint --no-inline-config --format json` over the
12 changed `.ts` files gave 12 files, 0 errors and 0 warnings. Each file
resolves under `--print-config`. `eslint.config.mjs` enables no
type-aware linting ("no `parserOptions.project`, no typed
`@typescript-eslint` rules"), so no untouched file's verdict can move.
The full `pnpm lint` is CI's.
## Acceptance notes
- **The RLS compile seam's no-guard reading.** This is noted, not filed.
`carrier:` #20822 group 2 (`driver-sql` F1), which removes the next copy
standing behind this reading.
- `plugin-security` `rls-compiler.ts` `rlsLowering` reads an absent
guard as "no datetime column". The guard is absent when
`getObjectFieldNames` cannot resolve the object. This is the same
population, and the same reading, that commit 2 changed on the engine.
- Its existing pin says so: "a guard with no types reads no column as
datetime" gives `{ signed_on: { $lte: '2026-01-05' } }`.
- After this PR, an RLS `using` policy with a bare-day upper bound, on
an object whose declared fields the security plugin cannot resolve,
reaches `driver-memory` as written, where the deleted copy used to widen
it. (A `check` clause reaches `matchesFilterCondition`, not this driver,
so it does not move here.) The changeset's `driver-memory` bullet names
this path (`86ccdc099e`, after contract review 5919688563), and the
seat's answer 5918373748 (A) carries the `rlsLowering` twin into group
2.
- Item 5 covers a filter composed after the engine's seam. Item 7's
general rule would read that seam type-blind.
- Measured only at unit level (that pin and §A here), not through a
public door. It is outside this card's file surface.
- The metadata-side memory pin is replaced by §B plus the
`driver-memory` pins because of the census ledger (above).
- The branch was re-stacked twice before this PR opened, with
`--force-with-lease` and all five conditions met: first to fold two WIP
commits into commit 3, then onto `main` `4d0b9cd542` after #20911 landed
in `engine.ts`. No merge commit remains.
- Not done here: #20822 group 2 (F1, F2), group 3 (F6, F7, F8), and the
stale matcher pointers the last group PR corrects.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent f80e2a6 commit 8460592
13 files changed
Lines changed: 662 additions & 102 deletions
File tree
- .changeset
- packages
- drivers/driver-memory/src
- metadata/src/loaders
- objectql/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
Lines changed: 21 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
47 | 62 | | |
48 | 63 | | |
49 | 64 | | |
50 | 65 | | |
51 | 66 | | |
52 | 67 | | |
53 | | - | |
| 68 | + | |
54 | 69 | | |
55 | 70 | | |
56 | 71 | | |
| |||
72 | 87 | | |
73 | 88 | | |
74 | 89 | | |
75 | | - | |
| 90 | + | |
76 | 91 | | |
77 | 92 | | |
78 | 93 | | |
| |||
143 | 158 | | |
144 | 159 | | |
145 | 160 | | |
146 | | - | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
147 | 164 | | |
148 | 165 | | |
149 | 166 | | |
| |||
Lines changed: 23 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
43 | | - | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
44 | 50 | | |
45 | 51 | | |
46 | 52 | | |
| |||
78 | 84 | | |
79 | 85 | | |
80 | 86 | | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
81 | 90 | | |
82 | 91 | | |
83 | 92 | | |
84 | | - | |
| 93 | + | |
85 | 94 | | |
86 | 95 | | |
87 | 96 | | |
| |||
90 | 99 | | |
91 | 100 | | |
92 | 101 | | |
93 | | - | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
94 | 110 | | |
95 | 111 | | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
96 | 115 | | |
97 | | - | |
| 116 | + | |
98 | 117 | | |
99 | 118 | | |
100 | 119 | | |
| |||
Lines changed: 23 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
38 | 52 | | |
39 | 53 | | |
40 | 54 | | |
| |||
79 | 93 | | |
80 | 94 | | |
81 | 95 | | |
82 | | - | |
| 96 | + | |
83 | 97 | | |
84 | 98 | | |
85 | 99 | | |
| |||
106 | 120 | | |
107 | 121 | | |
108 | 122 | | |
109 | | - | |
| 123 | + | |
110 | 124 | | |
111 | 125 | | |
112 | 126 | | |
113 | 127 | | |
114 | 128 | | |
115 | 129 | | |
116 | 130 | | |
117 | | - | |
| 131 | + | |
118 | 132 | | |
119 | 133 | | |
120 | 134 | | |
121 | 135 | | |
122 | 136 | | |
123 | 137 | | |
124 | 138 | | |
125 | | - | |
| 139 | + | |
126 | 140 | | |
127 | | - | |
| 141 | + | |
128 | 142 | | |
129 | 143 | | |
130 | 144 | | |
| |||
145 | 159 | | |
146 | 160 | | |
147 | 161 | | |
148 | | - | |
| 162 | + | |
149 | 163 | | |
150 | 164 | | |
151 | 165 | | |
| |||
162 | 176 | | |
163 | 177 | | |
164 | 178 | | |
165 | | - | |
| 179 | + | |
166 | 180 | | |
167 | 181 | | |
168 | 182 | | |
| |||
187 | 201 | | |
188 | 202 | | |
189 | 203 | | |
190 | | - | |
| 204 | + | |
191 | 205 | | |
192 | 206 | | |
193 | 207 | | |
| |||
0 commit comments