- Updated dependencies [9e45b63]
- @objectstack/spec@16.1.0
-
6b51346: feat(formula):
dateField == today()now matches — AST temporal-comparison rewrite (#3183)Behavior change (the fix): a
Field.datecompared with==/!=against a temporal function now matches on the calendar day. Previously it silently returned the wrong answer —record.due_date == today()was alwaysfalse(and!= today()alwaystrue) even for a same-day record, because aField.datereads back as aYYYY-MM-DDstring (ADR-0053 Phase 1) and cel-js's equality (overloads.jsisEqual) treats a string and a timestamp as unequal without consulting any overload.celEngine.evaluatenow rewrites the parsed AST: for each==/!=whose one operand istoday()/daysFromNow()/daysAgo()/now(), the field operand is wrapped indate(...)(the stdlib coercion), then the expression is serialized and evaluated. Sorecord.due_date == today()runs asdate(record.due_date) == today().- Per-occurrence, not per-field:
record.d == "2026-06-20" || record.d == today()keeps the string-literal comparison intact while fixing the temporal one. - Type-blind-safe:
date()degrades gracefully — an already-Date(Field.datetime) operand passes through; a non-date string or null →Invalid Date→ the comparison staysfalse, exactly as before. No field-type information is needed, and no currently-correct result is worsened. - Cheap: the rewrite only reserializes when such a comparison is present
(a plain-
includesgate skips the rest), and is memoized per source string.
Applies to every interpreter site — read-time
Field.formula, default values, validation rules, hook conditions, and flow conditions — since all route throughcelEngine.evaluate. RLS/sharing conditions are unaffected: they compile viacel-to-filter, which already rejects function calls as a loud authoring error.Supersedes the #3192 advisory lint. That build-time warning (
checkTemporalDateEquality) flaggeddateField == today()as a silent-miss; with the runtime fixed it would be a false alarm, so it (and thetemporalEqualityFieldshelper it used) is removed. Authors can now write the naturalrecord.due_date == today()directly; thedate(...)/daysBetween(...) == 0/ range idioms all keep working. - Per-occurrence, not per-field:
-
80273c8: feat(formula): warn when a
datefield is compared to a temporal function with==/!=(#3183)A
Field.datedeserializes as aYYYY-MM-DDstring (ADR-0053 Phase 1), and cel-js's equality hard-codesstring == <timestamp>tofalse— it returnsfalsefor a string left operand without ever consulting a registered overload, and refuses cross-type object equality (@marcbachmann/cel-jsoverloads.jsisEqual). So the most natural "is it due today" predicate —record.due_date == today() // silently false, even when due_date IS today record.due_date != today() // silently true for a same-day record— compiles clean, throws nothing, and silently never matches. Same silent-miss family as #1928; timezone-independent (fails identically at UTC) and cross-cutting (formulas, validation, RLS, flow/action/sharing/hook predicates).
cel-js gives no operator-layer hook to fix the comparison, so this adds a build-time advisory warning (the established ADR-0032 guardrail strategy) rather than a runtime behavior change.
validateExpressionreuses the sharedExprSchemaHint.fieldTypes(the same per-field type map the #1928 tier-4 soundness check already threads through@objectstack/lint) to flag a==/!=between adatefield (record./previous./bare) andtoday()/daysFromNow()/daysAgo()/now(), with a self-correcting message pointing at the working idioms:date(record.d) == today(), a range (>= … && <= …), ordaysBetween(today(), record.d) == 0.Warning severity — never fails the build (the write/validation path may carry a real
Date). Restricted totype: 'date'(unambiguously a string);datetimeis excluded to avoid false positives. Ordering operators (>=/<=/</>) already work — cel-js throws for them, tripping the engine's existing string-hydration retry — so they are not flagged.A runtime fix (normalizing the peer of a temporal operand in the data layer) remains tracked in #3183; a naive "hydrate date fields to
Date" version would trade this silent-miss for another (breakingdateField == "2026-06-20"), so it needs its own design. -
7125007: Stored
Field.formulafields that compute dates/durations no longer silently evaluate tonull(#3306). Three independent CEL gaps made shipped template formulas (e.g.hr_employee.tenure_years,hr_time_off_request.days) returnnullwith no parse/build/runtime error:-
The null-guard idiom
cond ? <value> : nullnow compiles and evaluates. cel-js's ternary type-unifier rejects a concreteint/double/stringbranch againstnull— so eventrue ? 5 : nullfaulted "Ternary branches must have the same type" and the whole formula nulled. AField.formulais inherently nullable and the catalog blesses both ternary and== null, so this is the canonical "compute value, else blank" shape. An AST pre-pass (mirroring the #3183 temporal-equality rewrite) wraps the non-null branch indyn(...)— value-preserving, null-branch-only, idempotent — so it type-checks and runs. Applied incompile(),evaluate(), and the build soundness check alike. -
floor(x)/ceil(x)are now registered (parallel toround/abs) and advertised in the catalog. They round toward −∞ / +∞, sofloor(-1.2) == -2— NOT interchangeable with integer division's round-toward-zero. Previouslyfloor(...)faultedfound no matching overloadand the formula nulled. -
Date arithmetic is now a build-time ERROR instead of a silent runtime
null.record.end_date - record.start_date + 1,today() + 30,record.date + ntype-check clean (operands aredyn) but always fault at runtime and never recover (a date string is not numeric, so hydration can't rescue it). The build soundness check now typesdate/datetimefields asgoogle.protobuf.Timestampand flags date/duration arithmetic against a number with a corrective message pointing atdaysBetween(a, b)/daysFromNow(n)/addDays(d, n)/addMonths(d, n). Sound by construction — ordering (date < today(),date < "2026-01-01"string-lex), equality (#3183), and string concatenation ("Due: " + date) are all runtime-tolerated and never flagged; only arithmetic against a number is. A!= nullguard on a date field no longer masks the inner fault (== nullno-op overloads registered in the check-only env).
Heads-up for downstream: (3) adds a NEW build-time error. A stored formula or predicate doing arithmetic on a
date/datetimefield (end - start + 1,today() + 30) that previously built (and nulled at runtime) will now failobjectstack build/validateStackExpressionswith a message telling you to usedaysBetween/daysFromNow/addDays. This only fires for genuinely-broken expressions that already returnednull.Fixes #3306.
-
-
ea32ec7: feat(formula,lint): advisory type-soundness warnings for formula/predicate expressions (#1928 tier 4)
Closes the last open guardrail from #1928. A
Field.formulaor record-scoped predicate that uses a text or boolean field with an arithmetic (+ - * / %) or ordering (< > <= >=) operator against a number faults the runtime overload and silently evaluates tonull(e.g.record.title * 2,record.is_active + 1). The build now surfaces this as a non-blocking warning with the offending field and a corrective message.Honours the ADR-0032 design law — the checker only flags what the runtime would also fail:
- Number / currency / percent / date / datetime fields are declared
dyn, so the cases the runtime rescues never warn —record.amount / 100(the #1930registerOperatorfix),record.due == today()and numeric-string / ISO-date values (the string-hydration retry), and numeric-codedselectoption values. - Equality (
==/!=) is excluded: a heterogeneous equality is runtime-safe (evaluates tofalse), never a fault.
New
firstTypeMismatch(source, fieldCelTypes, scope)export in@objectstack/formula(and an optionalfieldTypeshint onvalidateExpression);@objectstack/lint'svalidateStackExpressionsthreads each object's field types into every checked site:- record-scoped sites (
record.<field>) — formula fields, validation rules, action / hook / sharing predicates; - flattened flow / automation conditions (bare
field) — where flow variables staydynand are never flagged, and equality stays runtime-safe.
Warnings are advisory in
objectstack build/validate(fatal only under--strict), matching the tier-3 channel. - Number / currency / percent / date / datetime fields are declared
-
e0859b1: fix(formula): retire the
jsexpression dialect and fix thehasDialectfalse-positive (#3278)The
jsexpression dialect was declared inExpressionDialectbut never shipped — it existed only as a registry stub with no engine and no author helper (cel/F/P→ CEL,tmpl→ template,cron→ cron; nothing ever emittedjs). Per ADR-0049 (enforce-or-remove) it is removed from the enum; the set is now{cel, cron, template}.Procedural JavaScript is unaffected: it remains the L2 authoring surface — the sandboxed, capability-gated
ScriptBody { language: 'js' }in hook/action bodies — which is a separate enum (hook-body.zod.ts), not an expression dialect.Also fixes a latent bug in
hasDialect: it detected stubs viadialect.startsWith('stub:'), but stubs were registered under their real name, so the check was dead code andhasDialect('js')returned a false-positivetrue. With the stub removed,hasDialectreports only registered real engines, and the registry test now asserts the negative case (hasDialect('js') === false) so the gate can actually go red.No runtime behavior changes for any valid persisted artifact — no producer ever emitted
dialect: 'js'. See the ADR-0058 addendum. -
Updated dependencies [f972574]
-
Updated dependencies [6289ec3]
-
Updated dependencies [22013aa]
-
Updated dependencies [3ad3dd5]
-
Updated dependencies [8efa395]
-
Updated dependencies [3a18b60]
-
Updated dependencies [a8aa34c]
-
Updated dependencies [a3823b2]
-
Updated dependencies [43a3efb]
-
Updated dependencies [524696a]
-
Updated dependencies [bfa3c3f]
-
Updated dependencies [5e3301d]
-
Updated dependencies [46e876c]
-
Updated dependencies [158aa14]
-
Updated dependencies [62a2117]
-
Updated dependencies [d2723e2]
-
Updated dependencies [fefcd54]
-
Updated dependencies [beaf2de]
-
Updated dependencies [369eb6e]
-
Updated dependencies [06ff734]
-
Updated dependencies [b659111]
-
Updated dependencies [5754a23]
-
Updated dependencies [6c270a6]
-
Updated dependencies [668dd17]
-
Updated dependencies [8abf133]
-
Updated dependencies [e0859b1]
-
Updated dependencies [04ecd4e]
-
Updated dependencies [4d5a892]
-
Updated dependencies [16cebeb]
-
Updated dependencies [86d30af]
-
Updated dependencies [8923843]
-
Updated dependencies [a2795f6]
-
Updated dependencies [f16b492]
-
Updated dependencies [4b6fde8]
-
Updated dependencies [2018df9]
-
Updated dependencies [fc5a3a2]
-
Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
-
7125007: Stored
Field.formulafields that compute dates/durations no longer silently evaluate tonull(#3306). Three independent CEL gaps made shipped template formulas (e.g.hr_employee.tenure_years,hr_time_off_request.days) returnnullwith no parse/build/runtime error:-
The null-guard idiom
cond ? <value> : nullnow compiles and evaluates. cel-js's ternary type-unifier rejects a concreteint/double/stringbranch againstnull— so eventrue ? 5 : nullfaulted "Ternary branches must have the same type" and the whole formula nulled. AField.formulais inherently nullable and the catalog blesses both ternary and== null, so this is the canonical "compute value, else blank" shape. An AST pre-pass (mirroring the #3183 temporal-equality rewrite) wraps the non-null branch indyn(...)— value-preserving, null-branch-only, idempotent — so it type-checks and runs. Applied incompile(),evaluate(), and the build soundness check alike. -
floor(x)/ceil(x)are now registered (parallel toround/abs) and advertised in the catalog. They round toward −∞ / +∞, sofloor(-1.2) == -2— NOT interchangeable with integer division's round-toward-zero. Previouslyfloor(...)faultedfound no matching overloadand the formula nulled. -
Date arithmetic is now a build-time ERROR instead of a silent runtime
null.record.end_date - record.start_date + 1,today() + 30,record.date + ntype-check clean (operands aredyn) but always fault at runtime and never recover (a date string is not numeric, so hydration can't rescue it). The build soundness check now typesdate/datetimefields asgoogle.protobuf.Timestampand flags date/duration arithmetic against a number with a corrective message pointing atdaysBetween(a, b)/daysFromNow(n)/addDays(d, n)/addMonths(d, n). Sound by construction — ordering (date < today(),date < "2026-01-01"string-lex), equality (#3183), and string concatenation ("Due: " + date) are all runtime-tolerated and never flagged; only arithmetic against a number is. A!= nullguard on a date field no longer masks the inner fault (== nullno-op overloads registered in the check-only env).
Heads-up for downstream: (3) adds a NEW build-time error. A stored formula or predicate doing arithmetic on a
date/datetimefield (end - start + 1,today() + 30) that previously built (and nulled at runtime) will now failobjectstack build/validateStackExpressionswith a message telling you to usedaysBetween/daysFromNow/addDays. This only fires for genuinely-broken expressions that already returnednull.Fixes #3306.
-
- Updated dependencies [6289ec3]
- Updated dependencies [8efa395]
- Updated dependencies [bfa3c3f]
- Updated dependencies [62a2117]
- Updated dependencies [06ff734]
- @objectstack/spec@16.0.0-rc.1
-
6b51346: feat(formula):
dateField == today()now matches — AST temporal-comparison rewrite (#3183)Behavior change (the fix): a
Field.datecompared with==/!=against a temporal function now matches on the calendar day. Previously it silently returned the wrong answer —record.due_date == today()was alwaysfalse(and!= today()alwaystrue) even for a same-day record, because aField.datereads back as aYYYY-MM-DDstring (ADR-0053 Phase 1) and cel-js's equality (overloads.jsisEqual) treats a string and a timestamp as unequal without consulting any overload.celEngine.evaluatenow rewrites the parsed AST: for each==/!=whose one operand istoday()/daysFromNow()/daysAgo()/now(), the field operand is wrapped indate(...)(the stdlib coercion), then the expression is serialized and evaluated. Sorecord.due_date == today()runs asdate(record.due_date) == today().- Per-occurrence, not per-field:
record.d == "2026-06-20" || record.d == today()keeps the string-literal comparison intact while fixing the temporal one. - Type-blind-safe:
date()degrades gracefully — an already-Date(Field.datetime) operand passes through; a non-date string or null →Invalid Date→ the comparison staysfalse, exactly as before. No field-type information is needed, and no currently-correct result is worsened. - Cheap: the rewrite only reserializes when such a comparison is present
(a plain-
includesgate skips the rest), and is memoized per source string.
Applies to every interpreter site — read-time
Field.formula, default values, validation rules, hook conditions, and flow conditions — since all route throughcelEngine.evaluate. RLS/sharing conditions are unaffected: they compile viacel-to-filter, which already rejects function calls as a loud authoring error.Supersedes the #3192 advisory lint. That build-time warning (
checkTemporalDateEquality) flaggeddateField == today()as a silent-miss; with the runtime fixed it would be a false alarm, so it (and thetemporalEqualityFieldshelper it used) is removed. Authors can now write the naturalrecord.due_date == today()directly; thedate(...)/daysBetween(...) == 0/ range idioms all keep working. - Per-occurrence, not per-field:
-
80273c8: feat(formula): warn when a
datefield is compared to a temporal function with==/!=(#3183)A
Field.datedeserializes as aYYYY-MM-DDstring (ADR-0053 Phase 1), and cel-js's equality hard-codesstring == <timestamp>tofalse— it returnsfalsefor a string left operand without ever consulting a registered overload, and refuses cross-type object equality (@marcbachmann/cel-jsoverloads.jsisEqual). So the most natural "is it due today" predicate —record.due_date == today() // silently false, even when due_date IS today record.due_date != today() // silently true for a same-day record— compiles clean, throws nothing, and silently never matches. Same silent-miss family as #1928; timezone-independent (fails identically at UTC) and cross-cutting (formulas, validation, RLS, flow/action/sharing/hook predicates).
cel-js gives no operator-layer hook to fix the comparison, so this adds a build-time advisory warning (the established ADR-0032 guardrail strategy) rather than a runtime behavior change.
validateExpressionreuses the sharedExprSchemaHint.fieldTypes(the same per-field type map the #1928 tier-4 soundness check already threads through@objectstack/lint) to flag a==/!=between adatefield (record./previous./bare) andtoday()/daysFromNow()/daysAgo()/now(), with a self-correcting message pointing at the working idioms:date(record.d) == today(), a range (>= … && <= …), ordaysBetween(today(), record.d) == 0.Warning severity — never fails the build (the write/validation path may carry a real
Date). Restricted totype: 'date'(unambiguously a string);datetimeis excluded to avoid false positives. Ordering operators (>=/<=/</>) already work — cel-js throws for them, tripping the engine's existing string-hydration retry — so they are not flagged.A runtime fix (normalizing the peer of a temporal operand in the data layer) remains tracked in #3183; a naive "hydrate date fields to
Date" version would trade this silent-miss for another (breakingdateField == "2026-06-20"), so it needs its own design. -
ea32ec7: feat(formula,lint): advisory type-soundness warnings for formula/predicate expressions (#1928 tier 4)
Closes the last open guardrail from #1928. A
Field.formulaor record-scoped predicate that uses a text or boolean field with an arithmetic (+ - * / %) or ordering (< > <= >=) operator against a number faults the runtime overload and silently evaluates tonull(e.g.record.title * 2,record.is_active + 1). The build now surfaces this as a non-blocking warning with the offending field and a corrective message.Honours the ADR-0032 design law — the checker only flags what the runtime would also fail:
- Number / currency / percent / date / datetime fields are declared
dyn, so the cases the runtime rescues never warn —record.amount / 100(the #1930registerOperatorfix),record.due == today()and numeric-string / ISO-date values (the string-hydration retry), and numeric-codedselectoption values. - Equality (
==/!=) is excluded: a heterogeneous equality is runtime-safe (evaluates tofalse), never a fault.
New
firstTypeMismatch(source, fieldCelTypes, scope)export in@objectstack/formula(and an optionalfieldTypeshint onvalidateExpression);@objectstack/lint'svalidateStackExpressionsthreads each object's field types into every checked site:- record-scoped sites (
record.<field>) — formula fields, validation rules, action / hook / sharing predicates; - flattened flow / automation conditions (bare
field) — where flow variables staydynand are never flagged, and equality stays runtime-safe.
Warnings are advisory in
objectstack build/validate(fatal only under--strict), matching the tier-3 channel. - Number / currency / percent / date / datetime fields are declared
-
e0859b1: fix(formula): retire the
jsexpression dialect and fix thehasDialectfalse-positive (#3278)The
jsexpression dialect was declared inExpressionDialectbut never shipped — it existed only as a registry stub with no engine and no author helper (cel/F/P→ CEL,tmpl→ template,cron→ cron; nothing ever emittedjs). Per ADR-0049 (enforce-or-remove) it is removed from the enum; the set is now{cel, cron, template}.Procedural JavaScript is unaffected: it remains the L2 authoring surface — the sandboxed, capability-gated
ScriptBody { language: 'js' }in hook/action bodies — which is a separate enum (hook-body.zod.ts), not an expression dialect.Also fixes a latent bug in
hasDialect: it detected stubs viadialect.startsWith('stub:'), but stubs were registered under their real name, so the check was dead code andhasDialect('js')returned a false-positivetrue. With the stub removed,hasDialectreports only registered real engines, and the registry test now asserts the negative case (hasDialect('js') === false) so the gate can actually go red.No runtime behavior changes for any valid persisted artifact — no producer ever emitted
dialect: 'js'. See the ADR-0058 addendum. -
Updated dependencies [f972574]
-
Updated dependencies [22013aa]
-
Updated dependencies [3ad3dd5]
-
Updated dependencies [3a18b60]
-
Updated dependencies [a8aa34c]
-
Updated dependencies [a3823b2]
-
Updated dependencies [43a3efb]
-
Updated dependencies [524696a]
-
Updated dependencies [5e3301d]
-
Updated dependencies [46e876c]
-
Updated dependencies [158aa14]
-
Updated dependencies [d2723e2]
-
Updated dependencies [fefcd54]
-
Updated dependencies [beaf2de]
-
Updated dependencies [369eb6e]
-
Updated dependencies [b659111]
-
Updated dependencies [5754a23]
-
Updated dependencies [6c270a6]
-
Updated dependencies [668dd17]
-
Updated dependencies [8abf133]
-
Updated dependencies [e0859b1]
-
Updated dependencies [04ecd4e]
-
Updated dependencies [4d5a892]
-
Updated dependencies [16cebeb]
-
Updated dependencies [86d30af]
-
Updated dependencies [8923843]
-
Updated dependencies [a2795f6]
-
Updated dependencies [f16b492]
-
Updated dependencies [4b6fde8]
-
Updated dependencies [2018df9]
-
Updated dependencies [fc5a3a2]
- @objectstack/spec@16.0.0-rc.0
- @objectstack/spec@15.1.1
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [3fe9df1]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [4109153]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [627f225]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- @objectstack/spec@15.1.0
- Updated dependencies [28b7c28]
- Updated dependencies [13749ec]
- Updated dependencies [e62c233]
- Updated dependencies [ed61c9b]
- Updated dependencies [31d04d4]
- @objectstack/spec@15.0.0
- Updated dependencies [16b4bf6]
- Updated dependencies [16b4bf6]
- Updated dependencies [10e8983]
- Updated dependencies [607aaf4]
- Updated dependencies [bb71321]
- @objectstack/spec@14.8.0
- Updated dependencies [d6a72eb]
- @objectstack/spec@14.7.0
- Updated dependencies [609cb13]
- Updated dependencies [ce6d151]
- @objectstack/spec@14.6.0
- Updated dependencies [526805e]
- Updated dependencies [d79ca07]
- Updated dependencies [33ebd34]
- Updated dependencies [c044f08]
- Updated dependencies [01274eb]
- @objectstack/spec@14.5.0
- Updated dependencies [7953832]
- Updated dependencies [82e745e]
- Updated dependencies [f3035bd]
- Updated dependencies [82c0d94]
- Updated dependencies [7449476]
- @objectstack/spec@14.4.0
- Updated dependencies [2a71f48]
- Updated dependencies [02f6af4]
- Updated dependencies [c1064f1]
- @objectstack/spec@14.3.0
- Updated dependencies [ac8f029]
- Updated dependencies [4ab9958]
- @objectstack/spec@14.2.0
- Updated dependencies [5a8465f]
- Updated dependencies [7f8620b]
- Updated dependencies [82ba3a6]
- @objectstack/spec@14.1.0
- Updated dependencies [0a8e685]
- Updated dependencies [afa8115]
- Updated dependencies [80f12ca]
- Updated dependencies [e2fa074]
- Updated dependencies [23c8668]
- Updated dependencies [29f017d]
- Updated dependencies [216fa9a]
- Updated dependencies [6c22b12]
- @objectstack/spec@14.0.0
-
6d83431: ADR-0090 P1 breaking wave — permission model v2 concept convergence.
Pre-launch one-step renames and secure defaults (no compatibility aliases, per ADR-0090 D3/D4 superseding ADR-0057 D5/D7's alias discipline):
sys_role→sys_position,sys_user_role→sys_user_position(fieldrole→position),sys_role_permission_set→sys_position_permission_set(fieldrole_id→position_id);RoleSchema/defineRole→PositionSchema/definePositionwith noparent(positions are flat; hierarchy lives on the business-unit tree).ExecutionContext.roles[]→positions[]; the EvalUser/CEL contractcurrent_user.roles→current_user.positions(formula validators updated); stack propertyroles:→positions:; metadata kindsrole/profile→position(profile kind removed).isProfileremoved fromPermissionSetSchema(ADR-0090 D2);isDefaultnarrows to an install-time suggestion;appDefaultProfileName→appDefaultPermissionSetName(isDefault-only).- OWD enum drops legacy aliases
read/read_write/full; new optionalexternalSharingModel(external dial,privatedefault) lands as P1 spec shape (ADR-0090 D11). - Secure default (D1): a custom object with an owner field and NO
sharingModelnow resolvesprivate(was: fully public). System objects keep their explicit posture. Unrecognised stored values fail closed. - ExecutionContext gains the P1 principal-taxonomy shape (D10):
principalKind/audience/onBehalfOf(optional, semantics phase in later). - Sharing recipients:
role→position(expanded viasys_user_position∪ the better-auth membership transition source);role_and_subordinatesremoved —unit_and_subordinatesnow expands the business-unit subtree (finishes ADR-0057 D5's re-homing).
- Updated dependencies [6d83431]
- Updated dependencies [01917c2]
- Updated dependencies [b271691]
- Updated dependencies [a5a1e41]
- Updated dependencies [466adf6]
- Updated dependencies [5be00c3]
- Updated dependencies [466adf6]
- Updated dependencies [2bee609]
- Updated dependencies [fc7e7f7]
- @objectstack/spec@13.0.0
- Updated dependencies [6cebf22]
- @objectstack/spec@12.6.0
- Updated dependencies [8b3d363]
- @objectstack/spec@12.5.0
- Updated dependencies [60dc3ba]
- @objectstack/spec@12.4.0
- Updated dependencies [e7eceec]
- @objectstack/spec@12.3.0
- Updated dependencies [fce8ff4]
- Updated dependencies [3962023]
- Updated dependencies [2bb193d]
- Updated dependencies [0426d27]
- Updated dependencies [da807f7]
- @objectstack/spec@12.2.0
- Updated dependencies [93e6d02]
- @objectstack/spec@12.1.0
- Updated dependencies [a8df396]
- Updated dependencies [e695fe0]
- Updated dependencies [7c09621]
- Updated dependencies [7709db4]
- Updated dependencies [2082109]
- Updated dependencies [7c09621]
- Updated dependencies [9860de4]
- Updated dependencies [069c205]
- @objectstack/spec@12.0.0
- Updated dependencies [6a9397e]
- Updated dependencies [c0efe5d]
- @objectstack/spec@11.10.0
- Updated dependencies [d3595d9]
- @objectstack/spec@11.9.0
- @objectstack/spec@11.8.0
- Updated dependencies [5178906]
- @objectstack/spec@11.7.0
- @objectstack/spec@11.6.0
- Updated dependencies [6ee4f04]
- Updated dependencies [c1e3a65]
- @objectstack/spec@11.5.0
- Updated dependencies [5821c51]
- Updated dependencies [a0fce3f]
- @objectstack/spec@11.4.0
- Updated dependencies [58e8e31]
- Updated dependencies [b4a5df0]
- @objectstack/spec@11.3.0
- Updated dependencies [d0f4b13]
- Updated dependencies [302bdab]
- @objectstack/spec@11.2.0
- Updated dependencies [ecf193f]
- Updated dependencies [51bec81]
- Updated dependencies [3e593a7]
- Updated dependencies [63d5403]
- @objectstack/spec@11.1.0
-
ef3ed67: Formula field typing:
inferExpressionType()+ a declaredreturnType.@objectstack/formula: newinferExpressionType()(and lower-levelinferCelType()) surfaces the cel-js type-checker's result for a CEL value/formula expression, mapped tonumber | text | boolean | date | unknown. Conservative — twodynoperands stayunknown; typed literals/stdlib returns pin a concrete type.@objectstack/spec:FieldSchemagains an optionalreturnType(number|text|boolean|date) so a formula field can carry its declared value type (the way Salesforce/Airtable do), letting consumers (dataset measures, formatting, validation) read a declared type instead of re-parsing the expression.
- Updated dependencies [ab5718a]
- Updated dependencies [4845c12]
- Updated dependencies [c1a754a]
- Updated dependencies [6fbe91f]
- Updated dependencies [715d667]
- Updated dependencies [5eef4cf]
- Updated dependencies [72759e1]
- Updated dependencies [6c4fbd9]
- Updated dependencies [ef3ed67]
- Updated dependencies [cd51229]
- Updated dependencies [7697a0e]
- Updated dependencies [e7e04f1]
- Updated dependencies [cfd5ac4]
- Updated dependencies [2be5c1f]
- Updated dependencies [ad143ce]
- Updated dependencies [5c4a8c8]
- Updated dependencies [3afaeed]
- Updated dependencies [8801c02]
- Updated dependencies [3d04e06]
- Updated dependencies [4a84c98]
- Updated dependencies [d980f0d]
- Updated dependencies [a658523]
- Updated dependencies [82ff91c]
- Updated dependencies [638f472]
- @objectstack/spec@11.0.0
- @objectstack/spec@10.3.0
- Updated dependencies [b496498]
- @objectstack/spec@10.2.0
- Updated dependencies [49da36e]
- Updated dependencies [ac79f16]
- @objectstack/spec@10.1.0
- cfd86ce: ADR-0058 — expression & predicate surface unification. Adds the canonical
CEL→FilterCondition pushdown compiler in
@objectstack/formula(compileCelToFilter,isPushdownableCel,lowerCelAst) plus an in-memorymatchesFilterConditionbackend (one AST, three backends).plugin-security(RLSusing, via a SQL bridge) andplugin-sharing(celToFilter) cut over to it, retiring the bespoke regex/field-equality front-ends. Compound sharing conditions now compile and enforce end-to-end (closes #1887). The RLScheckclause is now enforced on the write post-image (insert/by-id update), fail-closed. Non-pushdownable predicates (arithmetic, functions, subqueries, cross-object) are an authoring compile error, never silently dropped (ADR-0049/0055).
-
48a307a: build: validate UI action
visible/disabledpredicates at compile timeExtends the ADR-0032 build-time expression check to cover action
visibleanddisabledpredicates (stack-level and object-attached), evaluated record-scoped like validation rules. A record-header / row action'svisibleis evaluated byActionEngineagainst{ record, recordId, objectName, user, … }with fail-closed semantics, so a bare field reference (!doneinstead of!record.done) throws at runtime and the action is silently hidden on every record — the trap behind the #2183 "Mark Done never hides" debugging hunt.os buildnow reports it as an error with the correctiverecord.<field>message instead of letting it ship.@objectstack/formula:ctxandfeaturesare added to the record-scope namespace roots (alongside the existinguser,data,context, …) so the ambient globals real action predicates use (record.id == ctx.user.id,features.multiOrgEnabled) are not false-positives. Verified against the full monorepo build (every example + platform bundle still compiles clean). -
25fc0e4: build: extend ADR-0032 predicate validation to all flat record-scoped sites
Builds on the action-predicate guard.
os buildnow also validates these record-scoped predicates for bare field references (statusinstead ofrecord.status), which otherwise evaluate to nothing at runtime and silently mis-behave:- field conditional rules —
requiredWhen,readonlyWhen,conditionalRequired,visibleWhen(server-enforced; a broken one is fail-open — the required/readonly rule just never fires); - sharing-rule
condition(security-critical — decides which rows a principal sees); - lifecycle hook
condition(skips the handler when false); - nested
whenonconditionalvalidation rules (previously only the top-level rule predicate was checked).
@objectstack/formula: addsparentto the record-scope namespace roots — master-detail inline grids inject the header record asparentfor a child field'sreadonlyWhen/requiredWhen(ADR-0036, #1581), soparent.statusis legitimate, not a bare ref. Verified against the full monorepo build (76 tasks clean).Not yet covered (separate follow-up — needs a recursive view/page tree walker and per-node scope classification): deeply-nested UI visibility predicates (
viewelement/sectionvisibleOn/condition,pagecomponentvisibility), object field-groupvisibleOn, and app-navvisible(user/feature-scoped, not record-scoped). - field conditional rules —
-
Updated dependencies [d7ff626]
-
Updated dependencies [2a1b16b]
-
Updated dependencies [e16f2a8]
-
Updated dependencies [e411a82]
-
Updated dependencies [a581385]
-
Updated dependencies [220ce5b]
-
Updated dependencies [3efe334]
-
Updated dependencies [feead7e]
-
Updated dependencies [6ca20b3]
-
Updated dependencies [5f875fe]
-
Updated dependencies [b469950]
- @objectstack/spec@10.0.0
- Updated dependencies [e7f6539]
- Updated dependencies [2365d07]
- Updated dependencies [6595b53]
- Updated dependencies [fa8964d]
- Updated dependencies [36138c7]
- Updated dependencies [a8e4f3b]
- Updated dependencies [4c213c2]
- Updated dependencies [2afb612]
- @objectstack/spec@9.11.0
- 1f88fd9: Add
addDays(date, n)andaddMonths(date, n)to the CEL standard library — shift an arbitrary date by a (possibly negative) number of days or months. UnlikedaysFromNow, these operate on a given date (the "next service date = last service + cycle" shape).addMonthsclamps to the target month's last day (addMonths(date('2026-01-31'), 1)→ Feb 28, never overflowing into March). Both coerce their inputs (Date | ISO string | epoch) and typenasdynso a record number field arriving as adoubledoesn't faultno such overload(#1928).
- Updated dependencies [db02bd5]
- Updated dependencies [641675d]
- Updated dependencies [94e9040]
- Updated dependencies [1f88fd9]
- Updated dependencies [1f88fd9]
- @objectstack/spec@9.10.0
- @objectstack/spec@9.9.1
-
d99a75a: feat(formula): timezone-aware
today()/daysFromNow()/daysAgo()(ADR-0053 Phase 2)These are now calendar-day functions resolved in a reference timezone, threaded from
ExecutionContext.timezone(#1978) throughEvalContext.timezoneinto the CEL stdlib. Each returns the reference-tz calendar day expressed as a UTC-midnightDate(ADR-0053 decision D1) — the one representation consistent with howField.datestrings hydrate, how the SQL driver normalizes date filters, and how Phase 1 stores dates. Sorecord.close_date == daysFromNow(30)now matches in-memory too, not just at the storage boundary. The timezone calculation usesIntl.DateTimeFormat(DST-safe; no hand-rolled offset math).⚠️ Behavior change:daysFromNow(n)/daysAgo(n)previously kept the wall-clock time ofnow(e.g.daysFromNow(30)at10:00Z→…T10:00:00Z). They now drop the time and return the calendar day at midnight (…T00:00:00Z) — the ADR-0053 "defect #3" fix.today()is unchanged at UTC (it already truncated to start-of-day). For a genuine sub-day offset use the documented escape hatchnow() + duration("Nh").With no reference timezone configured the zone resolves to
UTC, sotoday()is byte-for-byte unchanged; only thedaysFromNow/daysAgomidnight-truncation differs from before.objectqlthreadsexecCtx.timezoneinto read-time formula evaluation (applyFormulaPlan) and default-value expressions (applyFieldDefaults).Part of #1980. (Consuming a non-UTC reference timezone end-to-end also needs the
localizationsettings manifest noted in #1978.) -
575448d: feat(formula,email): render
datetimein a reference timezone (ADR-0053 Phase 2)datetimetemplate holes now render in a reference timezone's wall-clock when one is supplied, at the presentation boundary — storage stays UTC.- Formula template engine — the
datetimeformatter takes the reference timezone fromEvalContext.timezone(threaded in #1980) and passes it toIntl.DateTimeFormat.{{ ts | datetime }}renders in that zone;{{ ts | datetime:iso }}stays UTC (machine-readable). Calendar-daydaterendering is intentionally unchanged (tz-naive — aField.datehas no zone). New exportedformatValue(name, value, arg, { locale, timeZone })makes the whitelisted formatters reusable outside the full CEL template engine. - Email pipeline —
plugin-email's renderer previously bypassed the formatter pipeline (String()only), so a datetime went out as raw ISO. Email holes now accept the shared formula formatters —{{ order.total | currency }},{{ ts | datetime }}— reusingformatValue(single source of truth), while keeping the engine's HTML-escaping and{{{ }}}raw-output semantics.SendTemplateInput.timezone(mirroring the existinglocale) flows into rendering so an email's datetime shows the recipient's wall-clock.
- Formula template engine — the
- Updated dependencies [84249a4]
- Updated dependencies [11af299]
- Updated dependencies [d5774b5]
- Updated dependencies [134043a]
- Updated dependencies [90108e0]
- Updated dependencies [9afeb2d]
- Updated dependencies [6bec07e]
- Updated dependencies [601cc11]
- Updated dependencies [575448d]
- @objectstack/spec@9.9.0
-
c17d2c8: feat(formula): register the CEL functions the authoring catalog advertises (daysBetween, abs, round, min, max, upper, lower, contains, startsWith, endsWith, matches, len, isEmpty, date, datetime)
introspectScope/CEL_STDLIB_FUNCTIONSadvertised 25 functions to authors (incl. AI), but only 8 were registered — 14 faulted at runtime (daysBetween,abs,round,min,max,upper,lower,len,isEmpty,contains,startsWith,endsWith,matches, plusdate/datetime). Authors were told to call functions that don't exist (e.g.daysBetweenfor "days remaining").Register the genuinely-useful set in
registerStdLibwith dyn-lenient signatures (so aField.datearriving as a string still works) and internal coercion, and reconcile the catalog so every advertised entry resolves — guarded by a test that evaluates everyCEL_STDLIB_FUNCTIONSentry. Pure additions; no behavior change to existing expressions.
- Updated dependencies [97c55b3]
- Updated dependencies [1b1f490]
- @objectstack/spec@9.8.0
-
ff0a87a: feat(validate): flag bare field references in record-scoped CEL sites at build time
Heads-up for downstream: this adds a NEW build-time error. A
Field.formulaor validation predicate that references a field bare (amountinstead ofrecord.amount) now failsobjectstack compile. These expressions were already silently broken at runtime (they evaluated tonull/ never fired), so this is a fix that surfaces a latent bug — but a stack carrying one will go from "builds, silently wrong" to "fails the build" on upgrade. The error message states the exact correction (write record.<field>).A
Field.formulaand an object validation predicate evaluate against therecordnamespace only — there is no field flattening — so a bare top-level identifier (amount,status) resolves to nothing and the expression silently evaluates tonull/ never fires. This is the silent-at-runtime class behind the broken example-crm formulas (#1927) and is exactly what AI authors get wrong.validateExpressionnow takes an evaluationscopeand, forscope: 'record', reports a bare reference with the corrective form (write record.<field>). The check is schema-free and acts only on cel-js'sUnknown variablefault, so it cannot false-positive on arithmetic/comparison/null-guard type overloads. Flow and automation conditions keep the defaultscope: 'flattened'— the record's fields ARE spread to top-level there (alongside flow variables), so bare refs are correct and are NOT flagged.objectstack compilewiresrecordscope for field formulas and validation predicates; flow conditions stay flattened.
-
82c7438: fix(formula): register mixed
double <op> intarithmetic overloads so number-field formulas computecel-js types a record field number as
doubleand a bare integer literal asint, and ships overloads only for matching numeric pairs. So an everyday formula likerecord.amount / 100orrecord.price * 2faulted at runtime (no such overload: dyn<double> / int); the engine caught the fault and the formula silently evaluated tonull— passing build, empty at runtime (#1928).The CEL engine now registers the missing
double <op> int/int <op> doubleoverloads for+ - * / %, computing the result as adouble(CEL's mixed-numeric promotion). Pureint op intis untouched, so integer division (7 / 2 == 3) keeps its semantics — the overloads fire only when the operands are genuinely adoubleand anint. Authors no longer need the/ 100.0float-literal workaround. -
417b6ac: feat(validate): advisory did-you-mean warnings for likely field typos in flow conditions
Adds a non-blocking warning channel to build-time expression validation (#1928 tier 3). Flow / automation conditions flatten the record's fields to top-level, so a bare
statusis correct — but a bare NON-field identifier is either a flow variable or a typo. When it is a near-miss of a known field (edit distance), the build now emits adid you mean \status`?warning instead of staying silent, WITHOUT failing the build (a genuine flow variable won't be close to a field name, so it stays quiet).ExprValidationResultgains awarningsarray andExprIssueaseverity;objectstack compile` prints warnings and only fails on errors. This closes the silent-skip gap for misspelled trigger-condition fields (the #1877 family) without the false-positive risk of a hard gate.- @objectstack/spec@9.7.0
-
bb00a50: fix(formula): catch unknown functions in CEL conditions at build (#1877)
compile()discarded cel-js's type-check verdict becausecheck()returns aTypeCheckResultobject ({ valid, error }), not an array — so theArray.isArray(checkErrors)guard never matched. A condition calling an unknown function (PRIOR(status), a typo'disBlnk(...)) type-checks asfound no matching overload, but that result never surfaced, soobjectstack compile,registerFlow, and thevalidate_expressiontool all accepted the predicate, which then silently no-op'd the flow at runtime. Now reads the documented{ valid, error }shape, closing the gap for flow conditions, validation rules, and field formulas at once. -
Updated dependencies [d1e930a]
-
Updated dependencies [71578f2]
-
Updated dependencies [5e3a301]
-
Updated dependencies [5db2742]
- @objectstack/spec@9.6.0
- Updated dependencies [ee72aae]
- @objectstack/spec@9.5.1
- Updated dependencies [d08551c]
- Updated dependencies [707aeed]
- Updated dependencies [7a103d4]
- Updated dependencies [4b01250]
- @objectstack/spec@9.5.0
- Updated dependencies [060467a]
- Updated dependencies [0856476]
- Updated dependencies [b678d8c]
- Updated dependencies [b678d8c]
- Updated dependencies [b678d8c]
- @objectstack/spec@9.4.0
- Updated dependencies [1ada658]
- Updated dependencies [3219191]
- Updated dependencies [290f631]
- Updated dependencies [50b7b47]
- Updated dependencies [f15d6f6]
- Updated dependencies [f8684ea]
- Updated dependencies [b4765be]
- @objectstack/spec@9.3.0
- Updated dependencies [2f57b75]
- Updated dependencies [2f57b75]
- @objectstack/spec@9.2.0
- Updated dependencies [b9062c9]
- @objectstack/spec@9.1.0
- Updated dependencies [1817845]
- @objectstack/spec@9.0.1
- Updated dependencies [4c3f693]
- Updated dependencies [0bf39f1]
- Updated dependencies [f533f42]
- Updated dependencies [1c83ee8]
- @objectstack/spec@9.0.0
- @objectstack/spec@8.0.1
- Updated dependencies [a46c017]
- Updated dependencies [b990b89]
- Updated dependencies [99111ec]
- Updated dependencies [d5a8161]
- Updated dependencies [5cf1f1b]
- Updated dependencies [9ef89d4]
- Updated dependencies [3306d2f]
- Updated dependencies [bc44195]
- Updated dependencies [9e2e229]
- @objectstack/spec@8.0.0
- @objectstack/spec@7.9.0
-
f01f9fa: fix(formula): hydrate ISO date/datetime strings on CEL
no such overloadfault (#1530)Date-typed formula fields and date predicates always evaluated to
null:Field.date/Field.datetimeserialize to ISO strings, and cel-js compared the raw string against thegoogle.protobuf.Timestampfromtoday()/now()/daysFromNow(), raisingno such overload(swallowed to null). The existing numeric-string fault-retry (#1534) is now extended to also coerce strict ISO-8601 date/date-time strings toDatebefore retrying once, fixing every caller (formula fields, flow conditions, validation/workflow predicates). Hydration runs only after a fault, so clean expressions are never re-interpreted and genuine non-temporal strings still fault loudly. -
Updated dependencies [06f2bbb]
-
Updated dependencies [36719db]
-
Updated dependencies [424ab26]
- @objectstack/spec@7.8.0
-
825ab06: fix(formula): hydrate string-serialized numeric fields in CEL comparisons (#1534)
Numeric fields that serialize as strings —
Field.rating(allowHalf)→"5.0",Field.currency(scale)→"250000.00",Field.percent— made comparisons likerecord.rating >= 4fault under strict CEL withno such overload: dyn >= int. In flow decision/edge conditions this silently dead-ended the run (no edge matched), and in objectqlapplyFormulaPlanit swallowed tonull.The CEL engine now retries an evaluation once with purely-numeric strings hydrated to numbers, but only after a
no such overloadfault — so a comparison that already type-checks is never re-interpreted (a zip like"02134"stays a string inrecord.zip == "02134"). Because both the automation condition path (service-automationevaluateCondition) and the objectql formula path route throughExpressionEngine.evaluate, both are fixed consistently. A genuinely non-numeric operand (e.g.record.rating >= 4whereratingis"high") still faults loudly rather than being silently rescued. -
Updated dependencies [b391955]
-
Updated dependencies [f06b64e]
-
Updated dependencies [023bf93]
- @objectstack/spec@7.7.0
-
c4a4cbd: ADR-0032 (phase 1): validate-by-default expression layer — no silent failure.
Kills the #1491 class where a malformed predicate (e.g. the
{record.x}template-brace-in-CEL mistake) silently evaluated tofalseand made a flow "fire" with no effect:- service-automation: flow
evaluateConditionno longer swallows CEL failures tofalse— it throws an attributed, corrective error; andregisterFlownow parse-validates every predicate (start/decision/edge condition) at registration, failing loudly with the offending location + source + the fix. - formula: new shared validator —
validateExpression(role, src, schema?),introspectScope,CEL_STDLIB_FUNCTIONS— with schema-aware field-existence- did-you-mean. The
{{ }}template engine gains a formatter whitelist (currency/number/percent/date/datetime/truncate/upper/lower/default/…) with defined value→string semantics; arbitrary logic in holes is rejected. Plain{{ path }}stays back-compatible.
- did-you-mean. The
- cli:
objectstack compilevalidates every flow / validation-rule / field-formula predicate against the resolved object schema and fails the build with located, corrective messages. - service-ai: new agent-callable
validate_expressiontool so authoring agents self-correct before committing. - spec: fix the
FlowSchemaJSDoc example that taught the badcondition: "{amount} < 500"single-brace form.
- service-automation: flow
- Updated dependencies [955d4c8]
- Updated dependencies [c4a4cbd]
- Updated dependencies [b046ec2]
- Updated dependencies [2170ad9]
- Updated dependencies [02d6359]
- Updated dependencies [7648242]
- Updated dependencies [8fa1e7f]
- Updated dependencies [55866f5]
- Updated dependencies [60f9c45]
- @objectstack/spec@7.6.0
- @objectstack/spec@7.5.0
- @objectstack/spec@7.4.1
- Updated dependencies [23c7107]
- Updated dependencies [c72daad]
- Updated dependencies [f115182]
- Updated dependencies [2faf9f2]
- Updated dependencies [2faf9f2]
- Updated dependencies [2faf9f2]
- Updated dependencies [58b450b]
- Updated dependencies [82eb6cf]
- Updated dependencies [13d8653]
- Updated dependencies [ff3d006]
- Updated dependencies [5e831de]
- @objectstack/spec@7.4.0
- Updated dependencies [5e7c554]
- @objectstack/spec@7.3.0
- @objectstack/spec@7.2.1
- @objectstack/spec@7.2.0
- Updated dependencies [47a92f4]
- @objectstack/spec@7.1.0
- Updated dependencies [74470ad]
- Updated dependencies [d29617e]
- Updated dependencies [dc72172]
- @objectstack/spec@7.0.0
- @objectstack/spec@6.9.0
- @objectstack/spec@6.8.1
- Updated dependencies [6e88f77]
- Updated dependencies [c8b9f57]
- @objectstack/spec@6.8.0
- @objectstack/spec@6.7.1
- Updated dependencies [430067b]
- Updated dependencies [4f9e9d4]
- @objectstack/spec@6.7.0
- Updated dependencies [a49cfc2]
- @objectstack/spec@6.6.0
- @objectstack/spec@6.5.1
- @objectstack/spec@6.5.0
- Updated dependencies [f8651cc]
- Updated dependencies [f8651cc]
- Updated dependencies [0bf6f9a]
- @objectstack/spec@6.4.0
- @objectstack/spec@6.3.0
- Updated dependencies [b4c74a9]
- @objectstack/spec@6.2.0
- @objectstack/spec@6.1.1
- Updated dependencies [93c0589]
- @objectstack/spec@6.1.0
- Updated dependencies [629a716]
- Updated dependencies [dbc4f7d]
- Updated dependencies [944f187]
- @objectstack/spec@6.0.0
- Updated dependencies [bab2b20]
- Updated dependencies [fa011d8]
- Updated dependencies [b806f58]
- @objectstack/spec@5.2.0
- Updated dependencies [75f4ee6]
- Updated dependencies [823d559]
- @objectstack/spec@5.1.0
- Updated dependencies [2f9073a]
- @objectstack/spec@5.0.0
- Updated dependencies [2869891]
- @objectstack/spec@4.2.0
- @objectstack/spec@4.1.1
- Updated dependencies [2108c30]
- Updated dependencies [23db640]
- @objectstack/spec@4.1.0
- 15e0df6: chore: unify all package versions to a single patch release
- Updated dependencies [15e0df6]
- @objectstack/spec@4.0.5