feat(spec): boolean aggregand column in the aggregation conformance fixture — ruled numeric min/max on every face - #12947
Conversation
…ixture, ruled numeric on every face Adds flag (3 true / 3 false, the FLAG_BY_ID distribution) to AGGREGATION_ROWS with seven ruled cases (sum=3, avg=0.5, min=0, max=1, count=6, count_distinct=2, grouped min east=1/west=0), extends the three SQL harness DDLs, aligns min/max over booleans to the numeric domain on every face per the 2026-08-28 maintainer ruling (option A, superseding #11249's false/true): objectql in-memory fallback, driver-memory data + analytics faces, driver-sql result presentation (boolean kind skipped for min/max), driver-mongodb lowering (numericAggregandExpr on min/max). FLAG_BY_ID private maps deleted in favour of the fixture column; ruling-B pins in the 11635/11151 suites flipped to the ruled 0/1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…olean-aggregation-conformance
📓 Docs Drift CheckThis PR changes 5 package(s): 13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 129 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 858227a1d8f9e3dbeb1ec20c7388d93bb7e042dd && git checkout 858227a1d8f9e3dbeb1ec20c7388d93bb7e042dd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d028b37cbce487f3529d5219cbc94373ad2bd2f9 870e84442a1f9dd65829a6c2048086e1fe0af43c && git checkout -B drift-repro d028b37cbce487f3529d5219cbc94373ad2bd2f9 && git merge --no-ff 870e84442a1f9dd65829a6c2048086e1fe0af43c
node scripts/docs-audit/affected-docs.mjs --json d028b37cbce487f3529d5219cbc94373ad2bd2f9
|
…ed 0/1 The 11782 cross-door parity suite keeps every row-read boolean pin (find, distinct, group keys — NOT superseded) and asserts the ruled numbers on the two order-statistic cells; the mongodb pipeline-builder unit test pins the boolean-only coercion wrapper on min/max stages, the same shape it already pins for sum/avg. Pin sweep across driver-sql/mongodb/memory/objectql/rest/qa surfaced no third file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
Fixes #11152
What this lands
The cross-driver aggregation conformance fixture (
AGGREGATION_ROWS,@objectstack/spec/data) gains its boolean column —flag, 3 true / 3 false, theFLAG_BY_IDdistribution already landed onmain(west[T,F,F,F], east[T,T]) — with seven ruled cases:sum(flag)=3,avg(flag)=0.5,min(flag)=0,max(flag)=1,count(flag)=6,count_distinct(flag)=2, and groupedmin(flag)(east=1 / west=0, the asymmetric cell). This closes the reach gap #11065 and #11151 were both found through: no conformance cell could see a boolean aggregand on any face.Ruling of record — maintainer 2026-08-28, applied on the card in comment 5448627494, ruling verbatim: 「12745 A回,其他同意。」 Option A: booleans aggregate as numbers on every face, with no per-aggregate exception —
min/maxover a boolean answer0/1(numeric spelling; the earlier #11249 ruling'sfalse/trueis superseded). Every value in this PR flows from that ruling and is cited, not re-litigated.Mandatory pre-implementation PG leg (the ruling's must-answer, measured FIRST)
Question: does PostgreSQL's
MIN/MAXover boolean error on driver-sql's actual storage (a third behaviour the ruling does not cover)?Measured on a live PostgreSQL 16.13 cluster provisioned in this container (initdb, port 54911, torn down after):
booleancolumn:select min(flag) …andmax(flag)both ERROR — SQLSTATE42883,function min(boolean) does not exist. The third behaviour is real for the bare form.sql-driver-11635-boolean-aggregand-answers.test.tspg cell ran against the live cluster — 19 passed / 1 skipped (the skip is the unprovisioned MySQL cell), so the fix(driver-sql): boolean aggregands answer the ruled #11249 contract — PG lowering cast + all-dialect min/max JSON-boolean presentation #11785 lowering cast (cast("flag" as int)) coversmin/maxas well assum/avgand PG does not error through the driver. Raw cast-form values measured:min=0, max=1, sum=3, avg=0.5— exactly the ruled numbers.Verdict: no stop condition; the ruled values are producible on PG's actual storage.
The measurement that set the final surface
Baseline (tree at
aef1b7e6, all enrolled faces green) → fixture+DDL-only intermediate state → post-change, every suite exit code captured before any pipe:false/truefalse/true)0/1)min(flag)answeredfalse,max(flag)answeredtrue, grouped min both booleansfalse/true+ a "min/max stay bare" stage pin)0/1+ the coercion stage pin)false/true(its harness coercesNumber(...);Number(false)is0)Two of those rows are the load-bearing finding: the SQL-face and objectql harnesses deliberately compare through
Number(...)(a wire-type necessity — node-pg hands bigint counts back as strings), which makes them structurally blind to thefalse-vs-0spelling. The value cases alone therefore cannot hold the ruled JSON domain; the strict pins in the per-driver boolean suites and the new uncoerced objectql pin are what hold it.Surface beyond the dispatched five files — declared, each edit cited to the ruling
The dispatched surface assumed the SQL and mongodb faces already answered
0/1. Measurement falsified that: #11785 (issue #11635) had implemented the superseded #11249 ruling as an all-dialect JSON-boolean presentation ofmin/maxresults insidesql-driver.ts, and PR #12819 (issue #11151) had implemented the same superseded ruling in driver-mongodb (bare$min/$max+ a stage pin explicitly forbidding the coercion). Landing only the dispatched files would have produced a fixture that says0/1while two published faces answerfalse/true— invisible to the conformance suites (theNumber(...)blindness above), i.e. exactly the phantom-conformance class this card exists to remove.Each widening edit is the same defect class as the card (the ruled boolean min/max cell), mechanical, pinned by the ruling's verbatim text, running under gate families this card already owed, and held by no other in-flight claim (verified against open PR branches):
packages/drivers/driver-sql/src/sql-driver.ts— ruling 5448627494: themin/maxresult presentation now skips the'boolean'kind (temporal presentation and the SQLite numeric repair untouched;find()row reads untouched). Ablation below proves the strict pins bite this exact layer.packages/drivers/driver-sql/src/sql-driver-11635-boolean-aggregand-answers.test.ts— in the dispatched surface for theFLAG_BY_IDdeletion (done — seeds the fixture column now); additionally its ruling-B pins (toBe(false)/toBe(true), grouped booleans) are flipped to the ruled0/1and the head note re-anchored to the 2026-08-28 ruling. Leaving a strict pin of superseded values in an edited file was not an option.packages/drivers/driver-mongodb/src/mongodb-aggregation.ts— ruling 5448627494; the dispatch licensed a mongodb patch given evidence the red is the same ruled cell, and the intermediate-state measurement above is that evidence (4 failures, all on the ruled min/max cells, answering the superseded booleans).$min/$maxnow wrap the same boolean-only$condcoercion$sum/$avguse; null/missing still pass through (empty window still answers null — pinned).packages/drivers/driver-mongodb/src/mongodb-11151-boolean-aggregand-answers.test.ts— pins flipped with the lowering: ruled0/1values, and the "min/max stay bare" stage pin inverted to "min/max wrap the same coercion" (the stage, not the values, is what a real mongod would execute). Its privateFLAG_BY_IDcopy is deleted in favour of the fixture column, same argument as the 11635 one. The evaluator testkit needed no change — it already models$cond/$type/$eqper row.packages/drivers/driver-memory/src/memory-analytics.ts— the ruled "driver-memory" alignment applied to BOTH of its faces, per the package's own recorded discipline ("one face aligned alone leaves the other free to keep its own answer", theavgover a boolean field returnsnullon driver-memory and a number on sqlite — same rows, same dataset, same executor #11065 shape): the analytics face's$min/$maxmingo arms wrap the existingnumericAggregandExpr. Pinned inmemory-boolean-aggregand.test.tsbeside the data-face pins.packages/objectql/src/in-memory-aggregation-conformance.test.ts— one uncoerced spelling pin (min/max overflaganswer the numbers), because this harness'sNumber(...)coercion cannot see the ruled spelling and the ruled reduce alignment would otherwise be pinned by nothing.Cross-lane files pinned by the ruling (
packages/objectql/src/in-memory-aggregation.ts, driver-memory'smemory-driver.tscomputeAggregate): the reduce arms map booleans to their numbers before comparison — value pinned by the maintainer ruling of 2026-08-28, whose applied record is comment 5448627494 on the card; the driver-memory investment-freeze (2026-08-05) is overridden here by that explicit newer, narrower ruling naming its reduce.Ablation — the presentation removal can fail, and where the blindness lives
Predicted before running: re-adding the ruling-B boolean presentation to
sql-driver.tsreds the 11635 suite on exactly 3 tests per executing cell (min, max, grouped) receiving booleans, while the conformance suite stays green under the same mutation (theNumber(...)blindness). Measured: mutation confirmed on disk by anchored counts (guard-anchor 1 to 0, marker 0 to 1) before any result was read; 11635 RAW-EXIT=1, 6 failed — the exact 3 tests on each of the sqlite and live-pg cells; conformance RAW-EXIT=0, 51 passed. Restore proven by observation, not exit code:git diff HEADempty andgit hash-objecton the worktree file equal to the HEAD blob (89cb9e2f87…), neither empty. Resolution-path note: both suites import the subject via same-package relative imports (vitest runssrc), so no dist hop sits between the mutation and the measurement; the spec fixture, which IS consumed across packages, was rebuilt and its dist grepped for the new column before any suite was read.Verification
Final tree:
373343c33(branch head after mergingorigin/mainat4d1142753; same-day churn constraint honoured — all nine conformance faces re-run at this exact head, all green, and the ratchet family re-run at it too: type-check-debt re-measure "31 ledger entries, none above its recorded number", query-options-erasure "baseline key set verified against 4d11427: no files added", type-check-coverage, nul-bytes — all RAW-EXIT 0). The gate battery below first ran at the pre-merge head1516d122; the incoming five commits touch.claude/**docs, one spec zod file, one PM script and a changeset —pnpm --filter @objectstack/spec build && check:generatedwas re-run at373343c33(all 14 artifacts up to date) alongside the faces and ratchets.373343c33, every RAW-EXIT captured pre-pipe, all 0: driver-sql conformance 51 passed/1 skip (sqlite + live pg cells; the skip is MySQL, unprovisioned here — NOT MEASURED locally, CI's live-dialect job owns it) · driver-sql 11635 19/1 skip (same MySQL skip) · turso-remote 26 · sqlite-wasm 22 · mongodb translation 43 · mongodb 11151 17 · memory conformance 48 · memory boolean 17 · objectql conformance 25.**/*.test.tsfrom their tsc programs (standing package config), so the two edited test files in those packages are exercised by vitest execution, not tsc.pnpm --filter @objectstack/spec check:generated— all 14 generated artifacts up to date (authorable-surface, api-surface, docs among them).node scripts/pm/dispatch-gates.mjs, no hand-fed paths): variant-docs, empty-state, authorable-surface, cross-package-test-inputs, doc-authoring, doc-formula-expressions, driver-conformance, durability-log-level, merge-driver, objectql-double-limit, objectui-changeset, page-declaration-shape, pm-half-states, published-files, spec-parsed-alias, slot-lookup, test-source-alias, type-source-resolution, changeset-gate-self-tests, adr-0087-registration, changeset-no-major, empty-changeset, ci-filter-parity, comment-mask-adoption, dev-prereqs, engine-split-ratio, plugin-teardown-shape, release-rehearsal self-test, query-options-erasure, type-check-coverage, type-check-debt (workspace closure built first, as lint.yml does), engine-double-contract, where-matcher, dispatcher-error-vocabulary, nul-bytes — all RAW-EXIT 0. One exception:scripts/pm/check-half-states.mjsexits 3 ("no reading at all") — it sweeps the GitHub board and this seat's session has no repo-scoped REST access; CI owns it..changeset/boolean-aggregands-numeric-min-max.md, patch across the five behaviour-bearing packages, FROM/TO stated per face (the same patch-level convention fix(driver-sql): boolean aggregands answer the ruled #11249 contract — PG lowering cast + all-dialect min/max JSON-boolean presentation #11785 and fix(driver-mongodb): a boolean aggregand answers the ruled values on this face too #12819 used for the previous flips of this same cell).Out-of-scope observations (recorded, not acted on)
sql-driver-out-of-contract-aggregate-function.test.ts,memory-analytics.ts,memory-driver.ts,mongodb-aggregation.tsmentionAGGREGATION_ROWSin comments only — no other suite seeds the fixture, so no further DDL surface exists.avg/sumover a boolean column tonull/0— the same cell as #11065, one driver over #11151 is closed and stays closed; [Decision]min/maxover a boolean aggregand: pin the cross-driver JSON answer —0/1(SQL) vsfalse/true(both in-memory faces) #11249 remains a historical record — its ruling is superseded on the min/max cell by this card's 2026-08-28 ruling, and the code comments that cited it now cite both. Out of scope here: [finding] sql-driver-aggregation-conformance.test.ts hard-codes better-sqlite3, so the aggregate-vocabulary standard never runs on Postgres — its pagination sibling already uses the live-dialect cells #11456 remains open (live-PG harness conversion, sequenced after this card by its own record).Clause-② note for the review chain: this PR changes published behaviour on the min/max-over-boolean cell of driver-sql (all dialects), driver-memory (both faces), driver-mongodb and objectql's fallback —
false/trueto0/1— and pins new contract expected values in the conformance fixture. Opened as draft; the PM attachesneeds:contract-review; no AI seat enqueues this.Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
Generated by Claude Code
Generated by Claude Code