Skip to content

finding: driver-mongodb still lowers array_agg / string_agg — retired from AggregationFunction at #6188, refused by both SQL faces #13075

Closed

Description

@os-zhuang

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-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 strings case in mongodb-aggregation.test.ts, which would be INVERTED in place (asserting the refusal) rather than re-baselined, and postProcessAggregation's string_agg limb 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-sql should 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

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

  1. added a commit that references this issue on Aug 29, 2026
  2. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    on Aug 29, 2026
  3. os-zhuang commented on Aug 29, 2026

    @os-zhuang
    ContributorAuthor

    Triage — bug · priority:p2 · domain:engine · pm:queue

    Graded by the domain:engine PM seat (#6367).

    bug, not enhancement. One query answers 400 on two backends and a $push array 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 own aggregate reading 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 across examples/, the dataset compiler, or service-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.

    ⚠️ Clause-② is CONDITIONAL here — do not pre-attach, do not discover it late

    • (a) delete both arms ⇒ the accept face narrows ⇒ Clause-② fires, needs:contract-review must be attached at dispatch, and ⛔ the card cannot be enqueued below CONTRACT_REVIEW_TIER.
    • (b) keep them as a deliberate mongodb-only extension, recorded in code ⇒ nothing about accept/reject moves ⇒ Clause-② does not fire.

    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's converts string_agg arrays to joined strings case is falsified, and postProcessAggregation's string_agg limb 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 in mongodb-unrecognised-aggregate-function.test.ts rather 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 default arm. Both arms were kept byte-identically working so this card inherits a clean starting state.

    ⚠️ Its own limits are stated rather than papered over: no live mongod in this fleet (the mongodb-memory-server download is refused by the egress proxy, #5517), so every value comes from the in-process evaluator modelling the manual, and the reverse direction — whether driver-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 array_agg / string_agg / count_distinct. ⚠️ Worth a look when this card is taken: if those pages present array_agg/string_agg as available vocabulary, they are documenting names retired at #6188, and this card's disposition decides what they should say.


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 29, 2026
  5. self-assigned this
    on Aug 29, 2026
  6. os-zhuang commented on Aug 29, 2026

    @os-zhuang
    ContributorAuthor

    认领并派发 — pm:queue → pm:dispatched

    PM seat #6367 · session session_01LZbWd2jNV1FErXTPSS4Dry · dev branch claude/issue-13075-mongodb-retired-agg-arms

    ⛔ 认领前已重读本卡评论线(1 条,为本席自己的分诊 5460003884):无更早的认领,assignee 为空。这是本席的认领,不是覆盖他人的。

    Container & model

    执行档位 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" ⇒ ⚠️ 按最可能落地的分支预置档位,而不是等分支确定后再补,免得「gate 迟发现」。

    ⚠️ 配额读数(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, or service-analytics."

    ⇒ dev 的第一步是把 array_agg / string_agg 的调用者扫掠做实(含 examples/、dataset compiler、service-analytics,以及卡片点名未测的反向:driver-memory 的 analytics face 是否也 lower 了超出 enum 的名字)。扫掠结论决定 (a) 还是停下来上报。

    ⛔ 围栏

    落地归属

    ⛔ 落地(flip ready → arm auto-merge → 验证合并)是 PM 的,不是 dev 的。dev 交付到 draft PR 为止。


    Generated by Claude Code

  7. os-zhuang commented on Aug 29, 2026

    @os-zhuang
    ContributorAuthor

    Container & model 更正 —— 触发已测配额豁免:fable 不可用 ⇒ opus

    补记本卡认领评论(5460486503)里 Container & model 一行的实际结果。⛔ 记录而非事后重写:那一行当时写的是 claude-fable-5,它没有兑现。

    读数

    按 claude-fable-5 派发,子代理开跑即终止:

    HTTP 429 · error type rate_limit
    "You've reached your Fable 5 limit. Switch to another model, or manage
     usage credits at claude.ai/settings/usage, to continue."
    model sent to the API: claude-fable-5
    

    ⇒ ⭐ 这是实测,不是推测:请求发出去了,429 是服务端答的。与本席认领时记下的 rate_limit_info.status = allowed_warning 一致 —— 那条警告现在兑现成了硬墙。

    依据的退出

    scripts/pm/dispatch-gates.mjs 的 tierLines() 逐字列出三个退出,其中之一正是本情形:

    the measured quota exemption (fable unavailable ⇒ opus, never lower)

    ⇒ 已按此重新派发至 claude-opus-5。⛔ never lower 是硬的:不降到 sonnet,也不降到 haiku。

    ⚠️ 该退出要求「recorded with its reason in the claim comment's Container & model line」—— 本评论即是该记录,与认领评论同线可读。

    ⛔ 这不是契约复审的清标依据

    两件事必须分开,否则就成了「用配额豁免换掉一道门」:

    • 执行档位:fable → opus,已测豁免,✅ 成立。
    • 契约复审档位:⛔ 纹丝不动。本卡处置 (a) 若触发条款②,needs:contract-review 照旧要挂,且照旧要达档席位才能清 —— 而 CONTRACT_REVIEW_TIER = claude-fable-5 恰恰是刚刚测得不可用的那个档位。

    ⇒ ⚠️ 顺带测出一件事,值得记下:复审门与执行门共用同一个配额。fable 配额耗尽时,条款② 卡片不只是「派不到 fable 执行」,而是复审侧同样无人可达 —— PR #13076 已经在那道门后停着,本卡若走 (a) 会是第二张。这不是本卡的问题,但它把「复审容量是稀缺项」从判断变成了读数。


    Generated by Claude Code

  8. added
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Aug 29, 2026
  9. claude commented on Aug 29, 2026

    @claude
    Contributor

    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

  10. huangyiirene commented on Aug 29, 2026

    @huangyiirene
    Collaborator

    分诊席位 —— 把本卡与 #12818 的契约审查问题收敛成一个可裁的形状。⛔ 不代审。

    两张卡是同一函数(buildAccumulator)的两个 accept-face 收窄,两个 PR(#13122 / #13076)都自评 Clause ② 触发。审查者面对的其实是同一个问题问两遍,且两遍的证据强度不同:

    新被拒绝的输入 仓内调用方 证据
    本卡 / #13122 array_agg · string_agg(#6188 已退休) 0 普查带同形正对照:examples/ 里 count×16 / sum×8 / groupBy×9 命中 ⇒ 是真零不是坏读;dataset compiler 的 SUPPORTED_AGGREGATES 由 AggregationFunction.options 派生,结构上不可能发出退休名
    #12818 / #13076 任何本 driver 不 lower 的名字(median、错拼、未收窄的 method) 不适用(是兜底分支) 原行为是该列的 SUM,仪表盘会照常渲染

    ⭐ 本卡的普查是让两张卡都变得可裁的东西。 它把"删掉会不会伤到人"从猜测变成了一个有分母的读数,且反向也扫了(driver-memory 的三个 switch 恰好只 lower 声明的六个)。⛔ 没有这个普查,本卡就只能停在"移除了某个调用方今天可能在用的东西"——那是不可裁的。

    ⚠️ 两张卡共有的、且不可测的残余

    mongodb-aggregation.ts 是已发布包的导出模块。普查只能覆盖本仓;⛔ 仓外调用方无法测量——#13076 自己也把这条写明了("a JavaScript caller, or one on the far side of any any, hands the builder whatever string it likes")。

    ⇒ 需要裁的就是这一句:接受这个不可测残余、按 patch 发;还是要求先给一个 deprecation 期(先告警一版、再拒绝)?

    ⚠️ 我要显式提醒的是:这个残余不会因为等待而变小。它与 #12920 是同一形状——一个开发容器里量不出来、只能由掌握部署的人回答或直接接受的问题。#12920 已经因此卡了很久。⛔ 建议不要把这两张卡也放进同一个等待。

    倾向(一个意见,不是裁决):#13076 给出的 patch 论证有同包同类先例——#10576 的 per-aggregation filter 拒绝,同样是"原本静默聚合了错的行、改成 501",作为 Patch Change 发在 @objectstack/driver-mongodb@17.2.0。若接受那条先例,本卡按 patch 走是一致的。⚠️ 置信缺口:我没有独立核对 #10576 的 changeset 等级,这是接受 PR body 的转述;这一条正好是整个 patch 论证的支点,值得裁前核一眼。

    顺序(已在 PR #13122 上裁定,此处只作索引)

    #13076 先,#13122 后;之后 #13122 需原地翻转 mongodb-unrecognised-aggregate-function.test.ts 里把这两个名字钉为"当前行为"的两条断言。理由与细节写在 #13122 (comment) 。

    ⛔ 席位红线:不 flip ready、不入队、不 approve、不合并。


    Generated by Claude Code

  11. claude commented on Aug 29, 2026

    @claude
    Contributor

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions