Skip to content

fix(lint): an over-budget visibility predicate is a SIZE fault, not "not valid CEL" (#7217) - #7267

Merged
os-help merged 1 commit into
mainfrom
claude/issue-7217-visibility-bounds-verdict
Aug 10, 2026
Merged

fix(lint): an over-budget visibility predicate is a SIZE fault, not "not valid CEL" (#7217)#7267
os-help merged 1 commit into
mainfrom
claude/issue-7217-visibility-bounds-verdict

Conversation

@os-help

@os-help os-help commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7217

The visibility-predicate-syntax gate collapsed two classes of refusal into one. parseCelToAst returns the same null for "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 past maxAstNodes 256 — 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 @ 0caf122f0 vs this branch)

Predicate: record.f0 == 0 && record.f1 == 1 && … && record.f79 == 79 (80 terms).

Before — rule visibility-predicate-syntax:

message: visibility predicate is not valid CEL — Exceeded maxAstNodes (256) (predicate: record.f0 == 0 && record.f1 == 1 && …). A predicate that does not parse can never evaluate, and the console falls OPEN …

hint: Visibility predicates are bare CEL, e.g. record.status == 'open'. Spellings from other languages do not parse: write == (not ===), != (not !== or <>), && (not and), || (not or), ! (not not).

After — rule visibility-predicate-over-budget:

message: visibility predicate is syntactically valid CEL but overruns the maxAstNodes budget (platform limit 256) (Exceeded maxAstNodes (256)) (predicate: record.f0 == 0 && …). The canonical front end refuses it, so it can never evaluate, and the console falls OPEN: the element renders unconditionally and looks exactly like one with no predicate at all (#5149).

hint: There is no syntax or dialect error to correct here — this is a SIZE fault, not a dialect mistake, so re-spelling the predicate will not fix it. Make it smaller, or move the work off the predicate: (1) collapse a long record.f == 'a' || record.f == 'b' || … chain into a single record.f in ['a', 'b', …], which is far fewer AST nodes (maxListElements is 64, so a very large set needs option 2); (2) precompute the heavy part into a formula/rollup field on the object and test that one field instead. Logic genuinely this large is not element visibility — compute it once on the record rather than re-deriving it in every predicate that needs it.

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 to parseCelToAstWithReason, and this is provable rather than asserted — parseCelToAst IS that entrance with the reason discarded:

export function parseCelToAst(source: string): CelAstNode | null {
  const parsed = parseCelToAstWithReason(source);
  return parsed.ok ? parsed.ast : null;
}

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. The type == '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: null degrades to "one of the platform's parse budgets" rather than guessing a key. The remedy's binding root follows the layer (record at runtime, data on a *.form.ts metadata 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.

Reverse verification (both directions, since only one of them is obvious)

  1. Restore the pre-visibility-predicate-syntax calls an over-budget predicate "not valid CEL" and prescribes the dialect — the #7073 defect at the visibility gate #7217 collapse (drop the kind === '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.
  2. The obvious way to get this wrong — make every refusal a size refusal: 22 red, the whole view/page 可见性谓词的 CEL **语法**在构建期无人校验 —— 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/lint full suite: 69 files, 1812 passed, 4 skipped.
  • New visibility-predicate-over-budget (#7217) block: 13 cases — the acceptance pair, one case per bound (maxAstNodes 80-term conjunction, maxDepth 60-level nest, maxListElements 200-element list, each asserting the bound that was actually exceeded is the one named), the flipped dialect pins, one-finding exclusivity with visibility-bare-identifier, elision, blank-predicate, the layer-rooted remedy, and the page-component carrier.
  • The pre-existing bounds case under visibility-predicate-syntax is 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.
  • Downstream sweep deliberately not run as a package sweep: the public-API change is purely additive (one new exported const; no signature narrowed, no exported type changed), so no consumer can fail to compile on it, and a repo-wide search shows no consumer reads these ids.

Scope

packages/formula was 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 in packages/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

…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
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 10, 2026 4:10am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint.

2 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/automation/hook-bodies.mdx (via @objectstack/lint)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint)

1 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/v17.mdx (via @objectstack/lint)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 10, 2026
@os-help
os-help marked this pull request as ready for review August 10, 2026 04:30
@os-help
os-help added this pull request to the merge queue Aug 10, 2026
Merged via the queue into main with commit ea8e849 Aug 10, 2026
26 checks passed
@os-help
os-help deleted the claude/issue-7217-visibility-bounds-verdict branch August 10, 2026 04:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

visibility-predicate-syntax calls an over-budget predicate "not valid CEL" and prescribes the dialect — the #7073 defect at the visibility gate

2 participants