You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(formula): converge the CEL pushdown parser onto the canonical front end, with an rc grace window (#6132)
`packages/formula/src/cel-to-filter.ts` — the one canonical CEL →
FilterCondition pushdown compiler (ADR-0058 D1/D2/D6) — kept a private,
limitless `new Environment({ unlistedVariablesAreDyn: true, enableOptionalTypes:
true })` with no `limits`, no stdlib and no `rewriteNullableTernary`, and read
`.ast` off it. That made the RLS / sharing pushdown path the one place on the
platform answering a different question from `celEngine.compile()` about what
parses: an 80-term conjunction, a 40-level nest and a 200-element `$in` all
reached REAL pushdown SQL while the interpreter refused each outright, and
`isSupportedRlsExpression` was a thin wrapper over the same env rather than an
independent gate.
It now parses through `parseCelToAstWithReason`, the canonical entry (#4812),
carrying DEFAULT_LIMITS.
Within the limits this is behaviour-preserving, measured rather than asserted:
across the 710 sources of the pushdown corpus both front ends accept, the only
AST difference is `rewriteNullableTernary`'s `dyn(...)` wrap on the null-guard
ternaries, and a ternary faults on its own `?:` node before the lowerer descends
into a branch — so reason AND detail are byte-identical. Pinned in
cel-to-filter-parse-convergence.test.ts, which rebuilds the old env to compare.
Over the limits, the maintainer's A' ruling (2026-08-08, on the issue) is
implemented as a single dated switch, `CEL_PUSHDOWN_LIMITS_MODE` in the new
`cel-pushdown-limits.ts`:
- `rc-grace` (shipping default, 17.0.0-rc.x): an over-limit predicate still
compiles, off the unbounded AST the canonical entry hands back for exactly
this purpose, and WARNs once per predicate naming the exceeded bound, the
platform's value for it, the predicate's own measure, and the GA
consequence.
- `fail-closed` (v17 GA, one line): the predicate is refused as
`{ reason: 'parse-error', detail: 'Exceeded maxAstNodes (256)' }`, which the
RLS path already routes to RLS_DENY_FILTER.
Both positions run in CI today — in `@objectstack/formula` and in
`@objectstack/plugin-security`, which owns the deny sentinel — so the GA half is
proven before it ships. Two assertions go red on the flip so it cannot be
silent.
Sister entrance: `parseCelToAstWithReason` separates "not valid CEL" from "valid
CEL, over budget" and names WHICH bound was blown, its platform value, and what
the source measures (cel-js's own accounting — the smallest bound it parses
under, found by probing the parser, because `maxDepth` counts parenthesised
recursion that leaves no AST node behind). Graded by the same by-class/by-code
classifier `compile`/`evaluate` use (#6223), never by error prose; the parity is
pinned. `parseCelToAst` is unchanged and still collapses every refusal to
`null`.
`@objectstack/lint` needs no change at either position: its two enforceability
gates read `isSupportedRlsExpression` / `compileCelToFilter` and both suites pin
"the lint verdict IS the consumer's verdict" in both directions, so authoring
reporting flips with the runtime by construction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bx3H8DJhBsmgDoMp8Tz87T
0 commit comments