@objectstack/formula 9.10.0 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +90 -0
- package/dist/index.d.mts +78 -2
- package/dist/index.d.ts +78 -2
- package/dist/index.js +369 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +365 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/cel-engine.ts +6 -0
- package/src/cel-to-filter.test.ts +218 -0
- package/src/cel-to-filter.ts +411 -0
- package/src/index.ts +6 -0
- package/src/matches-filter.test.ts +110 -0
- package/src/matches-filter.ts +115 -0
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cel-engine.ts","../src/stdlib.ts","../src/cron-engine.ts","../src/template-engine.ts","../src/registry.ts","../src/seed-eval.ts","../src/normalize.ts","../src/validate.ts"],"sourcesContent":["/**\n * CEL dialect engine — wraps `@marcbachmann/cel-js` with the ObjectStack\n * stdlib, bounded execution limits, and result coercion.\n *\n * Why a thin wrapper:\n *\n * - cel-js returns `BigInt` for ints. The kernel and CRM expect plain\n * numbers, so we coerce at the boundary.\n * - cel-js parses dotted names as receiver-typed methods; we register\n * `now()`, `today()`, `daysFromNow()` as bare functions and let `os.*`\n * refer to context data only (see {@link buildScope}).\n * - Bounds (`maxAstNodes`, `maxDepth`, …) are enforced spec-wide so\n * third-party plugins can't ship runaway predicates.\n */\n\nimport { Environment } from '@marcbachmann/cel-js';\nimport type { Expression } from '@objectstack/spec';\n\nimport { buildScope, registerNumericCoercions, registerStdLib } from './stdlib';\nimport type { DialectEngine, EvalContext, EvalResult } from './types';\n\n/**\n * Default execution bounds. Picked conservatively — every metadata-authored\n * expression we've seen is well under these. If you hit them, the expression\n * is too complex for ObjectStack and should be moved to a hook (`dialect: js`).\n */\nexport const DEFAULT_LIMITS = {\n maxAstNodes: 256,\n maxDepth: 32,\n maxListElements: 64,\n maxMapEntries: 64,\n maxCallArguments: 16,\n} as const;\n\nfunction buildEnv(now: () => Date, timezone = 'UTC'): Environment {\n const env = new Environment({\n unlistedVariablesAreDyn: true,\n enableOptionalTypes: true,\n limits: DEFAULT_LIMITS,\n });\n return registerNumericCoercions(registerStdLib(env, now, timezone));\n}\n\n/**\n * Namespace roots that a `record`-scoped CEL site may legitimately reference.\n * Declared as `map` (dyn values) so member access (`record.foo`) and any\n * arithmetic/comparison on it defers to runtime — the strict env faults ONLY on\n * an *undeclared* top-level identifier, i.e. a bare field reference. Generous on\n * purpose: an unknown root is a missed catch, a missing root is a false positive\n * that would break the build, so we err toward declaring more.\n */\nconst SCOPE_ROOTS = [\n 'record', 'previous', 'input', 'output', 'os', 'vars', 'variables',\n 'automation', 'context', 'args', 'item', 'env', 'user', 'step', 'result',\n 'trigger', 'event', 'payload', 'data', 'params', 'config', 'settings',\n] as const;\n\n/**\n * A `record`-scoped environment (`unlistedVariablesAreDyn: false`) for detecting\n * bare field references. It reuses the real stdlib so function calls don't fault;\n * only undeclared *variables* do. Built once — `parse`/`check` do not mutate it.\n */\nfunction buildScopedEnv(knownFields: readonly string[]): Environment {\n const env = new Environment({\n unlistedVariablesAreDyn: false,\n enableOptionalTypes: true,\n limits: DEFAULT_LIMITS,\n });\n registerStdLib(env, () => new Date(0));\n for (const root of SCOPE_ROOTS) {\n try { env.registerVariable(root, 'map'); } catch { /* duplicate — ignore */ }\n }\n // `knownFields` are declared as `dyn` so they (and member/arith/compare on\n // them) never fault — only a genuinely-undeclared top-level identifier does.\n // Empty for a record-scope site (any bare field is a bug); the trigger\n // object's fields for a flattened flow condition (only a NON-field bare ref —\n // a typo or flow variable — is then interesting).\n for (const field of knownFields) {\n try { env.registerVariable(field, 'dyn'); } catch { /* duplicate / reserved — ignore */ }\n }\n return env;\n}\n\n// Roots-only env reused for the common record-scope check (no per-call rebuild).\nlet recordScopeEnv: Environment | undefined;\n\n/**\n * In a `record`-scoped CEL site — a `Field.formula` or an object validation\n * predicate — the evaluation scope binds only the `record`/`previous`/… *namespaces*\n * (no field flattening). A bare top-level identifier like `amount` or `status`\n * therefore resolves to nothing and the expression silently evaluates to `null`\n * / never fires (#1928, the class behind #1927's broken formulas). Returns the\n * first such bare reference, or `null`.\n *\n * Acts ONLY on cel-js's `Unknown variable: X` fault, so it cannot false-positive\n * on arithmetic/comparison overloads — and it must NOT be applied to flow /\n * automation conditions, where the record's fields ARE flattened to top-level\n * and bare references are correct.\n */\nexport function firstUndeclaredReference(\n source: string,\n knownFields: readonly string[] = [],\n): string | null {\n if (typeof source !== 'string' || !source.trim()) return null;\n try {\n const env = knownFields.length === 0\n ? (recordScopeEnv ??= buildScopedEnv([]))\n : buildScopedEnv(knownFields);\n const result = env.parse(source).check?.() as\n | { valid: boolean; error?: { message?: string } }\n | undefined;\n if (result && result.valid === false) {\n const m = /Unknown variable:\\s*([A-Za-z_$][\\w$]*)/.exec(result.error?.message ?? '');\n if (m) return m[1];\n }\n } catch {\n // Parse/other faults are the syntax checker's job (celEngine.compile); this\n // helper only reports the undeclared-variable case.\n }\n return null;\n}\n\n/** @deprecated use {@link firstUndeclaredReference} with no fields. */\nexport function detectBareReference(source: string): string | null {\n return firstUndeclaredReference(source);\n}\n\n/** Coerce cel-js's BigInt-flavored return into spec-friendly JS values. */\nfunction coerce(value: unknown): unknown {\n if (typeof value === 'bigint') {\n // BigInt → number when safe, else string to avoid silent truncation.\n if (value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) {\n return Number(value);\n }\n return value.toString();\n }\n if (Array.isArray(value)) return value.map(coerce);\n if (value && typeof value === 'object' && !(value instanceof Date)) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) out[k] = coerce(v);\n return out;\n }\n return value;\n}\n\n/**\n * A string that is *entirely* a JS number literal: optional sign, integer\n * and/or fractional part, optional exponent. Deliberately strict — `\"5.0\"`,\n * `\"250000.00\"`, `\"-3\"`, `\"1e3\"` match; `\"5px\"`, `\"0x10\"`, `\" \"`, `\"\"`,\n * `\"1,000\"`, `\"v2\"` do not.\n */\n// The fractional part is a single optional `(?:\\.\\d*)?` group anchored by the\n// literal `.` — never the ambiguous `\\d+\\.?\\d*`, whose adjacent unbounded\n// quantifiers (`\\d+\\d*` when the dot is absent) backtrack polynomially on long\n// digit runs (CodeQL ReDoS). This matches the same strings without the hazard.\nconst NUMERIC_STRING_RE = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/;\n\n/**\n * A string that is an ISO-8601 date (`\"2026-06-20\"`) or date-time\n * (`\"2026-06-20T08:15:35.244Z\"`, `\"2026-06-20 08:15\"`, `\"...+02:00\"`). Strict\n * and anchored — no nested unbounded quantifiers, so no ReDoS hazard (every\n * sub-group is bounded or a single `\\.\\d+`). `Field.date` / `Field.datetime`\n * serialize to these; cel-js compares them as `string` and faults against the\n * `google.protobuf.Timestamp` returned by `today()` / `now()` / `daysFromNow()`.\n */\nconst ISO_TEMPORAL_STRING_RE =\n /^\\d{4}-\\d{2}-\\d{2}(?:[T ]\\d{2}:\\d{2}(?::\\d{2})?(?:\\.\\d+)?(?:Z|[+-]\\d{2}:?\\d{2})?)?$/;\n\n/**\n * cel-js raises `no such overload: dyn <op> int` (and kin) when a comparison\n * or arithmetic operator sees a `string` on one side and a number on the\n * other. ADR-0032 §1c — numeric fields that serialize as strings (`Field.rating`\n * → `\"5.0\"`, `Field.currency` → `\"250000.00\"`, `Field.percent`) trip this in\n * flow conditions / formulas (#1530, #1534) even though the schema and the\n * build-time validator treat them as numeric.\n */\nfunction isNumericOverloadError(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return /no such overload/i.test(message);\n}\n\n/**\n * Recursively coerce string values that faulted a CEL overload into their\n * intended primitive: entirely-numeric literals → `number` (#1534), and\n * ISO-8601 date / date-time strings → `Date` (cel-js `google.protobuf.Timestamp`)\n * (#1530). Used only on the {@link isNumericOverloadError} retry path, so it can\n * never change a comparison that already evaluated cleanly — it only rescues one\n * that already faulted. Strings that are neither (a zip like `\"02134\"`, free\n * text) pass through untouched; if the retry still cannot type-check, the\n * original loud error is preserved.\n */\nfunction hydrateOverloadStrings(value: unknown): unknown {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed.length > 0) {\n if (NUMERIC_STRING_RE.test(trimmed)) {\n const n = Number(trimmed);\n if (Number.isFinite(n)) return n;\n } else if (ISO_TEMPORAL_STRING_RE.test(trimmed)) {\n const ms = Date.parse(trimmed);\n if (!Number.isNaN(ms)) return new Date(ms);\n }\n }\n return value;\n }\n if (Array.isArray(value)) return value.map(hydrateOverloadStrings);\n if (value && typeof value === 'object' && !(value instanceof Date)) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) out[k] = hydrateOverloadStrings(v);\n return out;\n }\n return value;\n}\n\nfunction classifyError(err: unknown): EvalResult<never> {\n const message = err instanceof Error ? err.message : String(err);\n let kind: 'parse' | 'type' | 'runtime' | 'bounds' = 'runtime';\n if (/Exceeded max/i.test(message)) kind = 'bounds';\n else if (/parse|unexpected|syntax/i.test(message)) kind = 'parse';\n else if (/type|unknown variable|undeclared/i.test(message)) kind = 'type';\n return { ok: false, error: { kind, message } };\n}\n\nexport const celEngine: DialectEngine = {\n dialect: 'cel',\n\n compile(source: string): EvalResult<unknown> {\n try {\n // We use a wall-clock now() here purely for parse-time stdlib\n // type-checking; the function is never actually called.\n const env = buildEnv(() => new Date(0));\n const compiled = env.parse(source);\n // Surface check errors eagerly. cel-js's `check()` returns a\n // `TypeCheckResult` object (`{ valid, type?, error? }`) — NOT an array —\n // so the type fault (including `found no matching overload for 'PRIOR(dyn)'`\n // when a condition calls an UNKNOWN function) only surfaces when we read\n // `valid === false`. The previous `Array.isArray(...)` guard never matched\n // an object, so unknown-function predicates type-checked clean and were\n // silently accepted by `objectstack build` / `registerFlow`, then no-op'd\n // the flow at runtime (#1877). Reading the documented shape closes that.\n const checkResult = compiled.check?.();\n if (checkResult && checkResult.valid === false) {\n return {\n ok: false,\n error: { kind: 'type', message: checkResult.error?.message ?? 'expression failed type checking' },\n };\n }\n return { ok: true, value: compiled.ast };\n } catch (err) {\n return classifyError(err);\n }\n },\n\n evaluate<T = unknown>(expr: Expression, ctx: EvalContext): EvalResult<T> {\n if (expr.dialect !== 'cel') {\n return {\n ok: false,\n error: { kind: 'dialect', message: `celEngine cannot evaluate dialect '${expr.dialect}'` },\n };\n }\n const source = expr.source;\n if (typeof source !== 'string' || source.length === 0) {\n // AST-only inputs: cel-js does not currently expose a public API to\n // re-execute a parsed AST without re-serializing. We persist `source`\n // as the canonical form during M9.1 and revisit AST-only execution in\n // M9.7 when we cut the spec persistence over.\n return {\n ok: false,\n error: { kind: 'parse', message: 'AST-only evaluation not yet supported; persist `source`' },\n };\n }\n\n const now = () => ctx.now ?? new Date();\n try {\n const env = buildEnv(now, ctx.timezone ?? 'UTC');\n const scope = buildScope(ctx);\n try {\n const raw = env.evaluate(source, scope);\n return { ok: true, value: coerce(raw) as T };\n } catch (err) {\n // ADR-0032 §1c — string-serialized fields make CEL raise\n // `no such overload`: numeric fields (`rating` → `\"5.0\"`,\n // `amount` → `\"250000.00\"`) on `record.rating >= 4` (#1534), and\n // date/datetime fields (`end_date` → `\"2026-06-20\"`) on\n // `record.end_date <= daysFromNow(60)` (#1530), since cel-js compares the\n // raw string against the `google.protobuf.Timestamp` from `today()` etc.\n // Hydrate those strings to number / Date and retry ONCE. This only runs\n // after a fault, so a comparison that already evaluated cleanly is never\n // re-interpreted; if the retry still cannot type-check, the original loud\n // error is reported.\n if (!isNumericOverloadError(err)) throw err;\n const hydrated = hydrateOverloadStrings(scope) as Record<string, unknown>;\n try {\n const raw = env.evaluate(source, hydrated);\n return { ok: true, value: coerce(raw) as T };\n } catch {\n // Hydration did not resolve it — surface the original fault, not the\n // retry's, so the message reflects what the author actually wrote.\n throw err;\n }\n }\n } catch (err) {\n return classifyError(err);\n }\n },\n};\n","/**\n * ObjectStack standard CEL function library.\n *\n * Registered into the per-evaluation `Environment` by the CEL engine. All\n * functions are pure given a pinned `now` — that determinism is what makes\n * `objectstack build` artifacts byte-stable across runs.\n *\n * Function naming intentionally avoids the `os.` prefix because cel-js binds\n * dotted names to receiver types. Instead, the `os` namespace in CEL holds\n * *data* (`os.user`, `os.org`, `os.env`) supplied by the caller's\n * {@link EvalContext}.\n */\n\nimport type { Environment } from '@marcbachmann/cel-js';\n\nimport type { EvalContext } from './types';\n\n/**\n * Calendar-day parts (y/m/d) of an instant *as seen in a timezone*\n * (ADR-0053 Phase 2). Uses `Intl.DateTimeFormat` so DST transitions are\n * handled correctly — never hand-rolled offset math. An unknown zone throws,\n * which the caller treats as a fall-through to UTC.\n */\nfunction partsInTz(d: Date, tz: string): { y: number; m: number; day: number } {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit',\n }).formatToParts(d);\n const get = (t: string) => Number(parts.find((p) => p.type === t)?.value);\n return { y: get('year'), m: get('month'), day: get('day') };\n}\n\n/**\n * The calendar day of an instant *in a reference timezone*, expressed as a\n * UTC-midnight `Date` (ADR-0053 Phase 2, decision D1). This is the one\n * representation consistent with how `Field.date` strings hydrate (UTC\n * midnight), how the SQL driver normalizes date filters, and how Phase 1\n * stores dates — so `record.date == today()` compares cleanly. Falls back to\n * the UTC calendar day for `UTC` or an invalid zone.\n */\nfunction calendarDayUtc(d: Date, tz: string): Date {\n if (tz && tz !== 'UTC') {\n try {\n const { y, m, day } = partsInTz(d, tz);\n return new Date(Date.UTC(y, m - 1, day));\n } catch {\n // unknown zone → fall through to UTC\n }\n }\n return startOfDayUtc(d);\n}\n\n/** Truncate a Date to start-of-day in UTC. */\nfunction startOfDayUtc(d: Date): Date {\n const out = new Date(d.getTime());\n out.setUTCHours(0, 0, 0, 0);\n return out;\n}\n\n/** Coerce a CEL value (Date | ISO string | epoch number) to a Date. */\nfunction toDate(v: unknown): Date {\n if (v instanceof Date) return v;\n if (typeof v === 'number' || typeof v === 'bigint') return new Date(Number(v));\n return new Date(String(v));\n}\n\n/** One UTC day in milliseconds. */\nconst MS_PER_DAY = 86_400_000;\n\n/** Add `n` days to a Date in UTC; returns a new Date. */\nfunction addDaysUtc(d: Date, n: number): Date {\n const out = new Date(d.getTime());\n out.setUTCDate(out.getUTCDate() + n);\n return out;\n}\n\n/**\n * Add `n` calendar months to a Date in UTC; returns a new Date. Clamps the day\n * to the target month's last day so `addMonths(date('2026-01-31'), 1)` yields\n * Feb 28, never an overflow into March — matching how authors expect a\n * \"next service date = last + N months\" rule to behave.\n */\nfunction addMonthsUtc(d: Date, n: number): Date {\n const out = new Date(d.getTime());\n const day = out.getUTCDate();\n out.setUTCDate(1); // avoid roll-over while shifting the month\n out.setUTCMonth(out.getUTCMonth() + n);\n const lastDay = new Date(Date.UTC(out.getUTCFullYear(), out.getUTCMonth() + 1, 0)).getUTCDate();\n out.setUTCDate(Math.min(day, lastDay));\n return out;\n}\n\n/**\n * Register the ObjectStack standard library into a CEL environment.\n *\n * The `now` resolver is closed over so each call uses the pinned\n * `EvalContext.now` (or wall-clock fallback). Implementations are kept tiny\n * and dependency-free — they're the contract surface for AI authors and must\n * stay legible.\n */\nexport function registerStdLib(\n env: Environment,\n now: () => Date,\n timezone = 'UTC',\n): Environment {\n // `today()` / `daysFromNow()` / `daysAgo()` are calendar-day functions: they\n // resolve to the reference-tz calendar day expressed as a UTC-midnight Date\n // (ADR-0053 Phase 2 D1), never an instant carrying wall-clock time. For a\n // genuine sub-day offset use `now() + duration(\"Nh\")`.\n return env\n .registerFunction('now(): google.protobuf.Timestamp', () => now())\n .registerFunction(\n 'today(): google.protobuf.Timestamp',\n () => calendarDayUtc(now(), timezone),\n )\n .registerFunction(\n 'daysFromNow(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(calendarDayUtc(now(), timezone), Number(n)),\n )\n .registerFunction(\n 'daysAgo(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(calendarDayUtc(now(), timezone), -Number(n)),\n )\n // Returns true when `value` is null, undefined, empty string, or empty list.\n // Matches the intent of legacy `ISBLANK()` while staying CEL-idiomatic.\n .registerFunction(\n 'isBlank(dyn): bool',\n (value: unknown) => {\n if (value === null || value === undefined) return true;\n if (typeof value === 'string') return value.length === 0;\n if (Array.isArray(value)) return value.length === 0;\n return false;\n },\n )\n // Returns `value` when not null/undefined, otherwise the `fallback`.\n // Use this to safely concatenate optional string fields:\n // coalesce(record.salutation, '') + ' ' + coalesce(record.first_name, '')\n .registerFunction(\n 'coalesce(dyn, dyn): dyn',\n (value: unknown, fallback: unknown) =>\n (value === null || value === undefined) ? fallback : value,\n )\n // Trim leading/trailing ASCII whitespace from a string. Returns '' for\n // null/undefined so it composes cleanly with `coalesce`.\n .registerFunction(\n 'trim(dyn): string',\n (value: unknown) => {\n if (value === null || value === undefined) return '';\n return String(value).trim();\n },\n )\n // Join a list of values with `sep`, dropping null/undefined/empty entries\n // first. Designed for display-name formulas like:\n // joinNonEmpty([record.salutation, record.first_name, record.last_name], ' ')\n // which produces 'Alice Martinez' (no leading/trailing/internal extra\n // spaces) when `salutation` is null.\n .registerFunction(\n 'joinNonEmpty(list, string): string',\n (list: unknown, sep: unknown) => {\n const arr = Array.isArray(list) ? list : [];\n const separator = typeof sep === 'string' ? sep : ' ';\n const parts: string[] = [];\n for (const item of arr) {\n if (item === null || item === undefined) continue;\n const s = String(item).trim();\n if (s.length > 0) parts.push(s);\n }\n return parts.join(separator);\n },\n )\n // ── Dates ────────────────────────────────────────────────────────────\n // Whole days from `a` to `b` (negative if `b` is before `a`). The common\n // shape is `daysBetween(today(), record.due)` for \"days remaining\". Args are\n // coerced (Date | ISO string | epoch) so a `Field.date` that arrives as a\n // string still works without the caller hydrating it.\n .registerFunction(\n 'daysBetween(dyn, dyn): int',\n (a: unknown, b: unknown) =>\n BigInt(Math.round((toDate(b).getTime() - toDate(a).getTime()) / MS_PER_DAY)),\n )\n // Shift an arbitrary date by a (possibly negative) number of days/months.\n // Unlike `daysFromNow`, these operate on a *given* date — the shape behind\n // \"next service date = last service + cycle\". Args are coerced (Date | ISO\n // string | epoch) so a `Field.date` arriving as a string works directly.\n // `addMonths` clamps to the target month's last day (Jan 31 +1mo → Feb 28).\n // `n` is typed `dyn` (not `int`): a record number field arrives as cel-js\n // `double`, so an `int` overload would fault `no such overload` (#1928).\n // We coerce defensively with `Number(...)`.\n .registerFunction(\n 'addDays(dyn, dyn): google.protobuf.Timestamp',\n (d: unknown, n: unknown) => addDaysUtc(toDate(d), Math.trunc(Number(n))),\n )\n .registerFunction(\n 'addMonths(dyn, dyn): google.protobuf.Timestamp',\n (d: unknown, n: unknown) => addMonthsUtc(toDate(d), Math.trunc(Number(n))),\n )\n // Parse an ISO date / date-time string to a Timestamp. `date` and `datetime`\n // are aliases — both accept either form (the field's own type decides the\n // intent); kept distinct because authors reach for whichever reads clearer.\n .registerFunction('date(dyn): google.protobuf.Timestamp', (s: unknown) => toDate(s))\n .registerFunction('datetime(dyn): google.protobuf.Timestamp', (s: unknown) => toDate(s))\n // ── Numbers ──────────────────────────────────────────────────────────\n .registerFunction('abs(dyn): double', (x: unknown) => Math.abs(Number(x)))\n .registerFunction('round(dyn): int', (x: unknown) => BigInt(Math.round(Number(x))))\n // min/max return the smaller/larger operand verbatim (type preserved) rather\n // than a coerced copy, so `min(record.a, record.b)` keeps int-ness when both\n // are ints. Comparison is numeric.\n .registerFunction('min(dyn, dyn): dyn', (a: unknown, b: unknown) => (Number(a) <= Number(b) ? a : b))\n .registerFunction('max(dyn, dyn): dyn', (a: unknown, b: unknown) => (Number(a) >= Number(b) ? a : b))\n // ── Strings ──────────────────────────────────────────────────────────\n // Free-function forms of the common string ops. CEL also exposes some as\n // receiver methods (`s.contains(x)`), but the authoring catalog advertises\n // the bare-call form, so register it to match what authors are told to use.\n .registerFunction('upper(dyn): string', (s: unknown) => String(s ?? '').toUpperCase())\n .registerFunction('lower(dyn): string', (s: unknown) => String(s ?? '').toLowerCase())\n .registerFunction('contains(dyn, dyn): bool', (s: unknown, sub: unknown) => String(s ?? '').includes(String(sub ?? '')))\n .registerFunction('startsWith(dyn, dyn): bool', (s: unknown, p: unknown) => String(s ?? '').startsWith(String(p ?? '')))\n .registerFunction('endsWith(dyn, dyn): bool', (s: unknown, p: unknown) => String(s ?? '').endsWith(String(p ?? '')))\n .registerFunction('matches(dyn, dyn): bool', (s: unknown, re: unknown) => new RegExp(String(re ?? '')).test(String(s ?? '')))\n // ── Collections ──────────────────────────────────────────────────────\n // `len` mirrors CEL's built-in `size()` for strings/lists/maps; `isEmpty` is\n // the inverse-of-non-empty companion to `isBlank` (true for null, '', []).\n .registerFunction('len(dyn): int', (v: unknown) => BigInt(lengthOf(v)))\n .registerFunction(\n 'isEmpty(dyn): bool',\n (v: unknown) => v === null || v === undefined || lengthOf(v) === 0,\n );\n}\n\n/** Length of a string / list / map (0 for scalars and null). */\nfunction lengthOf(v: unknown): number {\n if (v === null || v === undefined) return 0;\n if (typeof v === 'string' || Array.isArray(v)) return v.length;\n if (typeof v === 'object') return Object.keys(v as Record<string, unknown>).length;\n return 0;\n}\n\n/**\n * Register mixed `double <op> int` / `int <op> double` arithmetic overloads.\n *\n * cel-js types a record field number as `double` and a bare integer literal as\n * `int`, and ships overloads only for matching pairs (`double op double`,\n * `int op int`). So a formula as ordinary as `record.amount / 100` or\n * `record.price * 2` faults at runtime (`no such overload: dyn<double> / int`);\n * the engine catches the fault and the formula silently evaluates to `null`\n * (#1928). Authors then have to know the cel-js quirk and write `/ 100.0`.\n *\n * We close the gap by registering the missing mixed overloads. The result is\n * always computed as a JS `double`, matching CEL's promotion rule for mixed\n * numeric arithmetic. Pure `int op int` is untouched, so integer division\n * (`7 / 2 == 3`) keeps its semantics — these overloads only fire when the two\n * operands are genuinely a `double` and an `int`.\n */\nexport function registerNumericCoercions(env: Environment): Environment {\n const ops: Record<string, (a: number, b: number) => number> = {\n '+': (a, b) => a + b,\n '-': (a, b) => a - b,\n '*': (a, b) => a * b,\n '/': (a, b) => a / b,\n '%': (a, b) => a % b,\n };\n for (const [op, fn] of Object.entries(ops)) {\n const impl = (a: unknown, b: unknown) => fn(Number(a), Number(b));\n env.registerOperator(`double ${op} int`, impl);\n env.registerOperator(`int ${op} double`, impl);\n }\n return env;\n}\n\n/**\n * Build the variable scope for a single evaluation. Absent fields are simply\n * not bound — CEL macros (`has(record.foo)`) handle missing-key safely.\n */\nexport function buildScope(ctx: EvalContext): Record<string, unknown> {\n const scope: Record<string, unknown> = {};\n\n if (ctx.record !== undefined) scope.record = ctx.record;\n if (ctx.previous !== undefined) scope.previous = ctx.previous;\n if (ctx.input !== undefined) scope.input = ctx.input;\n\n // Namespaced data — written as `os.user.id`, `os.env`, etc. in CEL.\n const os: Record<string, unknown> = {};\n if (ctx.user !== undefined) os.user = ctx.user;\n if (ctx.org !== undefined) os.org = ctx.org;\n if (ctx.env !== undefined) os.env = ctx.env;\n if (Object.keys(os).length > 0) scope.os = os;\n\n if (ctx.extra !== undefined) Object.assign(scope, ctx.extra);\n\n return scope;\n}\n","/**\n * Cron dialect engine.\n *\n * Validates cron expressions at compile time without depending on a parser.\n * Actual schedule firing lives in the scheduler service — this engine just\n * round-trips the expression through `Expression.evaluate`, returning the\n * source so callers can hand it to a scheduler library.\n *\n * Accepted forms:\n * - 5-field standard cron: `m h dom mon dow`\n * - 6-field extended cron: `s m h dom mon dow`\n * - Aliases: @yearly, @annually, @monthly, @weekly, @daily, @hourly, @reboot\n */\n\nimport type { Expression } from '@objectstack/spec';\n\nimport type { DialectEngine, EvalContext, EvalResult } from './types';\n\nconst ALIASES = new Set([\n '@yearly', '@annually', '@monthly', '@weekly', '@daily', '@hourly', '@reboot',\n]);\n\nfunction validate(source: string): EvalResult<string> {\n const trimmed = source.trim();\n if (trimmed.length === 0) {\n return { ok: false, error: { kind: 'parse', message: 'cron source is empty' } };\n }\n if (trimmed.startsWith('@')) {\n if (!ALIASES.has(trimmed)) {\n return {\n ok: false,\n error: { kind: 'parse', message: `unknown cron alias '${trimmed}'` },\n };\n }\n return { ok: true, value: trimmed };\n }\n const fields = trimmed.split(/\\s+/);\n if (fields.length !== 5 && fields.length !== 6) {\n return {\n ok: false,\n error: {\n kind: 'parse',\n message: `cron requires 5 or 6 space-separated fields, got ${fields.length}`,\n },\n };\n }\n // Each field must use only allowed cron characters.\n const allowed = /^[\\d*/,\\-?LWA-Z#]+$/i;\n for (let i = 0; i < fields.length; i++) {\n if (!allowed.test(fields[i])) {\n return {\n ok: false,\n error: {\n kind: 'parse',\n message: `cron field ${i + 1} contains invalid characters: '${fields[i]}'`,\n },\n };\n }\n }\n return { ok: true, value: trimmed };\n}\n\nexport const cronEngine: DialectEngine = {\n dialect: 'cron',\n\n compile(source: string): EvalResult<unknown> {\n return validate(source);\n },\n\n evaluate<T = unknown>(expr: Expression, _ctx: EvalContext): EvalResult<T> {\n if (expr.dialect !== 'cron') {\n return {\n ok: false,\n error: { kind: 'dialect', message: `cronEngine cannot evaluate dialect '${expr.dialect}'` },\n };\n }\n if (typeof expr.source !== 'string') {\n return { ok: false, error: { kind: 'parse', message: 'cron Expression.source required' } };\n }\n const result = validate(expr.source);\n if (!result.ok) return result as EvalResult<T>;\n // Cron expressions don't \"evaluate\" to a value at predicate time — they\n // describe a schedule. Returning the source lets schedulers consume it.\n return { ok: true, value: result.value as unknown as T };\n },\n};\n","/**\n * Template dialect engine — strict Mustache subset with a formatter whitelist.\n *\n * Holes are `{{ path }}` or `{{ path | formatter[:'arg'] }}` (ADR-0032 §3).\n * Holes are restricted to a **field/variable path** plus a **whitelisted\n * formatter** — never arbitrary CEL logic — so the grammar stays small (low\n * author/agent error surface), GUI-pickable (path + formatter dropdown), and\n * display strings stay declarative. Real logic belongs in `Predicate`/`Expr`\n * (CEL) fields, where it is validated and visible.\n *\n * The variable scope is the same as CEL (`record`, `previous`, `input`,\n * `os.user/org/env`, plus `extra`), so authors move fluidly between a CEL\n * formula and a template body without re-learning a namespace.\n *\n * Value→string semantics are explicit and defined per formatter (numbers,\n * dates, money, percent, null), instead of implicit coercion.\n */\n\nimport type { Expression } from '@objectstack/spec';\n\nimport { buildScope } from './stdlib';\nimport type { DialectEngine, EvalContext, EvalResult } from './types';\n\n/**\n * A hole: capture the full inner content (no `}` allowed inside). Uses a single\n * greedy `[^}]*` (not `\\s*…\\s*` around a lazy group) so the pattern is linear —\n * `\\s` is a subset of `[^}]`, and wrapping a lazy group in `\\s*` creates an\n * ambiguous (polynomial-ReDoS) matcher. Surrounding whitespace is stripped in\n * `parseHole` instead.\n */\nconst HOLE_RE = /\\{\\{([^}]*)\\}\\}/g;\n\n// ───────────────────────── formatter whitelist (ADR-0032 §3) ──────────────\n\ntype Formatter = (\n value: unknown,\n arg: string | undefined,\n locale: string,\n timeZone?: string,\n) => string;\n\nfunction asNumber(v: unknown): number | undefined {\n if (typeof v === 'number') return v;\n if (typeof v === 'bigint') return Number(v);\n if (typeof v === 'string' && v.trim() !== '' && !Number.isNaN(Number(v))) return Number(v);\n return undefined;\n}\n\nfunction asDate(v: unknown): Date | undefined {\n if (v instanceof Date) return v;\n if (typeof v === 'number') return new Date(v);\n if (typeof v === 'string') {\n const d = new Date(v);\n if (!Number.isNaN(d.getTime())) return d;\n }\n return undefined;\n}\n\nconst FORMATTERS: Record<string, Formatter> = {\n upper: (v) => baseString(v).toUpperCase(),\n lower: (v) => baseString(v).toLowerCase(),\n trim: (v) => baseString(v).trim(),\n // number | number:2 → grouped, optional fixed decimals\n number: (v, arg, locale) => {\n const n = asNumber(v);\n if (n === undefined) return baseString(v);\n const digits = arg !== undefined ? Number(arg) : undefined;\n return new Intl.NumberFormat(locale, digits !== undefined && !Number.isNaN(digits)\n ? { minimumFractionDigits: digits, maximumFractionDigits: digits } : {}).format(n);\n },\n // currency | currency:EUR → defaults to USD\n currency: (v, arg, locale) => {\n const n = asNumber(v);\n if (n === undefined) return baseString(v);\n const code = (arg && arg.trim()) || 'USD';\n try {\n return new Intl.NumberFormat(locale, { style: 'currency', currency: code }).format(n);\n } catch {\n return new Intl.NumberFormat(locale, { style: 'currency', currency: 'USD' }).format(n);\n }\n },\n // percent | percent:1 → 0.42 → \"42%\" (value is a 0..1 ratio)\n percent: (v, arg, locale) => {\n const n = asNumber(v);\n if (n === undefined) return baseString(v);\n const digits = arg !== undefined ? Number(arg) : 0;\n return new Intl.NumberFormat(locale, {\n style: 'percent',\n minimumFractionDigits: Number.isNaN(digits) ? 0 : digits,\n maximumFractionDigits: Number.isNaN(digits) ? 0 : digits,\n }).format(n);\n },\n // date | date:long | date:iso → date-only. Intentionally tz-naive\n // (ADR-0053): a `Field.date` is a calendar day with no zone, so rendering\n // never applies a reference timezone — that would shift the day.\n date: (v, arg, locale) => {\n const d = asDate(v);\n if (!d) return baseString(v);\n if (arg === 'iso') return d.toISOString().slice(0, 10);\n const style = arg === 'long' ? 'long' : arg === 'medium' ? 'medium' : 'short';\n return new Intl.DateTimeFormat(locale, { dateStyle: style as 'short' | 'medium' | 'long' }).format(d);\n },\n // datetime | datetime:long | datetime:iso. A `datetime` is a UTC instant;\n // when a reference `timeZone` is supplied (ADR-0053 Phase 2) the wall-clock\n // styles render in that zone. `iso` stays UTC (machine-readable, unambiguous).\n datetime: (v, arg, locale, timeZone) => {\n const d = asDate(v);\n if (!d) return baseString(v);\n if (arg === 'iso') return d.toISOString();\n const style = arg === 'long' ? 'long' : arg === 'medium' ? 'medium' : 'short';\n return new Intl.DateTimeFormat(locale, {\n dateStyle: style as 'short' | 'medium' | 'long',\n timeStyle: style as 'short' | 'medium' | 'long',\n ...(timeZone ? { timeZone } : {}),\n }).format(d);\n },\n // truncate:80 → cut with an ellipsis\n truncate: (v, arg) => {\n const s = baseString(v);\n const len = arg !== undefined ? Number(arg) : 80;\n if (Number.isNaN(len) || s.length <= len) return s;\n return s.slice(0, Math.max(0, len - 1)) + '…';\n },\n // default:'N/A' → fallback when the value is null/undefined/empty\n default: (v, arg) => {\n const s = baseString(v);\n return s === '' ? (arg ?? '') : s;\n },\n json: (v) => {\n try { return JSON.stringify(v); } catch { return String(v); }\n },\n};\n\n/** Public list of whitelisted template formatters (for introspection/docs). */\nexport const TEMPLATE_FORMATTERS: string[] = Object.keys(FORMATTERS);\n\n/**\n * Apply a whitelisted formatter to a value, the single source of truth for\n * value→string semantics across dialects. Returns `undefined` for an unknown\n * formatter name so callers can decide how to handle it (the template engine\n * rejects at compile time; other consumers may pass the raw value through).\n *\n * Exported so renderers that don't run the full CEL template engine — notably\n * the email pipeline (ADR-0053 Phase 2 slice 4) — format dates, money, etc.\n * identically to in-app templates, including reference-timezone `datetime`.\n */\nexport function formatValue(\n name: string,\n value: unknown,\n arg: string | undefined,\n opts: { locale?: string; timeZone?: string } = {},\n): string | undefined {\n const fmt = FORMATTERS[name];\n if (!fmt) return undefined;\n return fmt(value, arg, opts.locale ?? 'en-US', opts.timeZone);\n}\n\nfunction baseString(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (typeof value === 'bigint') return value.toString();\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\nfunction resolvePath(scope: Record<string, unknown>, path: string): unknown {\n const normalized = path.replace(/\\[(\\w+)\\]/g, '.$1');\n const segments = normalized.split('.').filter(Boolean);\n let cursor: unknown = scope;\n for (const seg of segments) {\n if (cursor == null || typeof cursor !== 'object') return undefined;\n cursor = (cursor as Record<string, unknown>)[seg];\n }\n return cursor;\n}\n\ninterface ParsedHole {\n path: string;\n filter?: { name: string; arg?: string };\n}\n\nconst PATH_ONLY_RE = /^[\\w.[\\]]+$/;\n\n/**\n * Parse a hole's inner content into a path + optional single formatter.\n * Returns null when the inner content is not a valid path[+formatter] form\n * (e.g. arbitrary CEL was written into a hole — rejected, ADR-0032 §3).\n */\nfunction parseHole(inner: string): ParsedHole | null {\n const pipe = inner.indexOf('|');\n if (pipe === -1) {\n const path = inner.trim();\n return PATH_ONLY_RE.test(path) ? { path } : null;\n }\n const path = inner.slice(0, pipe).trim();\n if (!PATH_ONLY_RE.test(path)) return null;\n const filterPart = inner.slice(pipe + 1).trim();\n // `name` or `name:arg` or `name:'arg'`\n const colon = filterPart.indexOf(':');\n let name = filterPart;\n let arg: string | undefined;\n if (colon !== -1) {\n name = filterPart.slice(0, colon).trim();\n arg = filterPart.slice(colon + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n if (!FORMATTERS[name]) return null;\n return { path, filter: { name, arg } };\n}\n\nfunction compileTemplate(source: string): EvalResult<ParsedHole[]> {\n const open = (source.match(/\\{\\{/g) ?? []).length;\n const close = (source.match(/\\}\\}/g) ?? []).length;\n if (open !== close) {\n return { ok: false, error: { kind: 'parse', message: 'template has unbalanced {{ }} delimiters' } };\n }\n const holes: ParsedHole[] = [];\n let m: RegExpExecArray | null;\n HOLE_RE.lastIndex = 0;\n while ((m = HOLE_RE.exec(source)) !== null) {\n const parsed = parseHole(m[1]);\n if (!parsed) {\n return {\n ok: false,\n error: {\n kind: 'parse',\n message:\n `invalid template hole \\`{{ ${m[1]} }}\\` — holes are a field path with an optional ` +\n `formatter (\\`{{ record.amount | currency }}\\`), not arbitrary logic. ` +\n `Move logic into a CEL field. Known formatters: ${TEMPLATE_FORMATTERS.join(', ')}.`,\n },\n };\n }\n holes.push(parsed);\n }\n return { ok: true, value: holes };\n}\n\nexport const templateEngine: DialectEngine = {\n dialect: 'template',\n\n compile(source: string): EvalResult<unknown> {\n return compileTemplate(source);\n },\n\n evaluate<T = unknown>(expr: Expression, ctx: EvalContext): EvalResult<T> {\n if (expr.dialect !== 'template') {\n return {\n ok: false,\n error: { kind: 'dialect', message: `templateEngine cannot evaluate dialect '${expr.dialect}'` },\n };\n }\n if (typeof expr.source !== 'string') {\n return { ok: false, error: { kind: 'parse', message: 'template Expression.source required' } };\n }\n const check = compileTemplate(expr.source);\n if (!check.ok) return check as EvalResult<T>;\n\n const scope = buildScope(ctx);\n const locale =\n (ctx.extra && typeof ctx.extra.locale === 'string' && ctx.extra.locale) ||\n (typeof (ctx as { locale?: string }).locale === 'string' && (ctx as { locale?: string }).locale) ||\n 'en-US';\n // Reference timezone for `datetime` rendering (ADR-0053 Phase 2). Unset →\n // Intl uses the runtime zone, matching pre-Phase-2 behavior.\n const timeZone = typeof ctx.timezone === 'string' ? ctx.timezone : undefined;\n\n const out = expr.source.replace(HOLE_RE, (_match, inner) => {\n const parsed = parseHole(String(inner));\n if (!parsed) return _match; // compile already validated; defensive\n const value = resolvePath(scope, parsed.path);\n if (parsed.filter) {\n return FORMATTERS[parsed.filter.name](value, parsed.filter.arg, locale as string, timeZone);\n }\n return baseString(value);\n });\n return { ok: true, value: out as unknown as T };\n },\n};\n","/**\n * Dialect-pluggable Expression engine registry.\n *\n * Replaces the per-call-site `compileFormula` / `evaluateFormula` direct\n * imports of the deleted custom engine. Call sites now ask the registry to\n * dispatch by `expression.dialect`.\n *\n * Stub dialects (`js`, `cron`) are registered at module load with explicit\n * `dialect`-error responses so call sites get a clear message instead of\n * silent `undefined` (the old engine's anti-pattern).\n */\n\nimport type { Expression } from '@objectstack/spec';\n\nimport { celEngine } from './cel-engine';\nimport { cronEngine } from './cron-engine';\nimport { templateEngine } from './template-engine';\nimport type { DialectEngine, EvalContext, EvalResult } from './types';\n\nconst registry = new Map<string, DialectEngine>();\n\n/** Register or replace a dialect engine. */\nexport function register(engine: DialectEngine): void {\n registry.set(engine.dialect, engine);\n}\n\n/** Look up a dialect engine without dispatching. */\nexport function getEngine(dialect: string): DialectEngine | undefined {\n return registry.get(dialect);\n}\n\n/** Whether a dialect has a real (non-stub) implementation registered. */\nexport function hasDialect(dialect: string): boolean {\n return registry.has(dialect) && !registry.get(dialect)!.dialect.startsWith('stub:');\n}\n\nfunction makeStub(dialect: string, reason: string): DialectEngine {\n return {\n dialect,\n compile: () => ({ ok: false, error: { kind: 'dialect', message: reason } }),\n evaluate: () => ({ ok: false, error: { kind: 'dialect', message: reason } }),\n };\n}\n\n// Real engines.\nregister(celEngine);\nregister(cronEngine);\nregister(templateEngine);\n\n// Stubs — `js` lives in @objectstack/plugin-js-vm (not yet shipped).\nregister(makeStub('js', \"dialect 'js' not registered. Install @objectstack/plugin-js-vm\"));\n\n/**\n * The unified evaluation entry point. Replaces the old direct calls to\n * `evaluateFormula` from the deleted custom engine.\n */\nexport const ExpressionEngine = {\n register,\n getEngine,\n hasDialect,\n\n /**\n * Compile-only — parse + type-check, returning the engine-native AST. Used\n * by `objectstack compile` to normalize source into AST in artifacts.\n */\n compile(expr: Expression): EvalResult<unknown> {\n const engine = registry.get(expr.dialect);\n if (!engine) {\n return {\n ok: false,\n error: { kind: 'dialect', message: `No engine registered for dialect '${expr.dialect}'` },\n };\n }\n if (typeof expr.source !== 'string') {\n return {\n ok: false,\n error: { kind: 'parse', message: 'Expression.source required for compile()' },\n };\n }\n return engine.compile(expr.source);\n },\n\n /**\n * Evaluate an expression in the given context. Never throws — branch on\n * `result.ok`. Errors carry a `kind` for caller-side classification.\n */\n evaluate<T = unknown>(expr: Expression, ctx: EvalContext): EvalResult<T> {\n const engine = registry.get(expr.dialect);\n if (!engine) {\n return {\n ok: false,\n error: { kind: 'dialect', message: `No engine registered for dialect '${expr.dialect}'` },\n };\n }\n return engine.evaluate<T>(expr, ctx);\n },\n};\n","/**\n * Seed-value resolver.\n *\n * `Seed.records` accepts {@link SeedValue} = primitive | Expression | array\n * | object — install-time resolution walks the tree and replaces any\n * Expression node with its evaluated result. This is what makes\n * `close_date: cel\\`now() + duration(\"P30D\")\\`` resolve to *the customer's*\n * \"today + 30 days\" instead of the developer's compile-time clock.\n */\n\nimport { ExpressionSchema, type Expression } from '@objectstack/spec';\n\nimport type { EvalContext, EvalResult } from './types';\nimport { ExpressionEngine } from './registry';\n\nexport type SeedPrimitive = string | number | boolean | null | Date;\nexport type SeedValue = SeedPrimitive | Expression | SeedValue[] | { [key: string]: SeedValue };\n\n/** Detect an Expression-shaped object without throwing on unrelated shapes. */\nfunction isExpressionLike(value: unknown): value is Expression {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const v = value as Record<string, unknown>;\n if (typeof v.dialect !== 'string') return false;\n return ExpressionSchema.safeParse(v).success;\n}\n\n/**\n * Recursively resolve a SeedValue. Records that contain Expression leaves are\n * evaluated with `ctx`; other values are passed through unchanged.\n *\n * Returns the first failure encountered. Callers (seed loader) typically\n * abort the whole record on failure rather than silently writing partial data.\n */\nexport function resolveSeed(\n value: SeedValue,\n ctx: EvalContext,\n): EvalResult<unknown> {\n if (value === null || value === undefined) {\n return { ok: true, value };\n }\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return { ok: true, value };\n }\n if (value instanceof Date) {\n return { ok: true, value };\n }\n if (Array.isArray(value)) {\n const out: unknown[] = [];\n for (const item of value) {\n const r = resolveSeed(item, ctx);\n if (!r.ok) return r;\n out.push(r.value);\n }\n return { ok: true, value: out };\n }\n if (isExpressionLike(value)) {\n return ExpressionEngine.evaluate(value, ctx);\n }\n // Plain object — recurse field-by-field.\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, SeedValue>)) {\n const r = resolveSeed(v, ctx);\n if (!r.ok) return r;\n out[k] = r.value;\n }\n return { ok: true, value: out };\n}\n\n/**\n * Resolve a single record (object of fields), pinning `ctx.now` so all\n * expressions within see one logical clock.\n */\nexport function resolveSeedRecord(\n record: Record<string, SeedValue>,\n ctx: EvalContext,\n): EvalResult<Record<string, unknown>> {\n const pinnedCtx: EvalContext = { ...ctx, now: ctx.now ?? new Date() };\n const result = resolveSeed(record, pinnedCtx) as EvalResult<Record<string, unknown>>;\n return result;\n}\n","/**\n * Build-time normalization helpers.\n *\n * The CLI `objectstack compile` step walks the assembled `objectstack.json`\n * artifact and rewrites every Expression so that:\n *\n * 1. String shorthand input is replaced by `{ dialect: 'cel', source }`.\n * 2. The persisted envelope carries an `ast` field produced by the dialect\n * engine (M9.2 deliverable). Source is retained for round-trip / debug.\n *\n * Spec layer cannot do step 2 because it must remain dependency-free; this\n * package owns the engine import and therefore the AST step.\n */\n\nimport {\n ExpressionInputSchema,\n ExpressionSchema,\n type Expression,\n type ExpressionInput,\n} from '@objectstack/spec';\n\nimport { ExpressionEngine } from './registry';\nimport type { EvalResult } from './types';\n\n/**\n * Normalize an {@link ExpressionInput} (string shorthand OR full envelope) into\n * a fully-resolved {@link Expression} carrying both `source` and `ast`.\n *\n * Returns an EvalResult so the caller can render a structured compile error\n * pointing at the offending metadata path.\n */\nexport function normalizeExpression(input: ExpressionInput): EvalResult<Expression> {\n const parsed = ExpressionInputSchema.safeParse(input);\n if (!parsed.success) {\n return {\n ok: false,\n error: { kind: 'parse', message: parsed.error.message },\n };\n }\n\n const expr = parsed.data as Expression;\n\n // Already AST-only — accept as-is.\n if (expr.ast !== undefined && expr.source === undefined) {\n return { ok: true, value: expr };\n }\n\n // Source-bearing: ask the dialect engine to compile. Failures surface here\n // as part of the build (no silent skip).\n const compiled = ExpressionEngine.compile(expr);\n if (!compiled.ok) {\n return compiled;\n }\n\n return {\n ok: true,\n value: {\n ...expr,\n ast: compiled.value,\n },\n };\n}\n\n/**\n * Walk an arbitrary JSON tree and normalize every embedded Expression in\n * place. Used by the build pipeline to traverse the assembled metadata\n * artifact. Returns the first error encountered (paired with the dotted path\n * for diagnostics) or `null` when fully clean.\n */\nexport function normalizeExpressionTree(\n root: unknown,\n path: string[] = [],\n): { path: string; error: import('./types').EvalError } | null {\n if (root === null || typeof root !== 'object') return null;\n\n if (looksLikeExpression(root)) {\n const r = normalizeExpression(root as ExpressionInput);\n if (!r.ok) return { path: path.join('.'), error: r.error };\n Object.assign(root as Record<string, unknown>, r.value);\n return null;\n }\n\n if (Array.isArray(root)) {\n for (let i = 0; i < root.length; i++) {\n const r = normalizeExpressionTree(root[i], [...path, String(i)]);\n if (r) return r;\n }\n return null;\n }\n\n for (const [k, v] of Object.entries(root as Record<string, unknown>)) {\n const r = normalizeExpressionTree(v, [...path, k]);\n if (r) return r;\n }\n return null;\n}\n\nfunction looksLikeExpression(value: unknown): boolean {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const v = value as Record<string, unknown>;\n if (typeof v.dialect !== 'string') return false;\n return ExpressionSchema.safeParse(v).success;\n}\n","/**\n * Shared expression validator (ADR-0032 §Decision 1/5).\n *\n * One validator, used by every author surface — `objectstack build`,\n * `registerFlow`/metadata registration, and the agent-callable\n * `validate_expression` tool — so a malformed expression is caught the same\n * way everywhere, with a message written for **self-correction** (Decision 1d):\n * it states what is wrong AND the correct form.\n *\n * Field roles map to dialects (Decision 2):\n * - `predicate` → bare CEL returning bool (`record.rating >= 4`)\n * - `value` → bare CEL of any type (`daysFromNow(3)`)\n * - `template` → text with `{{ path }}` holes (`Hot lead: {{ record.name }}`)\n *\n * The #1 author error (human or LLM) is wrapping a field reference in single\n * `{…}` braces inside a CEL field — `{x}` parses as a CEL map literal and fails.\n * This validator detects that specific mistake and returns the exact fix.\n */\n\nimport { celEngine, firstUndeclaredReference } from './cel-engine';\nimport { templateEngine } from './template-engine';\n\nexport type FieldRole = 'predicate' | 'value' | 'template';\n\n/**\n * Loose input accepted by the validator: a bare string, or any object exposing\n * `dialect`/`source` (the Expression envelope, or a not-yet-narrowed value from\n * a `config.condition` / `edge.condition` field). Kept structural so call sites\n * need not pre-narrow to the strict {@link Expression} dialect union.\n */\nexport type ExprInput = string | { dialect?: string; source?: string } | null | undefined;\n\n/** Optional schema context for field-existence checks (Decision 1b, v1). */\nexport interface ExprSchemaHint {\n /** Object the expression is authored against (for error text). */\n objectName?: string;\n /** Known top-level field names, so `record.<field>` can be checked. */\n fields?: readonly string[];\n /**\n * Evaluation scope of the authoring site — determines whether a bare top-level\n * identifier is legal (#1928):\n * - `'record'` → the record is bound only as the `record` namespace, with\n * no field flattening (`Field.formula`, object validation\n * predicates). A bare `amount` resolves to nothing and the\n * expression silently evaluates to `null` / never fires, so\n * it MUST be written `record.amount`. We flag bare refs.\n * - `'flattened'` → the record's own fields are spread to top-level alongside\n * flow variables (flow / automation conditions), so bare\n * `status` is correct and is NOT an error. Flow variables\n * are not schema-knowable, so a non-field bare identifier\n * can't be soundly told apart from a typo — but when one is\n * a near-miss of a known field we emit a non-blocking\n * did-you-mean *warning*. (Default.)\n */\n scope?: 'record' | 'flattened';\n}\n\nexport interface ExprValidationError {\n /** Self-correcting message: what is wrong + the correct form. */\n message: string;\n /** The offending source, echoed for location. */\n source: string;\n}\n\nexport interface ExprValidationResult {\n ok: boolean;\n errors: ExprValidationError[];\n /**\n * Non-blocking advisories (#1928 tier 3): a likely-typo'd field reference in a\n * flattened flow condition. Never affects `ok` — callers surface these without\n * failing the build, since a bare identifier there may legitimately be a flow\n * variable.\n */\n warnings: ExprValidationError[];\n}\n\n/** A bare `{x}` that is NOT part of a `{{x}}` mustache hole. */\nconst SINGLE_BRACE_RE = /(?:^|[^{])\\{\\s*([A-Za-z_$][\\w.$]*)\\s*\\}(?!\\})/;\n/** `record.<field>` / `previous.<field>` head references for field-existence. */\nconst RECORD_REF_RE = /\\b(?:record|previous)\\.([A-Za-z_$][\\w$]*)/g;\n\n/** The dialect a field role expects (Decision 2). */\nexport function expectedDialect(role: FieldRole): 'cel' | 'template' {\n return role === 'template' ? 'template' : 'cel';\n}\n\nfunction toSource(input: ExprInput): { dialect?: string; source: string } {\n if (input == null) return { source: '' };\n if (typeof input === 'string') return { source: input };\n return { dialect: input.dialect, source: input.source ?? '' };\n}\n\nfunction bracesHint(source: string): string | null {\n const m = SINGLE_BRACE_RE.exec(source);\n if (!m) return null;\n const ref = m[1];\n return (\n `it looks like a \\`{${ref}}\\` template brace was used inside a CEL expression — ` +\n `\\`{…}\\` parses as a CEL map literal and fails. Write the bare reference instead, e.g. \\`${ref}\\`.`\n );\n}\n\nfunction checkFieldExistence(source: string, schema: ExprSchemaHint | undefined, errors: ExprValidationError[]): void {\n if (!schema?.fields || schema.fields.length === 0) return;\n const known = new Set(schema.fields);\n const seen = new Set<string>();\n let m: RegExpExecArray | null;\n RECORD_REF_RE.lastIndex = 0;\n while ((m = RECORD_REF_RE.exec(source)) !== null) {\n const field = m[1];\n if (seen.has(field) || known.has(field)) continue;\n seen.add(field);\n const suggestion = nearest(field, schema.fields);\n errors.push({\n source,\n message:\n `unknown field \\`${field}\\`${schema.objectName ? ` on \\`${schema.objectName}\\`` : ''}` +\n (suggestion ? ` — did you mean \\`${suggestion}\\`?` : ''),\n });\n }\n}\n\n/** Cheap edit-distance suggestion for typo'd field names. */\nfunction nearest(name: string, candidates: readonly string[]): string | undefined {\n let best: string | undefined;\n let bestD = Infinity;\n for (const c of candidates) {\n const d = levenshtein(name, c);\n if (d < bestD) { bestD = d; best = c; }\n }\n return bestD <= Math.max(2, Math.floor(name.length / 3)) ? best : undefined;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length, n = b.length;\n const dp = Array.from({ length: m + 1 }, (_, i) => i);\n for (let j = 1; j <= n; j++) {\n let prev = dp[0];\n dp[0] = j;\n for (let i = 1; i <= m; i++) {\n const tmp = dp[i];\n dp[i] = Math.min(dp[i] + 1, dp[i - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));\n prev = tmp;\n }\n }\n return dp[m];\n}\n\n/**\n * Validate one expression for a given field role. Never throws — returns a\n * structured result. Call sites decide whether to throw (build/registration)\n * or report (agent tool).\n */\nexport function validateExpression(\n role: FieldRole,\n input: ExprInput,\n schema?: ExprSchemaHint,\n): ExprValidationResult {\n const { dialect, source } = toSource(input);\n const errors: ExprValidationError[] = [];\n const warnings: ExprValidationError[] = [];\n if (!source.trim()) return { ok: true, errors, warnings };\n\n if (role === 'template') {\n // Templates must be the `template` dialect (or untyped string). Reject a\n // CEL envelope mistakenly placed in a text field.\n if (dialect && dialect !== 'template') {\n errors.push({ source, message: `expected a text template but got a \\`${dialect}\\` expression.` });\n return { ok: false, errors, warnings };\n }\n const compiled = templateEngine.compile(source);\n if (!compiled.ok) {\n errors.push({ source, message: `invalid template: ${compiled.error.message} (holes use \\`{{ path }}\\`).` });\n }\n // A single `{x}` in a template is the legacy/deprecated form (ADR-0032 §3).\n const hint = SINGLE_BRACE_RE.test(source) ? bracesHintForTemplate(source) : null;\n if (hint) errors.push({ source, message: hint });\n return { ok: errors.length === 0, errors, warnings };\n }\n\n // predicate | value → CEL\n if (dialect && dialect !== 'cel') {\n errors.push({ source, message: `expected a CEL expression but got a \\`${dialect}\\` dialect.` });\n return { ok: false, errors, warnings };\n }\n const compiled = celEngine.compile(source);\n if (!compiled.ok) {\n const hint = bracesHint(source);\n errors.push({\n source,\n message:\n `invalid CEL ${role}: ${compiled.error.message}` +\n (hint ? ` — ${hint}` : ` — ${role}s are bare CEL (e.g. \\`record.rating >= 4\\`).`),\n });\n } else {\n checkFieldExistence(source, schema, errors);\n if (schema?.scope === 'record') {\n // In a `record`-scoped site a bare top-level identifier is a silent bug —\n // it must be `record.<field>` (#1928). Hard error.\n const bare = firstUndeclaredReference(source);\n if (bare) {\n errors.push({\n source,\n message:\n `bare reference \\`${bare}\\` — a formula/validation expression binds the record as the ` +\n `\\`record\\` namespace, not at top level, so \\`${bare}\\` resolves to nothing and the ` +\n `expression silently evaluates to null. Write \\`record.${bare}\\`.`,\n });\n }\n } else if (schema?.fields && schema.fields.length > 0) {\n // Flattened flow/automation condition: the record's fields ARE bound at\n // top-level, so a bare ref is normally correct. But a *non-field* bare\n // identifier is either a flow variable or a typo. When it is a near-miss\n // of a known field, warn (did-you-mean) WITHOUT failing the build —\n // a genuine flow variable won't be edit-distance-close to a field. (#1928)\n const unknown = firstUndeclaredReference(source, schema.fields);\n if (unknown) {\n const suggestion = nearest(unknown, schema.fields);\n if (suggestion) {\n warnings.push({\n source,\n message:\n `\\`${unknown}\\` is not a field of \\`${schema.objectName ?? 'the trigger object'}\\` — ` +\n `did you mean \\`${suggestion}\\`? (flow conditions reference fields bare, e.g. \\`${suggestion} == …\\`). ` +\n `If \\`${unknown}\\` is a flow variable this is safe to ignore.`,\n });\n }\n }\n }\n }\n return { ok: errors.length === 0, errors, warnings };\n}\n\nfunction bracesHintForTemplate(source: string): string {\n const m = SINGLE_BRACE_RE.exec(source);\n const ref = m?.[1] ?? 'field';\n return `single-brace \\`{${ref}}\\` is not a valid template hole — use double braces: \\`{{ ${ref} }}\\`.`;\n}\n\n/**\n * Introspect what an author (esp. an agent) may use in a field (Decision 1e):\n * the expected dialect, the in-scope field references, and the callable\n * functions. Feeds the authoring context so the model does not guess.\n */\nexport function introspectScope(role: FieldRole, schema?: ExprSchemaHint): {\n dialect: 'cel' | 'template';\n fields: string[];\n roots: string[];\n functions: string[];\n} {\n return {\n dialect: expectedDialect(role),\n fields: [...(schema?.fields ?? [])],\n roots: ['record', 'previous', 'input', 'os', 'vars'],\n functions: CEL_STDLIB_FUNCTIONS,\n };\n}\n\n/**\n * Public catalog of CEL functions available in expressions — what `introspectScope`\n * advertises to authors (incl. AI). Every entry MUST actually resolve at runtime:\n * either registered in `registerStdLib` or a verified cel-js built-in. Drifting this\n * list ahead of the runtime tells the author to call functions that fault (#1928).\n */\nexport const CEL_STDLIB_FUNCTIONS: string[] = [\n // Dates (registered stdlib)\n 'now', 'today', 'daysFromNow', 'daysAgo', 'daysBetween', 'addDays', 'addMonths', 'date', 'datetime',\n // Numbers (registered stdlib)\n 'abs', 'round', 'min', 'max',\n // Strings (registered stdlib)\n 'upper', 'lower', 'trim', 'contains', 'startsWith', 'endsWith', 'matches', 'joinNonEmpty',\n // Collections / null-ish (registered stdlib)\n 'isBlank', 'isEmpty', 'coalesce', 'len',\n // cel-js built-ins (verified to resolve)\n 'size', 'has', 'int', 'string', 'bool', 'double', 'timestamp', 'duration',\n];\n"],"mappings":"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","names":["path","out","ExpressionSchema","ExpressionSchema","compiled"]}
|
|
1
|
+
{"version":3,"sources":["../src/cel-engine.ts","../src/stdlib.ts","../src/cron-engine.ts","../src/template-engine.ts","../src/registry.ts","../src/seed-eval.ts","../src/normalize.ts","../src/cel-to-filter.ts","../src/matches-filter.ts","../src/validate.ts"],"sourcesContent":["/**\n * CEL dialect engine — wraps `@marcbachmann/cel-js` with the ObjectStack\n * stdlib, bounded execution limits, and result coercion.\n *\n * Why a thin wrapper:\n *\n * - cel-js returns `BigInt` for ints. The kernel and CRM expect plain\n * numbers, so we coerce at the boundary.\n * - cel-js parses dotted names as receiver-typed methods; we register\n * `now()`, `today()`, `daysFromNow()` as bare functions and let `os.*`\n * refer to context data only (see {@link buildScope}).\n * - Bounds (`maxAstNodes`, `maxDepth`, …) are enforced spec-wide so\n * third-party plugins can't ship runaway predicates.\n */\n\nimport { Environment } from '@marcbachmann/cel-js';\nimport type { Expression } from '@objectstack/spec';\n\nimport { buildScope, registerNumericCoercions, registerStdLib } from './stdlib';\nimport type { DialectEngine, EvalContext, EvalResult } from './types';\n\n/**\n * Default execution bounds. Picked conservatively — every metadata-authored\n * expression we've seen is well under these. If you hit them, the expression\n * is too complex for ObjectStack and should be moved to a hook (`dialect: js`).\n */\nexport const DEFAULT_LIMITS = {\n maxAstNodes: 256,\n maxDepth: 32,\n maxListElements: 64,\n maxMapEntries: 64,\n maxCallArguments: 16,\n} as const;\n\nfunction buildEnv(now: () => Date, timezone = 'UTC'): Environment {\n const env = new Environment({\n unlistedVariablesAreDyn: true,\n enableOptionalTypes: true,\n limits: DEFAULT_LIMITS,\n });\n return registerNumericCoercions(registerStdLib(env, now, timezone));\n}\n\n/**\n * Namespace roots that a `record`-scoped CEL site may legitimately reference.\n * Declared as `map` (dyn values) so member access (`record.foo`) and any\n * arithmetic/comparison on it defers to runtime — the strict env faults ONLY on\n * an *undeclared* top-level identifier, i.e. a bare field reference. Generous on\n * purpose: an unknown root is a missed catch, a missing root is a false positive\n * that would break the build, so we err toward declaring more.\n */\nconst SCOPE_ROOTS = [\n 'record', 'previous', 'input', 'output', 'os', 'vars', 'variables',\n 'automation', 'context', 'args', 'item', 'env', 'user', 'step', 'result',\n 'trigger', 'event', 'payload', 'data', 'params', 'config', 'settings',\n // UI action / predicate context (ActionEngine, renderers): the current\n // record plus ambient globals exposed to `visible`/`disabled` predicates.\n 'ctx', 'features',\n // Master-detail inline grids inject the header record as `parent` for a\n // child field's `readonlyWhen`/`requiredWhen` predicate (ADR-0036, #1581).\n 'parent',\n] as const;\n\n/**\n * A `record`-scoped environment (`unlistedVariablesAreDyn: false`) for detecting\n * bare field references. It reuses the real stdlib so function calls don't fault;\n * only undeclared *variables* do. Built once — `parse`/`check` do not mutate it.\n */\nfunction buildScopedEnv(knownFields: readonly string[]): Environment {\n const env = new Environment({\n unlistedVariablesAreDyn: false,\n enableOptionalTypes: true,\n limits: DEFAULT_LIMITS,\n });\n registerStdLib(env, () => new Date(0));\n for (const root of SCOPE_ROOTS) {\n try { env.registerVariable(root, 'map'); } catch { /* duplicate — ignore */ }\n }\n // `knownFields` are declared as `dyn` so they (and member/arith/compare on\n // them) never fault — only a genuinely-undeclared top-level identifier does.\n // Empty for a record-scope site (any bare field is a bug); the trigger\n // object's fields for a flattened flow condition (only a NON-field bare ref —\n // a typo or flow variable — is then interesting).\n for (const field of knownFields) {\n try { env.registerVariable(field, 'dyn'); } catch { /* duplicate / reserved — ignore */ }\n }\n return env;\n}\n\n// Roots-only env reused for the common record-scope check (no per-call rebuild).\nlet recordScopeEnv: Environment | undefined;\n\n/**\n * In a `record`-scoped CEL site — a `Field.formula` or an object validation\n * predicate — the evaluation scope binds only the `record`/`previous`/… *namespaces*\n * (no field flattening). A bare top-level identifier like `amount` or `status`\n * therefore resolves to nothing and the expression silently evaluates to `null`\n * / never fires (#1928, the class behind #1927's broken formulas). Returns the\n * first such bare reference, or `null`.\n *\n * Acts ONLY on cel-js's `Unknown variable: X` fault, so it cannot false-positive\n * on arithmetic/comparison overloads — and it must NOT be applied to flow /\n * automation conditions, where the record's fields ARE flattened to top-level\n * and bare references are correct.\n */\nexport function firstUndeclaredReference(\n source: string,\n knownFields: readonly string[] = [],\n): string | null {\n if (typeof source !== 'string' || !source.trim()) return null;\n try {\n const env = knownFields.length === 0\n ? (recordScopeEnv ??= buildScopedEnv([]))\n : buildScopedEnv(knownFields);\n const result = env.parse(source).check?.() as\n | { valid: boolean; error?: { message?: string } }\n | undefined;\n if (result && result.valid === false) {\n const m = /Unknown variable:\\s*([A-Za-z_$][\\w$]*)/.exec(result.error?.message ?? '');\n if (m) return m[1];\n }\n } catch {\n // Parse/other faults are the syntax checker's job (celEngine.compile); this\n // helper only reports the undeclared-variable case.\n }\n return null;\n}\n\n/** @deprecated use {@link firstUndeclaredReference} with no fields. */\nexport function detectBareReference(source: string): string | null {\n return firstUndeclaredReference(source);\n}\n\n/** Coerce cel-js's BigInt-flavored return into spec-friendly JS values. */\nfunction coerce(value: unknown): unknown {\n if (typeof value === 'bigint') {\n // BigInt → number when safe, else string to avoid silent truncation.\n if (value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) {\n return Number(value);\n }\n return value.toString();\n }\n if (Array.isArray(value)) return value.map(coerce);\n if (value && typeof value === 'object' && !(value instanceof Date)) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) out[k] = coerce(v);\n return out;\n }\n return value;\n}\n\n/**\n * A string that is *entirely* a JS number literal: optional sign, integer\n * and/or fractional part, optional exponent. Deliberately strict — `\"5.0\"`,\n * `\"250000.00\"`, `\"-3\"`, `\"1e3\"` match; `\"5px\"`, `\"0x10\"`, `\" \"`, `\"\"`,\n * `\"1,000\"`, `\"v2\"` do not.\n */\n// The fractional part is a single optional `(?:\\.\\d*)?` group anchored by the\n// literal `.` — never the ambiguous `\\d+\\.?\\d*`, whose adjacent unbounded\n// quantifiers (`\\d+\\d*` when the dot is absent) backtrack polynomially on long\n// digit runs (CodeQL ReDoS). This matches the same strings without the hazard.\nconst NUMERIC_STRING_RE = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/;\n\n/**\n * A string that is an ISO-8601 date (`\"2026-06-20\"`) or date-time\n * (`\"2026-06-20T08:15:35.244Z\"`, `\"2026-06-20 08:15\"`, `\"...+02:00\"`). Strict\n * and anchored — no nested unbounded quantifiers, so no ReDoS hazard (every\n * sub-group is bounded or a single `\\.\\d+`). `Field.date` / `Field.datetime`\n * serialize to these; cel-js compares them as `string` and faults against the\n * `google.protobuf.Timestamp` returned by `today()` / `now()` / `daysFromNow()`.\n */\nconst ISO_TEMPORAL_STRING_RE =\n /^\\d{4}-\\d{2}-\\d{2}(?:[T ]\\d{2}:\\d{2}(?::\\d{2})?(?:\\.\\d+)?(?:Z|[+-]\\d{2}:?\\d{2})?)?$/;\n\n/**\n * cel-js raises `no such overload: dyn <op> int` (and kin) when a comparison\n * or arithmetic operator sees a `string` on one side and a number on the\n * other. ADR-0032 §1c — numeric fields that serialize as strings (`Field.rating`\n * → `\"5.0\"`, `Field.currency` → `\"250000.00\"`, `Field.percent`) trip this in\n * flow conditions / formulas (#1530, #1534) even though the schema and the\n * build-time validator treat them as numeric.\n */\nfunction isNumericOverloadError(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return /no such overload/i.test(message);\n}\n\n/**\n * Recursively coerce string values that faulted a CEL overload into their\n * intended primitive: entirely-numeric literals → `number` (#1534), and\n * ISO-8601 date / date-time strings → `Date` (cel-js `google.protobuf.Timestamp`)\n * (#1530). Used only on the {@link isNumericOverloadError} retry path, so it can\n * never change a comparison that already evaluated cleanly — it only rescues one\n * that already faulted. Strings that are neither (a zip like `\"02134\"`, free\n * text) pass through untouched; if the retry still cannot type-check, the\n * original loud error is preserved.\n */\nfunction hydrateOverloadStrings(value: unknown): unknown {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed.length > 0) {\n if (NUMERIC_STRING_RE.test(trimmed)) {\n const n = Number(trimmed);\n if (Number.isFinite(n)) return n;\n } else if (ISO_TEMPORAL_STRING_RE.test(trimmed)) {\n const ms = Date.parse(trimmed);\n if (!Number.isNaN(ms)) return new Date(ms);\n }\n }\n return value;\n }\n if (Array.isArray(value)) return value.map(hydrateOverloadStrings);\n if (value && typeof value === 'object' && !(value instanceof Date)) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) out[k] = hydrateOverloadStrings(v);\n return out;\n }\n return value;\n}\n\nfunction classifyError(err: unknown): EvalResult<never> {\n const message = err instanceof Error ? err.message : String(err);\n let kind: 'parse' | 'type' | 'runtime' | 'bounds' = 'runtime';\n if (/Exceeded max/i.test(message)) kind = 'bounds';\n else if (/parse|unexpected|syntax/i.test(message)) kind = 'parse';\n else if (/type|unknown variable|undeclared/i.test(message)) kind = 'type';\n return { ok: false, error: { kind, message } };\n}\n\nexport const celEngine: DialectEngine = {\n dialect: 'cel',\n\n compile(source: string): EvalResult<unknown> {\n try {\n // We use a wall-clock now() here purely for parse-time stdlib\n // type-checking; the function is never actually called.\n const env = buildEnv(() => new Date(0));\n const compiled = env.parse(source);\n // Surface check errors eagerly. cel-js's `check()` returns a\n // `TypeCheckResult` object (`{ valid, type?, error? }`) — NOT an array —\n // so the type fault (including `found no matching overload for 'PRIOR(dyn)'`\n // when a condition calls an UNKNOWN function) only surfaces when we read\n // `valid === false`. The previous `Array.isArray(...)` guard never matched\n // an object, so unknown-function predicates type-checked clean and were\n // silently accepted by `objectstack build` / `registerFlow`, then no-op'd\n // the flow at runtime (#1877). Reading the documented shape closes that.\n const checkResult = compiled.check?.();\n if (checkResult && checkResult.valid === false) {\n return {\n ok: false,\n error: { kind: 'type', message: checkResult.error?.message ?? 'expression failed type checking' },\n };\n }\n return { ok: true, value: compiled.ast };\n } catch (err) {\n return classifyError(err);\n }\n },\n\n evaluate<T = unknown>(expr: Expression, ctx: EvalContext): EvalResult<T> {\n if (expr.dialect !== 'cel') {\n return {\n ok: false,\n error: { kind: 'dialect', message: `celEngine cannot evaluate dialect '${expr.dialect}'` },\n };\n }\n const source = expr.source;\n if (typeof source !== 'string' || source.length === 0) {\n // AST-only inputs: cel-js does not currently expose a public API to\n // re-execute a parsed AST without re-serializing. We persist `source`\n // as the canonical form during M9.1 and revisit AST-only execution in\n // M9.7 when we cut the spec persistence over.\n return {\n ok: false,\n error: { kind: 'parse', message: 'AST-only evaluation not yet supported; persist `source`' },\n };\n }\n\n const now = () => ctx.now ?? new Date();\n try {\n const env = buildEnv(now, ctx.timezone ?? 'UTC');\n const scope = buildScope(ctx);\n try {\n const raw = env.evaluate(source, scope);\n return { ok: true, value: coerce(raw) as T };\n } catch (err) {\n // ADR-0032 §1c — string-serialized fields make CEL raise\n // `no such overload`: numeric fields (`rating` → `\"5.0\"`,\n // `amount` → `\"250000.00\"`) on `record.rating >= 4` (#1534), and\n // date/datetime fields (`end_date` → `\"2026-06-20\"`) on\n // `record.end_date <= daysFromNow(60)` (#1530), since cel-js compares the\n // raw string against the `google.protobuf.Timestamp` from `today()` etc.\n // Hydrate those strings to number / Date and retry ONCE. This only runs\n // after a fault, so a comparison that already evaluated cleanly is never\n // re-interpreted; if the retry still cannot type-check, the original loud\n // error is reported.\n if (!isNumericOverloadError(err)) throw err;\n const hydrated = hydrateOverloadStrings(scope) as Record<string, unknown>;\n try {\n const raw = env.evaluate(source, hydrated);\n return { ok: true, value: coerce(raw) as T };\n } catch {\n // Hydration did not resolve it — surface the original fault, not the\n // retry's, so the message reflects what the author actually wrote.\n throw err;\n }\n }\n } catch (err) {\n return classifyError(err);\n }\n },\n};\n","/**\n * ObjectStack standard CEL function library.\n *\n * Registered into the per-evaluation `Environment` by the CEL engine. All\n * functions are pure given a pinned `now` — that determinism is what makes\n * `objectstack build` artifacts byte-stable across runs.\n *\n * Function naming intentionally avoids the `os.` prefix because cel-js binds\n * dotted names to receiver types. Instead, the `os` namespace in CEL holds\n * *data* (`os.user`, `os.org`, `os.env`) supplied by the caller's\n * {@link EvalContext}.\n */\n\nimport type { Environment } from '@marcbachmann/cel-js';\n\nimport type { EvalContext } from './types';\n\n/**\n * Calendar-day parts (y/m/d) of an instant *as seen in a timezone*\n * (ADR-0053 Phase 2). Uses `Intl.DateTimeFormat` so DST transitions are\n * handled correctly — never hand-rolled offset math. An unknown zone throws,\n * which the caller treats as a fall-through to UTC.\n */\nfunction partsInTz(d: Date, tz: string): { y: number; m: number; day: number } {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit',\n }).formatToParts(d);\n const get = (t: string) => Number(parts.find((p) => p.type === t)?.value);\n return { y: get('year'), m: get('month'), day: get('day') };\n}\n\n/**\n * The calendar day of an instant *in a reference timezone*, expressed as a\n * UTC-midnight `Date` (ADR-0053 Phase 2, decision D1). This is the one\n * representation consistent with how `Field.date` strings hydrate (UTC\n * midnight), how the SQL driver normalizes date filters, and how Phase 1\n * stores dates — so `record.date == today()` compares cleanly. Falls back to\n * the UTC calendar day for `UTC` or an invalid zone.\n */\nfunction calendarDayUtc(d: Date, tz: string): Date {\n if (tz && tz !== 'UTC') {\n try {\n const { y, m, day } = partsInTz(d, tz);\n return new Date(Date.UTC(y, m - 1, day));\n } catch {\n // unknown zone → fall through to UTC\n }\n }\n return startOfDayUtc(d);\n}\n\n/** Truncate a Date to start-of-day in UTC. */\nfunction startOfDayUtc(d: Date): Date {\n const out = new Date(d.getTime());\n out.setUTCHours(0, 0, 0, 0);\n return out;\n}\n\n/** Coerce a CEL value (Date | ISO string | epoch number) to a Date. */\nfunction toDate(v: unknown): Date {\n if (v instanceof Date) return v;\n if (typeof v === 'number' || typeof v === 'bigint') return new Date(Number(v));\n return new Date(String(v));\n}\n\n/** One UTC day in milliseconds. */\nconst MS_PER_DAY = 86_400_000;\n\n/** Add `n` days to a Date in UTC; returns a new Date. */\nfunction addDaysUtc(d: Date, n: number): Date {\n const out = new Date(d.getTime());\n out.setUTCDate(out.getUTCDate() + n);\n return out;\n}\n\n/**\n * Add `n` calendar months to a Date in UTC; returns a new Date. Clamps the day\n * to the target month's last day so `addMonths(date('2026-01-31'), 1)` yields\n * Feb 28, never an overflow into March — matching how authors expect a\n * \"next service date = last + N months\" rule to behave.\n */\nfunction addMonthsUtc(d: Date, n: number): Date {\n const out = new Date(d.getTime());\n const day = out.getUTCDate();\n out.setUTCDate(1); // avoid roll-over while shifting the month\n out.setUTCMonth(out.getUTCMonth() + n);\n const lastDay = new Date(Date.UTC(out.getUTCFullYear(), out.getUTCMonth() + 1, 0)).getUTCDate();\n out.setUTCDate(Math.min(day, lastDay));\n return out;\n}\n\n/**\n * Register the ObjectStack standard library into a CEL environment.\n *\n * The `now` resolver is closed over so each call uses the pinned\n * `EvalContext.now` (or wall-clock fallback). Implementations are kept tiny\n * and dependency-free — they're the contract surface for AI authors and must\n * stay legible.\n */\nexport function registerStdLib(\n env: Environment,\n now: () => Date,\n timezone = 'UTC',\n): Environment {\n // `today()` / `daysFromNow()` / `daysAgo()` are calendar-day functions: they\n // resolve to the reference-tz calendar day expressed as a UTC-midnight Date\n // (ADR-0053 Phase 2 D1), never an instant carrying wall-clock time. For a\n // genuine sub-day offset use `now() + duration(\"Nh\")`.\n return env\n .registerFunction('now(): google.protobuf.Timestamp', () => now())\n .registerFunction(\n 'today(): google.protobuf.Timestamp',\n () => calendarDayUtc(now(), timezone),\n )\n .registerFunction(\n 'daysFromNow(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(calendarDayUtc(now(), timezone), Number(n)),\n )\n .registerFunction(\n 'daysAgo(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(calendarDayUtc(now(), timezone), -Number(n)),\n )\n // Returns true when `value` is null, undefined, empty string, or empty list.\n // Matches the intent of legacy `ISBLANK()` while staying CEL-idiomatic.\n .registerFunction(\n 'isBlank(dyn): bool',\n (value: unknown) => {\n if (value === null || value === undefined) return true;\n if (typeof value === 'string') return value.length === 0;\n if (Array.isArray(value)) return value.length === 0;\n return false;\n },\n )\n // Returns `value` when not null/undefined, otherwise the `fallback`.\n // Use this to safely concatenate optional string fields:\n // coalesce(record.salutation, '') + ' ' + coalesce(record.first_name, '')\n .registerFunction(\n 'coalesce(dyn, dyn): dyn',\n (value: unknown, fallback: unknown) =>\n (value === null || value === undefined) ? fallback : value,\n )\n // Trim leading/trailing ASCII whitespace from a string. Returns '' for\n // null/undefined so it composes cleanly with `coalesce`.\n .registerFunction(\n 'trim(dyn): string',\n (value: unknown) => {\n if (value === null || value === undefined) return '';\n return String(value).trim();\n },\n )\n // Join a list of values with `sep`, dropping null/undefined/empty entries\n // first. Designed for display-name formulas like:\n // joinNonEmpty([record.salutation, record.first_name, record.last_name], ' ')\n // which produces 'Alice Martinez' (no leading/trailing/internal extra\n // spaces) when `salutation` is null.\n .registerFunction(\n 'joinNonEmpty(list, string): string',\n (list: unknown, sep: unknown) => {\n const arr = Array.isArray(list) ? list : [];\n const separator = typeof sep === 'string' ? sep : ' ';\n const parts: string[] = [];\n for (const item of arr) {\n if (item === null || item === undefined) continue;\n const s = String(item).trim();\n if (s.length > 0) parts.push(s);\n }\n return parts.join(separator);\n },\n )\n // ── Dates ────────────────────────────────────────────────────────────\n // Whole days from `a` to `b` (negative if `b` is before `a`). The common\n // shape is `daysBetween(today(), record.due)` for \"days remaining\". Args are\n // coerced (Date | ISO string | epoch) so a `Field.date` that arrives as a\n // string still works without the caller hydrating it.\n .registerFunction(\n 'daysBetween(dyn, dyn): int',\n (a: unknown, b: unknown) =>\n BigInt(Math.round((toDate(b).getTime() - toDate(a).getTime()) / MS_PER_DAY)),\n )\n // Shift an arbitrary date by a (possibly negative) number of days/months.\n // Unlike `daysFromNow`, these operate on a *given* date — the shape behind\n // \"next service date = last service + cycle\". Args are coerced (Date | ISO\n // string | epoch) so a `Field.date` arriving as a string works directly.\n // `addMonths` clamps to the target month's last day (Jan 31 +1mo → Feb 28).\n // `n` is typed `dyn` (not `int`): a record number field arrives as cel-js\n // `double`, so an `int` overload would fault `no such overload` (#1928).\n // We coerce defensively with `Number(...)`.\n .registerFunction(\n 'addDays(dyn, dyn): google.protobuf.Timestamp',\n (d: unknown, n: unknown) => addDaysUtc(toDate(d), Math.trunc(Number(n))),\n )\n .registerFunction(\n 'addMonths(dyn, dyn): google.protobuf.Timestamp',\n (d: unknown, n: unknown) => addMonthsUtc(toDate(d), Math.trunc(Number(n))),\n )\n // Parse an ISO date / date-time string to a Timestamp. `date` and `datetime`\n // are aliases — both accept either form (the field's own type decides the\n // intent); kept distinct because authors reach for whichever reads clearer.\n .registerFunction('date(dyn): google.protobuf.Timestamp', (s: unknown) => toDate(s))\n .registerFunction('datetime(dyn): google.protobuf.Timestamp', (s: unknown) => toDate(s))\n // ── Numbers ──────────────────────────────────────────────────────────\n .registerFunction('abs(dyn): double', (x: unknown) => Math.abs(Number(x)))\n .registerFunction('round(dyn): int', (x: unknown) => BigInt(Math.round(Number(x))))\n // min/max return the smaller/larger operand verbatim (type preserved) rather\n // than a coerced copy, so `min(record.a, record.b)` keeps int-ness when both\n // are ints. Comparison is numeric.\n .registerFunction('min(dyn, dyn): dyn', (a: unknown, b: unknown) => (Number(a) <= Number(b) ? a : b))\n .registerFunction('max(dyn, dyn): dyn', (a: unknown, b: unknown) => (Number(a) >= Number(b) ? a : b))\n // ── Strings ──────────────────────────────────────────────────────────\n // Free-function forms of the common string ops. CEL also exposes some as\n // receiver methods (`s.contains(x)`), but the authoring catalog advertises\n // the bare-call form, so register it to match what authors are told to use.\n .registerFunction('upper(dyn): string', (s: unknown) => String(s ?? '').toUpperCase())\n .registerFunction('lower(dyn): string', (s: unknown) => String(s ?? '').toLowerCase())\n .registerFunction('contains(dyn, dyn): bool', (s: unknown, sub: unknown) => String(s ?? '').includes(String(sub ?? '')))\n .registerFunction('startsWith(dyn, dyn): bool', (s: unknown, p: unknown) => String(s ?? '').startsWith(String(p ?? '')))\n .registerFunction('endsWith(dyn, dyn): bool', (s: unknown, p: unknown) => String(s ?? '').endsWith(String(p ?? '')))\n .registerFunction('matches(dyn, dyn): bool', (s: unknown, re: unknown) => new RegExp(String(re ?? '')).test(String(s ?? '')))\n // ── Collections ──────────────────────────────────────────────────────\n // `len` mirrors CEL's built-in `size()` for strings/lists/maps; `isEmpty` is\n // the inverse-of-non-empty companion to `isBlank` (true for null, '', []).\n .registerFunction('len(dyn): int', (v: unknown) => BigInt(lengthOf(v)))\n .registerFunction(\n 'isEmpty(dyn): bool',\n (v: unknown) => v === null || v === undefined || lengthOf(v) === 0,\n );\n}\n\n/** Length of a string / list / map (0 for scalars and null). */\nfunction lengthOf(v: unknown): number {\n if (v === null || v === undefined) return 0;\n if (typeof v === 'string' || Array.isArray(v)) return v.length;\n if (typeof v === 'object') return Object.keys(v as Record<string, unknown>).length;\n return 0;\n}\n\n/**\n * Register mixed `double <op> int` / `int <op> double` arithmetic overloads.\n *\n * cel-js types a record field number as `double` and a bare integer literal as\n * `int`, and ships overloads only for matching pairs (`double op double`,\n * `int op int`). So a formula as ordinary as `record.amount / 100` or\n * `record.price * 2` faults at runtime (`no such overload: dyn<double> / int`);\n * the engine catches the fault and the formula silently evaluates to `null`\n * (#1928). Authors then have to know the cel-js quirk and write `/ 100.0`.\n *\n * We close the gap by registering the missing mixed overloads. The result is\n * always computed as a JS `double`, matching CEL's promotion rule for mixed\n * numeric arithmetic. Pure `int op int` is untouched, so integer division\n * (`7 / 2 == 3`) keeps its semantics — these overloads only fire when the two\n * operands are genuinely a `double` and an `int`.\n */\nexport function registerNumericCoercions(env: Environment): Environment {\n const ops: Record<string, (a: number, b: number) => number> = {\n '+': (a, b) => a + b,\n '-': (a, b) => a - b,\n '*': (a, b) => a * b,\n '/': (a, b) => a / b,\n '%': (a, b) => a % b,\n };\n for (const [op, fn] of Object.entries(ops)) {\n const impl = (a: unknown, b: unknown) => fn(Number(a), Number(b));\n env.registerOperator(`double ${op} int`, impl);\n env.registerOperator(`int ${op} double`, impl);\n }\n return env;\n}\n\n/**\n * Build the variable scope for a single evaluation. Absent fields are simply\n * not bound — CEL macros (`has(record.foo)`) handle missing-key safely.\n */\nexport function buildScope(ctx: EvalContext): Record<string, unknown> {\n const scope: Record<string, unknown> = {};\n\n if (ctx.record !== undefined) scope.record = ctx.record;\n if (ctx.previous !== undefined) scope.previous = ctx.previous;\n if (ctx.input !== undefined) scope.input = ctx.input;\n\n // Namespaced data — written as `os.user.id`, `os.env`, etc. in CEL.\n const os: Record<string, unknown> = {};\n if (ctx.user !== undefined) os.user = ctx.user;\n if (ctx.org !== undefined) os.org = ctx.org;\n if (ctx.env !== undefined) os.env = ctx.env;\n if (Object.keys(os).length > 0) scope.os = os;\n\n if (ctx.extra !== undefined) Object.assign(scope, ctx.extra);\n\n return scope;\n}\n","/**\n * Cron dialect engine.\n *\n * Validates cron expressions at compile time without depending on a parser.\n * Actual schedule firing lives in the scheduler service — this engine just\n * round-trips the expression through `Expression.evaluate`, returning the\n * source so callers can hand it to a scheduler library.\n *\n * Accepted forms:\n * - 5-field standard cron: `m h dom mon dow`\n * - 6-field extended cron: `s m h dom mon dow`\n * - Aliases: @yearly, @annually, @monthly, @weekly, @daily, @hourly, @reboot\n */\n\nimport type { Expression } from '@objectstack/spec';\n\nimport type { DialectEngine, EvalContext, EvalResult } from './types';\n\nconst ALIASES = new Set([\n '@yearly', '@annually', '@monthly', '@weekly', '@daily', '@hourly', '@reboot',\n]);\n\nfunction validate(source: string): EvalResult<string> {\n const trimmed = source.trim();\n if (trimmed.length === 0) {\n return { ok: false, error: { kind: 'parse', message: 'cron source is empty' } };\n }\n if (trimmed.startsWith('@')) {\n if (!ALIASES.has(trimmed)) {\n return {\n ok: false,\n error: { kind: 'parse', message: `unknown cron alias '${trimmed}'` },\n };\n }\n return { ok: true, value: trimmed };\n }\n const fields = trimmed.split(/\\s+/);\n if (fields.length !== 5 && fields.length !== 6) {\n return {\n ok: false,\n error: {\n kind: 'parse',\n message: `cron requires 5 or 6 space-separated fields, got ${fields.length}`,\n },\n };\n }\n // Each field must use only allowed cron characters.\n const allowed = /^[\\d*/,\\-?LWA-Z#]+$/i;\n for (let i = 0; i < fields.length; i++) {\n if (!allowed.test(fields[i])) {\n return {\n ok: false,\n error: {\n kind: 'parse',\n message: `cron field ${i + 1} contains invalid characters: '${fields[i]}'`,\n },\n };\n }\n }\n return { ok: true, value: trimmed };\n}\n\nexport const cronEngine: DialectEngine = {\n dialect: 'cron',\n\n compile(source: string): EvalResult<unknown> {\n return validate(source);\n },\n\n evaluate<T = unknown>(expr: Expression, _ctx: EvalContext): EvalResult<T> {\n if (expr.dialect !== 'cron') {\n return {\n ok: false,\n error: { kind: 'dialect', message: `cronEngine cannot evaluate dialect '${expr.dialect}'` },\n };\n }\n if (typeof expr.source !== 'string') {\n return { ok: false, error: { kind: 'parse', message: 'cron Expression.source required' } };\n }\n const result = validate(expr.source);\n if (!result.ok) return result as EvalResult<T>;\n // Cron expressions don't \"evaluate\" to a value at predicate time — they\n // describe a schedule. Returning the source lets schedulers consume it.\n return { ok: true, value: result.value as unknown as T };\n },\n};\n","/**\n * Template dialect engine — strict Mustache subset with a formatter whitelist.\n *\n * Holes are `{{ path }}` or `{{ path | formatter[:'arg'] }}` (ADR-0032 §3).\n * Holes are restricted to a **field/variable path** plus a **whitelisted\n * formatter** — never arbitrary CEL logic — so the grammar stays small (low\n * author/agent error surface), GUI-pickable (path + formatter dropdown), and\n * display strings stay declarative. Real logic belongs in `Predicate`/`Expr`\n * (CEL) fields, where it is validated and visible.\n *\n * The variable scope is the same as CEL (`record`, `previous`, `input`,\n * `os.user/org/env`, plus `extra`), so authors move fluidly between a CEL\n * formula and a template body without re-learning a namespace.\n *\n * Value→string semantics are explicit and defined per formatter (numbers,\n * dates, money, percent, null), instead of implicit coercion.\n */\n\nimport type { Expression } from '@objectstack/spec';\n\nimport { buildScope } from './stdlib';\nimport type { DialectEngine, EvalContext, EvalResult } from './types';\n\n/**\n * A hole: capture the full inner content (no `}` allowed inside). Uses a single\n * greedy `[^}]*` (not `\\s*…\\s*` around a lazy group) so the pattern is linear —\n * `\\s` is a subset of `[^}]`, and wrapping a lazy group in `\\s*` creates an\n * ambiguous (polynomial-ReDoS) matcher. Surrounding whitespace is stripped in\n * `parseHole` instead.\n */\nconst HOLE_RE = /\\{\\{([^}]*)\\}\\}/g;\n\n// ───────────────────────── formatter whitelist (ADR-0032 §3) ──────────────\n\ntype Formatter = (\n value: unknown,\n arg: string | undefined,\n locale: string,\n timeZone?: string,\n) => string;\n\nfunction asNumber(v: unknown): number | undefined {\n if (typeof v === 'number') return v;\n if (typeof v === 'bigint') return Number(v);\n if (typeof v === 'string' && v.trim() !== '' && !Number.isNaN(Number(v))) return Number(v);\n return undefined;\n}\n\nfunction asDate(v: unknown): Date | undefined {\n if (v instanceof Date) return v;\n if (typeof v === 'number') return new Date(v);\n if (typeof v === 'string') {\n const d = new Date(v);\n if (!Number.isNaN(d.getTime())) return d;\n }\n return undefined;\n}\n\nconst FORMATTERS: Record<string, Formatter> = {\n upper: (v) => baseString(v).toUpperCase(),\n lower: (v) => baseString(v).toLowerCase(),\n trim: (v) => baseString(v).trim(),\n // number | number:2 → grouped, optional fixed decimals\n number: (v, arg, locale) => {\n const n = asNumber(v);\n if (n === undefined) return baseString(v);\n const digits = arg !== undefined ? Number(arg) : undefined;\n return new Intl.NumberFormat(locale, digits !== undefined && !Number.isNaN(digits)\n ? { minimumFractionDigits: digits, maximumFractionDigits: digits } : {}).format(n);\n },\n // currency | currency:EUR → defaults to USD\n currency: (v, arg, locale) => {\n const n = asNumber(v);\n if (n === undefined) return baseString(v);\n const code = (arg && arg.trim()) || 'USD';\n try {\n return new Intl.NumberFormat(locale, { style: 'currency', currency: code }).format(n);\n } catch {\n return new Intl.NumberFormat(locale, { style: 'currency', currency: 'USD' }).format(n);\n }\n },\n // percent | percent:1 → 0.42 → \"42%\" (value is a 0..1 ratio)\n percent: (v, arg, locale) => {\n const n = asNumber(v);\n if (n === undefined) return baseString(v);\n const digits = arg !== undefined ? Number(arg) : 0;\n return new Intl.NumberFormat(locale, {\n style: 'percent',\n minimumFractionDigits: Number.isNaN(digits) ? 0 : digits,\n maximumFractionDigits: Number.isNaN(digits) ? 0 : digits,\n }).format(n);\n },\n // date | date:long | date:iso → date-only. Intentionally tz-naive\n // (ADR-0053): a `Field.date` is a calendar day with no zone, so rendering\n // never applies a reference timezone — that would shift the day.\n date: (v, arg, locale) => {\n const d = asDate(v);\n if (!d) return baseString(v);\n if (arg === 'iso') return d.toISOString().slice(0, 10);\n const style = arg === 'long' ? 'long' : arg === 'medium' ? 'medium' : 'short';\n return new Intl.DateTimeFormat(locale, { dateStyle: style as 'short' | 'medium' | 'long' }).format(d);\n },\n // datetime | datetime:long | datetime:iso. A `datetime` is a UTC instant;\n // when a reference `timeZone` is supplied (ADR-0053 Phase 2) the wall-clock\n // styles render in that zone. `iso` stays UTC (machine-readable, unambiguous).\n datetime: (v, arg, locale, timeZone) => {\n const d = asDate(v);\n if (!d) return baseString(v);\n if (arg === 'iso') return d.toISOString();\n const style = arg === 'long' ? 'long' : arg === 'medium' ? 'medium' : 'short';\n return new Intl.DateTimeFormat(locale, {\n dateStyle: style as 'short' | 'medium' | 'long',\n timeStyle: style as 'short' | 'medium' | 'long',\n ...(timeZone ? { timeZone } : {}),\n }).format(d);\n },\n // truncate:80 → cut with an ellipsis\n truncate: (v, arg) => {\n const s = baseString(v);\n const len = arg !== undefined ? Number(arg) : 80;\n if (Number.isNaN(len) || s.length <= len) return s;\n return s.slice(0, Math.max(0, len - 1)) + '…';\n },\n // default:'N/A' → fallback when the value is null/undefined/empty\n default: (v, arg) => {\n const s = baseString(v);\n return s === '' ? (arg ?? '') : s;\n },\n json: (v) => {\n try { return JSON.stringify(v); } catch { return String(v); }\n },\n};\n\n/** Public list of whitelisted template formatters (for introspection/docs). */\nexport const TEMPLATE_FORMATTERS: string[] = Object.keys(FORMATTERS);\n\n/**\n * Apply a whitelisted formatter to a value, the single source of truth for\n * value→string semantics across dialects. Returns `undefined` for an unknown\n * formatter name so callers can decide how to handle it (the template engine\n * rejects at compile time; other consumers may pass the raw value through).\n *\n * Exported so renderers that don't run the full CEL template engine — notably\n * the email pipeline (ADR-0053 Phase 2 slice 4) — format dates, money, etc.\n * identically to in-app templates, including reference-timezone `datetime`.\n */\nexport function formatValue(\n name: string,\n value: unknown,\n arg: string | undefined,\n opts: { locale?: string; timeZone?: string } = {},\n): string | undefined {\n const fmt = FORMATTERS[name];\n if (!fmt) return undefined;\n return fmt(value, arg, opts.locale ?? 'en-US', opts.timeZone);\n}\n\nfunction baseString(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (typeof value === 'bigint') return value.toString();\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\nfunction resolvePath(scope: Record<string, unknown>, path: string): unknown {\n const normalized = path.replace(/\\[(\\w+)\\]/g, '.$1');\n const segments = normalized.split('.').filter(Boolean);\n let cursor: unknown = scope;\n for (const seg of segments) {\n if (cursor == null || typeof cursor !== 'object') return undefined;\n cursor = (cursor as Record<string, unknown>)[seg];\n }\n return cursor;\n}\n\ninterface ParsedHole {\n path: string;\n filter?: { name: string; arg?: string };\n}\n\nconst PATH_ONLY_RE = /^[\\w.[\\]]+$/;\n\n/**\n * Parse a hole's inner content into a path + optional single formatter.\n * Returns null when the inner content is not a valid path[+formatter] form\n * (e.g. arbitrary CEL was written into a hole — rejected, ADR-0032 §3).\n */\nfunction parseHole(inner: string): ParsedHole | null {\n const pipe = inner.indexOf('|');\n if (pipe === -1) {\n const path = inner.trim();\n return PATH_ONLY_RE.test(path) ? { path } : null;\n }\n const path = inner.slice(0, pipe).trim();\n if (!PATH_ONLY_RE.test(path)) return null;\n const filterPart = inner.slice(pipe + 1).trim();\n // `name` or `name:arg` or `name:'arg'`\n const colon = filterPart.indexOf(':');\n let name = filterPart;\n let arg: string | undefined;\n if (colon !== -1) {\n name = filterPart.slice(0, colon).trim();\n arg = filterPart.slice(colon + 1).trim().replace(/^['\"]|['\"]$/g, '');\n }\n if (!FORMATTERS[name]) return null;\n return { path, filter: { name, arg } };\n}\n\nfunction compileTemplate(source: string): EvalResult<ParsedHole[]> {\n const open = (source.match(/\\{\\{/g) ?? []).length;\n const close = (source.match(/\\}\\}/g) ?? []).length;\n if (open !== close) {\n return { ok: false, error: { kind: 'parse', message: 'template has unbalanced {{ }} delimiters' } };\n }\n const holes: ParsedHole[] = [];\n let m: RegExpExecArray | null;\n HOLE_RE.lastIndex = 0;\n while ((m = HOLE_RE.exec(source)) !== null) {\n const parsed = parseHole(m[1]);\n if (!parsed) {\n return {\n ok: false,\n error: {\n kind: 'parse',\n message:\n `invalid template hole \\`{{ ${m[1]} }}\\` — holes are a field path with an optional ` +\n `formatter (\\`{{ record.amount | currency }}\\`), not arbitrary logic. ` +\n `Move logic into a CEL field. Known formatters: ${TEMPLATE_FORMATTERS.join(', ')}.`,\n },\n };\n }\n holes.push(parsed);\n }\n return { ok: true, value: holes };\n}\n\nexport const templateEngine: DialectEngine = {\n dialect: 'template',\n\n compile(source: string): EvalResult<unknown> {\n return compileTemplate(source);\n },\n\n evaluate<T = unknown>(expr: Expression, ctx: EvalContext): EvalResult<T> {\n if (expr.dialect !== 'template') {\n return {\n ok: false,\n error: { kind: 'dialect', message: `templateEngine cannot evaluate dialect '${expr.dialect}'` },\n };\n }\n if (typeof expr.source !== 'string') {\n return { ok: false, error: { kind: 'parse', message: 'template Expression.source required' } };\n }\n const check = compileTemplate(expr.source);\n if (!check.ok) return check as EvalResult<T>;\n\n const scope = buildScope(ctx);\n const locale =\n (ctx.extra && typeof ctx.extra.locale === 'string' && ctx.extra.locale) ||\n (typeof (ctx as { locale?: string }).locale === 'string' && (ctx as { locale?: string }).locale) ||\n 'en-US';\n // Reference timezone for `datetime` rendering (ADR-0053 Phase 2). Unset →\n // Intl uses the runtime zone, matching pre-Phase-2 behavior.\n const timeZone = typeof ctx.timezone === 'string' ? ctx.timezone : undefined;\n\n const out = expr.source.replace(HOLE_RE, (_match, inner) => {\n const parsed = parseHole(String(inner));\n if (!parsed) return _match; // compile already validated; defensive\n const value = resolvePath(scope, parsed.path);\n if (parsed.filter) {\n return FORMATTERS[parsed.filter.name](value, parsed.filter.arg, locale as string, timeZone);\n }\n return baseString(value);\n });\n return { ok: true, value: out as unknown as T };\n },\n};\n","/**\n * Dialect-pluggable Expression engine registry.\n *\n * Replaces the per-call-site `compileFormula` / `evaluateFormula` direct\n * imports of the deleted custom engine. Call sites now ask the registry to\n * dispatch by `expression.dialect`.\n *\n * Stub dialects (`js`, `cron`) are registered at module load with explicit\n * `dialect`-error responses so call sites get a clear message instead of\n * silent `undefined` (the old engine's anti-pattern).\n */\n\nimport type { Expression } from '@objectstack/spec';\n\nimport { celEngine } from './cel-engine';\nimport { cronEngine } from './cron-engine';\nimport { templateEngine } from './template-engine';\nimport type { DialectEngine, EvalContext, EvalResult } from './types';\n\nconst registry = new Map<string, DialectEngine>();\n\n/** Register or replace a dialect engine. */\nexport function register(engine: DialectEngine): void {\n registry.set(engine.dialect, engine);\n}\n\n/** Look up a dialect engine without dispatching. */\nexport function getEngine(dialect: string): DialectEngine | undefined {\n return registry.get(dialect);\n}\n\n/** Whether a dialect has a real (non-stub) implementation registered. */\nexport function hasDialect(dialect: string): boolean {\n return registry.has(dialect) && !registry.get(dialect)!.dialect.startsWith('stub:');\n}\n\nfunction makeStub(dialect: string, reason: string): DialectEngine {\n return {\n dialect,\n compile: () => ({ ok: false, error: { kind: 'dialect', message: reason } }),\n evaluate: () => ({ ok: false, error: { kind: 'dialect', message: reason } }),\n };\n}\n\n// Real engines.\nregister(celEngine);\nregister(cronEngine);\nregister(templateEngine);\n\n// Stubs — `js` lives in @objectstack/plugin-js-vm (not yet shipped).\nregister(makeStub('js', \"dialect 'js' not registered. Install @objectstack/plugin-js-vm\"));\n\n/**\n * The unified evaluation entry point. Replaces the old direct calls to\n * `evaluateFormula` from the deleted custom engine.\n */\nexport const ExpressionEngine = {\n register,\n getEngine,\n hasDialect,\n\n /**\n * Compile-only — parse + type-check, returning the engine-native AST. Used\n * by `objectstack compile` to normalize source into AST in artifacts.\n */\n compile(expr: Expression): EvalResult<unknown> {\n const engine = registry.get(expr.dialect);\n if (!engine) {\n return {\n ok: false,\n error: { kind: 'dialect', message: `No engine registered for dialect '${expr.dialect}'` },\n };\n }\n if (typeof expr.source !== 'string') {\n return {\n ok: false,\n error: { kind: 'parse', message: 'Expression.source required for compile()' },\n };\n }\n return engine.compile(expr.source);\n },\n\n /**\n * Evaluate an expression in the given context. Never throws — branch on\n * `result.ok`. Errors carry a `kind` for caller-side classification.\n */\n evaluate<T = unknown>(expr: Expression, ctx: EvalContext): EvalResult<T> {\n const engine = registry.get(expr.dialect);\n if (!engine) {\n return {\n ok: false,\n error: { kind: 'dialect', message: `No engine registered for dialect '${expr.dialect}'` },\n };\n }\n return engine.evaluate<T>(expr, ctx);\n },\n};\n","/**\n * Seed-value resolver.\n *\n * `Seed.records` accepts {@link SeedValue} = primitive | Expression | array\n * | object — install-time resolution walks the tree and replaces any\n * Expression node with its evaluated result. This is what makes\n * `close_date: cel\\`now() + duration(\"P30D\")\\`` resolve to *the customer's*\n * \"today + 30 days\" instead of the developer's compile-time clock.\n */\n\nimport { ExpressionSchema, type Expression } from '@objectstack/spec';\n\nimport type { EvalContext, EvalResult } from './types';\nimport { ExpressionEngine } from './registry';\n\nexport type SeedPrimitive = string | number | boolean | null | Date;\nexport type SeedValue = SeedPrimitive | Expression | SeedValue[] | { [key: string]: SeedValue };\n\n/** Detect an Expression-shaped object without throwing on unrelated shapes. */\nfunction isExpressionLike(value: unknown): value is Expression {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const v = value as Record<string, unknown>;\n if (typeof v.dialect !== 'string') return false;\n return ExpressionSchema.safeParse(v).success;\n}\n\n/**\n * Recursively resolve a SeedValue. Records that contain Expression leaves are\n * evaluated with `ctx`; other values are passed through unchanged.\n *\n * Returns the first failure encountered. Callers (seed loader) typically\n * abort the whole record on failure rather than silently writing partial data.\n */\nexport function resolveSeed(\n value: SeedValue,\n ctx: EvalContext,\n): EvalResult<unknown> {\n if (value === null || value === undefined) {\n return { ok: true, value };\n }\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return { ok: true, value };\n }\n if (value instanceof Date) {\n return { ok: true, value };\n }\n if (Array.isArray(value)) {\n const out: unknown[] = [];\n for (const item of value) {\n const r = resolveSeed(item, ctx);\n if (!r.ok) return r;\n out.push(r.value);\n }\n return { ok: true, value: out };\n }\n if (isExpressionLike(value)) {\n return ExpressionEngine.evaluate(value, ctx);\n }\n // Plain object — recurse field-by-field.\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, SeedValue>)) {\n const r = resolveSeed(v, ctx);\n if (!r.ok) return r;\n out[k] = r.value;\n }\n return { ok: true, value: out };\n}\n\n/**\n * Resolve a single record (object of fields), pinning `ctx.now` so all\n * expressions within see one logical clock.\n */\nexport function resolveSeedRecord(\n record: Record<string, SeedValue>,\n ctx: EvalContext,\n): EvalResult<Record<string, unknown>> {\n const pinnedCtx: EvalContext = { ...ctx, now: ctx.now ?? new Date() };\n const result = resolveSeed(record, pinnedCtx) as EvalResult<Record<string, unknown>>;\n return result;\n}\n","/**\n * Build-time normalization helpers.\n *\n * The CLI `objectstack compile` step walks the assembled `objectstack.json`\n * artifact and rewrites every Expression so that:\n *\n * 1. String shorthand input is replaced by `{ dialect: 'cel', source }`.\n * 2. The persisted envelope carries an `ast` field produced by the dialect\n * engine (M9.2 deliverable). Source is retained for round-trip / debug.\n *\n * Spec layer cannot do step 2 because it must remain dependency-free; this\n * package owns the engine import and therefore the AST step.\n */\n\nimport {\n ExpressionInputSchema,\n ExpressionSchema,\n type Expression,\n type ExpressionInput,\n} from '@objectstack/spec';\n\nimport { ExpressionEngine } from './registry';\nimport type { EvalResult } from './types';\n\n/**\n * Normalize an {@link ExpressionInput} (string shorthand OR full envelope) into\n * a fully-resolved {@link Expression} carrying both `source` and `ast`.\n *\n * Returns an EvalResult so the caller can render a structured compile error\n * pointing at the offending metadata path.\n */\nexport function normalizeExpression(input: ExpressionInput): EvalResult<Expression> {\n const parsed = ExpressionInputSchema.safeParse(input);\n if (!parsed.success) {\n return {\n ok: false,\n error: { kind: 'parse', message: parsed.error.message },\n };\n }\n\n const expr = parsed.data as Expression;\n\n // Already AST-only — accept as-is.\n if (expr.ast !== undefined && expr.source === undefined) {\n return { ok: true, value: expr };\n }\n\n // Source-bearing: ask the dialect engine to compile. Failures surface here\n // as part of the build (no silent skip).\n const compiled = ExpressionEngine.compile(expr);\n if (!compiled.ok) {\n return compiled;\n }\n\n return {\n ok: true,\n value: {\n ...expr,\n ast: compiled.value,\n },\n };\n}\n\n/**\n * Walk an arbitrary JSON tree and normalize every embedded Expression in\n * place. Used by the build pipeline to traverse the assembled metadata\n * artifact. Returns the first error encountered (paired with the dotted path\n * for diagnostics) or `null` when fully clean.\n */\nexport function normalizeExpressionTree(\n root: unknown,\n path: string[] = [],\n): { path: string; error: import('./types').EvalError } | null {\n if (root === null || typeof root !== 'object') return null;\n\n if (looksLikeExpression(root)) {\n const r = normalizeExpression(root as ExpressionInput);\n if (!r.ok) return { path: path.join('.'), error: r.error };\n Object.assign(root as Record<string, unknown>, r.value);\n return null;\n }\n\n if (Array.isArray(root)) {\n for (let i = 0; i < root.length; i++) {\n const r = normalizeExpressionTree(root[i], [...path, String(i)]);\n if (r) return r;\n }\n return null;\n }\n\n for (const [k, v] of Object.entries(root as Record<string, unknown>)) {\n const r = normalizeExpressionTree(v, [...path, k]);\n if (r) return r;\n }\n return null;\n}\n\nfunction looksLikeExpression(value: unknown): boolean {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const v = value as Record<string, unknown>;\n if (typeof v.dialect !== 'string') return false;\n return ExpressionSchema.safeParse(v).success;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Canonical CEL → FilterCondition pushdown compiler (ADR-0058 D1/D2/D6).\n *\n * ObjectStack has ONE authoring language (CEL) and ONE good interpreter\n * (`cel-engine.ts`), but historically THREE disconnected \"compile-to-filter\"\n * front-ends: `plugin-security/rls-compiler.ts`'s 4-form regex, `plugin-sharing`'s\n * `celToFilter`, and the ObjectUI array-AST path. They diverged — which is the\n * root of #1887 (a sharing `condition` that the interpreter understands but no\n * compiler lowers, so it never enforces).\n *\n * This module is the single, canonical lowering. It takes the **same parsed\n * `@marcbachmann/cel-js` AST the interpreter uses** (`env.parse(src).ast`) and\n * lowers the pushdown-able subset to a Mongo-style {@link FilterCondition} — the\n * one shape BOTH backends already consume: the ObjectQL engine `where` (AND-injected\n * by plugin-security) and the analytics SQL backend\n * (`service-analytics/read-scope-sql.ts`). One AST, two backends (D6).\n *\n * ## Supported subset (ADR-0058 D2)\n * `==` `!=` `>` `<` `>=` `<=` · `in` (→ `$in`) · `&&` `||` `!` ·\n * `== null` / `!= null` (→ `$null`) · string methods `startsWith` / `endsWith`\n * / `contains` (→ `$startsWith` / `$endsWith` / `$contains`).\n * `not in` is `!(x in y)`. Negation wraps in `$not`.\n *\n * ## Hard boundaries (ADR-0055 stands)\n * - **No subqueries, no cross-object traversal.** A field path is a SINGLE\n * column (`record.region` → `region`, bare `owner` → `owner`). A multi-segment\n * relation path (`record.account.region`) is an authoring-time compile error,\n * not a silent join.\n * - Arithmetic (`+ - * / %`), function calls (`size(...)`), ternary, maps, and\n * any other non-pushdown shape are a compile error — NEVER silently dropped.\n * A dropped predicate leaves an object unprotected; failing closed is the\n * security-correct outcome (ADR-0049/0056 D4).\n *\n * ## Value resolution\n * A leaf rooted at a `variableRoot` (default `current_user`) is resolved against\n * `opts.variables` to a literal — `current_user.id` → the caller's id,\n * `current_user.org_user_ids` → a pre-resolved membership array for `$in`\n * (honours ADR-0055: the runtime pre-resolves the set; the compiler never emits\n * a subquery). A variable that resolves to `undefined`/`null` yields\n * `unresolved-variable` (the \"no active org\" fail-closed path).\n */\n\nimport { Environment } from '@marcbachmann/cel-js';\nimport type { ASTNode } from '@marcbachmann/cel-js';\nimport type { FilterCondition } from '@objectstack/spec/data';\n\n// ---------------------------------------------------------------------------\n// Public contract\n// ---------------------------------------------------------------------------\n\nexport type CelFilterFailReason =\n /** CEL did not parse (syntax error). */\n | 'parse-error'\n /** Shape is not pushdown-able (arithmetic, function call, relation traversal, …). */\n | 'unsupported'\n /** A required `variableRoot` reference was undefined/null in `variables`. */\n | 'unresolved-variable';\n\nexport type CelFilterCompileResult =\n | { ok: true; filter: FilterCondition }\n | { ok: false; reason: CelFilterFailReason; detail: string };\n\nexport interface CelFilterCompileOptions {\n /** Member-access roots that denote a record FIELD path. Default `['record']`. */\n fieldRoots?: readonly string[];\n /** Roots resolved as VALUES against {@link variables}. Default `['current_user']`. */\n variableRoots?: readonly string[];\n /**\n * Value-resolution context, keyed by variable root. e.g.\n * `{ current_user: { id, organization_id, org_user_ids } }`. A `record.*`\n * (field) reference is NEVER resolved here — only `variableRoot` leaves are.\n */\n variables?: Record<string, unknown>;\n}\n\n/** Symbol returned for a variable leaf during a shape-only check (never executed). */\nconst SHAPE_VALUE = Symbol('cel-filter-shape-placeholder');\n\nclass CompileError extends Error {\n constructor(public reason: CelFilterFailReason, message: string) {\n super(message);\n this.name = 'CelFilterCompileError';\n }\n}\n\n// A roots-permissive env: parsing is purely syntactic (we read `.ast`, never\n// `.check()`/`.evaluate()`), so any identifier or method call parses. Built once.\nlet parseEnv: Environment | undefined;\nfunction getParseEnv(): Environment {\n if (!parseEnv) {\n parseEnv = new Environment({ unlistedVariablesAreDyn: true, enableOptionalTypes: true });\n }\n return parseEnv;\n}\n\n/** Unwrap a CEL expression input — accepts a raw string or `{ source }`. */\nfunction toSource(input: string | { source?: string } | null | undefined): string | null {\n if (typeof input === 'string') return input.trim() || null;\n if (input && typeof input === 'object' && typeof input.source === 'string') {\n return input.source.trim() || null;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Entry points\n// ---------------------------------------------------------------------------\n\n/**\n * Compile a CEL predicate into a {@link FilterCondition}, resolving `variableRoot`\n * leaves against `opts.variables`. Returns a discriminated result — never throws\n * for an authoring-level fault; a `false` result with a reason is the caller's\n * cue to fail closed (deny) or surface a compile error.\n */\nexport function compileCelToFilter(\n input: string | { source?: string },\n opts: CelFilterCompileOptions = {},\n): CelFilterCompileResult {\n const source = toSource(input);\n if (!source) return { ok: false, reason: 'parse-error', detail: 'empty expression' };\n let ast: ASTNode;\n try {\n ast = getParseEnv().parse(source).ast;\n } catch (err) {\n return { ok: false, reason: 'parse-error', detail: (err as Error).message?.split('\\n')[0] ?? 'parse error' };\n }\n return lowerCelAst(ast, opts, 'value');\n}\n\n/**\n * Shape-only check: is this CEL predicate pushdown-able at all? Used by the\n * authoring gate (ADR-0056 D4) to REJECT a predicate the runtime could only\n * silently drop. Does not resolve `variables`.\n */\nexport function isPushdownableCel(\n input: string | { source?: string },\n opts: Pick<CelFilterCompileOptions, 'fieldRoots' | 'variableRoots'> = {},\n): { ok: true } | { ok: false; reason: CelFilterFailReason; detail: string } {\n const source = toSource(input);\n if (!source) return { ok: false, reason: 'parse-error', detail: 'empty expression' };\n let ast: ASTNode;\n try {\n ast = getParseEnv().parse(source).ast;\n } catch (err) {\n return { ok: false, reason: 'parse-error', detail: (err as Error).message?.split('\\n')[0] ?? 'parse error' };\n }\n const res = lowerCelAst(ast, opts, 'shape');\n return res.ok ? { ok: true } : { ok: false, reason: res.reason, detail: res.detail };\n}\n\n/**\n * Lower a pre-parsed cel-js AST node — the variant that lets the interpreter and\n * the compiler share ONE parse (ADR-0058 D6, \"one AST, two backends\").\n */\nexport function lowerCelAst(\n ast: ASTNode,\n opts: CelFilterCompileOptions = {},\n mode: 'value' | 'shape' = 'value',\n): CelFilterCompileResult {\n const ctx: Ctx = {\n fieldRoots: new Set(opts.fieldRoots ?? ['record']),\n variableRoots: new Set(opts.variableRoots ?? ['current_user']),\n variables: opts.variables ?? {},\n mode,\n };\n try {\n return { ok: true, filter: lowerCondition(ast, ctx) };\n } catch (err) {\n if (err instanceof CompileError) return { ok: false, reason: err.reason, detail: err.message };\n return { ok: false, reason: 'unsupported', detail: (err as Error).message ?? 'compile error' };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\ninterface Ctx {\n fieldRoots: Set<string>;\n variableRoots: Set<string>;\n variables: Record<string, unknown>;\n mode: 'value' | 'shape';\n}\n\ntype Leaf =\n | { kind: 'field'; path: string }\n | { kind: 'literal'; value: unknown }\n | { kind: 'var'; path: string[] };\n\nconst FLIP: Record<string, string> = { '>': '<', '<': '>', '>=': '<=', '<=': '>=', '==': '==', '!=': '!=' };\nconst CMP_OP: Record<string, string> = { '>': '$gt', '>=': '$gte', '<': '$lt', '<=': '$lte' };\nconst STRING_METHOD: Record<string, string> = { startsWith: '$startsWith', endsWith: '$endsWith', contains: '$contains' };\n\n/** Lower a boolean-valued node into a FilterCondition. Throws CompileError. */\nfunction lowerCondition(node: ASTNode, ctx: Ctx): FilterCondition {\n const op = node.op;\n const args = node.args as unknown;\n switch (op) {\n case '&&':\n return combine('$and', node, ctx);\n case '||':\n return combine('$or', node, ctx);\n case '!_':\n return { $not: lowerCondition(args as ASTNode, ctx) };\n case '==':\n case '!=':\n case '>':\n case '>=':\n case '<':\n case '<=':\n return lowerComparison(op, (args as [ASTNode, ASTNode])[0], (args as [ASTNode, ASTNode])[1], ctx);\n case 'in':\n return lowerMembership((args as [ASTNode, ASTNode])[0], (args as [ASTNode, ASTNode])[1], ctx);\n case 'rcall':\n return lowerStringMethod(args as [string, ASTNode, ASTNode[]], ctx);\n case 'value': {\n // A bare boolean condition. `true` → no restriction; anything else fails\n // closed (we never let a non-true constant become allow-all).\n const v = coerceLiteral(args);\n if (v === true) return {};\n throw new CompileError('unsupported', `constant non-true predicate (${String(v)})`);\n }\n default:\n throw new CompileError('unsupported', `unsupported operator \"${String(op)}\"`);\n }\n}\n\nfunction combine(key: '$and' | '$or', node: ASTNode, ctx: Ctx): FilterCondition {\n const [l, r] = node.args as [ASTNode, ASTNode];\n const parts: FilterCondition[] = [];\n for (const child of [lowerCondition(l, ctx), lowerCondition(r, ctx)]) {\n // Flatten same-key nesting so `a && b && c` is one `$and: [a,b,c]`.\n const nested = (child as Record<string, unknown>)[key];\n if (Array.isArray(nested) && Object.keys(child).length === 1) parts.push(...(nested as FilterCondition[]));\n else parts.push(child);\n }\n return { [key]: parts } as FilterCondition;\n}\n\nfunction lowerComparison(op: string, lNode: ASTNode, rNode: ASTNode, ctx: Ctx): FilterCondition {\n const L = classify(lNode, ctx);\n const R = classify(rNode, ctx);\n const lField = L.kind === 'field';\n const rField = R.kind === 'field';\n\n if (lField && rField) {\n // field-to-field comparison → `{ $field: otherPath }` reference.\n return emit((L as { path: string }).path, op, { $field: (R as { path: string }).path }, true);\n }\n if (lField) return emit((L as { path: string }).path, op, resolveValue(R, ctx), false);\n if (rField) return emit((R as { path: string }).path, FLIP[op] ?? op, resolveValue(L, ctx), false);\n\n // Neither side is a field: a constant comparison. Fold the always-true case\n // (`1 == 1`, the RLS allow-all) to \"no restriction\"; refuse the rest (a\n // non-true constant must fail closed, never become allow-all).\n const lv = resolveValue(L, ctx);\n const rv = resolveValue(R, ctx);\n if (ctx.mode === 'shape') return {}; // shape check: structurally fine\n const truth = constFold(op, lv, rv);\n if (truth === true) return {};\n throw new CompileError('unsupported', `constant ${op} predicate that is not always-true`);\n}\n\nfunction lowerMembership(elemNode: ASTNode, containerNode: ASTNode, ctx: Ctx): FilterCondition {\n const elem = classify(elemNode, ctx);\n if (elem.kind !== 'field') {\n throw new CompileError('unsupported', `\\`in\\` requires a field on the left (got ${elem.kind})`);\n }\n const container = classify(containerNode, ctx);\n const value = resolveValue(container, ctx);\n if (value !== SHAPE_VALUE && !Array.isArray(value)) {\n throw new CompileError('unsupported', `\\`in\\` requires an array/list on the right`);\n }\n return { [(elem as { path: string }).path]: { $in: value } } as FilterCondition;\n}\n\nfunction lowerStringMethod(args: [string, ASTNode, ASTNode[]], ctx: Ctx): FilterCondition {\n const [method, receiver, callArgs] = args;\n const mapped = STRING_METHOD[method];\n if (!mapped) throw new CompileError('unsupported', `unsupported method \"${method}()\"`);\n const recv = classify(receiver, ctx);\n if (recv.kind !== 'field') throw new CompileError('unsupported', `\"${method}()\" must be called on a field`);\n if (!Array.isArray(callArgs) || callArgs.length !== 1) {\n throw new CompileError('unsupported', `\"${method}()\" takes exactly one argument`);\n }\n const arg = resolveValue(classify(callArgs[0], ctx), ctx);\n if (arg !== SHAPE_VALUE && typeof arg !== 'string') {\n throw new CompileError('unsupported', `\"${method}()\" argument must be a string literal`);\n }\n return { [(recv as { path: string }).path]: { [mapped]: arg } } as FilterCondition;\n}\n\n/** Build `{ field: <op> value }`. `isRef` true → value is a `{ $field }` reference. */\nfunction emit(field: string, op: string, value: unknown, isRef: boolean): FilterCondition {\n if (op === '==') {\n if (!isRef && value === null) return { [field]: { $null: true } } as FilterCondition;\n if (isRef) return { [field]: { $eq: value } } as FilterCondition;\n return { [field]: value } as FilterCondition; // implicit equality\n }\n if (op === '!=') {\n if (!isRef && value === null) return { [field]: { $null: false } } as FilterCondition;\n return { [field]: { $ne: value } } as FilterCondition;\n }\n const cmp = CMP_OP[op];\n if (cmp) return { [field]: { [cmp]: value } } as FilterCondition;\n throw new CompileError('unsupported', `unsupported comparison \"${op}\"`);\n}\n\n/** Syntactically classify an operand node. Throws on a non-pushdown shape. */\nfunction classify(node: ASTNode, ctx: Ctx): Leaf {\n switch (node.op) {\n case 'value':\n return { kind: 'literal', value: coerceLiteral(node.args) };\n case 'list': {\n const items = (node.args as ASTNode[]).map((n) => {\n const leaf = classify(n, ctx);\n if (leaf.kind !== 'literal') {\n throw new CompileError('unsupported', 'list elements must be literals');\n }\n return leaf.value;\n });\n return { kind: 'literal', value: items };\n }\n case 'id': {\n const name = node.args as string;\n if (ctx.variableRoots.has(name)) return { kind: 'var', path: [name] };\n // Bare identifier = a single record field (RLS convention).\n return { kind: 'field', path: name };\n }\n case '.':\n case '.?': {\n const [recv, field] = node.args as [ASTNode, string];\n const chain = memberChain(recv, field);\n if (!chain) throw new CompileError('unsupported', 'unsupported member-access expression');\n const [root, ...rest] = chain;\n if (ctx.variableRoots.has(root)) return { kind: 'var', path: chain };\n if (ctx.fieldRoots.has(root)) {\n if (rest.length !== 1) {\n // `record.account.region` = cross-object traversal (ADR-0055): refuse.\n throw new CompileError('unsupported', `cross-object/nested field path \"${chain.join('.')}\" is not pushdown-able`);\n }\n return { kind: 'field', path: rest[0] };\n }\n // A `.`-chain rooted at an unknown identifier = relation traversal.\n throw new CompileError('unsupported', `cross-object field path \"${chain.join('.')}\" is not pushdown-able`);\n }\n default:\n throw new CompileError('unsupported', `unsupported operand \"${String(node.op)}\"`);\n }\n}\n\n/** Flatten a `.`-member chain into `[root, seg, seg, …]`, or null if not a pure path. */\nfunction memberChain(recv: ASTNode, field: string): string[] | null {\n if (recv.op === 'id') return [recv.args as string, field];\n if (recv.op === '.' || recv.op === '.?') {\n const [innerRecv, innerField] = recv.args as [ASTNode, string];\n const inner = memberChain(innerRecv, innerField);\n return inner ? [...inner, field] : null;\n }\n return null;\n}\n\n/** Resolve a leaf to its VALUE (literal directly; var via `variables`). */\nfunction resolveValue(leaf: Leaf, ctx: Ctx): unknown {\n if (leaf.kind === 'literal') return leaf.value;\n if (leaf.kind === 'field') {\n throw new CompileError('unsupported', `expected a value but got field \"${leaf.path}\"`);\n }\n // var\n if (ctx.mode === 'shape') return SHAPE_VALUE;\n let cur: unknown = ctx.variables;\n for (const seg of leaf.path) {\n if (cur == null || typeof cur !== 'object') {\n throw new CompileError('unresolved-variable', `variable \"${leaf.path.join('.')}\" is not resolvable`);\n }\n cur = (cur as Record<string, unknown>)[seg];\n }\n if (cur === undefined || cur === null) {\n throw new CompileError('unresolved-variable', `variable \"${leaf.path.join('.')}\" is ${String(cur)}`);\n }\n return cur;\n}\n\n/** Coerce a cel-js literal to a plain JS value (cel-js uses BigInt for ints). */\nfunction coerceLiteral(v: unknown): unknown {\n if (typeof v === 'bigint') return Number(v);\n if (v === null || typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') return v;\n // `v` is already non-null here (the line above returns for null), so a\n // further `v !== null` would be a dead comparison; rely on the early return.\n if (typeof v === 'object' && typeof (v as { valueOf?: unknown }).valueOf === 'function') {\n const prim = (v as { valueOf: () => unknown }).valueOf();\n if (typeof prim === 'bigint') return Number(prim);\n if (typeof prim === 'number' || typeof prim === 'string' || typeof prim === 'boolean') return prim;\n }\n throw new CompileError('unsupported', `unsupported literal type \"${typeof v}\"`);\n}\n\n/** Compile-time fold of a comparison between two concrete values. */\nfunction constFold(op: string, l: unknown, r: unknown): boolean | undefined {\n switch (op) {\n case '==': return l === r;\n case '!=': return l !== r;\n case '>': return (l as number) > (r as number);\n case '>=': return (l as number) >= (r as number);\n case '<': return (l as number) < (r as number);\n case '<=': return (l as number) <= (r as number);\n default: return undefined;\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * matchesFilterCondition — evaluate a Mongo-style {@link FilterCondition} against\n * ONE in-memory record (ADR-0058 D4/D6).\n *\n * This is the third backend for the canonical filter shape, completing the\n * round-trip: `compileCelToFilter` lowers CEL → FilterCondition; the engine runs\n * it as a `where`; `read-scope-sql` lowers it to SQL; and THIS evaluates it\n * against a single record for write-side validation — the RLS `check` clause\n * (post-image of an insert/update), where there is no query to push down to.\n *\n * Security posture: **fail closed.** Anything it cannot evaluate — a malformed\n * node, an unknown operator, a nested relation object a flat record can't\n * satisfy — returns `false` (the write is denied), never `true`. The operator\n * vocabulary mirrors `read-scope-sql.ts` so the in-memory and SQL backends agree.\n */\n\nimport type { FilterCondition } from '@objectstack/spec/data';\n\n/** True iff `record` satisfies `filter`. A null/empty filter matches everything. */\nexport function matchesFilterCondition(record: Record<string, unknown>, filter: FilterCondition | null | undefined): boolean {\n if (filter == null) return true;\n if (typeof filter !== 'object' || Array.isArray(filter)) return false;\n return evalNode(record, filter as Record<string, unknown>);\n}\n\nfunction evalNode(record: Record<string, unknown>, node: Record<string, unknown>): boolean {\n // A node is the AND of all its entries.\n for (const [key, val] of Object.entries(node)) {\n if (key === '$and') {\n if (!Array.isArray(val) || !val.every((c) => evalNode(record, c as Record<string, unknown>))) return false;\n } else if (key === '$or') {\n if (!Array.isArray(val) || val.length === 0 || !val.some((c) => evalNode(record, c as Record<string, unknown>))) return false;\n } else if (key === '$not') {\n if (val == null || typeof val !== 'object') return false;\n if (evalNode(record, val as Record<string, unknown>)) return false;\n } else if (key.startsWith('$')) {\n return false; // unknown top-level operator → fail closed\n } else {\n if (!evalField(record, key, val)) return false;\n }\n }\n return true;\n}\n\nfunction evalField(record: Record<string, unknown>, field: string, spec: unknown): boolean {\n const actual = getPath(record, field);\n // `{ field: null }` → IS NULL.\n if (spec === null) return actual == null;\n // Scalar / Date → implicit equality.\n if (typeof spec !== 'object' || spec instanceof Date) return looseEq(actual, spec);\n // A bare array value is not a valid field spec (must be `{ $in: [...] }`).\n if (Array.isArray(spec)) return false;\n\n const ops = spec as Record<string, unknown>;\n const keys = Object.keys(ops);\n // Must be all-operators; a non-`$` key means a nested relation a flat record\n // cannot satisfy → fail closed.\n if (keys.length === 0 || keys.some((k) => !k.startsWith('$'))) return false;\n for (const op of keys) {\n if (!evalOp(actual, op, ops[op], record)) return false;\n }\n return true;\n}\n\nfunction evalOp(actual: unknown, op: string, raw: unknown, record: Record<string, unknown>): boolean {\n const v = resolveValue(raw, record);\n switch (op) {\n case '$eq': return v === null ? actual == null : looseEq(actual, v);\n case '$ne': return v === null ? actual != null : !looseEq(actual, v);\n case '$gt': return actual != null && v != null && (actual as never) > (v as never);\n case '$gte': return actual != null && v != null && (actual as never) >= (v as never);\n case '$lt': return actual != null && v != null && (actual as never) < (v as never);\n case '$lte': return actual != null && v != null && (actual as never) <= (v as never);\n case '$in': return Array.isArray(v) && v.some((x) => looseEq(actual, x));\n case '$nin': return Array.isArray(v) && !v.some((x) => looseEq(actual, x));\n case '$between':\n return Array.isArray(v) && v.length === 2 && actual != null\n && (actual as never) >= (v[0] as never) && (actual as never) <= (v[1] as never);\n case '$contains': return typeof actual === 'string' && typeof v === 'string' && actual.includes(v);\n case '$notContains': return !(typeof actual === 'string' && typeof v === 'string' && actual.includes(v));\n case '$startsWith': return typeof actual === 'string' && typeof v === 'string' && actual.startsWith(v);\n case '$endsWith': return typeof actual === 'string' && typeof v === 'string' && actual.endsWith(v);\n case '$null': return v === true ? actual == null : actual != null;\n case '$exists': return v === true ? actual !== undefined : actual === undefined;\n default: return false; // unknown operator → fail closed\n }\n}\n\n/** Resolve a `{ $field: 'path' }` reference against the record; else passthrough. */\nfunction resolveValue(raw: unknown, record: Record<string, unknown>): unknown {\n if (raw && typeof raw === 'object' && !Array.isArray(raw) && '$field' in (raw as Record<string, unknown>)) {\n return getPath(record, String((raw as Record<string, unknown>).$field));\n }\n return raw;\n}\n\nfunction getPath(record: Record<string, unknown>, path: string): unknown {\n if (!path.includes('.')) return record[path];\n let cur: unknown = record;\n for (const seg of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined;\n cur = (cur as Record<string, unknown>)[seg];\n }\n return cur;\n}\n\n/** Equality that treats Dates by time-value; otherwise strict. */\nfunction looseEq(a: unknown, b: unknown): boolean {\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (a instanceof Date && (typeof b === 'string' || typeof b === 'number')) return a.getTime() === new Date(b).getTime();\n if (b instanceof Date && (typeof a === 'string' || typeof a === 'number')) return new Date(a).getTime() === b.getTime();\n return a === b;\n}\n","/**\n * Shared expression validator (ADR-0032 §Decision 1/5).\n *\n * One validator, used by every author surface — `objectstack build`,\n * `registerFlow`/metadata registration, and the agent-callable\n * `validate_expression` tool — so a malformed expression is caught the same\n * way everywhere, with a message written for **self-correction** (Decision 1d):\n * it states what is wrong AND the correct form.\n *\n * Field roles map to dialects (Decision 2):\n * - `predicate` → bare CEL returning bool (`record.rating >= 4`)\n * - `value` → bare CEL of any type (`daysFromNow(3)`)\n * - `template` → text with `{{ path }}` holes (`Hot lead: {{ record.name }}`)\n *\n * The #1 author error (human or LLM) is wrapping a field reference in single\n * `{…}` braces inside a CEL field — `{x}` parses as a CEL map literal and fails.\n * This validator detects that specific mistake and returns the exact fix.\n */\n\nimport { celEngine, firstUndeclaredReference } from './cel-engine';\nimport { templateEngine } from './template-engine';\n\nexport type FieldRole = 'predicate' | 'value' | 'template';\n\n/**\n * Loose input accepted by the validator: a bare string, or any object exposing\n * `dialect`/`source` (the Expression envelope, or a not-yet-narrowed value from\n * a `config.condition` / `edge.condition` field). Kept structural so call sites\n * need not pre-narrow to the strict {@link Expression} dialect union.\n */\nexport type ExprInput = string | { dialect?: string; source?: string } | null | undefined;\n\n/** Optional schema context for field-existence checks (Decision 1b, v1). */\nexport interface ExprSchemaHint {\n /** Object the expression is authored against (for error text). */\n objectName?: string;\n /** Known top-level field names, so `record.<field>` can be checked. */\n fields?: readonly string[];\n /**\n * Evaluation scope of the authoring site — determines whether a bare top-level\n * identifier is legal (#1928):\n * - `'record'` → the record is bound only as the `record` namespace, with\n * no field flattening (`Field.formula`, object validation\n * predicates). A bare `amount` resolves to nothing and the\n * expression silently evaluates to `null` / never fires, so\n * it MUST be written `record.amount`. We flag bare refs.\n * - `'flattened'` → the record's own fields are spread to top-level alongside\n * flow variables (flow / automation conditions), so bare\n * `status` is correct and is NOT an error. Flow variables\n * are not schema-knowable, so a non-field bare identifier\n * can't be soundly told apart from a typo — but when one is\n * a near-miss of a known field we emit a non-blocking\n * did-you-mean *warning*. (Default.)\n */\n scope?: 'record' | 'flattened';\n}\n\nexport interface ExprValidationError {\n /** Self-correcting message: what is wrong + the correct form. */\n message: string;\n /** The offending source, echoed for location. */\n source: string;\n}\n\nexport interface ExprValidationResult {\n ok: boolean;\n errors: ExprValidationError[];\n /**\n * Non-blocking advisories (#1928 tier 3): a likely-typo'd field reference in a\n * flattened flow condition. Never affects `ok` — callers surface these without\n * failing the build, since a bare identifier there may legitimately be a flow\n * variable.\n */\n warnings: ExprValidationError[];\n}\n\n/** A bare `{x}` that is NOT part of a `{{x}}` mustache hole. */\nconst SINGLE_BRACE_RE = /(?:^|[^{])\\{\\s*([A-Za-z_$][\\w.$]*)\\s*\\}(?!\\})/;\n/** `record.<field>` / `previous.<field>` head references for field-existence. */\nconst RECORD_REF_RE = /\\b(?:record|previous)\\.([A-Za-z_$][\\w$]*)/g;\n\n/** The dialect a field role expects (Decision 2). */\nexport function expectedDialect(role: FieldRole): 'cel' | 'template' {\n return role === 'template' ? 'template' : 'cel';\n}\n\nfunction toSource(input: ExprInput): { dialect?: string; source: string } {\n if (input == null) return { source: '' };\n if (typeof input === 'string') return { source: input };\n return { dialect: input.dialect, source: input.source ?? '' };\n}\n\nfunction bracesHint(source: string): string | null {\n const m = SINGLE_BRACE_RE.exec(source);\n if (!m) return null;\n const ref = m[1];\n return (\n `it looks like a \\`{${ref}}\\` template brace was used inside a CEL expression — ` +\n `\\`{…}\\` parses as a CEL map literal and fails. Write the bare reference instead, e.g. \\`${ref}\\`.`\n );\n}\n\nfunction checkFieldExistence(source: string, schema: ExprSchemaHint | undefined, errors: ExprValidationError[]): void {\n if (!schema?.fields || schema.fields.length === 0) return;\n const known = new Set(schema.fields);\n const seen = new Set<string>();\n let m: RegExpExecArray | null;\n RECORD_REF_RE.lastIndex = 0;\n while ((m = RECORD_REF_RE.exec(source)) !== null) {\n const field = m[1];\n if (seen.has(field) || known.has(field)) continue;\n seen.add(field);\n const suggestion = nearest(field, schema.fields);\n errors.push({\n source,\n message:\n `unknown field \\`${field}\\`${schema.objectName ? ` on \\`${schema.objectName}\\`` : ''}` +\n (suggestion ? ` — did you mean \\`${suggestion}\\`?` : ''),\n });\n }\n}\n\n/** Cheap edit-distance suggestion for typo'd field names. */\nfunction nearest(name: string, candidates: readonly string[]): string | undefined {\n let best: string | undefined;\n let bestD = Infinity;\n for (const c of candidates) {\n const d = levenshtein(name, c);\n if (d < bestD) { bestD = d; best = c; }\n }\n return bestD <= Math.max(2, Math.floor(name.length / 3)) ? best : undefined;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length, n = b.length;\n const dp = Array.from({ length: m + 1 }, (_, i) => i);\n for (let j = 1; j <= n; j++) {\n let prev = dp[0];\n dp[0] = j;\n for (let i = 1; i <= m; i++) {\n const tmp = dp[i];\n dp[i] = Math.min(dp[i] + 1, dp[i - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));\n prev = tmp;\n }\n }\n return dp[m];\n}\n\n/**\n * Validate one expression for a given field role. Never throws — returns a\n * structured result. Call sites decide whether to throw (build/registration)\n * or report (agent tool).\n */\nexport function validateExpression(\n role: FieldRole,\n input: ExprInput,\n schema?: ExprSchemaHint,\n): ExprValidationResult {\n const { dialect, source } = toSource(input);\n const errors: ExprValidationError[] = [];\n const warnings: ExprValidationError[] = [];\n if (!source.trim()) return { ok: true, errors, warnings };\n\n if (role === 'template') {\n // Templates must be the `template` dialect (or untyped string). Reject a\n // CEL envelope mistakenly placed in a text field.\n if (dialect && dialect !== 'template') {\n errors.push({ source, message: `expected a text template but got a \\`${dialect}\\` expression.` });\n return { ok: false, errors, warnings };\n }\n const compiled = templateEngine.compile(source);\n if (!compiled.ok) {\n errors.push({ source, message: `invalid template: ${compiled.error.message} (holes use \\`{{ path }}\\`).` });\n }\n // A single `{x}` in a template is the legacy/deprecated form (ADR-0032 §3).\n const hint = SINGLE_BRACE_RE.test(source) ? bracesHintForTemplate(source) : null;\n if (hint) errors.push({ source, message: hint });\n return { ok: errors.length === 0, errors, warnings };\n }\n\n // predicate | value → CEL\n if (dialect && dialect !== 'cel') {\n errors.push({ source, message: `expected a CEL expression but got a \\`${dialect}\\` dialect.` });\n return { ok: false, errors, warnings };\n }\n const compiled = celEngine.compile(source);\n if (!compiled.ok) {\n const hint = bracesHint(source);\n errors.push({\n source,\n message:\n `invalid CEL ${role}: ${compiled.error.message}` +\n (hint ? ` — ${hint}` : ` — ${role}s are bare CEL (e.g. \\`record.rating >= 4\\`).`),\n });\n } else {\n checkFieldExistence(source, schema, errors);\n if (schema?.scope === 'record') {\n // In a `record`-scoped site a bare top-level identifier is a silent bug —\n // it must be `record.<field>` (#1928). Hard error.\n const bare = firstUndeclaredReference(source);\n if (bare) {\n errors.push({\n source,\n message:\n `bare reference \\`${bare}\\` — a formula/validation expression binds the record as the ` +\n `\\`record\\` namespace, not at top level, so \\`${bare}\\` resolves to nothing and the ` +\n `expression silently evaluates to null. Write \\`record.${bare}\\`.`,\n });\n }\n } else if (schema?.fields && schema.fields.length > 0) {\n // Flattened flow/automation condition: the record's fields ARE bound at\n // top-level, so a bare ref is normally correct. But a *non-field* bare\n // identifier is either a flow variable or a typo. When it is a near-miss\n // of a known field, warn (did-you-mean) WITHOUT failing the build —\n // a genuine flow variable won't be edit-distance-close to a field. (#1928)\n const unknown = firstUndeclaredReference(source, schema.fields);\n if (unknown) {\n const suggestion = nearest(unknown, schema.fields);\n if (suggestion) {\n warnings.push({\n source,\n message:\n `\\`${unknown}\\` is not a field of \\`${schema.objectName ?? 'the trigger object'}\\` — ` +\n `did you mean \\`${suggestion}\\`? (flow conditions reference fields bare, e.g. \\`${suggestion} == …\\`). ` +\n `If \\`${unknown}\\` is a flow variable this is safe to ignore.`,\n });\n }\n }\n }\n }\n return { ok: errors.length === 0, errors, warnings };\n}\n\nfunction bracesHintForTemplate(source: string): string {\n const m = SINGLE_BRACE_RE.exec(source);\n const ref = m?.[1] ?? 'field';\n return `single-brace \\`{${ref}}\\` is not a valid template hole — use double braces: \\`{{ ${ref} }}\\`.`;\n}\n\n/**\n * Introspect what an author (esp. an agent) may use in a field (Decision 1e):\n * the expected dialect, the in-scope field references, and the callable\n * functions. Feeds the authoring context so the model does not guess.\n */\nexport function introspectScope(role: FieldRole, schema?: ExprSchemaHint): {\n dialect: 'cel' | 'template';\n fields: string[];\n roots: string[];\n functions: string[];\n} {\n return {\n dialect: expectedDialect(role),\n fields: [...(schema?.fields ?? [])],\n roots: ['record', 'previous', 'input', 'os', 'vars'],\n functions: CEL_STDLIB_FUNCTIONS,\n };\n}\n\n/**\n * Public catalog of CEL functions available in expressions — what `introspectScope`\n * advertises to authors (incl. AI). Every entry MUST actually resolve at runtime:\n * either registered in `registerStdLib` or a verified cel-js built-in. Drifting this\n * list ahead of the runtime tells the author to call functions that fault (#1928).\n */\nexport const CEL_STDLIB_FUNCTIONS: string[] = [\n // Dates (registered stdlib)\n 'now', 'today', 'daysFromNow', 'daysAgo', 'daysBetween', 'addDays', 'addMonths', 'date', 'datetime',\n // Numbers (registered stdlib)\n 'abs', 'round', 'min', 'max',\n // Strings (registered stdlib)\n 'upper', 'lower', 'trim', 'contains', 'startsWith', 'endsWith', 'matches', 'joinNonEmpty',\n // Collections / null-ish (registered stdlib)\n 'isBlank', 'isEmpty', 'coalesce', 'len',\n // cel-js built-ins (verified to resolve)\n 'size', 'has', 'int', 'string', 'bool', 'double', 'timestamp', 'duration',\n];\n"],"mappings":"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","names":["path","out","ExpressionSchema","ExpressionSchema","Environment","resolveValue","toSource","compiled"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/formula",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "ObjectStack canonical expression engine — CEL (cel-js) + ObjectStack stdlib + dialect registry",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@marcbachmann/cel-js": "^7.6.1",
|
|
17
|
-
"@objectstack/spec": "
|
|
17
|
+
"@objectstack/spec": "10.0.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"typescript": "^6.0.3",
|
package/src/cel-engine.ts
CHANGED
|
@@ -53,6 +53,12 @@ const SCOPE_ROOTS = [
|
|
|
53
53
|
'record', 'previous', 'input', 'output', 'os', 'vars', 'variables',
|
|
54
54
|
'automation', 'context', 'args', 'item', 'env', 'user', 'step', 'result',
|
|
55
55
|
'trigger', 'event', 'payload', 'data', 'params', 'config', 'settings',
|
|
56
|
+
// UI action / predicate context (ActionEngine, renderers): the current
|
|
57
|
+
// record plus ambient globals exposed to `visible`/`disabled` predicates.
|
|
58
|
+
'ctx', 'features',
|
|
59
|
+
// Master-detail inline grids inject the header record as `parent` for a
|
|
60
|
+
// child field's `readonlyWhen`/`requiredWhen` predicate (ADR-0036, #1581).
|
|
61
|
+
'parent',
|
|
56
62
|
] as const;
|
|
57
63
|
|
|
58
64
|
/**
|