Skip to content

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

Description

@os-help

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 through validateExpression (its own docblock, validate-visibility-predicates.ts:396-399: "this function neither parses with an environment of its own nor reaches for celEngine.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, the visibility-predicate-syntax gate (#6253):

// celSyntaxFault, :401
if (parseCelToAst(source) !== null) return null;
const parsed = collectCelRootIdentifiers(source);
const detail = parsed.ok ? 'the expression could not be parsed' : parsed.error.split('\n')[0].trim();

parseCelToAst returns null for a bounds overrun exactly as it does for a syntax fault (that collapse is by design — cel-engine.ts docblock, "Returns null … when the source is empty or does not parse"). So an over-budget visibleWhen — perfect CEL, merely past maxAstNodes 256 — reaches the syntax gate and is reported as:

  • message: visibility predicate is not valid CEL — Exceeded maxAstNodes (256) … — the headline is false. It IS valid CEL.
  • hint (:609, the fallback arm, since no NON_CEL_SPELLINGS token 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:

$ npx tsx probe.ts
parseCelToAst(over-budget) = null
collectCelRootIdentifiers.ok = false
detail = Exceeded maxAstNodes (256)

So celSyntaxFault returns { 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 to parseCelToAstWithReason, the reason-carrying entrance that answers the same parse question and additionally names the bound. validate-rls-predicate-enforceability.ts:223 already reads it from this package. So the minimal move looks like: ask parseCelToAstWithReason, and on kind: 'bounds' emit a size message + size hint (name the bound and its value) instead of the syntax headline and the dialect hint; on kind: '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-syntax is 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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions