Skip to content

[观察] rls.zod.ts using 属性上方的 TSDoc 块仍宣称「Exactly four forms compile」——与同属性 .describe()(#6762 修正后)直接矛盾 #6919

Description

@os-project-manager

性质

观察类(finding),不加 pm:queue不进生成器,今天没有用户会撞上它 —— 影响面是「读源码的作者被一段过时的语法规范误导」。不认领,仅记录。

来自 #6762 / PR #6918 的 out-of-scope finding(Prime Directive #10),未指派。

2026-08-09 更新 —— 本卡范围已收窄,只剩未发布的那一半。
早先本卡的评论区还记录了同文件 :79 模块级 docblock 的同类问题(「A small, fixed expression grammar (equality, set-membership, always-true)」,会逐字渲染到 content/docs/references/security/rls.mdx:79)。经 PM 裁定,那一半是已发布面、属于 #6762 的条目范围,已在 PR #6918 内一并修掉,不再属于本卡。
因此本卡现在纯粹是属性级 TSDoc(不进生成器)—— 这也让 finding 这个等级从「勉强站得住」变成「确实正确」。下方评论区里关于 :79 的那条记录已过时,仅作历史留存

事实

packages/spec/src/security/rls.zod.tsusing 属性上方的 TSDoc 块(origin/main @ 2672f855f 实测 :296-354)写着:

 * The reference RLS compiler implements a deliberately **small, fixed
 * grammar** rather than a general SQL parser. Exactly four forms compile;
 * anything else fails closed (the policy matches zero rows). Keep `using`
 * to one of:
 *
 * 1. `field = current_user.< prop >` — equality against a context value
 * 2. `field = 'literal'` — equality against a single-quoted string literal
 * 3. `field IN (current_user.< array_prop >)` — set membership ...
 * 4. `1 = 1` — always true / no restriction
 *
 * There is intentionally **no** support for `AND`/`OR`/`NOT`, comparison
 * operators other than `=`, ...

(上面两处 < prop > / < array_prop > 是为绕开 GitHub body sanitizer 加的空格,源码里没有空格。)

这与实测不符。在 PR #6918 里我对 isSupportedRlsExpressionpackages/formula/src/rls-predicate.ts)逐条实测:

  • ENFORCES!=<<=>>=incurrent_user.* 数组以及对内联字面量列表(status in ['draft', 'pending']);&&||;裸 true
  • FAILS CLOSED:SQL 的 AND / OR / NOT IN / IS NULL / LIKE、算术、子查询、跨对象 traversal、裸真值字段、取反 !

所以这段块注释一半对一半错:它说 SQL 的 AND/OR 不支持 —— 对;它说「恰好四种形式编译」「除 = 外没有比较算子」—— 错,CEL 的 &&/|| 与全套比较算子都会真正下推。

isSupportedRlsExpression 自己的注释早就点破了这点:"This is broader than the historical 4 forms — comparisons (amount > 100) and == now ENFORCE"

为什么 PR #6918 只打标记、不重写

PR #6918 已在 :310 处加了一段 ⚠️ STALE 标记指向本卡,说明下方四项枚举 under-states、.describe() 才是当前事实。标记不是修复:它把「两条互相矛盾的断言」降级为「一条断言 + 一句诚实警告」,本卡落地时应连同标记一并移除。

之所以不在那张卡里重写:

  1. 它不进生成器。 gen:docs 只渲染属性的 .describe()模块级 docblock,从不渲染属性级 TSDoc。实测复证:给这段 TSDoc 加标记后 content/docs/references/** 零 diff。所以它是观察类。
  2. 它是一段约 60 行的语法规范散文,还顺带对 context values、§7.3.1 dynamic membership、prohibited 清单做规范性陈述 —— 重写值得自己一轮复核,塞进一张两条目 sweep 卡会被淹没。

建议的修法

把这段块注释按 .describe():79 已确立的口径重述:讲能被下推的形态而非固定计数(⛔ 「四」换成「八」是同一个缺陷),canonical CEL 在前、SQL 拼写作为过渡桥接(ADR-0058 D1,sqlPredicateToCel 已标 @deprecated)。同属性的 5 条 @example"organization_id = current_user.organization_id" 等)也全是 SQL 方言,同一轮一起改比较合算。顺手移除 :310 的 STALE 标记。

⚠️ 注意 #6763没有任何门读 spec 的 TSDoc @example,所以这些例子改完也仍然没有回归保护 —— 这条与 #6763 是同一根因的两个面。

Related

#6762 / PR #6918.describe() 与已发布的 :79 模块 docblock,均已修)、#6763(无门读 TSDoc @example)、#6641 / PR #6729check 子句的 @example)、ADR-0058 D1。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions