Repository navigation
Description
修 #5324 / #5328 时把 driver-memory 的 normalizeFilterCondition 与 driver-mongodb 的 translateFilter 逐 arm 对读(后者是前者 $not → $nor 改写的先例),发现 mongodb 侧带着同一批「该拒收没拒收」的 arm。按 Prime Directive #10 单独记在这里,unassigned。
位置
packages/plugins/driver-mongodb/src/mongodb-filter.ts,translateCondition(:96)与 translateFieldOperators(:170)。
三条静默吞掉 + 一条信封缺失
1. $between 形状错误 —— 与 #5328 逐字同款
case '$between':
if (Array.isArray(value) && value.length === 2) {
result.$gte = store(value[0]);
…
}
break; // ← 形状不对时什么都不写,整条约束消失这是 #5328 在 driver-memory 上修掉的那段代码的孪生。同一输入 { score: { $between: 5 } }:
| 后端 | 行为 |
|---|---|
| driver-sql | INVALID_FILTER / 400 —— Operator "$between" on field "score" requires a [min, max] value array.(实测) |
| driver-memory | INVALID_FILTER / 400(#5328 修复后,措辞与 SQL 首句逐字一致) |
| driver-mongodb | 不抛,字段归一化成 {},交给 MongoDB → 结构相等比较 |
2. $and / $or 非数组、$not 非对象 —— 整条组合子消失
case '$and':
if (Array.isArray(value)) { … }
break; // 非数组 → 什么都不写
case '$not':
if (value && typeof value === 'object') { … }
break; // 非对象 → 什么都不写一个组合子写坏了不报错,而是不产生任何谓词 —— 在 $and 位置上等于「匹配全表」,方向是放大。driver-sql 的 assertFilterNodeList / assertFilterNode(#5134)与 driver-memory 现在的 shape gate(#5324)都拒收这些。
3. 未知算子拒收了,但丢了信封
default:
throw new Error(`[mongodb] unsupported filter operator '${op}'`);拒收本身是对的(注释还写明了 $where / $function 的 P0 理由,值得保留)。缺的是拒收的身份:
- 没有
code,没有status→mapDataError走 default 分支 → 客户端拿到 500 形状的裸{ error }。这正是 data: unsupported-filter-operator refusal ships without error.code and leaks the [sql-driver] prefix #4436 为 driver-sql、{ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240 为 driver-memory 已经补上的那件事,mongodb 是唯一还没补的后端。 - 消息里带
[mongodb]前缀 —— driver-sql 的unsupportedFilterError注释明确写过这类前缀「is driver-internal wording, and shipping it to clients is exactly what the analytics /query 未做 cube 存在性校验,未注册名直达驱动当表名;且错误路径原样回显驱动 SQL(#3770 同类,另一子系统) #3867 sanitiser exists to stop」,已从 SQL 侧删除。
另外 translateCondition 的 default 分支会跳过 ['limit','offset','fields','orderBy'] 四个 key —— 这是把查询级字段混进 where 的历史兜底,QueryASTSchema.where 是 FilterConditionSchema,不该有这四个 key;顺带一并看。
建议(不代裁决)
四家后端里三家已经统一到「无法编译 → INVALID_FILTER / 400 + 位置信息」,mongodb 补齐即可:
- 建一个与
driver-sql/driver-memory孪生的filter-refusal.ts(两边现有实现逐字可抄),unsupportedFilterError带INVALID_FILTER/ 400、不带包名前缀; $between补 else 抛错,措辞用 driver-sql 的首句Operator "$between" on field "…" requires a [min, max] value array.;- 组合子操作数补
assertFilterNode/assertFilterNodeList; default的现有 throw 换成带信封的版本。
未验证的部分
我没有跑 driver-mongodb 的测试套件(它需要真实 mongod / mongodb-memory-server,本地未起),以上全部来自逐行代码对读,不是实测。mongodb-filter-logic-conformance.test.ts 是否会因为这些改动变红也未验证。请实现方先实测再动手。
关联:#5328(driver-memory 的 $between 同款,已修)、#5324(未知算子逃出信封,已修)、#5134(driver-sql 的组合子形状门)、#4436 / #5240(信封统一)、#3867(消息前缀不外泄)、#4405(mongodb 的 $not → $nor 翻译)。
Activity
os-zhuang commentedon Aug 5, 2026
实测补充(正文自述「全部来自代码对读、不是实测」,这里补上),外加一条正文没覆盖、严重度更高的缝
修 #5347 / #5348(PR #5368)时需要动
mongodb-filter.ts的$nullarm,顺带把本条列的每一项都跑了一遍。没有改本条列的任何一项(那是本条的范围),只在这里留证据。跑法:
translateFilter是纯函数,不需要 mongod。本包的 live 套件依赖mongodb-memory-server(~123MB 下载,本容器下载不通,五个文件 121 例被describe.skipIf跳过),而这些缺陷全部落在翻译层 —— 它的输出就是发给 MongoDB 的查询文档,所以在这一层测既完整又到处都跑得起来。正文三条,逐条确认属实
$between: 5→ 字段归一化成{},正文说的「交给 MongoDB 做结构相等比较」属实({score: {}}匹配score字面等于空对象的文档,即通常是零行)。$and/$or非数组、$not非对象 → 整条组合子消失,返回{}= 匹配全表。方向是放大,正文说对了。default:的 throw 确实无code、无status,且带[mongodb]前缀。$op原样透传给 MongoDBtranslateCondition的switch只认$and/$or/$not,其余键走default:的字段路径。而字段路径的第一个判断是「值是不是带$键的对象」—— 不是的话就当隐式相等写进mongoFilter[key]:这两个文档会原封不动地发给 MongoDB,由 MongoDB 执行。
$where是服务端 JavaScript;$nor是一个 MongoDB 真实支持、但 Filter Protocol 从未声明的组合子。也就是说:#5348 的正文推测「driver-mongodb 的
translateCondition至少不会把它编成列(落到default的字段分支)」—— 这句话字面为真,但结论反了:它比编成列更严重。编成列(driver-sql / cloud RemoteTransport 的行为)只是静默空集;原样透传是让后端求值一个契约从未声明的算子,正是这个驱动自己点名的 P0。对照,同一批输入在其他后端:
{ $where: 'return true' }driver-memory(两个面)INVALID_FILTER/ 400(#5324 已修)driver-sql/driver-sqlite-wasm/ Turso localINVALID_FILTER/ 400RemoteTransportdriver-mongodb为什么记在本条而不另开一单
本条已经在要求给本包建
filter-refusal.ts孪生 + 给组合子操作数补assertFilterNode/assertFilterNodeList。节点位置的$键闸门是同一个函数(translateCondition)、同一次改动里顺手的一行,driver-memory的assertFilterConditionShape里逐字可抄:放在三个已声明组合子的
case之后、default:的字段路径之前 —— 因为「把$where当字段名」正是字段路径干的事。落在本条的完成范围内,所以不另开孪生单(objectstack#4949 的立单纪律)。但它把本条的严重度往上抬了一档,triage 时值得单独看一眼:其余三条是静默的错误答案,这一条是未声明算子被后端求值。PR #5368 在本文件里动了什么(避免和本条打架)
只动了两处,都与本条列的四项不重叠:
$nullarm 加了非布尔比较值的拒收($null的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347 裁定 A,四家后端一起收)。原来if (value === true) … else …,非布尔全落else→$ne: null,与 driver-sql 的IS NULL相反。unsupportedFilterError辅助函数 —— 把filterArrayReachedDriverError里原本内联的INVALID_FILTER/ 400 构造提取出来,让新增的拒收复用同一个信封,没有另造第三套。刻意没碰本条的
default: throw new Error('[mongodb] …'):换信封是本条的活,搭在 #5347 上做会变成一次无关的行为变更。抽出来的unsupportedFilterError正好是本条要的那个函数,修本条时直接用即可(注释里也写明了这一点)。另外
translateCondition/translateFieldOperators现在多了一个path参数(filter.$or[1].stage.$null这种位置字符串),本条补拒收时可以直接用来给消息带路径 —— 与 driver-sql / driver-memory 的措辞对齐。正文提到的「顺带一并看」那条
translateCondition的default跳过['limit','offset','fields','orderBy']四个 key —— 确认还在,行为如正文所述。补一句现场:driver-sql的applyFilters有逐字相同的一行(非算子分支里if (['limit','offset','fields','orderBy'].includes(key)) continue;),所以这是两家共有的历史兜底,不是 mongodb 独有的;要清就两家一起清,否则又是一处分叉。关联:#5347 / #5348 / PR #5368、#5324、#5328、#5240、#4436、objectstack-ai/cloud#1077。
Generated by Claude Code
Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
与 PR #5323 的范围对账(同步轮,会话
session_01ErbEDVAg1No9gdg1pgDAGB)PR #5323(#5239 的 driver-mongodb 三值归约,已同步到 main@
cdfbee2f0,head806949567)合入后,本单四项里的第 2 项整项闭合:$and/$or非数组、$not非对象不再静默吞掉 —— 归约的校验遍历(assertFilterNodeList/assertFilterNode)按 ADR-0112 信封(INVALID_FILTER/ 400)拒收并点名位置(filter.$or[0]式),消息不带[mongodb]前缀(analytics /query 未做 cube 存在性校验,未注册名直达驱动当表名;且错误路径原样回显驱动 SQL(#3770 同类,另一子系统) #3867)。12 形状 + 3 控制项 pin 在mongodb-filter-boolean-identity.test.ts;上面正文评论实测的{ $and: 'nope' }/{ $or: 42 }/{ $not: 'nope' }→{}三条,现全部 400。其余各项刻意未动,仍归本单:
$between形状错误({ score: { $between: 5 } }→{"score":{}})——发射器 arm 原样;$op原样透传({ $where: 'return true' }/{ $nor: […] }仍原样发给 MongoDB)——归约把非组合子键一律判'clause',与改前行为一致,没有替本单裁决;修复时评论里那行if (key.startsWith('$')) throw …现在有了现成挂点:reduceFilterKey三个组合子分支之后、字段判定之前;translateFieldOperators的default:裸Error信封 —— 未动(fix(driver-sql,driver-memory,driver-mongodb): 越界过滤输入在门口拒收,而不是每个后端各答一个 (#5347, #5348) #5368 已抽出的unsupportedFilterError仍在等本单来用)。另:修 #5347/#5368 与 #5239 归约的合流时发现并新立 #5376 ——
{ field: {} }(#5240 拍板拒收、#5327 已闸四家)在本驱动仍被作答,与本单同文件同信封,修哪单先到都建议顺带对齐另一单的挂点。Generated by Claude Code
Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
分诊(backlog sweep,主 backlog 代扫):挂⚠️ 与 #5376 同包,认领时跨轮串行。不构成认领。会话:
pm:queue+domain:engine。理由:落点packages/plugins/driver-mongodb(driver-*属 engine 域);与已裁的 #5328 同缝(形状错误的组合子被静默吞掉)+ 算子拒收缺 ADR-0112 信封,属把既有裁决铺到位,符合 auto-queue 判据。session_01N3uGFF8teXbpgtbEJ1aYXuGenerated by Claude Code
os-zhuang commentedon Aug 5, 2026
转
pm:on-hold(2026-08-05,维护者裁决,锚点 #5499):driver-mongodb 暂无客户,团队聚焦,投入冻结(撤销今晨入队分诊)。重启条件:出现真实客户需求时由维护者解冻。不派发、不催。会话:session_01N3uGFF8teXbpgtbEJ1aYXuGenerated by Claude Code
huangyiirene commentedon Aug 11, 2026
Claim: PM loop round 2 (drivers seat, post-unfreeze batch 1)
Session:
session_013ZU38vNzLXCscgdFZjKd53Branch:
claude/issue-5346-mongodb-filter-refusalsWorktree:
objectstack-issue-5346Domain:
domain:driversFile surface:
packages/drivers/driver-mongodb/src/mongodb-filter.ts(regions:classifyFilterKey+ the$betweenemitter arm),packages/drivers/driver-mongodb/src/mongodb-filter-*.test.ts,.changeset/*(stop on breach; explain in the report)Container & model: M,
mode:cloud,model: opusSerial constraints cleared: hot-file
sql-driver.tsuntouched by this card; lane in-flight was ZERO at selection; #7177 and #6682 (mongodb halves) deliberately DEFERRED to later rounds because they plausibly touch the same filter test files /translateFieldOperators— recorded here so the next sweep sees the ordering.Authorization: maintainer unfroze driver-mongodb 2026-08-11 (verbatim ruling recorded on #5499, comment
5249019855). This card rides with #5376 (same file, same envelope — both cards recommend the pairing): one dev, one PR,Fixes #5346+Fixes #5376.Scope note from the PM's pre-dispatch measurement on
origin/main@76d74ecb4: of this card's original four items, TWO are already fixed by other lanes — the combinator shape gates landed via #5239/#5323 (sharedreduceFilterVerdictwalk) and thedefault:arm envelope landed via #5702 (the docblock atmongodb-filter.ts:289records the carve-out's removal). Remaining scope: (1)$betweenmalformed-shape silent swallow (:611, no else), (2) node-level undeclared$-key pass-through (classifyFilterKeyreturns'clause'for any non-combinator key, so{ $where: 'return true' }reaches MongoDB — the P0-adjacent item from comment5186668033), plus #5376's{ field: {} }gate. Dev verifies premise-first as always.Generated by Claude Code
huangyiirene commentedon Aug 11, 2026
os-dev report — #5346 + #5376, one branch, one PR
Draft PR: #7528 · Branch:
claude/issue-5346-mongodb-filter-refusals· Base:origin/main@76d74ecb4· Session:session_018oM7XYyQ6AveqQs6eqxDdvPremise verification — the PM's hypothesis CONFIRMED
Two of #5346's original four items were already fixed by other lanes. Measured, not read:
Item 2's gates now hang off the shared
reduceFilterVerdictwalk asassertFilterNode/assertFilterNodeList; item 3's envelope routes throughunsupportedFilterError, and the docblock records the removal of the carve-out sentence that named this card. Zero diff on both — reported converged, not re-implemented.Remaining scope, all three confirmed still open and all three fixed here:
The gate-position argument, measured rather than asserted
The PM's ruling that the load-bearing copy belongs on the walk is confirmed by direct measurement — before the fix, all three shapes translated to
{}, i.e. match-all, when a{}disjunct settled the enclosing$or:An emitter-side gate would have refused or ignored each depending on its siblings. All three gates went into
classifyFilterKey; the$betweenemitter arm keeps a local check as defense for its own invariant, the dual-gate pattern the$nullarm documents. OneunsupportedFilterError, no third envelope.Verification
driver-mongodbpackage suitecheck:driver-conformancetsc --noEmitspec→types→metadata-core→core→driver-mongodb— buildsmongodb-memory-serverbinary blocked (proxy 403),describe.skipIf-gated by #551737 new cases in
mongodb-filter-shape-refusal.test.ts; every refusal case assertscodeandstatus, with the pinned sibling leading sentence asserted on top of the envelope rather than instead of it.One pre-existing pin was rewritten, not deleted:
mongodb-filter-boolean-identity.test.ts's "a field constrained by zero operators is still not ruled on (#5240)" recorded #5239's deliberate non-decision. #5376 is that decision arriving, so it now asserts the refusal while still pinning what #5239 was careful about — the verdict is unchanged, a field key still classifies'clause'.packages/drivers/driver-memorynot touched (#5499 keeps it frozen). Nocontent/docs/releases/edit. Changeset:.changeset/mongodb-filter-shape-refusals.md(patch).Open questions
None blocking. Two observations recorded for the PM rather than acted on:
['limit','offset','fields','orderBy']carve-out that driver-mongodb 带着 #5328 的同一条缝:形状错误的$between/ 非数组$and、$or/ 非对象$not全被静默吞掉,且它的算子拒收没有 ADR-0112 信封 #5346's body asked to "look at in passing" is left in place. Comment 5186668033 establisheddriver-sql'sapplyFilterscarries a byte-identical line, so it is a two-package historical fallback, not a mongodb quirk — clearing it here alone would create the divergence it is meant to remove. It is pinned by a new control case so a future cleanup has a measurement to work against.FILTER_LOGIC_CASESstill has noexpectRejectiondiscriminant, so these shapes could not enrol in the shared table (its own docblock atfilter-logic-conformance.ts:127-133names extending the shape as the spec lane's work). The private test file followsmongodb-null-comparand-refusal.test.ts's precedent. Extending the table remains spec-lane work and is unblocked by this PR —driver-mongodbis now compliant, so the last "one backend still answering" caveat in that docblock is stale and can be dropped whenever the table lands.CI convergence, the ready-flip and landing are the PM's.
Generated by Claude Code
huangyiirene commentedon Aug 11, 2026
ACCEPT — PR #7528 (covers this card and #5376; verdict recorded on both).
Review of record, verified against GitHub rather than the report's claims:
mongodb-filter.ts, two existing filter test files, one new suite, one changeset). Nocontent/docs/releases/, nodocs/adr/**, no generated-file paths,driver-memoryuntouched.$and/$or/$not归约成布尔单位元,非 filter 节点先响亮拒收 (#5239) #5323 combinator gates, drivers:$regex响亮拒收 +$icontains各后端实现(#4706 裁决 B 案 · 驱动半边) #5702 envelope) were re-measured and reported converged with zero diff — exactly the premise-first outcome the dispatch asked for.$betweenmalformed, node-level undeclared$-key pass-through,{ field: {} }) all refuse withINVALID_FILTER/ 400 through the existing constructor; gates on the validating walk with the sibling-dependence fixtures pinned (the three pre-fix match-all measurements are now test cases, so the gates cannot drift back into the emitter).codeANDstatus, sibling leading sentences asserted on top of the envelope. The [spec] FILTER_LOGIC_CASES 补空组合子的布尔单位元四条 —— 需与 driver-mongodb 的单位元归约同时落地 #5239 "deliberate non-decision" pin was rewritten to assert the arriving decision while keeping the verdict-unchanged half — reviewed and correct, not a deleted assertion.completed: success(the gate-family jobs, read individually).Flipping ready + queueing via auto-merge now. CI convergence and landing tracked by this seat.
Generated by Claude Code
fix(driver-mongodb): refuse malformed $between, undeclared node-level…
docs(driver-mongodb): re-anchor the dead tracker citations to the com…