@lexq/cli 0.1.22 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -7,7 +7,7 @@
7
7
  LexQ CLI (`@lexq/cli`, binary: `lexq`) manages a policy execution engine. Policies are business rules (if-then) that
8
8
  evaluate input facts and produce actions (discounts, blocks, notifications, etc.).
9
9
 
10
- The CLI also doubles as an **MCP server** — run `lexq serve --mcp` to expose 62 tools to any MCP-compatible AI client.
10
+ The CLI also doubles as an **MCP server** — run `lexq serve --mcp` to expose 63 tools to any MCP-compatible AI client.
11
11
 
12
12
  This file tells you how to use the CLI as an AI agent.
13
13
 
@@ -35,7 +35,7 @@ Detailed documentation lives in the `skills/` directory. **Read the relevant ski
35
35
  | **Shared** | `skills/lexq-shared/SKILL.md` | Core concepts, auth, workflow, error codes. **Read first.** |
36
36
  | **Groups** | `skills/lexq-groups/SKILL.md` | Policy groups, conflict resolution, A/B testing |
37
37
  | **Rules** | `skills/lexq-rules/SKILL.md` | Condition syntax, action types, mutex, examples |
38
- | **Simulation** | `skills/lexq-simulation/SKILL.md` | Dry run, batch simulation, compare |
38
+ | **Simulation** | `skills/lexq-simulation/SKILL.md` | Dry run, Impact Simulation, compare |
39
39
  | **Execution** | `skills/lexq-execution/SKILL.md` | History, stats, failure logs, integrations, webhook subscriptions |
40
40
  | **Recipes** | `skills/lexq-recipes/SKILL.md` | End-to-end workflows (copy-paste ready) |
41
41
 
@@ -142,7 +142,7 @@ Connect via:
142
142
  - **Cloud:** `https://mcp.lexq.io` (OAuth 2.1)
143
143
  - **Local stdio:** `npx @lexq/cli serve --mcp`
144
144
 
145
- 62 tools mirror the CLI command inventory.
145
+ 63 tools mirror the CLI command inventory.
146
146
 
147
147
  ## Troubleshooting
148
148
 
package/CONTEXT.md CHANGED
@@ -7,7 +7,7 @@ simulation, deploy to production, and execute via REST API — all without modif
7
7
 
8
8
  **Core differentiators:**
9
9
 
10
- - Pre-deploy simulation against real data
10
+ - Impact Simulation against real data
11
11
  - A/B testing for rule versions
12
12
  - Git-style versioning with full audit trail
13
13
  - Platform event webhooks for deployment lifecycle notifications
@@ -91,7 +91,7 @@ Tenant
91
91
 
92
92
  ### Action Types
93
93
 
94
- `DISCOUNT`, `POINT`, `COUPON_ISSUE`, `BLOCK`, `NOTIFICATION`, `WEBHOOK`, `SET_FACT`, `ADD_TAG`
94
+ `MUTATE_FACT`, `INCREMENT_FACT`, `EMIT_EVENT`, `BLOCK`, `EMIT_NOTIFICATION`, `EMIT_WEBHOOK`, `SET_FACT`, `ADD_TAG`
95
95
 
96
96
  ### Conflict Resolution Modes
97
97
 
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # LexQ CLI
2
2
 
3
- > **The decision platform for engineering teams.**
3
+ > **The Decision Operations Platform for engineering teams.**
4
4
  > CLI and MCP server for [LexQ](https://lexq.io) — move business rules out of
5
- > your deploy pipeline, prove every change with simulation, and ship without
5
+ > your deploy pipeline, prove every change with Impact Simulation, and ship without
6
6
  > a git push.
7
7
 
8
8
  **[Website](https://lexq.io)** · **[Docs](https://docs.lexq.io)** · **[Console](https://console.lexq.io)**
@@ -24,7 +24,7 @@ that changes weekly shouldn't live in code that ships quarterly.
24
24
 
25
25
  **LexQ separates the two.** Rules live outside your application. You change
26
26
  them in a visual console or through this CLI. You prove every change with
27
- batch simulation against real execution data. You deploy without touching
27
+ Impact Simulation against real execution data. You deploy without touching
28
28
  your codebase.
29
29
 
30
30
  Every decision leaves a full audit trace.
@@ -71,8 +71,14 @@ lexq rules create --group-id <GROUP_ID> --version-id <VERSION_ID> --json '{
71
71
  "valueType": "STRING"
72
72
  },
73
73
  "actions": [{
74
- "type": "DISCOUNT",
75
- "parameters": {"method":"PERCENTAGE","rate":10,"refVar":"payment_amount"}
74
+ "type": "MUTATE_FACT",
75
+ "parameters": {
76
+ "method": "PERCENTAGE",
77
+ "rate": 10,
78
+ "refVar": "payment_amount",
79
+ "operator": "SUB",
80
+ "rounding": { "mode": "HALF_UP", "scale": 0 }
81
+ }
76
82
  }]
77
83
  }'
78
84
 
@@ -176,7 +182,7 @@ skills/
176
182
  ├── lexq-shared/SKILL.md Core concepts, auth, workflow
177
183
  ├── lexq-groups/SKILL.md Policy groups, conflict resolution, A/B testing
178
184
  ├── lexq-rules/SKILL.md Condition syntax, action types, mutex
179
- ├── lexq-simulation/SKILL.md Dry run, batch simulation, compare
185
+ ├── lexq-simulation/SKILL.md Dry run, Impact Simulation, compare
180
186
  ├── lexq-execution/SKILL.md Execution history, stats, failure logs
181
187
  └── lexq-recipes/SKILL.md End-to-end recipes
182
188
 
package/dist/index.js CHANGED
@@ -3296,7 +3296,7 @@ function registerFactTools(server, callApi) {
3296
3296
  "lexq_facts_action_metadata",
3297
3297
  {
3298
3298
  title: "Get Action Runtime Fact Metadata",
3299
- description: "Retrieve runtime Facts metadata for each Action type. Shows which Facts are required as input, produced as output, or consumed at runtime by each Action (DISCOUNT, SEND_SMS, ADD_TAG, SET_FACT, etc.). Use this BEFORE designing rules to understand which Action produces which output variables (e.g., DISCOUNT produces last_discount_amount). Data is static and changes only on engine deployment \u2014 safe to cache in-session.",
3299
+ description: "Retrieve runtime Facts metadata for each Action type. Shows which Facts are required as input, produced as output, or consumed at runtime by each Action (MUTATE_FACT, INCREMENT_FACT, EMIT_EVENT, BLOCK, EMIT_NOTIFICATION, EMIT_WEBHOOK, SET_FACT, ADD_TAG). Use this BEFORE designing rules to understand which Action mutates which fact (e.g., MUTATE_FACT mutates the refVar fact and auto-generates {fact_name}__delta in generatedVariables). Data is static and changes only on engine deployment \u2014 safe to cache in-session.",
3300
3300
  inputSchema: {}
3301
3301
  },
3302
3302
  async () => callApi("GET", "schema/action-metadata")
@@ -3447,8 +3447,8 @@ function registerAnalyticsTools(server, callApi) {
3447
3447
 
3448
3448
  Returns:
3449
3449
  inputFacts — normalized input facts
3450
- mutatedFacts — input facts changed by rule actions (e.g. DISCOUNT mutates payment_amount)
3451
- generatedVariables — new variables created by rules (e.g. last_discount_amount)
3450
+ mutatedFacts — input facts changed by rule actions (e.g. MUTATE_FACT mutates payment_amount)
3451
+ generatedVariables — system-generated values; every fact in mutatedFacts gets a paired {fact_name}__delta key (signed difference)
3452
3452
  executionTraces — per-rule match status
3453
3453
  decisionTraces — per-rule decision (SELECTED / NO_MATCH / BLOCKED / etc.)
3454
3454
 
@@ -3509,7 +3509,7 @@ function registerAnalyticsTools(server, callApi) {
3509
3509
  {
3510
3510
  title: "Start Simulation",
3511
3511
  description: dedent14`
3512
- Start a batch simulation against historical or uploaded data.
3512
+ Start an Impact Simulation against historical or uploaded data.
3513
3513
 
3514
3514
  dataset.type: "HISTORICAL" or "UPLOADED"
3515
3515
  dataset.source (when HISTORICAL): "EXECUTION_LOGS"
@@ -3667,7 +3667,7 @@ function registerHistoryTools(server, callApi) {
3667
3667
  "lexq_history_get",
3668
3668
  {
3669
3669
  title: "Get Execution Detail",
3670
- description: "Get full execution detail including request facts, result traces, and decision traces.",
3670
+ description: "Get full execution detail including inputFacts, mutatedFacts, generatedVariables, executionTraces, and decisionTraces.",
3671
3671
  inputSchema: {
3672
3672
  traceId: z7.string().describe("Trace ID from execution history")
3673
3673
  }
@@ -3991,7 +3991,9 @@ function getVersion2() {
3991
3991
  }
3992
3992
  function createCli() {
3993
3993
  const program = new Command();
3994
- program.name("lexq").description("LexQ CLI \u2014 manage policies, simulate rules, and deploy from the terminal.").version(getVersion2(), "-V, --version").option("--format <format>", "Output format: json or table", "json").option("--api-key <key>", "Override stored API key").option("--base-url <url>", "Override API base URL").option("--dry-run", "Preview the HTTP request without executing").option("--verbose", "Show request/response details").option("--no-color", "Disable colored output");
3994
+ program.name("lexq").description(
3995
+ "Command-line interface for the LexQ Decision Operations Platform. Manage policies, run Impact Simulation, and deploy from your terminal."
3996
+ ).version(getVersion2(), "-V, --version").option("--format <format>", "Output format: json or table", "json").option("--api-key <key>", "Override stored API key").option("--base-url <url>", "Override API base URL").option("--dry-run", "Preview the HTTP request without executing").option("--verbose", "Show request/response details").option("--no-color", "Disable colored output");
3995
3997
  registerAuthCommands(program);
3996
3998
  registerStatusCommand(program);
3997
3999
  registerGroupCommands(program);
@@ -27,7 +27,7 @@ type CallApi = (method: string, path: string, opts?: {
27
27
  declare function paginationParams(page?: number, size?: number): Record<string, string>;
28
28
 
29
29
  /**
30
- * Registers all 62 MCP tools on the given server.
30
+ * Registers all 63 MCP tools on the given server.
31
31
  *
32
32
  * @param server - McpServer instance
33
33
  * @param callApi - API caller function (config-based for CLI, Bearer-based for HTTP)
@@ -465,7 +465,7 @@ function registerFactTools(server, callApi) {
465
465
  "lexq_facts_action_metadata",
466
466
  {
467
467
  title: "Get Action Runtime Fact Metadata",
468
- description: "Retrieve runtime Facts metadata for each Action type. Shows which Facts are required as input, produced as output, or consumed at runtime by each Action (DISCOUNT, SEND_SMS, ADD_TAG, SET_FACT, etc.). Use this BEFORE designing rules to understand which Action produces which output variables (e.g., DISCOUNT produces last_discount_amount). Data is static and changes only on engine deployment \u2014 safe to cache in-session.",
468
+ description: "Retrieve runtime Facts metadata for each Action type. Shows which Facts are required as input, produced as output, or consumed at runtime by each Action (MUTATE_FACT, INCREMENT_FACT, EMIT_EVENT, BLOCK, EMIT_NOTIFICATION, EMIT_WEBHOOK, SET_FACT, ADD_TAG). Use this BEFORE designing rules to understand which Action mutates which fact (e.g., MUTATE_FACT mutates the refVar fact and auto-generates {fact_name}__delta in generatedVariables). Data is static and changes only on engine deployment \u2014 safe to cache in-session.",
469
469
  inputSchema: {}
470
470
  },
471
471
  async () => callApi("GET", "schema/action-metadata")
@@ -616,8 +616,8 @@ function registerAnalyticsTools(server, callApi) {
616
616
 
617
617
  Returns:
618
618
  inputFacts — normalized input facts
619
- mutatedFacts — input facts changed by rule actions (e.g. DISCOUNT mutates payment_amount)
620
- generatedVariables — new variables created by rules (e.g. last_discount_amount)
619
+ mutatedFacts — input facts changed by rule actions (e.g. MUTATE_FACT mutates payment_amount)
620
+ generatedVariables — system-generated values; every fact in mutatedFacts gets a paired {fact_name}__delta key (signed difference)
621
621
  executionTraces — per-rule match status
622
622
  decisionTraces — per-rule decision (SELECTED / NO_MATCH / BLOCKED / etc.)
623
623
 
@@ -678,7 +678,7 @@ function registerAnalyticsTools(server, callApi) {
678
678
  {
679
679
  title: "Start Simulation",
680
680
  description: dedent2`
681
- Start a batch simulation against historical or uploaded data.
681
+ Start an Impact Simulation against historical or uploaded data.
682
682
 
683
683
  dataset.type: "HISTORICAL" or "UPLOADED"
684
684
  dataset.source (when HISTORICAL): "EXECUTION_LOGS"
@@ -836,7 +836,7 @@ function registerHistoryTools(server, callApi) {
836
836
  "lexq_history_get",
837
837
  {
838
838
  title: "Get Execution Detail",
839
- description: "Get full execution detail including request facts, result traces, and decision traces.",
839
+ description: "Get full execution detail including inputFacts, mutatedFacts, generatedVariables, executionTraces, and decisionTraces.",
840
840
  inputSchema: {
841
841
  traceId: z7.string().describe("Trace ID from execution history")
842
842
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexq/cli",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "LexQ CLI — manage policies, simulate rules, and deploy from the terminal. Built for humans and AI agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,7 +4,8 @@
4
4
 
5
5
  ## Overview
6
6
 
7
- Once policies are deployed, the engine evaluates incoming facts against live rules. This skill covers **observing** production behavior: execution history, statistics, failure logs, and integrations.
7
+ Once policies are deployed, the engine evaluates incoming facts against live rules. This skill covers **observing**
8
+ production behavior: execution history, statistics, failure logs, and integrations.
8
9
 
9
10
  ## 1. Execution History
10
11
 
@@ -16,23 +17,23 @@ lexq history list --page 0 --size 20
16
17
 
17
18
  Filter options:
18
19
 
19
- | Flag | Description | Example |
20
- |---|---|---|
21
- | `--trace-id <id>` | Filter by trace ID | `--trace-id abc123` |
22
- | `--group-id <id>` | Filter by policy group | `--group-id <gid>` |
23
- | `--version-id <id>` | Filter by version | `--version-id <vid>` |
24
- | `--status <status>` | Filter by status | `--status SUCCESS` |
25
- | `--start-date <date>` | Start date (ISO) | `--start-date 2025-01-01` |
26
- | `--end-date <date>` | End date (ISO) | `--end-date 2025-01-31` |
20
+ | Flag | Description | Example |
21
+ |-----------------------|------------------------|---------------------------|
22
+ | `--trace-id <id>` | Filter by trace ID | `--trace-id abc123` |
23
+ | `--group-id <id>` | Filter by policy group | `--group-id <gid>` |
24
+ | `--version-id <id>` | Filter by version | `--version-id <vid>` |
25
+ | `--status <status>` | Filter by status | `--status SUCCESS` |
26
+ | `--start-date <date>` | Start date (ISO) | `--start-date 2025-01-01` |
27
+ | `--end-date <date>` | End date (ISO) | `--end-date 2025-01-31` |
27
28
 
28
29
  ### Execution Statuses
29
30
 
30
- | Status | Meaning |
31
- |---|---|
32
- | `SUCCESS` | At least one rule matched and actions executed |
33
- | `NO_MATCH` | No rules matched the input facts |
34
- | `ERROR` | Engine error during evaluation |
35
- | `TIMEOUT` | Execution exceeded time limit |
31
+ | Status | Meaning |
32
+ |------------|------------------------------------------------|
33
+ | `SUCCESS` | At least one rule matched and actions executed |
34
+ | `NO_MATCH` | No rules matched the input facts |
35
+ | `ERROR` | Engine error during evaluation |
36
+ | `TIMEOUT` | Execution exceeded time limit |
36
37
 
37
38
  ### Get Execution Detail
38
39
 
@@ -40,7 +41,7 @@ Filter options:
40
41
  lexq history get --id <executionId>
41
42
  ```
42
43
 
43
- Returns full detail including `requestFacts`, `resultTraces`, and `decisionTraces`.
44
+ Returns full detail including `inputFacts`, `executionTraces`, and `decisionTraces`.
44
45
 
45
46
  ### Execution Statistics
46
47
 
@@ -62,6 +63,7 @@ Returns KPI summary:
62
63
  ```
63
64
 
64
65
  Use stats to monitor:
66
+
65
67
  - **Success rate** — should be close to 1.0 for well-configured policies
66
68
  - **Average latency** — should be single-digit ms under normal load
67
69
  - **No-match count** — high values may indicate missing rules or incomplete fact definitions
@@ -78,14 +80,14 @@ lexq logs list --page 0 --size 20
78
80
 
79
81
  Filter options:
80
82
 
81
- | Flag | Description | Values |
82
- |---|---|---|
83
- | `--category <cat>` | Task category | `INTEGRATION`, `INTERNAL` |
84
- | `--task-type <type>` | Task type | `COUPON_ISSUE`, `POINT_EARN`, `NOTIFICATION_SEND`, `WEBHOOK_EXECUTE` |
85
- | `--status <status>` | Log status | `PENDING`, `RESOLVED`, `IGNORED` |
86
- | `--keyword <kw>` | Search in refId, refSubId, errorMessage | any string |
87
- | `--start-date <date>` | Start date (ISO) | `2025-01-01` |
88
- | `--end-date <date>` | End date (ISO) | `2025-01-31` |
83
+ | Flag | Description | Values |
84
+ |-----------------------|-----------------------------------------|----------------------------------------------------------------------|
85
+ | `--category <cat>` | Task category | `INTEGRATION`, `INTERNAL` |
86
+ | `--task-type <type>` | Task type | `COUPON_ISSUE`, `POINT_EARN`, `NOTIFICATION_SEND`, `WEBHOOK_EXECUTE` |
87
+ | `--status <status>` | Log status | `PENDING`, `RESOLVED`, `IGNORED` |
88
+ | `--keyword <kw>` | Search in refId, refSubId, errorMessage | any string |
89
+ | `--start-date <date>` | Start date (ISO) | `2025-01-01` |
90
+ | `--end-date <date>` | End date (ISO) | `2025-01-31` |
89
91
 
90
92
  ### Get Failure Log Detail
91
93
 
@@ -35,8 +35,14 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
35
35
  "valueType": "NUMBER"
36
36
  },
37
37
  "actions": [{
38
- "type": "DISCOUNT",
39
- "parameters": { "method": "PERCENTAGE", "rate": 20, "refVar": "payment_amount" }
38
+ "type": "MUTATE_FACT",
39
+ "parameters": {
40
+ "refVar": "payment_amount",
41
+ "method": "PERCENTAGE",
42
+ "operator": "SUB",
43
+ "rate": 20,
44
+ "rounding": { "mode": "HALF_UP", "scale": 0 }
45
+ }
40
46
  }]
41
47
  }'
42
48
 
@@ -52,8 +58,14 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
52
58
  ]
53
59
  },
54
60
  "actions": [{
55
- "type": "DISCOUNT",
56
- "parameters": { "method": "PERCENTAGE", "rate": 10, "refVar": "payment_amount" }
61
+ "type": "MUTATE_FACT",
62
+ "parameters": {
63
+ "refVar": "payment_amount",
64
+ "method": "PERCENTAGE",
65
+ "operator": "SUB",
66
+ "rate": 10,
67
+ "rounding": { "mode": "HALF_UP", "scale": 0 }
68
+ }
57
69
  }]
58
70
  }'
59
71
 
@@ -68,17 +80,23 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
68
80
  "valueType": "NUMBER"
69
81
  },
70
82
  "actions": [{
71
- "type": "DISCOUNT",
72
- "parameters": { "method": "PERCENTAGE", "rate": 5, "refVar": "payment_amount" }
83
+ "type": "MUTATE_FACT",
84
+ "parameters": {
85
+ "refVar": "payment_amount",
86
+ "method": "PERCENTAGE",
87
+ "operator": "SUB",
88
+ "rate": 5,
89
+ "rounding": { "mode": "HALF_UP", "scale": 0 }
90
+ }
73
91
  }]
74
92
  }'
75
93
 
76
94
  # 5. Validate
77
95
  lexq analytics dry-run --version-id <vid> --debug --mock --json '{"facts":{"payment_amount":600000}}'
78
- # Expected: 20% discount 120000
96
+ # Expected: mutatedFacts.payment_amount = 480000, generatedVariables.payment_amount__delta = -120000
79
97
 
80
98
  lexq analytics dry-run --version-id <vid> --debug --mock --json '{"facts":{"payment_amount":200000}}'
81
- # Expected: 10% discount 20000
99
+ # Expected: mutatedFacts.payment_amount = 180000, generatedVariables.payment_amount__delta = -20000
82
100
 
83
101
  # 6. Deploy
84
102
  lexq deploy publish --group-id <gid> --version-id <vid> --memo "Tiered discount v1"
@@ -160,8 +178,14 @@ lexq versions clone --group-id <gid> --version-id <v1id>
160
178
  # 2. Update the discount rule in v2
161
179
  lexq rules update --group-id <gid> --version-id <v2id> --id <ruleId> --json '{
162
180
  "actions": [{
163
- "type": "DISCOUNT",
164
- "parameters": { "method": "PERCENTAGE", "rate": 15, "refVar": "payment_amount" }
181
+ "type": "MUTATE_FACT",
182
+ "parameters": {
183
+ "refVar": "payment_amount",
184
+ "method": "PERCENTAGE",
185
+ "operator": "SUB",
186
+ "rate": 15,
187
+ "rounding": { "mode": "HALF_UP", "scale": 0 }
188
+ }
165
189
  }]
166
190
  }'
167
191
 
@@ -205,10 +229,11 @@ lexq versions create --group-id <gid> --json '{"commitMessage": "Points program
205
229
 
206
230
  lexq facts create --key purchase_amount --name "Purchase Amount" --type NUMBER --required
207
231
  lexq facts create --key is_first_purchase --name "First Purchase" --type BOOLEAN
232
+ lexq facts create --key total_points --name "Total Points" --type NUMBER
208
233
 
209
- # Double points for first purchase
234
+ # Bonus points for first purchase (fixed 200)
210
235
  lexq rules create --group-id <gid> --version-id <vid> --json '{
211
- "name": "First Purchase Double Points",
236
+ "name": "First Purchase Bonus Points",
212
237
  "priority": 0,
213
238
  "condition": {
214
239
  "type": "SINGLE",
@@ -218,12 +243,30 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
218
243
  "valueType": "BOOLEAN"
219
244
  },
220
245
  "actions": [
221
- { "type": "POINT", "parameters": { "amount": 200, "pointType": "BONUS" } },
222
- { "type": "NOTIFICATION", "parameters": { "channel": "PUSH", "template": "welcome_points" } }
246
+ {
247
+ "type": "INCREMENT_FACT",
248
+ "parameters": {
249
+ "targetVar": "total_points",
250
+ "method": "AMOUNT",
251
+ "value": 200,
252
+ "rounding": { "mode": "HALF_UP", "scale": 0 }
253
+ }
254
+ },
255
+ {
256
+ "type": "EMIT_NOTIFICATION",
257
+ "parameters": {
258
+ "integrationId": "<notification-integration-uuid>",
259
+ "target": "user_id",
260
+ "notificationPayload": {
261
+ "channel": "PUSH",
262
+ "templateId": "welcome_points"
263
+ }
264
+ }
265
+ }
223
266
  ]
224
267
  }'
225
268
 
226
- # Standard points (1 point per 1000 KRW)
269
+ # Standard points: 0.1% of purchase_amount = 1 point per 1000 KRW
227
270
  lexq rules create --group-id <gid> --version-id <vid> --json '{
228
271
  "name": "Standard Purchase Points",
229
272
  "priority": 1,
@@ -235,7 +278,16 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
235
278
  "valueType": "NUMBER"
236
279
  },
237
280
  "actions": [
238
- { "type": "SET_FACT", "parameters": { "key": "points_earned", "value": "purchase_amount / 1000" } }
281
+ {
282
+ "type": "INCREMENT_FACT",
283
+ "parameters": {
284
+ "targetVar": "total_points",
285
+ "refVar": "purchase_amount",
286
+ "method": "PERCENTAGE",
287
+ "rate": 0.1,
288
+ "rounding": { "mode": "FLOOR", "scale": 0 }
289
+ }
290
+ }
239
291
  ]
240
292
  }'
241
293
  ```
@@ -267,7 +319,17 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
267
319
  "valueType": "NUMBER"
268
320
  },
269
321
  "actions": [
270
- { "type": "WEBHOOK", "parameters": { "url": "https://api.example.com/webhooks/orders", "method": "POST" } },
322
+ {
323
+ "type": "EMIT_WEBHOOK",
324
+ "parameters": {
325
+ "url": "https://api.example.com/webhooks/orders",
326
+ "payloadTemplate": {
327
+ "event": "rule_matched",
328
+ "rule": "{{ruleName}}",
329
+ "amount": "{{output.order_total}}"
330
+ }
331
+ }
332
+ },
271
333
  { "type": "ADD_TAG", "parameters": { "tag": "large_order" } }
272
334
  ]
273
335
  }'
@@ -289,7 +351,16 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
289
351
  "condition": {
290
352
  "type": "SINGLE", "field": "customer_tier", "operator": "EQUALS", "value": "VIP", "valueType": "STRING"
291
353
  },
292
- "actions": [{ "type": "DISCOUNT", "parameters": { "method": "PERCENTAGE", "rate": 20, "refVar": "payment_amount" } }]
354
+ "actions": [{
355
+ "type": "MUTATE_FACT",
356
+ "parameters": {
357
+ "refVar": "payment_amount",
358
+ "method": "PERCENTAGE",
359
+ "operator": "SUB",
360
+ "rate": 20,
361
+ "rounding": { "mode": "HALF_UP", "scale": 0 }
362
+ }
363
+ }]
293
364
  }'
294
365
 
295
366
  lexq rules create --group-id <gid> --version-id <vid> --json '{
@@ -301,7 +372,16 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
301
372
  "condition": {
302
373
  "type": "SINGLE", "field": "payment_amount", "operator": "GREATER_THAN_OR_EQUAL", "value": 50000, "valueType": "NUMBER"
303
374
  },
304
- "actions": [{ "type": "DISCOUNT", "parameters": { "method": "PERCENTAGE", "rate": 15, "refVar": "payment_amount" } }]
375
+ "actions": [{
376
+ "type": "MUTATE_FACT",
377
+ "parameters": {
378
+ "refVar": "payment_amount",
379
+ "method": "PERCENTAGE",
380
+ "operator": "SUB",
381
+ "rate": 15,
382
+ "rounding": { "mode": "HALF_UP", "scale": 0 }
383
+ }
384
+ }]
305
385
  }'
306
386
 
307
387
  # If a VIP customer pays 50000+, only the 20% VIP discount fires (priority 0 wins).
@@ -322,7 +402,16 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
322
402
  "condition": {
323
403
  "type": "SINGLE", "field": "user_region", "operator": "IN", "value": ["KR"], "valueType": "LIST_STRING"
324
404
  },
325
- "actions": [{ "type": "COUPON_ISSUE", "parameters": { "couponId": "KR_WELCOME_2025", "expiryDays": 30 } }]
405
+ "actions": [{
406
+ "type": "EMIT_EVENT",
407
+ "parameters": {
408
+ "integrationId": "<coupon-integration-uuid>",
409
+ "eventPayload": {
410
+ "couponId": "KR_WELCOME_2025",
411
+ "expiryDays": 30
412
+ }
413
+ }
414
+ }]
326
415
  }'
327
416
 
328
417
  lexq rules create --group-id <gid> --version-id <vid> --json '{
@@ -331,7 +420,16 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
331
420
  "condition": {
332
421
  "type": "SINGLE", "field": "user_region", "operator": "IN", "value": ["US"], "valueType": "LIST_STRING"
333
422
  },
334
- "actions": [{ "type": "COUPON_ISSUE", "parameters": { "couponId": "US_WELCOME_2025", "expiryDays": 14 } }]
423
+ "actions": [{
424
+ "type": "EMIT_EVENT",
425
+ "parameters": {
426
+ "integrationId": "<coupon-integration-uuid>",
427
+ "eventPayload": {
428
+ "couponId": "US_WELCOME_2025",
429
+ "expiryDays": 14
430
+ }
431
+ }
432
+ }]
335
433
  }'
336
434
  ```
337
435
 
@@ -4,7 +4,8 @@
4
4
 
5
5
  ## Overview
6
6
 
7
- A **Policy Rule** is a condition → actions pair within a version. Rules are evaluated in priority order (0 = highest). When a rule's condition matches the input facts, its actions fire.
7
+ A **Policy Rule** is a condition → actions pair within a version. Rules are evaluated in priority order (0 = highest).
8
+ When a rule's condition matches the input facts, its actions fire.
8
9
 
9
10
  ## Rule Structure
10
11
 
@@ -12,8 +13,12 @@ A **Policy Rule** is a condition → actions pair within a version. Rules are ev
12
13
  {
13
14
  "name": "VIP 10% Discount",
14
15
  "priority": 0,
15
- "condition": { ... },
16
- "actions": [ ... ],
16
+ "condition": {
17
+ ...
18
+ },
19
+ "actions": [
20
+ ...
21
+ ],
17
22
  "mutexGroup": null,
18
23
  "mutexMode": "NONE",
19
24
  "mutexStrategy": "FIRST_MATCH",
@@ -65,27 +70,27 @@ Conditions use a tree structure with two node types: `SINGLE` and `GROUP`.
65
70
 
66
71
  ### Operators
67
72
 
68
- | Operator | Types | Description |
69
- |---|---|---|
70
- | `EQUALS` | all | Exact match |
71
- | `NOT_EQUALS` | all | Negation |
72
- | `GREATER_THAN` | NUMBER | `>` |
73
- | `GREATER_THAN_OR_EQUAL` | NUMBER | `>=` |
74
- | `LESS_THAN` | NUMBER | `<` |
75
- | `LESS_THAN_OR_EQUAL` | NUMBER | `<=` |
76
- | `CONTAINS` | STRING | Substring match |
77
- | `IN` | STRING, NUMBER | Value is in the provided list |
78
- | `NOT_IN` | STRING, NUMBER | Value is not in the provided list |
73
+ | Operator | Types | Description |
74
+ |-------------------------|----------------|-----------------------------------|
75
+ | `EQUALS` | all | Exact match |
76
+ | `NOT_EQUALS` | all | Negation |
77
+ | `GREATER_THAN` | NUMBER | `>` |
78
+ | `GREATER_THAN_OR_EQUAL` | NUMBER | `>=` |
79
+ | `LESS_THAN` | NUMBER | `<` |
80
+ | `LESS_THAN_OR_EQUAL` | NUMBER | `<=` |
81
+ | `CONTAINS` | STRING | Substring match |
82
+ | `IN` | STRING, NUMBER | Value is in the provided list |
83
+ | `NOT_IN` | STRING, NUMBER | Value is not in the provided list |
79
84
 
80
85
  ### Value Types
81
86
 
82
- | Type | JSON Value | Example |
83
- |---|---|---|
84
- | `STRING` | `"string"` | `"VIP"` |
85
- | `NUMBER` | `number` | `100000` |
86
- | `BOOLEAN` | `true/false` | `true` |
87
- | `LIST_STRING` | `["a","b"]` | `["KR","US"]` |
88
- | `LIST_NUMBER` | `[1,2]` | `[10000, 20000]` |
87
+ | Type | JSON Value | Example |
88
+ |---------------|--------------|------------------|
89
+ | `STRING` | `"string"` | `"VIP"` |
90
+ | `NUMBER` | `number` | `100000` |
91
+ | `BOOLEAN` | `true/false` | `true` |
92
+ | `LIST_STRING` | `["a","b"]` | `["KR","US"]` |
93
+ | `LIST_NUMBER` | `[1,2]` | `[10000, 20000]` |
89
94
 
90
95
  ### Nested Conditions Example
91
96
 
@@ -100,12 +105,31 @@ Conditions use a tree structure with two node types: `SINGLE` and `GROUP`.
100
105
  "type": "GROUP",
101
106
  "operator": "AND",
102
107
  "children": [
103
- { "type": "SINGLE", "field": "customer_tier", "operator": "EQUALS", "value": "VIP", "valueType": "STRING" },
104
- { "type": "SINGLE", "field": "payment_amount", "operator": "GREATER_THAN_OR_EQUAL", "value": 100000, "valueType": "NUMBER" }
108
+ {
109
+ "type": "SINGLE",
110
+ "field": "customer_tier",
111
+ "operator": "EQUALS",
112
+ "value": "VIP",
113
+ "valueType": "STRING"
114
+ },
115
+ {
116
+ "type": "SINGLE",
117
+ "field": "payment_amount",
118
+ "operator": "GREATER_THAN_OR_EQUAL",
119
+ "value": 100000,
120
+ "valueType": "NUMBER"
121
+ }
105
122
  ]
106
123
  },
107
124
  {
108
- "type": "SINGLE", "field": "region", "operator": "IN", "value": ["KR", "JP"], "valueType": "LIST_STRING"
125
+ "type": "SINGLE",
126
+ "field": "region",
127
+ "operator": "IN",
128
+ "value": [
129
+ "KR",
130
+ "JP"
131
+ ],
132
+ "valueType": "LIST_STRING"
109
133
  }
110
134
  ]
111
135
  }
@@ -115,26 +139,34 @@ Conditions use a tree structure with two node types: `SINGLE` and `GROUP`.
115
139
 
116
140
  Each rule can have multiple actions. Actions fire sequentially.
117
141
 
118
- | Type | Description | Key Parameters |
119
- |---|---|---|
120
- | `DISCOUNT` | Apply a discount | `method` (PERCENTAGE/FIXED), `rate`, `refVar` |
121
- | `POINT` | Award points | `amount`, `pointType` |
122
- | `COUPON_ISSUE` | Issue a coupon | `couponId`, `expiryDays` |
123
- | `BLOCK` | Block the transaction | `reason`, `code` |
124
- | `NOTIFICATION` | Send notification | `channel`, `template` |
125
- | `WEBHOOK` | Call external URL | `url`, `method`, `headers`, `body` |
126
- | `SET_FACT` | Set an output variable | `key`, `value` |
127
- | `ADD_TAG` | Add a tag to the result | `tag` |
142
+ | Type | Description | Key Parameters |
143
+ |---------------------|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
144
+ | `MUTATE_FACT` | Mutate a fact value (arithmetic) | `refVar`, `method` (PERCENTAGE/AMOUNT), `operator` (ADD/SUB/MUL/DIV), `rate` or `value`, `rounding` |
145
+ | `INCREMENT_FACT` | Increment a fact (cumulative add) | `targetVar`, `refVar`, `method`, `value` or `rate`, `rounding` |
146
+ | `EMIT_EVENT` | Emit an event to an external integration (coupons, etc.) | `integrationId`, `eventPayload` (Map) |
147
+ | `BLOCK` | Block the transaction | `reason`, `code` |
148
+ | `EMIT_NOTIFICATION` | Send a notification | `integrationId`, `target`, `notificationPayload` (Map) |
149
+ | `EMIT_WEBHOOK` | Call an external URL | `url`, `payloadTemplate` |
150
+ | `SET_FACT` | Set a fact value (literal assignment) | `key`, `value` |
151
+ | `ADD_TAG` | Add a tag to the result | `tag` |
128
152
 
129
- ### Action Example: 10% Percentage Discount
153
+ ### Action Example: 10% Discount via MUTATE_FACT
154
+
155
+ Reduces `payment_amount` by 10%. `__delta` is auto-generated in `generatedVariables` (e.g.,
156
+ `payment_amount__delta: -10000` for a 100,000 input).
130
157
 
131
158
  ```json
132
159
  {
133
- "type": "DISCOUNT",
160
+ "type": "MUTATE_FACT",
134
161
  "parameters": {
162
+ "refVar": "payment_amount",
135
163
  "method": "PERCENTAGE",
164
+ "operator": "SUB",
136
165
  "rate": 10,
137
- "refVar": "payment_amount"
166
+ "rounding": {
167
+ "mode": "HALF_UP",
168
+ "scale": 0
169
+ }
138
170
  }
139
171
  }
140
172
  ```
@@ -183,8 +215,14 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
183
215
  },
184
216
  "actions": [
185
217
  {
186
- "type": "DISCOUNT",
187
- "parameters": { "method": "PERCENTAGE", "rate": 10, "refVar": "payment_amount" }
218
+ "type": "MUTATE_FACT",
219
+ "parameters": {
220
+ "refVar": "payment_amount",
221
+ "method": "PERCENTAGE",
222
+ "operator": "SUB",
223
+ "rate": 10,
224
+ "rounding": { "mode": "HALF_UP", "scale": 0 }
225
+ }
188
226
  }
189
227
  ],
190
228
  "isEnabled": true
@@ -198,8 +236,14 @@ lexq rules update --group-id <gid> --version-id <vid> --id <ruleId> --json '{
198
236
  "name": "VIP 15% Discount",
199
237
  "actions": [
200
238
  {
201
- "type": "DISCOUNT",
202
- "parameters": { "method": "PERCENTAGE", "rate": 15, "refVar": "payment_amount" }
239
+ "type": "MUTATE_FACT",
240
+ "parameters": {
241
+ "refVar": "payment_amount",
242
+ "method": "PERCENTAGE",
243
+ "operator": "SUB",
244
+ "rate": 15,
245
+ "rounding": { "mode": "HALF_UP", "scale": 0 }
246
+ }
203
247
  }
204
248
  ]
205
249
  }'
@@ -234,11 +278,11 @@ lexq rules toggle --group-id <gid> --version-id <vid> --id <ruleId> --enabled fa
234
278
 
235
279
  Within a single version, rules can belong to a `mutexGroup` to limit how many fire.
236
280
 
237
- | mutexMode | Behavior |
238
- |---|---|
239
- | `NONE` | All matching rules fire (default) |
240
- | `EXCLUSIVE` | Only one rule per mutex group fires |
241
- | `MAX_N` | Up to `mutexLimit` rules per mutex group fire |
281
+ | mutexMode | Behavior |
282
+ |-------------|-----------------------------------------------|
283
+ | `NONE` | All matching rules fire (default) |
284
+ | `EXCLUSIVE` | Only one rule per mutex group fires |
285
+ | `MAX_N` | Up to `mutexLimit` rules per mutex group fire |
242
286
 
243
287
  ```bash
244
288
  lexq rules create --group-id <gid> --version-id <vid> --json '{
@@ -6,11 +6,11 @@
6
6
 
7
7
  LexQ provides three levels of pre-deploy validation:
8
8
 
9
- | Tool | Scope | When to Use |
10
- |---|---|---|
11
- | **Dry Run** | Single input | Quick validation of one scenario |
12
- | **Dry Run Compare** | Single input, two versions | Side-by-side version comparison |
13
- | **Simulation** | Batch (historical data) | Full regression test before deploy |
9
+ | Tool | Scope | When to Use |
10
+ |---------------------|----------------------------|------------------------------------|
11
+ | **Dry Run** | Single input | Quick validation of one scenario |
12
+ | **Dry Run Compare** | Single input, two versions | Side-by-side version comparison |
13
+ | **Simulation** | Batch (historical data) | Full regression test before deploy |
14
14
 
15
15
  **Golden rule:** Always dry-run before publishing, always simulate before deploying to production.
16
16
 
@@ -30,11 +30,29 @@ Response includes:
30
30
  "versionId": "...",
31
31
  "versionNo": 3,
32
32
  "requiredFacts": [
33
- { "key": "payment_amount", "type": "NUMBER", "required": true, "usedBy": ["VIP Discount", "Premium Block"] },
34
- { "key": "customer_tier", "type": "STRING", "required": true, "usedBy": ["VIP Discount"] }
33
+ {
34
+ "key": "payment_amount",
35
+ "type": "NUMBER",
36
+ "required": true,
37
+ "usedBy": [
38
+ "VIP Discount",
39
+ "Premium Block"
40
+ ]
41
+ },
42
+ {
43
+ "key": "customer_tier",
44
+ "type": "STRING",
45
+ "required": true,
46
+ "usedBy": [
47
+ "VIP Discount"
48
+ ]
49
+ }
35
50
  ],
36
51
  "exampleRequest": {
37
- "facts": { "payment_amount": 0, "customer_tier": "" },
52
+ "facts": {
53
+ "payment_amount": 0,
54
+ "customer_tier": ""
55
+ },
38
56
  "context": {}
39
57
  }
40
58
  }
@@ -57,11 +75,11 @@ lexq analytics dry-run --version-id <vid> --json '{
57
75
 
58
76
  ### Options
59
77
 
60
- | Flag | Description | Default |
61
- |---|---|---|
62
- | `--debug` | Include execution traces (which rules matched, why) | `false` |
63
- | `--mock` | Mock external calls (webhooks, integrations) | `false` |
64
- | `--file <path>` | Read request body from file instead of `--json` | — |
78
+ | Flag | Description | Default |
79
+ |-----------------|-----------------------------------------------------|---------|
80
+ | `--debug` | Include execution traces (which rules matched, why) | `false` |
81
+ | `--mock` | Mock external calls (webhooks, integrations) | `false` |
82
+ | `--file <path>` | Read request body from file instead of `--json` | — |
65
83
 
66
84
  ### Recommended: Always Use `--debug --mock`
67
85
 
@@ -75,53 +93,77 @@ lexq analytics dry-run --version-id <vid> --debug --mock --json '{
75
93
 
76
94
  ```json
77
95
  {
78
- "outputVariables": { "discount_amount": 15000 },
79
- "executionTraces": [
80
- {
81
- "traceId": "...",
82
- "ruleId": "...",
83
- "ruleName": "VIP 10% Discount",
84
- "matched": true,
85
- "matchExpression": "(customer_tier == VIP AND payment_amount >= 100000)",
86
- "generatedActions": [ { "type": "DISCOUNT", "parameters": { ... } } ]
87
- }
88
- ],
89
- "decisionTraces": [
90
- {
91
- "ruleId": "...",
92
- "ruleName": "VIP 10% Discount",
93
- "status": "SELECTED",
94
- "reasonCode": "FINAL_WINNER",
95
- "reasonDetail": "..."
96
- }
97
- ],
98
- "latencyMs": 12,
99
- "versionNo": 3
96
+ "result": "SUCCESS",
97
+ "data": {
98
+ "inputFacts": {
99
+ "payment_amount": 150000,
100
+ "customer_tier": "VIP"
101
+ },
102
+ "mutatedFacts": {
103
+ "payment_amount": 135000,
104
+ "customer_tier": "VIP"
105
+ },
106
+ "generatedVariables": {
107
+ "payment_amount__delta": -15000
108
+ },
109
+ "executionTraces": [
110
+ {
111
+ "traceId": "...",
112
+ "ruleId": "...",
113
+ "ruleName": "VIP 10% Discount",
114
+ "matched": true,
115
+ "matchExpression": "(customer_tier == VIP AND payment_amount >= 100000)",
116
+ "generatedActions": [
117
+ {
118
+ "type": "MUTATE_FACT",
119
+ "parameters": {
120
+ ...
121
+ }
122
+ }
123
+ ]
124
+ }
125
+ ],
126
+ "decisionTraces": [
127
+ {
128
+ "ruleId": "...",
129
+ "ruleName": "VIP 10% Discount",
130
+ "status": "SELECTED",
131
+ "reasonCode": "FINAL_WINNER",
132
+ "reasonDetail": "..."
133
+ }
134
+ ],
135
+ "latencyMs": 12,
136
+ "versionNo": 3
137
+ }
100
138
  }
101
139
  ```
102
140
 
141
+ - `mutatedFacts` — input facts changed by rule actions (e.g., `MUTATE_FACT` reduces `payment_amount`)
142
+ - `generatedVariables` — for every fact in `mutatedFacts`, a paired `{fact_name}__delta` key is auto-generated with the
143
+ signed difference (negative = decrease, positive = increase)
144
+
103
145
  ### Reading Decision Traces
104
146
 
105
- | Status | Meaning |
106
- |---|---|
107
- | `SELECTED` | Rule matched and its actions fired |
108
- | `NO_MATCH` | Condition did not match the input |
109
- | `NOT_SELECTED` | Matched but excluded by conflict resolution |
110
- | `BLOCKED_MUTEX` | Blocked by mutex group constraint |
111
- | `LOST_PRIORITY` | Lost to a higher-priority rule |
112
- | `DROPPED_LIMIT` | Execution limit reached |
113
- | `ERROR` | Rule evaluation failed |
147
+ | Status | Meaning |
148
+ |-----------------|---------------------------------------------|
149
+ | `SELECTED` | Rule matched and its actions fired |
150
+ | `NO_MATCH` | Condition did not match the input |
151
+ | `NOT_SELECTED` | Matched but excluded by conflict resolution |
152
+ | `BLOCKED_MUTEX` | Blocked by mutex group constraint |
153
+ | `LOST_PRIORITY` | Lost to a higher-priority rule |
154
+ | `DROPPED_LIMIT` | Execution limit reached |
155
+ | `ERROR` | Rule evaluation failed |
114
156
 
115
157
  ### Reading Reason Codes
116
158
 
117
- | Code | Meaning |
118
- |---|---|
119
- | `FINAL_WINNER` | Successfully executed |
120
- | `CONDITION_MISMATCH` | Input facts didn't satisfy the condition |
159
+ | Code | Meaning |
160
+ |-----------------------|----------------------------------------------------------|
161
+ | `FINAL_WINNER` | Successfully executed |
162
+ | `CONDITION_MISMATCH` | Input facts didn't satisfy the condition |
121
163
  | `MUTEX_PRIORITY_LOST` | Another rule in the same mutex group had higher priority |
122
- | `MUTEX_LIMIT_REACHED` | Mutex group's max rules already fired |
123
- | `GROUP_LIMIT_REACHED` | Group's `executionLimit` reached |
124
- | `ACTION_ERROR` | Action execution failed (e.g., webhook timeout) |
164
+ | `MUTEX_LIMIT_REACHED` | Mutex group's max rules already fired |
165
+ | `GROUP_LIMIT_REACHED` | Group's `executionLimit` reached |
166
+ | `ACTION_ERROR` | Action execution failed (e.g., webhook timeout) |
125
167
 
126
168
  ## 3. Dry Run Compare
127
169
 
@@ -137,7 +179,7 @@ lexq analytics dry-run-compare --json '{
137
179
 
138
180
  Useful for validating that changes in a new version produce expected differences.
139
181
 
140
- ## 4. Batch Simulation
182
+ ## 4. Impact Simulation
141
183
 
142
184
  Run a full regression test against historical execution data:
143
185
 
@@ -157,7 +199,7 @@ lexq analytics simulation start --json '{
157
199
  "maxRecords": 10000,
158
200
  "baselinePolicyVersionId": "<currentLiveVersionId>",
159
201
  "metricConfig": {
160
- "targetVariable": "discount_amount",
202
+ "targetVariable": "payment_amount__delta",
161
203
  "aggregationType": "SUM"
162
204
  }
163
205
  }
@@ -166,13 +208,14 @@ lexq analytics simulation start --json '{
166
208
 
167
209
  ### Dataset Types
168
210
 
169
- | Type | Source | Description |
170
- |---|---|---|
171
- | `HISTORICAL` | `EXECUTION_LOGS` | Replay past executions from a date range |
172
- | `MANUAL` | `REQUEST_BODY` | Provide `manualData` array in the request |
173
- | `UPLOADED` | `S3_BUCKET` | Reference an uploaded dataset by `path` |
211
+ | Type | Source | Description |
212
+ |--------------|------------------|-------------------------------------------|
213
+ | `HISTORICAL` | `EXECUTION_LOGS` | Replay past executions from a date range |
214
+ | `MANUAL` | `REQUEST_BODY` | Provide `manualData` array in the request |
215
+ | `UPLOADED` | `S3_BUCKET` | Reference an uploaded dataset by `path` |
174
216
 
175
217
  ### File Upload Dataset
218
+
176
219
  ```bash
177
220
  # 1. Download template (optional)
178
221
  lexq analytics dataset template \
@@ -212,13 +255,13 @@ lexq analytics simulation status --id <simulationId>
212
255
 
213
256
  Simulation is async. Poll until `status` is `COMPLETED` or `FAILED`.
214
257
 
215
- | Status | Meaning |
216
- |---|---|
217
- | `PENDING` | Queued |
218
- | `RUNNING` | In progress (`progress` field shows 0–100) |
219
- | `COMPLETED` | Done — results available |
220
- | `FAILED` | Error occurred |
221
- | `CANCELLED` | Manually cancelled |
258
+ | Status | Meaning |
259
+ |-------------|--------------------------------------------|
260
+ | `PENDING` | Queued |
261
+ | `RUNNING` | In progress (`progress` field shows 0–100) |
262
+ | `COMPLETED` | Done — results available |
263
+ | `FAILED` | Error occurred |
264
+ | `CANCELLED` | Manually cancelled |
222
265
 
223
266
  ### List Simulations
224
267
 
@@ -254,7 +297,7 @@ lexq analytics simulation export --id <simulationId> --format csv --output resul
254
297
  "matchRate": 0.85
255
298
  },
256
299
  "metricSummary": {
257
- "targetVariable": "discount_amount",
300
+ "targetVariable": "payment_amount__delta",
258
301
  "aggregationType": "SUM",
259
302
  "baselineValue": 5000000,
260
303
  "simulatedValue": 4500000,
@@ -273,7 +316,12 @@ lexq analytics simulation export --id <simulationId> --format csv --output resul
273
316
  }
274
317
  },
275
318
  "ruleStats": [
276
- { "ruleId": "...", "ruleName": "VIP Discount", "matchedCount": 5000, "metricValue": 3000000 }
319
+ {
320
+ "ruleId": "...",
321
+ "ruleName": "VIP Discount",
322
+ "matchedCount": 5000,
323
+ "metricValue": 3000000
324
+ }
277
325
  ]
278
326
  }
279
327
  ```
@@ -299,7 +347,7 @@ lexq analytics simulation start --json '{
299
347
  "options": {
300
348
  "baselinePolicyVersionId": "<currentLiveVersionId>",
301
349
  "includeRuleStats": true,
302
- "metricConfig": { "targetVariable": "discount_amount", "aggregationType": "SUM" }
350
+ "metricConfig": { "targetVariable": "payment_amount__delta", "aggregationType": "SUM" }
303
351
  }
304
352
  }'
305
353