@lexq/cli 0.1.0
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 +117 -0
- package/CONTEXT.md +158 -0
- package/README.md +153 -0
- package/dist/index.js +1850 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/skills/lexq-execution/SKILL.md +211 -0
- package/skills/lexq-groups/SKILL.md +181 -0
- package/skills/lexq-recipes/SKILL.md +407 -0
- package/skills/lexq-rules/SKILL.md +264 -0
- package/skills/lexq-shared/SKILL.md +139 -0
- package/skills/lexq-simulation/SKILL.md +279 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# LexQ CLI — Policy Rules
|
|
2
|
+
|
|
3
|
+
> **Prerequisite:** Read `lexq-shared/SKILL.md` first.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
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.
|
|
8
|
+
|
|
9
|
+
## Rule Structure
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"name": "VIP 10% Discount",
|
|
14
|
+
"priority": 0,
|
|
15
|
+
"condition": { ... },
|
|
16
|
+
"actions": [ ... ],
|
|
17
|
+
"mutexGroup": null,
|
|
18
|
+
"mutexMode": "NONE",
|
|
19
|
+
"mutexStrategy": "FIRST_MATCH",
|
|
20
|
+
"mutexLimit": null,
|
|
21
|
+
"isEnabled": true
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Condition Syntax
|
|
26
|
+
|
|
27
|
+
Conditions use a tree structure with two node types: `SINGLE` and `GROUP`.
|
|
28
|
+
|
|
29
|
+
### SINGLE Condition
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"type": "SINGLE",
|
|
34
|
+
"field": "payment_amount",
|
|
35
|
+
"operator": "GREATER_THAN_OR_EQUAL",
|
|
36
|
+
"value": 100000,
|
|
37
|
+
"valueType": "NUMBER"
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### GROUP Condition (logical combinator)
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"type": "GROUP",
|
|
46
|
+
"operator": "AND",
|
|
47
|
+
"children": [
|
|
48
|
+
{
|
|
49
|
+
"type": "SINGLE",
|
|
50
|
+
"field": "customer_tier",
|
|
51
|
+
"operator": "EQUALS",
|
|
52
|
+
"value": "VIP",
|
|
53
|
+
"valueType": "STRING"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"type": "SINGLE",
|
|
57
|
+
"field": "payment_amount",
|
|
58
|
+
"operator": "GREATER_THAN",
|
|
59
|
+
"value": 50000,
|
|
60
|
+
"valueType": "NUMBER"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Operators
|
|
67
|
+
|
|
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 |
|
|
79
|
+
|
|
80
|
+
### Value Types
|
|
81
|
+
|
|
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]` |
|
|
89
|
+
|
|
90
|
+
### Nested Conditions Example
|
|
91
|
+
|
|
92
|
+
`(customer_tier = "VIP" AND payment_amount >= 100000) OR region IN ["KR", "JP"]`:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"type": "GROUP",
|
|
97
|
+
"operator": "OR",
|
|
98
|
+
"children": [
|
|
99
|
+
{
|
|
100
|
+
"type": "GROUP",
|
|
101
|
+
"operator": "AND",
|
|
102
|
+
"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" }
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"type": "SINGLE", "field": "region", "operator": "IN", "value": ["KR", "JP"], "valueType": "LIST_STRING"
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Action Types
|
|
115
|
+
|
|
116
|
+
Each rule can have multiple actions. Actions fire sequentially.
|
|
117
|
+
|
|
118
|
+
| Type | Description | Key Parameters |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `DISCOUNT` | Apply a discount | `method` (PERCENTAGE/FIXED), `rate`, `referenceFactKey` |
|
|
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` |
|
|
128
|
+
|
|
129
|
+
### Action Example: 10% Percentage Discount
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"type": "DISCOUNT",
|
|
134
|
+
"parameters": {
|
|
135
|
+
"method": "PERCENTAGE",
|
|
136
|
+
"rate": 10,
|
|
137
|
+
"referenceFactKey": "payment_amount"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Action Example: Block Transaction
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"type": "BLOCK",
|
|
147
|
+
"parameters": {
|
|
148
|
+
"reason": "Suspected fraud",
|
|
149
|
+
"code": "FRAUD_DETECTED"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## CRUD Commands
|
|
155
|
+
|
|
156
|
+
### List Rules
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
lexq rules list --group-id <gid> --version-id <vid> --page 0 --size 20
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Get Rule Detail
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
lexq rules get --group-id <gid> --version-id <vid> --id <ruleId>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Create Rule
|
|
169
|
+
|
|
170
|
+
**Important:** Always run `lexq facts list` first to confirm available fact keys and types.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
174
|
+
"name": "VIP 10% Discount",
|
|
175
|
+
"priority": 0,
|
|
176
|
+
"condition": {
|
|
177
|
+
"type": "GROUP",
|
|
178
|
+
"operator": "AND",
|
|
179
|
+
"children": [
|
|
180
|
+
{ "type": "SINGLE", "field": "customer_tier", "operator": "EQUALS", "value": "VIP", "valueType": "STRING" },
|
|
181
|
+
{ "type": "SINGLE", "field": "payment_amount", "operator": "GREATER_THAN_OR_EQUAL", "value": 100000, "valueType": "NUMBER" }
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
"actions": [
|
|
185
|
+
{
|
|
186
|
+
"type": "DISCOUNT",
|
|
187
|
+
"parameters": { "method": "PERCENTAGE", "rate": 10, "referenceFactKey": "payment_amount" }
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
"isEnabled": true
|
|
191
|
+
}'
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Update Rule
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
lexq rules update --group-id <gid> --version-id <vid> --id <ruleId> --json '{
|
|
198
|
+
"name": "VIP 15% Discount",
|
|
199
|
+
"actions": [
|
|
200
|
+
{
|
|
201
|
+
"type": "DISCOUNT",
|
|
202
|
+
"parameters": { "method": "PERCENTAGE", "rate": 15, "referenceFactKey": "payment_amount" }
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}'
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Delete Rule
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
lexq rules delete --group-id <gid> --version-id <vid> --id <ruleId>
|
|
212
|
+
lexq rules delete --group-id <gid> --version-id <vid> --id <ruleId> --force
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Reorder Rules
|
|
216
|
+
|
|
217
|
+
Pass rule IDs in desired priority order (index 0 = highest priority):
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
lexq rules reorder --group-id <gid> --version-id <vid> \
|
|
221
|
+
--rule-ids "ruleId_A,ruleId_B,ruleId_C"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Toggle Rule
|
|
225
|
+
|
|
226
|
+
Enable or disable a rule without deleting it:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
lexq rules toggle --group-id <gid> --version-id <vid> --id <ruleId> --enabled true
|
|
230
|
+
lexq rules toggle --group-id <gid> --version-id <vid> --id <ruleId> --enabled false
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Mutex (Rule-Level Conflict Resolution)
|
|
234
|
+
|
|
235
|
+
Within a single version, rules can belong to a `mutexGroup` to limit how many fire.
|
|
236
|
+
|
|
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 |
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
245
|
+
"name": "Discount A",
|
|
246
|
+
"priority": 0,
|
|
247
|
+
"mutexGroup": "discounts",
|
|
248
|
+
"mutexMode": "EXCLUSIVE",
|
|
249
|
+
"mutexStrategy": "HIGHEST_PRIORITY",
|
|
250
|
+
"condition": { ... },
|
|
251
|
+
"actions": [ ... ]
|
|
252
|
+
}'
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Constraint:** All rules in the same `mutexGroup` must use identical `mutexMode` and `mutexStrategy`.
|
|
256
|
+
|
|
257
|
+
## Pre-Create Checklist
|
|
258
|
+
|
|
259
|
+
Before creating rules, always:
|
|
260
|
+
|
|
261
|
+
1. **Check available facts:** `lexq facts list`
|
|
262
|
+
2. **Confirm the version is DRAFT:** `lexq versions get --group-id <gid> --id <vid>` → status must be `DRAFT`
|
|
263
|
+
3. **Use exact fact keys** from the fact definitions (snake_case, case-sensitive)
|
|
264
|
+
4. **Match value types** — a fact defined as `NUMBER` must receive numeric values, not strings
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# LexQ CLI — Shared Context
|
|
2
|
+
|
|
3
|
+
> **Read this skill first.** All other LexQ skills assume you have read this document.
|
|
4
|
+
|
|
5
|
+
## What is LexQ?
|
|
6
|
+
|
|
7
|
+
LexQ is a **policy execution engine**. Customers define business rules (conditions → actions), deploy them to production, and execute them via API — all without touching application code. Core differentiators are **pre-deploy simulation** and **A/B testing** for rule versions.
|
|
8
|
+
|
|
9
|
+
## Authentication
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Store your API key (persisted at ~/.lexq/config.json)
|
|
13
|
+
lexq auth login
|
|
14
|
+
# Enter your API Key: lxk_xxxxxxxxxxxxx
|
|
15
|
+
|
|
16
|
+
# Verify authentication
|
|
17
|
+
lexq auth whoami
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The API key is passed as `X-API-KEY` header on every request. You can also override per-command:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
lexq groups list --api-key lxk_override_key
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Base URL:** `https://api.lexq.io/api/v1/partners` (default). Override with `--base-url`.
|
|
27
|
+
|
|
28
|
+
## Core Concepts
|
|
29
|
+
|
|
30
|
+
| Concept | Description | Analogy |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| **Policy Group** | A container for rule versions. Has a lifecycle status (ACTIVE / DISABLED / ARCHIVED). | Git repository |
|
|
33
|
+
| **Policy Version** | A snapshot of rules within a group. Follows DRAFT → ACTIVE → ARCHIVED lifecycle. | Git branch / commit |
|
|
34
|
+
| **Policy Rule** | A condition + actions pair within a version. Evaluated in priority order. | if-then statement |
|
|
35
|
+
| **Fact Definition** | Input schema — declares available variables and their types (STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER). | Function parameter |
|
|
36
|
+
| **Deployment** | Promotes a PUBLISHED version to live traffic. Supports rollback. | Production release |
|
|
37
|
+
| **Dry Run** | Tests a single input against a DRAFT or ACTIVE version without side effects. | Unit test |
|
|
38
|
+
| **Simulation** | Batch-tests a version against historical execution data. Compares with a baseline. | Integration test suite |
|
|
39
|
+
|
|
40
|
+
## Standard Workflow
|
|
41
|
+
|
|
42
|
+
This is the typical lifecycle. **Always follow this order:**
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
1. lexq groups create → Create a policy group
|
|
46
|
+
2. lexq versions create → Create a DRAFT version inside it
|
|
47
|
+
3. lexq facts create → Register input variables (if not already defined)
|
|
48
|
+
4. lexq rules create → Add rules with conditions + actions
|
|
49
|
+
5. lexq analytics dry-run → Test with sample facts (validate before publish)
|
|
50
|
+
6. lexq deploy publish → DRAFT → ACTIVE (locks the version)
|
|
51
|
+
7. lexq deploy live → Deploy ACTIVE version to production
|
|
52
|
+
8. lexq analytics simulation → Run batch comparison against baseline (optional)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Critical Ordering Constraints
|
|
56
|
+
|
|
57
|
+
- **Cannot create rules without a DRAFT version.** Create the version first.
|
|
58
|
+
- **Cannot publish without at least one rule.** Add rules before publishing.
|
|
59
|
+
- **Cannot modify a published version.** Clone it to create a new DRAFT if changes are needed.
|
|
60
|
+
- **Cannot deploy a DRAFT version.** Must publish first (DRAFT → ACTIVE).
|
|
61
|
+
- **Always run `lexq analytics dry-run` before publishing.** This is your safety net.
|
|
62
|
+
|
|
63
|
+
## Global Options
|
|
64
|
+
|
|
65
|
+
Every command accepts these flags:
|
|
66
|
+
|
|
67
|
+
| Flag | Description | Default |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `--format <json\|table>` | Output format | `json` |
|
|
70
|
+
| `--api-key <key>` | Override stored API key | from config |
|
|
71
|
+
| `--base-url <url>` | Override API base URL | `https://api.lexq.io/api/v1/partners` |
|
|
72
|
+
| `--dry-run` | Preview the HTTP request without executing | off |
|
|
73
|
+
| `--verbose` | Show request/response details | off |
|
|
74
|
+
| `--no-color` | Disable colored output | off |
|
|
75
|
+
|
|
76
|
+
**Agent best practice:** Always use `--format json` (the default). Parse JSON output programmatically. Use `--format table` only when displaying to humans.
|
|
77
|
+
|
|
78
|
+
## Command Groups
|
|
79
|
+
|
|
80
|
+
| Group | Commands | Description |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `auth` | `login`, `logout`, `whoami` | Authentication |
|
|
83
|
+
| `status` | (root) | API health check |
|
|
84
|
+
| `groups` | `list`, `get`, `create`, `update`, `delete` + `ab-test start\|stop\|adjust` | Policy group CRUD + A/B testing |
|
|
85
|
+
| `versions` | `list`, `get`, `create`, `update`, `delete`, `clone` | Version CRUD |
|
|
86
|
+
| `rules` | `list`, `get`, `create`, `update`, `delete`, `reorder`, `toggle` | Rule CRUD |
|
|
87
|
+
| `facts` | `list`, `create`, `update`, `delete` | Fact definition CRUD |
|
|
88
|
+
| `deploy` | `publish`, `live`, `rollback`, `undeploy`, `history`, `detail`, `overview` | Deployment lifecycle |
|
|
89
|
+
| `analytics` | `dry-run`, `dry-run-compare`, `requirements`, `simulation start/status/list/cancel/export` | Testing & analysis |
|
|
90
|
+
| `history` | `list`, `get`, `stats` | Execution history |
|
|
91
|
+
| `integrations` | `list`, `get`, `save`, `delete`, `config-spec` | External integrations |
|
|
92
|
+
| `logs` | `list`, `get`, `action`, `bulk-action` | Failure log management |
|
|
93
|
+
|
|
94
|
+
## Pagination
|
|
95
|
+
|
|
96
|
+
All list endpoints return a `PageResponse`:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"content": [...],
|
|
101
|
+
"totalElements": 42,
|
|
102
|
+
"totalPages": 3,
|
|
103
|
+
"pageNo": 0,
|
|
104
|
+
"pageSize": 20
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Use `--page` and `--size` to paginate. Pages are **0-indexed**.
|
|
109
|
+
|
|
110
|
+
## Error Handling
|
|
111
|
+
|
|
112
|
+
API errors return:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"result": "ERROR",
|
|
117
|
+
"message": "Policy version not found.",
|
|
118
|
+
"code": "ENTITY_NOT_FOUND"
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Common error codes and what to do:**
|
|
123
|
+
|
|
124
|
+
| Code | Meaning | Action |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| `ENTITY_NOT_FOUND` | Resource doesn't exist | Verify the ID. Run the corresponding `list` command. |
|
|
127
|
+
| `INVALID_INPUT` | Validation failed | Check required fields. Run `lexq analytics requirements` for fact requirements. |
|
|
128
|
+
| `CANNOT_MODIFY` | Version is not DRAFT | Clone the version to create a new DRAFT: `lexq versions clone` |
|
|
129
|
+
| `EMPTY_RULES` | Publish attempted with 0 rules | Add at least one rule before publishing. |
|
|
130
|
+
| `UNAUTHORIZED` | Invalid or missing API key | Run `lexq auth login` with a valid key. |
|
|
131
|
+
|
|
132
|
+
## Important Conventions
|
|
133
|
+
|
|
134
|
+
1. **Fact keys use `snake_case`.** Example: `payment_amount`, `customer_tier`. Always lowercase.
|
|
135
|
+
2. **IDs are UUIDs.** Always copy the full ID from list/create output — do not guess.
|
|
136
|
+
3. **Dates use ISO 8601.** Example: `2025-01-01T00:00:00Z`. Time zone is UTC.
|
|
137
|
+
4. **JSON bodies via `--json`.** Most create/update commands accept `--json '<body>'` for the request body.
|
|
138
|
+
5. **File input via `--file`.** Analytics commands accept `--file path/to/body.json` as an alternative to `--json`.
|
|
139
|
+
6. **Confirmation prompts.** Destructive operations (delete, cancel, undeploy) prompt for confirmation. Use `--force` to skip in automation.
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# LexQ CLI — Simulation & Testing
|
|
2
|
+
|
|
3
|
+
> **Prerequisite:** Read `lexq-shared/SKILL.md` first.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
LexQ provides three levels of pre-deploy validation:
|
|
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 |
|
|
14
|
+
|
|
15
|
+
**Golden rule:** Always dry-run before publishing, always simulate before deploying to production.
|
|
16
|
+
|
|
17
|
+
## 1. Requirements Analysis
|
|
18
|
+
|
|
19
|
+
Before testing, check what facts a version expects:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
lexq analytics requirements --group-id <gid> --version-id <vid>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Response includes:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"groupId": "...",
|
|
30
|
+
"versionId": "...",
|
|
31
|
+
"versionNo": 3,
|
|
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"] }
|
|
35
|
+
],
|
|
36
|
+
"exampleRequest": {
|
|
37
|
+
"facts": { "payment_amount": 0, "customer_tier": "" },
|
|
38
|
+
"context": {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Always run this before dry-run.** Copy `exampleRequest.facts` as your starting template and fill in real values.
|
|
44
|
+
|
|
45
|
+
## 2. Dry Run (Single Input)
|
|
46
|
+
|
|
47
|
+
Test a single set of facts against a version:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
lexq analytics dry-run --version-id <vid> --json '{
|
|
51
|
+
"facts": {
|
|
52
|
+
"payment_amount": 150000,
|
|
53
|
+
"customer_tier": "VIP"
|
|
54
|
+
}
|
|
55
|
+
}'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Options
|
|
59
|
+
|
|
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` | — |
|
|
65
|
+
|
|
66
|
+
### Recommended: Always Use `--debug --mock`
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
lexq analytics dry-run --version-id <vid> --debug --mock --json '{
|
|
70
|
+
"facts": { "payment_amount": 150000, "customer_tier": "VIP" }
|
|
71
|
+
}'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Response Structure
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
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
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Reading Decision Traces
|
|
104
|
+
|
|
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 |
|
|
114
|
+
|
|
115
|
+
### Reading Reason Codes
|
|
116
|
+
|
|
117
|
+
| Code | Meaning |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `FINAL_WINNER` | Successfully executed |
|
|
120
|
+
| `CONDITION_MISMATCH` | Input facts didn't satisfy the condition |
|
|
121
|
+
| `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) |
|
|
125
|
+
|
|
126
|
+
## 3. Dry Run Compare
|
|
127
|
+
|
|
128
|
+
Compare how two versions evaluate the same input:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
lexq analytics dry-run-compare --json '{
|
|
132
|
+
"facts": { "payment_amount": 150000, "customer_tier": "VIP" },
|
|
133
|
+
"versionIdA": "<baselineVersionId>",
|
|
134
|
+
"versionIdB": "<candidateVersionId>"
|
|
135
|
+
}'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Useful for validating that changes in a new version produce expected differences.
|
|
139
|
+
|
|
140
|
+
## 4. Batch Simulation
|
|
141
|
+
|
|
142
|
+
Run a full regression test against historical execution data:
|
|
143
|
+
|
|
144
|
+
### Start Simulation
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
lexq analytics simulation start --json '{
|
|
148
|
+
"policyVersionId": "<targetVersionId>",
|
|
149
|
+
"dataset": {
|
|
150
|
+
"type": "HISTORICAL",
|
|
151
|
+
"source": "EXECUTION_LOGS",
|
|
152
|
+
"from": "2025-01-01",
|
|
153
|
+
"to": "2025-01-31"
|
|
154
|
+
},
|
|
155
|
+
"options": {
|
|
156
|
+
"includeRuleStats": true,
|
|
157
|
+
"maxRecords": 10000,
|
|
158
|
+
"baselinePolicyVersionId": "<currentLiveVersionId>",
|
|
159
|
+
"metricConfig": {
|
|
160
|
+
"targetVariable": "discount_amount",
|
|
161
|
+
"aggregationType": "SUM"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Dataset Types
|
|
168
|
+
|
|
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` |
|
|
174
|
+
|
|
175
|
+
### Check Status (Poll)
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
lexq analytics simulation status --id <simulationId>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Simulation is async. Poll until `status` is `COMPLETED` or `FAILED`.
|
|
182
|
+
|
|
183
|
+
| Status | Meaning |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `PENDING` | Queued |
|
|
186
|
+
| `RUNNING` | In progress (`progress` field shows 0–100) |
|
|
187
|
+
| `COMPLETED` | Done — results available |
|
|
188
|
+
| `FAILED` | Error occurred |
|
|
189
|
+
| `CANCELLED` | Manually cancelled |
|
|
190
|
+
|
|
191
|
+
### List Simulations
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
lexq analytics simulation list --page 0 --size 20
|
|
195
|
+
lexq analytics simulation list --status COMPLETED --from 2025-01-01 --to 2025-01-31
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Cancel Simulation
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
lexq analytics simulation cancel --id <simulationId>
|
|
202
|
+
lexq analytics simulation cancel --id <simulationId> --force
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Export Results
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
lexq analytics simulation export --id <simulationId> --format json
|
|
209
|
+
lexq analytics simulation export --id <simulationId> --format csv --output results.csv
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Simulation Response (COMPLETED)
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"simulationId": "...",
|
|
217
|
+
"status": "COMPLETED",
|
|
218
|
+
"summary": {
|
|
219
|
+
"totalRecords": 10000,
|
|
220
|
+
"matchedRecords": 8500,
|
|
221
|
+
"executionTimeMs": 3200,
|
|
222
|
+
"matchRate": 0.85
|
|
223
|
+
},
|
|
224
|
+
"metricSummary": {
|
|
225
|
+
"targetVariable": "discount_amount",
|
|
226
|
+
"aggregationType": "SUM",
|
|
227
|
+
"baselineValue": 5000000,
|
|
228
|
+
"simulatedValue": 4500000,
|
|
229
|
+
"delta": -500000,
|
|
230
|
+
"deltaPercentage": -10.0
|
|
231
|
+
},
|
|
232
|
+
"policyImpact": {
|
|
233
|
+
"policyVersionId": "...",
|
|
234
|
+
"comparison": {
|
|
235
|
+
"baselineVersionId": "...",
|
|
236
|
+
"difference": {
|
|
237
|
+
"matchedCountDelta": -200,
|
|
238
|
+
"matchedRateDelta": -0.02,
|
|
239
|
+
"metricValueDelta": -500000
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
"ruleStats": [
|
|
244
|
+
{ "ruleId": "...", "ruleName": "VIP Discount", "matchedCount": 5000, "metricValue": 3000000 }
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Agent Workflow: Validate Before Deploy
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# 1. Check what facts the version needs
|
|
253
|
+
lexq analytics requirements --group-id <gid> --version-id <vid>
|
|
254
|
+
|
|
255
|
+
# 2. Dry-run with representative inputs
|
|
256
|
+
lexq analytics dry-run --version-id <vid> --debug --mock --json '{
|
|
257
|
+
"facts": { "payment_amount": 150000, "customer_tier": "VIP" }
|
|
258
|
+
}'
|
|
259
|
+
|
|
260
|
+
# 3. If dry-run looks good, publish
|
|
261
|
+
lexq deploy publish --group-id <gid> --version-id <vid> --memo "Validated by agent"
|
|
262
|
+
|
|
263
|
+
# 4. Run simulation comparing new vs. current live version
|
|
264
|
+
lexq analytics simulation start --json '{
|
|
265
|
+
"policyVersionId": "<newVersionId>",
|
|
266
|
+
"dataset": { "type": "HISTORICAL", "source": "EXECUTION_LOGS", "from": "2025-01-01", "to": "2025-01-31" },
|
|
267
|
+
"options": {
|
|
268
|
+
"baselinePolicyVersionId": "<currentLiveVersionId>",
|
|
269
|
+
"includeRuleStats": true,
|
|
270
|
+
"metricConfig": { "targetVariable": "discount_amount", "aggregationType": "SUM" }
|
|
271
|
+
}
|
|
272
|
+
}'
|
|
273
|
+
|
|
274
|
+
# 5. Poll until complete
|
|
275
|
+
lexq analytics simulation status --id <simId>
|
|
276
|
+
|
|
277
|
+
# 6. If results acceptable, deploy
|
|
278
|
+
lexq deploy live --group-id <gid> --version-id <newVersionId> --memo "Simulation passed"
|
|
279
|
+
```
|