@lexq/cli 0.1.39 → 0.1.41

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.
@@ -10,7 +10,6 @@
10
10
  # 1. Create group
11
11
  lexq groups create --json '{
12
12
  "name": "tiered-discount",
13
- "priority": 0,
14
13
  "description": "Apply discount based on payment amount tiers"
15
14
  }'
16
15
  # → Save the group ID
@@ -23,10 +22,9 @@ lexq versions create --group-id <gid> --json '{"commitMessage": "Initial tiered
23
22
  lexq facts create --key payment_amount --name "Payment Amount" --type NUMBER --required
24
23
  lexq facts create --key customer_tier --name "Customer Tier" --type STRING
25
24
 
26
- # 4. Add rules (highest priority first)
25
+ # 4. Add rules — creation order becomes priority order (first created = priority 1 = highest)
27
26
  lexq rules create --group-id <gid> --version-id <vid> --json '{
28
27
  "name": "Premium Tier - 20%",
29
- "priority": 0,
30
28
  "condition": {
31
29
  "type": "SINGLE",
32
30
  "field": "payment_amount",
@@ -37,10 +35,10 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
37
35
  "actions": [{
38
36
  "type": "MUTATE_FACT",
39
37
  "parameters": {
40
- "refVar": "payment_amount",
38
+ "targetVar": "payment_amount",
41
39
  "method": "PERCENTAGE",
42
40
  "operator": "SUB",
43
- "rate": 20,
41
+ "operand": 20,
44
42
  "rounding": { "mode": "HALF_UP", "scale": 0 }
45
43
  }
46
44
  }]
@@ -48,7 +46,6 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
48
46
 
49
47
  lexq rules create --group-id <gid> --version-id <vid> --json '{
50
48
  "name": "Gold Tier - 10%",
51
- "priority": 1,
52
49
  "condition": {
53
50
  "type": "GROUP",
54
51
  "operator": "AND",
@@ -60,10 +57,10 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
60
57
  "actions": [{
61
58
  "type": "MUTATE_FACT",
62
59
  "parameters": {
63
- "refVar": "payment_amount",
60
+ "targetVar": "payment_amount",
64
61
  "method": "PERCENTAGE",
65
62
  "operator": "SUB",
66
- "rate": 10,
63
+ "operand": 10,
67
64
  "rounding": { "mode": "HALF_UP", "scale": 0 }
68
65
  }
69
66
  }]
@@ -71,7 +68,6 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
71
68
 
72
69
  lexq rules create --group-id <gid> --version-id <vid> --json '{
73
70
  "name": "Base Tier - 5%",
74
- "priority": 2,
75
71
  "condition": {
76
72
  "type": "SINGLE",
77
73
  "field": "payment_amount",
@@ -82,20 +78,20 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
82
78
  "actions": [{
83
79
  "type": "MUTATE_FACT",
84
80
  "parameters": {
85
- "refVar": "payment_amount",
81
+ "targetVar": "payment_amount",
86
82
  "method": "PERCENTAGE",
87
83
  "operator": "SUB",
88
- "rate": 5,
84
+ "operand": 5,
89
85
  "rounding": { "mode": "HALF_UP", "scale": 0 }
90
86
  }
91
87
  }]
92
88
  }'
93
89
 
94
90
  # 5. Validate
95
- lexq analytics dry-run --version-id <vid> --debug --mock --json '{"facts":{"payment_amount":600000}}'
91
+ lexq analytics dry-run --version-id <vid> --debug --json '{"facts":{"payment_amount":600000}}'
96
92
  # Expected: mutatedFacts.payment_amount = 480000, generatedVariables.payment_amount__delta = -120000
97
93
 
98
- lexq analytics dry-run --version-id <vid> --debug --mock --json '{"facts":{"payment_amount":200000}}'
94
+ lexq analytics dry-run --version-id <vid> --debug --json '{"facts":{"payment_amount":200000}}'
99
95
  # Expected: mutatedFacts.payment_amount = 180000, generatedVariables.payment_amount__delta = -20000
100
96
 
101
97
  # 6. Deploy
@@ -112,7 +108,6 @@ lexq deploy live --group-id <gid> --version-id <vid> --memo "Go live"
112
108
  ```bash
113
109
  lexq groups create --json '{
114
110
  "name": "fraud-detection",
115
- "priority": 0,
116
111
  "description": "Block suspicious transactions"
117
112
  }'
118
113
 
@@ -125,7 +120,6 @@ lexq facts create --key country_code --name "Country Code" --type STRING
125
120
  # High-value + high-frequency
126
121
  lexq rules create --group-id <gid> --version-id <vid> --json '{
127
122
  "name": "High Risk - Large + Frequent",
128
- "priority": 0,
129
123
  "condition": {
130
124
  "type": "GROUP",
131
125
  "operator": "AND",
@@ -135,15 +129,13 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
135
129
  ]
136
130
  },
137
131
  "actions": [
138
- { "type": "BLOCK", "parameters": { "reason": "High value + high frequency", "code": "FRAUD_HIGH_RISK" } },
139
- { "type": "ADD_TAG", "parameters": { "tag": "fraud_review" } }
132
+ { "type": "BLOCK", "parameters": { "reason": "High value + high frequency" } }
140
133
  ]
141
134
  }'
142
135
 
143
136
  # Sanctioned country
144
137
  lexq rules create --group-id <gid> --version-id <vid> --json '{
145
138
  "name": "Sanctioned Country Block",
146
- "priority": 1,
147
139
  "condition": {
148
140
  "type": "SINGLE",
149
141
  "field": "country_code",
@@ -152,12 +144,12 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
152
144
  "valueType": "LIST_STRING"
153
145
  },
154
146
  "actions": [
155
- { "type": "BLOCK", "parameters": { "reason": "Sanctioned country", "code": "COUNTRY_BLOCKED" } }
147
+ { "type": "BLOCK", "parameters": { "reason": "Sanctioned country" } }
156
148
  ]
157
149
  }'
158
150
 
159
151
  # Validate
160
- lexq analytics dry-run --version-id <vid> --debug --mock --json '{
152
+ lexq analytics dry-run --version-id <vid> --debug --json '{
161
153
  "facts": { "transaction_amount": 10000000, "transaction_count_24h": 15, "country_code": "KR" }
162
154
  }'
163
155
  ```
@@ -180,17 +172,17 @@ lexq rules update --group-id <gid> --version-id <v2id> --id <ruleId> --json '{
180
172
  "actions": [{
181
173
  "type": "MUTATE_FACT",
182
174
  "parameters": {
183
- "refVar": "payment_amount",
175
+ "targetVar": "payment_amount",
184
176
  "method": "PERCENTAGE",
185
177
  "operator": "SUB",
186
- "rate": 15,
178
+ "operand": 15,
187
179
  "rounding": { "mode": "HALF_UP", "scale": 0 }
188
180
  }
189
181
  }]
190
182
  }'
191
183
 
192
184
  # 3. Validate with dry-run
193
- lexq analytics dry-run --version-id <v2id> --debug --mock --json '{
185
+ lexq analytics dry-run --version-id <v2id> --debug --json '{
194
186
  "facts": { "payment_amount": 100000, "customer_tier": "VIP" }
195
187
  }'
196
188
 
@@ -218,10 +210,13 @@ lexq groups ab-test stop --group-id <gid> --force
218
210
 
219
211
  **Goal:** Award loyalty points based on purchase behavior.
220
212
 
213
+ **Note:** `total_points` must be present in the request facts. The engine is stateless — it does
214
+ not read your database. Send the customer's current balance (or `0` for a new customer) and apply
215
+ the returned value yourself. `MUTATE_FACT` throws if the target fact is absent.
216
+
221
217
  ```bash
222
218
  lexq groups create --json '{
223
219
  "name": "loyalty-points",
224
- "priority": 1,
225
220
  "activationMode": "NONE"
226
221
  }'
227
222
 
@@ -229,12 +224,11 @@ lexq versions create --group-id <gid> --json '{"commitMessage": "Points program
229
224
 
230
225
  lexq facts create --key purchase_amount --name "Purchase Amount" --type NUMBER --required
231
226
  lexq facts create --key is_first_purchase --name "First Purchase" --type BOOLEAN
232
- lexq facts create --key total_points --name "Total Points" --type NUMBER
227
+ lexq facts create --key total_points --name "Total Points" --type NUMBER --required
233
228
 
234
229
  # Bonus points for first purchase (fixed 200)
235
230
  lexq rules create --group-id <gid> --version-id <vid> --json '{
236
231
  "name": "First Purchase Bonus Points",
237
- "priority": 0,
238
232
  "condition": {
239
233
  "type": "SINGLE",
240
234
  "field": "is_first_purchase",
@@ -244,23 +238,12 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
244
238
  },
245
239
  "actions": [
246
240
  {
247
- "type": "INCREMENT_FACT",
241
+ "type": "MUTATE_FACT",
248
242
  "parameters": {
249
243
  "targetVar": "total_points",
244
+ "operator": "ADD",
250
245
  "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
- }
246
+ "operand": 200
264
247
  }
265
248
  }
266
249
  ]
@@ -269,7 +252,6 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
269
252
  # Standard points: 0.1% of purchase_amount = 1 point per 1000 KRW
270
253
  lexq rules create --group-id <gid> --version-id <vid> --json '{
271
254
  "name": "Standard Purchase Points",
272
- "priority": 1,
273
255
  "condition": {
274
256
  "type": "SINGLE",
275
257
  "field": "purchase_amount",
@@ -279,72 +261,39 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
279
261
  },
280
262
  "actions": [
281
263
  {
282
- "type": "INCREMENT_FACT",
264
+ "type": "MUTATE_FACT",
283
265
  "parameters": {
284
266
  "targetVar": "total_points",
285
267
  "refVar": "purchase_amount",
268
+ "operator": "ADD",
286
269
  "method": "PERCENTAGE",
287
- "rate": 0.1,
270
+ "operand": 0.1,
288
271
  "rounding": { "mode": "FLOOR", "scale": 0 }
289
272
  }
290
273
  }
291
274
  ]
292
275
  }'
293
- ```
294
276
 
295
- ---
296
-
297
- ## Recipe 5: Webhook Integration
298
-
299
- **Goal:** Call an external API when a rule matches.
300
-
301
- ```bash
302
- # 1. Create webhook integration
303
- lexq integrations save --json '{
304
- "type": "WEBHOOK",
305
- "name": "Order Processing Webhook",
306
- "baseUrl": "https://api.example.com/webhooks/orders",
307
- "isActive": true
308
- }'
309
-
310
- # 2. Use WEBHOOK action in a rule
311
- lexq rules create --group-id <gid> --version-id <vid> --json '{
312
- "name": "Large Order Alert",
313
- "priority": 0,
314
- "condition": {
315
- "type": "SINGLE",
316
- "field": "order_total",
317
- "operator": "GREATER_THAN",
318
- "value": 1000000,
319
- "valueType": "NUMBER"
320
- },
321
- "actions": [
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
- },
333
- { "type": "ADD_TAG", "parameters": { "tag": "large_order" } }
334
- ]
277
+ # Verify — new customer, 50,000 KRW purchase → 200 bonus + 50 standard = 250
278
+ lexq analytics dry-run --version-id <vid> --debug --json '{
279
+ "facts": { "purchase_amount": 50000, "is_first_purchase": true, "total_points": 0 }
335
280
  }'
336
281
  ```
337
282
 
283
+ `total_points` and `refVar: purchase_amount` are two different facts — this is exactly what
284
+ `refVar` exists for. Omitting it would compute `total_points += total_points × 0.1%`.
285
+
286
+ Read `total_points` and `total_points__delta` from `generatedVariables` in the response.
287
+
338
288
  ---
339
289
 
340
- ## Recipe 6: Exclusive Discount (Mutex)
290
+ ## Recipe 5: Exclusive Discount (Mutex)
341
291
 
342
292
  **Goal:** Ensure only the best discount applies when multiple rules match.
343
293
 
344
294
  ```bash
345
295
  lexq rules create --group-id <gid> --version-id <vid> --json '{
346
296
  "name": "VIP Discount 20%",
347
- "priority": 0,
348
297
  "mutexGroup": "best-discount",
349
298
  "mutexMode": "EXCLUSIVE",
350
299
  "mutexStrategy": "HIGHEST_PRIORITY",
@@ -354,10 +303,10 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
354
303
  "actions": [{
355
304
  "type": "MUTATE_FACT",
356
305
  "parameters": {
357
- "refVar": "payment_amount",
306
+ "targetVar": "payment_amount",
358
307
  "method": "PERCENTAGE",
359
308
  "operator": "SUB",
360
- "rate": 20,
309
+ "operand": 20,
361
310
  "rounding": { "mode": "HALF_UP", "scale": 0 }
362
311
  }
363
312
  }]
@@ -365,7 +314,6 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
365
314
 
366
315
  lexq rules create --group-id <gid> --version-id <vid> --json '{
367
316
  "name": "Seasonal Sale 15%",
368
- "priority": 1,
369
317
  "mutexGroup": "best-discount",
370
318
  "mutexMode": "EXCLUSIVE",
371
319
  "mutexStrategy": "HIGHEST_PRIORITY",
@@ -375,67 +323,26 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
375
323
  "actions": [{
376
324
  "type": "MUTATE_FACT",
377
325
  "parameters": {
378
- "refVar": "payment_amount",
326
+ "targetVar": "payment_amount",
379
327
  "method": "PERCENTAGE",
380
328
  "operator": "SUB",
381
- "rate": 15,
329
+ "operand": 15,
382
330
  "rounding": { "mode": "HALF_UP", "scale": 0 }
383
331
  }
384
332
  }]
385
333
  }'
386
334
 
387
- # If a VIP customer pays 50000+, only the 20% VIP discount fires (priority 0 wins).
388
- ```
389
-
390
- ---
391
-
392
- ## Recipe 7: Region-Based Coupon
393
-
394
- **Goal:** Issue different coupons by region.
395
-
396
- ```bash
397
- lexq facts create --key user_region --name "User Region" --type STRING --required
398
-
399
- lexq rules create --group-id <gid> --version-id <vid> --json '{
400
- "name": "Korea Welcome Coupon",
401
- "priority": 0,
402
- "condition": {
403
- "type": "SINGLE", "field": "user_region", "operator": "IN", "value": ["KR"], "valueType": "LIST_STRING"
404
- },
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
- }]
415
- }'
416
-
417
- lexq rules create --group-id <gid> --version-id <vid> --json '{
418
- "name": "US Welcome Coupon",
419
- "priority": 1,
420
- "condition": {
421
- "type": "SINGLE", "field": "user_region", "operator": "IN", "value": ["US"], "valueType": "LIST_STRING"
422
- },
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
- }]
433
- }'
335
+ # Rules are appended in creation order. Confirm the order with `lexq rules list`,
336
+ # or set it explicitly:
337
+ # lexq rules reorder --group-id <gid> --version-id <vid> --rule-ids "<vipRuleId>,<seasonalRuleId>"
338
+ #
339
+ # If a VIP customer pays 50000+, only the 20% VIP discount fires — HIGHEST_PRIORITY
340
+ # picks the rule with the lower priority number within the mutex group.
434
341
  ```
435
342
 
436
343
  ---
437
344
 
438
- ## Recipe 8: Version Rollback
345
+ ## Recipe 6: Version Rollback
439
346
 
440
347
  **Goal:** Something went wrong in production — revert to the previous version.
441
348
 
@@ -453,27 +360,37 @@ lexq history stats
453
360
 
454
361
  ---
455
362
 
456
- ## Recipe 9: Monitoring + Auto-Resolve Failures
363
+ ## Recipe 7: Monitoring + Failure Triage
364
+
365
+ **Goal:** Check for pending failures and clear the queue.
457
366
 
458
- **Goal:** Check for pending failures and resolve them.
367
+ Failure logs are a dead-letter record for background tasks — platform event webhooks and scheduled
368
+ deployments. There is no retry action: the engine does not re-execute a failed task on request.
369
+ Fix the cause at the source (webhook endpoint, version state), then mark the log.
459
370
 
460
371
  ```bash
461
372
  # 1. List pending failures
462
373
  lexq logs list --status PENDING --page 0 --size 50
463
374
 
464
- # 2. Retry transient failures
465
- lexq logs bulk-action --ids "id1,id2,id3" --action RETRY
375
+ # 2. Inspect one to see the original payload and error
376
+ lexq logs get --id <logId>
466
377
 
467
- # 3. Resolve permanent failures (after manual review)
468
- lexq logs bulk-action --ids "id4,id5" --action RESOLVE
378
+ # 3. Mark as resolved after fixing the cause externally
379
+ lexq logs bulk-action --ids "id1,id2,id3" --action RESOLVE
469
380
 
470
- # 4. Verify clean state
381
+ # 4. Mark as intentionally skipped (won't appear in PENDING again)
382
+ lexq logs bulk-action --ids "id4,id5" --action IGNORE
383
+
384
+ # 5. Verify clean state
471
385
  lexq logs list --status PENDING --page 0 --size 10
472
386
  ```
473
387
 
388
+ `RESOLVE` and `IGNORE` differ only in intent — both remove the log from PENDING. Use `RESOLVE`
389
+ when the underlying problem was fixed, `IGNORE` when it does not need fixing.
390
+
474
391
  ---
475
392
 
476
- ## Recipe 10: Full Policy Migration Workflow
393
+ ## Recipe 8: Full Policy Migration Workflow
477
394
 
478
395
  **Goal:** Create an entirely new version of a policy with different logic.
479
396
 
@@ -490,7 +407,7 @@ lexq rules delete --group-id <gid> --version-id <newVid> --id <obsoleteRuleId> -
490
407
  lexq rules create --group-id <gid> --version-id <newVid> --json '{...}'
491
408
 
492
409
  # 4. Dry-run test multiple scenarios
493
- lexq analytics dry-run --version-id <newVid> --debug --mock --json '{"facts":{...}}'
410
+ lexq analytics dry-run --version-id <newVid> --debug --json '{"facts":{...}}'
494
411
 
495
412
  # 5. Run simulation against live baseline
496
413
  lexq deploy publish --group-id <gid> --version-id <newVid> --memo "v2 migration"
@@ -502,4 +419,60 @@ lexq analytics simulation start --json '{
502
419
 
503
420
  # 6. If simulation passes, deploy
504
421
  lexq deploy live --group-id <gid> --version-id <newVid> --memo "Migration complete"
505
- ```
422
+ ```
423
+
424
+ ---
425
+
426
+ ## Recipe 9: Flag-Based Segmentation
427
+
428
+ **Goal:** Set segment flags that downstream systems read from the decision response.
429
+
430
+ `SET_FACT` assigns a value — it does not append. To accumulate into a `LIST_STRING` fact, send the
431
+ current list in the request facts and assign the full new list. For most segmentation, separate
432
+ boolean or string flags are simpler and easier to query.
433
+
434
+ ```bash
435
+ lexq groups create --json '{"name": "segmentation", "activationMode": "NONE"}'
436
+ lexq versions create --group-id <gid> --json '{"commitMessage": "Segments v1"}'
437
+
438
+ lexq facts create --key lifetime_value --name "Lifetime Value" --type NUMBER --required
439
+ lexq facts create --key signup_days --name "Days Since Signup" --type NUMBER --required
440
+ lexq facts create --key support_tier --name "Support Tier" --type STRING
441
+ lexq facts create --key beta_enabled --name "Beta Access" --type BOOLEAN
442
+
443
+ # High-value customer → priority support
444
+ lexq rules create --group-id <gid> --version-id <vid> --json '{
445
+ "name": "High Value Segment",
446
+ "condition": {
447
+ "type": "SINGLE", "field": "lifetime_value",
448
+ "operator": "GREATER_THAN_OR_EQUAL", "value": 1000000, "valueType": "NUMBER"
449
+ },
450
+ "actions": [
451
+ { "type": "SET_FACT", "parameters": { "targetVar": "support_tier", "value": "PRIORITY" } }
452
+ ]
453
+ }'
454
+
455
+ # Long-tenured customer → beta access
456
+ lexq rules create --group-id <gid> --version-id <vid> --json '{
457
+ "name": "Veteran Beta Access",
458
+ "condition": {
459
+ "type": "SINGLE", "field": "signup_days",
460
+ "operator": "GREATER_THAN", "value": 365, "valueType": "NUMBER"
461
+ },
462
+ "actions": [
463
+ { "type": "SET_FACT", "parameters": { "targetVar": "beta_enabled", "value": true } }
464
+ ]
465
+ }'
466
+
467
+ lexq analytics dry-run --version-id <vid> --debug --json '{
468
+ "facts": { "lifetime_value": 1500000, "signup_days": 400 }
469
+ }'
470
+ ```
471
+
472
+ **Notes**
473
+
474
+ - `SET_FACT` creates the fact if it does not exist. `MUTATE_FACT` requires the target to already
475
+ be present — that is the division of labor between them.
476
+ - Flags appear in `generatedVariables` in the response. `SET_FACT` produces no `__delta` because
477
+ it is an assignment, not an arithmetic change.
478
+ - Segment on a `LIST_STRING` fact with `HAS_ANY` / `HAS_ALL` / `HAS_NONE`, not `CONTAINS`.