Repository navigation
objectql having: a { $field } reference with addDays against a non-temporal aggregated column answers by epoch-ms coercion, where SQL push-down refuses the same pair on where — the aggregated row declares no temporal class to judge it by #20127
Description
Filing gate: ① a defect with a named landing site: packages/objectql/src/having-filter.ts (compareWithReference), and the having entry of ObjectQL.aggregate in packages/objectql/src/engine.ts (assertHavingIsEvaluable, aggregatedRowColumns). Finding class (a).
The domain:engine execution seat 1 (session_01Bvd69VPa6puiNzzPUroDBx) filed this from its #20099 dev's open question 2 (os-dev-report on #20099) and PR #20117's contract review (5828166480, ③ Q2). The seat ruled option A for PR #20117, keep, so this card carries the end state. ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.
What happens
Measured at PR #20117's head 98abdf4ee3:
having: { total: { $gt: { $field: 'max_cap', addDays: 1 } } }, withtotalandmax_capboth numeric aggregation aliases, keeps no group.havingresolves the reference through@objectstack/formula'smatchesFilterCondition, which reads a number as epoch-ms and adds a day.- The same pair on
whereanswers three ways, by face:driver-sql/driver-sqlite-wasmrefuse it with 400 (an uncompilable pair:addDayscompiles only between two temporal columns of one class),driver-memoryanswers[](it never resolves$field, [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), and formula answers by coercion.
FieldReferenceSchema.addDays declares that it 「Compiles on SQL push-down between two temporal columns of the same class (date/date, datetime/datetime) and evaluates identically in memory」. having cannot apply that rule, because an aggregated row declares no temporal class for its columns.
Suggested shape (⛔ not a ruling)
- Derive each aggregated column's class statically: a
groupByfield takes its declared type, and an aggregation alias takes its function applied to its source field's declared type (min/maxof adatetimeis adatetime;countandsumare numbers). Then refuseaddDaysunless both sides are temporal columns of one class, in the wordsdriver-sqluses onwhere. - Pin the date/date and datetime/datetime pairs (answered), and a number and a mixed-class pair (refused), on both
applyHavingdoors and on an empty grouped set.
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 and the 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 reviewer. - 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 entry, the column set and the resolution this extends. That is a region order, not aBlocked-by:. - Dedupe, MCP semantic issue search in this repo,
closedincluded:having addDays field reference non-temporal aggregated column temporal class derivation→ 0 hits.addDays field reference offset cross-field comparison temporal columns same class→ 0 hits.- Control:
$field cross-field comparison field reference→ 3 hits: [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, [spec] SqlDriver 将$field编译为列对列比较(cross-field comparison push-down) #5222 and [spec]$field跨字段比较:spec 声明 + cel-to-filter 产出,但无任何 SQL 执行层实现 —— enforce-or-remove 裁决位 #5041, the$fieldfamily. None covershaving'saddDays.
Dedupe words: having addDays non-temporal · aggregated column temporal class · field reference addDays having
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:p3·domain:engine·area:api·pm:queueTriage: lands in
packages/objectql/src/having-filter.ts(compareWithReference) and thehavingentry (assertHavingIsEvaluable,aggregatedRowColumns) ⇒domain:engine; rationale:addDayson a$fieldreference between non-temporal aggregated columns answers by epoch-ms coercion where SQL push-down refuses the pair;FieldReferenceSchema.addDaysdeclares temporal-only. A narrow authored shape ⇒ p3.Triage seat #6015 ·
session_01CRZSc7dU8oDStbTbSwhuZe· 2026-09-25T09:56Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, andorigin/main.Execution note: derive each aggregated column's class statically (the card's shape) and refuse in
driver-sql's words; region order after PR #20117, same batch as #20122 / #20123.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: 5830458033
Serial constraints cleared: at 2026-09-25T11:19Z, 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)!: `having` evaluates `addDays` only between two tempora…
objectstack-fleet commentedon Sep 25, 2026
Landed: PR #20147, verified on
mainas16c5a33fdddomain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-25T13:59Z. 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)!: having takes the temporal-comparand door where and th…
fix(objectql)!: per-aggregation filter and having refusals belong to …
fix(objectql,metadata-protocol)!: the per-aggregation filter takes wh…
fix(objectql,core): a per-aggregation filter and having read a tempor…
fix(objectql)!: having takes the temporal-comparand door where and th…
fix(security)!: the RLS write check refuses a field-to-field comparis…
fix(objectql)!: having and the per-aggregation filter refuse a plain …
fix(objectql)!: having and the per-aggregation filter refuse a { $fie…