@lexq/cli 1.1.0 → 1.1.2
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 +2 -2
- package/CONTEXT.md +5 -5
- package/README.md +1 -1
- package/dist/index.js +74 -38
- package/dist/mcp/register.js +32 -19
- package/package.json +1 -1
- package/skills/lexq-groups/SKILL.md +38 -11
- package/skills/lexq-recipes/SKILL.md +4 -2
- package/skills/lexq-rules/SKILL.md +11 -4
- package/skills/lexq-shared/SKILL.md +3 -0
package/AGENTS.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
## What is this?
|
|
6
6
|
|
|
7
7
|
LexQ CLI (`@lexq/cli`, binary: `lexq`) manages a policy execution engine. Policies are business rules (if-then) that
|
|
8
|
-
evaluate input facts and produce actions
|
|
8
|
+
evaluate input facts and produce actions: change a number such as a discount, set a fact, or record a rejection.
|
|
9
9
|
|
|
10
10
|
The CLI also doubles as an **MCP server** — run `lexq serve --mcp` to expose the full command inventory as MCP tools to
|
|
11
11
|
any MCP-compatible AI client.
|
|
@@ -106,7 +106,7 @@ lexq deploy live --group-id $GID --version-id $VID --memo "Initial deploy"
|
|
|
106
106
|
|
|
107
107
|
## Example: Subscribe to Deployment Notifications
|
|
108
108
|
|
|
109
|
-
Webhook subscriptions fire on
|
|
109
|
+
Webhook subscriptions fire on deployment lifecycle events.
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
112
|
# Subscribe to Slack for deployment events
|
package/CONTEXT.md
CHANGED
|
@@ -64,11 +64,11 @@ Tenant
|
|
|
64
64
|
|
|
65
65
|
### Policy Group Status
|
|
66
66
|
|
|
67
|
-
| Value | Description
|
|
68
|
-
|
|
69
|
-
| `ACTIVE` | Normal operation
|
|
70
|
-
| `DISABLED` |
|
|
71
|
-
| `ARCHIVED` |
|
|
67
|
+
| Value | Description |
|
|
68
|
+
|------------|---------------------------------------------------------------------------------------------------------------------|
|
|
69
|
+
| `ACTIVE` | Normal operation |
|
|
70
|
+
| `DISABLED` | Production execution rejected (`P-009`); dry run, simulation, and replay still work |
|
|
71
|
+
| `ARCHIVED` | Deleted. Kept for audit if it was ever executed, deployed, or replayed; otherwise permanently deleted after 30 days |
|
|
72
72
|
|
|
73
73
|
### Policy Version Status
|
|
74
74
|
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ Requires **Node.js 22+**.
|
|
|
48
48
|
```bash
|
|
49
49
|
# 1. Authenticate
|
|
50
50
|
lexq auth login
|
|
51
|
-
# Enter your API key (create one at console.lexq.io → Management → API Keys)
|
|
51
|
+
# Enter your API key (create one with the Manage scope at console.lexq.io → Management → API Keys)
|
|
52
52
|
|
|
53
53
|
# 2. Verify
|
|
54
54
|
lexq auth whoami
|
package/dist/index.js
CHANGED
|
@@ -163,6 +163,10 @@ import Table from "cli-table3";
|
|
|
163
163
|
function printJson(data) {
|
|
164
164
|
console.log(stringifyJson(data, 2));
|
|
165
165
|
}
|
|
166
|
+
function groupLabel(name, archivedAt, fallback = "\u2013") {
|
|
167
|
+
const label = name ?? fallback;
|
|
168
|
+
return archivedAt ? `(deleted) ${label}` : label;
|
|
169
|
+
}
|
|
166
170
|
function printTable(headers, rows, options) {
|
|
167
171
|
const table = new Table({
|
|
168
172
|
head: headers,
|
|
@@ -227,7 +231,8 @@ function registerAuthCommands(program) {
|
|
|
227
231
|
whoami Verify authentication and show account info
|
|
228
232
|
|
|
229
233
|
Getting Started:
|
|
230
|
-
1.
|
|
234
|
+
1. Create an API key with the Manage scope in the LexQ Console (Management → API Keys).
|
|
235
|
+
An "Execute only" key is for the execution API and does not work with this CLI.
|
|
231
236
|
2. Run: lexq auth login
|
|
232
237
|
3. Verify: lexq auth whoami
|
|
233
238
|
`
|
|
@@ -269,7 +274,7 @@ function registerAuthCommands(program) {
|
|
|
269
274
|
|
|
270
275
|
Example:
|
|
271
276
|
$ lexq auth whoami
|
|
272
|
-
{ "tenantId": "abc-123", "userId": "...", "role": "
|
|
277
|
+
{ "tenantId": "abc-123", "userId": "...", "role": "API_ADMIN", "tenantTimezone": "Asia/Seoul", "apiKey": "lexq****abcd", "baseUrl": "https://api.lexq.io/api/v1/partners" }
|
|
273
278
|
`
|
|
274
279
|
).action(async () => {
|
|
275
280
|
try {
|
|
@@ -310,7 +315,7 @@ function registerStatusCommand(program) {
|
|
|
310
315
|
|
|
311
316
|
Example:
|
|
312
317
|
$ lexq status
|
|
313
|
-
{ "status": "ok", "latencyMs": 142, "tenantId": "abc-123", "role": "
|
|
318
|
+
{ "status": "ok", "latencyMs": 142, "tenantId": "abc-123", "role": "API_ADMIN", "tenantTimezone": "Asia/Seoul" }
|
|
314
319
|
|
|
315
320
|
Use this to verify your API key is valid and the LexQ API is reachable.
|
|
316
321
|
tenantTimezone is the zone every yyyy-MM-dd date option is read in.
|
|
@@ -342,6 +347,8 @@ var MAX_ROUNDING_SCALE = 34;
|
|
|
342
347
|
var MIN_TRAFFIC_RATE = 1;
|
|
343
348
|
var MAX_TRAFFIC_RATE = 99;
|
|
344
349
|
var REPLAY_WINDOW_MAX_RECORDS = 5e4;
|
|
350
|
+
var DEFAULT_SIMULATION_RECORDS = 1e3;
|
|
351
|
+
var MAX_SIMULATION_RECORDS = 1e5;
|
|
345
352
|
var PROFILE_SAMPLE_PERMILLE = 10;
|
|
346
353
|
var LATENCY_WINDOW_MILLIS = 6e4;
|
|
347
354
|
var TAIL_MIN_OBS = 3;
|
|
@@ -384,18 +391,21 @@ function registerGroupCommands(program) {
|
|
|
384
391
|
It controls deployment lifecycle, conflict resolution, and A/B testing.
|
|
385
392
|
|
|
386
393
|
Commands:
|
|
387
|
-
list List
|
|
394
|
+
list List policy groups (deleted groups are not included)
|
|
388
395
|
get Get group detail by ID
|
|
389
396
|
create Create a new group
|
|
390
397
|
update Update group settings
|
|
391
|
-
reorder Change group priorities (drag &
|
|
398
|
+
reorder Change group priorities (drag & drop equivalent)
|
|
392
399
|
delete Archive a group
|
|
393
400
|
ab-test Manage A/B tests (start, stop, adjust)
|
|
394
401
|
|
|
395
|
-
Statuses: ACTIVE, DISABLED
|
|
402
|
+
Statuses: ACTIVE, DISABLED, ARCHIVED
|
|
403
|
+
DISABLED stops production execution (403 P-009) and keeps the deployed version and any A/B test.
|
|
404
|
+
Dry run, simulation, and replay still work. Set ACTIVE to resume.
|
|
405
|
+
ARCHIVED means deleted. Only 'lexq groups delete' sets it, and it cannot be undone.
|
|
396
406
|
`
|
|
397
407
|
);
|
|
398
|
-
groups.command("list").description("List
|
|
408
|
+
groups.command("list").description("List policy groups (deleted groups are not included)").action(async () => {
|
|
399
409
|
try {
|
|
400
410
|
const globalOpts = program.opts();
|
|
401
411
|
const format = globalOpts.format ?? "json";
|
|
@@ -490,6 +500,7 @@ ${data.length} total`);
|
|
|
490
500
|
$ lexq groups update --id <groupId> --json '{"description": "Updated", "status": "DISABLED"}'
|
|
491
501
|
|
|
492
502
|
All fields are optional — only provided fields are updated. priority is not settable here — use 'lexq groups reorder'.
|
|
503
|
+
A deleted group cannot be updated (409 P-059).
|
|
493
504
|
`
|
|
494
505
|
).action(async (opts) => {
|
|
495
506
|
try {
|
|
@@ -512,7 +523,15 @@ ${data.length} total`);
|
|
|
512
523
|
"after",
|
|
513
524
|
dedent3`
|
|
514
525
|
|
|
515
|
-
|
|
526
|
+
A deployed group cannot be deleted (P-020). Undeploy it first, which also ends a running A/B test.
|
|
527
|
+
|
|
528
|
+
A deleted group no longer appears in 'lexq groups list', and its name can be reused right away.
|
|
529
|
+
It cannot be restored, and calls that change it (settings, versions, rules, starting an A/B
|
|
530
|
+
test, publish, deploy, rollback, scheduling a deployment) return 409 P-059. If it was ever
|
|
531
|
+
executed, deployed, or replayed, it is kept for audit; otherwise it is permanently deleted
|
|
532
|
+
after 30 days.
|
|
533
|
+
|
|
534
|
+
Use --force to skip the confirmation prompt.
|
|
516
535
|
`
|
|
517
536
|
).action(async (opts) => {
|
|
518
537
|
try {
|
|
@@ -1328,7 +1347,9 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
1328
1347
|
dedent6`
|
|
1329
1348
|
|
|
1330
1349
|
Facts used in a version's rules that have no definition yet (read-only).
|
|
1331
|
-
|
|
1350
|
+
Keys a rule only reads do not block publish/deploy; register them to enable
|
|
1351
|
+
validation. A SET_FACT or MUTATE_FACT targetVar must already be registered:
|
|
1352
|
+
saving or publishing fails with ACT-032 otherwise.
|
|
1332
1353
|
|
|
1333
1354
|
Example:
|
|
1334
1355
|
$ lexq facts unregistered --group-id <gid> --version-id <vid> --format table
|
|
@@ -1799,7 +1820,7 @@ function registerDeployCommands(program) {
|
|
|
1799
1820
|
data.content.map((d) => [
|
|
1800
1821
|
d.id.substring(0, 8),
|
|
1801
1822
|
d.deploymentType,
|
|
1802
|
-
d.policyGroupName,
|
|
1823
|
+
groupLabel(d.policyGroupName, d.policyGroupArchivedAt),
|
|
1803
1824
|
d.versionNo != null ? `v${d.versionNo}` : "\u2013",
|
|
1804
1825
|
d.deployedByName,
|
|
1805
1826
|
utcMinute(d.deployedAt)
|
|
@@ -2022,7 +2043,11 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2022
2043
|
["Status", "Group", "Version", "Scheduled For", "By", "Result"],
|
|
2023
2044
|
data.content.map((s) => [
|
|
2024
2045
|
s.status,
|
|
2025
|
-
|
|
2046
|
+
groupLabel(
|
|
2047
|
+
s.policyGroupName,
|
|
2048
|
+
s.policyGroupArchivedAt,
|
|
2049
|
+
s.policyGroupId.substring(0, 8)
|
|
2050
|
+
),
|
|
2026
2051
|
s.versionNo != null ? `v${s.versionNo}` : "\u2013",
|
|
2027
2052
|
utcMinute(s.scheduledFor),
|
|
2028
2053
|
s.scheduledByName,
|
|
@@ -2375,7 +2400,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2375
2400
|
["ID", "Group", "Target", "Status", "Match", "Records", "Created"],
|
|
2376
2401
|
data.content.map((s) => [
|
|
2377
2402
|
s.simulationId.substring(0, 8),
|
|
2378
|
-
s.policyGroupName,
|
|
2403
|
+
groupLabel(s.policyGroupName, s.policyGroupArchivedAt),
|
|
2379
2404
|
s.targetVersionName ?? "\u2013",
|
|
2380
2405
|
s.status,
|
|
2381
2406
|
`${s.matchRate.toFixed(1)}%`,
|
|
@@ -2736,12 +2761,12 @@ function registerHistoryCommands(program) {
|
|
|
2736
2761
|
["Trace", "Group", "Version", "Status", "Matched", "Latency", "At"],
|
|
2737
2762
|
data.content.map((h) => [
|
|
2738
2763
|
h.traceId.substring(0, 12),
|
|
2739
|
-
h.policyGroupName
|
|
2764
|
+
groupLabel(h.policyGroupName, h.policyGroupArchivedAt),
|
|
2740
2765
|
h.policyVersionNo != null ? `v${h.policyVersionNo}` : "\u2013",
|
|
2741
2766
|
h.status,
|
|
2742
2767
|
h.isMatched ? "\u2713" : "\u2717",
|
|
2743
2768
|
`${h.latencyMs}ms`,
|
|
2744
|
-
utcMinute(h.
|
|
2769
|
+
utcMinute(h.executedAt)
|
|
2745
2770
|
]),
|
|
2746
2771
|
{ truncate: 20 }
|
|
2747
2772
|
);
|
|
@@ -3250,8 +3275,8 @@ function registerWebhookSubscriptionCommands(program) {
|
|
|
3250
3275
|
"after",
|
|
3251
3276
|
dedent14`
|
|
3252
3277
|
|
|
3253
|
-
Receive notifications
|
|
3254
|
-
|
|
3278
|
+
Receive notifications for deployment lifecycle events: publish, deploy, rollback, and
|
|
3279
|
+
undeploy, plus a deployment being scheduled or a schedule being canceled.
|
|
3255
3280
|
|
|
3256
3281
|
Commands:
|
|
3257
3282
|
list List all webhook subscriptions
|
|
@@ -3260,8 +3285,8 @@ function registerWebhookSubscriptionCommands(program) {
|
|
|
3260
3285
|
delete Delete a subscription
|
|
3261
3286
|
test Send a test event to verify connectivity
|
|
3262
3287
|
|
|
3263
|
-
|
|
3264
|
-
|
|
3288
|
+
Rule actions never call external systems. These subscriptions are how LexQ pushes deployment
|
|
3289
|
+
events to you.
|
|
3265
3290
|
`
|
|
3266
3291
|
);
|
|
3267
3292
|
webhooks.command("list").description("List webhook subscriptions").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
@@ -3346,7 +3371,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
3346
3371
|
Fields:
|
|
3347
3372
|
name string Subscription name (required, unique per tenant)
|
|
3348
3373
|
webhookUrl string Webhook endpoint URL (required)
|
|
3349
|
-
subscribedEvents string[]
|
|
3374
|
+
subscribedEvents string[] ${PlatformEventType.join(", ")}
|
|
3350
3375
|
payloadFormat string GENERIC (full JSON) or SLACK ({"text":"..."}) [default: GENERIC]
|
|
3351
3376
|
secret string HMAC-SHA256 signing secret (optional)
|
|
3352
3377
|
isActive boolean Enable/disable (optional) [default: true]
|
|
@@ -3560,7 +3585,7 @@ function registerGroupTools(server, callApi) {
|
|
|
3560
3585
|
{
|
|
3561
3586
|
title: "List Policy Groups",
|
|
3562
3587
|
annotations: { readOnlyHint: true },
|
|
3563
|
-
description: "List
|
|
3588
|
+
description: "List policy groups (tenant-wide, priority ASC). Deleted groups are not included.",
|
|
3564
3589
|
inputSchema: z2.object({})
|
|
3565
3590
|
},
|
|
3566
3591
|
async () => callApi("GET", "policy-groups")
|
|
@@ -3613,12 +3638,14 @@ function registerGroupTools(server, callApi) {
|
|
|
3613
3638
|
{
|
|
3614
3639
|
title: "Update Policy Group",
|
|
3615
3640
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
3616
|
-
description: "Update a policy group. Only provided fields are updated; omitted fields remain unchanged.",
|
|
3641
|
+
description: "Update a policy group. Only provided fields are updated; omitted fields remain unchanged. A deleted group cannot be updated (409 P-059).",
|
|
3617
3642
|
inputSchema: z2.object({
|
|
3618
3643
|
groupId: z2.string().uuid().describe("Policy group ID"),
|
|
3619
3644
|
name: z2.string().optional().describe("New name"),
|
|
3620
3645
|
description: z2.string().optional().describe("New description"),
|
|
3621
|
-
status: z2.enum(["ACTIVE", "DISABLED"]).optional().describe(
|
|
3646
|
+
status: z2.enum(["ACTIVE", "DISABLED"]).optional().describe(
|
|
3647
|
+
"DISABLED stops production execution of this group: execution calls return 403 (P-009), and a composite call that includes the group is rejected in full. The deployed version and any A/B test are kept, and dry run, Impact Simulation, and Decision Replay keep working, so a fix can be verified before setting ACTIVE again. To go back to the previous version, use lexq_deploy_rollback. To take the version out of production, use lexq_deploy_undeploy."
|
|
3648
|
+
),
|
|
3622
3649
|
activationGroup: z2.string().optional().describe(
|
|
3623
3650
|
"Activation group (Execution Group) cluster key. Policy groups sharing this key compete, and group priority picks the winners."
|
|
3624
3651
|
),
|
|
@@ -3636,7 +3663,7 @@ function registerGroupTools(server, callApi) {
|
|
|
3636
3663
|
{
|
|
3637
3664
|
title: "Delete Policy Group",
|
|
3638
3665
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
3639
|
-
description: "
|
|
3666
|
+
description: "Delete (archive) a policy group. A deployed group cannot be deleted (P-020); undeploy it first with lexq_deploy_undeploy, which also ends a running A/B test. This cannot be undone. The group no longer appears in lexq_groups_list and its name can be reused right away. Calls that change it (settings, versions, rules, starting an A/B test, publish, deploy, rollback, scheduling a deployment) return 409 P-059. If it was ever executed, deployed, or replayed, it is kept for audit; otherwise it is permanently deleted after 30 days.",
|
|
3640
3667
|
inputSchema: z2.object({
|
|
3641
3668
|
groupId: z2.string().uuid().describe("Policy group ID")
|
|
3642
3669
|
})
|
|
@@ -3842,7 +3869,12 @@ function registerRuleTools(server, callApi) {
|
|
|
3842
3869
|
If a required key is missing, ask the user to confirm the type, isRequired, and description
|
|
3843
3870
|
before calling lexq_facts_create — registering facts enables type validation, Console UI
|
|
3844
3871
|
autocomplete, and the dry-run requirements analyzer.
|
|
3845
|
-
|
|
3872
|
+
|
|
3873
|
+
An action's targetVar (SET_FACT, MUTATE_FACT) must be a registered fact. Saving or
|
|
3874
|
+
publishing a rule whose targetVar is not registered fails with ACT-032, so register it
|
|
3875
|
+
with lexq_facts_create first. Keys a rule only reads (condition fields, refVar) may stay
|
|
3876
|
+
unregistered.
|
|
3877
|
+
|
|
3846
3878
|
After saving, lexq_facts_unregistered lists any keys this version references but has not
|
|
3847
3879
|
defined (non-blocking, version-wide) — use it to decide what to register.
|
|
3848
3880
|
|
|
@@ -3871,8 +3903,9 @@ function registerRuleTools(server, callApi) {
|
|
|
3871
3903
|
|
|
3872
3904
|
Action parameter schemas:
|
|
3873
3905
|
- MUTATE_FACT: { targetVar: string, operator: "ASSIGN"|"ADD"|"SUB"|"MUL"|"DIV", method: "PERCENTAGE"|"AMOUNT", operand: number, refVar?: string, rounding?: RoundingOption }
|
|
3874
|
-
targetVar is the fact this action reads and writes. It must
|
|
3875
|
-
|
|
3906
|
+
targetVar is the fact this action reads and writes. It must be a registered fact
|
|
3907
|
+
(ACT-032 otherwise), and the request must send it (P-015 otherwise). Its value must be
|
|
3908
|
+
a number when the action runs.
|
|
3876
3909
|
A missing required fact throws (no 0 default).
|
|
3877
3910
|
operand is the arithmetic operand; the unit is dictated by method (percent when
|
|
3878
3911
|
PERCENTAGE, absolute amount when AMOUNT). Ranges are not constrained — negative values
|
|
@@ -3890,8 +3923,10 @@ function registerRuleTools(server, callApi) {
|
|
|
3890
3923
|
DIV targetVar /= operand | invalid
|
|
3891
3924
|
Constraints: DIV + PERCENTAGE is invalid (use MUL with the inverse). DIV + AMOUNT
|
|
3892
3925
|
requires operand !== 0.
|
|
3893
|
-
- SET_FACT: { targetVar: string, value: string|number|boolean }
|
|
3894
|
-
|
|
3926
|
+
- SET_FACT: { targetVar: string, value: string|number|boolean } targetVar must be a
|
|
3927
|
+
registered fact (ACT-032 otherwise). The request does not have to send it: SET_FACT is
|
|
3928
|
+
the only action that can write a key absent from the input facts. MUTATE_FACT requires
|
|
3929
|
+
its target to be present in the input facts.
|
|
3895
3930
|
- BLOCK: { reason: string } Records a rejection decision. It does NOT halt rule execution —
|
|
3896
3931
|
subsequent actions and subsequent winning rules still run. Enforcement is the caller's
|
|
3897
3932
|
responsibility; the decision surfaces as the isBlocked fact.
|
|
@@ -3916,7 +3951,7 @@ function registerRuleTools(server, callApi) {
|
|
|
3916
3951
|
{
|
|
3917
3952
|
title: "Update Rule",
|
|
3918
3953
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
3919
|
-
description: `Update an existing rule in a DRAFT version. Only provided fields are changed. A new condition tree must stay within ${MAX_CONDITIONS_PER_RULE} conditions (SINGLE nodes) and ${MAX_CONDITION_DEPTH} levels of nesting below the root.`,
|
|
3954
|
+
description: `Update an existing rule in a DRAFT version. Only provided fields are changed. A new condition tree must stay within ${MAX_CONDITIONS_PER_RULE} conditions (SINGLE nodes) and ${MAX_CONDITION_DEPTH} levels of nesting below the root. Saving re-checks every action in the rule, including ones you did not change, and fails with ACT-032 if any action's targetVar is not a registered fact.`,
|
|
3920
3955
|
inputSchema: z4.object({
|
|
3921
3956
|
groupId: z4.string().uuid().describe("Policy group ID"),
|
|
3922
3957
|
versionId: z4.string().uuid().describe("Version ID"),
|
|
@@ -4055,13 +4090,13 @@ function registerFactTools(server, callApi) {
|
|
|
4055
4090
|
{
|
|
4056
4091
|
title: "Update Fact Definition",
|
|
4057
4092
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4058
|
-
description: "Update a fact definition. The key is immutable. The type can change only while no rule references the fact; if any does, the call fails with FD-007 and reports the count. Only the fields you send are changed. System facts accept name, description, and PII only.",
|
|
4093
|
+
description: "Update a fact definition. The key is immutable. The type can change only while no rule references the fact; if any does, the call fails with FD-007 and reports the count. Rules in deleted groups do not count. Only the fields you send are changed. System facts accept name, description, and PII only.",
|
|
4059
4094
|
inputSchema: z5.object({
|
|
4060
4095
|
factId: z5.string().uuid().describe("Fact definition ID"),
|
|
4061
4096
|
name: z5.string().optional().describe("Display name"),
|
|
4062
4097
|
description: z5.string().optional().describe("Description"),
|
|
4063
4098
|
type: z5.enum(VALUE_TYPES).optional().describe(
|
|
4064
|
-
"Value type. Omit to leave it unchanged. Changing it fails with FD-007 while any rule references the fact."
|
|
4099
|
+
"Value type. Omit to leave it unchanged. Changing it fails with FD-007 while any rule outside a deleted group references the fact."
|
|
4065
4100
|
),
|
|
4066
4101
|
isRequired: z5.boolean().optional().describe("Required flag"),
|
|
4067
4102
|
isPii: z5.boolean().optional().describe("PII flag \u2014 enables/disables masking (changeable even on system facts)"),
|
|
@@ -4083,7 +4118,7 @@ function registerFactTools(server, callApi) {
|
|
|
4083
4118
|
{
|
|
4084
4119
|
title: "Delete Fact Definition",
|
|
4085
4120
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4086
|
-
description: "Delete a fact definition. System facts cannot be deleted. Neither can a fact that any rule references: that call fails with FD-006 and reports the count. Remove the references first.",
|
|
4121
|
+
description: "Delete a fact definition. System facts cannot be deleted. Neither can a fact that any rule references: that call fails with FD-006 and reports the count. Rules in deleted groups do not count. Remove the references first.",
|
|
4087
4122
|
inputSchema: z5.object({
|
|
4088
4123
|
factId: z5.string().uuid().describe("Fact definition ID")
|
|
4089
4124
|
})
|
|
@@ -4095,7 +4130,7 @@ function registerFactTools(server, callApi) {
|
|
|
4095
4130
|
{
|
|
4096
4131
|
title: "Get Action Runtime Fact Metadata",
|
|
4097
4132
|
annotations: { readOnlyHint: true },
|
|
4098
|
-
description: "Retrieve runtime fact requirements per Action type. For each Action, shows which input facts must be present in the execution payload \u2014 e.g. MUTATE_FACT always requires its targetVar fact, plus refVar when one is specified. The factRequired flag describes the FACT, not the parameter: refVar is an optional parameter, but if you specify it the named fact must exist. A required fact absent at runtime throws \u2014 the engine never defaults to 0.
|
|
4133
|
+
description: "Retrieve runtime fact requirements per Action type. For each Action, shows which input facts must be present in the execution payload \u2014 e.g. MUTATE_FACT always requires its targetVar fact, plus refVar when one is specified. The factRequired flag describes the FACT, not the parameter: refVar is an optional parameter, but if you specify it the named fact must exist. A required fact absent at runtime throws \u2014 the engine never defaults to 0. These facts must be sent in the request (P-015 otherwise), even when an earlier action in the same rule writes them. SET_FACT requires no input fact: it is the only action that can write a key the request did not send, and its targetVar must be a registered fact (ACT-032). Static data, safe to cache in-session.",
|
|
4099
4134
|
inputSchema: z5.object({})
|
|
4100
4135
|
},
|
|
4101
4136
|
async () => callApi("GET", "schema/action-metadata")
|
|
@@ -4105,7 +4140,7 @@ function registerFactTools(server, callApi) {
|
|
|
4105
4140
|
{
|
|
4106
4141
|
title: "List Unregistered Facts",
|
|
4107
4142
|
annotations: { readOnlyHint: true },
|
|
4108
|
-
description: "List facts referenced by a version's rules but not yet defined (read-only
|
|
4143
|
+
description: "List facts referenced by a version's rules but not yet defined (read-only). Keys that rules only read do not block publish/deploy; a rule whose action targetVar is not a registered fact is rejected at save and publish with ACT-032. Version-wide: covers every rule in the version. Each entry carries the inferred type, suggested name, and where it is referenced (condition/action). Register them with lexq_facts_create to enable type validation and the dry-run requirements analyzer.",
|
|
4109
4144
|
inputSchema: z5.object({
|
|
4110
4145
|
groupId: z5.string().uuid().describe("Policy group ID"),
|
|
4111
4146
|
versionId: z5.string().uuid().describe("Version ID")
|
|
@@ -4138,7 +4173,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4138
4173
|
{
|
|
4139
4174
|
title: "Publish Version",
|
|
4140
4175
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
4141
|
-
description: `Publish a DRAFT version (DRAFT \u2192 ACTIVE). Locks the version from further edits. Must have at least one rule. Undefined facts
|
|
4176
|
+
description: `Publish a DRAFT version (DRAFT \u2192 ACTIVE). Locks the version from further edits. Must have at least one rule. Undefined facts that rules only read do not block publishing; call lexq_facts_unregistered first to review them. Publishing fails if an action's targetVar is not a registered fact (ACT-032), if the version's compiled rules exceed ${MAX_SNAPSHOT_BYTES / 1024} KB, or if its effective end has already passed (P-058); move the end into the future with lexq_versions_update first.`,
|
|
4142
4177
|
inputSchema: z6.object({
|
|
4143
4178
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4144
4179
|
versionId: z6.string().uuid().describe("Version ID to publish"),
|
|
@@ -4395,7 +4430,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4395
4430
|
UPLOADED → source S3_BUCKET, with dataset.path (the path returned by lexq_dataset_upload)
|
|
4396
4431
|
MANUAL → source REQUEST_BODY, with dataset.manualData (array of fact records)
|
|
4397
4432
|
|
|
4398
|
-
options.maxRecords: number (max
|
|
4433
|
+
options.maxRecords: number (max ${MAX_SIMULATION_RECORDS}). When omitted, HISTORICAL and UPLOADED run at
|
|
4434
|
+
most ${DEFAULT_SIMULATION_RECORDS} records and MANUAL runs every record sent.
|
|
4399
4435
|
options.baselinePolicyVersionId: uuid (optional, for baseline comparison)
|
|
4400
4436
|
options.includeRuleStats: boolean
|
|
4401
4437
|
options.metricConfig: optional — omit for plain execution count. To aggregate a fact, pass
|
|
@@ -4875,7 +4911,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
4875
4911
|
{
|
|
4876
4912
|
title: "List Webhook Subscriptions",
|
|
4877
4913
|
annotations: { readOnlyHint: true },
|
|
4878
|
-
description: "List platform event webhook subscriptions. These receive deployment lifecycle notifications
|
|
4914
|
+
description: "List platform event webhook subscriptions. These receive deployment lifecycle notifications: publish, deploy, rollback, and undeploy, plus a deployment being scheduled or a schedule being canceled.",
|
|
4879
4915
|
inputSchema: z13.object({
|
|
4880
4916
|
page: z13.number().int().min(0).default(0).describe("Page number"),
|
|
4881
4917
|
size: z13.number().int().min(1).max(100).default(20).describe("Page size")
|
|
@@ -4903,7 +4939,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
4903
4939
|
{
|
|
4904
4940
|
title: "Save Webhook Subscription",
|
|
4905
4941
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4906
|
-
description:
|
|
4942
|
+
description: `Create or update a webhook subscription. Omit id to create, provide id to update. Events: ${PlatformEventType.join(", ")}. Formats: GENERIC (full JSON), SLACK ({"text": "..."}).`,
|
|
4907
4943
|
inputSchema: z13.object({
|
|
4908
4944
|
id: z13.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
|
|
4909
4945
|
name: z13.string().min(1).describe("Subscription name (unique per tenant)"),
|
package/dist/mcp/register.js
CHANGED
|
@@ -37,6 +37,8 @@ var WebhookPayloadFormat = ["GENERIC", "SLACK"];
|
|
|
37
37
|
// src/types/constants.ts
|
|
38
38
|
var MAX_ROUNDING_SCALE = 34;
|
|
39
39
|
var REPLAY_WINDOW_MAX_RECORDS = 5e4;
|
|
40
|
+
var DEFAULT_SIMULATION_RECORDS = 1e3;
|
|
41
|
+
var MAX_SIMULATION_RECORDS = 1e5;
|
|
40
42
|
var PROFILE_SAMPLE_PERMILLE = 10;
|
|
41
43
|
var LATENCY_WINDOW_MILLIS = 6e4;
|
|
42
44
|
var TAIL_MIN_OBS = 3;
|
|
@@ -58,7 +60,7 @@ function registerGroupTools(server, callApi) {
|
|
|
58
60
|
{
|
|
59
61
|
title: "List Policy Groups",
|
|
60
62
|
annotations: { readOnlyHint: true },
|
|
61
|
-
description: "List
|
|
63
|
+
description: "List policy groups (tenant-wide, priority ASC). Deleted groups are not included.",
|
|
62
64
|
inputSchema: z2.object({})
|
|
63
65
|
},
|
|
64
66
|
async () => callApi("GET", "policy-groups")
|
|
@@ -111,12 +113,14 @@ function registerGroupTools(server, callApi) {
|
|
|
111
113
|
{
|
|
112
114
|
title: "Update Policy Group",
|
|
113
115
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
114
|
-
description: "Update a policy group. Only provided fields are updated; omitted fields remain unchanged.",
|
|
116
|
+
description: "Update a policy group. Only provided fields are updated; omitted fields remain unchanged. A deleted group cannot be updated (409 P-059).",
|
|
115
117
|
inputSchema: z2.object({
|
|
116
118
|
groupId: z2.string().uuid().describe("Policy group ID"),
|
|
117
119
|
name: z2.string().optional().describe("New name"),
|
|
118
120
|
description: z2.string().optional().describe("New description"),
|
|
119
|
-
status: z2.enum(["ACTIVE", "DISABLED"]).optional().describe(
|
|
121
|
+
status: z2.enum(["ACTIVE", "DISABLED"]).optional().describe(
|
|
122
|
+
"DISABLED stops production execution of this group: execution calls return 403 (P-009), and a composite call that includes the group is rejected in full. The deployed version and any A/B test are kept, and dry run, Impact Simulation, and Decision Replay keep working, so a fix can be verified before setting ACTIVE again. To go back to the previous version, use lexq_deploy_rollback. To take the version out of production, use lexq_deploy_undeploy."
|
|
123
|
+
),
|
|
120
124
|
activationGroup: z2.string().optional().describe(
|
|
121
125
|
"Activation group (Execution Group) cluster key. Policy groups sharing this key compete, and group priority picks the winners."
|
|
122
126
|
),
|
|
@@ -134,7 +138,7 @@ function registerGroupTools(server, callApi) {
|
|
|
134
138
|
{
|
|
135
139
|
title: "Delete Policy Group",
|
|
136
140
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
137
|
-
description: "
|
|
141
|
+
description: "Delete (archive) a policy group. A deployed group cannot be deleted (P-020); undeploy it first with lexq_deploy_undeploy, which also ends a running A/B test. This cannot be undone. The group no longer appears in lexq_groups_list and its name can be reused right away. Calls that change it (settings, versions, rules, starting an A/B test, publish, deploy, rollback, scheduling a deployment) return 409 P-059. If it was ever executed, deployed, or replayed, it is kept for audit; otherwise it is permanently deleted after 30 days.",
|
|
138
142
|
inputSchema: z2.object({
|
|
139
143
|
groupId: z2.string().uuid().describe("Policy group ID")
|
|
140
144
|
})
|
|
@@ -409,7 +413,12 @@ function registerRuleTools(server, callApi) {
|
|
|
409
413
|
If a required key is missing, ask the user to confirm the type, isRequired, and description
|
|
410
414
|
before calling lexq_facts_create — registering facts enables type validation, Console UI
|
|
411
415
|
autocomplete, and the dry-run requirements analyzer.
|
|
412
|
-
|
|
416
|
+
|
|
417
|
+
An action's targetVar (SET_FACT, MUTATE_FACT) must be a registered fact. Saving or
|
|
418
|
+
publishing a rule whose targetVar is not registered fails with ACT-032, so register it
|
|
419
|
+
with lexq_facts_create first. Keys a rule only reads (condition fields, refVar) may stay
|
|
420
|
+
unregistered.
|
|
421
|
+
|
|
413
422
|
After saving, lexq_facts_unregistered lists any keys this version references but has not
|
|
414
423
|
defined (non-blocking, version-wide) — use it to decide what to register.
|
|
415
424
|
|
|
@@ -438,8 +447,9 @@ function registerRuleTools(server, callApi) {
|
|
|
438
447
|
|
|
439
448
|
Action parameter schemas:
|
|
440
449
|
- MUTATE_FACT: { targetVar: string, operator: "ASSIGN"|"ADD"|"SUB"|"MUL"|"DIV", method: "PERCENTAGE"|"AMOUNT", operand: number, refVar?: string, rounding?: RoundingOption }
|
|
441
|
-
targetVar is the fact this action reads and writes. It must
|
|
442
|
-
|
|
450
|
+
targetVar is the fact this action reads and writes. It must be a registered fact
|
|
451
|
+
(ACT-032 otherwise), and the request must send it (P-015 otherwise). Its value must be
|
|
452
|
+
a number when the action runs.
|
|
443
453
|
A missing required fact throws (no 0 default).
|
|
444
454
|
operand is the arithmetic operand; the unit is dictated by method (percent when
|
|
445
455
|
PERCENTAGE, absolute amount when AMOUNT). Ranges are not constrained — negative values
|
|
@@ -457,8 +467,10 @@ function registerRuleTools(server, callApi) {
|
|
|
457
467
|
DIV targetVar /= operand | invalid
|
|
458
468
|
Constraints: DIV + PERCENTAGE is invalid (use MUL with the inverse). DIV + AMOUNT
|
|
459
469
|
requires operand !== 0.
|
|
460
|
-
- SET_FACT: { targetVar: string, value: string|number|boolean }
|
|
461
|
-
|
|
470
|
+
- SET_FACT: { targetVar: string, value: string|number|boolean } targetVar must be a
|
|
471
|
+
registered fact (ACT-032 otherwise). The request does not have to send it: SET_FACT is
|
|
472
|
+
the only action that can write a key absent from the input facts. MUTATE_FACT requires
|
|
473
|
+
its target to be present in the input facts.
|
|
462
474
|
- BLOCK: { reason: string } Records a rejection decision. It does NOT halt rule execution —
|
|
463
475
|
subsequent actions and subsequent winning rules still run. Enforcement is the caller's
|
|
464
476
|
responsibility; the decision surfaces as the isBlocked fact.
|
|
@@ -483,7 +495,7 @@ function registerRuleTools(server, callApi) {
|
|
|
483
495
|
{
|
|
484
496
|
title: "Update Rule",
|
|
485
497
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
486
|
-
description: `Update an existing rule in a DRAFT version. Only provided fields are changed. A new condition tree must stay within ${MAX_CONDITIONS_PER_RULE} conditions (SINGLE nodes) and ${MAX_CONDITION_DEPTH} levels of nesting below the root.`,
|
|
498
|
+
description: `Update an existing rule in a DRAFT version. Only provided fields are changed. A new condition tree must stay within ${MAX_CONDITIONS_PER_RULE} conditions (SINGLE nodes) and ${MAX_CONDITION_DEPTH} levels of nesting below the root. Saving re-checks every action in the rule, including ones you did not change, and fails with ACT-032 if any action's targetVar is not a registered fact.`,
|
|
487
499
|
inputSchema: z4.object({
|
|
488
500
|
groupId: z4.string().uuid().describe("Policy group ID"),
|
|
489
501
|
versionId: z4.string().uuid().describe("Version ID"),
|
|
@@ -626,13 +638,13 @@ function registerFactTools(server, callApi) {
|
|
|
626
638
|
{
|
|
627
639
|
title: "Update Fact Definition",
|
|
628
640
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
629
|
-
description: "Update a fact definition. The key is immutable. The type can change only while no rule references the fact; if any does, the call fails with FD-007 and reports the count. Only the fields you send are changed. System facts accept name, description, and PII only.",
|
|
641
|
+
description: "Update a fact definition. The key is immutable. The type can change only while no rule references the fact; if any does, the call fails with FD-007 and reports the count. Rules in deleted groups do not count. Only the fields you send are changed. System facts accept name, description, and PII only.",
|
|
630
642
|
inputSchema: z5.object({
|
|
631
643
|
factId: z5.string().uuid().describe("Fact definition ID"),
|
|
632
644
|
name: z5.string().optional().describe("Display name"),
|
|
633
645
|
description: z5.string().optional().describe("Description"),
|
|
634
646
|
type: z5.enum(VALUE_TYPES).optional().describe(
|
|
635
|
-
"Value type. Omit to leave it unchanged. Changing it fails with FD-007 while any rule references the fact."
|
|
647
|
+
"Value type. Omit to leave it unchanged. Changing it fails with FD-007 while any rule outside a deleted group references the fact."
|
|
636
648
|
),
|
|
637
649
|
isRequired: z5.boolean().optional().describe("Required flag"),
|
|
638
650
|
isPii: z5.boolean().optional().describe("PII flag \u2014 enables/disables masking (changeable even on system facts)"),
|
|
@@ -654,7 +666,7 @@ function registerFactTools(server, callApi) {
|
|
|
654
666
|
{
|
|
655
667
|
title: "Delete Fact Definition",
|
|
656
668
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
657
|
-
description: "Delete a fact definition. System facts cannot be deleted. Neither can a fact that any rule references: that call fails with FD-006 and reports the count. Remove the references first.",
|
|
669
|
+
description: "Delete a fact definition. System facts cannot be deleted. Neither can a fact that any rule references: that call fails with FD-006 and reports the count. Rules in deleted groups do not count. Remove the references first.",
|
|
658
670
|
inputSchema: z5.object({
|
|
659
671
|
factId: z5.string().uuid().describe("Fact definition ID")
|
|
660
672
|
})
|
|
@@ -666,7 +678,7 @@ function registerFactTools(server, callApi) {
|
|
|
666
678
|
{
|
|
667
679
|
title: "Get Action Runtime Fact Metadata",
|
|
668
680
|
annotations: { readOnlyHint: true },
|
|
669
|
-
description: "Retrieve runtime fact requirements per Action type. For each Action, shows which input facts must be present in the execution payload \u2014 e.g. MUTATE_FACT always requires its targetVar fact, plus refVar when one is specified. The factRequired flag describes the FACT, not the parameter: refVar is an optional parameter, but if you specify it the named fact must exist. A required fact absent at runtime throws \u2014 the engine never defaults to 0.
|
|
681
|
+
description: "Retrieve runtime fact requirements per Action type. For each Action, shows which input facts must be present in the execution payload \u2014 e.g. MUTATE_FACT always requires its targetVar fact, plus refVar when one is specified. The factRequired flag describes the FACT, not the parameter: refVar is an optional parameter, but if you specify it the named fact must exist. A required fact absent at runtime throws \u2014 the engine never defaults to 0. These facts must be sent in the request (P-015 otherwise), even when an earlier action in the same rule writes them. SET_FACT requires no input fact: it is the only action that can write a key the request did not send, and its targetVar must be a registered fact (ACT-032). Static data, safe to cache in-session.",
|
|
670
682
|
inputSchema: z5.object({})
|
|
671
683
|
},
|
|
672
684
|
async () => callApi("GET", "schema/action-metadata")
|
|
@@ -676,7 +688,7 @@ function registerFactTools(server, callApi) {
|
|
|
676
688
|
{
|
|
677
689
|
title: "List Unregistered Facts",
|
|
678
690
|
annotations: { readOnlyHint: true },
|
|
679
|
-
description: "List facts referenced by a version's rules but not yet defined (read-only
|
|
691
|
+
description: "List facts referenced by a version's rules but not yet defined (read-only). Keys that rules only read do not block publish/deploy; a rule whose action targetVar is not a registered fact is rejected at save and publish with ACT-032. Version-wide: covers every rule in the version. Each entry carries the inferred type, suggested name, and where it is referenced (condition/action). Register them with lexq_facts_create to enable type validation and the dry-run requirements analyzer.",
|
|
680
692
|
inputSchema: z5.object({
|
|
681
693
|
groupId: z5.string().uuid().describe("Policy group ID"),
|
|
682
694
|
versionId: z5.string().uuid().describe("Version ID")
|
|
@@ -709,7 +721,7 @@ function registerDeployTools(server, callApi) {
|
|
|
709
721
|
{
|
|
710
722
|
title: "Publish Version",
|
|
711
723
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
712
|
-
description: `Publish a DRAFT version (DRAFT \u2192 ACTIVE). Locks the version from further edits. Must have at least one rule. Undefined facts
|
|
724
|
+
description: `Publish a DRAFT version (DRAFT \u2192 ACTIVE). Locks the version from further edits. Must have at least one rule. Undefined facts that rules only read do not block publishing; call lexq_facts_unregistered first to review them. Publishing fails if an action's targetVar is not a registered fact (ACT-032), if the version's compiled rules exceed ${MAX_SNAPSHOT_BYTES / 1024} KB, or if its effective end has already passed (P-058); move the end into the future with lexq_versions_update first.`,
|
|
713
725
|
inputSchema: z6.object({
|
|
714
726
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
715
727
|
versionId: z6.string().uuid().describe("Version ID to publish"),
|
|
@@ -966,7 +978,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
966
978
|
UPLOADED → source S3_BUCKET, with dataset.path (the path returned by lexq_dataset_upload)
|
|
967
979
|
MANUAL → source REQUEST_BODY, with dataset.manualData (array of fact records)
|
|
968
980
|
|
|
969
|
-
options.maxRecords: number (max
|
|
981
|
+
options.maxRecords: number (max ${MAX_SIMULATION_RECORDS}). When omitted, HISTORICAL and UPLOADED run at
|
|
982
|
+
most ${DEFAULT_SIMULATION_RECORDS} records and MANUAL runs every record sent.
|
|
970
983
|
options.baselinePolicyVersionId: uuid (optional, for baseline comparison)
|
|
971
984
|
options.includeRuleStats: boolean
|
|
972
985
|
options.metricConfig: optional — omit for plain execution count. To aggregate a fact, pass
|
|
@@ -1446,7 +1459,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
1446
1459
|
{
|
|
1447
1460
|
title: "List Webhook Subscriptions",
|
|
1448
1461
|
annotations: { readOnlyHint: true },
|
|
1449
|
-
description: "List platform event webhook subscriptions. These receive deployment lifecycle notifications
|
|
1462
|
+
description: "List platform event webhook subscriptions. These receive deployment lifecycle notifications: publish, deploy, rollback, and undeploy, plus a deployment being scheduled or a schedule being canceled.",
|
|
1450
1463
|
inputSchema: z13.object({
|
|
1451
1464
|
page: z13.number().int().min(0).default(0).describe("Page number"),
|
|
1452
1465
|
size: z13.number().int().min(1).max(100).default(20).describe("Page size")
|
|
@@ -1474,7 +1487,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
1474
1487
|
{
|
|
1475
1488
|
title: "Save Webhook Subscription",
|
|
1476
1489
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
1477
|
-
description:
|
|
1490
|
+
description: `Create or update a webhook subscription. Omit id to create, provide id to update. Events: ${PlatformEventType.join(", ")}. Formats: GENERIC (full JSON), SLACK ({"text": "..."}).`,
|
|
1478
1491
|
inputSchema: z13.object({
|
|
1479
1492
|
id: z13.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
|
|
1480
1493
|
name: z13.string().min(1).describe("Subscription name (unique per tenant)"),
|
package/package.json
CHANGED
|
@@ -17,9 +17,24 @@ controls conflict resolution when multiple groups interact.
|
|
|
17
17
|
| `DISABLED` | `ARCHIVED` | **no** |
|
|
18
18
|
|
|
19
19
|
- **ACTIVE** — executions are processed.
|
|
20
|
-
- **DISABLED** —
|
|
20
|
+
- **DISABLED** — production execution stops. Execution calls return `P-009` (403), and a composite call that includes
|
|
21
|
+
the group is rejected in full. The live version and any A/B test are kept, and dry run, Impact Simulation, and
|
|
22
|
+
Decision Replay keep working, so you can verify a fix before setting `ACTIVE` again.
|
|
21
23
|
- **ARCHIVED** — removed from execution. Reached only through `lexq groups delete`, not by setting `status` directly.
|
|
22
24
|
|
|
25
|
+
### Disable, Roll Back, or Undeploy?
|
|
26
|
+
|
|
27
|
+
| Situation | Use | Callers get |
|
|
28
|
+
|-----------------------------------------------------------------------|------------------------|------------------------------------------------------------------------|
|
|
29
|
+
| The previously deployed version is correct | `lexq deploy rollback` | Decisions from the previous version |
|
|
30
|
+
| This version has to leave production | `lexq deploy undeploy` | `P-019` (400). Composite calls skip the group. A running A/B test ends |
|
|
31
|
+
| Stop now without knowing the cause, then resume with the same version | `status: DISABLED` | `P-009` (403). Composite calls that include the group are rejected |
|
|
32
|
+
|
|
33
|
+
Do not disable a group for a planned end, such as a promotion with a known end date. Set the version's `effectiveTo`
|
|
34
|
+
instead. When that time passes, the group switches to its newest `ACTIVE` version that is still in effect, or is
|
|
35
|
+
undeployed if there is none. Composite calls keep working either way. A disabled group rejects every composite call
|
|
36
|
+
that includes it.
|
|
37
|
+
|
|
23
38
|
## CRUD Commands
|
|
24
39
|
|
|
25
40
|
### List Groups
|
|
@@ -93,20 +108,31 @@ lexq groups delete --id <groupId>
|
|
|
93
108
|
lexq groups delete --id <groupId> --force
|
|
94
109
|
```
|
|
95
110
|
|
|
96
|
-
**`delete` archives — it does not erase.** The group becomes `ARCHIVED` and stops
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
**`delete` archives — it does not erase right away.** The group becomes `ARCHIVED` and stops
|
|
112
|
+
executing. If it was ever executed, deployed, or replayed, it is kept for audit, along with its
|
|
113
|
+
versions and rules. Otherwise it is permanently deleted after 30 days, together with its
|
|
114
|
+
versions, rules, simulations, and schedules. Deleting an already-archived group is a no-op.
|
|
99
115
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
| Condition | What to do first |
|
|
103
|
-
|------------------------|-----------------------------------------------------|
|
|
104
|
-
| The group is deployed | `lexq deploy undeploy --group-id <gid>` |
|
|
105
|
-
| An A/B test is running | `lexq groups ab-test stop --group-id <gid> --force` |
|
|
116
|
+
A deployed group cannot be deleted (`P-020`). Undeploy it first with
|
|
117
|
+
`lexq deploy undeploy --group-id <gid> --memo "<reason>"`, which also ends a running A/B test.
|
|
106
118
|
|
|
107
119
|
Archiving cancels any pending scheduled deployment and closes the priority gap — remaining groups
|
|
108
120
|
are renumbered 1…N.
|
|
109
121
|
|
|
122
|
+
A deleted group no longer appears in `lexq groups list` and cannot be restored or changed.
|
|
123
|
+
Changing its settings, versions, or rules, starting an A/B test, and publishing, deploying,
|
|
124
|
+
rolling back, or scheduling a deployment all return `409` `P-059`. To start over, create a new
|
|
125
|
+
group.
|
|
126
|
+
|
|
127
|
+
**The name can be reused right away.** Records keep the group's ID and the name it had, so after
|
|
128
|
+
a new group takes the name, deployment history, scheduled deployments, execution history,
|
|
129
|
+
simulations, and replays can list two groups under that name. These records carry
|
|
130
|
+
`policyGroupArchivedAt`, the time the group was deleted (`null` if it has not been). In the
|
|
131
|
+
tables of `lexq deploy history`, `deploy schedules`, `history list`, and
|
|
132
|
+
`analytics simulation list` (`--format table`), a deleted group's name shows as
|
|
133
|
+
`(deleted) <name>`. To see one group's deployments or executions only, pass its ID to
|
|
134
|
+
`lexq deploy history --group-id` or `lexq history list --group-id`.
|
|
135
|
+
|
|
110
136
|
## Conflict Resolution
|
|
111
137
|
|
|
112
138
|
### Activation Mode (across groups in the same `activationGroup`)
|
|
@@ -246,4 +272,5 @@ lets you reuse the existing group instead of handling the error.
|
|
|
246
272
|
lexq groups update --id <groupId> --json '{"status": "DISABLED"}'
|
|
247
273
|
```
|
|
248
274
|
|
|
249
|
-
This immediately stops
|
|
275
|
+
This immediately stops production execution for the group. Dry run, Impact Simulation, and Decision Replay keep
|
|
276
|
+
working. No undeploy needed.
|
|
@@ -480,8 +480,10 @@ lexq analytics dry-run --version-id <vid> --debug --json '{
|
|
|
480
480
|
|
|
481
481
|
**Notes**
|
|
482
482
|
|
|
483
|
-
- `SET_FACT`
|
|
484
|
-
|
|
483
|
+
- Register a `SET_FACT` target before saving the rule, as the step above does. A rule whose
|
|
484
|
+
`targetVar` is not a registered fact is rejected with `ACT-032`.
|
|
485
|
+
- At execution, `SET_FACT` writes the key even when the request did not send it. `MUTATE_FACT`
|
|
486
|
+
requires the request to send its target. That is the division of labor between them.
|
|
485
487
|
- Flags appear in `generatedVariables` in the response. `SET_FACT` produces no `__delta` because
|
|
486
488
|
it is an assignment, not an arithmetic change.
|
|
487
489
|
- Segment on a `LIST_STRING` fact with `HAS_ANY` / `HAS_ALL` / `HAS_NONE`, not `CONTAINS`.
|
|
@@ -181,14 +181,14 @@ Each rule can have multiple actions. Actions fire sequentially.
|
|
|
181
181
|
| Type | Description | Key Parameters |
|
|
182
182
|
|---------------|--------------------------------------------|----------------------------------------------------------------------|
|
|
183
183
|
| `MUTATE_FACT` | Arithmetic change to a numeric fact | `targetVar`, `operator`, `method`, `operand`, `refVar?`, `rounding?` |
|
|
184
|
-
| `SET_FACT` | Assign a literal value
|
|
184
|
+
| `SET_FACT` | Assign a literal value to a registered fact | `targetVar`, `value` |
|
|
185
185
|
| `BLOCK` | Record a rejection decision | `reason` |
|
|
186
186
|
|
|
187
187
|
### `MUTATE_FACT` parameters
|
|
188
188
|
|
|
189
189
|
| Parameter | Required | Meaning |
|
|
190
190
|
|-------------|----------|-------------------------------------------------------------------------------|
|
|
191
|
-
| `targetVar` | always | The fact this action **reads and writes**. Must
|
|
191
|
+
| `targetVar` | always | The fact this action **reads and writes**. Must be registered and present as a number at execution. |
|
|
192
192
|
| `operator` | always | `ASSIGN` \| `ADD` \| `SUB` \| `MUL` \| `DIV` |
|
|
193
193
|
| `method` | always | `PERCENTAGE` \| `AMOUNT` — dictates the unit of `operand` |
|
|
194
194
|
| `operand` | always | The arithmetic operand. Percent when PERCENTAGE, absolute amount when AMOUNT. |
|
|
@@ -235,8 +235,15 @@ Use `refVar` when the base differs from the target:
|
|
|
235
235
|
|
|
236
236
|
### `SET_FACT` vs `MUTATE_FACT`
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
Both actions write to `targetVar`, and it must be a **registered fact**. Saving or publishing a
|
|
239
|
+
rule whose `targetVar` is not registered fails with `ACT-032`; register it with
|
|
240
|
+
`lexq facts create` first. Keys a rule only reads (condition fields, `refVar`) may stay
|
|
241
|
+
unregistered.
|
|
242
|
+
|
|
243
|
+
At execution, they differ in what the request must send. `SET_FACT` writes the value even when
|
|
244
|
+
the request did not send the key. `MUTATE_FACT` requires the request to send the target (`P-015`
|
|
245
|
+
otherwise), and the target's value must be a number when the action runs. "Write something the
|
|
246
|
+
request didn't send" is `SET_FACT`'s job.
|
|
240
247
|
|
|
241
248
|
### `BLOCK` does not halt execution
|
|
242
249
|
|
|
@@ -19,6 +19,9 @@ lexq auth login
|
|
|
19
19
|
lexq auth whoami
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
Create the key with the **Manage** scope in the Console (Management → API Keys). An **Execute only** key is
|
|
23
|
+
for calling the execution API from your application and does not work with this CLI.
|
|
24
|
+
|
|
22
25
|
The API key is passed as `X-API-KEY` header on every request. You can also override per-command:
|
|
23
26
|
|
|
24
27
|
```bash
|