Repository navigation
rls.zod.ts 的 check @example status IN ('draft', 'pending') 编译不出来 —— 照抄 schema 自带示例的策略会 fail-closed 拒绝全部访问 #6641
Description
Activity
Triage:
pm:queue+domain:spec-surface+target:v17.- Landing anchor (read on
origin/main@04476e7): the@exampleis live atpackages/spec/src/security/rls.zod.ts:383. The card's preferred route 1 (rewrite the example to CEL bracket formstatus in ['draft', 'pending']) is text-only — the legal metadata set is byte-identical before/after ⇒domain:spec-surface. Flip condition: if the fix instead widens the deprecatedsqlPredicateToCelbridge (route 2), that changes what compiles ⇒ report back for re-route todomain:spec. target:v17ground ④: an author copying the schema's own example gets a fail-closed policy (all rows denied) plus a lint error — the "follow the docs and it fails" first-hour shape, on the shipped contract's self-documentation.- Dedup: out-of-scope record from formula 内部还剩第三个 CEL 解析入口:cel-to-filter.ts 自建 limitless env,与 celEngine 对「什么能解析」仍不一致 #6132's reachability measurement; no open card on this example (repo-scoped search).
- Semantic check the card asks for (
status in [...]is the intended check meaning) belongs in the fixing PR.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Landing anchor (read on
os-project-manager commented
on Aug 8, 2026 CollaboratorMore actionspm:blockedapplied +Blocked-by: #5593body line (paired write; spec-surface seat #6298, sessionsession_018ffcE95NaMJcL9XJ9VDYgk). Reason: #5593's in-flight claim (2026-08-08T04:39Z, branchclaude/issue-5583-strictness-batch) declares a 14-file surface that includessecurity/rls.zod.ts, this card's landing file. Same-file claims serialize strictly,target:v17grants ordering priority but no serialization exemption. Unlock = #5593 closes; per seat rule E7 the unlock re-verifies this card's quoted lines AT the merged ref before dispatch (the migration touches this file's error-map wiring and may shift the @example's anchors).
Generated by Claude Code
os-project-manager commented
on Aug 8, 2026 CollaboratorMore actionsUnlock + claim — and a PM correction on how this card came to be blocked (spec-surface seat #6298, session
session_018ffcE95NaMJcL9XJ9VDYgk).Correction first. The
pm:blocked+Blocked-by: #5593this seat applied at ~12:25Z was based on a stale read. #5593 had already closed at 09:45:48Z (PR #6595, mergede0f300ba5) — but asearch_issuesquery at 12:21Z still returned it asstate: openwithupdated_atfrozen at 04:39Z, i.e. GitHub's search index was serving a ~2.5-hour-old snapshot, and this seat took a state-machine decision on it. Four cards (this one, #6628, #6631, #6619) sat blocked on an already-satisfied condition for ~50 minutes. No dev time was lost, but the lesson is recorded on the seat post: issue state for a gating decision is read withissue_read, never from search results.E7 re-verification at the merged ref (the discipline that turns an unlock into a fact rather than an assumption — and the reason this card is dispatchable rather than merely unblocked): on
origin/mainaftere0f300ba5, the@exampleis still present, verbatim, at:377(the card's:386anchor drifted by #5593's own migration of this file's alias table — exactly the drift E7 exists to catch). Premise holds in full.pm:blockedremoved and theBlocked-bybody line deleted, paired with this comment.Claim: PM loop round 8 (
domain:spec-surfaceseat #6298)
Session:session_018ffcE95NaMJcL9XJ9VDYgk(GitHubos-project-manager)
Branch:claude/issue-6641-rls-check-example-cel
Worktree:objectstack-issue-6641
Domain:domain:spec-surface
File surface:packages/spec/src/security/rls.zod.ts(thecheckproperty's@exampleline only), any pin on that example,.changeset/*.md(stop on breach; explain in the report)
Serial constraints cleared: #5593 MERGED (e0f300ba5) — its 44-call-site migration touched this file and is now landed, so branching from freshorigin/mainpicks it up. In-lane #6701 (shared/retry-policy.zod.ts+conversions/registry.ts) and #6689 (type-alias-convention.pin.test.ts) are file-disjoint and both already in the landing window.
Container weight: S,mode:subagentshared container.
Generated by Claude Code
os-project-manager commented
on Aug 8, 2026 CollaboratorMore actionsACCEPT — PR #6729 (spec-surface seat #6298, session
session_018ffcE95NaMJcL9XJ9VDYgk; early review from the diff. Ready-flip waits for the dev's final report per flip precondition E6.)Verified against the diff:
- 3 files: the one-line
@examplefix insecurity/rls.zod.ts(status IN ('draft', 'pending')→status in ['draft', 'pending']), a pin inpackages/formula/src/rls-predicate.test.ts, one changeset (@objectstack/spec: patch). Route 1 taken as ruled; the deprecatedsqlPredicateToCelbridge is untouched — zero acceptance-surface bytes, nothing that compiles today stops compiling. - The required check was done and is in the record: the replacement is measured through the runtime's own path (
compileCelToFilter→{ status: { $in: [...] } }), and — the part that makes it a correct example rather than merely a compiling one — under CHECK-clause semantics it accepts the enumerated values and refuses a non-enumerated one,null, and an absent field. "Only allow certain statuses" is now true of the example, not just parseable. - The neighbouring
IN (current_user.team_member_ids)examples were left alone with the reason measured, not assumed: a single(expr)is a legal CEL parenthesised group, so that spelling survives the bridge and only collapses at two elements. That is also the explanation for why this defect hid — measuring one example never covered the other.
The pin is the best part of this card and worth naming. A TSDoc
@exampleis not reachable from any import, so no ordinary unit test can ever go red on it — which is precisely why a deny-every-row predicate sat in the schema's own documentation. The new test reads the source text, extracts every@exampleonusingandcheck, and runs them through the ADR-0056 D4 shape gate, with three properties I'd want copied by the next card of this class:- anti-vacuity guard (
length >= 3), so reshaping the docblock cannot turn the loop green-over-nothing; - found by idiom, not by wording (
/\bin\b/i, deliberately case-insensitive so the old SQL spelling is caught and failed on the bracket assertion rather than filtered out into an empty list — a laxer pin would have called that green); - expectations read out of what it compiles to, so renaming the field or the statuses keeps it green while breaking the idiom fails.
That converts this defect class from "invisible until an author hits it" to "fails a test", which is the durable half of the fix.
Generated by Claude Code
- 3 files: the one-line
- added a commit that references this issue
on Aug 8, 2026 - added a commit that references this issue
on Aug 17, 2026
范围外发现,出自 #6132 的可达性测量(未在该单内修)。按 Prime Directive #10 记录,不指派。
事实(
origin/main0b63b5677基线,实测)packages/spec/src/security/rls.zod.ts:386的check属性 TSDoc 带这条@example(2026-08-08T13:1xZ 在e0f300ba5之后重锚:现于:377,内容逐字未变):它编译不出来。实测(
sqlPredicateToCel+isSupportedRlsExpression+compileCelToFilter,与运行期RLSCompiler.compileExpression同一条路径):@exampleisSupportedRlsExpressioncompileCelToFilterorganization_id = current_user.organization_idorganization_id == current_user.organization_id{"organization_id":"o1"}owner_id = current_user.idowner_id == current_user.id{"owner_id":"u1"}status = 'published'status == 'published'{"status":"published"}assigned_to_id IN (current_user.team_member_ids)assigned_to_id in (current_user.team_member_ids){"assigned_to_id":{"$in":["u1","u2"]}}1 = 11 == 1{}status IN ('draft', 'pending')status in ('draft', 'pending')原因:
sqlPredicateToCel只把IN这个词换成in,不改写括号。CEL 的列表字面量是['draft', 'pending'](方括号),('draft', 'pending')在 CEL 里不是合法表达式,于是 parse 失败。单元素的IN (current_user.xxx)之所以能过,是因为(expr)恰好是合法的括号分组 —— 一进多元素就塌。影响
不是「示例排版错」那一类。作者照抄 schema 自带示例写出的策略,在运行期走的是:
compileExpression→compileCelToFilter返回!ok→ 返回null→compileFilter里filters.length === 0→ 返回RLS_DENY_FILTER。即该对象上的这条策略拒绝全部行(单策略时是彻底拒绝)。同时
@objectstack/lint的validateRlsPredicateEnforceability也会因isSupportedRlsExpression为 false 而报错。所以症状是「按官方示例写,然后 lint 报错 / 数据全空」,自纠成本不低 —— 作者没有理由怀疑 schema 自己的@example。这正是 Prime Directive #10 的 declared ≠ enforced 形状:文档广告了一个运行时不提供的能力。
两个修法(留给 triage,不代裁)
@example改成 CEL 方括号形式status in ['draft', 'pending'],与 ADR-0058 D1「CEL 是 canonical」一致。using的几条@example也仍是 SQL 风格,但那几条都实测可编译,属于 transitional bridge 的既有面。sqlPredicateToCel把IN (a, b, c)的圆括号列表改写成 CEL 方括号列表。这是扩桥,而sqlPredicateToCel的 TSDoc 明写自己是@deprecated过渡桥、「只桥历史支持的子集」,方向上与「让 SQL 风格逐步退场」相反。倾向 1 —— 契约优先:CEL 是 canonical,示例就该是 CEL;把一个已声明 deprecated 的桥加宽,是给要退场的方言追加新面。但这条示例是
check子句唯一的「枚举取值」范例,改写时值得顺带确认status in ['draft', 'pending']在 check 语义下确实是想表达的意思。关联
#6132(测量出处)、ADR-0058 D1(一个 canonical 形状闸)、ADR-0056 D4(RLS 谓词形状闸门)。