@lexq/cli 0.1.40 → 0.1.42

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.
@@ -4,15 +4,18 @@
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).
8
- 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
8
+ order **lower number wins**, and priorities are 1-based (1 is highest). When a rule's condition
9
+ matches the input facts, its actions fire.
10
+
11
+ `priority` is assigned by the server, not by you. New rules are appended last. Use
12
+ `lexq rules reorder` to change the order.
9
13
 
10
14
  ## Rule Structure
11
15
 
12
16
  ```json
13
17
  {
14
18
  "name": "VIP 10% Discount",
15
- "priority": 0,
16
19
  "condition": {
17
20
  ...
18
21
  },
@@ -21,7 +24,7 @@ When a rule's condition matches the input facts, its actions fire.
21
24
  ],
22
25
  "mutexGroup": null,
23
26
  "mutexMode": "NONE",
24
- "mutexStrategy": "FIRST_MATCH",
27
+ "mutexStrategy": "HIGHEST_PRIORITY",
25
28
  "mutexLimit": null,
26
29
  "isEnabled": true
27
30
  }
@@ -175,21 +178,71 @@ Do **not** use `CONTAINS` on a list fact — that idiom works in some rule engin
175
178
 
176
179
  Each rule can have multiple actions. Actions fire sequentially.
177
180
 
178
- | Type | Description | Key Parameters |
179
- |---------------------|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
180
- | `MUTATE_FACT` | Mutate a fact value (arithmetic) | `refVar`, `method` (PERCENTAGE/AMOUNT), `operator` (ADD/SUB/MUL/DIV), `rate` or `value`, `rounding` |
181
- | `INCREMENT_FACT` | Increment a fact (cumulative add) | `targetVar`, `refVar`, `method`, `value` or `rate`, `rounding` |
182
- | `EMIT_EVENT` | Emit an event to an external integration (coupons, etc.) | `integrationId`, `eventPayload` (Map) |
183
- | `BLOCK` | Block the transaction | `reason`, `code` |
184
- | `EMIT_NOTIFICATION` | Send a notification | `integrationId`, `targetVar`, `notificationPayload` (Map) |
185
- | `EMIT_WEBHOOK` | Call an external URL | `url`, `payloadTemplate` |
186
- | `SET_FACT` | Set a fact value (literal assignment) | `key`, `value` |
187
- | `ADD_TAG` | Append a tag to a list fact | `tag`, `targetVar` (optional, default `user_tags`) |
188
-
189
- **Two different `targetVar` meanings.** `EMIT_NOTIFICATION.targetVar` is a **read** it names the
190
- fact holding the recipient (`phone_number`, `email`, `device_token`), and the action throws if that
191
- fact is absent from the request. `ADD_TAG.targetVar` is a **write** the list is created if absent,
192
- and adding a tag that is already present is a no-op.
181
+ | Type | Description | Key Parameters |
182
+ |---------------|--------------------------------------------|----------------------------------------------------------------------|
183
+ | `MUTATE_FACT` | Arithmetic change to a numeric fact | `targetVar`, `operator`, `method`, `operand`, `refVar?`, `rounding?` |
184
+ | `SET_FACT` | Assign a literal value creates if absent | `targetVar`, `value` |
185
+ | `BLOCK` | Record a rejection decision | `reason` |
186
+
187
+ ### `MUTATE_FACT` parameters
188
+
189
+ | Parameter | Required | Meaning |
190
+ |-------------|----------|-------------------------------------------------------------------------------|
191
+ | `targetVar` | always | The fact this action **reads and writes**. Must already exist as a number. |
192
+ | `operator` | always | `ASSIGN` \| `ADD` \| `SUB` \| `MUL` \| `DIV` |
193
+ | `method` | always | `PERCENTAGE` \| `AMOUNT` dictates the unit of `operand` |
194
+ | `operand` | always | The arithmetic operand. Percent when PERCENTAGE, absolute amount when AMOUNT. |
195
+ | `refVar` | optional | Base for percentage calculation. Omit to use `targetVar` itself. |
196
+ | `rounding` | optional | `{ scale: 0..16, mode?: HALF_UP \| ... }`. Omit for lossless full precision. |
197
+
198
+ **operator × method matrix**
199
+
200
+ | operator | `AMOUNT` | `PERCENTAGE` |
201
+ |----------|------------------------|-------------------------------------|
202
+ | `ASSIGN` | `targetVar = operand` | `targetVar = refVar × operand/100` |
203
+ | `ADD` | `targetVar += operand` | `targetVar += refVar × operand/100` |
204
+ | `SUB` | `targetVar -= operand` | `targetVar -= refVar × operand/100` |
205
+ | `MUL` | `targetVar *= operand` | `targetVar *= (operand/100 + 1)` |
206
+ | `DIV` | `targetVar /= operand` | **invalid** |
207
+
208
+ `DIV` + `PERCENTAGE` is rejected — use `MUL` with the inverse. `DIV` + `AMOUNT` requires
209
+ `operand !== 0`.
210
+
211
+ **`refVar` is only meaningful in `PERCENTAGE` × {`ASSIGN`, `ADD`, `SUB`}.** Specifying it in any
212
+ other cell is an error, not a silent no-op. `AMOUNT` has no base concept, and `MUL` × `PERCENTAGE`
213
+ is a multiplier shorthand that does not read a base.
214
+
215
+ Use `refVar` when the base differs from the target:
216
+
217
+ ```json
218
+ {
219
+ "type": "MUTATE_FACT",
220
+ "parameters": {
221
+ "targetVar": "loyalty_point",
222
+ "refVar": "order_total",
223
+ "operator": "ADD",
224
+ "method": "PERCENTAGE",
225
+ "operand": 5
226
+ }
227
+ }
228
+ ```
229
+
230
+ `loyalty_point += order_total × 5%` — two different facts. Omitting `refVar` would compute
231
+ `loyalty_point += loyalty_point × 5%` instead.
232
+
233
+ **Ranges are not constrained.** Negative operands and percentages above 100 are valid — refunds
234
+ (`-5`), surcharges (`150`), risk scores, and game points all need them.
235
+
236
+ ### `SET_FACT` vs `MUTATE_FACT`
237
+
238
+ `SET_FACT` creates the fact if it does not exist. `MUTATE_FACT` requires the target to already be
239
+ present as a number and throws otherwise. "Make something that wasn't there" is `SET_FACT`'s job.
240
+
241
+ ### `BLOCK` does not halt execution
242
+
243
+ `BLOCK` records a rejection decision by writing the `is_blocked` fact. Subsequent actions in the
244
+ same rule and subsequent winning rules still run. Enforcement is the caller's responsibility —
245
+ read `is_blocked` from the response.
193
246
 
194
247
  ### Action Example: 10% Discount via MUTATE_FACT
195
248
 
@@ -200,10 +253,10 @@ Reduces `payment_amount` by 10%. `__delta` is auto-generated in `generatedVariab
200
253
  {
201
254
  "type": "MUTATE_FACT",
202
255
  "parameters": {
203
- "refVar": "payment_amount",
256
+ "targetVar": "payment_amount",
204
257
  "method": "PERCENTAGE",
205
258
  "operator": "SUB",
206
- "rate": 10,
259
+ "operand": 10,
207
260
  "rounding": {
208
261
  "mode": "HALF_UP",
209
262
  "scale": 0
@@ -218,8 +271,7 @@ Reduces `payment_amount` by 10%. `__delta` is auto-generated in `generatedVariab
218
271
  {
219
272
  "type": "BLOCK",
220
273
  "parameters": {
221
- "reason": "Suspected fraud",
222
- "code": "FRAUD_DETECTED"
274
+ "reason": "Suspected fraud"
223
275
  }
224
276
  }
225
277
  ```
@@ -245,7 +297,6 @@ lexq rules get --group-id <gid> --version-id <vid> --id <ruleId>
245
297
  ```bash
246
298
  lexq rules create --group-id <gid> --version-id <vid> --json '{
247
299
  "name": "VIP 10% Discount",
248
- "priority": 0,
249
300
  "condition": {
250
301
  "type": "GROUP",
251
302
  "operator": "AND",
@@ -258,10 +309,10 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
258
309
  {
259
310
  "type": "MUTATE_FACT",
260
311
  "parameters": {
261
- "refVar": "payment_amount",
312
+ "targetVar": "payment_amount",
262
313
  "method": "PERCENTAGE",
263
314
  "operator": "SUB",
264
- "rate": 10,
315
+ "operand": 10,
265
316
  "rounding": { "mode": "HALF_UP", "scale": 0 }
266
317
  }
267
318
  }
@@ -279,10 +330,10 @@ lexq rules update --group-id <gid> --version-id <vid> --id <ruleId> --json '{
279
330
  {
280
331
  "type": "MUTATE_FACT",
281
332
  "parameters": {
282
- "refVar": "payment_amount",
333
+ "targetVar": "payment_amount",
283
334
  "method": "PERCENTAGE",
284
335
  "operator": "SUB",
285
- "rate": 15,
336
+ "operand": 15,
286
337
  "rounding": { "mode": "HALF_UP", "scale": 0 }
287
338
  }
288
339
  }
@@ -299,7 +350,8 @@ lexq rules delete --group-id <gid> --version-id <vid> --id <ruleId> --force
299
350
 
300
351
  ### Reorder Rules
301
352
 
302
- Pass rule IDs in desired priority order (index 0 = highest priority):
353
+ Pass rule IDs in desired order. The server assigns priority 1, 2, 3, the first ID becomes
354
+ priority 1 (highest):
303
355
 
304
356
  ```bash
305
357
  lexq rules reorder --group-id <gid> --version-id <vid> \
@@ -319,26 +371,44 @@ lexq rules toggle --group-id <gid> --version-id <vid> --id <ruleId> --enabled fa
319
371
 
320
372
  Within a single version, rules can belong to a `mutexGroup` to limit how many fire.
321
373
 
322
- | mutexMode | Behavior |
323
- |-------------|-----------------------------------------------|
324
- | `NONE` | All matching rules fire (default) |
325
- | `EXCLUSIVE` | Only one rule per mutex group fires |
326
- | `MAX_N` | Up to `mutexLimit` rules per mutex group fire |
374
+ | mutexMode | Behavior |
375
+ |-------------|---------------------------------------------------------------|
376
+ | `NONE` | All matching rules fire (default) |
377
+ | `EXCLUSIVE` | Only one rule per mutex group fires |
378
+ | `MAX_N` | Up to `mutexLimit` rules fire. Omit it and the server sets 2. |
327
379
 
328
380
  ```bash
329
381
  lexq rules create --group-id <gid> --version-id <vid> --json '{
330
382
  "name": "Discount A",
331
- "priority": 0,
332
383
  "mutexGroup": "discounts",
333
384
  "mutexMode": "EXCLUSIVE",
334
385
  "mutexStrategy": "HIGHEST_PRIORITY",
335
386
  "condition": { ... },
336
387
  "actions": [ ... ]
337
388
  }'
389
+
390
+ # Top two of the group fire
391
+ lexq rules create --group-id <gid> --version-id <vid> --json '{
392
+ "name": "Stackable Promo A",
393
+ "mutexGroup": "promos",
394
+ "mutexMode": "MAX_N",
395
+ "mutexLimit": 2,
396
+ "mutexStrategy": "HIGHEST_PRIORITY",
397
+ "condition": { ... },
398
+ "actions": [ ... ]
399
+ }'
338
400
  ```
339
401
 
340
402
  **Constraint:** All rules in the same `mutexGroup` must use identical `mutexMode` and `mutexStrategy`.
341
403
 
404
+ **`mutexGroup` is what turns mutex on.** Sending `mutexMode` without a `mutexGroup` is silently normalized to `NONE` —
405
+ there is no group to be exclusive within. Conversely, setting a `mutexGroup` without a `mutexMode` defaults to
406
+ `EXCLUSIVE`.
407
+
408
+ `mutexStrategy` currently accepts only `HIGHEST_PRIORITY` — the rule with the lowest priority
409
+ number in the group wins. `mutexMode: MAX_N` requires `mutexLimit`; omit it and the server
410
+ sets 2.
411
+
342
412
  ## Pre-Create Checklist
343
413
 
344
414
  Before creating rules, always:
@@ -35,7 +35,7 @@ lexq groups list --api-key lexq_us_override_key
35
35
  | **Policy Version** | A snapshot of rules within a group. Follows DRAFT → ACTIVE → ARCHIVED lifecycle. | Git branch / commit |
36
36
  | **Policy Rule** | A condition + actions pair within a version. Evaluated in priority order. | if-then statement |
37
37
  | **Fact Definition** | Input schema — declares available variables and their types (STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER). | Function parameter |
38
- | **Deployment** | Promotes a PUBLISHED version to live traffic. Supports rollback. | Production release |
38
+ | **Deployment** | Promotes an ACTIVE version to live traffic. Supports rollback and scheduled arming. | Production release |
39
39
  | **Dry Run** | Tests a single input against a DRAFT or ACTIVE version without side effects. | Unit test |
40
40
  | **Simulation** | Batch-tests a version against historical execution data. Compares with a baseline. | Integration test suite |
41
41
 
@@ -50,15 +50,15 @@ This is the typical lifecycle. **Always follow this order:**
50
50
  4. lexq rules create → Add rules with conditions + actions
51
51
  5. lexq analytics dry-run → Test with sample facts (validate before publish)
52
52
  6. lexq deploy publish → DRAFT → ACTIVE (locks the version)
53
- 7. lexq deploy live Deploy ACTIVE version to production
54
- 8. lexq analytics simulation Run batch comparison against baseline (optional)
53
+ 7. lexq analytics simulation Batch-test against historical data with a baseline (optional)
54
+ 8. lexq deploy live Deploy ACTIVE version to production
55
55
  ```
56
56
 
57
57
  ### Critical Ordering Constraints
58
58
 
59
59
  - **Cannot create rules without a DRAFT version.** Create the version first.
60
60
  - **Cannot publish without at least one rule.** Add rules before publishing.
61
- - **Cannot modify a published version.** Clone it to create a new DRAFT if changes are needed.
61
+ **Cannot modify an ACTIVE version.** Clone it to create a new DRAFT if changes are needed.
62
62
  - **Cannot deploy a DRAFT version.** Must publish first (DRAFT → ACTIVE).
63
63
  - **Always run `lexq analytics dry-run` before publishing.** This is your safety net.
64
64
 
@@ -80,19 +80,24 @@ Every command accepts these flags:
80
80
 
81
81
  ## Command Groups
82
82
 
83
- | Group | Commands | Description |
84
- |----------------|--------------------------------------------------------------------------------------------|---------------------------------|
85
- | `auth` | `login`, `logout`, `whoami` | Authentication |
86
- | `status` | (root) | API health check |
87
- | `groups` | `list`, `get`, `create`, `update`, `delete` + `ab-test start\|stop\|adjust` | Policy group CRUD + A/B testing |
88
- | `versions` | `list`, `get`, `create`, `update`, `delete`, `clone` | Version CRUD |
89
- | `rules` | `list`, `get`, `create`, `update`, `delete`, `reorder`, `toggle` | Rule CRUD |
90
- | `facts` | `list`, `create`, `update`, `delete` | Fact definition CRUD |
91
- | `deploy` | `publish`, `live`, `rollback`, `undeploy`, `history`, `detail`, `overview` | Deployment lifecycle |
92
- | `analytics` | `dry-run`, `dry-run-compare`, `requirements`, `simulation start/status/list/cancel/export` | Testing & analysis |
93
- | `history` | `list`, `get`, `stats` | Execution history |
94
- | `integrations` | `list`, `get`, `save`, `delete`, `config-spec` | External integrations |
95
- | `logs` | `list`, `get`, `action`, `bulk-action` | Failure log management |
83
+ | Group | Commands | Description |
84
+ |-------------------------|----------------------------------------------------------------------------------------------------------------------------|---------------------------------|
85
+ | `auth` | `login`, `logout`, `whoami` | Authentication |
86
+ | `status` | (root) | API health check |
87
+ | `groups` | `list`, `get`, `create`, `update`, `delete`, `reorder` + `ab-test start\|stop\|adjust` | Policy group CRUD + A/B testing |
88
+ | `versions` | `list`, `get`, `create`, `update`, `delete`, `clone` | Version CRUD |
89
+ | `rules` | `list`, `get`, `create`, `update`, `delete`, `reorder`, `toggle` | Rule CRUD |
90
+ | `facts` | `list`, `create`, `update`, `delete` | Fact definition CRUD |
91
+ | `domain-templates` | `list`, `preview`, `apply` | Industry starter packs |
92
+ | `deploy` | `publish`, `live`, `rollback`, `undeploy`, `history`, `detail`, `overview`, `schedule`, `unschedule`, `schedules` | Deployment lifecycle |
93
+ | `analytics` | `dry-run`, `dry-run-compare`, `requirements`, `simulation start\|status\|list\|cancel\|export`, `dataset upload\|template` | Testing & analysis |
94
+ | `profile` | (root, takes `<groupId>`) | Per-rule latency profile |
95
+ | `history` | `list`, `get`, `stats` | Execution history |
96
+ | `replay` | `decision`, `start`, `list`, `get`, `cancel` | Decision replay |
97
+ | `provenance` | `get`, `reveal-audits` | Decision provenance + PII audit |
98
+ | `logs` | `list`, `get`, `action`, `bulk-action` | Failure log management |
99
+ | `webhook-subscriptions` | `list`, `get`, `save`, `delete`, `test` | Platform event webhooks |
100
+ | `serve` | `--mcp` | MCP server over stdio |
96
101
 
97
102
  ## Pagination
98
103
 
@@ -120,19 +125,27 @@ API errors return:
120
125
  {
121
126
  "result": "ERROR",
122
127
  "message": "Policy version not found.",
123
- "code": "ENTITY_NOT_FOUND"
128
+ "errorCode": "P-002"
124
129
  }
125
130
  ```
126
131
 
127
- **Common error codes and what to do:**
132
+ `errorCode` is a stable identifier of the form `<domain>-<number>`. `message` is the
133
+ human-readable explanation and is localized by `Accept-Language`. **Branch on `errorCode`, never
134
+ on `message`.**
128
135
 
129
- | Code | Meaning | Action |
130
- |--------------------|--------------------------------|---------------------------------------------------------------------------------|
131
- | `ENTITY_NOT_FOUND` | Resource doesn't exist | Verify the ID. Run the corresponding `list` command. |
132
- | `INVALID_INPUT` | Validation failed | Check required fields. Run `lexq analytics requirements` for fact requirements. |
133
- | `CANNOT_MODIFY` | Version is not DRAFT | Clone the version to create a new DRAFT: `lexq versions clone` |
134
- | `EMPTY_RULES` | Publish attempted with 0 rules | Add at least one rule before publishing. |
135
- | `UNAUTHORIZED` | Invalid or missing API key | Run `lexq auth login` with a valid key. |
136
+ Prefixes you will encounter through the CLI:
137
+
138
+ | Prefix | Domain | Example cause |
139
+ |--------|-----------------------|------------------------------------------------|
140
+ | `C-` | Common validation | Malformed or missing request field |
141
+ | `A-` | Auth | Invalid or missing API key |
142
+ | `P-` | Policy group/version | Not found, wrong lifecycle state, already live |
143
+ | `ACT-` | Action parameters | Missing or invalid action parameter |
144
+ | `FD-` | Fact definitions | Duplicate key, system fact immutable |
145
+ | `AN-` | Analytics | Dry-run or simulation failure |
146
+ | `FL-` | Failure logs | Log not found |
147
+ | `WH-` | Webhook subscriptions | Invalid URL, delivery failure |
148
+ | `I-` | Idempotency | Duplicate idempotency key |
136
149
 
137
150
  ## Important Conventions
138
151
 
@@ -140,6 +153,13 @@ API errors return:
140
153
  2. **IDs are UUIDs.** Always copy the full ID from list/create output — do not guess.
141
154
  3. **Dates use ISO 8601.** Example: `2025-01-01T00:00:00Z`. Time zone is UTC.
142
155
  4. **JSON bodies via `--json`.** Most create/update commands accept `--json '<body>'` for the request body.
143
- 5. **File input via `--file`.** Analytics commands accept `--file path/to/body.json` as an alternative to `--json`.
156
+ 5. **File input via `--file`.** `analytics dry-run`, `dry-run-compare`, `simulation start` accept
157
+ `--file path/to/body.json` as an alternative to `--json`. `analytics dataset upload` requires
158
+ `--file` (CSV or JSON). `simulation export` and `dataset template` write output with `--output`.
144
159
  6. **Confirmation prompts.** Destructive operations (delete, cancel, undeploy) prompt for confirmation. Use `--force` to
145
- skip in automation.
160
+ skip in automation.
161
+
162
+ ## Skill Formatting
163
+
164
+ Catalog-style skills (`lexq-recipes`) separate independent items with `---`. Narrative skills do
165
+ not use it at all. Never mix both in one file.
@@ -78,17 +78,19 @@ lexq analytics dry-run --version-id <vid> --json '{
78
78
  | Flag | Description | Default |
79
79
  |-----------------|-----------------------------------------------------|---------|
80
80
  | `--debug` | Include execution traces (which rules matched, why) | `false` |
81
- | `--mock` | Mock external calls (webhooks, integrations) | `false` |
82
81
  | `--file <path>` | Read request body from file instead of `--json` | — |
83
82
 
84
- ### Recommended: Always Use `--debug --mock`
83
+ ### Recommended: Always Use `--debug`
85
84
 
86
85
  ```bash
87
- lexq analytics dry-run --version-id <vid> --debug --mock --json '{
86
+ lexq analytics dry-run --version-id <vid> --debug --json '{
88
87
  "facts": { "payment_amount": 150000, "customer_tier": "VIP" }
89
88
  }'
90
89
  ```
91
90
 
91
+ Dry run has no side effects — actions only mutate the fact map in memory. There is nothing
92
+ external to mock.
93
+
92
94
  ### Response Structure
93
95
 
94
96
  ```json
@@ -145,27 +147,32 @@ lexq analytics dry-run --version-id <vid> --debug --mock --json '{
145
147
 
146
148
  Each trace carries a `status` (what happened) and a `reasonCode` (why).
147
149
 
148
- | Status | Meaning |
149
- |----------------|----------------------------------------------------|
150
- | `SELECTED` | Rule matched and its actions fired |
151
- | `NO_MATCH` | Condition did not match, or could not be evaluated |
152
- | `NOT_SELECTED` | Matched but excluded by conflict resolution |
153
- | `BLOCKED` | Blocked by a mutex group or group activation limit |
154
- | `ERROR` | Action execution failed |
150
+ | Status | Meaning |
151
+ |------------|---------------------------------------------------------------------------------|
152
+ | `SELECTED` | Rule matched and its actions fired |
153
+ | `NO_MATCH` | Condition did not match, or could not be evaluated |
154
+ | `BLOCKED` | Matched but lost conflict resolution — see `reasonCode` for which round and why |
155
+ | `ERROR` | Action execution failed |
156
+
157
+ `BLOCKED` is unrelated to the `BLOCK` action. A `BLOCK` action writes the `is_blocked` fact and its own rule stays
158
+ `SELECTED`; `BLOCKED` means the rule was dropped by activation-group or mutex competition.
155
159
 
156
160
  ### Reading Reason Codes
157
161
 
158
- | Code | Meaning |
159
- |--------------------------|----------------------------------------------------------|
160
- | `FINAL_WINNER` | Successfully executed |
161
- | `CONDITION_MISMATCH` | Condition not satisfied, or could not be evaluated |
162
- | `EFFECTIVE_DATE_INVALID` | Outside the version's effective date range |
163
- | `MUTEX_PRIORITY_LOST` | Another rule in the same mutex group had higher priority |
164
- | `MUTEX_LIMIT_REACHED` | Mutex group's max rules already fired |
165
- | `GROUP_PRIORITY_LOST` | Another group in the same activation group won |
166
- | `GROUP_LIMIT_REACHED` | Group's `executionLimit` reached |
167
- | `ACTION_ERROR` | Action execution failed (e.g., webhook timeout) |
168
- | `ENGINE_ERROR` | Internal engine failure |
162
+ | Code | Meaning |
163
+ |-----------------------|----------------------------------------------------------|
164
+ | `FINAL_WINNER` | Successfully executed |
165
+ | `CONDITION_MISMATCH` | Condition not satisfied, or could not be evaluated |
166
+ | `MUTEX_PRIORITY_LOST` | Another rule in the same mutex group had higher priority |
167
+ | `MUTEX_LIMIT_REACHED` | Mutex group's max rules already fired |
168
+ | `GROUP_PRIORITY_LOST` | Another group in the same activation group won |
169
+ | `GROUP_LIMIT_REACHED` | Group's `executionLimit` reached |
170
+ | `ACTION_ERROR` | Action execution failed (e.g. required fact absent) |
171
+ | `ENGINE_ERROR` | Internal engine failure |
172
+
173
+ A version outside its effective date range is filtered **before** evaluation — no trace is produced for it at all. If a
174
+ rule you expect never appears in `decisionTraces`, check the version's `effectiveFrom` / `effectiveTo` with
175
+ `lexq versions get`.
169
176
 
170
177
  #### `reasonDetail` on unevaluable conditions
171
178
 
@@ -238,19 +245,19 @@ lexq analytics simulation start --json '{
238
245
  ```bash
239
246
  # 1. Download template (optional)
240
247
  lexq analytics dataset template \
241
- --group-id --version-id --format csv --output template.csv
248
+ --group-id <gid> --version-id <vid> --format csv --output template.csv
242
249
 
243
250
  # 2. Upload dataset
244
251
  lexq analytics dataset upload --file ./my-data.csv
245
- # → path: datasets//a1b2c3d4e5f6.csv
252
+ # → path: datasets/<tenantId>/a1b2c3d4e5f6.csv
246
253
 
247
254
  # 3. Start simulation with uploaded path
248
255
  lexq analytics simulation start --json '{
249
- "policyVersionId": "",
256
+ "policyVersionId": "<vid>",
250
257
  "dataset": {
251
258
  "type": "UPLOADED",
252
259
  "source": "S3_BUCKET",
253
- "path": "datasets//a1b2c3d4e5f6.csv"
260
+ "path": "<path returned by dataset upload>"
254
261
  },
255
262
  "options": { "includeRuleStats": true, "maxRecords": 10000 }
256
263
  }'
@@ -259,13 +266,6 @@ lexq analytics simulation start --json '{
259
266
  **CSV format:** Header row with fact keys, data rows with values. Types auto-detected.
260
267
  **JSON format:** Array of objects `[{"key": "value"}, ...]`
261
268
 
262
- ### MCP — Dataset Tools
263
-
264
- ```
265
- lexq_dataset_template → Get sample CSV/JSON based on version's required facts
266
- lexq_dataset_upload → Upload inline CSV/JSON content to S3, returns path
267
- ```
268
-
269
269
  ### Check Status (Poll)
270
270
 
271
271
  ```bash
@@ -352,7 +352,7 @@ lexq analytics simulation export --id <simulationId> --format csv --output resul
352
352
  lexq analytics requirements --group-id <gid> --version-id <vid>
353
353
 
354
354
  # 2. Dry-run with representative inputs
355
- lexq analytics dry-run --version-id <vid> --debug --mock --json '{
355
+ lexq analytics dry-run --version-id <vid> --debug --json '{
356
356
  "facts": { "payment_amount": 150000, "customer_tier": "VIP" }
357
357
  }'
358
358