feat(formula): dateField == today() now matches — AST temporal-comparison rewrite (#3183)#3205
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 17 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…arison rewrite (#3183) A `Field.date` reads back as a "YYYY-MM-DD" string (ADR-0053 Phase 1), and cel-js's equality treats a string and a timestamp as unequal without consulting any overload, so `record.due_date == today()` silently returned false (and `!= today()` silently true) even for a same-day record. celEngine.evaluate now rewrites the parsed AST: for each `==`/`!=` whose one operand is today()/daysFromNow()/daysAgo()/now(), the field operand is wrapped in date(...) (the stdlib coercion), then serialized and evaluated — so `record.due_date == today()` runs as `date(record.due_date) == today()`. - Per-occurrence: a mixed `d == "literal" || d == today()` keeps the literal comparison intact while fixing the temporal one. - Type-blind-safe: date() degrades gracefully (already-Date passes through; non-date string / null → Invalid Date → stays false), so no field types are needed and no currently-correct result is worsened. - Cheap: reserializes only when such a comparison is present (plain-includes gate) and memoizes source → rewritten source. AST-based, no regex on input. Covers every interpreter site (formulas, defaults, validation, hooks, flow conditions) via the single evaluate chokepoint. RLS/sharing unaffected (cel-to-filter rejects function calls loudly). Supersedes the #3192 advisory lint (checkTemporalDateEquality + the temporalEqualityFields helper), now removed — with the runtime fixed it would be a false alarm. Flips the #3181 KNOWN GAP characterization test to assert the fix, and adds an objectql end-to-end test (a date formula field read via find()). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SuiM565BZ3TR1VD3prMguB
os-zhuang
force-pushed
the
claude/date-equality-runtime-hydration-3183
branch
from
July 18, 2026 12:24
143f3af to
ffdc20d
Compare
os-zhuang
marked this pull request as ready for review
July 18, 2026 12:24
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.
Closes #3183. The runtime fix for the date-equality silent-miss the #3192 guardrail warned about.
The bug
A
Field.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. So the most natural predicate —— compiled clean and silently returned the wrong answer. Ordering operators already worked (cel-js throws for them → the engine's existing string-hydration retry fires); only
==/!=failed silently.The fix — AST temporal-comparison rewrite
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 serialized (@marcbachmann/cel-jsserialize) and evaluated. Sorecord.due_date == today()runs asdate(record.due_date) == today()— comparing two Timestamps.record.d == "2026-06-20" || record.d == today()keeps the string-literal comparison intact while fixing the temporal one. No trade-off, no fail-safe skip.date()/toDatedegrades 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 info needed, and no currently-correct result is worsened.includesgate skips the rest) and memoizessource → rewritten source, soapplyFormulaPlan's per-row × per-formula loop parses once per distinct source. AST-based — no regex on author input (no ReDoS).date(record.d)is acall, not a field ref, so it's never re-wrapped.Covers every interpreter site (read-time formulas, defaults, validation rules, hook conditions, flow conditions) through the single
evaluatechokepoint. 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
With the runtime fixed, the build-time warning (
checkTemporalDateEquality+ thetemporalEqualityFieldshelper) would be a false alarm, so it's removed. Authors write the naturalrecord.due_date == today()directly.Tests
cel-engine.test.ts—rewriteTemporalEqualityunit tests (all four fns, both orders, idempotent, per-occurrence mixed case, adversarial input no-throw); end-to-end eval (string date field matches;!=dual; literal unchanged; already-Date/non-date/null unaffected). Flips the test(formula): lock ADR-0053 Phase 2 Slice 3 acceptance criteria (#1980) #3181KNOWN GAPtest to assert the fix.objectql/engine.test.ts— end-to-end: anis_due_todayformula field on a record read viaengine.find()(real driverYYYY-MM-DDround-trip) evaluatestrue.datefield == temporal-function silent-miss (#3183) #3192 lint tests.@objectstack/formula233 ·@objectstack/lint230 ·@objectstack/objectql941;tsc+ ESLint clean on all three. Changeset:@objectstack/formulaminor (behavior change called out).🤖 Generated with Claude Code
Generated by Claude Code