@objectstack/formula 7.7.0 → 7.9.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 +8 -8
- package/CHANGELOG.md +27 -0
- package/dist/index.js +13 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/cel-engine.test.ts +75 -0
- package/src/cel-engine.ts +41 -20
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/formula@7.
|
|
2
|
+
> @objectstack/formula@7.9.0 build /home/runner/work/framework/framework/packages/formula
|
|
3
3
|
> tsup --config ../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[32mESM[39m [1mdist/index.mjs [22m[32m23.
|
|
14
|
-
[32mESM[39m [1mdist/index.mjs.map [22m[
|
|
15
|
-
[32mESM[39m ⚡️ Build success in
|
|
16
|
-
[32mCJS[39m [1mdist/index.js [22m[
|
|
17
|
-
[32mCJS[39m [1mdist/index.js.map [22m[
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in
|
|
13
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m23.34 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m60.97 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 67ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.js [22m[32m25.06 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m62.34 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 76ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 4594ms
|
|
21
21
|
[32mDTS[39m [1mdist/index.d.mts [22m[32m14.02 KB[39m
|
|
22
22
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m14.02 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @objectstack/formula
|
|
2
2
|
|
|
3
|
+
## 7.9.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @objectstack/spec@7.9.0
|
|
8
|
+
|
|
9
|
+
## 7.8.0
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- f01f9fa: fix(formula): hydrate ISO date/datetime strings on CEL `no such overload` fault (#1530)
|
|
14
|
+
|
|
15
|
+
Date-typed formula fields and date predicates always evaluated to `null`:
|
|
16
|
+
`Field.date`/`Field.datetime` serialize to ISO strings, and cel-js compared the
|
|
17
|
+
raw string against the `google.protobuf.Timestamp` from `today()`/`now()`/
|
|
18
|
+
`daysFromNow()`, raising `no such overload` (swallowed to null). The existing
|
|
19
|
+
numeric-string fault-retry (#1534) is now extended to also coerce strict ISO-8601
|
|
20
|
+
date/date-time strings to `Date` before retrying once, fixing every caller
|
|
21
|
+
(formula fields, flow conditions, validation/workflow predicates). Hydration runs
|
|
22
|
+
only after a fault, so clean expressions are never re-interpreted and genuine
|
|
23
|
+
non-temporal strings still fault loudly.
|
|
24
|
+
|
|
25
|
+
- Updated dependencies [06f2bbb]
|
|
26
|
+
- Updated dependencies [36719db]
|
|
27
|
+
- Updated dependencies [424ab26]
|
|
28
|
+
- @objectstack/spec@7.8.0
|
|
29
|
+
|
|
3
30
|
## 7.7.0
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -144,23 +144,29 @@ function coerce(value) {
|
|
|
144
144
|
return value;
|
|
145
145
|
}
|
|
146
146
|
var NUMERIC_STRING_RE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
147
|
+
var ISO_TEMPORAL_STRING_RE = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
147
148
|
function isNumericOverloadError(err) {
|
|
148
149
|
const message = err instanceof Error ? err.message : String(err);
|
|
149
150
|
return /no such overload/i.test(message);
|
|
150
151
|
}
|
|
151
|
-
function
|
|
152
|
+
function hydrateOverloadStrings(value) {
|
|
152
153
|
if (typeof value === "string") {
|
|
153
154
|
const trimmed = value.trim();
|
|
154
|
-
if (trimmed.length > 0
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
if (trimmed.length > 0) {
|
|
156
|
+
if (NUMERIC_STRING_RE.test(trimmed)) {
|
|
157
|
+
const n = Number(trimmed);
|
|
158
|
+
if (Number.isFinite(n)) return n;
|
|
159
|
+
} else if (ISO_TEMPORAL_STRING_RE.test(trimmed)) {
|
|
160
|
+
const ms = Date.parse(trimmed);
|
|
161
|
+
if (!Number.isNaN(ms)) return new Date(ms);
|
|
162
|
+
}
|
|
157
163
|
}
|
|
158
164
|
return value;
|
|
159
165
|
}
|
|
160
|
-
if (Array.isArray(value)) return value.map(
|
|
166
|
+
if (Array.isArray(value)) return value.map(hydrateOverloadStrings);
|
|
161
167
|
if (value && typeof value === "object" && !(value instanceof Date)) {
|
|
162
168
|
const out = {};
|
|
163
|
-
for (const [k, v] of Object.entries(value)) out[k] =
|
|
169
|
+
for (const [k, v] of Object.entries(value)) out[k] = hydrateOverloadStrings(v);
|
|
164
170
|
return out;
|
|
165
171
|
}
|
|
166
172
|
return value;
|
|
@@ -214,7 +220,7 @@ var celEngine = {
|
|
|
214
220
|
return { ok: true, value: coerce(raw) };
|
|
215
221
|
} catch (err) {
|
|
216
222
|
if (!isNumericOverloadError(err)) throw err;
|
|
217
|
-
const hydrated =
|
|
223
|
+
const hydrated = hydrateOverloadStrings(scope);
|
|
218
224
|
try {
|
|
219
225
|
const raw = env.evaluate(source, hydrated);
|
|
220
226
|
return { ok: true, value: coerce(raw) };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../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 * @objectstack/formula\n *\n * Canonical expression engine for ObjectStack. CEL (Common Expression\n * Language) is the default dialect; `js` and `cron` are dispatched to\n * dedicated plugin engines.\n *\n * @see content/docs/concepts/north-star.mdx §8 \"No private expression DSL\"\n * @see ROADMAP.md M9 \"Expression Unification\"\n */\n\nexport { ExpressionEngine, getEngine, hasDialect, register } from './registry';\nexport { celEngine, DEFAULT_LIMITS } from './cel-engine';\nexport { cronEngine } from './cron-engine';\nexport { templateEngine, TEMPLATE_FORMATTERS } from './template-engine';\nexport { registerStdLib, buildScope } from './stdlib';\nexport { resolveSeed, resolveSeedRecord } from './seed-eval';\nexport { normalizeExpression, normalizeExpressionTree } from './normalize';\n// ADR-0032 — shared validator + introspection (one validator for build,\n// registration, and the agent-callable validate_expression tool).\nexport { validateExpression, introspectScope, expectedDialect, CEL_STDLIB_FUNCTIONS } from './validate';\nexport type { FieldRole, ExprSchemaHint, ExprValidationError, ExprValidationResult } from './validate';\nexport type { SeedValue, SeedPrimitive } from './seed-eval';\nexport type { DialectEngine, EvalContext, EvalResult, EvalError } from './types';\n","/**\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, 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): Environment {\n const env = new Environment({\n unlistedVariablesAreDyn: true,\n enableOptionalTypes: true,\n limits: DEFAULT_LIMITS,\n });\n return registerStdLib(env, now);\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 * 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 are *entirely* numeric literals into\n * numbers. Used only on the {@link isNumericOverloadError} retry path, so it\n * can never change a comparison that already evaluated cleanly — it only\n * rescues one that already faulted. Dates and non-numeric strings pass through\n * untouched (a zip like `\"02134\"` only changes if the surrounding expression\n * already faulted, in which case the original loud error is preserved when the\n * retry still cannot type-check).\n */\nfunction hydrateNumericStrings(value: unknown): unknown {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed.length > 0 && NUMERIC_STRING_RE.test(trimmed)) {\n const n = Number(trimmed);\n if (Number.isFinite(n)) return n;\n }\n return value;\n }\n if (Array.isArray(value)) return value.map(hydrateNumericStrings);\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] = hydrateNumericStrings(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.\n const checkErrors = compiled.check?.();\n if (checkErrors && Array.isArray(checkErrors) && checkErrors.length > 0) {\n return {\n ok: false,\n error: { kind: 'type', message: checkErrors.join('; ') },\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);\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 numeric fields (`rating` → `\"5.0\"`,\n // `amount` → `\"250000.00\"`) make `record.rating >= 4` raise CEL's\n // `no such overload: dyn >= int`. Hydrate purely-numeric strings to\n // numbers and retry ONCE. This only runs after a fault, so a comparison\n // that already evaluated cleanly is never re-interpreted; if the retry\n // still cannot type-check, the original loud error is reported (#1534).\n if (!isNumericOverloadError(err)) throw err;\n const hydrated = hydrateNumericStrings(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/** 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/** 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 * 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): Environment {\n return env\n .registerFunction('now(): google.protobuf.Timestamp', () => now())\n .registerFunction(\n 'today(): google.protobuf.Timestamp',\n () => startOfDayUtc(now()),\n )\n .registerFunction(\n 'daysFromNow(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(now(), Number(n)),\n )\n .registerFunction(\n 'daysAgo(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(now(), -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}\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 = (value: unknown, arg: string | undefined, locale: string) => 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\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\n datetime: (v, arg, locale) => {\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 }).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\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\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);\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 * `Dataset.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 } 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\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\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 if (!source.trim()) return { ok: true, errors };\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 };\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 };\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 };\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 }\n return { ok: errors.length === 0, errors };\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/** Public catalog of CEL stdlib functions available in expressions. */\nexport const CEL_STDLIB_FUNCTIONS: string[] = [\n 'now', 'today', 'daysFromNow', 'daysBetween', 'date', 'datetime', 'timestamp',\n 'isBlank', 'isEmpty', 'coalesce', 'len', 'size', 'int', 'float', 'string', 'bool',\n 'upper', 'lower', 'trim', 'contains', 'startsWith', 'endsWith', 'matches',\n 'has', 'min', 'max', 'abs', 'round',\n];\n"],"mappings":"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","names":["path","out","import_spec","compiled"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../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 * @objectstack/formula\n *\n * Canonical expression engine for ObjectStack. CEL (Common Expression\n * Language) is the default dialect; `js` and `cron` are dispatched to\n * dedicated plugin engines.\n *\n * @see content/docs/concepts/north-star.mdx §8 \"No private expression DSL\"\n * @see ROADMAP.md M9 \"Expression Unification\"\n */\n\nexport { ExpressionEngine, getEngine, hasDialect, register } from './registry';\nexport { celEngine, DEFAULT_LIMITS } from './cel-engine';\nexport { cronEngine } from './cron-engine';\nexport { templateEngine, TEMPLATE_FORMATTERS } from './template-engine';\nexport { registerStdLib, buildScope } from './stdlib';\nexport { resolveSeed, resolveSeedRecord } from './seed-eval';\nexport { normalizeExpression, normalizeExpressionTree } from './normalize';\n// ADR-0032 — shared validator + introspection (one validator for build,\n// registration, and the agent-callable validate_expression tool).\nexport { validateExpression, introspectScope, expectedDialect, CEL_STDLIB_FUNCTIONS } from './validate';\nexport type { FieldRole, ExprSchemaHint, ExprValidationError, ExprValidationResult } from './validate';\nexport type { SeedValue, SeedPrimitive } from './seed-eval';\nexport type { DialectEngine, EvalContext, EvalResult, EvalError } from './types';\n","/**\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, 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): Environment {\n const env = new Environment({\n unlistedVariablesAreDyn: true,\n enableOptionalTypes: true,\n limits: DEFAULT_LIMITS,\n });\n return registerStdLib(env, now);\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.\n const checkErrors = compiled.check?.();\n if (checkErrors && Array.isArray(checkErrors) && checkErrors.length > 0) {\n return {\n ok: false,\n error: { kind: 'type', message: checkErrors.join('; ') },\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);\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/** 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/** 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 * 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): Environment {\n return env\n .registerFunction('now(): google.protobuf.Timestamp', () => now())\n .registerFunction(\n 'today(): google.protobuf.Timestamp',\n () => startOfDayUtc(now()),\n )\n .registerFunction(\n 'daysFromNow(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(now(), Number(n)),\n )\n .registerFunction(\n 'daysAgo(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(now(), -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}\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 = (value: unknown, arg: string | undefined, locale: string) => 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\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\n datetime: (v, arg, locale) => {\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 }).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\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\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);\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 * `Dataset.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 } 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\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\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 if (!source.trim()) return { ok: true, errors };\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 };\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 };\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 };\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 }\n return { ok: errors.length === 0, errors };\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/** Public catalog of CEL stdlib functions available in expressions. */\nexport const CEL_STDLIB_FUNCTIONS: string[] = [\n 'now', 'today', 'daysFromNow', 'daysBetween', 'date', 'datetime', 'timestamp',\n 'isBlank', 'isEmpty', 'coalesce', 'len', 'size', 'int', 'float', 'string', 'bool',\n 'upper', 'lower', 'trim', 'contains', 'startsWith', 'endsWith', 'matches',\n 'has', 'min', 'max', 'abs', 'round',\n];\n"],"mappings":"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","names":["path","out","import_spec","compiled"]}
|
package/dist/index.mjs
CHANGED
|
@@ -100,23 +100,29 @@ function coerce(value) {
|
|
|
100
100
|
return value;
|
|
101
101
|
}
|
|
102
102
|
var NUMERIC_STRING_RE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
103
|
+
var ISO_TEMPORAL_STRING_RE = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
103
104
|
function isNumericOverloadError(err) {
|
|
104
105
|
const message = err instanceof Error ? err.message : String(err);
|
|
105
106
|
return /no such overload/i.test(message);
|
|
106
107
|
}
|
|
107
|
-
function
|
|
108
|
+
function hydrateOverloadStrings(value) {
|
|
108
109
|
if (typeof value === "string") {
|
|
109
110
|
const trimmed = value.trim();
|
|
110
|
-
if (trimmed.length > 0
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
if (trimmed.length > 0) {
|
|
112
|
+
if (NUMERIC_STRING_RE.test(trimmed)) {
|
|
113
|
+
const n = Number(trimmed);
|
|
114
|
+
if (Number.isFinite(n)) return n;
|
|
115
|
+
} else if (ISO_TEMPORAL_STRING_RE.test(trimmed)) {
|
|
116
|
+
const ms = Date.parse(trimmed);
|
|
117
|
+
if (!Number.isNaN(ms)) return new Date(ms);
|
|
118
|
+
}
|
|
113
119
|
}
|
|
114
120
|
return value;
|
|
115
121
|
}
|
|
116
|
-
if (Array.isArray(value)) return value.map(
|
|
122
|
+
if (Array.isArray(value)) return value.map(hydrateOverloadStrings);
|
|
117
123
|
if (value && typeof value === "object" && !(value instanceof Date)) {
|
|
118
124
|
const out = {};
|
|
119
|
-
for (const [k, v] of Object.entries(value)) out[k] =
|
|
125
|
+
for (const [k, v] of Object.entries(value)) out[k] = hydrateOverloadStrings(v);
|
|
120
126
|
return out;
|
|
121
127
|
}
|
|
122
128
|
return value;
|
|
@@ -170,7 +176,7 @@ var celEngine = {
|
|
|
170
176
|
return { ok: true, value: coerce(raw) };
|
|
171
177
|
} catch (err) {
|
|
172
178
|
if (!isNumericOverloadError(err)) throw err;
|
|
173
|
-
const hydrated =
|
|
179
|
+
const hydrated = hydrateOverloadStrings(scope);
|
|
174
180
|
try {
|
|
175
181
|
const raw = env.evaluate(source, hydrated);
|
|
176
182
|
return { ok: true, value: coerce(raw) };
|
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, 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): Environment {\n const env = new Environment({\n unlistedVariablesAreDyn: true,\n enableOptionalTypes: true,\n limits: DEFAULT_LIMITS,\n });\n return registerStdLib(env, now);\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 * 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 are *entirely* numeric literals into\n * numbers. Used only on the {@link isNumericOverloadError} retry path, so it\n * can never change a comparison that already evaluated cleanly — it only\n * rescues one that already faulted. Dates and non-numeric strings pass through\n * untouched (a zip like `\"02134\"` only changes if the surrounding expression\n * already faulted, in which case the original loud error is preserved when the\n * retry still cannot type-check).\n */\nfunction hydrateNumericStrings(value: unknown): unknown {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed.length > 0 && NUMERIC_STRING_RE.test(trimmed)) {\n const n = Number(trimmed);\n if (Number.isFinite(n)) return n;\n }\n return value;\n }\n if (Array.isArray(value)) return value.map(hydrateNumericStrings);\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] = hydrateNumericStrings(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.\n const checkErrors = compiled.check?.();\n if (checkErrors && Array.isArray(checkErrors) && checkErrors.length > 0) {\n return {\n ok: false,\n error: { kind: 'type', message: checkErrors.join('; ') },\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);\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 numeric fields (`rating` → `\"5.0\"`,\n // `amount` → `\"250000.00\"`) make `record.rating >= 4` raise CEL's\n // `no such overload: dyn >= int`. Hydrate purely-numeric strings to\n // numbers and retry ONCE. This only runs after a fault, so a comparison\n // that already evaluated cleanly is never re-interpreted; if the retry\n // still cannot type-check, the original loud error is reported (#1534).\n if (!isNumericOverloadError(err)) throw err;\n const hydrated = hydrateNumericStrings(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/** 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/** 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 * 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): Environment {\n return env\n .registerFunction('now(): google.protobuf.Timestamp', () => now())\n .registerFunction(\n 'today(): google.protobuf.Timestamp',\n () => startOfDayUtc(now()),\n )\n .registerFunction(\n 'daysFromNow(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(now(), Number(n)),\n )\n .registerFunction(\n 'daysAgo(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(now(), -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}\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 = (value: unknown, arg: string | undefined, locale: string) => 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\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\n datetime: (v, arg, locale) => {\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 }).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\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\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);\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 * `Dataset.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 } 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\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\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 if (!source.trim()) return { ok: true, errors };\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 };\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 };\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 };\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 }\n return { ok: errors.length === 0, errors };\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/** Public catalog of CEL stdlib functions available in expressions. */\nexport const CEL_STDLIB_FUNCTIONS: string[] = [\n 'now', 'today', 'daysFromNow', 'daysBetween', 'date', 'datetime', 'timestamp',\n 'isBlank', 'isEmpty', 'coalesce', 'len', 'size', 'int', 'float', 'string', 'bool',\n 'upper', 'lower', 'trim', 'contains', 'startsWith', 'endsWith', 'matches',\n 'has', 'min', 'max', 'abs', 'round',\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/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, 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): Environment {\n const env = new Environment({\n unlistedVariablesAreDyn: true,\n enableOptionalTypes: true,\n limits: DEFAULT_LIMITS,\n });\n return registerStdLib(env, now);\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.\n const checkErrors = compiled.check?.();\n if (checkErrors && Array.isArray(checkErrors) && checkErrors.length > 0) {\n return {\n ok: false,\n error: { kind: 'type', message: checkErrors.join('; ') },\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);\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/** 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/** 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 * 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): Environment {\n return env\n .registerFunction('now(): google.protobuf.Timestamp', () => now())\n .registerFunction(\n 'today(): google.protobuf.Timestamp',\n () => startOfDayUtc(now()),\n )\n .registerFunction(\n 'daysFromNow(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(now(), Number(n)),\n )\n .registerFunction(\n 'daysAgo(int): google.protobuf.Timestamp',\n (n: bigint | number) => addDaysUtc(now(), -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}\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 = (value: unknown, arg: string | undefined, locale: string) => 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\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\n datetime: (v, arg, locale) => {\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 }).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\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\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);\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 * `Dataset.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 } 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\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\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 if (!source.trim()) return { ok: true, errors };\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 };\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 };\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 };\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 }\n return { ok: errors.length === 0, errors };\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/** Public catalog of CEL stdlib functions available in expressions. */\nexport const CEL_STDLIB_FUNCTIONS: string[] = [\n 'now', 'today', 'daysFromNow', 'daysBetween', 'date', 'datetime', 'timestamp',\n 'isBlank', 'isEmpty', 'coalesce', 'len', 'size', 'int', 'float', 'string', 'bool',\n 'upper', 'lower', 'trim', 'contains', 'startsWith', 'endsWith', 'matches',\n 'has', 'min', 'max', 'abs', 'round',\n];\n"],"mappings":"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","names":["path","out","ExpressionSchema","ExpressionSchema","compiled"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/formula",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.9.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": "7.
|
|
17
|
+
"@objectstack/spec": "7.9.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"typescript": "^6.0.3",
|
package/src/cel-engine.test.ts
CHANGED
|
@@ -147,4 +147,79 @@ describe('celEngine', () => {
|
|
|
147
147
|
expect(r.ok).toBe(false);
|
|
148
148
|
});
|
|
149
149
|
});
|
|
150
|
+
|
|
151
|
+
// ADR-0032 §1c — string-serialized date/datetime fields (#1530). Field.date
|
|
152
|
+
// serializes to "YYYY-MM-DD" and Field.datetime to a full ISO string; cel-js
|
|
153
|
+
// compares those raw strings against the google.protobuf.Timestamp returned by
|
|
154
|
+
// today()/now()/daysFromNow() and faults `no such overload`, which previously
|
|
155
|
+
// surfaced as a silent `null`.
|
|
156
|
+
describe('date/datetime-string field hydration (#1530)', () => {
|
|
157
|
+
const now = new Date('2026-06-02T08:00:00Z');
|
|
158
|
+
|
|
159
|
+
it('compares a date-only field against today()/daysFromNow() (is_expiring_soon)', () => {
|
|
160
|
+
const r = celEngine.evaluate(
|
|
161
|
+
cel('record.end_date >= today() && record.end_date <= daysFromNow(60)'),
|
|
162
|
+
{ now, record: { end_date: '2026-06-20' } },
|
|
163
|
+
);
|
|
164
|
+
expect(r).toEqual({ ok: true, value: true });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('returns false (not a fault) when the date compare is unmet', () => {
|
|
168
|
+
const r = celEngine.evaluate(cel('record.end_date <= daysFromNow(60)'), {
|
|
169
|
+
now,
|
|
170
|
+
record: { end_date: '2027-01-01' },
|
|
171
|
+
});
|
|
172
|
+
expect(r).toEqual({ ok: true, value: false });
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('handles is_overdue: a past date-only field < today()', () => {
|
|
176
|
+
const r = celEngine.evaluate(cel('record.due_date < today()'), {
|
|
177
|
+
now,
|
|
178
|
+
record: { due_date: '2026-05-31' },
|
|
179
|
+
});
|
|
180
|
+
expect(r).toEqual({ ok: true, value: true });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('hydrates a full ISO datetime field against now()', () => {
|
|
184
|
+
const r = celEngine.evaluate(cel('record.resolution_due_at < now()'), {
|
|
185
|
+
now,
|
|
186
|
+
record: { resolution_due_at: '2026-06-01T08:15:35.244Z' },
|
|
187
|
+
});
|
|
188
|
+
expect(r).toEqual({ ok: true, value: true });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('supports timestamp arithmetic on hydrated date fields (today() - hire_date)', () => {
|
|
192
|
+
// hire_date ~2.4y before `now` → tenure exceeds 2 years (17520h).
|
|
193
|
+
const r = celEngine.evaluate(
|
|
194
|
+
cel('(today() - record.hire_date) > duration("17520h")'),
|
|
195
|
+
{ now, record: { hire_date: '2024-01-01' } },
|
|
196
|
+
);
|
|
197
|
+
expect(r).toEqual({ ok: true, value: true });
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('hydrates date + numeric strings together in one record', () => {
|
|
201
|
+
const r = celEngine.evaluate(
|
|
202
|
+
cel('record.amount >= 1000 && record.end_date >= today()'),
|
|
203
|
+
{ now, record: { amount: '2500.00', end_date: '2026-06-20' } },
|
|
204
|
+
);
|
|
205
|
+
expect(r).toEqual({ ok: true, value: true });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('does not coerce non-temporal strings (still faults loudly)', () => {
|
|
209
|
+
const r = celEngine.evaluate(cel('record.end_date <= today()'), {
|
|
210
|
+
now,
|
|
211
|
+
record: { end_date: 'soon' },
|
|
212
|
+
});
|
|
213
|
+
expect(r.ok).toBe(false);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('leaves genuine date-string equality untouched (no spurious coercion)', () => {
|
|
217
|
+
// string == string type-checks, so the retry never runs and the value
|
|
218
|
+
// stays a string.
|
|
219
|
+
const r = celEngine.evaluate(cel('record.end_date == "2026-06-20"'), {
|
|
220
|
+
record: { end_date: '2026-06-20' },
|
|
221
|
+
});
|
|
222
|
+
expect(r).toEqual({ ok: true, value: true });
|
|
223
|
+
});
|
|
224
|
+
});
|
|
150
225
|
});
|
package/src/cel-engine.ts
CHANGED
|
@@ -71,6 +71,17 @@ function coerce(value: unknown): unknown {
|
|
|
71
71
|
// digit runs (CodeQL ReDoS). This matches the same strings without the hazard.
|
|
72
72
|
const NUMERIC_STRING_RE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* A string that is an ISO-8601 date (`"2026-06-20"`) or date-time
|
|
76
|
+
* (`"2026-06-20T08:15:35.244Z"`, `"2026-06-20 08:15"`, `"...+02:00"`). Strict
|
|
77
|
+
* and anchored — no nested unbounded quantifiers, so no ReDoS hazard (every
|
|
78
|
+
* sub-group is bounded or a single `\.\d+`). `Field.date` / `Field.datetime`
|
|
79
|
+
* serialize to these; cel-js compares them as `string` and faults against the
|
|
80
|
+
* `google.protobuf.Timestamp` returned by `today()` / `now()` / `daysFromNow()`.
|
|
81
|
+
*/
|
|
82
|
+
const ISO_TEMPORAL_STRING_RE =
|
|
83
|
+
/^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
84
|
+
|
|
74
85
|
/**
|
|
75
86
|
* cel-js raises `no such overload: dyn <op> int` (and kin) when a comparison
|
|
76
87
|
* or arithmetic operator sees a `string` on one side and a number on the
|
|
@@ -85,27 +96,33 @@ function isNumericOverloadError(err: unknown): boolean {
|
|
|
85
96
|
}
|
|
86
97
|
|
|
87
98
|
/**
|
|
88
|
-
* Recursively coerce string values that
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* already faulted
|
|
94
|
-
* retry still cannot type-check
|
|
99
|
+
* Recursively coerce string values that faulted a CEL overload into their
|
|
100
|
+
* intended primitive: entirely-numeric literals → `number` (#1534), and
|
|
101
|
+
* ISO-8601 date / date-time strings → `Date` (cel-js `google.protobuf.Timestamp`)
|
|
102
|
+
* (#1530). Used only on the {@link isNumericOverloadError} retry path, so it can
|
|
103
|
+
* never change a comparison that already evaluated cleanly — it only rescues one
|
|
104
|
+
* that already faulted. Strings that are neither (a zip like `"02134"`, free
|
|
105
|
+
* text) pass through untouched; if the retry still cannot type-check, the
|
|
106
|
+
* original loud error is preserved.
|
|
95
107
|
*/
|
|
96
|
-
function
|
|
108
|
+
function hydrateOverloadStrings(value: unknown): unknown {
|
|
97
109
|
if (typeof value === 'string') {
|
|
98
110
|
const trimmed = value.trim();
|
|
99
|
-
if (trimmed.length > 0
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
if (trimmed.length > 0) {
|
|
112
|
+
if (NUMERIC_STRING_RE.test(trimmed)) {
|
|
113
|
+
const n = Number(trimmed);
|
|
114
|
+
if (Number.isFinite(n)) return n;
|
|
115
|
+
} else if (ISO_TEMPORAL_STRING_RE.test(trimmed)) {
|
|
116
|
+
const ms = Date.parse(trimmed);
|
|
117
|
+
if (!Number.isNaN(ms)) return new Date(ms);
|
|
118
|
+
}
|
|
102
119
|
}
|
|
103
120
|
return value;
|
|
104
121
|
}
|
|
105
|
-
if (Array.isArray(value)) return value.map(
|
|
122
|
+
if (Array.isArray(value)) return value.map(hydrateOverloadStrings);
|
|
106
123
|
if (value && typeof value === 'object' && !(value instanceof Date)) {
|
|
107
124
|
const out: Record<string, unknown> = {};
|
|
108
|
-
for (const [k, v] of Object.entries(value)) out[k] =
|
|
125
|
+
for (const [k, v] of Object.entries(value)) out[k] = hydrateOverloadStrings(v);
|
|
109
126
|
return out;
|
|
110
127
|
}
|
|
111
128
|
return value;
|
|
@@ -170,14 +187,18 @@ export const celEngine: DialectEngine = {
|
|
|
170
187
|
const raw = env.evaluate(source, scope);
|
|
171
188
|
return { ok: true, value: coerce(raw) as T };
|
|
172
189
|
} catch (err) {
|
|
173
|
-
// ADR-0032 §1c — string-serialized
|
|
174
|
-
// `
|
|
175
|
-
// `
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
190
|
+
// ADR-0032 §1c — string-serialized fields make CEL raise
|
|
191
|
+
// `no such overload`: numeric fields (`rating` → `"5.0"`,
|
|
192
|
+
// `amount` → `"250000.00"`) on `record.rating >= 4` (#1534), and
|
|
193
|
+
// date/datetime fields (`end_date` → `"2026-06-20"`) on
|
|
194
|
+
// `record.end_date <= daysFromNow(60)` (#1530), since cel-js compares the
|
|
195
|
+
// raw string against the `google.protobuf.Timestamp` from `today()` etc.
|
|
196
|
+
// Hydrate those strings to number / Date and retry ONCE. This only runs
|
|
197
|
+
// after a fault, so a comparison that already evaluated cleanly is never
|
|
198
|
+
// re-interpreted; if the retry still cannot type-check, the original loud
|
|
199
|
+
// error is reported.
|
|
179
200
|
if (!isNumericOverloadError(err)) throw err;
|
|
180
|
-
const hydrated =
|
|
201
|
+
const hydrated = hydrateOverloadStrings(scope) as Record<string, unknown>;
|
|
181
202
|
try {
|
|
182
203
|
const raw = env.evaluate(source, hydrated);
|
|
183
204
|
return { ok: true, value: coerce(raw) as T };
|