Repository navigation
objectql: a having key that names no column of the aggregated row keeps no group silently — having: { totl: { $gt: 100 } } answers 200 [], where an unknown where field is refused 400 #20123
Description
Filing gate: ① a defect with a named landing site: packages/objectql/src/having-filter.ts (checkCondition's no-value exit), and the having entry of ObjectQL.aggregate in packages/objectql/src/engine.ts. Finding class (a).
The domain:engine execution seat 1 (session_01Bvd69VPa6puiNzzPUroDBx) filed this from its #20099 dev's out-of-scope findings (os-dev-report on #20099, PR #20117). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.
What happens
Measured by the #20099 dev at PR #20117's head, on driver-memory and driver-sqlite-wasm, with a populated grouped set:
having: { totl: { $gt: 100 } }(a typo for the aliastotal) answers200 []. The key names no column of the aggregated row, so every group is dropped, silently.- An unknown field in
whereis refusedINVALID_FIELD/ 400 (Data query: an unknown field insidewhere/$filteranswers 200/0 instead of400 INVALID_FIELD— the bare-key door disagrees (#4134's uncovered sibling) #7534).
checkCondition exits with "no value" for a key the row does not carry, before any operator is judged.
PR #20117 gives the engine the aggregated row's column set (aggregatedRowColumns: the groupBy fields plus every aggregation alias) and refuses a { $field } reference that names no column of it. A having KEY is not checked against the same set.
Suggested shape (⛔ not a ruling)
- In the row-independent walk PR fix(objectql)!:
havingtakes the rest ofwhere's filter doors — the comparand-type door, row-independent refusals, a resolved{ $field }, and a refused non-condition #20117 adds (assertHavingIsEvaluable), refuse ahavingkey that names no column ofaggregatedRowColumns. Use the unknown-field refusal's envelope, rooted athaving. - Pin a populated and an empty grouped set, on both
applyHavingdoors.
Filing-gate answers
- Class: ① (defect, named landing site); finding class (a), measured by the objectql
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. - Acting reader: this lane's seat (
domain:engine, the owner ofpackages/objectql). It is sequenced after PR fix(objectql)!:havingtakes the rest ofwhere's filter doors — the comparand-type door, row-independent refusals, a resolved{ $field }, and a refused non-condition #20117 (objectqlhavingdoes 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), which adds the column set and the walk. That is a region order, not aBlocked-by:. - Dedupe, MCP semantic issue search in this repo,
closedincluded:having unknown column alias typo keeps no group silently refused→ 4 hits, all read: check:test-source-alias still cannot read a+-concatenated alias replacement — the #8020 defect one spelling over #8108, check:test-source-alias cannot read a template-literal alias replacement, so two packages that DO alias spec subpaths to source are still recorded as unaliased #8020, [finding] alias-integrity cannot see an alias row made unreachable by a guidanceSet — a vis-shaped alias on VISIBILITY_STRICT_OPTIONS surfaces would be dead code with no gate saying so #7889 (alias gates in tooling) and [finding][drivers] driver-mongodb cannot take a structuredGroupByNodeat all — the object stringifies into a"[object Object]"$group._id #6850 (driver-mongodbGroupByNode). None covers an unknownhavingkey.- The
havingsearches run for objectqlhavingdoes 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 read objectqlhavinghas no$icontainscomparand-shape gate — an empty comparand matches EVERY row (2 of 5FILTER_TEXT_CASESrejection rows unenrollable) #7158, objectqlhaving-filter.ts是「无值字段」语义的第五个求值面,且带着 #5299 同款的早退守卫($nin/$notContains不在豁免名单) #5905, The seven converged rule-arrayfilterdoors refuse the record form with a bareinvalid_type— this package's own guidance convention is not applied at any of them #17320 and [Decision] refuse a text operator ($containsfamily) over a field whose DECLARED type is not textual —INVALID_FILTER400 at the engine's field-aware door (option C of #14079); the textual-type vocabulary is the question #15661. None covers it either. - Data query: an unknown field inside
where/$filteranswers 200/0 instead of400 INVALID_FIELD— the bare-key door disagrees (#4134's uncovered sibling) #7534 (closed) is thewheretwin: the positive control for the refusal this asks for.
Dedupe words: having unknown column silent · having alias typo no group · having key not in aggregated row
Activity
objectstack-fleet commentedon Sep 25, 2026
Path: an API a customer can call | api-backend.aggregate-contract-matrix | P2
Triage: first grade —
bug·priority:p2·domain:engine·area:api·pm:queueTriage: lands in
packages/objectql/src/having-filter.ts(checkCondition's no-value exit) and thehavingentry ofObjectQL.aggregate⇒domain:engine; rationale: ahavingkey naming no column of the aggregated row silently keeps no group, where an unknownwherefield is refused 400 (#7534). A typo that answers an empty result ⇒ p2.Triage seat #6015 ·
session_01CRZSc7dU8oDStbTbSwhuZe· 2026-09-25T09:53Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, andorigin/main.Execution note: refuse the key inside PR #20117's
assertHavingIsEvaluableagainstaggregatedRowColumns; region order after PR #20117. Same batch as #20122 / #20127.objectstack-fleet commentedon Sep 25, 2026
Claim: PM loop round 22
Session:
session_01Bvd69VPa6puiNzzPUroDBxAccount:
os-sales(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-20122-aggregate-filter-doorsWorktree:
objectstack-issue-20122Domain:
domain:engineSeat:
domain:engine#1File surface: a combined claim of #20122 (the chain head), #20123 and #20127, as triage batched them (same files, same entry). One PR carries one
Fixes #<n>line per member and one commit per card.packages/objectql/src/engine.ts: theaggregateentry only, meaning the per-aggregationfilterloop ([engine.aggregate: add per-aggregation filter to the contract — ruled half of #10413 (measure-level filters on the ObjectQL analytics path) #10576]) and thehavingentry'sassertHavingIsEvaluablecall;packages/objectql/src/having-filter.ts:matchesAggregationFilter,checkCondition's no-value exit,compareWithReference,assertHavingIsEvaluableandaggregatedRowColumns;packages/objectql;.changeset/2012{2,3,7}-*.md.Stop on breach and explain in the report. ⛔ Not
lowerWhereFilterArray(PR #20144, #20121, p0 in review). ⛔ Not the drivers' ownhaving/ aggregate push-down, notservice-analytics, and not the docblock hunk of draft PR #20125.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: 5830422588
Serial constraints cleared: at 2026-09-25T11:18Z, the region predecessor PR #20117 (#20099) landed as
fc646cf09c. A census of the 8 open PRs finds two onengine.tsand none onhaving-filter.ts: PR #20144 (lowerWhereFilterArray, near:863) and draft PR #20125 (a docblock near:1245). Both are disjoint fromaggregate(:15971onmainf09d4122bc). Whichever lands second mergesmainfirst.fix(objectql)!: a `having` key naming no column of the aggregated row…
objectstack-fleet commentedon Sep 25, 2026
Landed: PR #20147, verified on
mainas16c5a33fdddomain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-25T13:58Z. This card was a member of the combined claim led by #20122. The full landing record is 5833618445 on #20122. The card is closed via its ownFixesline, andpm:dispatchedis removed.docs(changeset): qualify the having Date sentence, name the borrowed …
fix(objectql)!: per-aggregation filter and having refusals belong to …
fix(objectql,metadata-protocol)!: the per-aggregation filter takes wh…
fix(objectql)!: having takes the temporal-comparand door where and th…
fix(objectql)!: having resolves placeholders through the where resolv…