fix(lint): an over-budget visibility predicate is a SIZE fault, not "not valid CEL" (#7217) - #7267
Conversation
…not valid CEL" (#7217) `visibility-predicate-syntax` reported a predicate that overruns a `DEFAULT_LIMITS` parse bound as "not valid CEL" and handed it the dialect prescription — a false headline plus advice that cannot succeed on a source already spelled in bare CEL. An author who follows it rewrites operators that were never wrong and returns with the same over-budget predicate. The verdict is untouched: same inputs refused, same severity, one finding per broken predicate. `celRefusal` now asks `parseCelToAstWithReason` — which `parseCelToAst` is literally implemented as, same env and same limits — so no source changes colour; only the explanation does. A bounds refusal is reported under a new error-level id `visibility-predicate-over-budget`, naming the bound and its platform value, for the reason #6778 / PR #6831 split `rls-predicate-over-budget` off `rls-predicate-unparseable`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJATVrh6V2ysutYUJigh3B
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
Fixes #7217
The
visibility-predicate-syntaxgate collapsed two classes of refusal into one.parseCelToAstreturns the samenullfor "this is not CEL" and for "this is CEL, and over the platform's parse budget" (deliberate on the producer's side, right for a caller that only wants an AST). A reporter is the caller it is wrong for: an 80-clause conjunction — flawless bare CEL, merely pastmaxAstNodes256 — was announced as not valid CEL and handed the dialect prescription.The failure being fixed is an instruction that makes an obedient author worse. The headline was false and the hint was unfollowable: the source is already bare CEL with the right operators, so an author who obeys the last sentence they were handed — an LLM author above all — rewrites operators that were never wrong and comes back with the same over-budget predicate.
Before / after (measured,
origin/main@0caf122f0vs this branch)Predicate:
record.f0 == 0 && record.f1 == 1 && … && record.f79 == 79(80 terms).Before — rule
visibility-predicate-syntax:After — rule
visibility-predicate-over-budget:A genuine dialect fault is byte-for-byte unchanged (
===still names the token and prescribes==; a fault with no single-token equivalent still gets the fallback dialect hint).The verdict does not move — only the explanation
The gate's docblock gives a good reason for not reaching
validateExpression/celEngine.compile: it would widen an error-level gate from "does not parse" to "does not type-check". That reason does not apply toparseCelToAstWithReason, and this is provable rather than asserted —parseCelToAstIS that entrance with the reason discarded:Same env, same
DEFAULT_LIMITS, identical accept/reject set. So no source changes colour; the split is made in the explanation and never in the verdict. Thetype == 'grid'blind spot this file pins deliberately stays a blind spot (its test is untouched and green).The bound, its platform value and the front end's summary come from the structured
overrun(limit/limitValue/summary), never from re-reading cel-js prose — the #6223 discipline, and the same fields the RLS gate reads.limit: nulldegrades to "one of the platform's parse budgets" rather than guessing a key. The remedy's binding root follows the layer (recordat runtime,dataon a*.form.tsmetadata form), because a remedy spelled in the wrong root would be a second unfollowable prescription.The open question: yes, the rule id changes too — and why
The card left this deliberately open. Decided yes: a new error-level id
visibility-predicate-over-budget, exported alongside the existing two.rls-predicate-unparseablewith SQL-vs-CEL prose — off-label for a bounds overrun #6778 / PR fix(lint): 超预算的 RLS 谓词有了自己的规则 idrls-predicate-over-budget(#6778) #6831 splitrls-predicate-over-budgetoffrls-predicate-unparseablefor exactly this defect shape, with a rationale written into that file: "The consequence is identical, the FIX is not, and allowlists /--jsonconsumers key on the id." Every word of it transfers. A reader who suppresses or greps "syntax" is reasoning about dialect mistakes; a size refusal is a different edit on a different axis.visibility-predicate-syntaxkeeps its id, its message and its hint for every source that genuinely is not CEL.visibility-predicate-syntaxstops suppressing the bounds class. Repo-wide search for the id (packages,examples,apps,content,.github,scripts) finds it only insidepackages/lint/srcand in changesets/changelogs — no config, no docs table, no example app, no CI pin. The registry needs no change either:authoring-rules.tsregisters the family (tier: 'gating', all three commands), not individual ids, and that is re-pinned by the existing test. This is the same cost 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 accepted, and the suppression it breaks was suppressing the wrong thing.Reverse verification (both directions, since only one of them is obvious)
visibility-predicate-syntaxcalls an over-budget predicate "not valid CEL" and prescribes the dialect — the #7073 defect at the visibility gate #7217 collapse (drop thekind === 'bounds'arm so bounds falls through to the syntax arm): 11 red, all of them in the new over-budget coverage — and the two dialect-direction pins stayed green, which is correct, because that direction restores exactly the old syntax behaviour.country === "USA"这类写法零诊断、运行时静默 fail-open #6253 dialect suite (===/!==/<>/and/or/not/=, the string-literal blanking case, the fallback-hint case, both alias carriers). A fix that over-reached would have been green on direction 1 alone; that is why the syntax block re-asserts the wording it must not lose.Both were performed with an in-worktree edit and restored from a copy — never
git stash.Tests
packages/lintfull suite: 69 files, 1812 passed, 4 skipped.visibility-predicate-over-budget (#7217)block: 13 cases — the acceptance pair, one case per bound (maxAstNodes80-term conjunction,maxDepth60-level nest,maxListElements200-element list, each asserting the bound that was actually exceeded is the one named), the flipped dialect pins, one-finding exclusivity withvisibility-bare-identifier, elision, blank-predicate, the layer-rooted remedy, and the page-component carrier.visibility-predicate-syntaxis rewritten in place into the exclusivity pin for the split (it asserted the old, false headline).pnpm --filter @objectstack/lint typecheck: clean. Build closure ('@objectstack/lint^...') built first.Scope
packages/formulawas not touched — the bounds reason was reachable without it, exactly as the card predicted.packages/lint/src/validate-visibility-predicates.ts(+ its tests), one line inpackages/lint/src/index.ts, one changeset. The load-bearing sentence "a SIZE fault, not a dialect mistake, so re-spelling … will not fix it" is reused verbatim from PR #7209's producer-side prescription, by copy and not by import — the remedies themselves are visibility-specific, since #7209's are deliberately slot-generic and this surface has one predicate per element (so no split remedy is offered at all).Generated by Claude Code