Summary
validateExpression refuses an unknown CEL function correctly, then hands the author a
prescription that cannot succeed: it tells them to write bare CEL, on a source that already
is bare CEL. This is the same defect #7073 / PR #7209 repaired for the bounds class,
still live for the type class.
Measured
packages/formula @ 936aa2d3a, @marcbachmann/cel-js@8.0.0:
validateExpression('predicate', "record.x.nosuchmethod('a')").errors[0].message
invalid CEL predicate: found no matching overload for 'dyn.nosuchmethod(string)'
> 1 | record.x.nosuchmethod('a')
^ - predicates are bare CEL (e.g. `record.rating >= 4`).
The front half is right and is cel-js's own vocabulary, matching the runtime fault exactly.
The trailer after the dash is the generic dialect prescription. The same trailer is attached
to totallyBogusFn(1,2), current_user.can(object, verb), and every other unknown-function
refusal.
Why it is wrong
In validate.ts the hint is chosen as:
const hint = (compiled.error.kind === 'bounds' ? boundsHint(source) : null) ?? bracesHint(source);
so a type fault gets bracesHint, which returns null for a source with no {x} brace, and
falls through to the default dialect trailer. The dialect is not what is wrong: the source is
already a bare CEL predicate and parses fine. What is wrong is one name.
This is precisely the reasoning boundsHint's own doc-comment gives for the class it repaired:
"an author who obeys the last sentence they were given - an LLM author above all - rewrites the
dialect, learns nothing, and comes back with the same" broken expression. A type fault is in
the same position: the last sentence the author is handed points at the one thing that is
correct.
Why it matters here specifically
#13594 documents that this refusal is the publish-time guard against the failure mode an
AI-authored app is most likely to hit - a plausible-looking function name that does not exist
is exactly what a generator invents - and that the runtime consequence is fail-closed and
near-silent (the action is hidden for every user including ones holding the grant). The guard
fires correctly today; the self-correction message that ADR-0032 Decision 1d exists to provide
is the part that misdirects.
Suggested direction (not a decision)
Give the type class its own prescription, the way bounds got one: name the unknown
function, and point at the callable set rather than at the dialect. introspectScope already
publishes CEL_STDLIB_FUNCTIONS for exactly this audience.
A did-you-mean suggestion needs care and may be worth omitting. Measured: nearestName is
exported and reusable, but against the function set it answers
nearestName('can', CEL_STDLIB_FUNCTIONS) with 'min' - a suggestion across an unrelated
namespace, which is worse than silence. nearestName('isBlnk', ...) correctly gives
'isBlank', so a threshold that admits real typos and refuses distant matches is the shape to
aim for, if one is shipped at all.
Filed unassigned from the #13594 dev seat while measuring that card; no repair attempted here,
and out of that card's scope. Searched the 390 open issues before filing - no duplicate.
Generated by Claude Code
Summary
validateExpressionrefuses an unknown CEL function correctly, then hands the author aprescription that cannot succeed: it tells them to write bare CEL, on a source that already
is bare CEL. This is the same defect #7073 / PR #7209 repaired for the
boundsclass,still live for the
typeclass.Measured
packages/formula@936aa2d3a,@marcbachmann/cel-js@8.0.0:The front half is right and is cel-js's own vocabulary, matching the runtime fault exactly.
The trailer after the dash is the generic dialect prescription. The same trailer is attached
to
totallyBogusFn(1,2),current_user.can(object, verb), and every other unknown-functionrefusal.
Why it is wrong
In
validate.tsthe hint is chosen as:so a
typefault getsbracesHint, which returns null for a source with no{x}brace, andfalls through to the default dialect trailer. The dialect is not what is wrong: the source is
already a bare CEL predicate and parses fine. What is wrong is one name.
This is precisely the reasoning
boundsHint's own doc-comment gives for the class it repaired:"an author who obeys the last sentence they were given - an LLM author above all - rewrites the
dialect, learns nothing, and comes back with the same" broken expression. A
typefault is inthe same position: the last sentence the author is handed points at the one thing that is
correct.
Why it matters here specifically
#13594 documents that this refusal is the publish-time guard against the failure mode an
AI-authored app is most likely to hit - a plausible-looking function name that does not exist
is exactly what a generator invents - and that the runtime consequence is fail-closed and
near-silent (the action is hidden for every user including ones holding the grant). The guard
fires correctly today; the self-correction message that ADR-0032 Decision 1d exists to provide
is the part that misdirects.
Suggested direction (not a decision)
Give the
typeclass its own prescription, the wayboundsgot one: name the unknownfunction, and point at the callable set rather than at the dialect.
introspectScopealreadypublishes
CEL_STDLIB_FUNCTIONSfor exactly this audience.A did-you-mean suggestion needs care and may be worth omitting. Measured:
nearestNameisexported and reusable, but against the function set it answers
nearestName('can', CEL_STDLIB_FUNCTIONS)with'min'- a suggestion across an unrelatednamespace, which is worse than silence.
nearestName('isBlnk', ...)correctly gives'isBlank', so a threshold that admits real typos and refuses distant matches is the shape toaim for, if one is shipped at all.
Filed unassigned from the #13594 dev seat while measuring that card; no repair attempted here,
and out of that card's scope. Searched the 390 open issues before filing - no duplicate.
Generated by Claude Code