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 (0 = highest priority).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
(customer_tier = "VIP" AND payment_amount >= 100000) OR region IN ["KR", "JP"]
In the example above,
customer_tier, region, and payment_amount 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 the requirements analyzer. Only user_id and user_tags 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 and integration payloads.MUTATE_FACT — Percentage
Reducepayment_amount by 10%:
payment_amount is 200,000 → reduced to 180,000. The change (-20,000) is exposed as payment_amount__delta in generatedVariables.
Operators
MUTATE_FACT — Fixed Amount
MUTATE_FACT — With Rounding
By default, calculator output is preserved at full precision (lossless). Use the optionalrounding field when you need a fixed scale.
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.
INCREMENT_FACT — Percentage
Add 1% ofpayment_amount to total_point:
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.INCREMENT_FACT — Fixed Amount
EMIT_EVENT
Emit a generic event payload to an external integration. The engine validates only thatintegrationId and a non-empty eventPayload map are present — payload structure is the integration provider’s responsibility.
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.
EMIT_NOTIFICATION
Send a notification through an external integration.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.
BLOCK — Block the Request
SET_FACT — Output Variable
ADD_TAG
targetVar is omitted, defaults to user_tags.
EMIT_WEBHOOK — Basic
payloadTemplate, the engine sends all input/output facts as the request body (system variables excluded).
EMIT_WEBHOOK — With Payload Template
UsepayloadTemplate to customize the request body. Template variables are resolved at execution time.
Available Template Variables
Slack requires
{"text": "..."}, Discord requires {"content": "..."}. Use payloadTemplate to match each platform’s expected format.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.Complete Rule Example
Next Steps
Fact Definitions
Define the input variables your rules expect.
Dry Run
Test your rules before publishing.

