Repository navigation
filter: the engine's compile surfaces answer $empty by the field's declared type (driver-sql and heirs, turso remote, driver-memory, driver-mongodb, formula, objectql having) — ruling A on #20399 #20444
Description
This card carries the domain:engine lane's arms for the $empty operator. The spec declaration is #20311's (PR #20442); the lowering flip is the flip card filed alongside this one. Filed by domain:spec seat 1 (session_01B3TqpoQbTAfG7G74GMDWNW, seat post #6017) at #20311's ACCEPT, as ruling A's execution parameters order. ⛔ Filed without domain:* / grade: the lane below is the ruling's; triage applies the labels.
Blocked-by: #20311
The rulings, verbatim
- Ruling A on [Decision] How does a filter say 「is empty」 on a multi-value field? A declared
$emptyoperator, or reopen the empty-list refusal (ruling B on #20311, its third arm) #20399 (5865693155): 「One sibling card per compile-surface lane, eachBlocked-by:[Decision] what 「is empty」 means on a text column and on a multi-value column: null only (the spec's lowering today), or null OR''/[]— three objectui builders disagree, and a stored sharing rule's rows depend on the answer #20311's spec PR:domain:engine— driver-sql and its heirs, tursoRemoteTransport, driver-memory, driver-mongodb, formula, objectqlhaving;domain:services— service-analytics' two faces. The two faces with no field declarations (the formula matcher, objectqlhaving) judge by value, diverging only on a non-text column holding''(the write-door class record write door:''skips every type check, so a number, boolean, date, datetime or time column stores an empty string — normalise it to null at the door (seam from objectui#10813) #20308 closed).」 - The maintainer's amendment (recorded on [Decision] what 「is empty」 means on a text column and on a multi-value column: null only (the spec's lowering today), or null OR
''/[]— three objectui builders disagree, and a stored sharing rule's rows depend on the answer #20311 as5868169573): 「照 $like 先例分阶段」.$emptystays out ofFILTER_OPERATORSuntil every surface has its arm; the flip card adds it.
What each surface does
Call the spec's single expansion, not a private reading: expandEmptyOperator(fieldDef) and EMPTY_OPERATOR_ARMS in @objectstack/spec/data (filter-empty-operator.ts, from PR #20442).
- The faces that know the declared type (driver-sql, turso
RemoteTransport, driver-memory, driver-mongodb) expand{ f: { $empty: true | false } }into the declared arm:- text-like: null or
''; - multi-value: null or
[]; - every other type: null only.
$empty: falseis the exact complement. An empty list is tested as a stored value, never as a$eq: []comparand (ruling 乙 on [finding] the comparand-SHAPE face declares it closes the door "for every driver at once", but an array in the IMPLICIT-EQUALITY slot passes it — anddriver-mongodbalone answers it, as an exact-array match #19757 stands).
- text-like: null or
- The faces with no declarations (formula
matchesFilterCondition, objectqlhaving) judge by value throughisEmptyFilterValue(value): null, undefined,''and[]count as empty.
Measured at dispatch of #20311 (PR #20442's premise A1, before any arm)
Every face in this lane refuses $empty with INVALID_FILTER / 400, except formula matchesFilterCondition, which answers a silent false (its fail-closed posture for an operator it has no arm for). That silent false falsifies formula's own docblock (packages/formula/src/matches-filter.ts header), which says a DECLARED operator never gets it. This card corrects that docblock with the arm.
Acceptance
- Each surface above has a
$emptyarm reading the spec expansion. The pins cover a text, a multi-value and a scalar field (null,'',[]and a value row),$empty: false, and nesting under$and/$or/$not. - The shared filter conformance table (
FILTER_LOGIC_CASESor its successor) carries the$emptyrows, so the surfaces cannot drift.havingstates its conclusion explicitly, because it is not covered by that table. - ⛔ This card does not add
$emptytoFILTER_OPERATORSand does not flip the lowering: both are the flip card's.
Dedupe: $empty has 0 occurrences in objectstack and objectui before PR #20442 (measured by #20311's dev). The only prior card on the question is #20399 (ruled). Dedupe words: $empty operator driver arm · is empty compile surface engine.
Activity
objectstack-fleet commentedon Sep 28, 2026
Triage: labels applied —
enhancement·priority:p2·domain:engine·area:records.pm:blockedstays (Blocked-by: #20311)Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T14:06Z. ⛔ Not a claim, ⛔ not a dispatch.$emptyoperator, or reopen the empty-list refusal (ruling B on #20311, its third arm) #20399 (5865693155) namesdomain:enginefor exactly these surfaces: driver-sql and its heirs, tursoRemoteTransport, driver-memory, driver-mongodb, formula, and objectqlhaving. Triage applies it as written.area:records, inherited from the parent [Decision] what 「is empty」 means on a text column and on a multi-value column: null only (the spec's lowering today), or null OR''/[]— three objectui builders disagree, and a stored sharing rule's rows depend on the answer #20311 ([Decision]· p2 ·area:records). This is ruled execution of a declared operator, so it isenhancement, not a defect.''/[]— three objectui builders disagree, and a stored sharing rule's rows depend on the answer #20311 (PR feat(spec): declare the staged $empty filter operator and its per-type expansion (#20311) #20442, the spec declaration andexpandEmptyOperator). Release when [Decision] what 「is empty」 means on a text column and on a multi-value column: null only (the spec's lowering today), or null OR''/[]— three objectui builders disagree, and a stored sharing rule's rows depend on the answer #20311 closes with PR feat(spec): declare the staged $empty filter operator and its per-type expansion (#20311) #20442 merged, after reading the merged export names.5868169573(「照 $like 先例分阶段」) holds: this card adds arms only. ⛔ NoFILTER_OPERATORSentry and no lowering flip; the flip card owns both.objectstack-fleet commentedon Sep 28, 2026
Unlock:
pm:blocked→pm:queue. #20311 closed with PR #20442; the spec's single expansion is onmainTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T18:10Z. ⛔ Not a claim, ⛔ not a dispatch.enhancement· p2 ·domain:engine·area:recordsunchanged.''/[]— three objectui builders disagree, and a stored sharing rule's rows depend on the answer #20311 closedcompletedwith PR feat(spec): declare the staged $empty filter operator and its per-type expansion (#20311) #20442, merged 2026-09-28T17:07Z.origin/main3062e5001inpackages/spec/src/data/filter-empty-operator.ts:EMPTY_OPERATOR_ARMS(:74);expandEmptyOperator(field: ValueShapeFieldDef)(:92), for the faces that know the declared type;isEmptyFilterValue(value, expansion?)(:113), for the formula matcher andhaving.FILTER_OPERATORSentry and no lowering flip.$empty) closed at the same landing.objectstack-fleet commentedon Sep 28, 2026
Cross-lane notice from
domain:services: the analytics faces'$emptyarms (#20445 → PR #20498) and what they leave to this carddomain:servicesseat (#6021) ·session_017B6YKCGu8CTY2KBWgwaHAs· 2026-09-28T19:16Z. ⛔ Not a claim, and nothing is relabelled. This is for whoever claims this card.$emptyarms:compileScopedFilterToSql, and the analytics where lowered to a valueless empty/notEmpty leaf compiled by the NativeSQL strategy and the ObjectQL echo. Both callexpandEmptyOperator.FILTER_OPERATORSand the lowering are untouched.{ $empty }to the engine, which refuses itINVALID_FILTER/ 400. The/analytics/sqlecho meanwhile renders the declared arm, so no face drops the predicate and no wrong rows are served. When this card's arm lands, execute and echo agree with no further change in service-analytics.$emptyon the ObjectQL execute face is also refused by driver-sql asINVALID_FILTER/ 400, notREAD_SCOPE_COMPILE_FAILED/ 500, becausejudgeFilteradmits it (measured by the analytics: service-analytics' two filter faces answer$emptyby the field's declared type (read-scope SQL, the analytics where) — ruling A on #20399 #20445 dev). It has no in-repo producer today (CELis_emptyemits$null), and it closes when the driver arm lands.packages/services/service-analytics/**and a changeset.Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Claim: PM loop round 23
Session:
session_01N8TPEsoJxPsdSdNKGnNGENAccount:
os-warren(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-20444-empty-operator-engine-armsWorktree:
objectstack-issue-20444Domain:
domain:engineSeat:
domain:engine#1File surface (the
$emptyarms of ruling A on #20399, 5865693155; the spec's single expansion landed with #20311 as PR #20442):expandEmptyOperator(fieldDef)/EMPTY_OPERATOR_ARMSfrom@objectstack/spec/data:packages/drivers/driver-sql/src/sql-driver.ts's filter compile (its heirs driver-sqlite-wasm and turso local inherit it);packages/drivers/driver-turso/src/remote-transport.ts;packages/drivers/driver-memory/src/memory-matcher.ts(andfilter-refusal.tswhere it lists the operators it refuses);packages/drivers/driver-mongodb/src/mongodb-filter.ts;isEmptyFilterValue(value):packages/formula/src/matches-filter.ts(its header docblock corrected with the arm) andpackages/objectql/src/having-filter.ts;packages/spec/src/data/filter-logic-conformance.ts, the sharedFILTER_LOGIC_CASEStable, gaining$emptyrows only (test data, a declared cross-lane edit; the spec seat is told on [PM seat] domain:spec — ⏳ vacant #6017);'',[]and a value row),$empty: false, and nesting under$and/$or/$not, plus an explicithavingconclusion;.changeset/20444-*.md.Stop on breach and explain in the report. ⛔ No
FILTER_OPERATORSentry and no lowering flip: both belong to the flip card (the maintainer's 「照 $like 先例分阶段」, 5868169573). ⛔ No private reading of emptiness: every arm calls the spec expansion. ⛔ Not service-analytics (PR #20498,domain:services). ⛔ Notsql-driver.ts'syncDeclaredIndexesregion (#20432, claim 5877196132, in flight).Container & model:
L,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)Clause-②: yes (widening)
Thread-read: 5876791510
Clause-② note: each driver's and matcher's own filter accept set widens to take the staged operator (it answers
INVALID_FILTER/ 400 today). The engine'sFILTER_OPERATORSkeeps refusing it until the flip card. Ruling A routes these arms todomain:engine; the at-tier review is owed.Serial constraints cleared: at 2026-09-28T20:44Z, a census of all 10 open PRs' file lists finds none on this surface's source files. PR #20504 (#20437) edits
driver-turso's constructor and config files, notremote-transport.ts. PR #20458 (#20300,domain:spec) edits driver-memory analytics test files only. #20432's dev (no PR yet) holdssql-driver.ts' index-sync region, not the filter compile; the later lander mergesmain.objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{
"issue": 20444,
"status": "done",
"branch": "claude/issue-20444-empty-operator-engine-arms",
"pr": "#20523",
"session": "session_01N8TPEsoJxPsdSdNKGnNGEN",
"premise_still_valid": true,
"summary": "Every compile face of the domain:engine lane now answers the staged $empty operator through the spec's one expansion from PR #20442. Declared row (expandEmptyOperator): driver-sql applyFilterCondition (so driver-sqlite-wasm and turso local), turso RemoteTransport (declaration handed down by TursoDriver via setDeclaredValueShapeResolver), driver-memory's live mingo path, driver-mongodb translateFilter and its aggregate $match; by value (isEmptyFilterValue): formula matchesFilterCondition, driver-memory's reference matcher, objectql having and the per-aggregation filter. SQL multi-value row: SQLite json_valid-guarded json_type/json_array_length, PostgreSQL jsonb equality with [], MySQL JSON_TYPE/JSON_LENGTH, refused on an unmodelled dialect; every predicate total, $empty: false the exact complement; an undeclared field and a non-boolean flag are refused INVALID_FILTER/400 on the query faces, never guessed. FILTER_LOGIC_CASES gains 7 $empty rows (every consumer censused and passing), formula's header docblock is corrected (H6), having's by-value conclusion is pinned (0 not empty, groupBy '' empty); FILTER_OPERATORS and the is_empty lowering are untouched.",
"tests": "Head measured 436a10a (after a true merge of origin/main). New pins: sql-driver-20444-empty-operator (live dialect matrix; SQLite measured, PG/MySQL declared to the Temporal Conformance job, NOT MEASURED locally: no server reachable), turso-20444-empty-operator (local and remote one row set, count), memory-20444-empty-operator, mongodb-20444-empty-operator (live-mongod half skipped: opt-in server absent), matches-filter-empty-operator, having-empty-operator; extended seam tests (driver-sql 3 builders, turso remote 2 methods) and driver-memory's clobber sweep. Full suites (pre-merge head ea3d959, verify lock): driver-sql 197 files passed, 1 failed (seam enumeration, fixed here, file re-run 107 passed), 11 skipped; driver-turso 77 files 2080 passed; sqlite-wasm 36 files 665 passed; driver-memory 59 files 1419 passed; driver-mongodb 29 passed 5 skipped, 656 tests; formula 42 files 1227 passed; objectql --project local 332 files 6636 passed; service-analytics 134 files 3165 passed. On 436a10a: typecheck exit 0 for driver-sql, driver-turso, driver-memory, driver-mongodb, formula, objectql, service-analytics (spec typecheck green pre-merge); $empty suites and every FILTER_LOGIC_CASES harness re-run green (sql 154 passed 4 skipped, turso 240, wasm 37, memory 194, mongodb 65 passed 50 skipped, formula 43, objectql 36, analytics 72, spec 73). Ablations via scripts/ablation-replace.mjs on committed tree with restore trap, each restored blob == HEAD and git diff HEAD empty: A1 driver-sql text arm without the '' limb: 4 red, shared FILTER_LOGIC rows stayed green (they do not discriminate the text row, as stated); A2 driver-memory multi-value as $in [null, []]: 8 red; A3 formula arm removed: first attempt a void run (replacement already in the anchor, tool refused, restored), re-run: 13 red incl. all 7 shared $empty rows. No dist preflight needed: each ablated subject is imported by its own suite through a relative src path. check:driver-conformance before and after: 50 covered, 0 DEBT, 0 exempt.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3 — each one repository_dispatch through the fleet relay (scripts/pm/fleet-write/dispatch.mjs), executed as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft, PR #20523); POST /repos//issues/20523/assignees (label-write --assign os-warren); POST /repos//issues/20444/comments (this os-dev-report via post-stamped.mjs). Plus git push of the branch (not a REST write). issue_patch on the PR: 0.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 436a10a derived 91 commands; all 91 ran, exit codes recorded before any pipe: 89 exit 0, 2 exit 3 (PREREQUISITE NOT MET, need a whole-workspace build) = NOT MEASURED: check:dual-build-cjs-loads, check:type-check-debt. --ran: 91 derived famil(ies) accounted for, 89 run, 2 NOT-MEASURED, 0 UNRUN. Narrowed probe for dual-build: the built CJS entry of each changed package loads under require. Lint narrowed: eslint.config.mjs lints packages/**/*.{ts,tsx,mts,cts} with no type-aware parsing (no parserOptions.project), so untouched files cannot move; eslint --no-inline-config --format json over the 26 changed .ts files: 26 entries, 0 errors, 0 warnings. CI: in_progress at report time.",
"line_budget": "1990 changed lines (+1965 / -25) across 27 files vs merge base 0bbe400 — under the 5000 human-merge threshold; no governed surface touched.",
"deviations": [
"CONFLICT named, not silently resolved: the order bars service-analytics, while H4 requires every FILTER_LOGIC_CASES consumer to pass the new rows or partition them. Since PR #20498 (merged) both analytics faces answer $empty only when handed the field's declaration, so read-scope-sql-conformance.test.ts and native-sql-filter-logic-conformance.test.ts now pass a text declaration for the fixture (test-only, +13/-1; no service-analytics source moved) and pass the rows rather than partition them. The services seat should see this.",
"packages/spec/src/data/filter-logic-conformance.ts: 7 rows plus a header paragraph explaining what the rows do and do not pin (the claim names the file; a declared cross-lane test-data edit). @objectstack/spec carries a minor line in the changeset because FILTER_LOGIC_CASES ships in the package.",
"driver-sql and turso remote refusal seam tests, driver-memory's operator-key clobber sweep and driver-mongodb's translation harness are edited beyond the new pins: each enumerates the operator vocabulary or its refusal builders and had to enrol $empty (the driver-sql seam test gains an optional per-row setup hook to reach the unknown-dialect refusal).",
"Changeset lines: driver-sql, driver-turso, driver-memory, driver-mongodb, formula, objectql, spec, all minor under Clause-② yes (widening); driver-sqlite-wasm has no line (no source moved; it inherits driver-sql)."
],
"files_changed": [
".changeset/20444-empty-operator-engine-arms.md",
"packages/drivers/driver-memory/src/filter-refusal.ts",
"packages/drivers/driver-memory/src/memory-20444-empty-operator.test.ts",
"packages/drivers/driver-memory/src/memory-driver.ts",
"packages/drivers/driver-memory/src/memory-matcher.ts",
"packages/drivers/driver-memory/src/memory-operator-key-clobber.test.ts",
"packages/drivers/driver-mongodb/src/index.ts",
"packages/drivers/driver-mongodb/src/mongodb-20444-empty-operator.test.ts",
"packages/drivers/driver-mongodb/src/mongodb-aggregation.ts",
"packages/drivers/driver-mongodb/src/mongodb-driver.ts",
"packages/drivers/driver-mongodb/src/mongodb-filter-logic-translation.test.ts",
"packages/drivers/driver-mongodb/src/mongodb-filter.ts",
"packages/drivers/driver-sql/src/sql-driver-20444-empty-operator.test.ts",
"packages/drivers/driver-sql/src/sql-driver-compile-refusal-seam.test.ts",
"packages/drivers/driver-sql/src/sql-driver-or-filter.test.ts",
"packages/drivers/driver-sql/src/sql-driver.ts",
"packages/drivers/driver-turso/src/remote-transport-compile-refusal-seam.test.ts",
"packages/drivers/driver-turso/src/remote-transport.ts",
"packages/drivers/driver-turso/src/turso-20444-empty-operator.test.ts",
"packages/drivers/driver-turso/src/turso-driver.ts",
"packages/formula/src/matches-filter-empty-operator.test.ts",
"packages/formula/src/matches-filter.ts",
"packages/objectql/src/having-empty-operator.test.ts",
"packages/objectql/src/having-filter.ts",
"packages/services/service-analytics/src/tests/native-sql-filter-logic-conformance.test.ts",
"packages/services/service-analytics/src/tests/read-scope-sql-conformance.test.ts",
"packages/spec/src/data/filter-logic-conformance.ts"
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: the flip card · noted, not filed — packages/spec/src/data/filter.zod.ts' FILTER_OPERATORS TSDoc table still says no face answers $empty and lists each face as refusing it, and filter-empty-operator.ts' header still says nothing in the repository calls the expansion; both already stale after PR #20498, staler now; the flip card rewrites that paragraph when it adds the operator.",
"carrier: 承接者:无 · noted, not filed — formula's matchesFilterCondition has accepted declared columns (options.fields: type and multiple) since PR #20427, after ruling A; with them it could answer $empty by the declared row as the read side of the same RLS policy does. Kept by-value per the ruling and the card; the two part only on a stored state the declaration does not predict (e.g. '' in a non-text column).",
"carrier: the flip card · noted — the engine's front door is the one remaining refusal on the ObjectQL execute path PR #20498 names; driver-memory's analytics face refuses $empty exactly as it refuses $null, so the flip moves nothing there."
]
}
objectstack-fleet commentedon Sep 28, 2026
Seat answer to the os-dev report on #20444 (PR #20523)
domain:engine#1·session_01N8TPEsoJxPsdSdNKGnNGEN(os-warren) · written 2026-09-28T22:46Z. ⛔ Not a claim. Claim 5878203784 stands, and this answer admits the files named below beside it.read-scope-sql-conformance.test.tsandnative-sql-filter-logic-conformance.test.ts(+13 / −1) hand the fixture a text declaration, so the two analytics faces (which answer$emptyonly when handed the field's declaration, since PR fix(service-analytics): both filter faces answer $empty by the field's declared type (#20445) #20498) pass the newFILTER_LOGIC_CASESrows rather than partitioning them. No service-analytics source moves. The dispatch order's H4 asked every consumer to pass or partition explicitly, and passing is the stronger answer. The services seat is told on [PM seat] domain:services — ⏳ vacant #6021.$emptyrows pin and do not) is accepted: the claim names that file, and the edit is test data plus its description.@objectstack/specgets aminorline becauseFILTER_LOGIC_CASESships in the package.minorunderClause-②: yes (widening), no line for driver-sqlite-wasm, whose source did not move) are accepted as reported. The at-tier review judges them.docs(spec): leave object.zod.ts and filter-logic-conformance.ts to th…
objectstack-fleet commentedon Sep 28, 2026
ACCEPT — PR #20523 at
436a10a3e9c7ac73ddc9983ad770e4b7469a1b72domain:engine#1·session_01N8TPEsoJxPsdSdNKGnNGEN(os-warren) · written 2026-09-28T23:05Z. Contract review of record: 5880315891 on PR #20523, at-tier, read-only, PASS on this head.Checklist, verified against GitHub rather than the reports:
Form: draft, base
main, first lineFixes #20444, and that is the only closing keyword in the body. Claim 5878203784 names the branchclaude/issue-20444-empty-operator-engine-armsand readsClause-②: yes (widening), which is also the line in the changeset and the PR body.Scope: 27 files, +1965 / −25:
expandEmptyOperator: driver-sql's filter compile (inherited by driver-sqlite-wasm and turso local), tursoRemoteTransport, driver-memory's live path and driver-mongodb'stranslateFilterplus its aggregate$match;isEmptyFilterValue: formulamatchesFilterCondition, driver-memory's reference matcher, and objectqlhavingwith its per-aggregationfilter;FILTER_LOGIC_CASES, which gains seven$emptyrows plus a header paragraph;Every file is inside claim 5878203784 plus the seat answer 5880101190. That answer admits the two service-analytics test files (+13 / −1, test only), the spec header, the enrolment edits and the changeset lines.
Five files carry the declaration to arms the claim names. This ACCEPT admits them as in-package wiring, and the review read each one:
driver-memory/src/memory-driver.ts(thevalueShapesmap and itssyncSchemafill);driver-mongodb/src/mongodb-driver.ts;mongodb-aggregation.ts;driver-mongodb/src/index.ts;driver-turso/src/turso-driver.ts(wiressetDeclaredValueShapeResolver).⛔
filter.zod.ts,FILTER_OPERATORSand theis_empty/is_not_emptylowering are untouched: they stay staged for the flip card filter: flipis_empty/is_not_emptyto$emptyand add it toFILTER_OPERATORS, once every compile surface answers it (the last step of ruling A on #20399) #20446. [finding] a misspelt field name in a field'srelatedListColumns,lookupColumns,lookupFilters[].fieldordependsOnpasses every authoring door, and fails only at view or picker time #20432's region (syncDeclaredIndexes) is also untouched.Changeset: seven packages
minorunderClause-②: yes (widening). They are driver-sql, driver-turso, driver-memory, driver-mongodb, formula, objectql and spec (FILTER_LOGIC_CASESships in spec). driver-sqlite-wasm has no line because its source did not move; it sits in thefixedgroup.Check Changesetissuccess.Governed surface: none.
check-governed-merges --pr 20523finds 0 of 27 paths on the register. The PR is 1,990 changed lines, under the human-merge threshold.CI: 35 check-runs on the head: 32
success, 3skipped(Build Docs, Console Pin Gate and the opt-in packed-tarball smoke), 0 failures.Temporal Conformance (live PG + MySQL)runs the whole driver-sql suite against both live dialects, including this PR'sDIALECT_CELLSpins, and it issuccess. This closes the dev's own "locally not measured on PG / MySQL" flag.Behaviour: a driver or evaluator called directly now answers
$emptyby the field's declared type, or by value where no declaration exists:'';[](on SQL, tested as stored JSON per dialect, never as a comparand);$empty: falseis the exact complement.An undeclared field, a standalone translator, an unmodelled SQL dialect and a non-boolean flag are refused with
INVALID_FILTER/ 400 on every query face. The engine's front door still refuses$emptyuntil filter: flipis_empty/is_not_emptyto$emptyand add it toFILTER_OPERATORS, once every compile surface answers it (the last step of ruling A on #20399) #20446.Carried out of this card (acceptance notes, carrier none):
falseinstead, which is its standing answer to an unevaluable condition. The PR body says this and the changeset does not: a one-clause prose gap with no level effect.minor:DeclaredValueShapeResolveris exported fromremote-transport.tsbut not from the driver-turso index, so the method's parameter type is usable but not nameable from the package entry;SqlDriver.valueShapeFieldsanddeclaredValueShapeareprotectedon an exported class.$emptypin of its own. Its multi-value construct is the inherited compiler unchanged, on the same JSON1 functions that already serve$contains.Carried to the flip card #20446: posted there as a cross-lane notice. It covers the
filter-empty-operator.tsheader sentence that the flip makes false, the re-measure of engine-injected audit, tenant and owner columns, and the document faces' null-equality over a list that holds a null element.Landing:
ready, then auto-merge through the queue. The merge closes this card (Fixes). The seat then verifies the squash onmainand removespm:dispatchedin the same act.objectstack-fleet commentedon Sep 28, 2026
Landing record: PR #20523 merged. This card is closed
completedby itsFixeslinedomain:engine#1·session_01N8TPEsoJxPsdSdNKGnNGEN(os-warren) · written 2026-09-28T23:25Z.Verified on
main:fb386074f57b234c98c40938aae7a0486ad50e8b, merged 2026-09-28T23:25Z by the queue, with one parent. It is an ancestor oforigin/main, and theorigin/maintip is the squash itself.436a10a3e's changes against its merge base0bbe4005e(the same md5 over every added and removed line).applyEmptyOperatoris present at the squash and absent at its parent, and so is the20444-empty-operator-engine-armschangeset.Fixes #20444, so no other card was closed.Delivered: every
domain:enginefilter face, called directly, answers the staged$emptyoperator through the spec's single expansion. The declared-type faces are driver-sql (and so driver-sqlite-wasm and turso local), turso remote, driver-memory's query path and driver-mongodb. The by-value faces are formulamatchesFilterCondition, driver-memory's reference matcher and objectqlhaving.'', a multi-value field when it is null or[](tested as stored JSON per SQL dialect), and any other type only when it is null.$empty: falseis the exact complement.INVALID_FILTER/ 400.$emptystays staged:FILTER_OPERATORSand theis_empty/is_not_emptylowering are unchanged, so the engine's front door still refuses it.minor,Clause-②: yes (widening).Carried out of this card:
is_empty/is_not_emptyto$emptyand add it toFILTER_OPERATORS, once every compile surface answers it (the last step of ruling A on #20399) #20446 (domain:spec,Blocked-by: #20444and#20445), in the cross-lane notice 5880345266:filter-empty-operator.tsheader sentence, which is now false;This card's blocker is now closed. The unlock is triage's to read.
falseanswer for a non-boolean flag; the "New optional API" list is not a full inventory; driver-sqlite-wasm has no per-type pin of its own.pm:dispatchedis removed in the same act as this record. The domain, area and type labels stay.fix(service-analytics): both filter faces answer $empty by the field'…
feat(drivers,formula,objectql): the engine's filter faces answer the …
docs(spec): re-anchor the dead tracker citations in data/ to the comm…
fix(spec): conformance-case notes and names state each decision in wo…