Repository navigation
plugin-auth: convertWhere() 对 not_in / starts_with / ends_with 没有分支,谓词被整条丢弃 —— 认证路径上的过滤放大 #5813
Description
Activity
分诊:
pm:queue+domain:identity。落点锚定:
packages/plugins/plugin-auth/src/objectql-adapter.ts的convertWhere()⇒ identity 行。⚠️ 与同族的 #5814 落在同一个函数,但两单不同类,分诊结论也不同(见下)。过时前提检查(
origin/main@846ed1f,实读git show origin/main:<path>):convertWhere的if / else if链命中eq/ne/in/gt/gte/lt/lte/contains八支,not_in/starts_with/ends_with零命中 ✅;零命中已用邻近词反查证伪「扫描器坏了」(同文件同 grep 打出了那八支)。链尾无else兜底 ✅。前提逐条成立。为什么判
pm:queue且值得优先:丢谓词是把结果变宽不是变窄,且发生在身份表上;update/delete/consumeOne的「先findOne(filter)再按 id 写」在{}下会写到任意一行。这是 restore-invariant 形状,与 driver-memory 匹配器default:臂、objectqlhaving改为拒收是同一条纪律(#3948 一族),不是「未实现的可选能力」。活体调用方GET /api/v1/auth/admin/list-users已挂载(auth-route-ledger.ts:161),searchOperator的枚举里starts_with/ends_with两个值直接命中。与 #5814 的分流(同函数、不同判级,派发前请读这一段):
缺什么 本轮判级 理由 #5813(本单) 三个算子没有分支 pm:queue词表里有现成对应( $nin/$startsWith/$endsWith),直译即可,无契约问题#5814 Where.mode: 'insensitive'从不读取needs-user-decision「不敏感的相等」这个算子在词表里不存在,需扩词表 / 降级 / 规范化三选一 ⛔ 同文件串行:两单落在同一个函数,不得同批。#5814 目前在决策箱,天然不会被选批,所以实际约束是:本单先行;若 #5814 日后裁决落地,其实施应在本单合入之后重新定价(本单会顺带把链尾改成响亮拒收,那正是 #5814 落地时
mode该走的接缝)。派发本单时建议带一条必答项:你的改动是否让 #5814 变简单 / 变难 / 变得不必要?查重:三仓 open issue 与 PR 各搜一遍(
convertWhere/not_in/starts_with/list-users)。#5710(同函数contains支译成裸$regex)已 closed(PR 已合),故不构成在飞同文件冲突,但其改动就在相邻几行 —— 实施前请对origin/main重读该函数,不要照着 issue 正文里的旧行号改。无影子单。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
认领:PM 循环第 3 轮(
domain:identity车道)
会话:session_01JwwiU9bjhwy2SWj13ho8uv
分支:claude/issue-5813-convertwhere-missing-ops
Worktree:objectstack-issue-5813
域:domain:identity
文件面:packages/plugins/plugin-auth/src/objectql-adapter.ts+ plugin-auth 内测试 +.changeset/*(越界即停,报告说明)前提已于 origin/main(846ed1f 后)复核:
convertWhere仍只有八支(eq/ne/in/gt/gte/lt/lte/contains),not_in/starts_with/ends_with零命中(邻近词反查通过),链尾无兜底。#5710 的 PR #5812 已合并 —— 本单基于合并后代码实施,同函数相邻行已变,不按 issue 旧行号。#5814 在决策箱天然串行,按分诊要求带必答项派发。
Generated by Claude Code
复核 ACCEPT(PM,
session_01JwwiU9bjhwy2SWj13ho8uv,第 3 轮):交付 PR #5844。- 范围核对:3 文件(实现 +152/−45、测试 +421、changeset),文件面守恒;fix(plugin-auth): better-auth 的
contains下译为$contains,比较值不再当正则求值 (#5710) #5812 的contains注释原样保留;无 releases 触碰。 - 实现核对:三算子直译(
$nin/$startsWith/$endsWith,均在FILTER_OPERATORS);链尾default响亮拒收且错误信息即操作指引;if/else if改switch拿到第二道锁 ——never收敛让上游第十二个算子先在 typecheck 红,词表面测试把SUPPORTED_WHERE_OPERATORS与 better-auth 运行时whereOperators钉成同一集合(这一面正是 plugin-auth:convertWhere()对not_in/starts_with/ends_with没有分支,谓词被整条丢弃 —— 认证路径上的过滤放大 #5813 得以发生的盲区)。operator ?? 'eq'是生产方自己声明的@default(factory 已物化,仅裸适配器路径生效),不是宽容别名 —— 论证成立。 - 测试质量:三面加一尾共 27 条,每个算子的行为面用例都有排除力(
x_abc是判别行);拒收 pin 断言「读根本没发生」;mode边界 pin 明确 plugin-auth:convertWhere()整体忽略 better-auth 的Where.mode: 'insensitive'(SCIM 会发它) #5814 缺陷原样保留。反向验证 17 条红,最锋利的实证:「删除 name 以 zed 开头的用户」修复前删掉的是表里第一行u_abc1—— 写路径选错行不再是推测。 - 必答项(plugin-auth:
convertWhere()整体忽略 better-auth 的Where.mode: 'insensitive'(SCIM 会发它) #5814 重定价,记入决策卡参考):变简单 ——mode落地时每个case臂是「该算子 insensitive 下译成什么」的落点、default臂是「无法表示」的拒收位,穷尽性检查会点名漏臂;SUPPORTED_WHERE_OPERATORS不需要动。 - 刻意不钉的 driver-memory
i标志分歧已核:确属 drivers:$regex响亮拒收 +$icontains各后端实现(#4706 裁决 B 案 · 驱动半边) #5702 既有预算(spec:$regex按 ADR-0049 退役 +$icontains入算子词表与 FILTER_LOGIC_CASES(#4706 裁决 B 案 · 契约半边,先行) #5701 changeset 具名记录),fixture 全小写规避正确。 - 下游最重消费者 runtime 1458 测试全绿;engine double 以
assertEngineDeleteDispatch开门并被门禁收录为 pinned。
CI 全绿(23 checks),转 ready 入合并队列。
Generated by Claude Code
- 范围核对:3 文件(实现 +152/−45、测试 +421、changeset),文件面守恒;fix(plugin-auth): better-auth 的
实测于
origin/main@1624f4a,在实施 #5710(同一个函数的contains支)时发现。与 #5710 不同类:#5710 是「译错了」($regex把字面值当模式),本单是「根本没译」—— 谓词消失,过滤放大。测到的东西
packages/plugins/plugin-auth/src/objectql-adapter.ts的convertWhere()是一串if / else if,只覆盖eq/ne/in/gt/gte/lt/lte/contains。better-auth 的算子词表(
@better-auth/core/src/db/adapter/index.ts:308-320)是:对不上的三个 ——
not_in、starts_with、ends_with—— 落在链尾之外,filter里不写任何键。没有else兜底,没有告警。一个只带这类条件的where因此编成{}:findMany/count变成全表(受limit截断);update/delete/consumeOne走的是「先findOne(filter)再按 id 写」,{}让findOne返回任意一行(通常是第一行),于是写到了错误的记录上。活体调用方
GET /api/v1/auth/admin/list-users(已挂载,auth-route-ledger.ts:161)直接把查询参数推进where(better-authdist/plugins/admin/routes.mjs:358-368):searchOperator的枚举是contains | starts_with | ends_with—— 后两个命中本缺陷;filterOperator的枚举就是整张whereOperators表 —— 含not_in。所以
?searchValue=abc&searchOperator=starts_with今天返回的是「全部用户」而不是「以 abc 开头的用户」,?filterField=email&filterOperator=not_in&filterValue=...同理不排除任何人。管理台的用户检索是它的主要消费者。为什么这是缺陷而不是「未实现的可选能力」
丢一个谓词不是把结果变窄,是变宽;在读路径上等于放大可见集合(#3948 反复论证过的形状,driver-memory 的匹配器
default:臂和 objectql 的having都为此改成了拒收)。这里的放大还发生在身份表上。修法(建议)
三个算子在
FILTER_OPERATORS里都有现成对应,一一直译即可:not_in$ninstarts_with$startsWithends_with$endsWith同时把链尾的静默补上:未识别的算子应当响亮拒收(抛错),而不是写不出键就算了 —— 否则 better-auth 下次加算子时,这个洞会以同样的方式重开一次。
相关
contains被译成裸$regex,用户输入当正则求值 —— 且是$regex退役唯一挡路的生产者 #5710(同一函数的contains支译成裸$regex;已提 PR,本单不在其范围内)$regexon driver-sql is not a regex — it compiles to a substring LIKE, so it both over-matches and silently matches nothing #4706 / spec:$regex按 ADR-0049 退役 +$icontains入算子词表与 FILTER_LOGIC_CASES(#4706 裁决 B 案 · 契约半边,先行) #5701 / drivers:$regex响亮拒收 +$icontains各后端实现(#4706 裁决 B 案 · 驱动半边) #5702(算子名实不符母单 / 契约半边 / driver 半边)starts_with/ends_with的大小写语义按 spec:$regex按 ADR-0049 退役 +$icontains入算子词表与 FILTER_LOGIC_CASES(#4706 裁决 B 案 · 契约半边,先行) #5701 Q2=A 是敏感的,与$startsWith/$endsWith契约一致。