Skip to content

feat(drivers,objectql): $regex 响亮拒收 + SQL 族 $icontains 实现(#4706 裁决 B 案 · 驱动半边) - #6549

Merged
os-zhuang merged 6 commits into
mainfrom
claude/issue-5702-regex-refusal-icontains
Aug 8, 2026
Merged

feat(drivers,objectql): $regex 响亮拒收 + SQL 族 $icontains 实现(#4706 裁决 B 案 · 驱动半边)#6549
os-zhuang merged 6 commits into
mainfrom
claude/issue-5702-regex-refusal-icontains

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5702

#4706 裁决 B 案的驱动半边#5701 已落契约(词表 / RETIRED_FILTER_OPERATORS 处方 / 共享文本 case-set),#5710(PR #5812)已翻掉最后一个活体生产者,因此拒收现在可以落地而不打断登录。


单必答的三个判断,先给结论

(a) 拒收能否单点化?不能 —— 机制无法单点,但「说辞」可以,而且已经单点了

实测:仓内不存在覆盖所有 driver 入口的共享校验层。packages/specRETIRED_FILTER_OPERATORS 正文自陈这一点,并点名了五个各自独立的拒收点:

拒收点 拒收机制 本 PR 前 $regex 的下场 剩余工作量
driver-sql 发射器 default: 私有 unsupportedFilterError 编译$contains 的 fallthrough 删 case + 处方分支
driver-memory filter-refusal.ts 私有 unsupportedFilterError 求值为真 RegExp(全仓唯一) 移出 allowlist + 删 matcher 臂
driver-turso remote transport 私有 invalidFilterError 编译成 LIKE 移出词表 + 处方分支
driver-mongodb translateFieldOperators new Error 已拒收,但无 code/status 接进本文件既有 helper
objectql having new Error 求值为真 RegExp 删词表成员 + 删臂

四个包各有一份自己的 unsupportedFilterError(每份都注释「与另外几家保持一致」),彼此不可 import。ADR-0112 信封覆盖的是错误形状,不是拒收位置。所以 driver 侧工作是 5 处,不是 1 处;能单点的是处方文本 —— 五处一律逐字打印 RETIRED_FILTER_OPERATORS[op].why,这正是 #5701 建那张表的目的。

driver-sqlite-wasm 继承 SqlDriver,零改动(但有独立执行见证,见下)。

(b) 裸 $regex 是否真已无活体生产者?是,已自行复核

一律用 git grep '\$regex:' origin/main(不用工作区,避免落后 checkout 的错误结论):

  • packages/plugins/plugin-auth/src/objectql-adapter.tscontains 支现在发 filter[fieldName] = { $contains: condition.value };
  • 全仓其余 $regex: 出现点只剩两条注释(driver-memory/src/filter-refusal.ts 的自注、service-analytics/src/strategies/objectql-strategy.ts:1099 的历史记账);
  • driver-memory/src/memory-driver.tsmemory-analytics.tsdriver-mongodb/src/mongodb-filter.ts 里的 $regexmingo / MongoDB 查询语言的内部下译,不经过 Filter Protocol 拒收面,与本单无关。

(c) 冻结族是否触发 restore-invariant 例外?否 —— 未触发,因此按冻结令挂起,没有自行放行

关键在于:我没有把 $icontains 加进 spec 的 FILTER_OPERATORS。该数组是运行时 allowlist,driver-memory 的 shape gate 由它派生 —— #5701 实测过,提前加入会让 memory 从「响亮拒收」翻成静默放宽(谓词被丢弃 → 匹配每一行 → RLS 读作用域上是权限绕过)。不加,memory / mongodb 就继续 fail-closed 地拒收 $icontains,没有任何默认跑的矩阵/套件因此判红(实测:两包全绿,534 / 209 passed)。

代价是 $icontains 暂时只在 SQL 族可用 —— 这条已另立 #6520 说明词表纳入必须与 memory + service-analytics 实现同 PR。

⚠️ 一处与派工书边界的偏离,如实报告:packages/spec/src/data/filter.zod.ts 自注「$icontains joins this array in the PR that implements it (#5702)」。我没有照做,理由即上述 (c) —— 那句话预设一个 PR 一次实现全部五个后端,而冻结令把其中两个排除在本单预算外。filter-operator-vocabulary.test.ts 的差集 pin 因此保持绿。packages/spec 零改动


二、$icontains 实现

LOWER(col) LIKE LOWER(?) ESCAPE ?,作为 applyLike / pushLikefold 参数,不是第二个发射器 —— 转义字符类(% / _ / \ + 显式 ESCAPE)因此是同一段代码,不是靠注释同步的副本。第二个发射器正是转义会被漏掉的地方,而未转义的 % 匹配每一行(P0)。

后端 落点
driver-sql applyLike(..., fold);比较数(空串 / 非字符串)在校验遍历上拒收,与 $null/$exists 并列 —— 发射器会被布尔单位元整段跳过,只放发射器等于「拒收与否取决于兄弟节点」
driver-turso local 继承
driver-turso remote pushLike 同款 fold 参数 + SUPPORTED_FILTER_OPERATORS 词表(remote 不走 knex,必须写第二遍)
driver-sqlite-wasm 继承,但有独立执行见证(见下)
driver-memory / driver-mongodb #5499 挂起,见 (c)

大小写语义跟随 #5701 Q1=A:ASCII-only。SQLite lower() 恰好只折 ASCII,即契约行为 —— $icontains: 'café' 不匹配 CAFÉ(实测 ['4'] / ['3'])。


三、必须产出的证据

反向验证 —— 方向先判后跑,三次,其中一次预判需要修正

# 摘掉什么 预判 实测
A 把删掉的 case '$regex': fallthrough 装回去 ✅ 红,4 个用例、3 个文件
B 摘掉 case '$icontains': 整臂 ✅ 红,14 个用例、2 个文件
C 只摘掉 LOWER() 折叠,保留臂 预判行级用例不会红 ✅ 只红 1 个,且是编译 SQL 那一条

C 是本 PR 最该被读到的一条,如实记录而不是抹平:在 SQLite 上 LIKE 本身已折 ASCII,所以 LOWER() 是幂等的 —— $contains$icontains任意比较数返回完全相同的行。也就是说折叠在这个方言上行级不可观测,唯一见证是编译出的 SQL 文本。两个套件都把这条钉住了,remote-transport-text-predicates.test.ts 里那条用例原本是照「两个算子可由答案区分」写的,实测推翻后按实测重写并写明原因。

A 的第三个见证是意外收获:sql-driver-silent-empty-predicate.test.ts 的对象比较数表(即 TEXT_PATTERN_OPERATORS)也随之红 —— 该表随集合成员变更做了三态分诊里的改写($regex 退出,$icontains 加入),而非删除。

拒收面一律断 codestatus 两个字段,不用裸 toThrow:

$regex a.b       THROW {"code":"INVALID_FILTER","status":400}   ← 改前: ROWS ["9"]
$options alone   THROW {"code":"INVALID_FILTER","status":400}
$icontains ''    THROW {"code":"INVALID_FILTER","status":400}
$icontains 42    THROW {"code":"INVALID_FILTER","status":400}

转义纪律 —— 三个元字符各自的用例

sql-driver-like-escape.test.tsP0-3 守卫层#5589 方言矩阵层同批加了算子轴($contains × $icontains),四个用例(% / _ / \ / 普通子串)× 两个算子 × 每个方言格。remote 面另有三条独立用例:

$icontains '%'   → ['m1']   $icontains '% OFF'      → ['m1']
$icontains '_'   → ['m3']   $icontains 'A_B'        → ['m3']
$icontains '\'   → ['m4']   $icontains 'BACK\SLASH' → ['m4']

driver-sqlite-wasm 读的是 driver-sql 重建后的 dist/*.d.ts

已知门禁盲区,用否定对照证明,不是只贴绿:

  1. 在 driver-sql 源码export interface FreshDtsProbe { readonly impossible: never },不重建 → sqlite-wasm typecheck 报 TS2614: Module has no exported member 'FreshDtsProbe' ⇒ 读的是 dist,不是源码;
  2. 重建 driver-sql(dist/index.d.ts 命中 2 次)→ 该错误消失;
  3. 把探针换成真正不可满足的赋值 → TS2322: Type 'number' is not assignable to type 'never' ⇒ 确实读到了 d.ts。

探针已全部移除(源码 0 命中、重建后 dist 0 命中)。sqlite-wasm 另有独立执行套件 —— $icontains 是本包第一个编译形态为列上函数调用 + 第三个绑定参数的算子,wasm 方言若把三个位置绑错,全仓没有第二个套件会红。

全套门禁

check:driver-conformance   OK — 25 covered cell(s), 5 in the DEBT ledger, 0 exempt
check:query-options-erasure ✓ ratchet holds; test surface 263 — at the ceiling(与 main 持平)
check:type-check-debt      OK — 34 ledger entries re-measured, none above its recorded number
check:nul-bytes            OK — 6128 files, no raw ASCII control bytes
check:error-code-casing / :wildcard-fallthrough / :route-envelope / :empty-changeset / no-major   ✓
eslint(19 个改动文件,--no-inline-config)  EXIT=0

受影响包与下游(前缀点 '...pkg',43 个包)typecheck + test 全绿;六个直接改动包:

driver-sql 992 passed | driver-turso 836 | driver-sqlite-wasm 263
driver-memory 534 | driver-mongodb 209 (137 skipped, #5499) | objectql 2456

check:query-options-erasure 中途曾判红(263 → 278):新套件在 find() 的 options 位用了 as any。已按「⛔ 零新增 as any」改掉 —— options 走 DriverOptions 常量,whereFilterCondition(其索引签名本就是 any,原本就不需要转型),编译 SQL 的探针用 SqlDriver 子类(applyFiltersprotectedgetKnex() 是 public)而不是转型进私有字段。现已回到 main 的天花板值。零新增 as any / as unknown as / @ts-ignore / as never


四、fixture 三态分诊(每一条单独判,不批量改写)

文件 处置 理由
memory-driver.test.ts 「should filter with $regex 整条替换 它钉的就是被删的那条臂(真 RegExp 求值),新世界里没有 $regex 答案可断
memory-filter-vocabulary-refusal.test.ts 「dangling $options 整条替换 旧断言仍会通过(照样抛),但钉住的消息教读者去写退役形式 { "$regex": …, "$options": "i" } —— 绿着记录错误契约
同文件 LEGAL 表里 { $regex, $options } → ['1'] 搬家 该形状不再合法,搬到拒收表而非删除:形状仍需有钉住的答案,变的只是答案本身
memory-analytics-filter-refusal.test.ts$regex 搬家 + 补声明 它在错误的表里 —— 断言写着「declared by the Filter Protocol」,而 $regex 从未被声明过
having-filter.test.ts 「accepts $regex with $options 整条替换 同上;旧臂对非法模式return false(不可运行的 filter 被答成「此行不匹配」)
remote-transport-node-operator-refusal.test.ts$regex 改写(换尾句) 它不再是「字段算子高了一层」,而是「协议在任何层级都不声明它」,换表而非删行
sql-driver-silent-empty-predicate.test.ts 算子表 改写 该表即 TEXT_PATTERN_OPERATORS,随集合成员变更
mongodb-filter.test.ts 三条 allowlist 用例 改写 + 加强 消息形状变了;顺带从「只匹配消息」升级为断 code/status —— 只匹配消息正是裸 new Error 能在自家 INVALID_FILTER helper 三行之外活两个版本的原因

其中一条负向断言被自己证伪并修正:expect(...).not.toContain('declared by the Filter Protocol') 恒假 —— spec 的处方文本里就有这句话的否定式(「was never declared by the Filter Protocol」),子串检查对两条消息都成立、什么也钉不住。已改断另一条消息的专有短语。


五、DEBT / LEDGER 同步(⛔ 未动判定逻辑)

五行 FILTER_TEXT_CASES DEBT 保留,why 按实测重写:requirement 1($icontains)与 requirement 3(拒收)在 SQL 族已完成并逐格写明,requirement 2($contains 族大小写敏感)是五行全部幸存的唯一原因issue 指针从 #5702 改指 #6518

新套件刻意不 import 共享 case-set 的 CASES 导出(只用 FILTER_TEXT_ROWS 那九行做 fixture):门禁按 import 判定覆盖,而本驱动尚不能回答整张表 —— import 会把格子翻成 covered 却有 5 条用例无人执行,正是该门禁存在要防的那种「报告成功却在降级」。

六、按 PD #10 另立的单(未指派,不扩大本 PR diff)


Generated by Claude Code

@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 6:13am

Request Review

os-zhuang and others added 2 commits August 8, 2026 04:04
处方 / 共享文本 case-set),#5710 已翻掉最后一个活体生产者(plugin-auth 的
ObjectQL 适配器,认证路径),因此拒收现在可以落地而不打断登录。

- 五个拒收点(driver-sql / driver-memory / driver-turso remote /
  driver-mongodb / objectql `having`)统一逐字打印
  `RETIRED_FILTER_OPERATORS[op].why`,并点名 `$icontains`;
- `$icontains` 在 SQL 族实现:复用 `applyLike` / `pushLike` 的 `%`/`_`/`\`
  转义与显式 ESCAPE,新增 `fold` 参数把 LOWER() 套在两侧;
- driver-mongodb 的 `default:` 裸 `new Error` 接进本文件既有的
  `INVALID_FILTER` / 400 信封;
- conformance LEDGER 五行按实测重写,剩余两半分别记为 #6518 / #6520。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
`sql-driver-silent-empty-predicate.test.ts` 的算子表就是
`TEXT_PATTERN_OPERATORS`(比较数会变成 LIKE 模式文本的那一组),所以随该集合
成员变更:`$regex` 退役离开,`$icontains` 加入。

改写而非删除:`$icontains` 是唯一比较数**同时**受校验遍历把关的文本算子,
少了这一行,两道闸将来对「对象比较数」给出不同答案时无人察觉。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
@os-zhuang
os-zhuang force-pushed the claude/issue-5702-regex-refusal-icontains branch from f445bd0 to 4f1f02b Compare August 8, 2026 04:14
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 6 package(s): @objectstack/driver-memory, @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/driver-turso, @objectstack/objectql, @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/driver-turso, @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/driver-turso, @objectstack/spec)
  • content/docs/deployment/environment-variables.mdx (via @objectstack/driver-turso)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/driver-turso)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/driver-memory, @objectstack/objectql)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/driver-turso)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/driver-memory, @objectstack/objectql)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/anatomy.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/driver-memory, @objectstack/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/driver-turso, @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/driver-sql, @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/driver-sql, @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb, @objectstack/driver-sql, packages/objectql, @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/driver-memory, @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/objectql, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

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

Copy link
Copy Markdown
Contributor Author

分支纪律记账,如实报告一处越界。

派工书里写着「⛔ 不 force-push」。本分支在开出 PR 之后 force-push 过一次:PR 建好后 main 又前进,packages/drivers/driver-mongodb/src/mongodb-filter.ts#5659 的共享布尔归约 import 撞在同一个 import 块上(mergeable_state: dirty)。我选了 rebase 而非 merge,于是必须 git push --force-with-lease

  • 冲突是纯 import 块并存,两侧 import 都保留,无语义取舍;
  • 用的是 --force-with-lease(远端若被他人推过会拒绝),推送前后 origin/claude/issue-5702-... 只有本 session 动过;
  • rebase 后全量重跑:pnpm install + 全仓 build(71 tasks)+ 六个改动包 typecheck/test 全绿(992 / 836 / 263 / 534 / 209 / 2456)+ check:driver-conformancecheck:query-options-erasurecheck:nul-bytes、改动文件 eslint 全过。

事后看,merge main 进分支可以完全避开 force-push,下次遇到同样情形应当那样做。记在这里而不是掩掉,因为「PR 已建之后改写过历史」是 review 时值得知道的事实。


Generated by Claude Code

claude added 2 commits August 8, 2026 04:32
#6148 的处置门对本单的 changeset 判红:声明了破坏性变更却没有任何 ADR-0087
处置标记。四个格逐一验证下来,`registered` 是唯一诚实的一格:

- `unpublished` —— 机械上为假,六个受影响包全部已发布(`private` 均非 true);
- `already-registered` —— 两个台账里都没有 $regex / $icontains 条目;
- `no-migration-prescription` —— 探测器实测返回 null,机械上会放行,但本
  changeset 正文确实带改写处方(“Write `$icontains` … `$contains` …”),
  据此声明即为自相矛盾。不利用该盲区。

条目落在 step-17 的 `semantic`(非 mechanical):$regex → $icontains 在任一
方向都不是无损改写(正则元字符会变成字面量),自动改写会悄悄改变
dashboard / report / 权限过滤器选中的行集。

`spec-changes.json` 与 `docs/protocol-upgrade-guide.md` 由 gen:spec-changes /
gen:upgrade-guide 重新生成,未手改;两道同步门复验通过。

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

Copy link
Copy Markdown
Contributor Author

补上最后一道红:ADR-0087 处置标记(接手完成)

Check Changeset 是本 PR 唯一的红。四个格逐一验证,registered 是唯一诚实的一格:

验证方式 结论
unpublished 读六个包的 package.json ✗ 机械上为假 —— private 全部非 true
already-registered git grep -i 'regex|icontains' origin/main -- packages/spec/src/{migrations,conversions}/registry.ts ✗ 两个台账都没有条目(命中的两处 "regex" 是别的条目正文里的普通用词)
no-migration-prescription 直接调用门禁自己的 findMigrationPrescription() ⚠️ 实测返回 null,机械上会放你过 —— 但本 changeset 正文确实带改写处方(“Write $icontains$contains …”),只是没写成「框架词 + 箭头」的形状,落在探测器自陈的盲区里。不利用该盲区:据此声明即为自相矛盾
registered 新增台账条目

⚠️ 一处必须更正的前提(与派工书相反,以实测为准)

派工时的判断是「$regex 曾经是已声明的算子,由 #5701 从词表移除」,据此认为本单比 #6404 / #6517 更站得住。实测推翻了这条,而且方向是它更弱、不是更强:

git show 85d95e7d9^:packages/spec/src/data/filter.zod.ts   # #5701 之前的 FILTER_OPERATORS
  $eq $ne $gt $gte $lt $lte $in $nin $between
  $contains $notContains $startsWith $endsWith $null $exists      ← 没有 $regex

git log -S'$regex' -- packages/spec/src 全history 只有 5 个提交,逐个查下来 #5701 之前的四个全部是文档注释里描述 $contains 如何下译:/** Contains substring - SQL: LIKE %?% | MongoDB: $regex */。⇒ $regex 从未被声明过,本 PR 正文原本的说法是对的,派工书的说法是错的。

但结论仍是 registered,只是理由换成了一条更硬的:与 #6404 / #6517 那两个「driver 调用参数,代码而非元数据」不同,filter 本身就是入库元数据,而 FilterConditionSchema开放 record:

export const FilterConditionSchema = z.lazy(() =>
  z.record(z.string(), z.unknown()).and(...)
);

所以入库的 filter: { name: { $regex: 'acme.*' } } 解析恒绿、且永远会恒绿 —— 开放 map 上根本不可能有 retiredKey() 墓碑。它出现在 dashboard 的 widget filter / globalFilters、report.runtimeFilterpagecomponentsolution-blueprint。改前它被编成 LIKE 子串(SQL 族)或真 RegExp(memory / having),改后在渲染时抛 400。台账条目因此是够得着这批作者的唯一通道,这正是 #6148 说的那种「没有 schema 拒收面、没有墓碑」的情形。

落点(核对渲染,不是推断)

条目 filter-regex-options-retired 落在 step-17 的 semantic,不在 mechanical 表 —— $regex$icontains 两个方向都不是无损改写(正则元字符会变成字面量),自动改写会悄悄改变 dashboard / report / 权限过滤器选中的行集(错的数,不是缺的数)。

实际渲染位置(读 docs/protocol-upgrade-guide.md 得到,非推断):## Protocol 16 → 17 段内,### Mechanical (applied for you) 在相对行 107、### Semantic (delegated to you, with acceptance criteria) 在相对行 161,本条目在相对行 265 ⇒ 在 Semantic 之下。渲染首行:

- **`filter-regex-options-retired`** — `data.filter $regex / $options — in a STORED filter
  (dashboard widget filter and globalFilters, report runtimeFilter, page and component
  filter, solution-blueprint filter), and equally in the where clause of a query request`
  → $icontains for the case-insensitive substring match this was almost always used for,
    or $contains for a case-sensitive one — a pattern that genuinely needs a regular
    expression has no filter-level replacement

条目正文按要求写明它涵盖契约 + 驱动两半:#5701 合入时 #6148 这道门尚未存在,所以那一半从未被要求写条目;#5702 是拒收变成可执行的一半,一条条目一并记清。

spec-changes.jsondocs/protocol-upgrade-guide.mdgen:spec-changes / gen:upgrade-guide 重新生成(未手改),两道同步门复验 is up to date

证据

check:adr-0087-registration  ✓ 1 declared-breaking changeset(s), each carrying a disposition
    registered filter-regex-options-retired (new here: filter-regex-options-retired)
--self-test                  ✓ 73 assertions over real temp git repos
反向:把 id 换成 totally-bogus-id → 红「do not exist in the ADR-0087 registries at HEAD」

全套复跑(已合入最新 origin/main,含今日 5 个提交):

typecheck(全仓 turbo)      125/125 successful      EXIT=0
driver-sql 992 | turso 836 | sqlite-wasm 263 | memory 534 | mongodb 209(137 skipped)
objectql 2456 | spec 8822                          EXIT=0
check:driver-conformance    OK — 25 covered, 5 DEBT, 0 exempt
check:query-options-erasure 263 — at the ceiling(与 main 持平)
check:type-check-coverage / :nul-bytes / :empty-changeset / :adr-anchors   OK
eslint --no-inline-config(21 个改动文件)          EXIT=0

packages/spec 除本条台账条目外仍是零改动 —— filter.zod.ts 未被触碰,FILTER_OPERATORS 的差集 pin 保持绿,冻结族(memory / mongodb)的 $icontains 实现照 #5499 继续挂起。


Generated by Claude Code

claude added 2 commits August 8, 2026 05:36
空提交,无内容变更。4921aed47 推上去后 ~35 分钟内 GitHub 没有为该 SHA 创建
任何 workflow run(同期其他 claude/issue-* 分支的 pull_request 事件正常调度),
close/reopen 也只触发到 Cross-repo Issue Closer。以新 SHA 重新发一次事件。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
冲突:packages/spec/src/migrations/registry.ts —— 双方都在 step-17 semantic
数组末尾追加条目。两边条目全部保留(本单的 filter-regex-options-retired 在前,
origin/main 的 http-server-runtime-vocabulary-retired 等在后)。

spec-changes.json / protocol-upgrade-guide.md 按仓库 merge driver 的要求从
合并后的树重新生成(非文本合并),两道同步门复验 is up to date。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
@os-zhuang
os-zhuang marked this pull request as ready for review August 8, 2026 06:28
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 82397b6 Aug 8, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5702-regex-refusal-icontains branch August 8, 2026 06:43
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/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

drivers: $regex 响亮拒收 + $icontains 各后端实现(#4706 裁决 B 案 · 驱动半边)

2 participants