> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lexq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Dry Run

> Test a single input against a policy version without side effects. Validate rule behavior before publishing.

## What is a Dry Run?

A **Dry Run** tests how your rules evaluate a single set of input facts against a specific policy version — without any side effects. No execution logs are written, no integrations are called, and no quotas are consumed. Think of it as a **unit test** for your rules.

<Info>
  **Dry Run vs Impact Simulation vs Decision Replay:** A Dry Run tests **one input you type** for quick validation. [Impact Simulation](/guides/simulation) tests a version against **hundreds or thousands** of historical inputs and compares the results against a baseline version. [Decision Replay](/guides/decision-replay) re-evaluates **one real past decision** against a candidate version and diffs the outcome. Use Dry Runs while iterating, Simulation for regression testing before deployment, and Replay to answer "what would this exact production decision have been?"
</Info>

## When to Use Dry Run

* After creating or modifying rules — verify they match expected inputs
* Before publishing a DRAFT version — your safety net
* When debugging unexpected execution results — reproduce the scenario
* When comparing two versions with the same input (Dry Run Compare)

## Running a Dry Run

### Console

Navigate to a policy version → **Dry Run** tab → enter input facts → click **Run**.

### CLI

```bash theme={null}
lexq analytics dry-run \
  --version-id <versionId> \
  --debug \
  --mock \
  --json '{"facts": {"payment_amount": 150000, "customer_tier": "VIP"}}'
```

| Flag      | Description                                                     |
| --------- | --------------------------------------------------------------- |
| `--debug` | Include execution traces and decision traces in the response    |
| `--mock`  | Mock external integration calls (webhooks, notifications, etc.) |

### API

```bash theme={null}
curl -X POST https://api.lexq.io/api/v1/partners/analytics/dry-run/versions/{versionId} \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "facts": { "payment_amount": 150000, "customer_tier": "VIP" },
    "includeDebugInfo": true,
    "mockExternalCalls": true
  }'
```

## Response

```json theme={null}
{
  "result": "SUCCESS",
  "data": {
    "inputFacts": {
      "payment_amount": 150000,
      "customer_tier": "VIP"
    },
    "mutatedFacts": {
      "payment_amount": 135000
    },
    "generatedVariables": {
      "payment_amount__delta": -15000,
      "discount_applied": true
    },
    "executionTraces": [ ... ],
    "decisionTraces": [ ... ],
    "latencyMs": 12,
    "versionNo": 3
  }
}
```

### State Layers

The `data` object exposes three layers of state explicitly — never merged into a single blob:

* **`inputFacts`** — facts as received from the request, normalized.
* **`mutatedFacts`** — only the facts whose values were changed by rule actions.
* **`generatedVariables`** — variables created by rule actions that did not exist in the input. This includes per-action change keys like `{refVar}__delta` (signed change from `MUTATE_FACT`) and `{targetVar}__delta` (non-negative increment from `INCREMENT_FACT`).

The merged "final state" can always be reconstructed as `{ ...inputFacts, ...mutatedFacts, ...generatedVariables }`. Keeping them separate makes audits, replays, and debugging deterministic — you always know which value originated where.

### Execution Traces

Each rule that the engine evaluated produces one execution trace. The trace records the exact match expression that was checked, the input facts the rule saw, and the actions it generated:

```json theme={null}
{
  "tenantId": "acme-corp",
  "policyGroupId": "01f2b274-...",
  "policyVersionId": "a6062090-...",
  "ruleId": "3b16ced1-...",
  "ruleName": "VIP 10% Discount",
  "executedAt": "2026-04-27T09:44:10Z",
  "matched": true,
  "matchExpression": "(customer_tier == 'VIP') && (payment_amount >= 100000)",
  "inputFacts": {
    "customer_tier": "VIP",
    "payment_amount": 150000
  },
  "generatedActions": [
    {
      "type": "MUTATE_FACT",
      "parameters": { "refVar": "payment_amount", "operator": "SUB", "method": "PERCENTAGE", "rate": 10 }
    }
  ]
}
```

The four identifiers (`tenantId`, `policyGroupId`, `policyVersionId`, `ruleId`) make every trace self-locating — you never need an external lookup to know which tenant, group, version, and rule produced it.

### Decision Traces

While execution traces record *what was evaluated*, decision traces record *what was selected*. Each rule appears in `decisionTraces` with its final status and the reason code that produced it:

```json theme={null}
{
  "ruleId": "3b16ced1-...",
  "ruleName": "VIP 10% Discount",
  "policyGroupId": "01f2b274-...",
  "policyVersionId": "a6062090-...",
  "status": "SELECTED",
  "reasonCode": "FINAL_WINNER",
  "reasonDetail": null
}
```

| `status`       | Meaning                                                                   |
| -------------- | ------------------------------------------------------------------------- |
| `SELECTED`     | Rule won and its actions were applied                                     |
| `NO_MATCH`     | Rule's condition evaluated to false                                       |
| `NOT_SELECTED` | Pre-competition filter (e.g., effective date — reserved for future)       |
| `BLOCKED`      | Lost mutex or activation group competition (`reasonCode` specifies scope) |
| `ERROR`        | Action execution failed                                                   |

| `reasonCode`             | When it appears                                                               |
| ------------------------ | ----------------------------------------------------------------------------- |
| `FINAL_WINNER`           | Rule was selected as the winner                                               |
| `CONDITION_MISMATCH`     | Input did not satisfy the condition                                           |
| `EFFECTIVE_DATE_INVALID` | Outside the rule's effective date window (reserved)                           |
| `MUTEX_PRIORITY_LOST`    | EXCLUSIVE mutex (limit=1) — another rule had higher priority/score            |
| `MUTEX_LIMIT_REACHED`    | MAX\_N mutex (limit>1) — `mutexLimit` already filled by higher-priority rules |
| `GROUP_PRIORITY_LOST`    | EXCLUSIVE activation group (limit=1) — another rule won by priority           |
| `GROUP_LIMIT_REACHED`    | MAX\_N activation group (limit>1) — `executionLimit` already filled           |
| `ACTION_ERROR`           | Action execution raised an error                                              |
| `ENGINE_ERROR`           | Engine-internal error during evaluation                                       |

`reasonDetail` is nullable — it carries a human-readable message when the engine has additional context to surface (e.g., the specific limit value that was hit).

<Info>
  See [Decision Trace](/guides/policy-rules#decision-trace) in the Policy Rules reference for the full status × reasonCode mapping.
</Info>

## Checking Requirements

Before running a Dry Run, check which facts the version expects:

```bash theme={null}
lexq analytics requirements --group-id <gid> --version-id <vid>
```

## Dry Run Compare

Compare how **two versions** evaluate the same input facts, side by side:

```bash theme={null}
lexq analytics dry-run-compare --json '{
  "versionIdA": "<baseline-version>",
  "versionIdB": "<candidate-version>",
  "facts": { "payment_amount": 200000, "customer_tier": "GOLD" }
}'
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Impact Simulation" icon="chart-bar" href="/guides/simulation">
    Test against hundreds of historical inputs at once.
  </Card>

  <Card title="Deployments" icon="rocket" href="/guides/deployments">
    Deploy your validated version to production.
  </Card>
</CardGroup>
