Repository navigation
objectql: an engine where that is a string, number or Map is dropped — engine.find({ where: 'amount > 100' }) returns every row, and a non-filter array is refused with no code or status; the same seam serves update and delete #20121
Description
Activity
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsPath: an API a customer can call | api-backend.query-contract-matrix | P2
Triage: first grade —
bug·priority:p1·domain:engine·area:api·pm:queueTriage: lands in
packages/objectql/src/engine.tslowerWhereFilterArray(the seam every caller-suppliedwherecrosses onfind/findOne/count/aggregate/update/delete) ⇒domain:engine; rationale: a string, number orMapwhereis dropped and the read returns every row, with nothing raised. The same seam runs beforeupdateanddeletedecide between the by-id and the multi-row path, so a malformedwherefrom any in-process caller (a hook, a flow, a plugin — code an AI writes) may rewrite or remove every row the caller can reach. Data integrity outranks the road ⇒ p1, with the escalation below.Triage seat #6015 ·
session_01CRZSc7dU8oDStbTbSwhuZe· 2026-09-25T08:44Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, andorigin/main.Execution notes (the order matters)
- Measure the write verbs first: a string / number /
Mapwhereon a multi-rowupdateanddelete, ondriver-memoryand a SQL driver. If either touches every reachable row, re-grade this card p0 on the card with the reading and land the refusal before anything else. - Refuse at the top of
lowerWhereFilterArrayanywherethat is neitherundefined, a plain filter object nor a FilterArray:INVALID_FILTER/ 400 in the ADR-0112 envelope, in the wire door's words; give the non-filter-array branch the same envelope. - Pin all six verbs with the four bad shapes, asserting code, status and that no driver call is made.
- Measure the write verbs first: a string / number /
- addedarea:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2and removed
on Sep 25, 2026 objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsClaim: PM loop round 22
Session:session_01Bvd69VPa6puiNzzPUroDBx
Account:os-sales(the seat's linked user asGET /useranswers it; always the card's assignee)
Branch:claude/issue-20121-where-shape-refused
Worktree:objectstack-issue-20121
Domain:domain:engine
Seat:domain:engine#1
File surface:packages/objectql/src/engine.ts:lowerWhereFilterArray(a refusal at its top for awherethat is neitherundefined, a plain filter object nor a FilterArray) and its non-filter-array branch (the same ADR-0112 envelope). The six call sites (find,findOne,update,delete,count,aggregate) only if the refusal cannot live in the seam;- tests in
packages/objectql, plus a dogfood pin if a verb needs a real SQL driver to measure; .changeset/20121-*.md.
Stop on breach and explain in the report. ⛔ Not the REST wire door (it already answers
INVALID_FILTER/ 400; its words are the ones to reuse). ⛔ Not the drivers' ownwherehandling. ⛔ Not thefilteralias (#4346). ⛔ Not thehavingregion (landed with PR #20117) or the docblock hunk draft PR #20125 edits.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)
Clause-②: no (narrowing)
Thread-read: 5829557014
Serial constraints cleared: at 2026-09-25T09:45Z, a census of the 7 open PRs finds one onengine.ts: draft PR #20125 (#20102), whose only hunk is a docblock near:1245. That is disjoint fromlowerWhereFilterArray(:863onmaina08e059c61). Whichever lands second mergesmainfirst. No in-flight claim of this lane touchesengine.ts: #20082 landed asa08e059c61, and #20024 (ii) is in the drivers.objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsos-dev-report
{
"write_verbs": "WHOLE-TABLE REWRITE AND WHOLE-TABLE DELETE at base a08e059. Measured before any edit, real ObjectQL, 4 rows seeded (2 per owner), driver-memory and SqlDriver (better-sqlite3 :memory:), rows read back past the engine. update(o, {name:Z}, {where, multi:true}) with where = string / number / Map: 4 of 4 rewritten on BOTH drivers without SecurityPlugin, and 4 of 4 with SecurityPlugin under a system context. delete(o, {where, multi:true}), same shapes: 4 of 4 deleted on both drivers, same two legs. With SecurityPlugin and an RLS-scoped member caller: the security middleware AND-composes the value into $and and the DRIVER refuses it (INVALID_FILTER/400), 0 rows touched, so "every row the caller can reach" is CONFIRMED without SecurityPlugin and under a system context, and REFUTED for an RLS-scoped caller (refused, not widened). where [1,2,3]: refused on every leg with code/status undefined, 0 rows. Without multi: refused on every leg by the dispatch rule ("Update/Delete requires an ID or options.multi=true", no code), 0 rows. Controls (filter object, FilterArray, multi): 2 of 4 without RLS, 1 of 4 for the member. Path: the MULTI-ROW path, identical on both drivers. The [#5158] branch that decides it is the lowering call in update() ("Lower before the by-id extraction below reads where.id") and delete() ("Same ordering reason as update()"), after which resolveEngineUpdateDispatch / resolveEngineDeleteDispatch find no where.id in a non-object where and return kind multi on options.multi. The string rides the seeded AST and the driver updateMany/deleteMany ignores it. The same value also stepped past the unscoped-write guard (dispatchUnscopedMultiWriteHooks, the sys_attachment / sys_comment predicate-less-write refusals), which reads only an absent or null where as unscoped. Head: every one of these cells is INVALID_FILTER/400 with 0 driver calls and 0 rows touched on every leg.",
"issue": 20121,
"status": "done",
"branch": "claude/issue-20121-where-shape-refused",
"pr": "#20144",
"session": "session_01Bvd69VPa6puiNzzPUroDBx",
"premise_still_valid": true,
"summary": "H1 reproduced at a08e059 on driver-memory and SqlDriver: a string/number/Map where made find/count/aggregate answer every row, findOne the first row (Map: its no-predicate guard refused, no code), and [1,2,3] was refused with code/status undefined. H2 is a whole-table rewrite/delete (write_verbs), for the seat to re-grade p0. Fix, in packages/objectql/src/engine.ts lowerWhereFilterArray only: a shape gate at the top of the seam refuses a where that is not undefined, null, an array or a filter object ([object Object] tag), via the package's existing invalidFilterError (INVALID_FILTER, status 400, httpStatus 400), in the wire door's words with a per-verb consequence clause; the non-filter-array branch now throws through the same builder, message unchanged. All six verbs lower before resolving a driver, so no driver call is made. No new export, no new code, no driver/REST/filter-alias/having change, #20125's docblock untouched. Changeset: objectql minor, Clause-2 no (narrowing), BREAKING banner, ADR-0087 not-required (no-migration-prescription), both gates green. PR assignee set to the card's (os-sales); the card assignee was already os-sales at pickup and was not written.",
"tests": "All at head aa923a1 unless noted. New unit pin packages/objectql/src/engine-where-shape-refusal.test.ts (47 cases: 6 verbs x 4 bad shapes asserting code, status, VERB('deal') prefix, 0 driver calls and an unwritten table; 6 x 2 controls; scoped-repository door; 5 accepted and 5 refused edge shapes), run with engine-filter-array-lowering.test.ts: "Test Files 2 passed (2) / Tests 107 passed (107)". New dogfood pin packages/qa/dogfood/test/engine-where-shape-refusal.test.ts (SqlDriver, update/delete multi x 4 bad shapes read through knex + 2 controls): against the BASE objectql dist "8 failed | 2 passed" (6 "expected the engine to refuse, and it answered", 2 "expected undefined to be INVALID_FILTER"); rebuilt: "10 passed", rerun at head "10 passed". objectql whole suite (both projects) at 6485915: "Test Files 317 passed (317) / Tests 5499 passed (5499)"; only the new test file changed after it (one repository case; the double honours limit and refuses combinators), engine.ts did not. plugin-security whole suite at 6485915 against the rebuilt objectql dist: "Test Files 135 passed (135) / Tests 2685 passed (2685)". Dogfood engine-where subset at 6485915 (declared narrowing; CI runs the full gate): new pin + attachments-unscoped-delete-gate, bulk-widener-probe, owner-anchor-and-bulk-writes, authored-row-write-scope, comments-permission-matrix, attachments-permission-matrix, owd-public-read-write-write-floor, hook-runas-fls, showcase-crud-persona-matrix: "Test Files 10 passed (10) / Tests 131 passed | 1 skipped (132)" (the skip is the pre-existing describe.skipIf(!organizationsAvailable)). Typecheck: objectql run typecheck green incl. check:test-typecheck (ledger held 40 files/234 errors/65 signatures; new file in tsconfig.test.json program per --listFiles, 1 hit); dogfood typecheck green. Ablation, fix committed first, via scripts/ablation-replace.mjs (anchor hit 1->0 on disk, blob changed, restore proven blob == HEAD and git diff HEAD empty; objectql unit suites read src, no dist leg needed): (1) shape gate -> if (false): at 544762f "23 failed | 23 passed (46)" = the 18 string/number/Map cells + 5 refused edges, array cells green on the array branch (predicted direction, observed); rerun at aa923a1 "24 failed | 23 passed (47)" (+ the repository case). (2) array-branch invalidFilterError( -> new Error(: "6 failed | 40 passed (46)", exactly the 6 array cells. Head probe (scratch, both drivers, security off/member/system): every bad shape INVALID_FILTER/400, 0 driver calls, 0 rows; every accepted shape answers as at base.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3 — all through the fleet-write relay as objectstack-fleet[bot]: (1) pr_create POST /repos/objectstack-ai/objectstack/pulls (draft, #20144), run 36126888503; (2) label-write assign POST /repos//issues/20144/assignees [os-sales], run 36126966424; (3) this os-dev-report comment POST /repos//issues/20121/comments. Plus git pushes (not REST): 5 pushes to claude/issue-20121-where-shape-refused (the empty-branch probe, then 4 WIP pushes). Reads: unauthenticated REST GETs of #20121, its comments, the PR list and PR #20144.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · aggregate aggregations[].filter drops a string exactly as where did: base and head, driver-memory, aggregations [{function count, field id, alias n, filter "amount > 100"}] counts every row per group (control {amount:{$gt:100}} counts 1 per group); spec AggregationNodeSchema.filter is FilterConditionSchema. It is its own loop ([#10576] in aggregate()), not lowerWhereFilterArray, so outside this claim. No reach: measured (the wire parses aggregations through AggregationNodeSchema; analytics ObjectQLStrategy produces objects), so noted in the PR Acceptance notes, not filed · dedupe words: aggregations filter string dropped · per-aggregation filter non-object · aggregate filter scalar unfiltered",
"carrier: 承接者:无 · a beforeFind hook assigning ctx.input.ast.where, or a middleware assigning opCtx.ast.where, after the seam is not re-checked: a string there reads every row (census, base and head). The value is hook/middleware-authored, no reach: measured, so Acceptance notes only · dedupe words: hook rewritten where unchecked · middleware ast.where string"
],
"gates": "Derived at aa923a1 with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (4 paths vs merge base a08e059; first stderr line names objectstack-ai/objectstack at aa923a1): 68 commands, ALL exit 0 (captured before any pipe); --ran reconciliation with "COMMAND :: exit CODE" lines: "68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero. First pass at c51cd3d: 2 real reds, both in my new double (check:objectql-double-limit: find ignored limit; check:where-matcher: combinator read as a field name), fixed in aa923a1; check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (8 unrelated packages without dist) - not a red; built them and it reran green ("105 published require entry point(s) across 67 package(s) load"). node scripts/check-issue-citations.mjs --base a08e059: exit 0, 6 citations judged, all resolve. check-changeset-no-major --base a08e059 exit 0; check-adr-0087-registration --base a08e059 exit 0. All 51 commands of the PM clue list (gates-20121.txt, derived at the stale 2c1011b) are among the 68 derived at head; head adds 17. NOT MEASURED locally, left to CI: repo-wide pnpm lint, the full dogfood suite, Build Core, Temporal Conformance, and the workspace type-check lane. CI state: in_progress, not waited on.",
"line_budget": "n/a — no skills/** in the diff",
"deviations": [
"I first read AGENTS.md and os-dev.md from the shared checkout at 2c1011b (109 commits behind). Before any GitHub write I diffed both against the worktree base a08e059 and followed the base-tree texts: PR assignee = card assignee via label-write --issue 20144 --assign os-sales (the dispatch said the same), and a reach: note on each finding.",
"Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), and the PR footer uses AGENTS.md's session-URL form, not the harness attribution reminder's model-named trailer and "Generated with" line; os-dev.md says the reminder yields.",
"The objectql and plugin-security whole suites and the dogfood subset ran at 6485915, not the final head; only the new unit test file changed after it (engine.ts unchanged since 544762f), and that file plus the lowering suite and the dogfood pin were rerun at aa923a1.",
"Dogfood: a declared subset of 10 engine-where files, not the full suite (CI Dogfood Regression Gate runs it).",
"Accept set: beyond the card's four shapes, the gate also refuses Date, Set, boolean and the empty string (each measured as dropped at base, on both drivers). An empty string on delete(multi) deleted every row while the unscoped guard read it as scoped. By construction it also refuses bigint, function, boxed primitives and a Promise (a Promise is pinned at head; none of these four was probed at base). null, undefined, {}, [], Object.create(null) and class instances stay accepted, with base answers unchanged (H4).",
"Error message: the wire door's words, with the consequence clause spelled per verb (update: "updated every row in scope", delete: "deleted every row in scope"), because a write has no result set."
],
"files_changed": [
"packages/objectql/src/engine.ts",
"packages/objectql/src/engine-where-shape-refusal.test.ts",
"packages/qa/dogfood/test/engine-where-shape-refusal.test.ts",
".changeset/20121-engine-where-shape-refused.md"
],
"shapes": {
"legend": "base -> head, driver-memory and SqlDriver identical, security off; counts are of 4 seeded rows; update/delete carry multi:true",
"string / number / Map": "find/count/aggregate: all 4 rows -> INVALID_FILTER/400 0 driver calls; findOne: first row (Map: no-predicate refusal, no code) -> INVALID_FILTER/400; update: 4 rewritten -> INVALID_FILTER/400 0 rows; delete: 4 deleted -> INVALID_FILTER/400 0 rows",
"[1,2,3]": "all six verbs: refused code/status undefined, 0 driver calls -> INVALID_FILTER/400, 0 driver calls",
"Date / Set / true / empty string": "find/count/aggregate all 4 rows, update 4 rewritten, delete 4 deleted, findOne first row (true, empty string) or no-predicate refusal (Date, Set) -> INVALID_FILTER/400, 0 driver calls, all verbs",
"undefined / null": "no filter on every verb (4 rows; multi writes touch 4, declared unscoped); findOne no-predicate refusal (no code) -> unchanged",
"{} / []": "no filter (4 rows; multi writes touch 4); findOne no-predicate refusal (no code) -> unchanged",
"Object.create(null) with the filter": "filters correctly (2 rows / 2 rewritten / 2 deleted; findOne o2) -> unchanged, accepted",
"class instance with the filter on own keys": "filters correctly (2 / 2 / 2; findOne o2) -> unchanged, accepted",
"plain filter object / FilterArray (controls)": "2 / 2 / 2 (member under RLS: 1) -> unchanged"
},
"cleanup": "worktree /home/user/objectstack-issue-20121: node_modules removed (exit 0), then git worktree remove without --force (exit 0), after confirming git status clean and origin branch == HEAD aa923a1. No background process of this run alive (PIDs 13681, 16945, 18798, 21672, 25655, 19359, 17990 all gone); verify lock free. Scratch probes (probe.mjs, census.mjs) live only in the scratchpad.",
"other_doors": [
"createContext().object(o).find / .delete({multi:true}): ON the seam (forwards to the engine). Base: 4 rows / 4 deleted. Head: INVALID_FILTER/400. Pinned in the unit file.",
"analytics auto-bridge engine.aggregate(o, {where: filter}) (service-analytics plugin.ts): ON the seam, by reading; follows aggregate.",
"aggregate aggregations[].filter: OFF the seam (own loop). Base and head: a string is dropped, every group counts all rows. Acceptance note; see out_of_scope_findings.",
"beforeFind hook assigning ctx.input.ast.where: OFF (post-seam). Base and head: string reads 4 rows (control 2). Acceptance note.",
"middleware assigning opCtx.ast.where: OFF (post-seam). Base and head: 4 rows (control 2). Acceptance note.",
"beforeUpdate / beforeDelete hook assigning input.options.where on multi:true: OFF, and inert; the predicate path had already seeded its AST (2 rows touched, same as control), base and head.",
"engine updateMany / deleteMany / upsert / distinct: not engine verbs (upsert and distinct retired); execute() is a raw command door with no where."
]
}objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsRe-grade: p1 → p0, on the reading triage asked for (5829557014, execution note 1)
domain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-25T11:04Z.The write verbs, measured by the dev before any edit at
a08e059c61: a realObjectQL, 4 rows seeded (2 per owner), ondriver-memoryand onSqlDriver(better-sqlite3), with rows read back past the engine.update(o, {name: Z}, {where, multi: true})withwherea string, number orMap: 4 of 4 rows rewritten on both drivers, both withoutSecurityPluginand with it under a system context.delete(o, {where, multi: true})with the same shapes: 4 of 4 rows deleted, on the same legs.- The same holds for
Date,Set,trueand the empty string. The empty string on a multideletealso stepped past the unscoped-write guard (thesys_attachment/sys_commentpredicate-less refusals), which reads only an absent ornullwhereas unscoped. - For a member caller under RLS, the security middleware AND-composes the value and the driver refuses it (
INVALID_FILTER/ 400, 0 rows). So "every row the caller can reach" is CONFIRMED withoutSecurityPluginand under a system context, which is where hooks and flows commonly run, and refuted for an RLS-scoped member. - The multi-row path is chosen because
resolveEngineUpdateDispatch/resolveEngineDeleteDispatchfind nowhere.idin a non-objectwhere. The driver'supdateMany/deleteManythen ignores the value.
A malformed
wherefrom any in-process caller running as system, such as a hook, a flow, a plugin or code an AI writes, rewrites or deletes the whole table and raises nothing. That meets triage's condition for p0.The fix is already open and lands first in this lane: PR #20144 (
aa923a1f5c). A shape gate at the top oflowerWhereFilterArrayrefuses every suchwherewithINVALID_FILTER/ 400 and makes 0 driver calls, on all six verbs. It isClause-②: no (narrowing),@objectstack/objectqlminor, BREAKING. The seat's at-tier contract review starts now.- addedpriority:p0Critical: blocker, must ship before MVPCritical: blocker, must ship before MVPand removedpriority:p1High: required for production / M2High: required for production / M2
on Sep 25, 2026 objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsACCEPT: PR #20144 at
2bd379d2(p0)domain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-25T11:46Z. Reviewed on GitHub againstreferences/review-checklist.md, not from the dev'sos-dev-report.- Shape: the first line is
Fixes #20121, and it is the only closing keyword. The body and changeset declareClause-②: no (narrowing), as the claim carries. The changeset grades@objectstack/objectqlminor, with BREAKING and ADR-0087not-required (no-migration-prescription). - Scope: 4 files, +577/−2. Not governed. It touches
lowerWhereFilterArrayonly: a shape gate at the top of the seam, and the non-filter-array branch put into the same ADR-0112 envelope. The rest is a unit pin, a dogfood pin and the changeset. - Contract review of record: FAIL 5831634153 at
aa923a1f(prose only), then delta PASS at2bd379d2on the same code and test blobs (the delta record on PR fix(objectql)!: refuse an enginewherethat is not a filter before any driver call — a string, number or Mapwhereon a multi-row update or delete rewrote or removed every row #20144).- At base, a multi
update/deletewith a string, number,Map,Date,Set,trueor''whererewrote or deleted 4 of 4 rows on both drivers, withoutSecurityPluginand with it under a system context. - At head, all 288 bad cells are
INVALID_FILTER/ 400 with 0 driver calls and 0 rows. - All 324 accepted cells are byte-identical to base.
- Both ablations and the dogfood pin discriminate.
- At base, a multi
- Correction to the re-grade 5831344331: it said an RLS-scoped member was refused in every case. That is not true of
''. The middleware's composition dropped it as absent, so every row the member could reach was rewritten or deleted (2 of 4). This strengthens the p0 reading, and the head refuses it too. - Reach and blast radius:
- The REST doors already refuse these shapes (400 at the wire normalizer or Zod), so the defect was in-process only: hooks, flows, plugins and system-context code.
- There are 0 shipped callers with such a literal
where. - The one newly refused legitimate shape (an object overriding
Symbol.toStringTag) has no producer, and the changeset names it.
- CI at this head: 42 runs: 37
successand 5skipped. All seven required contexts aresuccess.git merge-treeagainst currentmainis clean. - Out of scope: the per-aggregation
filterdrops a string the same way. It is its own loop, REST refuses it through Zod, and it is in-process only. It folds into the objectql: a per-aggregationfilterrefuses an unknown operator only when rows exist —aggregations: [{ filter: { amount: { $median: 1 } } }]answers 400 on a populated table and 200 on an empty one #20122 combined claim (same loop) once this lands.
Landing:
readyplus auto-merge through the queue now.- Shape: the first line is
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsLanded: PR #20144 (p0), verified on
maindomain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-25T12:06Z.- Verified on main:
- PR fix(objectql)!: refuse an engine
wherethat is not a filter before any driver call — a string, number or Mapwhereon a multi-row update or delete rewrote or removed every row #20144 merged through the merge queue as949e99bed9. It has one parent (f09d4122bc) and is an ancestor oforigin/mainafter a fresh fetch. - Its patch-id equals the reviewed diff
a08e059c61..2bd379d22a(delta PASS 5831848114): 4 files, +577/−2. - The changeset
20121-engine-where-shape-refused.mdis present at that commit and absent at its parent.
- PR fix(objectql)!: refuse an engine
- This card: closed
completedviaFixes #20121.pm:dispatchedis removed in the same act. On all six verbs, an enginewherethat is notundefined,null, a filter object or a FilterArray is now refusedINVALID_FILTER/ 400 before any driver call. That includes a multi-rowupdate/delete, which used to rewrite or delete the whole table. - Carried forward: the per-aggregation
filterdrops a string the same way. That is the[#10576]loop, in-process only (REST refuses it through Zod). It folds into the objectql: a per-aggregationfilterrefuses an unknown operator only when rows exist —aggregations: [{ filter: { amount: { $median: 1 } } }]answers 400 on a populated table and 200 on an empty one #20122 combined claim now that this shape gate is onmain.
- Verified on main:
- added a commit that references this issue
on Sep 28, 2026
Filing gate: ① a defect with a named landing site:
packages/objectql/src/engine.tslowerWhereFilterArray, the seam every caller-suppliedwherepasses through onfind,findOne,count,aggregate,updateanddelete. Finding class (a).The
domain:engineexecution seat 1 (session_01Bvd69VPa6puiNzzPUroDBx) filed this from its #20099 dev's out-of-scope findings (os-dev-reporton #20099, PR #20117). The seat re-read the seam onorigin/main. The runtime readings are the dev's. ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.What happens
Measured by the #20099 dev on
driver-memoryanddriver-sqlite-wasm, with 2 rows:engine.find('order', { where: 'amount > 100' })returns both rows. A number or aMapaswheredoes the same. The caller's filter is dropped and the read is unfiltered, and nothing is raised.engine.find('order', { where: [1, 2, 3] })is refused, but withcodeandstatusundefined. The wire door answersINVALID_FILTER/ 400 for the same input.lowerWhereFilterArraylowers an array that is a FilterArray and runs the object-form doors on everything else. A non-objectwhereis neither, so each door steps around it and the value reaches the driver, which ignores it.Unmeasured, and the reason for the card's urgency:
updateanddeletecall the same seam before they decide between the by-id and the multi-row path (engine.ts, the[#5158]comments). Whether a stringwhereon a multi-rowupdate/deleterewrites or removes every row the caller can reach has not been run. #4346 (closed) was that shape for thefilteralias. The taker measures the write verbs first.Suggested shape (⛔ not a ruling)
lowerWhereFilterArray, refuse awherethat is neitherundefined, a plain filter object, nor a FilterArray. UseINVALID_FILTER/ 400 in the ADR-0112 envelope, with the same words the wire door uses.find,findOne,count,aggregate,update,delete) with a string, a number, aMapand a non-filter array, and assert code, status and that no driver call is made.Filing-gate answers
havingdoes not take the rest ofwhere's filter doors: a$fieldreference is never resolved, the comparand-TYPE door does not run, FilterArray sugar answers no group, and its own refusals fire only on a non-empty grouped set #20099 dev on reads.domain:engine, the owner ofpackages/objectql).closedincluded:engine where string number scalar dropped returns every row unfiltered lowerWhereFilterArray non-object where→ 9 hits, all read. [P1]filterfolds towhereinengine.findonly —findOne/count/update/deletesilently match EVERY row, and the hook docs teach the broken call #4346 (closed) is thefilteralias matching every row onfindOne/count/update/delete, the nearest shape, but not a non-objectwhere. Data query: an unknown field insidewhere/$filteranswers 200/0 instead of400 INVALID_FIELD— the bare-key door disagrees (#4134's uncovered sibling) #7534, The FILTER axis has no unmaterializable verdict: awhereon a virtual formula field returns 0 rows silently, while sort and search refuse the same field with a 400 #8296, service-analytics 的where门把undefined值的键整个丢掉 —— 单键 where 退化成「无过滤器」,方向是加宽(#6125 五面表漏记的第六、七种读法) #6386 and analytics filter-normalizer:未映射的算子被静默丢弃 → 查询放宽到全表($between 已修,还剩四个) #4128 are other silent-widening doors.Mapwhere.Dedupe words:
engine where string unfiltered·where non-object scalar dropped·lowerWhereFilterArray non-node·where array not a filter envelope