Repository navigation
[finding] $exists with a non-boolean comparand ("yes", 1) is accepted at every door and inverted on driver-memory: it returns the rows with NO value; the spec declares $exists: z.boolean(), and its $null twin is refused #20897
Description
Filing gate: ① a defect with a named landing site. Finding class (b). reach: measured at the public engine.find door on the in-memory driver by #20822 group 1's dev (os-dev-report 5915148713 on #20822, out_of_scope_findings[0]).
Filed by the domain:engine execution seat 1 (session_01DEvba2nBuD4tWzfq8r8NFY, os-support-ai). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.
What happens
The object px has a text field name. Row a has name = "won", and row b has name = null.
where |
driver-memory | right answer |
|---|---|---|
{ name: { $exists: true } } |
[a] |
[a] |
{ name: { $exists: "yes" } } |
[b], the no-value row |
refused, as $null is |
{ name: { $exists: 1 } } |
[b] |
refused |
- The spec declares
$exists: z.boolean()inFieldOperatorsSchema(packages/spec/src/data/filter.zod.ts, about:1590and:1670). YetparseFilterASTaccepts a non-boolean, and no door refuses it. driver-memorylowersval === trueto$ne nulland anything else to$eq null, so a truthy flag inverts.- The twin operator
$nullrefuses a non-boolean on every driver ($null的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347; the refusal is worded in PR fix(driver-turso,driver-sql): refusals and log lines state each decision in words instead of a tracker number (stage 4) #20877). - The retired reference matcher read
$existsby truthiness. PR refactor(driver-memory): retire the reference matcher and the cube face's own whole-day bound (#5930 step 4, group 1: F4 + F5; F3 stopped) #20895 (#5930 step 4 (domain:engine): the engine-fed faces delete their hand-copied filter meaning (driver-sql, turso remote, memory query, mongodb, formula,having); the memory reference matcher retires (D6) #20822 group 1) pins the live path's answer, the divergence named inmemory-null-comparand-refusal.test.ts.
Seam: spec:FieldOperatorsSchema.$exists (boolean) → runtime:objectql engine filter doors (no refusal) → driver-memory normalizeFieldOperators $exists arm. Measure the other drivers.
Scope for whoever takes it (⛔ not a ruling)
- A non-boolean
$existsis refused at the engine's comparand doors, in the$nullrefusal's words, on every driver. The spec already declares the boolean. - Pins:
"yes",1and"false"refused on memory and SQLite, plus atrue/falsecontrol.
Dedupe
mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:
- 「$exists non-boolean comparand refused truthy string inverted driver-memory exists yes returns null rows」: 24 hits. [finding] driver-memory answers
$containson a stored array by substring per element (u1matches a row storingu10), where the SQL drivers answer membership; the spec docblock records the gap against a card that answers 404 #20874 (open) is$containson a stored array. [finding] driver-memory's matcher answers a NULL comparand inconsistently across the two readings of "no value" —$in:[null]/$nin:[null]disagree while$null/$ne:nullagree #13357, driver-memory's reference matcher puts a NULL-VALUED row inside$gt/$gte/$lt/$lteon a NUMERIC column —nullcoerces to0, and the live mingo path excludes it #13553 and driver-memory's reference matcher matches a NULL-VALUED row against a well-formed bounded$between— the live mingo path excludes it, so one package answers one filter two ways #13549 (closed) are reference-matcher null divergences. [finding] a no-operator object under a lookup, master_detail or json field answers per driver: the declared nested-relation filter returns no rows on memory and a 400 on SQL, and a json object comparand deep-equals on memory and is refused on SQL #20745, driver-sql on PostgreSQL answers 500 for a boolean or Date compared against a number field (where { amount: { $gt: true } }), while memory answers no rows and SQLite every row: the non-string half #20336 / #20351 left out #20502, RLS: a policy's compiled filter skips the shared comparand-shape faces, so a null list member or null ordering bound reaches driver-sql, and the read and the write check disagree (the read hides a row its own check admits) #20212, [finding]$newith an array comparand splits across backends: driver-sql and driver-memory refuse (400), driver-mongodb answers, formula matches every row — and both shared faces pass it #19886 and [finding] driver-memory's own reference matcher has no$fieldarm — a cross-field comparand (bare or withaddDays) reaching it is presumably compared as a literal object rather than resolved or refused (grep reading, to be measured) #15104 (closed) are other comparand shapes. None is a non-boolean$exists.
Dedupe words: exists non-boolean comparand refused · $exists truthy string inverted driver-memory · 5347 null twin exists
Generated by Claude Code
Activity
objectstack-fleet commentedon Sep 30, 2026
Triage: first grade —
bug·priority:p2·domain:engine·area:api·pm:queue. Direction: a non-boolean$existsis refused at the comparand doors, in$null's wordsTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T18:00Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: the engine's comparand doors (
packages/objectql) ⇒domain:engine.driver-memory's arm is fixed by the refusal reaching it first.Why p2. A silent inversion on memory:
$exists: "yes"returns the rows with no value. The spec declares$existsboolean, and its twin$nullalready refuses a non-boolean.Direction.
$existswithINVALID_FILTER/400, through the same function and words as$null's refusal ($null的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347, worded in PR fix(driver-turso,driver-sql): refusals and log lines state each decision in words instead of a tracker number (stage 4) #20877). ⛔ No second message."yes",1and"false"are refused;true/falsebehave as today (the control).objectstack-fleet commentedon Sep 30, 2026
Claim: PM loop round 1
Session:
session_01Ujdtvqs7ree7WyQmEDwEnGAccount:
os-litant(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-20897-exists-non-boolean-refusedWorktree:
objectstack-issue-20897Domain:
domain:engineSeat:
domain:engine#2(seat post #20966; the maintainer named this card for seat 2 in chat: 「你是第2个席位,你只处理 p2 的卡片,#20873、#20874、#20897」)File surface (triage's direction 5916839636: a non-boolean
$existsis refused withINVALID_FILTER/400, in$null's words, on every driver):packages/drivers/driver-memory/src/filter-refusal.ts: the comparand-shape gate that refuses a non-boolean$nulland$emptyrefuses a non-boolean$exists, in the wordsdriver-sqlalready uses for it (nonBooleanExistsComparandError). ⛔ No second message.packages/drivers/driver-mongodb/src/mongodb-filter.ts: the same, beside its$nullgate, if measured accepting today.packages/objectql(an engine-level door): only if the measurement shows the per-face gates cannot give one answer on every face. The report names the landing chosen and why.packages/drivers/driver-memory/src/memory-null-comparand-refusal.test.ts, the case "$existsis deliberately NOT tightened here", pins the inverted answer this card removes. It turns into a refusal pin, ⛔ not deleted."yes",1and"false"refused on memory and SQLite (and MongoDB where its suite runs);true/falseunchanged (the control)..changeset/20897-*.md.Stop on breach and explain in the report.
driver-memory/src/memory-driver.ts([finding] driver-memory answers$containson a stored array by substring per element (u1matches a row storingu10), where the SQL drivers answer membership; the spec docblock records the gap against a card that answers 404 #20874, seat 2, this round): a memory entry the gate does not reach is named with its answer, not edited.driver-sqlordriver-turso(#5930 step 4 (domain:engine): the engine-fed faces delete their hand-copied filter meaning (driver-sql, turso remote, memory query, mongodb, formula,having); the memory reference matcher retires (D6) #20822 group 2 in flight there); both are measured, and both already carry a non-boolean$existsrefusal onorigin/main.packages/objectql/src/having-filter.ts(serial behind #5930 step 4 (domain:engine): the engine-fed faces delete their hand-copied filter meaning (driver-sql, turso remote, memory query, mongodb, formula,having); the memory reference matcher retires (D6) #20822's F8, then [finding] a per-aggregationfilterwith$containson a multiple lookup counts 0 on every driver while the samewherefinds the rows: the engine's aggregation evaluator never matches a stored array #20873) and ⛔ notpackages/formula/src/matches-filter.ts(refusal text: the cross-class field-comparison refusal (972 characters) is cut at the 500-character client bound before its remedy sentence, so no caller of/dataorsecurity/explainreads the fix #20869 in flight): each is measured and named with its answer.Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)Clause-②: no
Thread-read: 5916839636
Serial constraints cleared: read at 2026-09-30T23:16Z against
origin/mainf6ccca4a4.domain:engine): the engine-fed faces delete their hand-copied filter meaning (driver-sql, turso remote, memory query, mongodb, formula,having); the memory reference matcher retires (D6) #20822 group 2 (in flight, claim 5920456159) editsdriver-sql,driver-tursoandrls-compiler.ts; group 3 (driver-mongodbF6,formulaF7,havingF8) is not claimed. Whichever of this card and group 3 lands second onmongodb-filter.tsmergesmain./dataorsecurity/explainreads the fix #20869 (in flight, claim 5920895653) editsformulamatches-filter.tsandplugin-securityexplain-engine.ts: off this surface.AutomationEngineresumeAuthority boot warning (#3801/#5561/#3823) and two objectql data-event warnings (#4639/#4626) #20513 stage 5b (queued) ismetadata-coreanddriver-mongodb'stest-mongod.ts: off this surface.driver-memory/src,driver-mongodb/srcorobjectql/srcproduction files.Clause-②: no:FieldOperatorsSchemadeclares$exists: z.boolean(), so the refusal pulls the faces back to the declared contract. It reuses the existingINVALID_FILTERcode anddriver-sql's existing wording; no key, code or accepted shape is added.objectstack-fleet commentedon Sep 30, 2026
os-dev-report
{
"issue": 20897,
"status": "done",
"branch": "claude/issue-20897-exists-non-boolean-refused",
"pr": "#20979",
"session": "session_01Ujdtvqs7ree7WyQmEDwEnG (subagent run; the id is the dispatching session's, as the commit trailers carry it)",
"premise_still_valid": true,
"summary": "driver-memory and driver-mongodb now refuse a non-boolean $exists comparand with INVALID_FILTER / 400, in driver-sql's nonBooleanExistsComparandError words. Each refusal sits beside the driver's existing $null refusal: memory's one shape gate (filter-refusal.ts assertFilterConditionShape, which covers find, findOne, count, aggregate, updateMany, deleteMany and the cube face's query() and generateSql()), and mongodb's validating walk (reduceFilterKey) plus a local check in the emitter arm. The premise held on driver-memory: $exists 'yes' / 1 returned the no-value row, the intent inverted, and its cube face answered the opposite rows by truthiness. 'Every door' did not hold: driver-sql, sqlite-wasm, both Turso transports and both service-analytics faces already refused (PM's H1). H1-H5 all held. No engine-level door was added: the only faces still accepting after this PR are the engine's in-process evaluators (objectql aggregation filter and having, in having-filter.ts) and formula matches-filter.ts, both fenced, and each measured and named. The pin 'is deliberately NOT tightened here' in memory-null-comparand-refusal.test.ts is flipped into a refusal pin (code + status + driver-sql's first sentence), not deleted.",
"tests": "head da4feaf. [driver-memory] pnpm --filter @objectstack/driver-memory test: 68 files, 1500 passed. [driver-mongodb] pnpm --filter @objectstack/driver-mongodb test: 30 passed, 5 skipped (live-mongod suites, opt-in OS_TEST_MONGODB_MEMORY_SERVER_ENABLED); 671 passed, 172 skipped. The new mongodb pins run on translateFilter and do not need mongod. [typecheck] typecheck for both packages exit 0; the new memory test is in tsc --noEmit's program (--listFiles count 1), the mongodb one in tsconfig.test.json (count 1). [cross-face] turso remote-transport-null-comparand-refusal + turso-local-remote-null-parity: 90 passed; service-analytics where-boolean-flag-refusal + read-scope-boolean-flag-comparand: 104 passed. [measurement] A scratch script over built dist ran engine.find / count / aggregate(where) / aggregate(agg filter) / aggregate(having) on memory and SQLite, memory driver entries, the cube face, sqlite-wasm, mongodb translateFilter and formula, before (f6ccca4) and after. Memory: every entry went from ROWS [b] to REFUSED INVALID_FILTER/400. Cube face: from sum over row a ('yes' / 1 / 'false') to REFUSED. Mongodb: from {$eq: null} to REFUSED. true / false unchanged everywhere. [ablation] Run from the committed fix via scripts/ablation-replace.mjs (anchor 1 to 0 and blob change asserted on disk; restore proven as blob == HEAD with git diff HEAD empty). Tests import src/, so no dist leg. (A) memory gate removed: 55 failed / 24 passed of 79. (B) mongodb walk gate removed, emitter check kept: 1 failed / 9 passed; only the identity-settled case, as predicted. (C) walk + emitter removed: 9 failed / 1 passed; only the true/false control. B's first attempt was an empty operation: the replacement contained the anchor, the tool refused, nothing was measured; re-run with a new anchor. [eslint] Narrowed to the 5 changed .ts files, --no-inline-config: 5 files / 0 errors / 0 warnings (from --format json). Population: each file resolves --print-config. Invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project), so untouched files' verdicts cannot move. [conformance ledger] node scripts/check-driver-conformance.mjs: 50 covered / 0 DEBT / 0 exempt, both before and after.",
"mcp_calls": "0",
"api_writes": "3, each one repository_dispatch through the fleet-write relay, executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, giving #20979; read-back 11757 bytes identical. (2) label-write assign, POST /repos//issues/20979/assignees with os-litant; read-back matches; zero label writes, because the dispatch named no label names and skip-changeset does not apply with a changeset present. (3) this os-dev-report comment, POST /repos//issues/20897/comments. Plus git push of the branch, which is not REST.",
"open_questions": [
{
"question": "The standing increment asks for the $exists refusal in shared conformance coverage. No spec case-set carries a driver-level refusal verdict for the flags: FILTER_LOGIC_CASES holds rows only, and FILTER_COMPARAND_TYPE_CASES' refusal verdict is the upstream parseFilterAST door. Enrol one?",
"options": [
"A: no new table; the refusal stays pinned per driver (sql, sqlite-wasm, turso parity, mongodb, memory), and memory's cross-face invariant (every entry refuses or equals find()) lands here",
"B: add a refusal verdict for non-boolean $null / $exists / $empty to a packages/spec case-set that every driver consumes (a spec type change and five driver suites)"
],
"recommendation": "A. Business need: every driver face now refuses and is pinned, and no producer is measured writing a non-boolean flag past the save door. Long-term: B is the sound shape once a second flag-shaped operator arrives. AI-error axis: both refuse loudly today. Startup scope: B widens a spec table and five suites for a rule that holds everywhere now. Revisit if a new driver arrives."
}
],
"out_of_scope_findings": [
"class: a · reach: engine.aggregate, measured on driver-memory AND driver-sql better-sqlite3 (HTTP not measured) · evidence: objectql having-filter.ts checkCondition reads $exists as exists !== !!target, so the aggregation filter { name: { $exists: 'false' } } sums the VALUED rows (s=1) and having { name: { $exists: 'false' } } returns the valued group 'won'. 'yes' and 1 give the same, 0 / null the no-value side. It is not refused on any driver, SQLite included, because the engine evaluates it in-process after the driver. The $null twin on the same two faces DROPS the constraint: $null 'yes' / 1 / 'false' / 0 / null return every row (s=3) and both groups, which is the widening direction. assertConditionIsEvaluable already refuses a non-boolean $empty there, not $null / $exists. Same family as the fenced having-filter serial (#20822 F8, then #20873): route into that family card, not a single-point card · dedupe words: having non-boolean $exists truthiness · aggregation filter $null non-boolean constraint dropped · assertConditionIsEvaluable flags $null $exists",
"carrier: #20869's in-flight PR on packages/formula/src/matches-filter.ts (or the #20822 F7 family) · noted, not filed: matchesFilterCondition reads $exists / $null by identity (v === true) and refuses neither, so $exists 'yes' / 1 / 'false' / 0 / null all match the no-value row. This face is total by contract, and no producer is measured emitting a non-boolean flag into it",
"carrier: #20874 (seat 2, same round, owns memory-driver.ts) · noted, not filed: normalizeFieldOperators' $exists arm has no local totality check (the $null arm has one). After this PR's gate it is unreachable with a non-boolean, so it is dead-code asymmetry",
"carrier: whoever lands the having-filter flag refusal (#20822 F8 / #20873) · noted, not filed: the filter.zod.ts save-door docblock ('Every query face refuses a non-boolean $null / $exists flag') and filter-save-door-refusals.ts ('refused on every query face') now hold for every driver, but still overstate objectql's aggregation filter / having and formula"
],
"gates": {
"head": "da4feaf8",
"derived_by": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 6 paths vs merge base f6ccca4)",
"derived": 60,
"ran_exit_0": 58,
"not_measured": [
"pnpm check:dual-build-cjs-loads: exit 3, PREREQUISITE NOT MET (reads every package's dist; 62 packages unbuilt in this partial build); declared to CI",
"pnpm check:type-check-debt: exit 3, PREREQUISITE NOT MET (re-measure needs the full build closure); declared to CI"
],
"reconciliation": "node scripts/pm/dispatch-gates.mjs --ran ran.list: 60 derived famil(ies) accounted for, 58 run, 2 NOT-MEASURED (exit 0)",
"note": "node scripts/check-plugin-teardown-shape.mjs --self-test first exited 3 (pinned fixture 621a487 outside the shallow clone). After git fetch --depth=1 of that one commit it exited 0; ran.list records the rerun.",
"ci_at_report": "31 check runs on da4feaf: 14 completed with 0 failures, 17 in_progress. Not awaited."
},
"deviations": [
"Shared conformance coverage (standing increment): not enrolled in a packages/spec case-set, because none carries a driver-refusal verdict and the spec tables are outside the claimed surface. The multi-face invariant is pinned in driver-memory (every entry refuses, and true/false equal find()); see open_questions[0].",
"Zone 3 engine.find pins: written at driver level, where the refusal lives (as $null's pins are). driver-memory and driver-sql take no @objectstack/objectql dependency. The engine pass-through was measured by a scratch script on both drivers (engine.find refuses on memory and SQLite after this PR).",
"SQLite pins: not duplicated. driver-sql is fenced, and its existing [#5369] block pins 'yes', 1, 0, null, undefined, {} and 'false' refused, with a true/false control.",
"Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By; the PR footer is os-dev.md's session-URL form.",
"git fetch --depth=1 origin 621a487 added one commit to the shared object store (additive), so a self-test could reach its pinned fixture.",
"origin/main moved 9 commits after f6ccca4, none touching driver-memory, driver-mongodb or spec/src/data. Not merged before the PR; the CI merge ref validates."
],
"files_changed": [
".changeset/20897-exists-non-boolean-refused.md",
"packages/drivers/driver-memory/src/filter-refusal.ts",
"packages/drivers/driver-memory/src/memory-exists-non-boolean-refusal.test.ts",
"packages/drivers/driver-memory/src/memory-null-comparand-refusal.test.ts",
"packages/drivers/driver-mongodb/src/mongodb-exists-non-boolean-refusal.test.ts",
"packages/drivers/driver-mongodb/src/mongodb-filter.ts"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
os-dev-report
{
"issue": 20897,
"status": "done",
"branch": "claude/issue-20897-exists-non-boolean-refused",
"pr": "#20979",
"session": "session_01Ujdtvqs7ree7WyQmEDwEnG (subagent run; the id is the dispatching session's, as the commit trailers carry it)",
"premise_still_valid": true,
"summary": "Patch round 1, three commits on the same branch; final head 378effc. (1) CI red at da4feaf (Test Core 5/6, @objectstack/client envelope-caller-census): the new memory suite's two analytics.query( calls on its own MemoryAnalyticsService are real call sites in that repo-wide census. Its prescription is to classify them, so LEDGER gains a NOT_SDK row (receiver service, count 2), and the two exact-count controls move with it (service receivers 1 to 3 with the file list pinned; NOT_SDK split 1 to 3). Nothing is exempted. (1b) The census therefore reads that driver-memory file, and check:cross-package-test-inputs went red until it was declared. I chose option (a): scripts/cross-package-test-inputs.mjs names the one file in @objectstack/client's globs, mirrored into turbo.json's @objectstack/client#test inputs. Per-file, not packages/, for the price the census header records. Option (b) has no sanctioned path: the census header names no exclusion for driver-package tests (SKIP_DIRS is build output only), and its receiver split exists to classify producer calls as NOT_SDK, which is what the ledger row does. (2) The changeset is rewritten as an accept-set narrowing: both packages minor, 'fix(driver-memory, driver-mongodb)!:', 'Clause-②: no (narrowing)', a BREAKING paragraph that keeps 'write the boolean itself', and one ADR-0087 marker: not-required (already-registered filter-query-face-comparands-refused-at-save). That registered entry's reason already states every query face refuses a non-boolean $null / $exists flag, and its replacement is this change's migration. The body carries that prescription, which a no-migration-prescription claim would contradict. (3) Open question A noted; no action.",
"tests": "final head 378effc. [census] pnpm --filter @objectstack/client exec vitest run --maxWorkers=2 src/envelope-caller-census.test.ts: 20 passed. Reverse leg: the census file restored from da4feaf (git restore --source, tree only), run against the new test: 2 failed / 18 passed, the same two cases CI named ('separates the producer-service receiver…', 'the mechanical enumeration and the hand ledger agree…'). Restore proven: blob == HEAD, git diff HEAD empty, status clean. [client] pnpm --filter @objectstack/client run typecheck && test: exit 0, 50 files / 641 passed. [cross-package] pnpm check:cross-package-test-inputs: exit 1 at 3682d8f (reproduced CI's 'named in packages/client/src/envelope-caller-census.test.ts'); after the glob alone, still exit 1 ('turbo.json @objectstack/client#test inputs are missing the declared glob'); after the turbo.json mirror, exit 0 ('29 package(s) read outside themselves, all declared'). node scripts/check-ci-filter-parity.mjs: exit 0. [changeset] node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0 ('1 declared-breaking changeset … not-required (already-registered)'). node scripts/check-changeset-no-major.mjs --base origin/main: exit 0. node scripts/check-empty-changeset.mjs --base origin/main: exit 0. pnpm check:changeset-gate-self-tests: exit 0. Level axis driven offline with --event: the current body ('Clause-②: no') exit 0, and the proposed body ('Clause-②: no (narrowing)') exit 0 ('no package whose packages//src/** it moves is graded patch'). [eslint] Narrowed to the 7 changed code files (6 .ts + scripts/cross-package-test-inputs.mjs), --no-inline-config: 7 files / 0 errors / 0 warnings from --format json; all 7 resolve --print-config; no type-aware linting in eslint.config.mjs, so untouched files' verdicts cannot move. turbo.json parses. [unchanged since da4feaf] driver-memory and driver-mongodb sources and tests: last round's 1500 / 671 passes and the A/B/C ablations stand. [conformance ledger] untouched this round (no conformance file changed): 50 covered / 0 DEBT / 0 exempt.",
"mcp_calls": "0",
"api_writes": "1: this os-dev-report comment, POST /repos//issues/20897/comments through the fleet-write relay as objectstack-fleet[bot]. Plus git push of three commits (9df5e06, 3682d8f, 378effc), which is not REST. No PR body write.",
"open_questions": [],
"out_of_scope_findings": [
"carried unchanged from os-dev-report 5921891523: class a · objectql having-filter aggregation filter / having read $exists by truthiness and drop a non-boolean $null constraint, on every driver (engine.aggregate, memory and SQLite measured) · route into #20822 F8 / #20873",
"carried unchanged: carrier #20869 (formula matches-filter identity read of $exists / $null) · carrier #20874 (memory-driver.ts $exists arm, no local totality check) · carrier: whoever lands the having-filter flag refusal (filter.zod.ts / filter-save-door-refusals.ts 'every query face refuses' prose) · noted, not filed"
],
"pr_body_edits": [
{
"replace": "Clause-②: no",
"with": "Clause-②: no (narrowing)",
"where": "line 2, the declaration line (whole line)"
},
{
"replace": "Why the declaration is no:
FieldOperatorsSchemadeclares$exists: z.boolean(), so this pulls two faces back to the declared contract. It reuses the existingINVALID_FILTERcode and driver-sql's existing wording, and it adds no key, code or accepted shape.","with": "Why the declaration is no (narrowing):
FieldOperatorsSchemadeclares$exists: z.boolean(), so this pulls two faces back to the declared contract. It reuses the existingINVALID_FILTERcode and driver-sql's existing wording, and it adds no key, code or accepted shape. It does narrow what driver-memory and driver-mongodb accept: a filter they used to answer is now refused. The changeset declares it BREAKING,minorfor both packages under the launch-window convention for accept-set narrowings, with the ADR-0087 dispositionnot-required (already-registered filter-query-face-comparands-refused-at-save). That registered entry's reason already states that every query face refuses a non-boolean flag, and its replacement is this change's migration.","where": "line 6, reads false now (says the declaration is plain no)"
},
{
"replace": "## Verification (head
da4feaf8)","with": "## Verification (driver changes at
da4feaf8; final head378effc8)","where": "heading, line 52"
},
{
"replace": "- Gates:
node scripts/pm/dispatch-gates.mjs --commandsafter the last commit derived 60 families.--ranreconciles them as 60 accounted for: 58 run and exit 0, and 2 NOT MEASURED. The first NOT MEASURED ischeck:dual-build-cjs-loads, which reads every package'sdist/. The second ischeck:type-check-debt, whose re-measure needs the whole build closure. Both exit 3 (PREREQUISITE NOT MET) on this partial build and are declared to CI.check-plugin-teardown-shape --self-testfirst exited 3 because its pinned fixture commit was outside the shallow clone. I fetched that one commit and it ran green.","with": "- Gates:
node scripts/pm/dispatch-gates.mjs --commandsafter the last commit (378effc8) derived 79 families.--ranreconciles them as 79 accounted for: 77 run and exit 0, and 2 NOT MEASURED. Those two arecheck:dual-build-cjs-loads, which reads every package'sdist/, andcheck:type-check-debt, whose re-measure needs the whole build closure. Both exit 3 (PREREQUISITE NOT MET) on this partial build and are declared to CI.check:skill-examplesfirst exited 3 for want of theclient-reactbuild, and ran green once that closure was built.","where": "line 63, stale counts and head"
},
{
"replace": "- ESLint, narrowed to the 5 changed
.tsfiles with--no-inline-config: 5 files, 0 errors, 0 warnings (counted from--format json). Each file resolves a config (--print-config).eslint.config.mjsenables no type-aware linting (noparserOptions.project), so this diff cannot move the verdict on an untouched file.","with": "- ESLint, narrowed to the 7 changed code files (the 6
.tsfiles andscripts/cross-package-test-inputs.mjs) with--no-inline-config: 7 files, 0 errors, 0 warnings (counted from--format json). Each file resolves a config (--print-config).eslint.config.mjsenables no type-aware linting (noparserOptions.project), so this diff cannot move the verdict on an untouched file.","where": "line 64, stale file count"
},
{
"insert_before": "## Acceptance notes",
"text": "## Outside the drivers: the envelope caller census\n\nThe new memory suite calls
analytics.query(twice on its ownMemoryAnalyticsService.@objectstack/client'senvelope-caller-census.test.tswalks the whole repo for that call shape, so CI went red atda4feaf8(Test Core (5/6)). The census prescribes classifying every counted site, and its receiver split already has the class for a producer call. SoLEDGERgains oneNOT_SDKrow (count 2, receiverservice), and its two exact-count controls move with it: the service-receiver control now expects 3 sites and pins their files, and the verdict split reads 3 not-SDK. Nothing is exempted or loosened. The census as it stood atda4feaf8goes red locally on the same two cases CI named. At378effc8it is 20/20 green, and the client package's tests (641) and typecheck pass.\n\nThe census reads that driver-memory file, sopnpm check:cross-package-test-inputsrequires it declared.scripts/cross-package-test-inputs.mjsnames the one file in@objectstack/client's globs, andturbo.jsonmirrors it into@objectstack/client#testinputs, so a change to that suite re-runs the census. It is per-file, notpackages/**, for the price the census header records.check:ci-filter-paritypasses, becausecorealready covers the path.\n\n","where": "new section; the body names no client, scripts or turbo.json change today"
}
],
"gates": {
"head": "378effc8",
"derived_by": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 9 paths vs merge base f6ccca4)",
"derived": 79,
"ran_exit_0": 77,
"not_measured": [
"pnpm check:dual-build-cjs-loads: exit 3, PREREQUISITE NOT MET (reads every package's dist); declared to CI",
"pnpm check:type-check-debt: exit 3, PREREQUISITE NOT MET (re-measure needs the full build closure); declared to CI"
],
"reconciliation": "node scripts/pm/dispatch-gates.mjs --ran ran3.list: 79 derived famil(ies) accounted for, 77 run, 2 NOT-MEASURED (exit 0)",
"note": "pnpm --filter @objectstack/spec run check:skill-examples (newly derived this round) first exited 3 (no client-react / spec dist in the recreated worktree); after building the client-react closure it exited 0 (259 examples type-check); ran3.list records the rerun. 18 families are new since da4feaf (the scripts/** and turbo.json edits): check-declaration-mirrors, check-scripts-symbol-anchors, check-self-test-wired, check-self-test-workflow-commands, check-whole-set-label-write (each with --self-test), check:agent-test-spelling, check:bash32-floor, check:cli-command-ids, check:entry-guard, check:parse-guard, check:pnpm-filter-targets, check:ratchet-remedy-authority, check:turbo-task-graph; all exit 0.",
"ci_at_report": "not read this round; not awaited"
},
"deviations": [
"Files outside the claim's surface, named for the claim amendment: packages/client/src/envelope-caller-census.test.ts (one LEDGER row, two exact-count controls moved to the new exact numbers, one header sentence); scripts/cross-package-test-inputs.mjs (one per-file glob under @objectstack/client); turbo.json (its mirror in @objectstack/client#test inputs). The last two were required by check:cross-package-test-inputs once the census row named the file.",
"ADR-0087 marker category: already-registered, not the no-migration-prescription the 4b4ee88 precedent used. The id filter-query-face-comparands-refused-at-save exists at the merge base, and the gate verifies it. It is the honest fit because this changeset keeps a migration prescription that the registered entry's replacement already records.",
"Worktree recreated as instructed and rebuilt (objectql / driver-memory / driver-sql / driver-mongodb / client closures, then client-react) so the dist-reading gates measured rather than ran on an empty build.",
"The PR body was not touched; pr_body_edits carries the exact replacements for the seat."
],
"files_changed": [
".changeset/20897-exists-non-boolean-refused.md",
"packages/client/src/envelope-caller-census.test.ts",
"packages/drivers/driver-memory/src/filter-refusal.ts",
"packages/drivers/driver-memory/src/memory-exists-non-boolean-refusal.test.ts",
"packages/drivers/driver-memory/src/memory-null-comparand-refusal.test.ts",
"packages/drivers/driver-mongodb/src/mongodb-exists-non-boolean-refusal.test.ts",
"packages/drivers/driver-mongodb/src/mongodb-filter.ts",
"scripts/cross-package-test-inputs.mjs",
"turbo.json"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Claim: PM loop round 1 (amendment of claim 5921382209: same session, same branch; the file surface follows the patch-round report 5922406027)
Session:
session_01Ujdtvqs7ree7WyQmEDwEnGAccount:
os-litant(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-20897-exists-non-boolean-refusedWorktree:
objectstack-issue-20897Domain:
domain:engineSeat:
domain:engine#2(seat post #20966; runs the lane queue by the maintainer's order recorded on #6367)File surface, as delivered in PR #20979 (head
378effc8): the original surface plus three declared additions.packages/drivers/driver-memory/src/filter-refusal.ts,packages/drivers/driver-mongodb/src/mongodb-filter.ts, their two new suites, and the flipped pin inmemory-null-comparand-refusal.test.ts, as claimed.packages/client/src/envelope-caller-census.test.ts. OneNOT_SDKledger row classifies the new memory suite's twoanalytics.query(producer calls, and its two exact-count controls move with it. This is a declared cross-lane touch (domain:cli, test only).scripts/cross-package-test-inputs.mjsandturbo.json. One per-file glob under@objectstack/clientand itsturbo.jsonmirror, whichcheck:cross-package-test-inputsrequires once the census names that file. This is a declared cross-lane touch: gate wiring, which the anchor rule'sscripts/exception assigns todomain:spec..changeset/20897-exists-non-boolean-refused.md,minorfor both drivers, BREAKING. It carries one ADR-0087 marker:not-required (already-registered filter-query-face-comparands-refused-at-save).Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)Clause-②: no
Thread-read: 5922406027
Serial constraints cleared: read at 2026-10-01T00:46Z against
origin/main05be35259.Clause-②: no, and the PR and its changeset carry the(narrowing)arm: a non-boolean$existsthat driver-memory and driver-mongodb used to answer is now refused (lane precedents: PR fix(objectql,spec)!: a groupBy on a multi-value field and a count_distinct on a JSON-stored field are refused INVALID_FIELD / 400 at the engine aggregate door, on every driver (#20808) #20911 and PR fix(objectql)!: a no-operator object beneath a relation, structured-JSON or undeclared id column is refused INVALID_FILTER / 400 on every driver (#20745) #20781). Nothing is widened. The landing owes an at-tier contract review, because the.changesetprose is BREAKING with a migration prescription and the PR touches a gate-wiring file.scripts/cross-package-test-inputs.mjs,turbo.jsonorenvelope-caller-census.test.ts(open-PR file lists read at the patch round's reading). #5930 step 4 (domain:engine): the engine-fed faces delete their hand-copied filter meaning (driver-sql, turso remote, memory query, mongodb, formula,having); the memory reference matcher retires (D6) #20822's group 3 F6 touchesmongodb-filter.tslater and mergesmainafter.objectstack-fleet commentedon Oct 1, 2026
Correction to claim amendment 5922434301: an in-flight overlap on the census files, and the main sha
domain:engine#2·session_01Ujdtvqs7ree7WyQmEDwEnG· 2026-10-01T00:48Z.scripts/cross-package-test-inputs.mjs,turbo.jsonorenvelope-caller-census.test.ts". Measured now over 20 open PRs: PR fix(service-analytics)!: the nested-relation filter gets the engine's answer on every analytics face — the related object read as the caller, capped (#20887) #20916 (#20802 analytics half (domain:services): the cube read and the analytics read scope answer{ relation: { field: value } }as the engine seam now serves it — as the caller, capped, one answer on every face #20887,domain:services) touches all three files. It makes the same kind of edit: aNOT_SDKledger row forpackages/rest/src/analytics-nested-relation-filter.test.ts(count 5), the same service-receiver exact-count control (to 6), and one glob in the same list and the sameturbo.jsoninputs. The two PRs conflict textually, and their counts must be summed. Whichever lands second mergesmainand recomputes the census controls: service receivers become 1 + 5 + 2 sites across three files. The merge queue arbitrates.mainsha in that amendment's reading line was stale:origin/mainread7fa67dadaat the act.objectstack-fleet commentedon Oct 1, 2026
Id fix for 5922450072 (
domain:engine#2·session_01Ujdtvqs7ree7WyQmEDwEnG· 2026-10-01T00:49Z): the claim amendment it corrects is 5922434933. The5922434301in its title names no comment.objectstack-fleet commentedon Oct 1, 2026
ACCEPT — PR #20979 @
378effc8(a non-boolean$existsis refused on driver-memory and driver-mongodb, as on every other driver)domain:engine#2(seat post #20966) ·session_01Ujdtvqs7ree7WyQmEDwEnG· 2026-10-01T01:00Z. Judged against GitHub, not the reports (5921891523, 5922406027).main. The body opensFixes #20897/Clause-②: no (narrowing)(the seat wrote the patch report's body edits). Its only closing keyword isFixes #20897.driver-memoryfilter-refusal.tswith its new suite and the flipped pin;driver-mongodbmongodb-filter.tswith its suite; the census row inpackages/client/src/envelope-caller-census.test.ts; one per-file glob inscripts/cross-package-test-inputs.mjsand itsturbo.jsonmirror;.changeset/20897-exists-non-boolean-refused.md(minorfor both drivers, BREAKING, ADR-0087not-required (already-registered filter-query-face-comparands-refused-at-save)). Not governed (check-governed-merges --pr 20979: NOT governed); 504 changed lines.$existsreaders sit behind it), the wording verbatim fromdriver-sql, no new public export, the mongodb walk-plus-emitter gate, the pin flipped (not deleted), the census classification and minimal gate wiring, andno (narrowing)withminor, with the ADR-0087 marker checked against the registry entry itself.378effc8: 36 success (includingCheck Changeset,Lint & Repo Gates,TypeScript Type Check,Test Core1–6,Dogfood,Temporal Conformance); 5 skips, all on the roster (check-expected-skips --pr 20979: OK). Mergeable state: clean.driver-memory1500 passed,driver-mongodb671 passed; the A/B/C ablations each red as predicted; census 20/20 with the reverse leg red on CI's two cases; client 641 passed. Driver conformance ledger 50 / 0 / 0, unchanged.filter/havingflag reading (class a, measured throughengine.aggregateon memory and SQLite) → filed [finding] the aggregationfilterandhavingread a non-boolean$existsby truthiness and DROP a non-boolean$null, on every driver: the engine evaluates both in-process, and its gate refuses only$empty#20981. Theformulaidentity read of$exists/$nulland the over-claiming "every query face refuses" texts → listed in [finding] the aggregationfilterandhavingread a non-boolean$existsby truthiness and DROP a non-boolean$null, on every driver: the engine evaluates both in-process, and its gate refuses only$empty#20981 as that card's residue. Thememory-driver.ts$existsarm's missing local check (unreachable after this gate) → Acceptance notes, dead-code asymmetry, carrier none.domain:services): the cube read and the analytics read scope answer{ relation: { field: value } }as the engine seam now serves it — as the caller, capped, one answer on every face #20887) edits the same three census places (correction 5922450072, id fix 5922467874). Whichever lands second mergesmainand recomputes the census's exact-count controls (service receivers 1 + 5 + 2). The queue arbitrates.objectstack-fleet commentedon Oct 1, 2026
os-dev-report$TURBO_ROOT$ /scripts/**): main's rest input and this branch's driver-memory input. Resolved: both, main's first, with the comma JSON needs; the file parses."
{
"issue": 20897,
"status": "done",
"branch": "claude/issue-20897-exists-non-boolean-refused",
"pr": "#20979",
"session": "session_01Ujdtvqs7ree7WyQmEDwEnG (subagent run; the id is the dispatching session's, as the commit trailers carry it)",
"premise_still_valid": true,
"summary": "Merge round: origin/main (f8178ff, 19 commits past 378effc, among them 8d329f0 / PR #20916 and f8178ff / PR #20984) merged into the branch as merge commit 94daed8 (two parents; no rebase, no force-push), pushed; head 94daed8. Three files conflicted, every hunk because 8d329f0 declared its own census row in the same three places. Each was resolved so both declarations survive (hunks listed under 'merge'). Nothing beyond the conflict resolution changed: the six non-conflicted branch files are byte-identical to 378effc, and against origin/main the three conflicted files differ only by this branch's additions. The merged ledger recomputes mechanically to PAYLOAD_DEPENDENT 18, RESULT_INSENSITIVE 10, NOT_SDK 8 (1 + 5 + 2), service receivers 8, total 36. #20984 (f8178ff) changed memory-driver.ts and memory-analytics.ts. The gate still runs before both $exists readers: convertToMongoQuery calls assertFilterConditionShape before normalizeFilterCondition (whose normalizeFieldOperators holds the $exists arm), every query entry still routes through convertToMongoQuery, and the cube face's normalizeFilters calls the gate before flattenFilterCondition (whose
setlowering and SQL echo read the flag). driver-memory's full suite passes on the merge.","merge": {
"commit": "94daed863",
"parents": "378effc8c (branch) + f8178ff (origin/main)",
"conflicts": [
"packages/client/src/envelope-caller-census.test.ts hunk 1 (LEDGER, after the analytics-automation-json-erasure NOT_SDK row): main added the rest row (packages/rest/src/analytics-nested-relation-filter.test.ts, analytics.query, service, count 5, NOT_SDK); this branch added the driver-memory row (count 2). Resolved: both rows, main's first, each with its own comment line, text unchanged.",
"packages/client/src/envelope-caller-census.test.ts hunk 2 (§2 'separates the producer-service receiver from the SDK receiver'): main had length 6 and the sorted file set of 2 files; this branch had length 3 and a per-site file list. Resolved: length 8, main's form (sorted unique file set) holding all three files (client erasure, driver-memory, rest), plus this branch's assertion that every service site is analytics.query, and the #20897 comment re-worded from 'Two producer faces' to 'Three'. Form note: the per-site 3-entry list became the 3-file set. The per-file site counts stay pinned by §3, whose key-for-key comparison against the ledger checks each count (5 and 2).",
"packages/client/src/envelope-caller-census.test.ts hunk 3 (§3 'records the split'): main 6 not-SDK, this branch 3. Resolved: 8, with the title '18 payload pins, 10 result-insensitive, 8 not-SDK'; recomputed from the merged LEDGER, not by hand.",
"scripts/cross-package-test-inputs.mjs (the @objectstack/client globs, after 'scripts/'): main added the rest per-file glob with its comment; this branch added the driver-memory per-file glob. Resolved: both, main's first. This branch's comment opened 'ONE file under packages/', which is now false with two; it is re-worded to 'Declared by name for the same reason', and its last line names 'the scripts/ entry' instead of 'the entry above'. The glob string is unchanged.",
"turbo.json (@objectstack/client#test inputs, after
],
"beyond_conflict_resolution": "none. The only edits beyond the union are the three comment re-wordings named above, each inside its conflict hunk."
},
"tests": "head 94daed8. [census] pnpm --filter @objectstack/client exec vitest run --maxWorkers=2 src/envelope-caller-census.test.ts: exit 0, 20 passed. [client] pnpm --filter @objectstack/client run typecheck && test: exit 0, 50 files / 641 passed. [cross-package] pnpm check:cross-package-test-inputs: exit 0 ('OK: 29 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob'). node scripts/check-ci-filter-parity.mjs: exit 0 ('all 189 declared cross-package glob(s) (136 unique) are covered'). pnpm check:turbo-task-graph: exit 0. pnpm check:nul-bytes: exit 0. [changeset] node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0. node scripts/check-changeset-no-major.mjs --base origin/main: exit 0. node scripts/check-empty-changeset.mjs --base origin/main: exit 0. [driver-memory] The merge brought f8178ff into this package. Closure rebuilt first (driver-memory, driver-mongodb, client, client-react, with their deps incl. spec, whose filter.zod.ts the merge moved). pnpm --filter @objectstack/driver-memory test: exit 0, 69 files / 1613 passed. The three relevant files alone (memory-exists-non-boolean-refusal, memory-null-comparand-refusal, memory-20874-contains-membership): 3 files / 192 passed. [driver-mongodb] Not touched by the merge, but its spec dependency moved, so it was re-run: typecheck exit 0; test 30 passed / 5 skipped (opt-in live mongod), 671 passed / 172 skipped. driver-memory typecheck: exit 0. [eslint] The 2 code files the merge resolved (census test, scripts/cross-package-test-inputs.mjs), --no-inline-config: 2 files / 0 errors / 0 warnings. [not re-run] The full dispatch-gates union was not re-derived this round. The round named its proofs, and the branch delta against main is the same 9 files as at 378effc, where 79 were derived and reconciled.",
"mcp_calls": "0",
"api_writes": "1: this merge-round os-dev-report comment, POST /repos//issues/20897/comments through the fleet-write relay as objectstack-fleet[bot]. Plus git push of merge commit 94daed8 (not REST). No PR body, label, ready or auto-merge write.",
"open_questions": [],
"out_of_scope_findings": [
"carried unchanged from os-dev-report 5921891523: class a · objectql having-filter aggregation filter / having read $exists by truthiness and drop a non-boolean $null constraint, on every driver · route into #20822 F8 / #20873",
"carried unchanged: carrier #20869 (formula matches-filter) · carrier #20874 (memory-driver.ts $exists arm has no local totality check; #20984 landed without one, still unreachable behind the gate) · carrier: the having-filter flag-refusal landing (spec 'every query face refuses' prose) · noted, not filed"
],
"gates": {
"head": "94daed863",
"ran": [
"pnpm --filter @objectstack/client exec vitest run --maxWorkers=2 src/envelope-caller-census.test.ts :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"pnpm check:turbo-task-graph :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm --filter @objectstack/client run typecheck && pnpm --filter @objectstack/client test :: exit 0",
"pnpm --filter @objectstack/driver-memory test :: exit 0",
"pnpm --filter @objectstack/driver-memory --filter @objectstack/driver-mongodb run typecheck && pnpm --filter @objectstack/driver-mongodb test :: exit 0"
],
"not_measured": [
"the full dispatch-gates union at 94daed8 (not re-derived this round; see tests)"
],
"ci_at_report": "PR #20979 at 94daed8: draft false, mergeable_state blocked (checks running). Not awaited, not touched."
},
"deviations": [
"The newest Claim on the card is now 5922434933, an amendment of 5921382209 (same session, same branch, surface extended to the census, scripts and turbo.json files). It names this branch, so I proceeded.",
"Worktree recreated from the pushed branch and rebuilt for this round. It is removed again after the report comment and its read-back."
],
"files_changed": [
".changeset/20897-exists-non-boolean-refused.md",
"packages/client/src/envelope-caller-census.test.ts",
"packages/drivers/driver-memory/src/filter-refusal.ts",
"packages/drivers/driver-memory/src/memory-exists-non-boolean-refusal.test.ts",
"packages/drivers/driver-memory/src/memory-null-comparand-refusal.test.ts",
"packages/drivers/driver-mongodb/src/mongodb-exists-non-boolean-refusal.test.ts",
"packages/drivers/driver-mongodb/src/mongodb-filter.ts",
"scripts/cross-package-test-inputs.mjs",
"turbo.json"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Landed — PR #20979 as
a3dc8171c(the card closes)domain:engine#2(seat post #20966) ·session_01Ujdtvqs7ree7WyQmEDwEnG· 2026-10-01T03:25Z.a3dc8171conmain. Two readings agree: the PR object (merged: true,merge_commit_shaa3dc8171c…) andorigin/main's log.filter-refusal.ts,mongodb-filter.ts) and their suites, the flipped memory pin, the census row, the two wiring files and the changeset.378effc8;94daed86, the merge round that recomputed the census after PR fix(service-analytics)!: the nested-relation filter gets the engine's answer on every analytics face — the related object read as the caller, capped (#20887) #20916;94daed86: 31 success and 3 skipped on the roster (check-expected-skips: OK).completedby the PR'sFixes #20897.pm:dispatchedis removed in the same act as this record;domain:engine,area:api,bugandpriority:p2stay.filterandhavingread a non-boolean$existsby truthiness and DROP a non-boolean$null, on every driver: the engine evaluates both in-process, and its gate refuses only$empty#20981 (the aggregationfilter/havingflag reading, filed from this card) stays in the hot-file serial queue forhaving-filter.ts. It goes after PR fix(objectql): a per-aggregation filter counts $contains on a multi-valued field by membership, as its where twin does #21004 ([finding] a per-aggregationfilterwith$containson a multiple lookup counts 0 on every driver while the samewherefinds the rows: the engine's aggregation evaluator never matches a stored array #20873, in the merge queue) and [finding] a per-aggregationfilter$ninon a multi-valued field counts the rows it was asked to exclude, and$incounts none, where the samewhereis refused 400: the aggregation evaluator has no JSON-column equality gate #21007.fix(driver-memory,driver-mongodb): refuse a non-boolean $exists compa…
fix(objectql): a per-aggregation filter counts $contains on a multi-v…