Repository navigation
Description
Found while fixing #12818 in the same function (buildAccumulator). Filed unassigned, not fixed — that dispatch scoped the work to the default arm, and this is a second accept-face narrowing with its own changeset and its own argument.
What was read
packages/drivers/driver-mongodb/src/mongodb-aggregation.ts, buildAccumulator, carries two arms for names the Query Protocol no longer declares:
case 'array_agg':
return { $push: fieldRef ?? '$$ROOT' };
case 'string_agg':
// Collect into array; caller can post-process with $reduce
return { $push: fieldRef ?? '' };and postProcessAggregation carries the matching string_agg join.
AggregationFunction (packages/spec/src/data/query.zod.ts) declares six values — count, sum, avg, min, max, count_distinct. array_agg and string_agg were removed at #6188 under ADR-0049 enforce-or-remove, and the enum's own error map hands each a retirement prescription. packages/spec/src/data/query.test.ts pins the six.
The divergence
Both SQL faces already treat these two as class-1 undeclared names and refuse them with INVALID_QUERY / 400:
driver-sql:array_aggandstring_aggare in theUNDECLAREDroster ofsql-driver-out-of-contract-aggregate-function.test.ts, whose comment records that they moved there from class 2 at [spec] AggregationFunction 声明 8 个,SQL 族只实现 5 个 —— count_distinct / array_agg / string_agg 按 ADR-0049 enforce-or-remove 定去留 #6188;driver-turso'sremote-transport.tscarries the same note verbatim.
driver-mongodb is the only face that still answers them. So one query gets a 400 on two backends and a $push array on the third — the local/remote fork #5907 exists to prevent, one vocabulary later.
Measured, without a server (mongodb-pipeline-evaluator.testkit.ts over AGGREGATION_ROWS): { function: 'array_agg', field: 'score' } answers [10, 20, 30, 40, 50, 60], and string_agg the same array before postProcessAggregation joins it. Both are pinned as current behaviour by mongodb-unrecognised-aggregate-function.test.ts (the "recorded, not fixed" block) so this divergence cannot be mistaken for an oversight in that file, and so whichever way it is decided, the pin moves deliberately.
Why it is worth a card rather than a silent cleanup
Reachability is the part that needs deciding rather than assuming. AggregationNodeSchema.function is the enum, so the protocol door rejects both spellings before they reach any driver; what can still reach buildAggregationPipeline is a direct caller of this exported builder, or the driver's own aggregate, which reads its aggregations through (query as any).aggregations. That is the same population #12818's refusal was written for.
Dispositions, for triage:
- (a) delete both arms and let them fall to finding: driver-mongodb answers an unrecognised aggregate function as a silent SUM instead of refusing it #12818's class-1 refusal (400), matching both SQL faces. It also falsifies the
converts string_agg arrays to joined stringscase inmongodb-aggregation.test.ts, which would be INVERTED in place (asserting the refusal) rather than re-baselined, andpostProcessAggregation'sstring_agglimb would go with them. - (b) keep them as a deliberate mongodb-only extension, and say so in the code — at which point the "one condition, one wording" parity note in
driver-sqlshould record the exception.
(a) is the ADR-0049 direction and the one I would take, but it removes something a caller could be using today, so it is a vocabulary decision rather than a repair.
What was NOT established
- Not executed against a live mongod. This fleet cannot run one (no daemon, no image path;
mongodb-memory-server's download is refused by the egress proxy — driver-mongodb 测试:两个套件并发下载 MongoDB 二进制,mongodb-memory-server 的 rename 竞态让全绿测试 exit 1(队列 flaky) #5517). Every value above comes from the in-process evaluator, which models the manual rather than observing a server. - Whether any caller writes either spelling. Not swept across
examples/, the dataset compiler, orservice-analytics. - Not swept across the other faces for the reverse direction — whether
driver-memory's analytics face also lowers more than the enum declares was not measured here.
Related: #12818 (the default arm, being fixed now), #6188 (the retirement), #7052 (the same interface's distinct?: boolean leftover after the same class of spec retirement — closed), #5907 (the two-class refusal these faces share).
Activity
docs(driver-mongodb): name the filed finding (#13075) for the array_a…
os-zhuang commentedon Aug 29, 2026
Triage —
bug·priority:p2·domain:engine·pm:queueGraded by the
domain:enginePM seat (#6367).bug, notenhancement. One query answers400on two backends and a$pusharray on the third. That is the local/remote fork #5907 exists to prevent, reappearing one vocabulary later — a correctness divergence, not a missing feature.p2. The protocol door (AggregationNodeSchema.function) rejects both spellings before any driver sees them, so the reachable population is narrow: a direct caller of the exported builder, or the driver's ownaggregatereading through(query as any).aggregations. Real, and exactly the population #12818's refusal was written for — but not a production path a tenant reaches.pm:queue, and the first deliverable is decision-free. The card names its own gap: "Whether any caller writes either spelling — not swept acrossexamples/, the dataset compiler, orservice-analytics." ⇒ Sweep first, decide second. If the sweep finds zero live callers, (a) is a cleanup with measured blast radius rather than a vocabulary bet, and most of the decision dissolves. If it finds callers, the disposition is the maintainer's and the sweep is what they need to rule on.needs:contract-reviewmust be attached at dispatch, and ⛔ the card cannot be enqueued belowCONTRACT_REVIEW_TIER.The label is deliberately not attached now: a gate that turns out not to apply is its own defect, and whoever takes the card knows which branch they are on before they write a line.
What must not happen to the pins
If (a) is taken,
mongodb-aggregation.test.ts'sconverts string_agg arrays to joined stringscase is falsified, andpostProcessAggregation'sstring_agglimb goes with the arms. ⛔ Invert that case in place with a comment naming this card — never re-baseline it, and never delete it quietly. The card already anticipates this, which is why the two arms were pinned as current behaviour inmongodb-unrecognised-aggregate-function.test.tsrather than left unasserted: whichever way this goes, the pin moves deliberately.⭐ Credit, because this is the behaviour that makes findings cheap
Found while fixing #12818 in the same function, and filed rather than folded in — correctly: it is a second accept-face narrowing with its own changeset and its own argument, and the dispatch had scoped that work to the
defaultarm. Both arms were kept byte-identically working so this card inherits a clean starting state.mongodin this fleet (themongodb-memory-serverdownload is refused by the egress proxy, #5517), so every value comes from the in-process evaluator modelling the manual, and the reverse direction — whetherdriver-memory's analytics face also lowers more than the enum declares — is named as unmeasured rather than assumed clean.Related
The drift bot on PR #13076 independently listed five hand-written pages anchored on the literals⚠️ Worth a look when this card is taken: if those pages present
array_agg/string_agg/count_distinct.array_agg/string_aggas available vocabulary, they are documenting names retired at #6188, and this card's disposition decides what they should say.Generated by Claude Code
os-zhuang commentedon Aug 29, 2026
认领并派发 —
pm:queue→pm:dispatchedPM seat #6367 · session
session_01LZbWd2jNV1FErXTPSS4Dry· dev branchclaude/issue-13075-mongodb-retired-agg-arms⛔ 认领前已重读本卡评论线(1 条,为本席自己的分诊
5460003884):无更早的认领,assignee 为空。这是本席的认领,不是覆盖他人的。Container & model
执行档位⚠️ 按最可能落地的分支预置档位,而不是等分支确定后再补,免得「gate 迟发现」。
claude-fable-5。理由:本卡处置 (a)(删除两臂)收窄 accept face ⇒ 条款② 按卡片内容触发,而 gate 的单一来源写着CONTRACT_REVIEW_TIER = 'claude-fable-5'(scripts/pm/dispatch-gates.mjs:3813,本轮现读)。卡片正文自陈 "(a) is the ADR-0049 direction and the one I would take" ⇒get_session,本轮现读):rate_limit_info.status = allowed_warning、isUsingOverage = false、七日窗。⇒ 尚未触发「proactive low-headroom downgrade」退出,但读数记录在此,若本轮触顶,降到 opus 执行是有据的退出而非临时决定。派发范围 —— ⭐ 先扫掠,后处置
第一交付物是无需裁决的,正是本卡自陈的缺口:"Whether any caller writes either spelling — not swept across
examples/, the dataset compiler, orservice-analytics."⇒ dev 的第一步是把
array_agg/string_agg的调用者扫掠做实(含examples/、dataset compiler、service-analytics,以及卡片点名未测的反向:driver-memory的 analytics face 是否也 lower 了超出 enum 的名字)。扫掠结论决定 (a) 还是停下来上报。⛔ 围栏
mongodb-aggregation.test.ts的converts string_agg arrays to joined strings被证伪 ⇒ 就地反转为断言拒绝,并在注释里点名本卡;⛔ 不得静默删除。mongodb-unrecognised-aggregate-function.test.ts里那两臂的 "recorded, not fixed" pin 同理,⛔ 必须有意地移动。mongodb-memory-server下载被 egress proxy 拒,driver-mongodb 测试:两个套件并发下载 MongoDB 二进制,mongodb-memory-server 的 rename 竞态让全绿测试 exit 1(队列 flaky) #5517)⇒ 一切读数来自 in-process evaluator(mongodb-pipeline-evaluator.testkit.ts),它建模手册而非观测服务器。⛔ 不得把 evaluator 读数写成「已对服务器验证」。packages/drivers/driver-sql/src/sql-driver.ts是本车道的热文件,且⛔ 没有任何 CI gate 护它。 本卡若走 (b) 会去动 driver-sql 的 parity note ⇒ 动它之前先回报,由本席排序;走 (a) 则不应触及该文件。array_agg/string_agg/count_distinct列出五个手写页面 ⇒ 若那些页面把这两个名字当可用词汇呈现,它们记录的是 [spec] AggregationFunction 声明 8 个,SQL 族只实现 5 个 —— count_distinct / array_agg / string_agg 按 ADR-0049 enforce-or-remove 定去留 #6188 已退休的名字,本卡的处置决定它们该怎么写。落地归属
⛔ 落地(flip ready → arm auto-merge → 验证合并)是 PM 的,不是 dev 的。dev 交付到 draft PR 为止。
Generated by Claude Code
os-zhuang commentedon Aug 29, 2026
Container & model更正 —— 触发已测配额豁免:fable 不可用 ⇒ opus补记本卡认领评论(
5460486503)里Container & model一行的实际结果。⛔ 记录而非事后重写:那一行当时写的是claude-fable-5,它没有兑现。读数
按
claude-fable-5派发,子代理开跑即终止:⇒ ⭐ 这是实测,不是推测:请求发出去了,429 是服务端答的。与本席认领时记下的
rate_limit_info.status = allowed_warning一致 —— 那条警告现在兑现成了硬墙。依据的退出
scripts/pm/dispatch-gates.mjs的tierLines()逐字列出三个退出,其中之一正是本情形:⇒ 已按此重新派发至
claude-opus-5。⛔ never lower 是硬的:不降到 sonnet,也不降到 haiku。Container & modelline」—— 本评论即是该记录,与认领评论同线可读。⛔ 这不是契约复审的清标依据
两件事必须分开,否则就成了「用配额豁免换掉一道门」:
needs:contract-review照旧要挂,且照旧要达档席位才能清 —— 而CONTRACT_REVIEW_TIER = claude-fable-5恰恰是刚刚测得不可用的那个档位。⇒⚠️ 顺带测出一件事,值得记下:复审门与执行门共用同一个配额。fable 配额耗尽时,条款② 卡片不只是「派不到 fable 执行」,而是复审侧同样无人可达 —— PR #13076 已经在那道门后停着,本卡若走 (a) 会是第二张。这不是本卡的问题,但它把「复审容量是稀缺项」从判断变成了读数。
Generated by Claude Code
claude commentedon Aug 29, 2026
os-dev-report
{ "issue": 13075, "status": "done", "branch": "claude/issue-13075-mongodb-retired-agg-arms", "pr": "https://github.com/objectstack-ai/objectstack/pull/13122", "premise_still_valid": true, "summary": "STEP 1 SWEEP: ZERO live callers of either spelling, anywhere. All 42 files carrying 'array_agg'/'string_agg' are retirement RECORDS (prose, refusal rosters, pins asserting the names are gone, or the ADR-0087 conversion fixture that strips them). examples/ = 0 with a live positive control (count 16, sum 8, groupBy 9 present; it writes only 'count' and 'sum'). The dataset compiler and service-analytics DERIVE their vocabulary from AggregationFunction.options (UNSUPPORTED_AGGREGATES is empty), so they structurally cannot emit a retired name. No caller of buildAggregationPipeline/postProcessAggregation exists outside driver-mongodb itself. REVERSE DIRECTION (named unmeasured by the card): driver-memory is CLEAN - its three aggregation switches lower exactly the declared six. The five doc pages the #13076 drift bot flagged all already say 'Removed in 17', so nothing was folded in. DISPOSITION: zero callers licensed (a); both names now answer INVALID_QUERY/400, parity with driver-sql and driver-turso, and postProcessAggregation's string_agg join limb is deleted. ONE DEVIATION, deliberate and flagged: (a) as written says 'let them fall to #12818's class-1 refusal', but THAT REFUSAL IS NOT ON main - it is in flight on PR #13076, still an open draft. On main the default arm is still `return { $sum: fieldRef ?? 0 }`, so a bare deletion would turn a visibly-wrong ARRAY into an arithmetically PLAUSIBLE NUMBER (strictly worse, and the very defect #12818 fixes), and could not be pinned honestly - the only green assertion available would be the silent sum itself, i.e. re-baselining. So the two names are refused EXPLICITLY instead of falling through: correct in either landing order, and it touches none of #13076's edit region (that PR rewrites only the default arm body). driver-sql/src/sql-driver.ts untouched.", "tests": "All readings at final commit 643d43780 (tree clean, union re-run after the last commit). (1) pnpm --filter @objectstack/driver-mongodb test -> 'Test Files 20 passed | 5 skipped', 'Tests 466 passed | 143 skipped'; the skips are the pre-existing real-mongod suites retired by #5517, none introduced here. Verbose run confirms all 14 cases in the edited file ran, including the 3 new/inverted ones. (2) pnpm lint (FULL repo, eslint . --no-inline-config, under the verify lock) -> exit 0. No narrowing, no caveat. (3) pnpm --filter @objectstack/driver-mongodb typecheck -> exit 0, but READ AS NOT MEASURED for the test layer: this package's tsconfig excludes **/*.test.ts (tsc --listFiles gives 0 hits for the edited test file). Re-checked with the exclusion lifted: ZERO errors in all three edited files; the 10 errors that surface are in 8 test files I never touched and match the recorded TEST_DEBT ledger entry for this package EXACTLY (recorded 10 = measured 10, TS1309 x7 + TS2550 x3), so this diff adds no debt. (4) Gate family DERIVED from the real diff (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, three-dot from merge base d48929efe) - the dispatch named none. 15 targeted gates green, each exit code captured before any pipe: check:driver-conformance (50 covered cells), check:undeclared-dep-imports (1816 specifiers judged - covers the new AggregationFunction value import), check:engine-double-contract (705 pinned), check:where-matcher, check:test-source-alias, check:cross-package-test-inputs, check:objectql-double-limit, check:type-source-resolution, check:keyed-text-bounds, check:comment-mask-adoption, check:changeset-gate-self-tests, check:nul-bytes (7286 files scanned), check-empty-changeset, check-changeset-no-major, check-adr-0087-registration. NOT run locally: check:type-check-debt --re-measure, which needs the whole workspace closure built; left to CI. (5) DOWNSTREAM bounded mechanically rather than by running 19 packages: the emitted dist/index.d.ts export list and the AggregationInput interface are unchanged, so no dependent can move on types. (Consumers enumerated with the PREFIX filter '...@objectstack/driver-mongodb' = dependents.) (6) ABLATION / reverse verification. Predicted direction: RED on exactly the new assertions, GREEN on the control. Observed exactly that - restoring the pre-fix mongodb-aggregation.ts from the merge base gives 'Tests 3 failed | 11 passed': the two refusal pins and the inverted pin fail, while the positive control walking AggregationFunction.options stays green, so the ablation is targeted rather than a blanket break. NO REBUILD NEEDED AND NONE CLAIMED: the suite imports the builder by relative path ('./mongodb-aggregation.js') inside its own package and this package's vitest.config.ts declares no alias, so vitest resolves to the sibling SOURCE, not dist. Mutation confirmed ON DISK by hash - c40300b0c8b8a06306fa157fcd88afec6f62843e vs HEAD blob b21ced0ecc78907934e29da800b6c091089d2715 - plus marker counts (lowering arms back 2, helper gone 0). Restore leg proven to the same standard: git checkout HEAD -- ABSOLUTE_PATH, hash back to b21ced0e, git diff HEAD empty. Both legs ran under a trap with absolute paths. (7) LIMIT, stated and not papered over: NO LIVE MONGOD in this fleet (mongodb-memory-server's download is refused by the egress proxy, #5517). Every behavioural reading comes from the in-process evaluator, which MODELS THE MANUAL rather than observing a server. Nothing here is verified against a real mongod.", "mcp_calls": "1 - one targeted MCP search_issues, declared as a channel switch: repo-scoped REST reads/writes worked all run (issue, comments, PR create, PR read-back), but /search/issues answered 403 'sessions are bound to their configured repositories', so the duplicate check took the sanctioned single MCP search. Everything else went through zero-quota REST.", "open_questions": [ { "question": "Landing order and refusal shape vs PR #13076. My PR refuses the two retired names with an EXPLICIT named arm instead of literally deleting them and falling through to #12818's default-arm refusal, because that refusal is not on main yet. Which shape does the PM/maintainer want?", "options": [ "A - Take this PR as-is (explicit named arm). Safe in EITHER landing order, no regression window, and after #13076 lands the arm still tells a caller the name was REMOVED rather than merely unrecognised - the same distinction AggregationFunction's own error map draws. Cost: the two refusal sites coexist in one switch, and the arm is arguably redundant once #13076 lands (driver-sql keeps a deliberately unreachable classifier branch for a comparable reason, so the repo tolerates this shape).", "B - Hold this card until #13076 merges, then re-cut as a pure deletion. Gives the exact parity SHAPE the SQL faces have (one generic classifier, no named arm). Cost: this card is blocked behind a PR that is itself parked behind the contract-review gate whose tier (claude-fable-5) was MEASURED unavailable this round, so 'wait' may be long; and the divergence the card is about stays live meanwhile.", "C - Merge this first, then have #13076 rebase and drop the now-redundant arm. Cost: churn on someone else's in-flight PR, and it is not mine to schedule." ], "recommendation": "A. It is the only form that is correct on main TODAY with an honestly-green pin, it removes the divergence now, and it costs one documented arm that a follow-up can trivially retire. B and C both make correctness wait on an ordering I do not own. Note the shapes differ only in WHERE the refusal lives - the ANSWER (INVALID_QUERY/400) is identical in all three." }, { "question": "A pin this PR falsifies lives on a branch, not on main. PR #13076 creates mongodb-unrecognised-aggregate-function.test.ts, whose 'recorded, not fixed' block pins these two arms as CURRENT BEHAVIOUR (its changeset says so explicitly: the two retired arms are 'byte-identically unchanged ... filed as #13075'). Whichever of the two PRs lands second must move that pin.", "options": [ "A - PM tracks it and tells whoever lands second to move the pin in the same PR.", "B - Fold the pin move into #13076 pre-emptively (it owns the file)." ], "recommendation": "A. I cannot move a pin in a file that does not exist on my base, and editing another card's in-flight branch is not mine to do. Flagged in my PR body under 'Pins moved deliberately' so the second lander cannot miss it." } ], "out_of_scope_findings": [ "NONE FILED - the one candidate turned out to be already covered, and filing it would have been a duplicate. driver-mongodb's tsconfig excludes **/*.test.ts so its test layer is type-checked by nothing; that is a KNOWN, LEDGERED, ratcheted state (check-type-check-coverage.mjs TEST_DEBT '@objectstack/driver-mongodb': errors 10, whose note already spells out the exclusion), my measurement matches it exactly at 10, and the general card is OPEN and QUEUED as #12511 ('a type error in a ledgered package's tests is invisible to its own typecheck ... 19 packages in that state'). Reported rather than re-filed.", "OBSERVATION, not filed: the causal reason these arms outlived the #6188 retirement is that AggregationInput.function is a bare `string` here, whereas objectql's in-memory fallback switches on the ENUM TYPE - so `case 'array_agg'` stopped compiling there and could not survive, while it compiled fine here. Typing this driver's function field would make the class mechanically impossible, but that is itself an accept-face narrowing needing its own card and its own contract review, so it is recorded here rather than done." ] }Generated by Claude Code
huangyiirene commentedon Aug 29, 2026
分诊席位 —— 把本卡与 #12818 的契约审查问题收敛成一个可裁的形状。⛔ 不代审。
两张卡是同一函数(
buildAccumulator)的两个 accept-face 收窄,两个 PR(#13122 / #13076)都自评 Clause ② 触发。审查者面对的其实是同一个问题问两遍,且两遍的证据强度不同:array_agg·string_agg(#6188 已退休)examples/里count×16 /sum×8 /groupBy×9 命中 ⇒ 是真零不是坏读;dataset compiler 的SUPPORTED_AGGREGATES由AggregationFunction.options派生,结构上不可能发出退休名median、错拼、未收窄的method)⭐ 本卡的普查是让两张卡都变得可裁的东西。 它把"删掉会不会伤到人"从猜测变成了一个有分母的读数,且反向也扫了(
driver-memory的三个 switch 恰好只 lower 声明的六个)。⛔ 没有这个普查,本卡就只能停在"移除了某个调用方今天可能在用的东西"——那是不可裁的。mongodb-aggregation.ts是已发布包的导出模块。普查只能覆盖本仓;⛔ 仓外调用方无法测量——#13076 自己也把这条写明了("a JavaScript caller, or one on the far side of anyany, hands the builder whatever string it likes")。⇒ 需要裁的就是这一句:接受这个不可测残余、按 patch 发;还是要求先给一个 deprecation 期(先告警一版、再拒绝)?
倾向(一个意见,不是裁决):#13076 给出的 patch 论证有同包同类先例——#10576 的 per-aggregation⚠️ 置信缺口:我没有独立核对 #10576 的 changeset 等级,这是接受 PR body 的转述;这一条正好是整个 patch 论证的支点,值得裁前核一眼。
filter拒绝,同样是"原本静默聚合了错的行、改成 501",作为 Patch Change 发在@objectstack/driver-mongodb@17.2.0。若接受那条先例,本卡按 patch 走是一致的。顺序(已在 PR #13122 上裁定,此处只作索引)
#13076 先,#13122 后;之后 #13122 需原地翻转
mongodb-unrecognised-aggregate-function.test.ts里把这两个名字钉为"当前行为"的两条断言。理由与细节写在 #13122 (comment) 。⛔ 席位红线:不 flip ready、不入队、不 approve、不合并。
Generated by Claude Code
claude commentedon Aug 29, 2026
os-dev-report
{ "issue": 13075, "status": "done", "branch": "claude/issue-13075-mongodb-retired-agg-arms", "pr": "https://github.com/objectstack-ai/objectstack/pull/13122", "premise_still_valid": true, "summary": "Follow-up reconciliation of PR #13122 with #13076 (the PR for #12818), which merged into main as e06237055. origin/main was merged into the branch as merge commit a1daceb92 (no force-push, no history rewrite); the textual merge was CLEAN, which is exactly the case AGENTS.md warns is not a working merge, and the driver-mongodb suite reproduced 3 failures on the merged tree before any fix. The four PM-ruled fixes: (1) the duplicate `AggregationFunction` import folded to a single occurrence by git's merge, verified by grep rather than assumed; (2) 'array_agg' and 'string_agg' removed from #13076's LOWERED_HERE roster, with the sibling LOWERED_AND_DECLARED deliberately KEPT rather than collapsed now that the two sets are equal (the filter is what keeps the refusal messages honest about the next name this face lowers ahead of the enum); (3) the three pins #13076 landed in mongodb-unrecognised-aggregate-function.test.ts INVERTED IN PLACE, never deleted and never re-baselined; (4) both refusal producers kept, refuseRetiredAggregateFunction ('was REMOVED ... at #6188') beside #13076's refuseAggregateFunction ('is not a declared aggregate function'). The two stale prose passages #13076 landed (LOWERED_HERE's warning paragraph and the AggregationInput docblock) are rewritten in the past tense naming #13075, reasoning intact. PR body updated with a Reconciliation section. Still draft, labels untouched, auto-merge not armed, nothing enqueued.", "reconciliation_detail": "Why the pins were INVERTED rather than deleted: #13076 wrote them to RECORD a divergence it could not close, so deleting them (or re-baselining the class-2 roster by quietly dropping two entries) leaves a tree indistinguishable from the divergence silently REOPENING. The two lowering cases now assert the refusal ENVELOPE - code and status per ADR-0112, the 'was REMOVED' wording, and that the message is NOT the class-1 undeclared sentence - never a bare toThrow(). The class-2 LOWERED roster shrinks to the declared six and the two names it lost move to a sibling RETIRED_AND_REFUSED constant whose members the same case now asserts DO refuse, so the removal stays a measured change in both directions. Three smaller clauses in the same falsified family were corrected and are NAMED in the PR body rather than fixed silently: this PR's own changeset (tense only - `default` no longer answers a $sum), the test file's head-note list of reverse-verification CONTROLS, and the UNDECLARED roster comment (both names are refused here now, but by the RETIRED producer, so they must stay out of that roster's startsWith assertions). packages/drivers/driver-sql/src/sql-driver.ts untouched; no test skipped, quarantined or disabled; nothing added to any baseline or ledger.", "tests": "REPRODUCED FIRST, on the merged tree: `pnpm --filter @objectstack/driver-mongodb test` = 'Test Files 1 failed | 20 passed | 5 skipped (26)' / 'Tests 3 failed | 484 passed | 143 skipped (630)' - exactly the three pins the PM named. FINAL HEAD 42ea291f5 (union run AFTER the last commit): same command = 'Test Files 21 passed | 5 skipped (26)' / 'Tests 487 passed | 143 skipped (630)', os-verify-lock 'VERDICT command-exit 0'. All three inverted pins observed green by name under --reporter=verbose. `pnpm lint` (full repo, `eslint . --no-inline-config`, no narrowing) LINT_EXIT=0. `pnpm --filter @objectstack/driver-mongodb typecheck` TYPECHECK_EXIT=0, but read as NOT MEASURED for the test layer: this package's tsconfig.json excludes **/*.test.ts. Re-run with the exclusion lifted and --listFiles confirming both edited test files are in the program: ZERO errors in all three edited files; the 10 errors that surface belong to 8 untouched test files and are an artifact of that ad-hoc config's module semantics, not a finding. GATES: family re-derived at the final head by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (5 paths vs merge base 44ea21eef, three-dot, script's own change set). 30 gates exit 0, exit status captured before any pipe - including check:where-matcher (run explicitly, since the derivation does not name it by path; gap filed as #13163), check:driver-conformance, check:engine-double-contract, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage, check:query-options-erasure, check:undeclared-dep-imports, check:nul-bytes, check:changeset-gate-self-tests, check:published-files. 3 gates NOT MEASURED, each by its own printed PREREQUISITE NOT MET verdict line, counted as neither pass nor red: check:dual-build-cjs-loads ('this gate reads built output, and some package has no dist/' - needs a full pnpm build this worktree does not carry), scripts/check-test-completeness.mjs ('grades a saved turbo run test log, and no log was named' - its own text says to record NOT MEASURED when reached from the derived family), scripts/pm/check-half-states.mjs ('the token in the environment is not a valid GitHub credential'). ABLATION (direction predicted before the run): restore the pre-#13075 $push lowering of both names and the inverted pins fail on refusalOf's own \"expected the builder to refuse ..., but it returned a pipeline\", NOT on an absent code. Measured: 'Tests 5 failed | 30 passed (35)', every failure on that exact message - the two inverted pins, the class-2 roster case, and the two sibling pins in mongodb-aggregation.test.ts; everything else green. NO REBUILD was involved and none is claimed: the suites import ./mongodb-aggregation.js by relative path inside their own package and this package's vitest config declares no alias, so vitest resolves the sibling SOURCE, never dist. Mutation proven ON DISK before any reading - marker counts anchored at the text meant to change (injected $push arm = 1, deleted refusal call = 0) plus a blob hash differing from the HEAD blob fc601da3. Restore leg proven the same way, under an EXIT INT TERM trap using absolute paths: `git checkout HEAD -- ABSOLUTE_PATH`, hash back to fc601da3, `git diff HEAD` empty, `git status --porcelain` empty.", "mcp_calls": "0 - every GitHub read and write on this run went through repo-scoped REST (probe: GET /repos/objectstack-ai/objectstack/pulls/13122 answered HTTP 200 from this container), so no MCP GitHub tool was called.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
fix(driver-mongodb): refuse an unrecognised aggregate function instea…