That reporting is advisory, not a gate. Inserting a stage does not reject a bad expression,
and
await_sync_or_fail does not fail on one (details) —
a stage that fails to compile is skipped by the service, and the test continues as though it had
been applied. The coordinator can also only check against schemas it already holds, so a stage
inserted before its subscriber registers is not checked at all.For an eager, hard failure while you are authoring an expression, attach it in-process with
attach_cel_stage / attachCelStage where the VPoint is registered: that path raises
immediately on a parse error, unknown field, or unknown struct type.Namespaces
args — the VPoint’s inputs. Parameters are reachable only through args:
BasePrice is args.base_price — see each SDK’s field-naming rules).
ctx — context functions registered by the service: ctx.applyTax(invoke(), 0.08). See context functions.
Varianz builtins
invoke()
Executes the next stage in the pipeline — or, at the end of the chain, the real function — and returns its result.
invoke()with zero arguments forwards the original arguments.invoke(a, b, ...)with exactly the VPoint’s parameter count substitutes different arguments.- Any other arity is a compile error.
invoke() fully replaces the function’s result; the real code never runs.
sample(name, value)
Captures value under the label name for the test session to assert on, and returns value unchanged. Exactly two arguments. Because it’s transparent, it composes anywhere:
Struct construction
- Type names must match the schema exactly and are case-sensitive. Use the simple name (
ChargeResult), never a package-qualified one (pb.ChargeResult,hipstershop.ChargeResult). - All required fields must be provided.
- Nested structs work inline:
GetProductOutput { id: "X", price: Money { currency_code: "USD", units: 42, nanos: 0 } }.
Supported syntax
Tuple values expose synthetic positional fields: read
pair._0, or pair[0] when the schema proves a fixed tuple.
Constructing a tuple is the one case where you name the wrapper. That name is derived, the same way in every runtime, from the tuple’s element types under a reserved V_ prefix:
bool, int, float, string, bytes, null, any — so every integer width is int and every float width is float. A user struct contributes its short name (orders.Order → Order); a container element is spelled whole, so a tuple over a list of orders is V_Tuple_List_struct_orders_Order_. If your service registers a tuple against a concrete class, that carrier joins the name too: V_Tuple_com_acme_MyTuple_string_int.
V_ is reserved for these — don’t start your own struct names with it.
Diagnostics, hover and completion use that same CEL vocabulary rather than whatever your runtime calls a type internally, so a string[] in TypeScript, a List<String> in Java and a list[str] in Python all report as list<string>. Containers render list<T>, map<K, V>, T? and (A, B).
Optionals and null at the stage boundary
During evaluation, CEL keeps the standard distinction — optional.of(null).hasValue() is true, optional.none().hasValue() is false. At the stage boundary, however, Varianz transports an optional as either its inner value or absent: both optional.of(null) and optional.none() materialize as absence to the SDK on the other side.
Not yet supported
- Comprehensions
map,filter,exists_one(onlyexists/all)
- More than one
invoke()per expression — the second fails the call withNo next stage found, after the body has already run. Binding it (let p = invoke()) is not a workaround when the VPoint returns a pointer: the bound value arrives wrapped as an optional and its fields are unreachable through the binding, thoughinvoke().fieldinline works - Field access on
invoke()when the VPoint returns a single-field struct — that return is flattened to the field’s scalar, soinvoke().ffails withValue is not a collection or map - Type conversions (
int(),string(), …) and date/time functions - Error diagnostics carry byte offsets, not line/column
Where CEL runs
CEL stages attach to a VPoint at an anchor —MID by default — scoped to your session. Insert them from any test SDK (cel(target, expression)); target syntax and routing filters are in Naming and routing.
The Python and TypeScript SDKs can also compile and run a CEL expression in-process, with no coordinator and no session, via attach_cel_stage / attachCelStage — same language, same schema validation, but the anchor is explicit and the stage applies to every call in that process. See local stages.