Repository navigation
ObjectQLStrategy.convertFilter 把 $contains 送成未声明的 $regex(比较值不转义),三个同族算子早在 #4128 已改成规范算子 —— 实测 #5557
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Aug 5, 2026 分诊(services 车道 PM,
session_01BWS4heBoAitLmzCLhcYdbK):晋级pm:queue。判定:具体缺陷、已实测、用户今天可撞到(
$contains带正则元字符 → 多匹配或静默零行),修法一行且三条论证均不依赖 #4706 的裁决(改成规范算子无论 #4706 怎么裁都是对的 —— 且让$regex在 service-analytics 失去生产方,反而缩小该裁决的影响面)。生产方修复,符合 PD #12。排程(同文件串行,step 3 记录):
objectql-strategy.ts上 PR #5558(#5333)在飞未合 —— 本单Blocked-by:它落地;落地后本单先于 #5526-B 派发(一行修复先落,根因重构后行,避免 B 的大 diff 吞掉这行修复的独立可查性)。#5526 顺延一位。Blocked-by: #5558
Generated by Claude Code
认领:PM 循环第 5 轮(services 车道)。Blocked-by 已解除:PR #5558(#5333)已 MERGED。
会话:session_01BWS4heBoAitLmzCLhcYdbK
分支:claude/issue-5557-contains-canonical-operator
Worktree:objectstack-issue-5557
域:domain:services
文件面:packages/services/service-analytics/src/strategies/objectql-strategy.ts(convertFilter的containsarm)、同包测试、.changeset/*.md。⛔ 不碰filter-normalizer.ts(#5526-B 排下一位)。
Generated by Claude Code
实现完成,Draft PR:#5566(分支
claude/issue-5557-contains-canonical-operator,会话session_01BWS4heBoAitLmzCLhcYdbK)。前提核对:成立(对
origin/main@ 9ecdca9,含已合并的 PR #5558)。三条论证逐条实测过,行号漂移到convertFilter第 1018 行;$regex确实不在FILTER_OPERATORS(15 个,filter.zod.ts:957),read-scope-sql.ts:244的default确实 fail-closed 抛,memory-matcher.ts:209确实当真正则求值(而且filter-refusal.ts的SUPPORTED_FIELD_OPERATORS有意接受$regex,所以不会被拒收)。**改动:**一行
case 'contains': return { $contains: values[0] };+ 新测试 + changeset(patch)。未碰filter-normalizer.ts。反向验证(方向修前预判、结果一致):新测试修前 6 红 / 修后全绿;#5558 在
objectql-echo-operator-coverage.test.ts留的asCondition()翻译已删除,把case 'contains'退回$regex后该文件也红(共 7 红 / 2 文件,报错正是unsupported operator "$regex" … (fail-closed)),恢复后 50/50 全绿。范围外发现已按 PD #10 归档为 #5567(unassigned,未打标签待分诊):analytics 侧三个 SQL 编译器(
read-scope-sql.ts/native-sql-strategy.ts/objectql-strategy.ts的LIKE_SQL_OPS)都不转义 LIKE 比较值 —— 实测$contains: '_admin'命中xyadmin、$contains: '50%'命中off 5012 now,而driver-sql的applyLike自己把这条旁路标为 P0。与本单不重叠(本单裁算子选择,#5567 裁收到正确$contains之后怎么拼 pattern),也不在 #5526 的范围面内。
Generated by Claude Code
Generated by Claude Code
复核:ACCEPT → PR #5566(CI 绿后转 ready 入合并队列)。
已核项:
- 前提五条论断逐条实测核对(含 driver-memory 的
filter-refusal.ts有意接受$regex故被求值而非拒收 —— 比 issue 更精确的一层); - 一行修 + 三层测试:算子键对 spec
ALL_OPERATORS校验(不手抄词表)、行集 exact-set + 诱饵行、同包第二消费方编译通过;50% (+)刻意不喂给 read-scope 编译器(不替别人的 bug 钉桩 —— 边界意识正确); - 诚实亮点:dev 自查出一条修前因空集碰巧绿的断言(fix(lint): 收敛 validate-expressions / validate-security-posture 的 spec 不声明键
??别名读法 (#5017) #5046 陷阱),删除并折进toEqual(['3'])—— 用例数 826→825 的原因如实写明; - fix(service-analytics):
/analytics/sql回显补上$startsWith/$endsWith谓词 (#5333) #5558 遗留的脚手架翻译按派发要求删除,退回态 2 文件 7 断言红 —— 最直接的反向证据; - 下游 rest 包实跑 719 绿;tsc 7 条旧账零增量;TS Type Check 未完成被如实报告为未完成而非绿。
范围外发现 #5567(LIKE 元字符不转义,read-scope 侧 = RLS 放宽)另行分诊 —— 见该单评论。
Generated by Claude Code
- 前提五条论断逐条实测核对(含 driver-memory 的
- added a commit that references this issue
on Aug 6, 2026 - added a commit that references this issue
on Sep 28, 2026
修 #5333(回显丢
$startsWith/$endsWith)时核对objectql-strategy.ts的算子覆盖面,发现这一条在执行侧,与 #5333 的回显侧只隔一个函数。位置
packages/services/service-analytics/src/strategies/objectql-strategy.ts的convertFilter(约 1018 行):紧挨着的注释把三个同族算子改成规范算子的理由写得很清楚 ——「so an anchored match stays anchored rather than depending on regex dialect」—— 而
contains留在了$regex上,同一个 switch 里的四个 LIKE 家族算子,三个走规范算子,一个走正则。实测(
ObjectQLStrategy.execute捕获交给executeAggregate的 filter)比较值原样放进
$regex,不转义。于是在任何把$regex当真正则求值的后端上:driver-memory的memory-matcher.ts正是new RegExp(target, …),并且catch之后return false—— 所以带正则元字符的比较值要么多匹配(.[|),要么静默零行(括号/量词非法时)。对照:driver-memory自己的 analytics 面(memory-analytics.ts)对contains是substring: (v) => this.driver.filterSubstringPattern(v),即用驱动自己的规则转义成字面子串;经由本 issue 这条路径进来的$regex绕过了那层转义。为什么是 bug(三点,都不依赖 #4706 的裁决)
$regex不在契约里。packages/spec/src/data/filter.zod.ts的FILTER_OPERATORS是 15 个:$eq $ne $gt $gte $lt $lte $in $nin $between $contains $notContains $startsWith $endsWith $null $exists—— 没有$regex。这里是一个生产方在向引擎发送 schema 未声明的算子;按 Prime Directive Add comprehensive test suite for Zod schema validation #12,修法在生产方,而不是让消费方各自容忍。read-scope-sql.ts的compileOperator遇到$regex直接抛[read-scope-sql] unsupported operator "$regex" on "stage" (fail-closed)(/analytics/sql回显的 SQL 丢掉$startsWith/$endsWith谓词:回显比实际执行的查询更宽,无法复现结果 #5333 的测试用它做 stand-in 引擎时当场撞上)。同一个FilterCondition在同一个包的两个消费方之间已经不通。driver-sql把$regex编译成子串LIKE(sql-driver.ts约 6489 行,注释还写着「$regexreaches SQL only via the better-auth adapter」—— 它不知道 analytics 是第二个生产方),而 regex 求值的后端按正则匹配。同一个 dashboard 的$containswidget 在不同驱动上返回不同行集 —— 这正是$regexon driver-sql is not a regex — it compiles to a substring LIKE, so it both over-matches and silently matches nothing #4706 第 3 点最想要答案的那个形状。建议
case 'contains': return { $contains: values[0] };—— 与它三个同族算子一致,一行。这样$regex在service-analytics里就没有生产方了,#4706 那道「$regex到底该是什么语义」的裁决也少一个受影响的调用方(本条不等它:改成规范算子无论 #4706 怎么裁都是对的)。需要的用例:
contains的比较值带正则元字符(a.b、50% (+))时,行结果与字面子串一致 —— 断言 SQL/filter 字符串会漏掉转义这一半。关联:#5333(同文件、回显侧的同类算子表漂移)、#4128(
convertFilter的default:从「当成等值」改成抛错、三个同族算子改成规范算子的那次)、#4706($regex语义待裁决,needs-user-decision)、#5374(driver-memoryanalytics 面改成整条谓词构建的那次,contains在那边是转义过的)。