fix(lint): 超预算的 RLS 谓词有了自己的规则 id rls-predicate-over-budget (#6778) - #6831
Conversation
`rowLevelSecurity[].using` / `.check` 里一条语法完美、可下推、只是超出平台解析 预算的 CEL 谓词(80 项合取越过 `maxAstNodes` 256),此前报在 `rls-predicate-unparseable` 名下,而那条规则的提示语讲的是 SQL 与 CEL 的方言 混淆。判决是对的,指路是错的:作者要做的是把谓词改小或拆开。 新增第三个 id,键控在 formula 姊妹入口 `parseCelToAstWithReason` 的 `kind: 'bounds'` 载荷上:消息点名具体越界的那个界与平台取值(三个界各报各的, 不写死),提示语给出真正的补救,并写明只有顶层 `||` 可以拆成多条策略——策略之间 是 OR,拆顶层 `&&` 会放大访问权限。 无行为变更:判定边界仍是 `isSupportedRlsExpression`,一字未动;区分只发生在解释 里。规则对 `cel-pushdown-limits.ts` 的 GA 开关保持中立,两个位置都有测试钉住。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AZgRyPVwi1jLb1mNNuUQ9o
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
座位验收( 先认错:派单里「 复核结果:
反向验证的关键在你选择证明的东西:6 红是次要的,真正的证明是哪些保持绿——三条真非 CEL、以及「拒绝集合与从前完全一致」。一个只证明「改了以后有东西变红」的分裂是装饰;你证明的是它只动越界那一半。 两个判断我特别认可: 不用 分享侧姊妹的排除有真实区分,不是偷懒。 提示语里「顶层
CI 收敛后由我摘草稿并入队。 Generated by Claude Code |
Fixes #6778
先说锚点:派单里「该文件不存在」是过期检出造成的,卡片的时序理由完好
派单要求先把 GA 翻转开关重新锚定再动手。实测结论:开关存在,且尚未翻转,因此卡片「sign-post 必须赶在 v17 GA limits flip 之前落地」的晋级理由成立,按 XS-S 实现。
packages/formula/src/cel-pushdown-limits.ts在origin/main上存在,随f6cd63549(formula 内部还剩第三个 CEL 解析入口:cel-to-filter.ts 自建 limitless env,与 celEngine 对「什么能解析」仍不一致 #6132 via PR fix(formula): converge the CEL pushdown parser onto the canonical front end, with an rc grace window (#6132) #6766)落地。e15bf7ef7上确实不存在:git cat-file -e e15bf7ef7:packages/formula/src/cel-pushdown-limits.ts报exists on disk, but not in 'e15bf7ef7'。派单时的find跑在这个落后于origin/main的检出上,所以查无此文件——不是重命名、搬家或移除。cel-pushdown-limits.ts:75):CEL_PUSHDOWN_LIMITS_MODE: CelPushdownLimitsMode = 'rc-grace'。翻转尚未发生。缺陷(本次实测的基线)
以 80 项合取(
record.f0 == 0 && …,实测 399 个 AST 节点,maxAstNodes为 256)走rowLevelSecurity[].using:rc-grace(当前发布态)isSupportedRlsExpression为true,lint 0 条发现fail-closed(v17 GA)rls-predicate-unparseable,提示语讲 SQL 与 CEL 的方言混淆判决是对的(消息确实携带
Exceeded maxAstNodes (256)),错的是 id 与指路:作者写的是语法完美、可下推、只是太大的 CEL,处方应是「改小或拆开」。姊妹入口给出的载荷本次实测为{"limit":"maxAstNodes","limitValue":256,"measured":null,"summary":"Exceeded maxAstNodes (256)"}。改动
新增第三个 id
rls-predicate-over-budget,键控在parseCelToAstWithReason的kind: 'bounds'载荷上——cel-to-filter.ts自己也从这个入口解析,所以「是越界还是语法错」在两边是同一个判决、同一个输入,且按错误类加结构化code分级而非按文案(#6223)。maxAstNodes256 /maxDepth32 /maxListElements64),不写死。越界谓词按定义很长,引文截断到 200 字符。||链折成field in [...];把集合预解析成current_user上的成员键(ADR-0105 D11);重复子表达式反范式化成本对象上的 formula/rollup 字段;以及只有顶层||可以拆成多条策略——适用策略之间是 OR,拆顶层&&会放大访问权限而不是保持它。rc-grace下也会走到这条分支的情形(越界且无界重解析同样失败、宽限窗口没有 AST 可放行)本就是真正的越界拒绝,报越界是对的。admitOverLimit:该选项被生产者明确写为宽限窗口专属、且随 GA 一并消失,lint 不该为了给消息加一个数字去依赖它。代价是measured在本路径上恒为null——这是生产者的设计(「测量意味着重新解析一个刚被判定为过大的源」),不是遗漏;「缩到多小才够」真正需要的界名与界值始终在。无行为变更:判定边界仍是
isSupportedRlsExpression,一字未动;同样的输入照样被拒,只是其中一类被告知了真正的原因。反向验证(先写下预期方向,再跑)
把新分支改成
if (overrun && false)(等价于把缺陷放回去),预测 6 红 / 37 绿,实测 6 failed | 37 passed,且红的正是预测的那六条。关键在于哪些保持绿——这才是区分度的证明,而不是装饰:三条真正非 CEL 的用例(SQL
AND、子查询、游离运算符)、「又超预算又不可解析者判为不可解析」、以及「拒绝的集合与从前完全一致」全部保持绿。只有越界那一半会动。两侧都成对钉住,将来任何把二者重新合并的改动都会在这里变红。
测试(均为本次实测)
npx vitest run src/validate-rls-predicate-enforceability.test.ts→ 43 passed(31 条既有 + 12 条新增)pnpm --filter @objectstack/lint test→ 67 files, 1738 passed | 4 skipped(基线 1726 条,净 +12)pnpm --filter @objectstack/lint typecheck→ 干净npx eslint(三个改动文件)→ 无输出node scripts/check-nul-bytes.mjs→ OK(6362 个跟踪文本文件)node scripts/check-empty-changeset.mjs→1 declaring changeset(s) added消费半径已扫:这三个 id 除规则文件本身外,全仓无任何注册表 / 文档 / 快照枚举(仅 CHANGELOG 与历史 changeset 提及),且未新增
AUTHORING_RULES条目,故不涉及跨包夹具。有意排除:分享侧姊妹(并非同一缺陷)
validate-sharing-rule-enforceability.ts:197对parse-error是直接 return 不报,把语法有意让渡给validateStackExpressions。所以 GA 之后一条越界的 sharingcondition在那条规则里不是「报错了 id」,而是根本不报——这是「让渡」而非「贴错标签」,与本卡的缺陷形状不同。要修就得决定越界是否应停止让渡,那会改变哪条规则报某个输入,涉及validateStackExpressions这个覆盖全栈每条 CEL 表达式的面,超出「同样的输入照样被拒,只是说对话」。已另行归档为观察类 finding,不在本 PR 修。Generated by Claude Code