fix(formula): null-guarded numeric formulas + floor/ceil + reject date arithmetic (#3306)#3348
Merged
Merged
Conversation
fix(list): route remaining system-field groupings through shared classifier (#2706) objectui@3b2e4d98d904d695a8372c394d46b81673011270
…e arithmetic (#3306) Shipped template `Field.formula` fields (hr_employee.tenure_years, hr_time_off_request.days) silently evaluated to null on the pinned runtime. Three independent CEL-engine gaps, now closed: 1. `cond ? <value> : null` — cel-js's ternary unifier rejects a concrete int/double/string branch against null (even `true ? 5 : null` faulted), so the blessed null-guard idiom nulled. AST pre-pass wraps the non-null branch in dyn() (value-preserving, null-branch-only, idempotent) so it compiles AND evaluates. Applied in compile(), evaluate(), and the build soundness check. Mirrors the #3183 rewriteTemporalEquality pattern. 2. floor/ceil were unregistered — `floor(...)` faulted and nulled. Registered (return int, round toward -inf/+inf, NOT trunc) + added to the catalog (CEL_STDLIB_FUNCTIONS + objectstack-formula SKILL.md). 3. Date arithmetic (`date - date + 1`, `today() + 30`) compiled clean (dyn operands) but always nulls at runtime. The soundness check now types date/datetime fields as Timestamp and flags date/duration ARITHMETIC against a number as a hard build ERROR pointing at daysBetween/daysFromNow/ addDays. Sound by construction: ordering, equality, and string concat of a date field stay clean (runtime-tolerated); a `!= null` guard no longer masks the inner fault (== null no-op overloads in the check-only env). Tests: null-guard idiom (compile+eval, incl. the two hr templates), floor/ceil semantics, date-arith RED on the shipped bug (incl. behind a null guard) and GREEN on every runtime-tolerated date use, stack-level validateStackExpressions. Fixes #3306. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 7 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Jul 20, 2026
Draft
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3306.
Shipped template
Field.formulafields silently evaluated tonullon the pinned runtime — no parse error, no build warning, no runtime error. Root-caused to three independent CEL-engine gaps (full ground-truth diagnosis in #3306); this closes all three.The three gaps
cond ? <number> : null(the null-guard idiom)int≠null— eventrue ? 5 : nullfaulted → whole formula nullsfloor/ceilunregisteredfloor(...)→no matching overload→ nullend - start + 1,today() + 30)daysBetween/daysFromNow/addDaysHow
cond ? x : nullindyn(...)(value-preserving, null-branch-only, idempotent), so a concrete branch unifies withnull. Mirrors the merged BaredateField == today()silently returns false — fix via AST temporal-comparison rewrite in the CEL engine #3183rewriteTemporalEquality; applied incompile(),evaluate(), and the build soundness check.stdlib(returnint, round toward −∞/+∞ — not trunc, sofloor(-1.2) == -2), added toCEL_STDLIB_FUNCTIONS+objectstack-formulaSKILL.md (the anti-driftskill-catalog-syncgate enforces this).date/datetimefields asgoogle.protobuf.Timestampand flags date/duration arithmetic against a number as a hard error. Sound by construction — only arithmetic-against-a-number is flagged; ordering (date < today(),date < "2026-01-01"string-lex), equality (BaredateField == today()silently returns false — fix via AST temporal-comparison rewrite in the CEL engine #3183), and concatenation ("Due: " + date) are runtime-tolerated and stay clean. A!= nullguard no longer masks the inner fault (== nullno-op overloads registered in the check-only env).Result
The two fields from the issue now compute:
Tests
floor(-1.2) == -2, double-arg coercion, catalog drift-guard.time_off.daysbug and behind a!= nullguard; GREEN on all 6 runtime-tolerated date uses; stack-levelvalidateStackExpressionsRED-on-broken / GREEN-on-rewrite.242 formula tests + 239 lint tests green;
@objectstack/formulatypecheck + DTS clean.The template-side rewrites (
hr_employee.tenure_years,hr_time_off_request.days) ship in a companion PR against thetemplatesrepo, plus wiringvalidateStackExpressionsinto templates CI.🤖 Generated with Claude Code