Skip to content

fix: align rules-engine-internal with json-logic-js (undefined & missing_some)#3634

Merged
ajpallares merged 13 commits into
mainfrom
pallares/rules-engine-jsonlogic-parity
Jun 24, 2026
Merged

fix: align rules-engine-internal with json-logic-js (undefined & missing_some)#3634
ajpallares merged 13 commits into
mainfrom
pallares/rules-engine-jsonlogic-parity

Conversation

@ajpallares

@ajpallares ajpallares commented Jun 22, 2026

Copy link
Copy Markdown
Member

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-ios and hybrids

Motivation

json-logic-js is the cross-SDK source of truth for rule evaluation. A conformance suite running the shared predicate fixtures against json-logic-js surfaced three edge cases where rules-engine-internal diverged. iOS counterpart: RevenueCat/purchases-ios#7066.

Description

  • Model JS undefined as a distinct Value subclass: {"and": []} / {"or": []} now reduce to undefined (falsy, but undefined !== null) instead of null.
  • {"log": []} logs "undefined" instead of "null".
  • missing_some treats a string options argument as an array-like list (matching missing.apply(this, options)) instead of throwing.
  • Shared predicate fixtures updated to the json-logic-js results.

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 undefined as Value.Undefined: Omitted operator args and empty and / or now yield undefined (falsy, but undefined !== null) instead of null. Coercion, truthiness, == / ===, and stringification ("undefined") follow JS; var treats an undefined path like an empty path (full scope) and coerces an undefined default to null.

Operator fixes: missing_some accepts string (and nullish) options like missing.apply—one key for a string, threshold from length (UTF-16)—instead of erroring on non-array options. Iteration operators use undefined for a missing predicate; log / substr match 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.

…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>
ajpallares and others added 2 commits June 22, 2026 14:17
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.28%. Comparing base (2214c5c) to head (8f462f4).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
ajpallares and others added 3 commits June 22, 2026 14:46
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>
@ajpallares ajpallares marked this pull request as ready for review June 23, 2026 08:30
@ajpallares ajpallares requested a review from a team as a code owner June 23, 2026 08:30
ajpallares and others added 2 commits June 23, 2026 12:19
- 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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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>

@tonidero tonidero left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚢

ajpallares and others added 4 commits June 23, 2026 12:36
`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>
@ajpallares ajpallares added this pull request to the merge queue Jun 24, 2026
Merged via the queue into main with commit 240522e Jun 24, 2026
37 checks passed
@ajpallares ajpallares deleted the pallares/rules-engine-jsonlogic-parity branch June 24, 2026 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2 participants