@lexq/cli 1.1.1 → 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 +54 -28
- package/dist/mcp/register.js +15 -10
- package/package.json +1 -1
- package/skills/lexq-groups/SKILL.md +38 -11
- 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 {
|
|
@@ -1801,7 +1820,7 @@ function registerDeployCommands(program) {
|
|
|
1801
1820
|
data.content.map((d) => [
|
|
1802
1821
|
d.id.substring(0, 8),
|
|
1803
1822
|
d.deploymentType,
|
|
1804
|
-
d.policyGroupName,
|
|
1823
|
+
groupLabel(d.policyGroupName, d.policyGroupArchivedAt),
|
|
1805
1824
|
d.versionNo != null ? `v${d.versionNo}` : "\u2013",
|
|
1806
1825
|
d.deployedByName,
|
|
1807
1826
|
utcMinute(d.deployedAt)
|
|
@@ -2024,7 +2043,11 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2024
2043
|
["Status", "Group", "Version", "Scheduled For", "By", "Result"],
|
|
2025
2044
|
data.content.map((s) => [
|
|
2026
2045
|
s.status,
|
|
2027
|
-
|
|
2046
|
+
groupLabel(
|
|
2047
|
+
s.policyGroupName,
|
|
2048
|
+
s.policyGroupArchivedAt,
|
|
2049
|
+
s.policyGroupId.substring(0, 8)
|
|
2050
|
+
),
|
|
2028
2051
|
s.versionNo != null ? `v${s.versionNo}` : "\u2013",
|
|
2029
2052
|
utcMinute(s.scheduledFor),
|
|
2030
2053
|
s.scheduledByName,
|
|
@@ -2377,7 +2400,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2377
2400
|
["ID", "Group", "Target", "Status", "Match", "Records", "Created"],
|
|
2378
2401
|
data.content.map((s) => [
|
|
2379
2402
|
s.simulationId.substring(0, 8),
|
|
2380
|
-
s.policyGroupName,
|
|
2403
|
+
groupLabel(s.policyGroupName, s.policyGroupArchivedAt),
|
|
2381
2404
|
s.targetVersionName ?? "\u2013",
|
|
2382
2405
|
s.status,
|
|
2383
2406
|
`${s.matchRate.toFixed(1)}%`,
|
|
@@ -2738,12 +2761,12 @@ function registerHistoryCommands(program) {
|
|
|
2738
2761
|
["Trace", "Group", "Version", "Status", "Matched", "Latency", "At"],
|
|
2739
2762
|
data.content.map((h) => [
|
|
2740
2763
|
h.traceId.substring(0, 12),
|
|
2741
|
-
h.policyGroupName
|
|
2764
|
+
groupLabel(h.policyGroupName, h.policyGroupArchivedAt),
|
|
2742
2765
|
h.policyVersionNo != null ? `v${h.policyVersionNo}` : "\u2013",
|
|
2743
2766
|
h.status,
|
|
2744
2767
|
h.isMatched ? "\u2713" : "\u2717",
|
|
2745
2768
|
`${h.latencyMs}ms`,
|
|
2746
|
-
utcMinute(h.
|
|
2769
|
+
utcMinute(h.executedAt)
|
|
2747
2770
|
]),
|
|
2748
2771
|
{ truncate: 20 }
|
|
2749
2772
|
);
|
|
@@ -3252,8 +3275,8 @@ function registerWebhookSubscriptionCommands(program) {
|
|
|
3252
3275
|
"after",
|
|
3253
3276
|
dedent14`
|
|
3254
3277
|
|
|
3255
|
-
Receive notifications
|
|
3256
|
-
|
|
3278
|
+
Receive notifications for deployment lifecycle events: publish, deploy, rollback, and
|
|
3279
|
+
undeploy, plus a deployment being scheduled or a schedule being canceled.
|
|
3257
3280
|
|
|
3258
3281
|
Commands:
|
|
3259
3282
|
list List all webhook subscriptions
|
|
@@ -3262,8 +3285,8 @@ function registerWebhookSubscriptionCommands(program) {
|
|
|
3262
3285
|
delete Delete a subscription
|
|
3263
3286
|
test Send a test event to verify connectivity
|
|
3264
3287
|
|
|
3265
|
-
|
|
3266
|
-
|
|
3288
|
+
Rule actions never call external systems. These subscriptions are how LexQ pushes deployment
|
|
3289
|
+
events to you.
|
|
3267
3290
|
`
|
|
3268
3291
|
);
|
|
3269
3292
|
webhooks.command("list").description("List webhook subscriptions").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
@@ -3348,7 +3371,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
3348
3371
|
Fields:
|
|
3349
3372
|
name string Subscription name (required, unique per tenant)
|
|
3350
3373
|
webhookUrl string Webhook endpoint URL (required)
|
|
3351
|
-
subscribedEvents string[]
|
|
3374
|
+
subscribedEvents string[] ${PlatformEventType.join(", ")}
|
|
3352
3375
|
payloadFormat string GENERIC (full JSON) or SLACK ({"text":"..."}) [default: GENERIC]
|
|
3353
3376
|
secret string HMAC-SHA256 signing secret (optional)
|
|
3354
3377
|
isActive boolean Enable/disable (optional) [default: true]
|
|
@@ -3562,7 +3585,7 @@ function registerGroupTools(server, callApi) {
|
|
|
3562
3585
|
{
|
|
3563
3586
|
title: "List Policy Groups",
|
|
3564
3587
|
annotations: { readOnlyHint: true },
|
|
3565
|
-
description: "List
|
|
3588
|
+
description: "List policy groups (tenant-wide, priority ASC). Deleted groups are not included.",
|
|
3566
3589
|
inputSchema: z2.object({})
|
|
3567
3590
|
},
|
|
3568
3591
|
async () => callApi("GET", "policy-groups")
|
|
@@ -3615,12 +3638,14 @@ function registerGroupTools(server, callApi) {
|
|
|
3615
3638
|
{
|
|
3616
3639
|
title: "Update Policy Group",
|
|
3617
3640
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
3618
|
-
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).",
|
|
3619
3642
|
inputSchema: z2.object({
|
|
3620
3643
|
groupId: z2.string().uuid().describe("Policy group ID"),
|
|
3621
3644
|
name: z2.string().optional().describe("New name"),
|
|
3622
3645
|
description: z2.string().optional().describe("New description"),
|
|
3623
|
-
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
|
+
),
|
|
3624
3649
|
activationGroup: z2.string().optional().describe(
|
|
3625
3650
|
"Activation group (Execution Group) cluster key. Policy groups sharing this key compete, and group priority picks the winners."
|
|
3626
3651
|
),
|
|
@@ -3638,7 +3663,7 @@ function registerGroupTools(server, callApi) {
|
|
|
3638
3663
|
{
|
|
3639
3664
|
title: "Delete Policy Group",
|
|
3640
3665
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
3641
|
-
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.",
|
|
3642
3667
|
inputSchema: z2.object({
|
|
3643
3668
|
groupId: z2.string().uuid().describe("Policy group ID")
|
|
3644
3669
|
})
|
|
@@ -4065,13 +4090,13 @@ function registerFactTools(server, callApi) {
|
|
|
4065
4090
|
{
|
|
4066
4091
|
title: "Update Fact Definition",
|
|
4067
4092
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4068
|
-
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.",
|
|
4069
4094
|
inputSchema: z5.object({
|
|
4070
4095
|
factId: z5.string().uuid().describe("Fact definition ID"),
|
|
4071
4096
|
name: z5.string().optional().describe("Display name"),
|
|
4072
4097
|
description: z5.string().optional().describe("Description"),
|
|
4073
4098
|
type: z5.enum(VALUE_TYPES).optional().describe(
|
|
4074
|
-
"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."
|
|
4075
4100
|
),
|
|
4076
4101
|
isRequired: z5.boolean().optional().describe("Required flag"),
|
|
4077
4102
|
isPii: z5.boolean().optional().describe("PII flag \u2014 enables/disables masking (changeable even on system facts)"),
|
|
@@ -4093,7 +4118,7 @@ function registerFactTools(server, callApi) {
|
|
|
4093
4118
|
{
|
|
4094
4119
|
title: "Delete Fact Definition",
|
|
4095
4120
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4096
|
-
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.",
|
|
4097
4122
|
inputSchema: z5.object({
|
|
4098
4123
|
factId: z5.string().uuid().describe("Fact definition ID")
|
|
4099
4124
|
})
|
|
@@ -4405,7 +4430,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4405
4430
|
UPLOADED → source S3_BUCKET, with dataset.path (the path returned by lexq_dataset_upload)
|
|
4406
4431
|
MANUAL → source REQUEST_BODY, with dataset.manualData (array of fact records)
|
|
4407
4432
|
|
|
4408
|
-
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.
|
|
4409
4435
|
options.baselinePolicyVersionId: uuid (optional, for baseline comparison)
|
|
4410
4436
|
options.includeRuleStats: boolean
|
|
4411
4437
|
options.metricConfig: optional — omit for plain execution count. To aggregate a fact, pass
|
|
@@ -4885,7 +4911,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
4885
4911
|
{
|
|
4886
4912
|
title: "List Webhook Subscriptions",
|
|
4887
4913
|
annotations: { readOnlyHint: true },
|
|
4888
|
-
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.",
|
|
4889
4915
|
inputSchema: z13.object({
|
|
4890
4916
|
page: z13.number().int().min(0).default(0).describe("Page number"),
|
|
4891
4917
|
size: z13.number().int().min(1).max(100).default(20).describe("Page size")
|
|
@@ -4913,7 +4939,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
4913
4939
|
{
|
|
4914
4940
|
title: "Save Webhook Subscription",
|
|
4915
4941
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4916
|
-
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": "..."}).`,
|
|
4917
4943
|
inputSchema: z13.object({
|
|
4918
4944
|
id: z13.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
|
|
4919
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
|
})
|
|
@@ -634,13 +638,13 @@ function registerFactTools(server, callApi) {
|
|
|
634
638
|
{
|
|
635
639
|
title: "Update Fact Definition",
|
|
636
640
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
637
|
-
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.",
|
|
638
642
|
inputSchema: z5.object({
|
|
639
643
|
factId: z5.string().uuid().describe("Fact definition ID"),
|
|
640
644
|
name: z5.string().optional().describe("Display name"),
|
|
641
645
|
description: z5.string().optional().describe("Description"),
|
|
642
646
|
type: z5.enum(VALUE_TYPES).optional().describe(
|
|
643
|
-
"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."
|
|
644
648
|
),
|
|
645
649
|
isRequired: z5.boolean().optional().describe("Required flag"),
|
|
646
650
|
isPii: z5.boolean().optional().describe("PII flag \u2014 enables/disables masking (changeable even on system facts)"),
|
|
@@ -662,7 +666,7 @@ function registerFactTools(server, callApi) {
|
|
|
662
666
|
{
|
|
663
667
|
title: "Delete Fact Definition",
|
|
664
668
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
665
|
-
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.",
|
|
666
670
|
inputSchema: z5.object({
|
|
667
671
|
factId: z5.string().uuid().describe("Fact definition ID")
|
|
668
672
|
})
|
|
@@ -974,7 +978,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
974
978
|
UPLOADED → source S3_BUCKET, with dataset.path (the path returned by lexq_dataset_upload)
|
|
975
979
|
MANUAL → source REQUEST_BODY, with dataset.manualData (array of fact records)
|
|
976
980
|
|
|
977
|
-
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.
|
|
978
983
|
options.baselinePolicyVersionId: uuid (optional, for baseline comparison)
|
|
979
984
|
options.includeRuleStats: boolean
|
|
980
985
|
options.metricConfig: optional — omit for plain execution count. To aggregate a fact, pass
|
|
@@ -1454,7 +1459,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
1454
1459
|
{
|
|
1455
1460
|
title: "List Webhook Subscriptions",
|
|
1456
1461
|
annotations: { readOnlyHint: true },
|
|
1457
|
-
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.",
|
|
1458
1463
|
inputSchema: z13.object({
|
|
1459
1464
|
page: z13.number().int().min(0).default(0).describe("Page number"),
|
|
1460
1465
|
size: z13.number().int().min(1).max(100).default(20).describe("Page size")
|
|
@@ -1482,7 +1487,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
1482
1487
|
{
|
|
1483
1488
|
title: "Save Webhook Subscription",
|
|
1484
1489
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
1485
|
-
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": "..."}).`,
|
|
1486
1491
|
inputSchema: z13.object({
|
|
1487
1492
|
id: z13.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
|
|
1488
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.
|
|
@@ -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
|