What is a Policy Rule?
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 — 1 is the highest. Within a version, priorities always form a contiguous1…N sequence: new rules are appended at the end, and reordering rewrites the whole sequence.
Condition Syntax
Conditions use a tree structure with two node types:SINGLE and GROUP.
SINGLE Condition
A leaf node that compares a single fact against a value:GROUP Condition
A branch node that combines child conditions withAND or OR:
Operators
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.Value Types
Nested Conditions Example
(customerTier = "VIP" AND paymentAmount >= 100000) OR region IN ["KR", "JP"]
In the example above,
customerTier, region, and paymentAmount are custom facts that should be registered in Fact Definitions. You can reference a fact before registering it — LexQ flags any unregistered key with a non-blocking warning rather than rejecting the rule — but registering enables type validation and Required Input Facts analysis. Only userId and userTags are system facts available by default.Action Types
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. The engine never calls external systems. It mutates facts and records decisions; your caller reads the response and acts on it. This preserves audit-grade separation between engine state and external state.MUTATE_FACT parameters
operator × method matrix
DIV + AMOUNT requires operand ≠ 0.
When targetVar and refVar differ
refVar is only meaningful in PERCENTAGE × . Specifying it in any other cell is an error, not a silent no-op — AMOUNT has no base concept, and MUL × PERCENTAGE is a multiplier shorthand that reads no base.
Use it when the base differs from the target:
loyaltyPoint += orderTotal × 5% — two different facts. Omitting refVar would compute loyaltyPoint += loyaltyPoint × 5% instead.
Ranges are not constrained. Negative operands and percentages above 100 are valid — refunds
(
-5), surcharges (150), risk scores, and game points all need them. Range validation belongs
in your fact definitions, not the engine.MUTATE_FACT — Percentage
ReducepaymentAmount by 10%. Currency facts should also set rounding — see below.
MUTATE_FACT — Fixed Amount
MUTATE_FACT — With Rounding
Withoutrounding, the result is preserved at full precision. Specify it when you need currency-scale output.
mode accepts UP, DOWN, CEILING, FLOOR, HALF_UP, HALF_DOWN, HALF_EVEN. Defaults to HALF_UP.
SET_FACT — Assign a value
Assigns a literal of any type. This is the only action that creates a fact that did not exist.SET_FACT assigns rather than appends. To accumulate into a LIST_STRING fact, send the current list in the request and assign the full new list.
Because it is an assignment and not an arithmetic change, SET_FACT produces no __delta.
BLOCK — Record a rejection
Generated Variables
In addition to the mutated facts, the engine exposes per-action change information ingeneratedVariables:
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.
Mutex — Rule-Level Conflict Resolution
Within a single version, rules can belong to a mutex group to control how many matching rules fire.
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).
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 categoryreasonCode— the specific reason within that category
DecisionStatus
DecisionReasonCode
Status × ReasonCode Mapping
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.CONDITION_MISMATCH covers two different facts: the condition evaluated to false, and the condition could not be evaluated (type mismatch, unresolved fact).
The specific cause is in reasonDetail — for example Evaluation error: FACT_TYPE_MISMATCH when a LIST_STRING fact receives a plain string.
A rule that fails to evaluate does not stop the execution; the rest of the rules run normally.Complete Rule Example
Next Steps
Fact Definitions
Define the input variables your rules expect.
Dry Run
Test your rules before publishing.

