fix: align rules-engine-internal with json-logic-js (undefined & missing_some)#3634
Merged
Conversation
…ng_some)
Model JS `undefined` as a distinct `Value` subclass so the engine matches
json-logic-js (the cross-SDK source of truth) on three edge cases, and
treat `missing_some` options as array-like:
- `{"and": []}` / `{"or": []}` reduce to `undefined` (falsy, but
`undefined !== null`) instead of `null`.
- `{"log": []}` logs `"undefined"` instead of `"null"`.
- `{"missing_some": [n, "<string>"]}` treats the string as an array-like
argument list (matching `missing.apply(this, options)`) instead of
throwing a type error.
Fixtures updated to the json-logic-js results accordingly.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3634 +/- ##
=======================================
Coverage 80.28% 80.28%
=======================================
Files 379 379
Lines 15570 15570
Branches 2173 2173
=======================================
Hits 12501 12501
Misses 2204 2204
Partials 865 865 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Compare the result via string coercion (== "a") instead of bare truthiness, pinning it to exactly ["a"]. Co-authored-by: Cursor <cursoragent@cursor.com>
json-logic-js treats a string `options` as a single missing key while using its character length for the threshold; the evaluator was instead splitting the string into per-character keys (only correct for a single char). Treat the whole string as one key and add fixtures covering the satisfied and below-threshold cases. Co-authored-by: Cursor <cursoragent@cursor.com>
- var: treat an `undefined` path arg as the empty path so it returns the full data scope (json-logic-js `typeof a === "undefined"`), instead of walking the bogus path "undefined". - Add fixtures covering var-undefined and the missing_some UTF-16 length edge (Kotlin `String.length` already matches JS). Co-authored-by: Cursor <cursoragent@cursor.com>
…ub.com/RevenueCat/purchases-android into pallares/rules-engine-jsonlogic-parity
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 16855e2. Configure here.
`missing` routes keys through `var`, where `undefined` resolves to the full scope and is therefore never missing. `keyAsPath` was stringifying `Value.Undefined` to the literal path "undefined" and reporting it missing; now it skips `Value.Undefined` like `Value.Null`. Adds a covering fixture. Co-authored-by: Cursor <cursoragent@cursor.com>
`evalTwo` substituted `Value.Null` for omitted binary operands, so a one-arg `===`/`!==` on an empty `and`/`or` compared `undefined === null` (wrong) instead of `undefined === undefined`. Default to `Value.Undefined` to match json-logic-js, and add covering fixtures. Co-authored-by: Cursor <cursoragent@cursor.com>
Match json-logic-js's undefined semantics in four spots: substr's missing source stringifies to "undefined"; map/filter/some/all/none and reduce default a missing predicate to undefined (observable via map's raw results and reduce's fold); and var coerces an undefined default to null. Adds byte-identical conformance fixtures. Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the nested ternary with two readable guard clauses. Co-authored-by: Cursor <cursoragent@cursor.com>
This was referenced Jun 24, 2026
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.

Checklist
purchases-iosand hybridsMotivation
json-logic-jsis the cross-SDK source of truth for rule evaluation. A conformance suite running the shared predicate fixtures againstjson-logic-jssurfaced three edge cases whererules-engine-internaldiverged. iOS counterpart: RevenueCat/purchases-ios#7066.Description
undefinedas a distinctValuesubclass:{"and": []}/{"or": []}now reduce toundefined(falsy, butundefined !== null) instead ofnull.{"log": []}logs"undefined"instead of"null".missing_sometreats a stringoptionsargument as an array-like list (matchingmissing.apply(this, options)) instead of throwing.json-logic-jsresults.Note
Medium Risk
Changes core rule evaluation semantics (undefined vs null, strict equality, missing_some); wrong behavior could flip rule outcomes, but risk is mitigated by a large expanded conformance fixture suite aligned to json-logic-js.
Overview
Brings rules-engine-internal in line with json-logic-js for edge cases surfaced by the shared predicate conformance suite.
JS
undefinedasValue.Undefined: Omitted operator args and emptyand/ornow yieldundefined(falsy, butundefined !== null) instead ofnull. Coercion, truthiness,==/===, and stringification ("undefined") follow JS;vartreats an undefined path like an empty path (full scope) and coerces an undefined default tonull.Operator fixes:
missing_someaccepts string (and nullish)optionslikemissing.apply—one key for a string, threshold fromlength(UTF-16)—instead of erroring on non-array options. Iteration operators useundefinedfor a missing predicate;log/substrmatch omitted-arg behavior.Tests: Predicate fixtures updated (382 cases); expectations for empty
and/or, equality,var,missing,map/reduce, etc.Reviewed by Cursor Bugbot for commit 8f462f4. Bugbot is set up for automated code reviews on this repo. Configure here.