Repository navigation
Commit e18fea6
Part of #20822
Clause-②: no
Group 3b of #20822 (#5930 step 4, the engine lane), under claim
5930666311. It carries F8, the three #20987 engine faces from triage
pointer 5922592744, the stale F1 pointers, and the docs sentence. F7
(`lteBound`) is untouched: its card #21109 was ruled A at 14:16Z and
remains open, and the deletion waits until that card's PR is on `main`.
## What changes
| face | change | commit |
|:--|:--|:--|
| F8, objectql `having` and `aggregations[i].filter` walker
(`having-filter.ts`) | the whole-day copy is deleted:
`wholeDayUpperBound`, its two arms, and the `nextUtcCalendarDay` /
`UNBOUNDED_ABOVE` imports | 4256b7c, e7006d8 |
| `driver-mongodb` `translateFieldOperators` | `$contains` /
`$notContains` ask membership on a declared JSON-stored field
(array-only `$elemMatch` over `jsonMembershipCandidates` from
`@objectstack/core`) | 098beef, 9080dd0, 565a47a |
| `formula` `matchesFilterCondition` | `$contains` / `$notContains` ask
membership by the column's declaration when `options.fields` names it,
else by the stored value (seat answer Q2 = C, 5926601042) | a525d25,
ff3127e |
| objectql `engine.ts` delete-probe docblock | comment only: membership
on every typed backend, and the superset reading only without a
declaration | 1faf2e9 |
| spec `filter.zod.ts` | docblock only: the three pointers to the
deleted `SqlDriver.calendarDay*Rewrite` name `lowerFilterCondition`; the
`$contains` implementation-status list gains `driver-mongodb` and
`formula` | 74d434e |
| `read-scope-shared-lowering-seam.test.ts` | case titles and header: a
guard without types no longer hands the RLS `using` bound as written |
74d434e |
| `query-syntax.mdx` | the direct-call sentence names `InMemoryDriver`,
`MongoDBDriver` and `applyInMemoryAggregation` beside the SqlDriver
family, and the aggregate positions the engine lowers | 74d434e |
## F8: measured first (one grep, one probe)
- **Grep.** `applyHaving` and `matchesHaving` are not exported from
either objectql entry. `matchesAggregationFilter` is reached through
`applyInMemoryAggregation`, which both entries export. In-repo callers
are `engine.aggregate` (seam-fed) and
`packages/verify/src/date-bucket-parity.ts`, whose ASTs carry no
per-aggregation filter.
- **The seam covers both positions with the same reader the copy used.**
`engine.aggregate` resolves then lowers `aggregations[i].filter` with
`declaredDatetimeLowering(schema)`, and `having` with
`aggregatedRowColumnTypes(...) === 'datetime'`. The copy's set was
`classOfDeclaredType(type) === 'datetime'`, and `INSTANT_TYPES` is `{
datetime }`, so the two sets are equal. With no field map, the seam is
type-blind on the per-aggregation filter and passes no column on
`having`; the copy passed none on either. Nothing composes into `having`
or an aggregation filter after the seam (`predicate-guard.ts` only reads
them).
- **Probe.** On a `datetime` field, `{ opened_at: { $lte: '2026-02-01' }
}` over 6 rows. Through `engine.aggregate`, before and after: 3 (the
whole day). Through `applyInMemoryAggregation(rows, ast, undefined,
fields)` called directly: 3 before, 2 after (that day's midnight, as
written). That is item 5. Group 3a graded the same move on F6 as `no`.
## The `$contains` faces
Each face is pinned on `u1` against a stored `["u10"]`, with a scalar
control:
- **mongodb**: `{ owners: { $contains: 'u1' } }` on a `multiple: true`
lookup emits `{ owners: { $elemMatch: { $in: ['u1'], $not: { $type:
'array' } } } }`. It used to emit `$regex: 'u1'`, which MongoDB applies
per element. A `text` field keeps `$regex`. A field the driver holds no
declaration for keeps `$regex`, as driver-sql does for a table it was
never told about.
- **formula**: `matchesFilterCondition({ owners: ['u10'] }, { owners: {
$contains: 'u1' } })` is false, `['u1', 'u2']` is true, and `{ title:
'u10' }` is true (substring).
The declaration H2 asked about: `MongoDBDriver` reads it through
`ValueShapeResolver` once `syncSchema` has run, and a direct
`translateFilter` call gets none. `jsonMembershipCandidates` (core, PR
#21117) supplies the candidates, parsed from JSON text into values.
`driver-mongodb` already depended on core. Formula depends on spec
alone, so it carries a value-level copy of the same candidate rule, as
objectql `having` and `driver-memory` do (see the acceptance notes).
The emitted mongo documents were also read through mingo 7.2.4
(driver-memory's evaluator) in a scratch probe. It agreed with the
server-free reader on every new case. A real `mongod` was NOT MEASURED:
there is no binary here, and the live block in the new test file is
skipped. That is the card's recorded gap.
## RLS effect of the formula face (H3)
The write check evaluates `check` with `matchesFilterCondition`, handed
the object's declared columns. The probe ran through ObjectQL,
SecurityPlugin and SqlDriver (better-sqlite3 and sqlite-wasm,
identical). Policy: `record.tags.contains('x')` on a `tags` field. The
"before" column is formula's pre-change arm, ablated in `dist/`.
| post-image `tags` | stored as | read under `using` | `check` before |
`check` after |
|:--|:--|:--|:--|:--|
| `['x']` | `['x']` | shown | 403 | admitted |
| `['a', 'x']` | `['a', 'x']` | shown | 403 | admitted |
| `['xy']` | `['xy']` | hidden | 403 | 403 |
| scalar `'xy'` | `['xy']` | hidden | **admitted** | 403 |
| scalar `'x'` | `['x']` | shown | admitted | **403** |
| `null` | `null` | hidden | 403 | 403 |
- No write path admits a row the read hides after this change, so the
stop condition (C widening the check past the read) is not met.
- One write path the base admitted while the read hid the stored row is
closed: scalar `'xy'`.
- One write is newly refused although the read shows the stored row:
scalar `'x'`. The check judges the raw post-image, before the write door
wraps a scalar into a list. This is the class #21109's ruling A
addresses for temporal columns ("the RLS write check judges the row as
it will be stored"). The multi-value wrap is not in that ruling's fold,
so it is reported to #21109's family rather than worked around here.
#21109 remains open.
## `Clause-②` (H5)
`no`, as claimed:
- No face adds or removes a refusal, and no export, type member or
authorable key changes.
- The answers move toward the declared contract. On `driver-mongodb`,
`$contains` narrows on declared JSON-stored fields (exact member instead
of a per-element substring). On `formula`, it widens on arrays and
narrows on a scalar stored in a declared JSON-stored column. On
objectql, a direct call compares as written.
- The reviewer should re-judge one line: through the RLS write check,
formula's move becomes an admit-set move in both directions (the table
above).
Levels: `@objectstack/objectql`, `@objectstack/driver-mongodb`,
`@objectstack/formula` and `@objectstack/spec` are `patch`. The spec
entry is docblock-only: `filter.zod.ts` ships in the spec tarball
(`files` includes `src/**/*.zod.ts`), so its edited docblocks publish
(patch round 1, 9a797d0, after review 5935291820). The docs and test
edits do not publish.
## Ablations (on committed heads; every restore proven blob == HEAD and
`git diff HEAD` empty)
- **F8 A1** re-plants the `$lte` whole-day arm (nested WRAP: import,
then arm; objectql tests import `src`). 3 red of 798, exactly the
direct-call `$lte` cells (per-aggregation `$lte`,
`applyInMemoryAggregation`, `having` on `min(datetime)`). Every seam-fed
cell stays green: the card's rows 3 and 4, the `having` rows, the
temporal kit, and `engine-shared-filter-lowering-seam`.
- The first A1 attempt was a no-op. Its replacement contained its own
anchor, the tool refused it ("the anchor count moved 1 -> 1"), and it
was rerun with a respelled import.
- **F8 A2** re-plants the `$between` arm. 1 red of 798, exactly the
direct `$between` cell.
- **M1** restores the always-`$regex` arms in `mongodb-filter.ts`. 10
red of 690, all membership cells in the new file. The scalar controls,
the no-declaration cell and all pre-existing suites stay green.
- **C1** forces `containsAsksMembership` to false in formula, then
rebuilds formula, which plugin-security consumes through `dist/`.
- The first attempt is VOID. That mutation failed the DTS build (unused
symbols), and esbuild folded its marker string, so
`ablation-dist-preflight` reported the marker absent from `dist/` (exit
1).
- The rerun used a marker esbuild keeps. Build exit 0, preflight found
the marker in 2 built files. formula: 8 red of 1253, all membership
cells. plugin-security: 4 red of 6, the check insert and update cells on
both drivers, with both `using` read cells green.
- The restore leg rebuilt formula; preflight `--absent` passed and the
tree was clean.
## Tests (final head a62f5ff, `vitest run --maxWorkers=2`, under the
verify lock)
| package | files | tests |
|:--|:--|:--|
| objectql (`--project local`) | 359 passed | 7078 passed |
| driver-mongodb | 31 passed, 5 skipped | 690 passed, 182 skipped (base
675 / 172; +15 / +10 is the new file and its live block) |
| formula | 43 passed | 1253 passed (base 1241) |
| plugin-security | 155 passed | 3327 passed, 23 skipped |
| service-analytics `read-scope-shared-lowering-seam` | 1 passed | 12
passed |
- At BASE f0cc16e, the objectql having/aggregate subset was 21 files
and 783 passed. After the deletion, before any test edit, it was still
783.
- `typecheck` (tsc plus `check:test-typecheck`) exits 0 for objectql,
driver-mongodb, formula, plugin-security and service-analytics.
- `spec` `check:generated`: 15 of 15 up to date.
- `check:driver-conformance` reads the same before (BASE) and after
(head): OK, 50 covered cells, 0 DEBT, 0 exempt.
- **Lint, narrowed.** `eslint --no-inline-config --format json` over the
11 changed `.ts` files at a62f5ff: 11 files, 0 errors, 0 warnings,
none ignored. The `.md` / `.mdx` files are outside eslint's configured
population ("no matching configuration"). `eslint.config.mjs` enables no
type-aware linting, so no untouched file's verdict can move. The full
`pnpm lint` is CI's.
- **Gates.** `dispatch-gates --commands` at a62f5ff derived 115
families from 13 paths. All 115 were run with exit codes recorded and
all exited 0. `--ran`: 115 derived, 115 run, 0 NOT-MEASURED, 0 UNRUN.
- On the first pass three gates refused with PREREQUISITE NOT MET:
`check:skill-examples`, `check:dual-build-cjs-loads` and `check:i18n`.
They passed after a full turbo build.
- `check:where-matcher` caught the new mongodb test double, whose
control probe threw on implicit equality; 565a47a fixes it.
## Acceptance notes
- **The membership candidate rule now has three value-level copies:**
`formula` (this PR), objectql `having-filter.ts` `storedArrayHasMember`,
and `driver-memory` `containsMemberCandidates`. Each follows core's
`jsonMembershipCandidates`. The home they could all import is
`@objectstack/spec/data` (formula depends on spec alone), as the
`having` docblock already says. Not filed: it is a duplication, not a
wrong answer.
- **The delete probe's off-shape scalar.** Membership is array-only on
every typed backend, so a `multiple: true` slot holding a bare scalar
(out-of-band data; the write door wraps scalars) is not matched by the
delete probe's `$contains` pushdown. The `storedReferenceIncludes`
scalar arm therefore never sees it. That was already true on driver-sql
and driver-memory, and this PR extends it to mongodb. Noted in the
docblock; not filed (no in-repo producer of such a slot was measured).
- **`query-syntax.mdx`'s `$contains` bullet.** It still describes only
the substring reading. That item is #20987's (its comment 5922379046),
and #20987 remains open; this PR edits only the direct-call sentence its
claim names.
- **`compileScopedFilterToSql` with no declarations handed in** reads no
column as `datetime` and compiles the bound as written. The RLS compile
seam reads a guard without types type-blind since group 2. The seam test
header now says so; the divergence is noted, not filed.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Ujdtvqs7ree7WyQmEDwEnG)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent b42e034 commit e18fea6
13 files changed
Lines changed: 898 additions & 134 deletions
File tree
- .changeset
- content/docs/protocol/objectql
- packages
- drivers/driver-mongodb/src
- formula/src
- objectql/src
- plugins/plugin-security/src
- services/service-analytics/src/__tests__
- spec/src/data
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
571 | 571 | | |
572 | 572 | | |
573 | 573 | | |
574 | | - | |
575 | | - | |
576 | | - | |
577 | | - | |
578 | | - | |
579 | | - | |
580 | | - | |
581 | | - | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
582 | 585 | | |
583 | 586 | | |
584 | 587 | | |
| |||
Lines changed: 201 additions & 0 deletions
| 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 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
202 | 202 | | |
203 | 203 | | |
204 | 204 | | |
205 | | - | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
206 | 208 | | |
207 | 209 | | |
208 | 210 | | |
| |||
694 | 696 | | |
695 | 697 | | |
696 | 698 | | |
697 | | - | |
| 699 | + | |
| 700 | + | |
698 | 701 | | |
699 | 702 | | |
700 | 703 | | |
| |||
833 | 836 | | |
834 | 837 | | |
835 | 838 | | |
836 | | - | |
837 | | - | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
838 | 843 | | |
839 | 844 | | |
840 | 845 | | |
| |||
0 commit comments