Repository navigation
Description
范围外发现,出自 #6132 的可达性测量(未在该单内修)。按 Prime Directive #10 记录,不指派。
事实(origin/main 0b63b5677 基线,实测)
packages/spec/src/security/rls.zod.ts:386 的 check 属性 TSDoc 带这条 @example(2026-08-08T13:1xZ 在 e0f300ba5 之后重锚:现于 :377,内容逐字未变):
@example "status IN ('draft', 'pending')" - Only allow certain statuses
它编译不出来。实测(sqlPredicateToCel + isSupportedRlsExpression + compileCelToFilter,与运行期 RLSCompiler.compileExpression 同一条路径):
@example |
桥接后 | isSupportedRlsExpression |
compileCelToFilter |
|---|---|---|---|
organization_id = current_user.organization_id |
organization_id == current_user.organization_id |
true | ok {"organization_id":"o1"} |
owner_id = current_user.id |
owner_id == current_user.id |
true | ok {"owner_id":"u1"} |
status = 'published' |
status == 'published' |
true | ok {"status":"published"} |
assigned_to_id IN (current_user.team_member_ids) |
assigned_to_id in (current_user.team_member_ids) |
true | ok {"assigned_to_id":{"$in":["u1","u2"]}} |
1 = 1 |
1 == 1 |
true | ok {} |
status IN ('draft', 'pending') |
status in ('draft', 'pending') |
false | FAIL parse-error: Expected RPAREN, got COMMA |
原因: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 谓词形状闸门)。
Activity
os-zhuang commentedon Aug 8, 2026
Triage:
pm:queue+domain:spec-surface+target:v17.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.status in [...]is the intended check meaning) belongs in the fixing PR.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-project-manager commentedon Aug 8, 2026
pm: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 commentedon Aug 8, 2026
Unlock + 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-celWorktree:
objectstack-issue-6641Domain:
domain:spec-surfaceFile 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 commentedon Aug 8, 2026
ACCEPT — 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:
@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.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.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:length >= 3), so reshaping the docblock cannot turn the loop green-over-nothing;/\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);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
fix(formula): resolve this test file's directory once, keeping TEST_D…
feat(lint): check:doc-formula-expressions reads spec TSDoc `@example`…