Found while implementing #11455 (the aggregate door's terminal backend-fault envelope). Filed unassigned as an out-of-scope finding — #11455 is fenced to enveloping-only and answering this needs a classifier it did not measure.
What was measured
On claude/issue-11455-bool-aggregate-envelope (i.e. with#11455's envelope in place), against embedded SQLite via the built @objectstack/driver-sql:
find where nosuchcol => code=INVALID_FILTER status=400
"Filter on 'nosuchcol' names a column that object 'probe_par' has no column for…"
count where nosuchcol => code=INVALID_FILTER status=400 (identical message)
agg where nosuchcol => code=DATABASE_ERROR status=500
"The database refused to run this query for object 'probe_par'. The driver could
not attribute the failure to any part of the request…"
agg avg(nosuchcol) => code=DATABASE_ERROR status=500
agg groupBy nosuchcol => code=DATABASE_ERROR status=500
The first two rows are #8790's ruled answer (maintainer, 2026-08-15): one unresolvable WHERE column, one answer, on both read halves, naming the column so the caller can fix it. aggregate() is the third read door and never got the arm — before #11455 it threw the raw dialect error, and after #11455 it answers the generic terminal envelope.
So the exact split #8790 closed is still open one door over: a list view's find()/count() say "'nosuchcol' is not a column of this object" with a 400, while the dashboard tile over the same object answers an opaque 500 for the same predicate.
Why #11455 deliberately did not fix it, and what the fix actually needs
A blanket isUnresolvableColumnError arm on this door would be wrong, which is why it was not added. unresolvableFilterColumnError's words are
Filter on '' names a column that object '' has no column for, so the predicate never ran.
and aggregate() names columns in three clauses, not one: the where (via applyFilters), the groupBy fields, and each aggregation's field. Rows 4 and 5 above show the other two — an author who wrote avg('nosuchcol') or grouped by a missing field would be told their filter was wrong. That is exactly the unsupportable attribution the #8931 ruling («同意 C», 2026-08-17) refuses to make, so #11455 took the honest generic envelope instead.
Closing it properly means deciding which clause the dialect-named column came from. The information is available at the call site without a new dialect predicate — aggregate() already holds the groupBy fields and the aggregation fields while it builds the statement, and unresolvableColumnNameOf already extracts the name from all three dialects' wordings — so the shape is roughly:
⚠️ Note the third arm matters: unresolvableColumnNameOf returning null is a real answer, and reading it as "not an unresolvable column after all" is the regression its own docblock warns about.
Scope note
Independent of #11152 / #11249 (whether a boolean aggregate should answer a number or refuse) — this is about a column that does not exist at all, on any type.
Region: packages/drivers/driver-sql/src/sql-driver.ts, the aggregate() body only.
Found while implementing #11455 (the aggregate door's terminal backend-fault envelope). Filed unassigned as an out-of-scope finding — #11455 is fenced to enveloping-only and answering this needs a classifier it did not measure.
What was measured
On
claude/issue-11455-bool-aggregate-envelope(i.e. with#11455's envelope in place), against embedded SQLite via the built@objectstack/driver-sql:The first two rows are #8790's ruled answer (maintainer, 2026-08-15): one unresolvable WHERE column, one answer, on both read halves, naming the column so the caller can fix it.
aggregate()is the third read door and never got the arm — before #11455 it threw the raw dialect error, and after #11455 it answers the generic terminal envelope.So the exact split #8790 closed is still open one door over: a list view's
find()/count()say "'nosuchcol' is not a column of this object" with a 400, while the dashboard tile over the same object answers an opaque 500 for the same predicate.Why #11455 deliberately did not fix it, and what the fix actually needs
A blanket
isUnresolvableColumnErrorarm on this door would be wrong, which is why it was not added.unresolvableFilterColumnError's words areand
aggregate()names columns in three clauses, not one: thewhere(viaapplyFilters), thegroupByfields, and each aggregation'sfield. Rows 4 and 5 above show the other two — an author who wroteavg('nosuchcol')or grouped by a missing field would be told their filter was wrong. That is exactly the unsupportable attribution the #8931 ruling («同意 C», 2026-08-17) refuses to make, so #11455 took the honest generic envelope instead.Closing it properly means deciding which clause the dialect-named column came from. The information is available at the call site without a new dialect predicate —
aggregate()already holds thegroupByfields and the aggregationfields while it builds the statement, andunresolvableColumnNameOfalready extracts the name from all three dialects' wordings — so the shape is roughly:groupBy/ aggregation field ⇒ a refusal about that clause (which does not exist yet;INVALID_FIELDandINVALID_QUERYare both catalogued candidates, and picking between them is the decision this card carries);find()silently returns [] whilecount()throws a raw dialect error with no ADR-0112 envelope #8790's existingunresolvableFilterColumnRefusalapplies verbatim;null(a wording nothing parsed) ⇒ the driver-sql (PG): sum/avg/min/max over a boolean column throw the raw PostgreSQL 42883 with no ADR-0112 envelope (status undefined) #11455 terminal, unchanged.unresolvableColumnNameOfreturningnullis a real answer, and reading it as "not an unresolvable column after all" is the regression its own docblock warns about.Scope note
Independent of #11152 / #11249 (whether a boolean aggregate should answer a number or refuse) — this is about a column that does not exist at all, on any type.
Region:
packages/drivers/driver-sql/src/sql-driver.ts, theaggregate()body only.