Skip to content

fix(analytics): where 门拒收 $ 算子与非 $ 键混写的字段约束(#6444) - #6583

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-6444-mixed-operator-refusal
Aug 8, 2026
Merged

fix(analytics): where 门拒收 $ 算子与非 $ 键混写的字段约束(#6444)#6583
os-project-manager merged 1 commit into
mainfrom
claude/issue-6444-mixed-operator-refusal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Closes #6444

按 2026-08-08 02:12Z 维护者裁决(Option A——拒收)实施,基于已并入 #6445origin/main1a53a0253)。只动 strategies/filter-normalizer.ts 与其测试;同包另一扇门 read-scope-sql.ts 零字节未动(不在 diff 中)。未实现任何展平回退(Option B 被裁决否决)。

实测(先验前提,再实现)

origin/main @ 1a53a0253 上直接调 normalizeAnalyticsFilterTree({ where }) 逐行复测。前提成立opKeys.length > 0 的分支只遍历 $ 键就 return,嵌套关系展平在其后,走不到——非 $ 兄弟键静默消失,与值无关:

where 归一化结果 读法
{d: {$eq: 1, nested: 'x'}} d equals [1] nested 合取项静默消失
{d: {$eq: 1, nested: undefined}} d equals [1] 同上——与值无关,正是 #6386 钉住的测量
{amount: {gte: 10, $lte: 20}} amount lte 20 漏写 $ 的典型手误:下界静默没了
{$not: {d: {$eq: 1, nested: 'x'}}} NOT(d set AND d = 1) 兄弟键在取反内部消失
{$not: {d: {$null: true, nested: 'x'}}} NOT(d set AND d notSet) 矛盾式取反成 TRUE ⇒ 每一行

最后一行是本轮实测新量到的、比 issue 正文更响的一格:#5146 的守卫在兄弟键还在时判的 requireValue,兄弟键随后在 fieldLeaves 里消失,剩下的合取自相矛盾,取反后放行整个数据集。每一行方向都是加宽——同一个函数 MONGO_TO_CUBE_OP 未命中分支的注释明令禁止的 #3650 败法。

裁决四项要求——逐项合规

# 要求 落点
1 消息点名非 $ 键并给出两种合法改写(算子拼法 gte$gte 嵌套关系形),区分两种无法消歧的意图 mixedFieldWrapperError:逐键点名(多键全列,有专门用例)、"gte" → "$gte" 加原位示例、嵌套形加它编成的点号成员 "amount.gte" 加显式 $and 组合(一个 JSON 对象写不了两次同名键)。消息合同由 shows BOTH legal rewrites 用例逐片断言;措辞唯一性(#5240)由擦除比对用例钉死
2 拒收测试断言 code + status(ADR-0112),裸 toThrow() 不算 新文件每条拒收用例(8 位置 + 多键 + 4 条改写路径)都断言 code === 'INVALID_FILTER'status === 400;账本的 envelope 块对第 11 行自动覆盖同两项
3 #6445 留下的 pin(a non-$ SIBLING … still dropped)翻成正向拒收断言 filter-normalizer-undefined-comparand.test.ts 同一用例原位翻转,保留原 pin 的同两条输入nested: undefined / nested: 'x'),断言消息 + code + status;注释记下翻转前后的可读历史
4 范围只此一门;#5930 五编译面收敛维持 on hold;按 #6410 条款逐面申报 见下表;read-scope-sql.ts 零字节

#6410 编译面清单——逐面申报

处置 依据(实读代码,非推断)
service-analytics filter-normalizer.ts changed(本 PR) assertUnmixedFieldWrapperfieldLeaves 包装分支、#5240 空约束闸之后
service-analytics read-scope-sql.ts already conformant compileFieldkeys.some((k) => !k.startsWith('$')) 即 fail-closed 拒收(:379-382)。零字节未动
driver-sql sql-driver.ts(wasm / turso-local 继承) already conformant(方向上) 对象比较数所有键都按算子迭代,非 $ 兄弟键落到发射器 default: throw unsupportedFilterError("Unsupported filter operator …")(:7416-7422)——响亮拒收、点名键,不丢
turso RemoteTransport buildWhereSQL already conformant unsupportedOperator 对非 $ 键有专门措辞object comparand whose key "…" is not an operator,:2189-2196),invalidFilterError 信封
formula matchesFilterCondition out of scope(如实记差异) evalField:任何非 $ 键使整个约束 return false(matches-filter.ts:161)——静默收窄(该行被排除),既不丢键加宽也不响亮拒收。与裁决面向不一致但方向相反且无声;按裁决要求 4 与 #5930 hold 不在本单动,差异如实记档
objectql having-filter.ts(半面,#5905 already conformant(方向上) isOperatorObject 只要有 $ 键即真,随后全部键按算子迭代,非 $ 键落 default: throw unknownOperator(op, 'condition')(:194-196)
driver-memory / driver-mongodb out of scope #5499 冻结:pin-annotate 而非翻转

反向验证(方向先预判、写进测试文件头,再跑)

唯一旋钮:摘掉 fieldLeavesassertUnmixedFieldWrapper(key, wrapper) 这一次调用。

预判:新文件 19 条中 15 红(8 个位置行 + 多键行 + 措辞唯一性行 + 双改写行 + 4 条 $not 改写路径行),4 条对照绿(两条纯形状、邻座拒收措辞、$eq: undefined 混写行——它由 #6386 的闸先拒,与本闸无关);翻转的 pin 1 红;账本第 11 行在两个循环里 2 红;其余全绿。合计 18 红

实测Test Files 3 failed | 66 passed / Tests 18 failed | 1393 passed——红的正是预判的 18 条、只在预判的 3 个文件里,逐条名单核对一致;两个纯形状对照块、#6386 全部用例、null 对照组全程未红一行。恢复闸后全绿。

措辞唯一性用例带着 #6445 在孪生用例上量出的防空转守卫(先逐行断言确实拒收,再擦除比对),反向档位下它按预期红在「did not refuse」一步,而不是空着绿。

null 对照组与 #6386 的闸:零字节

filter-normalizer-undefined-comparand.test.ts 的 diff 只有翻转的那一个用例:13 行 NULL_CONTROL、七行表、位置清单、$null/$exists 旗标块逐字节未动,全程绿。comparand()$null/$exists 恒等读、#5332/#5526 裁决——不在 diff 中。两闸次序为实测事实并钉了一条用例:{d: {$eq: undefined, nested: 'x'}}#6386 的闸先答(同信封,REST 面无差别),注释写明这是实现事实而非契约。

夹具分诊与消费半径

门禁(全部实跑)

结果
pnpm --filter @objectstack/service-analytics test Test Files 69 passed (69) / Tests 1411 passed (1411)(基线 68 文件;本 PR +1 文件 +19 用例,另 3 条用例原位改写)
tsc --noEmit -p packages/services/service-analytics 10 errors,与 check-type-check-coverage.mjs 账本记录的 10 一致;逐文件核对,全部在原有 3 个测试文件,本 PR 改动文件类型错误
eslint(4 个改动 ts 文件) exit 0
node scripts/check-nul-bytes.mjs OK (scanned 6158 tracked text file(s));另对 5 个改动文件跑 grep -naP 控制字节自扫描,clean
pnpm check:engine-double-contract OK — 92 pinned, 133 in the DEBT ledger, 2 exempt.
check:error-code-casing ✓ no lowercase error codes in 3198 scanned file(s) (ADR-0112).
check:route-envelope ✓ Dispatcher domains — 16 audited … (0 ratcheted)
check:empty-changeset ✓ … (1 declaring changeset(s) added)
下游 pnpm --filter @objectstack/rest test Test Files 66 passed (66) / Tests 939 passed (939)
下游 pnpm --filter @objectstack/runtime test Test Files 111 passed (111) / Tests 1611 passed (1611)

Changeset:.changeset/mixed-wrapper-refusal.md,patch @ @objectstack/service-analytics(可观测行为变更:一个此前被静默收窄读取的形状改为 400 拒收)。

触达性(与 #6386 的一个实质差别)

undefined 过不了 JSON,本形状过得了:它可以躺在库存 dashboard filter / report runtimeFilter / dataset filter 里,也可以由 AI 直接写出({amount: {gte: 10, $lte: 20}} 正是漏 $ 的典型手误)。examples 全扫无现存实例,issue 正文「未证实触达」维持成立;本 PR 把这个形状从「静默画一张比作者写的宽的图」变成一句点名键、给出两种改法的 400。


Generated by Claude Code

…sibling keys (#6444)

A field constraint object carrying $-operator keys and non-$ keys at once
used to compile its operators and silently DROP every non-$ sibling —
fieldLeaves's operator arm iterated opKeys only and returned, and the
nested-relation flatten sits after that early return. Dropping a conjunct
WIDENS the query (#3650), and inside a $not the surviving guard could be
contradictory, negating to TRUE — every row.

Ruled Option A (refuse) on 2026-08-08: the mixed wrapper is refused through
the module's one envelope (INVALID_FILTER / 400), with a message that names
the offending non-$ key(s) and shows BOTH legal rewrites — the operator
spelling (gte -> $gte) and the nested-relation form — because the shape has
two intents this door cannot tell apart. Option B (flattening) was rejected:
it would compile the missing-$ typo into a predicate on a non-existent
member such as amount.gte.

The two pure shapes do not move: all-$ wrappers compile as before, all-non-$
wrappers keep flattening to the dotted member. #6386's sibling-drop pin flips
to a positive refusal assertion; the refusal ledger gains the eleventh row
(addedAfter5352: #6444). read-scope-sql.ts, the $null/$exists flag
semantics and the null-comparand rulings (#5332 / #5526) are untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USNUyHEr7uaU6MoEWXitei
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 8, 2026 5:53am

Request Review

@github-actions github-actions Bot added the size/l label Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-analytics.

8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/data-api.mdx (via @objectstack/service-analytics)
  • content/docs/api/index.mdx (via @objectstack/service-analytics)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/service-analytics)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/service-analytics)
  • content/docs/plugins/packages.mdx (via @objectstack/service-analytics)
  • content/docs/releases/implementation-status.mdx (via @objectstack/service-analytics)
  • content/docs/releases/v17.mdx (via @objectstack/service-analytics)
  • content/docs/releases/v9.mdx (via @objectstack/service-analytics)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 8, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 8, 2026 05:56
@os-project-manager
os-project-manager added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 2604d34 Aug 8, 2026
25 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-6444-mixed-operator-refusal branch August 8, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants