You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
While converting the templates repo's roll-ups (framework#1867 follow-up) I boot-verified several stored Field.formula fields that silently evaluate to null on the pinned runtime (@objectstack/formula@15.1.1). No parse error, no runtime error, no objectstack build warning — the field just returns null. This is the exact silent-miss class ADR-0053 / ADR-0032 exist to kill, but for function calls / arithmetic, not the == today() equality case that #3183 just fixed.
Two of these are real, user-facing fields in the shipped hr template:
Field
Expression (origin/main)
Runtime value (15.1.1)
hr_time_off_request.days
(record.end_date - record.start_date) + 1
null for every request
hr_employee.tenure_years
floor((today() - record.hire_date) / 365)
null for every employee
hr_time_off_request.days is the leave-duration in the core time-off workflow — every seeded request shows a blank "Calendar Days".
Verified repro
Fresh objectstack dev --fresh on hr (@objectstack 15.1.1), signed in, GET /api/v1/data/...:
hr_employee.tenure_years → null (all 7 rows), and I could not get a non-null result with any of:
floor((today() - hire_date) / 365) (original)
daysBetween(record.hire_date, today()) ← bare, still null
daysBetween(date(record.hire_date), today()) ← explicit coercion, still null
daysBetween(...) / 365, daysBetween(...) / round(365) ← still null
By contrast, today(), daysFromNow(n), addDays(date, n), round, abs, and date ordering comparisons all work in 15.1.1 (I used daysFromNow/addDays to fix two sibling formulas — see below).
What appears to be going on
daysBetweenis correct in framework HEAD — implemented in packages/formula/src/stdlib.ts:176, listed in CEL_STDLIB_FUNCTIONS (validate.ts:446), and engine-tested (cel-engine.test.ts:449,456, including a date-string field arg). But calling it from a stored formula field on the published 15.1.1 returns null, and templates pin 15.1.1 (the latest published — there is nothing newer to bump to). So either the working daysBetween stored-formula path never shipped in 15.1.1, or the end-to-end stored-formula path has a gap the engine-level unit tests don't cover. Two related operators have no support at all and also null out silently: Timestamp − Timestamp (→ a number) and a floor builtin — both things template authors reach for naturally.
Related prior work: #1302 (original "missing daysBetween/date-arithmetic", closed not_planned then implemented via ADR-0053), #1979 (thread ctx into applyFormulaPlan), #3183 (silent dateField == today(), fixed 2026-07-18). This is the same silent-null family, one layer over: function/arith calls in stored formulas rather than equality.
Ask
Confirm end-to-end + release: add a test that a stored formula field (via engine.find() → applyFormulaPlan, real driver YYYY-MM-DD round-trip) using daysBetween returns the right value, and make sure it ships in the next published @objectstack/formula. Then templates can restore time_off.days / tenure_years.
Template regression coverage: nothing currently catches that a shipped template's formula field evaluates to null on the pinned release — a boot-and-assert-non-null smoke over template formula fields would have caught all of these.
What I already shipped template-side (using functions that DO work in 15.1.1)
Same root class, fixable without daysBetween, so done in templates:
time_off.days and tenure_years are left for this framework fix — they need a working daysBetween (or Timestamp subtraction), which 15.1.1 doesn't provide in stored formulas.
Summary
While converting the
templatesrepo's roll-ups (framework#1867 follow-up) I boot-verified several storedField.formulafields that silently evaluate tonullon the pinned runtime (@objectstack/formula@15.1.1). No parse error, no runtime error, noobjectstack buildwarning — the field just returnsnull. This is the exact silent-miss class ADR-0053 / ADR-0032 exist to kill, but for function calls / arithmetic, not the== today()equality case that #3183 just fixed.Two of these are real, user-facing fields in the shipped
hrtemplate:hr_time_off_request.days(record.end_date - record.start_date) + 1hr_employee.tenure_yearsfloor((today() - record.hire_date) / 365)hr_time_off_request.daysis the leave-duration in the core time-off workflow — every seeded request shows a blank "Calendar Days".Verified repro
Fresh
objectstack dev --freshonhr(@objectstack 15.1.1), signed in,GET /api/v1/data/...:hr_time_off_request.days→null(all 6 seeded rows).hr_employee.tenure_years→null(all 7 rows), and I could not get a non-null result with any of:floor((today() - hire_date) / 365)(original)daysBetween(record.hire_date, today())← bare, still nulldaysBetween(date(record.hire_date), today())← explicit coercion, still nulldaysBetween(...) / 365,daysBetween(...) / round(365)← still nullBy contrast,
today(),daysFromNow(n),addDays(date, n),round,abs, and date ordering comparisons all work in 15.1.1 (I useddaysFromNow/addDaysto fix two sibling formulas — see below).What appears to be going on
daysBetweenis correct in framework HEAD — implemented inpackages/formula/src/stdlib.ts:176, listed inCEL_STDLIB_FUNCTIONS(validate.ts:446), and engine-tested (cel-engine.test.ts:449,456, including a date-string field arg). But calling it from a stored formula field on the published 15.1.1 returns null, and templates pin 15.1.1 (the latest published — there is nothing newer to bump to). So either the workingdaysBetweenstored-formula path never shipped in 15.1.1, or the end-to-end stored-formula path has a gap the engine-level unit tests don't cover. Two related operators have no support at all and also null out silently:Timestamp − Timestamp(→ a number) and afloorbuiltin — both things template authors reach for naturally.Related prior work: #1302 (original "missing daysBetween/date-arithmetic", closed not_planned then implemented via ADR-0053), #1979 (thread ctx into
applyFormulaPlan), #3183 (silentdateField == today(), fixed 2026-07-18). This is the same silent-null family, one layer over: function/arith calls in stored formulas rather than equality.Ask
engine.find()→applyFormulaPlan, real driverYYYY-MM-DDround-trip) usingdaysBetweenreturns the right value, and make sure it ships in the next published@objectstack/formula. Then templates can restoretime_off.days/tenure_years.dateField == today()silently returns false — fix via AST temporal-comparison rewrite in the CEL engine #3183's philosophy —cel-to-filteralready fails closed), not a silent runtimenull. Silent null is the trap here.What I already shipped template-side (using functions that DO work in 15.1.1)
Same root class, fixable without
daysBetween, so done intemplates:compliance_control.is_overdue_for_review:last_assessed_at + freq*86400000→addDays(last_assessed_at, freq) < today()(templates#92, merged).hr_document.is_expiring_soon/expiry_status:today() + 30/60→daysFromNow(30/60)(templates#93).time_off.daysandtenure_yearsare left for this framework fix — they need a workingdaysBetween(orTimestampsubtraction), which 15.1.1 doesn't provide in stored formulas.