/** * @module @nhtio/adk/batteries/orchestration/cells/jexl * * The `jexl` predicate cell. * * This cell lets a branch or select node express its predicate as a JEXL expression SOURCE * STRING, evaluated against a plain-data snapshot of the run's outputs. It is the alternative to * the declarative {@link import('./structured') structured} cell for authors who want the express * power of a real expression grammar. * * Why it is safe without a watchdog * --------------------------------- * RESOURCE BOUND — stated because the absence of a watchdog is easy to over-read. Expression-only * rules out non-termination, not slowness: a collection filter is LINEAR in the collection, so * evaluation cost scales with whatever a `call` node returned. `PlanBounds.maxEncodedBytes` caps * the PLAN, not a tool's runtime output, and the predicate reads the output. Measured: a filter * over 200,000 elements evaluates in roughly 100ms, so ordinary data is not a concern — but a consumer * whose tool can return unbounded data should cap it in `CallInvokerFn`, because this cell will * not. * * JEXL is a custom lexer/parser/AST interpreter, **not** `eval()`. It is expression-only by * design: there are no statements, no assignment, no loops, no function definitions. It is * therefore structurally non-Turing-complete and cannot fail to terminate on anything but * pathological data size. The grammar deliberately exposes only the safe surface — * comparisons, ternary/elvis, collection filtering (`employees[.age >= retireAge].first`, which * JEXL translates to a `filter` + `map` + projection), and the `|` transform pipe. * * The security boundary is the transform pipe. Every transform is host-registered through * `addTransform`, so the host decides the entire callable surface. This cell ships a CLOSED * ALLOWLIST: accept an optional `transforms` option, register exactly those, and make sure a * predicate cannot reach anything unregistered. If no `transforms` are supplied the pipe * resolves nothing, and any predicate that reaches for a transform fails closed at evaluation. * * Two cross-cutting properties hold by construction and are worth carrying into any caller: * * 1. The context is PRE-MARSHALLED PLAIN DATA. The evaluator builds a fresh plain record from * `ctx.outputs` and never hands JEXL a live object whose methods are reachable, so a * predicate can read values but cannot invoke arbitrary code through object identity. * 2. There is no clock and no randomness unless deliberately injected through a transform. A * branch or select node is therefore safe to re-enter unconditionally when a run resumes: * re-evaluating the same source string against the same snapshot always yields the same * verdict. Evaluation stays synchronous via `evalSync` — reproducible even though the cell's * seam is async. * * Honest dependency note * ---------------------- * JEXL was last published 2022-06-19. It is a stable-but-frozen dependency rather than an * actively maintained one. That is acceptable here because the grammar this cell exposes is * closed and the transform surface is host-owned, so there is nothing upstream can change that * this cell depends on; but it should be read plainly: do not assume ongoing upstream work. * Unlike the Lua cell, this cell is browser-safe. */ import type { PredicateEvaluator } from "../types"; /** * A host-registered transform, keyed by the name a predicate uses on the `|` pipe. * * The first argument is the piped value; the rest are the arguments given in the predicate. The * value is whatever the preceding expression produced (never a live object with reachable * methods — the context is marshalled plain data), so a transform should treat its input as a * value and return a value. */ export type JexlTransform = (value: unknown, ...args: unknown[]) => unknown; /** * Options for {@link createJexlCell}. */ export interface JexlCellOptions { /** * The CLOSED transform allowlist to register on the engine, keyed by pipe name. * * Registering nothing (the default) means the `|` pipe resolves no transform at all. This is * the cell's security boundary: a predicate can never reach a transform that was not listed * here. */ transforms?: Record; } /** * Create the `jexl` predicate cell. * * The cell treats a branch/select node's `predicate` as a JEXL expression source string. `load` * lazily resolves the optional `jexl` peer and registers the closed transform allowlist; `load` * and `validate` run the dialect lint and a parse attempt so a syntax error surfaces at freeze * rather than mid-run; `evaluate` synchronously evaluates the source against a marshalled * snapshot of the outputs. * * @param options - optional cell configuration (the closed transform allowlist). * @returns a {@link PredicateEvaluator} whose `id` is `'jexl'`. */ export declare const createJexlCell: (options?: JexlCellOptions) => PredicateEvaluator;