A Policy Rule is a condition → actions pair. When the input facts satisfy the condition, the rule’s actions fire. Rules are evaluated in priority order (0 = highest priority).
IF condition matches → THEN execute actions
Each rule belongs to a specific policy version and has a name, a priority, a condition (tree-structured logic), and one or more actions.
Conditions use a tree structure with two node types: SINGLE and GROUP.
LexQ uses its own condition DTO format with type: "SINGLE" / type: "GROUP", field, operator, value, and valueType fields. Do not confuse this with the Console’s internal form representation which may use different field names. Always use the engine format when calling the API or CLI.
For IN / NOT_IN, the Compatible Types column refers to the type of the fact being checked (STRING or NUMBER). The value you provide is the list itself — its valueType is LIST_STRING or LIST_NUMBER.
In the example above, customer_tier, region, and payment_amount are custom facts that must be registered in Fact Definitions before use. Only user_id and user_tags are system facts available by default.
LexQ engine actions are domain-agnostic primitives. The engine sees only numbers and structures — it does not assume commerce, fintech, insurance, or any specific business model. Domain-specific semantics live in your fact names and integration payloads.
Type
Description
Key Parameters
MUTATE_FACT
Mutate a numeric fact in place using an arithmetic operator
refVar, operator, method, rate (when PERCENTAGE) or value, rounding (optional)
INCREMENT_FACT
Increment a numeric fact by a calculated amount
targetVar, method, refVar,rate (when PERCENTAGE) or value, rounding (optional)
rounding.scale is an integer in [0, 16]. rounding.mode is one of HALF_UP (default), HALF_DOWN, HALF_EVEN, FLOOR, CEILING, DOWN, UP. Omitting rounding keeps full precision — round downstream (e.g. when rendering a currency display) instead of in the engine.
If payment_amount is 100,000 → total_point is incremented by 1,000. The increment is exposed as total_point__delta in generatedVariables.
INCREMENT_FACT only mutates engine-side facts. To synchronize with an external system (e.g. a points service), compose [INCREMENT_FACT, EMIT_EVENT] as a chain in the same rule. The engine does not call external systems on behalf of INCREMENT_FACT — this preserves audit-grade separation between engine state and external state.
Emit a generic event payload to an external integration. The engine validates only that integrationId and a non-empty eventPayload map are present — payload structure is the integration provider’s responsibility.
Domain-specific keys (couponId, ticketId, claimId, etc.) are routed by the integration provider. This makes EMIT_EVENT a true generic primitive — usable for coupon issuance, ticket creation, insurance claim submission, or any event-style external call.
targetVar identifies the recipient fact (e.g. phone_number, email, device_token). notificationPayload carries channel, template, and variable mapping for the integration provider — the engine does not interpret these keys.
SET_FACT is a literal value setter, not an expression evaluator. It assigns a fixed value to a key in the output. It does not support arithmetic expressions or references to other facts.
Slack requires {"text": "..."}, Discord requires {"content": "..."}. Use payloadTemplate to match each platform’s expected format.
{{fact.xxx}} and {{output.xxx}} reference the same data map. If an earlier action (e.g., MUTATE_FACT) modifies payment_amount, both {{fact.payment_amount}} and {{output.payment_amount}} reflect the modified value.
In addition to the mutated facts, the engine exposes per-action change information in generatedVariables:
Pattern
Meaning
Generated By
{refVar}__delta
Net change to refVar (mutated - original, signed)
MUTATE_FACT
{targetVar}__delta
Net increment to targetVar (always non-negative)
INCREMENT_FACT
When multiple actions in a rule mutate the same fact, __delta reports the cumulative change. Per-action snapshots are available in executionTraces for audit drill-down.
Within a single version, rules can belong to a mutex group to control how many matching rules fire.
Field
Description
mutexGroup
A string key grouping related rules (e.g., "best-discount")
mutexMode
NONE (default), EXCLUSIVE, or MAX_N
mutexStrategy
FIRST_MATCH, HIGHEST_PRIORITY, or MAX_BENEFIT
mutexLimit
Max number of rules to fire from this group (for MAX_N)
When mutexMode is EXCLUSIVE, only the single winning rule fires. When it is MAX_N, up to mutexLimit rules fire in priority order. Other matching rules in the same mutex group are skipped and logged in the Decision Trace with status BLOCKED and reason MUTEX_PRIORITY_LOST or MUTEX_LIMIT_REACHED (see Decision Trace).
Every rule evaluation produces a decision trace entry explaining whether the rule fired and why. This is the core of LexQ’s audit-grade reasoning — every outcome can be traced back to a specific status and reason code.A decision trace entry has two classifying fields:
status — the high-level outcome category
reasonCode — the specific reason within that category
When debugging “why didn’t my rule fire?”, the decision trace gives you the answer in two steps: the status tells you which category of filtering removed the rule, and the reasonCode tells you exactly which check rejected it.