fix(service-analytics): contains 以规范算子 $contains 送进引擎,比较值不再落进正则位置 (#5557) - #5566
Merged
Merged
Conversation
… not an unescaped `$regex` (#5557) `ObjectQLStrategy.convertFilter` handles the four LIKE-family operators in one `switch`. Three of them — `notContains` / `startsWith` / `endsWith` — have passed through as canonical spec operators since #4128; `contains` alone was `{ $regex: values[0] }`, dropping the comparand VERBATIM into a regex position. Measured, engine filter per `where`: {stage: {$contains: 'a.b'}} => {stage: {$regex: 'a.b'}} {stage: {$notContains: 'a.b'}} => {stage: {$notContains: 'a.b'}} {stage: {$startsWith: 'a.b'}} => {stage: {$startsWith: 'a.b'}} {stage: {$endsWith: 'a.b'}} => {stage: {$endsWith: 'a.b'}} Three consequences, none of which waits on #4706's ruling about `$regex`: 1. `$regex` is not in `filter.zod.ts`'s `FILTER_OPERATORS` (fifteen operators, none of them `$regex`), so this was a PRODUCER emitting an operator the contract does not declare — Prime Directive #12 fixes the producer. 2. `compileScopedFilterToSql` in this same package is a `FilterCondition` consumer and fails closed on `$regex`, so one filter tree no longer travelled between two consumers of the same contract in the same directory. 3. A backend that reads `$regex` as a real regex — driver-memory's `memory-matcher.ts`, deliberately, for plugin-auth's adapter — read `a.b` as "a, any character, b" and could not compile `50% (+)` at all, so its `catch { return false }` answered zero rows for a filter that has a match. `driver-sql` compiles the same `$regex` to a substring LIKE: one widget, two row sets, depending on the driver. `MONGO_TO_CUBE_OP` maps `$contains` -> `contains` and nothing else does, so returning `$contains` is the round trip of the author's own key. `objectql-contains-canonical-operator.test.ts` pins both halves, because either alone misses the defect: the emitted operator keys are checked against the spec's own `ALL_OPERATORS` rather than a hand-written copy, and the ROW IDS are taken through an evaluator that mirrors `memory-matcher.ts`'s arms — the only face on which "the comparand is not escaped" is visible at all. Also drops the `$regex` -> `$contains` translation PR #5558 left in `objectql-echo-operator-coverage.test.ts`'s stand-in engine: its reason to exist was this issue. That file is now the most direct reverse evidence — reverting `case 'contains'` turns its `$contains` row red with the fail-closed refusal from consequence 2 (measured: 7 failures across 2 files reverted, 0 restored). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BWS4heBoAitLmzCLhcYdbK
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…ately does not pin The comment said "filed separately" with no number, which is the kind of dangling reference the next reader cannot follow. #5567 is the analytics SQL compilers interpolating a LIKE comparand without escaping `%` / `_`; the decoy fixture row also now says why it earns its place on the regex face (the exact row set) rather than through a wildcard assertion that face cannot make. Comments only — 50 files / 825 tests still pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BWS4heBoAitLmzCLhcYdbK
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5557
前提核对(对
origin/main@ 9ecdca9,含刚合并的 PR #5558)issue 的前提成立,行号已漂移到
convertFilter的第 1018 行附近。三条论证逐条实测:case 'contains': return { $regex: values[0] };objectql-strategy.ts:1018,三个同族算子确为规范算子$regex不在filter.zod.ts的FILTER_OPERATORSfilter.zod.ts:957),无$regexread-scope-sql.ts的compileOperator对$regexfail-closedread-scope-sql.ts:244的default直接抛$regex当真正则求值memory-matcher.ts:209,`new RegExp(target, condition.$options$regex编译成子串 LIKEsql-driver.ts:6495,与$contains共用applyContainsLike改动
一行(
objectql-strategy.ts):filter-normalizer.ts的MONGO_TO_CUBE_OP只把$contains映到contains,别无来源,所以这里回送$contains就是作者自己那个 key 的往返 —— 与三个同族算子完全一致。⛔ 按分诊要求未碰
filter-normalizer.ts(#5526-B 排下一位)。测试
新增
packages/services/service-analytics/src/__tests__/objectql-contains-canonical-operator.test.ts。issue 明确指出「只断言 SQL/filter 字符串会漏掉转义这一半」,所以两半都断言:filter.zod.ts的ALL_OPERATORS校验 —— 取自 spec 而非手抄一份(filter-refusal.ts对它自己那份词表说过同样的话:手抄的副本只在打字那天与契约一致)。memory-matcher.ts各 arm 的求值面上(镜像而非 import:service-analytics不依赖任何驱动)。元字符用例:$contains: 'a.b'只命中字面行,不命中axb;$contains: '50% (+)'命中它该命中的那一行,而且用toEqual(['3'])断言恰好只有那一行 —— 修前的答案是空集,一个只检查「行 3 在不在」的断言会被「什么都不产出」满足(这正是 PR fix(lint): 收敛 validate-expressions / validate-security-posture 的 spec 不声明键??别名读法 (#5017) #5046 记下的那个陷阱);exact set 同时排掉诱饵行 4(行 3 去掉那个%),这就是「比较值逐字符是字面量」的含义。FilterCondition消费方:四个 LIKE 算子的产出全部送进compileScopedFilterToSql确认编译得过(修前$contains那条抛 fail-closed)。50% (+)刻意不用在第 3 项上:那个编译器的 LIKE 元字符转义是它自己的另一个缺陷(已另行归档,见下),从本文件断言它等于替别人的 bug 钉桩。反向验证(方向修前预判,结果与预判一致:修前红 / 修后绿)
修前(新测试文件,
case 'contains'仍为$regex):6 个断言红修后:
Test Files 50 passed (50) / Tests 825 passed (825)。#5558 留下的翻译已删除 —— 本修复最直接的反向证据
PR #5558 在
objectql-echo-operator-coverage.test.ts的替身引擎里留了一个asCondition(),把$regex翻回$contains(注释指向本单)。它存在的理由现在没有了,已删除,options.filter原样送进compileScopedFilterToSql。于是把
case 'contains'退回$regex之后,该文件也红 —— 7 个断言、2 个文件:恢复修复后回到 50/50 全绿。
消费半径清扫
service-analytics的下游是cli/rest/verify/qa-dogfood。逐个 grep 过它们的 fixture:packages/rest/src/analytics-filter-refusal-envelope.test.ts:292的$regex是字面错误消息字符串(mockRejectedValue),不经convertFilter,且read-scope-sql.ts的 fail-closed 行为本次未改 —— 不受影响;packages/rest/src/export-integration.test.ts的替身驱动已经理解$contains($search折叠出的形状),本改动只让它更对;pnpm --filter @objectstack/rest test:Test Files 48 passed (48) / Tests 719 passed (719)。typecheck
service-analytics在scripts/check-type-check-coverage.mjs里是 measured DEBT 条目(无typecheckscript),所以直接跑了它自己的tsc --noEmit -p tsconfig.json:7 条报错,全部落在analytics-service.test.ts/measure-source-field-gate.test.ts/objectql-timedimension-projection.test.ts—— 本 PR 三个文件一条都没有,均为先存在的债(与 ledger 记的 3 条有漂移,那是 main 上的既有情况,不是本改动引入)。node scripts/check-nul-bytes.mjs:OK (scanned 5509 tracked text file(s));本 PR 四个文件另做了grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'自扫,干净。范围外发现
$contains/$startsWith/$endsWith的比较值在 analytics 侧的三个 SQL 编译器里都没有做 LIKE 元字符转义(read-scope-sql.ts的compileOperator、native-sql-strategy.ts的likePattern、objectql-strategy.ts的LIKE_SQL_OPS),而driver-sql的applyLike是显式ESCAPE '\'的、并在注释里把%旁路称作 P0。已按 Prime Directive #10 另行归档(unassigned),不在本 PR 修。关联:#5333 / PR #5558(同文件回显侧)、#4128(同函数上次同类修复)、#4706(
$regex语义待裁 —— 本改动让$regex在 service-analytics 失去生产方,反而缩小该裁决的影响面)。🤖 Generated with Claude Code
https://claude.ai/code/session_01BWS4heBoAitLmzCLhcYdbK
Generated by Claude Code