Skip to main content

Overview

Yggdrasil supports 12 core operators for building rule conditions. Each operator handles type coercion automatically for CSV string values.

Supported Operators

OperatorAliasesDescriptionExample
>=greater_than_or_equal, gteGreater than or equalamount >= 10000
>greater_than, gtGreater thanamount > 5000
<=less_than_or_equal, lteLess than or equalamount <= 1000
<less_than, ltLess thanamount < 10000
==equals, eq, equalEquality (with type coercion)status == "approved"
!=not_equals, neq, ne, not_equalInequalitytype != "CASH_OUT"
INSet membershiptype IN ["DEBIT", "WIRE"]
BETWEENRange check [min, max]amount BETWEEN [8000, 10000]
existsField is present and non-emptyemail exists
not_existsField is missing or emptydpo_contact not_exists
containsincludesCase-insensitive substring matchdescription contains "crypto"
MATCHregexRegular expression testemail MATCH "^[a-z]+@"

Operator Normalization

The engine normalizes operator aliases from LLM extraction to standard forms:
// From in-memory-backend.ts:223-244
private normalizeOperator(op: string): string {
    const normalized = op.trim().toLowerCase();
    const map: Record<string, string> = {
        // Standard
        '>=': '>=', '>': '>', '<=': '<=', '<': '<',
        '==': '==', '!=': '!=', 'in': 'IN', 'between': 'BETWEEN',
        // Gemini aliases
        'equals': '==', 'equal': '==', 'eq': '==',
        'not_equals': '!=', 'not_equal': '!=', 'neq': '!=', 'ne': '!=',
        'greater_than': '>', 'gt': '>',
        'greater_than_or_equal': '>=', 'gte': '>=',
        'less_than': '<', 'lt': '<',
        'less_than_or_equal': '<=', 'lte': '<=',
        'exists': 'EXISTS', 'not_exists': 'NOT_EXISTS',
        'contains': 'CONTAINS', 'includes': 'CONTAINS',
        'match': 'MATCH', 'regex': 'MATCH',
    };
    return map[normalized] || op;
}

Numeric Comparisons

Operators: >=, >, <=, < Numeric comparisons use parseFloat() on both sides to handle CSV string values:
// From in-memory-backend.ts:182-189
case '>=':
    return parseFloat(leftValue) >= parseFloat(rightValue);
case '>':
    return parseFloat(leftValue) > parseFloat(rightValue);
case '<=':
    return parseFloat(leftValue) <= parseFloat(rightValue);
case '<':
    return parseFloat(leftValue) < parseFloat(rightValue);

Example

{
  "field": "amount",
  "operator": ">=",
  "value": 10000
}
Matches records where amount >= 10000.

Equality Operators

Operators: ==, != Equality uses type coercion to handle CSV string values:
// From in-memory-backend.ts:59-84
function coerceEquals(left: any, right: any): boolean {
    // Same type — direct comparison
    if (typeof left === typeof right) return left === right;

    // Rule value is boolean, CSV value is string
    if (typeof right === 'boolean' && typeof left === 'string') {
        return (left.toLowerCase() === 'true') === right;
    }

    // Rule value is number, CSV value is string
    if (typeof right === 'number' && typeof left === 'string') {
        return parseFloat(left) === right;
    }

    // Rule value is string, CSV value is number/boolean
    if (typeof left === 'boolean' && typeof right === 'string') {
        return left === (right.toLowerCase() === 'true');
    }
    if (typeof left === 'number' && typeof right === 'string') {
        return left === parseFloat(right);
    }

    // Fallback: loose equality
    return left == right;
}

Type Coercion Examples

CSV ValueRule ValueMatch?
"true"true
"false"false
"16"16
"10000"10000
"approved""approved"

Set Membership

Operator: IN Checks if a value exists in an array:
// From in-memory-backend.ts:198-199
case 'IN':
    return Array.isArray(rightValue) && rightValue.includes(leftValue);

Example

{
  "field": "transaction_type",
  "operator": "IN",
  "value": ["DEBIT", "WIRE", "TRANSFER"]
}
Matches records where transaction_type is one of the listed values.

Range Check

Operator: BETWEEN Checks if a value is within a range (inclusive):
// From in-memory-backend.ts:200-205
case 'BETWEEN':
    return (
        Array.isArray(rightValue) &&
        (leftValue as number) >= rightValue[0] &&
        (leftValue as number) <= rightValue[1]
    );

Example

{
  "field": "amount",
  "operator": "BETWEEN",
  "value": [8000, 10000]
}
Matches records where 8000 <= amount <= 10000.

Existence Operators

Operators: exists, not_exists These operators test for field presence/absence before checking for null:
// From in-memory-backend.ts:162-167
if (op === 'EXISTS') {
    return leftValue !== undefined && leftValue !== null && leftValue !== '';
}
if (op === 'NOT_EXISTS') {
    return leftValue === undefined || leftValue === null || leftValue === '';
}

Example: Required Field

{
  "field": "dpo_contact",
  "operator": "exists"
}
Matches records where dpo_contact is present and non-empty.

Example: Missing Field

{
  "field": "consent_withdrawn",
  "operator": "not_exists"
}
Matches records where consent_withdrawn is missing or empty.

String Matching

Contains

Operator: contains (alias: includes) Case-insensitive substring match:
// From in-memory-backend.ts:208-210
case 'CONTAINS':
    return typeof leftValue === 'string' && typeof rightValue === 'string' &&
        leftValue.toLowerCase().includes(rightValue.toLowerCase());
Example:
{
  "field": "description",
  "operator": "contains",
  "value": "crypto"
}
Matches “Cryptocurrency transaction”, “CRYPTO PURCHASE”, “crypto-related”.

Regex

Operator: MATCH (alias: regex) Regular expression test:
// From in-memory-backend.ts:211-215
case 'MATCH':
    if (typeof leftValue === 'string' && typeof rightValue === 'string') {
        return new RegExp(rightValue).test(leftValue);
    }
    return false;
Example: Email validation
{
  "field": "email",
  "operator": "MATCH",
  "value": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
}

Cross-Field Comparison

You can compare two fields using value_type: "field":
// From in-memory-backend.ts:174-178
if (cond.value_type === 'field' && typeof rightValue === 'string') {
    rightValue = record[rightValue];
    if (rightValue === undefined || rightValue === null) return false;
}

Example: Balance Mismatch

{
  "AND": [
    {
      "field": "newbalanceOrig",
      "operator": "!=",
      "value": "expectedBalance",
      "value_type": "field"
    }
  ]
}
This compares newbalanceOrig to the expectedBalance field in the same record.

Sanity Checks

The engine includes safety guards to prevent false positives:
// From in-memory-backend.ts:169-172
if (leftValue === undefined || leftValue === null) {
    return false;
}
If a field is missing or undefined, the condition does not match (except for exists/not_exists).

Compound Conditions

Operators can be combined with AND/OR logic:
{
  "AND": [
    { "field": "amount", "operator": ">=", "value": 8000 },
    { "field": "amount", "operator": "<", "value": 10000 },
    { "field": "type", "operator": "IN", "value": ["DEBIT", "WIRE"] }
  ]
}
See Architecture for details on condition evaluation.

Type Coercion Summary

CSV InputExpected TypeCoercion
"true"booleantrue
"false"booleanfalse
"123"number123
"10000.50"number10000.5
"approved"string"approved"

Error Handling

Unknown operators are logged and return false:
// From in-memory-backend.ts:217-219
default:
    console.warn(`[ENGINE] Unknown operator: '${cond.operator}' in rule condition for field '${cond.field}'`);
    return false;

Next Steps

Rule Types

Learn when to use WINDOWED vs SINGLE-TX

Architecture

Understand the execution flow

Build docs developers (and LLMs) love