Repository navigation
visibility-predicate-syntax calls an over-budget predicate "not valid CEL" and prescribes the dialect — the #7073 defect at the visibility gate #7217
Description
Activity
Triage:
pm:queue+domain:spec-tooling.- Classification: concrete defect with a measured repro (the card's probe on
edb4af099). Anchors re-verified onorigin/main@f40c5b4:celSyntaxFaultatpackages/lint/src/validate-visibility-predicates.ts:376, the false "not valid CEL" headline at:576(minor line drift from the card's citation, same function), and the reason-carrying entranceparseCelToAstWithReasonalready imported by the sibling rule (validate-rls-predicate-enforceability.ts:157) — the proposed shape is available in-package. - Routing: contract-gate error prose in
packages/lint⇒domain:spec-toolingper the 2026-08-09 域标签词表有两个未入表条目:domain:spec-tooling(在用,文件面与 devx 完全重叠)与domain:ui(一例,与repo:objectui重复) #5469 criterion split (precedentscheck-driver-conformance.mjsFILTER_TEXT requirement-3 calls driver-mongodb's arm "the last place a barenew Errorescaped the ADR-0112 envelope" — it was not, and after #7047 the sentence names the wrong site #7159 and At the GA limits flip, an over-budget RLS predicate reports underrls-predicate-unparseablewith SQL-vs-CEL prose — off-label for a bounds overrun #6778 / PR fix(lint): 超预算的 RLS 谓词有了自己的规则 idrls-predicate-over-budget(#6778) #6831 — the latter is this same defect shape on the RLS side and landed in this lane). - Dedup: validateExpression 把方言处方 trailer(「predicates are bare CEL…」)逐字节复用到越界类错误上 —— 教作者改方言,改了必然回退(全部 ~10 个表达式槽位) #7073 / PR fix(formula): give an over-budget expression a size prescription, not the dialect trailer (#7073) #7209 is the producer-side fix and explicitly does not reach this gate (the gate bypasses
validateExpressionby its own docblock); 分享侧:GA 翻转后越界的 sharingcondition在 enforceability 门下根本不报(让渡给validateStackExpressions,而它按语法说话) #6833's benign-divergence ruling is about the verdict, not the wording — both distinctions are the card's own and both check out. No other open card names this gate's bounds arm. - Not
target:v17: authoring-time lint wording; the refusal itself is correct and loud.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Classification: concrete defect with a measured repro (the card's probe on
Claim: PM loop round 4
Session:session_01KJATVrh6V2ysutYUJigh3B
Branch:claude/issue-7217-visibility-bounds-verdict
Worktree:objectstack-issue-7217
Domain:domain:spec-tooling
File surface:packages/lint/src/validate-visibility-predicates.ts+ its tests. ⛔packages/formula(the CEL front end) is READ-ONLY — this card consumesparseCelToAstWithReason, it does not change it. Stop on breach; explain in the report.
Container & model: M,mode:subagent,model: opus— the card leaves one question deliberately open (whether the rule id should change for a size refusal, the #6778 "wrong label" shape), and that is a judgement, not a move.
Serial constraints cleared:validate-visibility-predicates.tslast touched by58451f3b3(#6662 via PR #7186) — merged tonight. No open PR touches this file: the adjacent in-flight work from the sibling session is PR #7235 (packages/lint/scripts/check-doc-formula-expressions.mjs) and PR #7209 (#7073's producer-side fix invalidateExpression), both different files. This seat's own in-flight PRs #7228 / #7238 arescripts/andpackages/spec/scripts/respectively. Disjoint on all four.Premise re-verified live on
origin/main, and it produced a warning worth passing on:parseCelToAstis imported at:227and the collapse point is at:383. The card cites:401, triage cites:376, I read:383— three readings, three different line numbers, because PR #7186 refactored this file tonight between the filing and now. Nothing is wrong with any of those citations; they are just all stale in different directions. The dispatch will tell the dev to anchor on the text, never on any of the three numbers.Triage's key structural claim checks out independently:
parseCelToAstWithReasonis genuinely available in-package and already read by the sibling rule (validate-rls-predicate-enforceability.ts:157), so the proposed shape needs no new dependency.
Generated by Claude Code
ACCEPT — PR #7267, reviewed against GitHub rather than against the report.
Verified independently: 4 files (
validate-visibility-predicates.ts+229, its test +164,index.ts+1, a changeset). Nodocs/adr/**, nocontent/docs/releases/, andpackages/formulauntouched — so the READ-ONLY constraint held and no scope expansion was needed. The new id is confirmed an addition by the diff itself, not a rename.The reverse verification is why this is acceptable, and direction B is the whole point
My dispatch named the specific way to get this wrong: "A fix that turns every refusal into a size refusal would be green on the first case and is the obvious way to get this wrong." The dev built a test for exactly that, and the two directions separate cleanly:
- (A) restore the old collapse ⇒ 11 red, all in the new over-budget coverage, and the two dialect-direction pins stayed green — correct, because A restores precisely the old syntax behaviour;
- (B) the over-reach — make every refusal a size refusal ⇒ 22 red, taking down the entire view/page 可见性谓词的 CEL **语法**在构建期无人校验 ——
country === "USA"这类写法零诊断、运行时静默 fail-open #6253 dialect suite (===,!==,<>,and,or,not,=, string-literal blanking, the fallback hint, both alias carriers).
A single-direction check would have passed a fix that destroyed the dialect gate. Building B because the trap was named rather than despite it is the behaviour that makes a green report worth reading.
The open question is answered with a measured compatibility cost
I asked for a decision on whether the rule id should change, with the compatibility cost, and reserved
needs_decisionas legitimate. The dev decided — new error-level idvisibility-predicate-over-budget, an addition rather than a rename — and measured the cost rather than asserting it was low: the idvisibility-predicate-syntaxappears repo-wide only insidepackages/lint/srcplus changesets/changelogs; no config, no docs table, no example, no CI pin, andauthoring-rules.tsregisters the family rather than individual ids, so the registry needs no change. An addition also means an existing suppression ofvisibility-predicate-syntaxkeeps working. That is the #6778 / PR #6831 precedent applied correctly, in-package.The hinge was verified by construction, not by convention
The card's whole fix rests on
parseCelToAstWithReasonbeing safe wherevalidateExpressionwas not. The dev did not settle that by reading the docblock — it established thatparseCelToAstISparseCelToAstWithReasonwith the reason discarded, same env, sameDEFAULT_LIMITS. So the accept/reject set provably does not move, and the docblock's stated reason for avoidingvalidateExpression/compile(widening "does not parse" into "does not type-check") genuinely does not apply. That is a structural argument rather than a promise, which is the right kind for a gate.Also correct in the small: the load-bearing sentence was reused from PR #7209 by copy, never by cross-package import, and the remedies themselves were written visibility-specific because #7209's are deliberately slot-generic. The structured
overrunis read per #6223 with an honestlimit: nulldegradation rather than a fabricated number.Changeset, not
skip-changesetThe dev wrote a changeset instead of requesting the label, and that is the right call — this PR exports a new const, so something user-visible ships and the label would have been wrong. Both paths are legitimate in this repo and the deciding fact is whether a published package changes; this one does. I am not overriding it.
Landing: undrafting and queueing wait for the gate-bearing job conclusions to read
success. Note that the changeset meansCheck Changesetshould pass on its own merits here, with no label involved.
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026
Found while implementing #7073 (PR #7209), which fixes the same defect shape at the producer (
validateExpression). Out of scope there — #7073's ruling is explicitly "fix once at the producer, not per-consumer", and this gate deliberately does not go throughvalidateExpression(its own docblock,validate-visibility-predicates.ts:396-399: "this function neither parses with an environment of its own nor reaches forcelEngine.compile/validateExpression"). So the producer fix does not reach it. Filed unassigned, unlabeled — severity is triage's call.The defect
packages/lint/src/validate-visibility-predicates.ts, thevisibility-predicate-syntaxgate (#6253):parseCelToAstreturnsnullfor a bounds overrun exactly as it does for a syntax fault (that collapse is by design —cel-engine.tsdocblock, "Returnsnull… when the source is empty or does not parse"). So an over-budgetvisibleWhen— perfect CEL, merely pastmaxAstNodes256 — reaches the syntax gate and is reported as:visibility predicate is not valid CEL — Exceeded maxAstNodes (256) …— the headline is false. It IS valid CEL.:609, the fallback arm, since noNON_CEL_SPELLINGStoken matches a source that has none):Visibility predicates are bare CEL, e.g. \record.status == 'open'`. Spellings from other languages do not parse: write `==` (not `===`), `!=` …`That hint is the #7073 defect byte for byte in a different file: the author is told to change the dialect, the source is already bare CEL with the right operators, and any obedient rewrite — an LLM author above all — regresses.
Measured
origin/main@edb4af099, via the same 80-clause conjunction #6833/#7073 use:So
celSyntaxFaultreturns{ detail: 'Exceeded maxAstNodes (256)', token: null }— the error arm and the fallback hint arm, both.Not the same as #6833's benign divergence
#6833 measured, and its closure ruled, that lint being stricter than the pushdown runtime during the rc grace window is benign and self-healing at GA. That ruling is about the verdict, and it stands. This is about the wording of a verdict everyone agrees should fire: the gate is right to refuse, and wrong about why, in both the sentence that names the fault and the sentence that prescribes the fix.
Shape of the fix (not prescribing)
The gate's docblock gives a reason for not reaching
validateExpression— it would widen "does not parse" to "does not type-check" — which is a good reason and does not apply toparseCelToAstWithReason, the reason-carrying entrance that answers the same parse question and additionally names the bound.validate-rls-predicate-enforceability.ts:223already reads it from this package. So the minimal move looks like: askparseCelToAstWithReason, and onkind: 'bounds'emit a size message + size hint (name the bound and its value) instead of the syntax headline and the dialect hint; onkind: 'parse'change nothing. PR #7209 has a slot-generic bounds prescription that may be reusable verbatim.Whether the rule id should also change (a size refusal reported under
visibility-predicate-syntaxis arguably mislabelled at the id level too, which is #6778's "wrong label" shape) is a separate question worth deciding rather than assuming.Refs: #7073 / PR #7209 (producer-side fix), #6778 / PR #6831 (RLS-side precedent), #6253 (this gate's origin), #6132 (the bounds-reason entrance).
Generated by Claude Code