Repository navigation
JS 求值面全体拒收 $icontains(driver-memory 两面 / driver-mongodb / objectql having / formula)—— SQL 族已实现,同一 filter 在内存 double 上抛错 #6520
Description
Activity
Triage:
needs-user-decision+domain:spec. Two independent grounds for the escalation, both verified onorigin/main@3510e4a:- The gating move is an acceptance-surface change.
packages/spec/src/data/filter.zod.ts:1085FILTER_OPERATORSdoes not include$icontains, andfilter-operator-vocabulary.test.ts:55pins the declared-minus-enforced diff set as exactly['$icontains']. Admitting it flips previously-rejected filters to accepted on every allowlist consumer —domain:specby the standing red line (any accept/reject behavior change, however small). The body's own constraint (vocabulary admission must ride in the SAME PR as the driver-memory + service-analytics implementations, else fail-closed flips to silent-permissive = A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948-class RLS bypass) also rules out a mechanical contract-first split, so this cannot be pre-split spec-first at triage. - The implementation halves sit inside the [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 investment freeze (driver-memory / driver-mongodb family) — drivers:
$regex响亮拒收 +$icontains各后端实现(#4706 裁决 B 案 · 驱动半边) #5702 explicitly deferred exactly this work per that freeze. Queueing it now would contradict a standing maintainer decision; the appetite question (lift the freeze for$icontainssemantic completion, and when) is the maintainer's to answer.
Sequencing: the premise "SQL family implements it" lands with #5702's in-flight PR; the decision can be made now, implementation follows #5702's merge. Dedup: family checked — #5702 (SQL half, in flight), #5814 (consumer, blocked on #5702), #6337 (objectui authoring face), #6518 (case-folding facet, separate card), #4706 (parent ruling). No duplicate.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- The gating move is an acceptance-surface change.
- added a commit that references this issue
on Aug 8, 2026 Maintainer ruling (2026-08-08): Option A — approved. Add $icontains to the spec FILTER_OPERATORS word list and bring every JS evaluation face up to parity, as a sanctioned one-off exception to the #5499 investment freeze, strictly for semantic parity — not new capability.
Hard constraints for the dispatch (from the card, now binding):
- Single PR, whole batch: spec word list + driver-memory (both faces) + driver-mongodb + service-analytics + objectql having + formula, plus conformance coverage. The word list must NOT land ahead of the evaluators — that would flip the JS faces' fail-closed refusal into silent acceptance, the A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948 RLS-bypass shape.
- The PR body must declare a conclusion for every compile face per the face-inventory clause in the dispatch playbook (changed / already compliant with evidence / explicitly out of scope with reason). The frozen driver-memory / driver-mongodb faces are being changed here under the sanctioned exception, so they report as changed.
Rationale (three-axis review): the SQL family already shipped the operator, so the same filter behaves differently between test (memory) and production (SQL) — a measured divergence, and downstream #5814 (SCIM case-insensitive matching) has its blocker cleared and would hit 400 on landing (business). One contract, five faces, one answer (long-term). An operator the contract declares and one face rejects is exactly the declared-vs-enforced trap to close for AI authors (containment).
Timing: the decision gate (#5702) closed 2026-08-08 06:43Z — implementation can start immediately.
State: needs-user-decision removed; pm:queue restored — spec lane coordinates the single-PR batch.
Maintainer directive (verbatim, covering all 14 inbox cards): 「你的建议全部接受」. Recorded by PM session session_01JaVVMrSxt7Tgi1uwEuDtH7.
Generated by Claude Code
os-project-manager commented
on Aug 9, 2026 CollaboratorMore actions#6993 普查实测补一格(2026-08-09,
origin/main@08863dd18,执行测量):本卡现状表中@objectstack/formulamatchesFilter一行标 ❌ 与其余拒收面并列,但它的「❌」与其他面不同类:matchesFilterCondition({ name: 'ACME CORP' }, { name: { $icontains: 'acme' } })→ 静默返回false(evalOp的default: return false; // unknown operator → fail closed),不抛错、无 code、无 message;$regex同样静默false。- 其余 JS 面(driver-memory 两面、driver-mongodb、objectql
having)均响亮拒收(throw;信封状态见 objectqlhaving拒收 retired/unknown 算子时缺 ADR-0112 信封(裸Error,无code/status)—— 五个拒收面中唯一一个 #7047)。
即 formula 面对一个 spec 已声明的算子给出的是「无声的否答」,不是「fail-closed 拒收」—— 恰是 #4706 对旧
$regex的第二项指控(illegal pattern →return false)在这一面的残留形态。给 formula 加$icontains臂时(本卡范围第 4 条),建议同 PR 决定 unknown-$算子在该面是维持静默false还是升级为 throw,并把决定写进matches-filter.ts的 docblock;filter.zod.ts的 "The other JS evaluators sit on the refusing side too" 一句对 formula 的措辞也应随之校正。Generated by Claude Code — session_018ffcE95NaMJcL9XJ9VDYgk
Generated by Claude Code
CLAIM — PM seat
domain:spec(sessionsession_01PiRUoQkTSBBmpyXBY3cVn2), dispatching to an os-dev subagent.- Branch:
claude/issue-6520-icontains-js-faces - Worktree:
../objectstack-issue-6520(per-task, PD Migrate documentation site to Fumadocs with monorepo structure and shared content #11) - Dispatch basis: the 2026-08-08 maintainer ruling above (Option A, sanctioned one-off [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 exception, semantic parity only) with its two hard constraints binding: single PR whole batch, and the word list must NOT land ahead of the evaluators. The 2026-08-09 census note is folded in: the formula face's unknown-
$posture (silentfalsevs throw) is decided IN the same PR and documented;filter.zod.ts's "refusing side" wording corrected for formula. ⚠️ Serialization: in-flight PR fix(spec):$betweenendpoints accept the ISO/clock strings the platform produces (#6571) #7058 (merge queue) editsfilter.zod.ts($between unions). First action: verify fix(spec):$betweenendpoints accept the ISO/clock strings the platform produces (#6571) #7058 has merged intoorigin/mainbefore finalizingfilter.zod.tsedits; lap if needed.- Face inventory clause: the PR body declares a conclusion for every compile face (changed / already-compliant with evidence / out-of-scope with reason), per the ruling.
Generated by Claude Code
- Branch:
PM bookkeeping:
pm:dispatched→pm:queue. The dispatched os-dev was stopped externally (mass session-level cancellation, 15:21Z patrol census) before pushing any code — no branch, zero work product. Claim released; card returns to the queue intact. The Option A ruling and its hard constraints (single PR whole batch; word list never ahead of the evaluators; #3948 probe) remain binding on the next dispatch; the #7058 serialization constraint is now moot (merged 14:03Z). Re-dispatch awaits the maintainer's word.
Generated by Claude Code
2 remaining items
RE-CLAIM — PM seat (session
session_01PiRUoQkTSBBmpyXBY3cVn2), fresh os-dev dispatched under the maintainer's re-acceleration (2026-08-09 chat). Branchclaude/issue-6520-icontains-js-faces, worktree../objectstack-issue-6520. The Option A ruling's hard constraints (single PR whole batch; word list never ahead of the evaluators; #3948 probe mandatory; per-face inventory) remain binding; the #7058 serialization is moot (merged).
Generated by Claude Code
- added a commit that references this issue
on Aug 16, 2026 - added 5 commits that reference this issue
on Aug 17, 2026 - added a commit that references this issue
on Sep 5, 2026 - added a commit that references this issue
on Sep 9, 2026 - added a commit that references this issue
on Sep 28, 2026 - added a commit that references this issue
on Sep 29, 2026 - added a commit that references this issue
on Oct 7, 2026
Part of #4706。从 #5702 实施期实测拆出:#5702 在 SQL 族(driver-sql / driver-sqlite-wasm / driver-turso 的 local + remote 两面)实现了
$icontains,JS 求值面一个都没有。现状(实测,#5702 落地后)
{ name: { $icontains: 'acme' } }INVALID_FILTER/ 400(SUPPORTED_FIELD_OPERATORS由 specFILTER_OPERATORS派生,该表刻意未收$icontains)default:拒收havingCONDITION_OPERATORS不含它@objectstack/formulamatchesFilter$icontains分支里)拒收是 fail-closed,不是静默放宽 —— 这是刻意的方向,不是缺陷本身。缺陷是同一条 filter 在两类后端上给两个答案:一个返回行,一个抛 400。
用户可见后果
应用测试跑内存 double、生产跑 SQL 是本仓的常见形态(
plugin-auth的auth-contains-filter.test.ts就是这个形状)。一条用$icontains的 filter 会在测试里抛错、在生产里正常,或者反过来 —— 正是 #4706 对$regex的原始指控(「the divergence only shows up when the app's tests run on the memory double and production runs SQL」),换了个算子重演。下游 #5814(better-auth
Where.mode: 'insensitive')一旦落地就会踩到:认证查询在内存 double 上会 400。为什么 #5702 没做
$regex响亮拒收 +$icontains各后端实现(#4706 裁决 B 案 · 驱动半边) #5702 正文明确「$icontains实现半边属语义补齐投入,默认挂起」;having/ formula 不在domain:drivers车道;$icontains加进 spec 的FILTER_OPERATORS是这些实现的前提,也是它们的风险。该数组是运行时 allowlist(driver-memory的 shape gate 与service-analytics的objectql-echo-operator-coverage.test.ts都从它派生),spec:$regex按 ADR-0049 退役 +$icontains入算子词表与 FILTER_LOGIC_CASES(#4706 裁决 B 案 · 契约半边,先行) #5701 实测过:提前加入会让 driver-memory 从「响亮拒收」翻成「静默放宽」(谓词被丢弃 → 匹配每一行 → RLS 读作用域上是权限绕过,A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948)。所以词表纳入必须与 driver-memory + service-analytics 的实现同 PR,不能先加词表。建议范围
packages/spec的FILTER_OPERATORS收入$icontains,同 PR 更新filter-operator-vocabulary.test.ts的差集 pin(该 pin 现在钉死差集恰为{ $icontains });toLowerCase()—— 契约以FILTER_TEXT_CASES的非 ASCII 不折 pin 为准);service-analytics的compileScopedFilterToSql补一条臂(否则 echo 覆盖测试判红);having与 formula 同批;scripts/check-driver-conformance.mjs里 driver-memory / driver-mongodb 两行FILTER_TEXT_CASESDEBT 的 requirement-1 半边。memory 侧实现本身是平凡的(真正则引擎已在,ASCII 折叠即可);贵的是词表纳入牵动的 allowlist 消费者。