@lexq/cli 2.1.0 → 2.1.1
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/dist/index.js +134 -71
- package/dist/mcp/register.js +73 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -387,11 +387,57 @@ var MAX_RULES_PER_VERSION = 100;
|
|
|
387
387
|
var MAX_CONDITIONS_PER_RULE = 25;
|
|
388
388
|
var MAX_CONDITION_DEPTH = 10;
|
|
389
389
|
var MAX_SNAPSHOT_BYTES = 131072;
|
|
390
|
+
var STATS_DEFAULT_WINDOW_DAYS = 30;
|
|
391
|
+
var STATS_MAX_WINDOW_DAYS = 31;
|
|
392
|
+
var FAILURE_LOG_LOOKBACK_DAYS = 7;
|
|
393
|
+
var PURGE_AFTER_DAYS = 30;
|
|
394
|
+
var DESCRIPTION_MAX_LENGTH = 255;
|
|
395
|
+
var COMMIT_MESSAGE_MAX_LENGTH = 500;
|
|
396
|
+
var SCHEDULE_TICK_MAX_MILLIS = 6e4;
|
|
390
397
|
|
|
391
398
|
// src/commands/groups.ts
|
|
392
399
|
import "commander";
|
|
393
400
|
import dedent3 from "dedent";
|
|
394
401
|
|
|
402
|
+
// src/types/enums.ts
|
|
403
|
+
var PolicyGroupStatus = ["ACTIVE", "DISABLED", "ARCHIVED"];
|
|
404
|
+
var LogicalOperator = ["AND", "OR"];
|
|
405
|
+
var MutateOperator = ["ASSIGN", "ADD", "SUB", "MUL", "DIV"];
|
|
406
|
+
var CalculationMethod = ["PERCENTAGE", "AMOUNT"];
|
|
407
|
+
var RoundingPolicy = [
|
|
408
|
+
"UP",
|
|
409
|
+
"DOWN",
|
|
410
|
+
"CEILING",
|
|
411
|
+
"FLOOR",
|
|
412
|
+
"HALF_UP",
|
|
413
|
+
"HALF_DOWN",
|
|
414
|
+
"HALF_EVEN"
|
|
415
|
+
];
|
|
416
|
+
var ValueType = ["STRING", "NUMBER", "BOOLEAN", "LIST_STRING", "LIST_NUMBER"];
|
|
417
|
+
var ConflictResolutionMode = ["NONE", "EXCLUSIVE", "MAX_N"];
|
|
418
|
+
var ConflictResolutionStrategy = ["HIGHEST_PRIORITY"];
|
|
419
|
+
var DeploymentType = ["DEPLOY", "ROLLBACK", "UNDEPLOY"];
|
|
420
|
+
var ApiExecutionStatus = ["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"];
|
|
421
|
+
var ProfileCacheState = ["HIT", "MISS"];
|
|
422
|
+
var SimulationStatus = ["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELED"];
|
|
423
|
+
var SimulationDatasetType = ["HISTORICAL", "UPLOADED", "MANUAL"];
|
|
424
|
+
var SimulationDatasetSource = ["EXECUTION_LOGS", "S3_BUCKET", "REQUEST_BODY"];
|
|
425
|
+
var SimulationMetricType = ["COUNT", "SUM", "AVG"];
|
|
426
|
+
var ExportFormat = ["CSV", "JSON"];
|
|
427
|
+
var FailureStatus = ["PENDING", "RESOLVED", "IGNORED"];
|
|
428
|
+
var FailureAction = ["IGNORE", "RESOLVE"];
|
|
429
|
+
var TaskType = ["PLATFORM_WEBHOOK", "SCHEDULED_DEPLOYMENT"];
|
|
430
|
+
var PlatformEventType = [
|
|
431
|
+
"VERSION_PUBLISHED",
|
|
432
|
+
"DEPLOYED",
|
|
433
|
+
"ROLLED_BACK",
|
|
434
|
+
"UNDEPLOYED",
|
|
435
|
+
"DEPLOY_SCHEDULED",
|
|
436
|
+
"DEPLOY_SCHEDULE_CANCELED"
|
|
437
|
+
];
|
|
438
|
+
var WebhookPayloadFormat = ["GENERIC", "SLACK"];
|
|
439
|
+
var DeploymentScheduleStatus = ["PENDING", "EXECUTED", "CANCELED", "FAILED"];
|
|
440
|
+
|
|
395
441
|
// src/lib/dates.ts
|
|
396
442
|
var ORG_DAY = "yyyy-MM-dd, a whole calendar day in the organization time zone (tenantTimezone in whoami), not UTC";
|
|
397
443
|
var START_DAY = `Start date, ${ORG_DAY}. Inclusive.`;
|
|
@@ -399,9 +445,6 @@ var END_DAY = `End date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
|
399
445
|
var WINDOW_START_DAY = `Window start date, ${ORG_DAY}. Inclusive.`;
|
|
400
446
|
var WINDOW_END_DAY = `Window end date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
401
447
|
var INSTANT = "ISO-8601 instant with Z or an offset, e.g. 2026-10-01T00:00:00Z or 2026-10-01T09:00:00+09:00. A bare date or a time without an offset is rejected.";
|
|
402
|
-
var STATS_DEFAULT_WINDOW_DAYS = 30;
|
|
403
|
-
var STATS_MAX_WINDOW_DAYS = 31;
|
|
404
|
-
var FAILURE_LOG_LOOKBACK_DAYS = 7;
|
|
405
448
|
function utcMinute(instant) {
|
|
406
449
|
return instant ? `${new Date(instant).toISOString().substring(0, 16)}Z` : "\u2013";
|
|
407
450
|
}
|
|
@@ -424,7 +467,7 @@ function registerGroupCommands(program) {
|
|
|
424
467
|
delete Archive a group
|
|
425
468
|
ab-test Manage A/B tests (start, stop, adjust)
|
|
426
469
|
|
|
427
|
-
Statuses:
|
|
470
|
+
Statuses: ${PolicyGroupStatus.join(", ")}
|
|
428
471
|
DISABLED stops production execution (403 P-009) and keeps the deployed version and any A/B test.
|
|
429
472
|
Dry run, simulation, and replay still work. Set ACTIVE to resume.
|
|
430
473
|
ARCHIVED means deleted. Only 'lexq groups delete' sets it, and it cannot be undone.
|
|
@@ -490,9 +533,9 @@ ${data.length} total`);
|
|
|
490
533
|
|
|
491
534
|
Fields:
|
|
492
535
|
name string Group name (required, unique per tenant)
|
|
493
|
-
description string Description (optional, max
|
|
536
|
+
description string Description (optional, max ${DESCRIPTION_MAX_LENGTH} chars)
|
|
494
537
|
activationGroup string Execution Group — conflict-resolution cluster key (optional)
|
|
495
|
-
activationMode string
|
|
538
|
+
activationMode string ${ConflictResolutionMode.join(" | ")} [default: NONE]
|
|
496
539
|
activationStrategy string HIGHEST_PRIORITY [default, and currently the only value]
|
|
497
540
|
executionLimit number Max rules to fire in MAX_N mode (optional)
|
|
498
541
|
status string ACTIVE | DISABLED [default: ACTIVE]
|
|
@@ -554,7 +597,7 @@ ${data.length} total`);
|
|
|
554
597
|
It cannot be restored, and calls that change it (settings, versions, rules, starting an A/B
|
|
555
598
|
test, publish, deploy, rollback, scheduling a deployment) return 409 P-059. If it was ever
|
|
556
599
|
executed, deployed, or replayed, it is kept for audit; otherwise it is permanently deleted
|
|
557
|
-
after
|
|
600
|
+
after ${PURGE_AFTER_DAYS} days.
|
|
558
601
|
|
|
559
602
|
Use --force to skip the confirmation prompt.
|
|
560
603
|
`
|
|
@@ -826,7 +869,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
826
869
|
}'
|
|
827
870
|
|
|
828
871
|
Fields:
|
|
829
|
-
commitMessage string Version description (optional, max
|
|
872
|
+
commitMessage string Version description (optional, max ${COMMIT_MESSAGE_MAX_LENGTH} chars)
|
|
830
873
|
effectiveFrom instant Start of effective period (optional, with Z or an offset)
|
|
831
874
|
effectiveTo instant End of effective period (optional, auto-expires)
|
|
832
875
|
`
|
|
@@ -1091,11 +1134,11 @@ ${data.length} total`);
|
|
|
1091
1134
|
refVar is the percentage base and is optional — omit to use targetVar itself.
|
|
1092
1135
|
It is only valid in PERCENTAGE × {ASSIGN, ADD, SUB}.
|
|
1093
1136
|
|
|
1094
|
-
Value Types:
|
|
1137
|
+
Value Types: ${ValueType.join(", ")}
|
|
1095
1138
|
|
|
1096
1139
|
Mutex (optional — rule-level conflict resolution):
|
|
1097
1140
|
mutexGroup string Logical grouping key (e.g., "best-discount")
|
|
1098
|
-
mutexMode string
|
|
1141
|
+
mutexMode string ${ConflictResolutionMode.join(" | ")} [default: NONE]
|
|
1099
1142
|
mutexStrategy string HIGHEST_PRIORITY (currently the only value)
|
|
1100
1143
|
mutexLimit number Max rules to fire in MAX_N mode (required when MAX_N)
|
|
1101
1144
|
|
|
@@ -1304,27 +1347,6 @@ function escapeUnsafe(json) {
|
|
|
1304
1347
|
|
|
1305
1348
|
// src/lib/export.ts
|
|
1306
1349
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
1307
|
-
|
|
1308
|
-
// src/types/enums.ts
|
|
1309
|
-
var ValueType = ["STRING", "NUMBER", "BOOLEAN", "LIST_STRING", "LIST_NUMBER"];
|
|
1310
|
-
var ConflictResolutionMode = ["NONE", "EXCLUSIVE", "MAX_N"];
|
|
1311
|
-
var ConflictResolutionStrategy = ["HIGHEST_PRIORITY"];
|
|
1312
|
-
var ProfileCacheState = ["HIT", "MISS"];
|
|
1313
|
-
var ExportFormat = ["CSV", "JSON"];
|
|
1314
|
-
var FailureStatus = ["PENDING", "RESOLVED", "IGNORED"];
|
|
1315
|
-
var FailureAction = ["IGNORE", "RESOLVE"];
|
|
1316
|
-
var TaskType = ["PLATFORM_WEBHOOK", "SCHEDULED_DEPLOYMENT"];
|
|
1317
|
-
var PlatformEventType = [
|
|
1318
|
-
"VERSION_PUBLISHED",
|
|
1319
|
-
"DEPLOYED",
|
|
1320
|
-
"ROLLED_BACK",
|
|
1321
|
-
"UNDEPLOYED",
|
|
1322
|
-
"DEPLOY_SCHEDULED",
|
|
1323
|
-
"DEPLOY_SCHEDULE_CANCELED"
|
|
1324
|
-
];
|
|
1325
|
-
var WebhookPayloadFormat = ["GENERIC", "SLACK"];
|
|
1326
|
-
|
|
1327
|
-
// src/lib/export.ts
|
|
1328
1350
|
var EXPORT_FORMATS = ExportFormat.map((name) => name.toLowerCase()).sort();
|
|
1329
1351
|
function parseFormat(value) {
|
|
1330
1352
|
if (typeof value === "string" && EXPORT_FORMATS.includes(value)) {
|
|
@@ -1459,7 +1481,7 @@ ${data.length} unregistered`);
|
|
|
1459
1481
|
process.exit(1);
|
|
1460
1482
|
}
|
|
1461
1483
|
});
|
|
1462
|
-
facts.command("create").description("Create a new fact definition").option("--key <key>", "Fact key (letters, numbers, underscores)").option("--name <n>", "Display name").option("--type <type>",
|
|
1484
|
+
facts.command("create").description("Create a new fact definition").option("--key <key>", "Fact key (letters, numbers, underscores)").option("--name <n>", "Display name").option("--type <type>", `Value type: ${ValueType.join(", ")}`).option("--description <desc>", "Description").option("--required", "Mark as required", false).option("--pii", "Mark as PII \u2014 value is masked on every read surface", false).option("--allowed-values <csv>", "Comma-separated list of accepted values").option("--min <number>", "Lower bound, inclusive (numeric types only)").option("--max <number>", "Upper bound, inclusive (numeric types only)").option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
1463
1485
|
"after",
|
|
1464
1486
|
dedent6`
|
|
1465
1487
|
|
|
@@ -1479,7 +1501,7 @@ ${data.length} unregistered`);
|
|
|
1479
1501
|
$ lexq facts create --key tier --name "Tier" --type STRING \
|
|
1480
1502
|
--allowed-values "GOLD, SILVER, BRONZE"
|
|
1481
1503
|
|
|
1482
|
-
Value Types:
|
|
1504
|
+
Value Types: ${ValueType.join(", ")}
|
|
1483
1505
|
Key Format: starts with a letter, then letters, numbers, and underscores.
|
|
1484
1506
|
Casing is yours to choose (e.g., paymentAmount, payment_amount)
|
|
1485
1507
|
|
|
@@ -1508,7 +1530,7 @@ ${data.length} unregistered`);
|
|
|
1508
1530
|
process.exit(1);
|
|
1509
1531
|
}
|
|
1510
1532
|
});
|
|
1511
|
-
facts.command("update").description("Update a fact definition").requiredOption("--id <factId>", "Fact definition ID").option("--name <n>", "Display name").option("--description <desc>", 'Description (pass "" to remove it)').option("--required", "Mark as required").option("--no-required", "Mark as not required").option("--pii", "Mark as PII (enables masking)").option("--no-pii", "Unmark as PII (disables masking \u2014 value becomes visible)").option("--type <type>",
|
|
1533
|
+
facts.command("update").description("Update a fact definition").requiredOption("--id <factId>", "Fact definition ID").option("--name <n>", "Display name").option("--description <desc>", 'Description (pass "" to remove it)').option("--required", "Mark as required").option("--no-required", "Mark as not required").option("--pii", "Mark as PII (enables masking)").option("--no-pii", "Unmark as PII (disables masking \u2014 value becomes visible)").option("--type <type>", `Value type: ${ValueType.join(", ")}`).option("--allowed-values <csv>", "Comma-separated list of accepted values").option("--min <number>", "Lower bound, inclusive (numeric types only)").option("--max <number>", "Upper bound, inclusive (numeric types only)").option("--clear-value-domain", "Remove the value domain entirely").option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
1512
1534
|
"after",
|
|
1513
1535
|
dedent6`
|
|
1514
1536
|
|
|
@@ -1892,7 +1914,7 @@ function registerDeployCommands(program) {
|
|
|
1892
1914
|
process.exit(1);
|
|
1893
1915
|
}
|
|
1894
1916
|
});
|
|
1895
|
-
deploy.command("history").description("List deployment history").option("--group-id <groupId>", "Filter by policy group").option("--types <types>",
|
|
1917
|
+
deploy.command("history").description("List deployment history").option("--group-id <groupId>", "Filter by policy group").option("--types <types>", `Filter by types (comma-separated: ${DeploymentType.join(",")})`).option("--start-date <date>", START_DAY).option("--end-date <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
|
|
1896
1918
|
"after",
|
|
1897
1919
|
dedent7`
|
|
1898
1920
|
|
|
@@ -2052,7 +2074,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2052
2074
|
dedent7`
|
|
2053
2075
|
|
|
2054
2076
|
The version must be ACTIVE with a future effective start date; the system
|
|
2055
|
-
deploys it automatically at that time (within one scheduler tick,
|
|
2077
|
+
deploys it automatically at that time (within one scheduler tick, ≤${SCHEDULE_TICK_MAX_MILLIS / 1e3}s).
|
|
2056
2078
|
That time is the version's effectiveFrom, set on the DRAFT before publishing;
|
|
2057
2079
|
this command takes no time of its own.
|
|
2058
2080
|
One pending schedule per group. Manual deploy/rollback/undeploy, starting
|
|
@@ -2377,8 +2399,8 @@ function registerAnalyticsCommands(program) {
|
|
|
2377
2399
|
|
|
2378
2400
|
$ lexq analytics simulation start --file simulation.json
|
|
2379
2401
|
|
|
2380
|
-
Dataset types:
|
|
2381
|
-
Dataset sources:
|
|
2402
|
+
Dataset types: ${SimulationDatasetType.join(", ")}
|
|
2403
|
+
Dataset sources: ${SimulationDatasetSource.join(", ")}
|
|
2382
2404
|
HISTORICAL from / to are yyyy-MM-dd, whole days in the organization time zone, both inclusive.
|
|
2383
2405
|
`
|
|
2384
2406
|
).action(async (opts) => {
|
|
@@ -2499,7 +2521,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2499
2521
|
process.exit(1);
|
|
2500
2522
|
}
|
|
2501
2523
|
});
|
|
2502
|
-
sim.command("list").description("List simulation history").option("--status <status>",
|
|
2524
|
+
sim.command("list").description("List simulation history").option("--status <status>", `Filter by status (${SimulationStatus.join(", ")})`).option("--from <date>", START_DAY).option("--to <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
2503
2525
|
try {
|
|
2504
2526
|
const globalOpts = program.opts();
|
|
2505
2527
|
const format = globalOpts.format ?? "json";
|
|
@@ -2703,6 +2725,15 @@ function resolveBody(opts) {
|
|
|
2703
2725
|
// src/commands/profile.ts
|
|
2704
2726
|
import "commander";
|
|
2705
2727
|
import dedent9 from "dedent";
|
|
2728
|
+
|
|
2729
|
+
// src/lib/percentiles.ts
|
|
2730
|
+
var PERCENTILE_MIN_N = {
|
|
2731
|
+
p50: TAIL_MIN_OBS * 2,
|
|
2732
|
+
p95: TAIL_MIN_OBS * 20,
|
|
2733
|
+
p99: TAIL_MIN_OBS * 100
|
|
2734
|
+
};
|
|
2735
|
+
|
|
2736
|
+
// src/commands/profile.ts
|
|
2706
2737
|
var ms = (nanos) => nanos == null ? "\u2013" : (nanos / 1e6).toFixed(2);
|
|
2707
2738
|
var msWithUnit = (nanos) => nanos == null ? "\u2013" : `${(nanos / 1e6).toFixed(2)}ms`;
|
|
2708
2739
|
function registerProfileCommands(program) {
|
|
@@ -2712,15 +2743,18 @@ function registerProfileCommands(program) {
|
|
|
2712
2743
|
).option("--version-id <versionId>", "Version to inspect (default: live version)").option(
|
|
2713
2744
|
"--from <instant>",
|
|
2714
2745
|
`Window start, ISO-8601 instant (default: ${PROFILE_DEFAULT_WINDOW_HOURS}h ago)`
|
|
2715
|
-
).option("--to <instant>", "Window end, ISO-8601 instant (default: now)").option(
|
|
2746
|
+
).option("--to <instant>", "Window end, ISO-8601 instant (default: now)").option(
|
|
2747
|
+
"--cache <state>",
|
|
2748
|
+
`Cache dimension for the rule table: ${ProfileCacheState.join(" | ")} (default: HIT)`
|
|
2749
|
+
).addHelpText(
|
|
2716
2750
|
"after",
|
|
2717
2751
|
dedent9`
|
|
2718
2752
|
|
|
2719
2753
|
Slow-rule judgment is relative only: flagged = p50 ≥ ${SLOW_MULTIPLIER}× the median of
|
|
2720
2754
|
per-rule p50s within the group. Absolute ms thresholds are intentionally
|
|
2721
|
-
not supported. Each percentile is withheld (–) unless n×(1−q) ≥
|
|
2722
|
-
p50 from n ≥
|
|
2723
|
-
on every call, rule detail from a deterministic
|
|
2755
|
+
not supported. Each percentile is withheld (–) unless n×(1−q) ≥ ${TAIL_MIN_OBS} —
|
|
2756
|
+
p50 from n ≥ ${PERCENTILE_MIN_N.p50}, p95 from n ≥ ${PERCENTILE_MIN_N.p95}, p99 from n ≥ ${PERCENTILE_MIN_N.p99}. TOTAL is recorded
|
|
2757
|
+
on every call, rule detail from a deterministic ${PROFILE_SAMPLE_PERMILLE / 10}% sample.
|
|
2724
2758
|
|
|
2725
2759
|
Examples:
|
|
2726
2760
|
$ lexq profile <groupId>
|
|
@@ -2818,10 +2852,10 @@ function registerHistoryCommands(program) {
|
|
|
2818
2852
|
get Get full execution detail (request facts, traces, decisions)
|
|
2819
2853
|
stats Aggregate statistics (success rate, latency, counts)
|
|
2820
2854
|
|
|
2821
|
-
Statuses:
|
|
2855
|
+
Statuses: ${ApiExecutionStatus.join(", ")}
|
|
2822
2856
|
`
|
|
2823
2857
|
);
|
|
2824
|
-
history.command("list").description("List execution history").option("--trace-id <traceId>", "Filter by trace ID").option("--group-id <groupId>", "Filter by policy group").option("--version-id <versionId>", "Filter by version").option("--status <status>",
|
|
2858
|
+
history.command("list").description("List execution history").option("--trace-id <traceId>", "Filter by trace ID").option("--group-id <groupId>", "Filter by policy group").option("--version-id <versionId>", "Filter by version").option("--status <status>", `Filter by status (${ApiExecutionStatus.join(", ")})`).option("--start-date <date>", START_DAY).option("--end-date <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
|
|
2825
2859
|
"after",
|
|
2826
2860
|
dedent10`
|
|
2827
2861
|
|
|
@@ -3214,6 +3248,18 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
3214
3248
|
// src/commands/logs.ts
|
|
3215
3249
|
import "commander";
|
|
3216
3250
|
import dedent13 from "dedent";
|
|
3251
|
+
|
|
3252
|
+
// src/lib/enum-notes.ts
|
|
3253
|
+
function withNotes(notes) {
|
|
3254
|
+
return Object.entries(notes).map(([value, note]) => note ? `${value} (${note})` : value);
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
// src/commands/logs.ts
|
|
3258
|
+
var STATUS_NOTES = {
|
|
3259
|
+
PENDING: "needs attention",
|
|
3260
|
+
RESOLVED: "",
|
|
3261
|
+
IGNORED: ""
|
|
3262
|
+
};
|
|
3217
3263
|
function registerLogCommands(program) {
|
|
3218
3264
|
const logs = program.command("logs").description("Failure logs").addHelpText(
|
|
3219
3265
|
"after",
|
|
@@ -3224,13 +3270,13 @@ function registerLogCommands(program) {
|
|
|
3224
3270
|
Commands:
|
|
3225
3271
|
list List failure logs with filters
|
|
3226
3272
|
get Get failure log detail (includes the original payload)
|
|
3227
|
-
action Process a single log (
|
|
3273
|
+
action Process a single log (${FailureAction.join(", ")})
|
|
3228
3274
|
bulk-action Process multiple logs at once
|
|
3229
3275
|
|
|
3230
|
-
Statuses:
|
|
3276
|
+
Statuses: ${withNotes(STATUS_NOTES).join(", ")}
|
|
3231
3277
|
`
|
|
3232
3278
|
);
|
|
3233
|
-
logs.command("list").description("List failure logs").option("--task-type <taskType>", `Filter by task type (${TaskType.join(", ")})`).option("--status <status>",
|
|
3279
|
+
logs.command("list").description("List failure logs").option("--task-type <taskType>", `Filter by task type (${TaskType.join(", ")})`).option("--status <status>", `Filter by status (${FailureStatus.join(", ")})`).option("--keyword <keyword>", "Search keyword").option("--start-date <date>", START_DAY).option("--end-date <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
|
|
3234
3280
|
"after",
|
|
3235
3281
|
dedent13`
|
|
3236
3282
|
|
|
@@ -3286,7 +3332,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
3286
3332
|
dedent13`
|
|
3287
3333
|
|
|
3288
3334
|
Includes the full payload that was used for the failed operation.
|
|
3289
|
-
Use this to inspect what went wrong before deciding to
|
|
3335
|
+
Use this to inspect what went wrong before deciding to ${FailureAction.join(" or ")}.
|
|
3290
3336
|
`
|
|
3291
3337
|
).action(async (opts) => {
|
|
3292
3338
|
try {
|
|
@@ -3303,7 +3349,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
3303
3349
|
process.exit(1);
|
|
3304
3350
|
}
|
|
3305
3351
|
});
|
|
3306
|
-
logs.command("action").description(
|
|
3352
|
+
logs.command("action").description(`Process a failure log action (${FailureAction.join(", ")})`).requiredOption("--id <logId>", "Log ID").requiredOption("--action <action>", `Action: ${FailureAction.join(" or ")}`).addHelpText(
|
|
3307
3353
|
"after",
|
|
3308
3354
|
dedent13`
|
|
3309
3355
|
|
|
@@ -3334,7 +3380,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
3334
3380
|
process.exit(1);
|
|
3335
3381
|
}
|
|
3336
3382
|
});
|
|
3337
|
-
logs.command("bulk-action").description("Bulk process failure logs").requiredOption("--ids <logIds>", "Comma-separated log IDs").requiredOption("--action <action>",
|
|
3383
|
+
logs.command("bulk-action").description("Bulk process failure logs").requiredOption("--ids <logIds>", "Comma-separated log IDs").requiredOption("--action <action>", `Action: ${FailureAction.join(" or ")}`).addHelpText(
|
|
3338
3384
|
"after",
|
|
3339
3385
|
dedent13`
|
|
3340
3386
|
|
|
@@ -3365,6 +3411,10 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
3365
3411
|
// src/commands/webhook-subscriptions.ts
|
|
3366
3412
|
import "commander";
|
|
3367
3413
|
import dedent14 from "dedent";
|
|
3414
|
+
var FORMAT_NOTES = {
|
|
3415
|
+
GENERIC: "full JSON",
|
|
3416
|
+
SLACK: '{"text":"..."}'
|
|
3417
|
+
};
|
|
3368
3418
|
function registerWebhookSubscriptionCommands(program) {
|
|
3369
3419
|
const webhooks = program.command("webhook-subscriptions").description("Manage platform event webhook subscriptions").addHelpText(
|
|
3370
3420
|
"after",
|
|
@@ -3467,7 +3517,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
3467
3517
|
name string Subscription name (required, unique per tenant)
|
|
3468
3518
|
webhookUrl string Webhook endpoint URL (required)
|
|
3469
3519
|
subscribedEvents string[] ${PlatformEventType.join(", ")}
|
|
3470
|
-
payloadFormat string
|
|
3520
|
+
payloadFormat string ${withNotes(FORMAT_NOTES).join(" or ")} [default: GENERIC]
|
|
3471
3521
|
secret string HMAC-SHA256 signing secret (optional)
|
|
3472
3522
|
isActive boolean Enable/disable (optional) [default: true]
|
|
3473
3523
|
id string Provide to update, omit to create
|
|
@@ -3744,7 +3794,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
3744
3794
|
options.baselinePolicyVersionId: uuid (optional, for baseline comparison)
|
|
3745
3795
|
options.includeRuleStats: boolean
|
|
3746
3796
|
options.metricConfig: optional — omit for plain execution count. To aggregate a fact, pass
|
|
3747
|
-
{ "targetVariable": "<fact>", "aggregationType":
|
|
3797
|
+
{ "targetVariable": "<fact>", "aggregationType": ${SimulationMetricType.map((t) => `"${t}"`).join(" | ")} }
|
|
3748
3798
|
|
|
3749
3799
|
Example (uploaded dataset):
|
|
3750
3800
|
{
|
|
@@ -3790,7 +3840,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
3790
3840
|
inputSchema: z.object({
|
|
3791
3841
|
page: z.number().int().min(0).default(0).describe("Page number"),
|
|
3792
3842
|
size: z.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
3793
|
-
status: z.enum(
|
|
3843
|
+
status: z.enum(SimulationStatus).optional().describe("Filter by status"),
|
|
3794
3844
|
from: z.string().optional().describe(START_DAY),
|
|
3795
3845
|
to: z.string().optional().describe(END_DAY)
|
|
3796
3846
|
})
|
|
@@ -4067,7 +4117,7 @@ function registerGroupTools(server, callApi) {
|
|
|
4067
4117
|
groupId: z4.string().uuid().describe("Policy group ID"),
|
|
4068
4118
|
name: z4.string().optional().describe("New name"),
|
|
4069
4119
|
description: z4.string().optional().describe("New description"),
|
|
4070
|
-
status: z4.enum(["ACTIVE", "DISABLED"]).optional().describe(
|
|
4120
|
+
status: z4.enum(PolicyGroupStatus).extract(["ACTIVE", "DISABLED"]).optional().describe(
|
|
4071
4121
|
"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."
|
|
4072
4122
|
),
|
|
4073
4123
|
activationGroup: z4.string().optional().describe(
|
|
@@ -4087,7 +4137,7 @@ function registerGroupTools(server, callApi) {
|
|
|
4087
4137
|
{
|
|
4088
4138
|
title: "Delete Policy Group",
|
|
4089
4139
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4090
|
-
description:
|
|
4140
|
+
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 ${PURGE_AFTER_DAYS} days.`,
|
|
4091
4141
|
inputSchema: z4.object({
|
|
4092
4142
|
groupId: z4.string().uuid().describe("Policy group ID")
|
|
4093
4143
|
})
|
|
@@ -4121,7 +4171,9 @@ function registerGroupTools(server, callApi) {
|
|
|
4121
4171
|
inputSchema: z4.object({
|
|
4122
4172
|
groupId: z4.string().uuid().describe("Policy group ID"),
|
|
4123
4173
|
testVersionId: z4.string().uuid().describe("Challenger version ID. Must be a published version of this group"),
|
|
4124
|
-
trafficRate: z4.number().int().min(
|
|
4174
|
+
trafficRate: z4.number().int().min(MIN_TRAFFIC_RATE).max(MAX_TRAFFIC_RATE).describe(
|
|
4175
|
+
`Traffic percentage routed to challenger (${MIN_TRAFFIC_RATE}-${MAX_TRAFFIC_RATE})`
|
|
4176
|
+
)
|
|
4125
4177
|
})
|
|
4126
4178
|
},
|
|
4127
4179
|
async ({ groupId, ...body }) => callApi("POST", `policy-groups/${groupId}/ab-test`, { body })
|
|
@@ -4146,7 +4198,9 @@ function registerGroupTools(server, callApi) {
|
|
|
4146
4198
|
description: "Adjust traffic rate of a running A/B test.",
|
|
4147
4199
|
inputSchema: z4.object({
|
|
4148
4200
|
groupId: z4.string().uuid().describe("Policy group ID"),
|
|
4149
|
-
trafficRate: z4.number().int().min(
|
|
4201
|
+
trafficRate: z4.number().int().min(MIN_TRAFFIC_RATE).max(MAX_TRAFFIC_RATE).describe(
|
|
4202
|
+
`New traffic percentage for challenger (${MIN_TRAFFIC_RATE}-${MAX_TRAFFIC_RATE})`
|
|
4203
|
+
)
|
|
4150
4204
|
})
|
|
4151
4205
|
},
|
|
4152
4206
|
async ({ groupId, ...body }) => callApi("PATCH", `policy-groups/${groupId}/ab-test/traffic-rate`, { body })
|
|
@@ -4253,6 +4307,7 @@ function registerVersionTools(server, callApi) {
|
|
|
4253
4307
|
// src/mcp/tools/rules.ts
|
|
4254
4308
|
import { z as z6 } from "zod";
|
|
4255
4309
|
import dedent17 from "dedent";
|
|
4310
|
+
var oneOf = (values) => values.map((v) => `"${v}"`).join("|");
|
|
4256
4311
|
function registerRuleTools(server, callApi) {
|
|
4257
4312
|
server.registerTool(
|
|
4258
4313
|
"lexq_rules_list",
|
|
@@ -4302,8 +4357,8 @@ function registerRuleTools(server, callApi) {
|
|
|
4302
4357
|
After saving, lexq_facts_unregistered lists any keys this version references but has not
|
|
4303
4358
|
defined (non-blocking, version-wide) — use it to decide what to register.
|
|
4304
4359
|
|
|
4305
|
-
Condition: { type: "SINGLE", field, operator, value, valueType } or { type: "GROUP", operator:
|
|
4306
|
-
Value types:
|
|
4360
|
+
Condition: { type: "SINGLE", field, operator, value, valueType } or { type: "GROUP", operator: ${oneOf(LogicalOperator)}, children: [...] }
|
|
4361
|
+
Value types: ${ValueType.join(", ")}
|
|
4307
4362
|
Limits: a condition tree may hold at most ${MAX_CONDITIONS_PER_RULE} conditions (SINGLE nodes; GROUP nodes do not count)
|
|
4308
4363
|
and nest at most ${MAX_CONDITION_DEPTH} levels below the root. A version holds at most ${MAX_RULES_PER_VERSION} rules.
|
|
4309
4364
|
|
|
@@ -4326,7 +4381,7 @@ function registerRuleTools(server, callApi) {
|
|
|
4326
4381
|
Actions: [{ type, parameters }]
|
|
4327
4382
|
|
|
4328
4383
|
Action parameter schemas:
|
|
4329
|
-
- MUTATE_FACT: { targetVar: string, operator:
|
|
4384
|
+
- MUTATE_FACT: { targetVar: string, operator: ${oneOf(MutateOperator)}, method: ${oneOf(CalculationMethod)}, operand: number, refVar?: string, rounding?: RoundingOption }
|
|
4330
4385
|
targetVar is the fact this action reads and writes. It must be a registered fact
|
|
4331
4386
|
(ACT-032 otherwise), and the request must send it (P-015 otherwise). Its value must be
|
|
4332
4387
|
a number when the action runs.
|
|
@@ -4365,7 +4420,7 @@ function registerRuleTools(server, callApi) {
|
|
|
4365
4420
|
writes, send that value as an input fact in a second request. If a rule should run
|
|
4366
4421
|
whenever another one does, put its actions in that rule instead.
|
|
4367
4422
|
|
|
4368
|
-
RoundingOption (optional, MUTATE_FACT only): { scale: integer (0..${MAX_ROUNDING_SCALE}), mode?:
|
|
4423
|
+
RoundingOption (optional, MUTATE_FACT only): { scale: integer (0..${MAX_ROUNDING_SCALE}), mode?: ${oneOf(RoundingPolicy)} } mode defaults to HALF_UP. When omitted, calculator output is preserved at full precision (lossless).
|
|
4369
4424
|
`,
|
|
4370
4425
|
inputSchema: z6.object({
|
|
4371
4426
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
@@ -4496,7 +4551,7 @@ function registerFactTools(server, callApi) {
|
|
|
4496
4551
|
{
|
|
4497
4552
|
title: "Create Fact Definition",
|
|
4498
4553
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
4499
|
-
description:
|
|
4554
|
+
description: `Register a new input variable. Key starts with a letter, then letters, numbers, and underscores (e.g. paymentAmount). Casing is not enforced. Types: ${VALUE_TYPES.join(", ")}.`,
|
|
4500
4555
|
inputSchema: z7.object({
|
|
4501
4556
|
key: z7.string().regex(FACT_KEY_PATTERN).describe("Variable key. Any casing; must start with a letter."),
|
|
4502
4557
|
name: z7.string().describe("Display name"),
|
|
@@ -4702,7 +4757,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4702
4757
|
{
|
|
4703
4758
|
title: "List Scheduled Deployments",
|
|
4704
4759
|
annotations: { readOnlyHint: true },
|
|
4705
|
-
description:
|
|
4760
|
+
description: `List scheduled deployments across all groups (all statuses: ${DeploymentScheduleStatus.join(", ")}), newest first.`,
|
|
4706
4761
|
inputSchema: z8.object({
|
|
4707
4762
|
page: z8.number().int().min(0).default(0).describe("Page number"),
|
|
4708
4763
|
size: z8.number().int().min(1).max(100).default(20).describe("Page size")
|
|
@@ -4720,7 +4775,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4720
4775
|
page: z8.number().int().min(0).default(0).describe("Page number"),
|
|
4721
4776
|
size: z8.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
4722
4777
|
groupId: z8.string().uuid().optional().describe("Filter by group ID"),
|
|
4723
|
-
types: z8.string().optional().describe(
|
|
4778
|
+
types: z8.string().optional().describe(`Filter by deployment types (comma-separated: ${DeploymentType.join(",")})`),
|
|
4724
4779
|
startDate: z8.string().optional().describe(START_DAY),
|
|
4725
4780
|
endDate: z8.string().optional().describe(END_DAY)
|
|
4726
4781
|
})
|
|
@@ -4802,7 +4857,7 @@ function registerProfileTools(server, callApi) {
|
|
|
4802
4857
|
{
|
|
4803
4858
|
title: "Group Latency Profile",
|
|
4804
4859
|
annotations: { readOnlyHint: true },
|
|
4805
|
-
description: "Per-rule latency profile of a policy group over a time window: group TOTAL distribution split by cache state (HIT = compiled ruleset cache hit, MISS = deep-load + compile), a per-rule CONDITION/ACTION percentile table, and slow-rule flags. " + RELATIVE_THRESHOLD + ` Every percentile is accompanied by its sample count n; a percentile is withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 needs n \u2265
|
|
4860
|
+
description: "Per-rule latency profile of a policy group over a time window: group TOTAL distribution split by cache state (HIT = compiled ruleset cache hit, MISS = deep-load + compile), a per-rule CONDITION/ACTION percentile table, and slow-rule flags. " + RELATIVE_THRESHOLD + ` Every percentile is accompanied by its sample count n; a percentile is withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 needs n \u2265 ${PERCENTILE_MIN_N.p50}, p95 n \u2265 ${PERCENTILE_MIN_N.p95}, p99 n \u2265 ${PERCENTILE_MIN_N.p99} \u2014 display gate, separate from the n \u2265 ${MIN_SAMPLES} judgment gate). Baselines report INSUFFICIENT_COHORT when fewer than ${MIN_COHORT_SIZE} rules qualify. Rule detail comes from a deterministic ${PROFILE_SAMPLE_PERMILLE / 10}% sample of calls; TOTAL is recorded for every call. Defaults: last ${PROFILE_DEFAULT_WINDOW_HOURS}h, live version, cacheState HIT.`,
|
|
4806
4861
|
inputSchema: z9.object({
|
|
4807
4862
|
groupId: z9.string().uuid().describe("Policy group ID"),
|
|
4808
4863
|
versionId: z9.string().uuid().optional().describe("Version to inspect (default: live version)"),
|
|
@@ -4810,7 +4865,7 @@ function registerProfileTools(server, callApi) {
|
|
|
4810
4865
|
`Window start, ISO-8601 instant (e.g. 2026-07-01T00:00:00Z). Default: ${PROFILE_DEFAULT_WINDOW_HOURS}h ago`
|
|
4811
4866
|
),
|
|
4812
4867
|
to: z9.string().optional().describe("Window end, ISO-8601 instant. Default: now"),
|
|
4813
|
-
cacheState: z9.enum(
|
|
4868
|
+
cacheState: z9.enum(ProfileCacheState).optional().describe("Cache dimension for the rule table and judgment (default: HIT)")
|
|
4814
4869
|
})
|
|
4815
4870
|
},
|
|
4816
4871
|
async ({ groupId, versionId, from, to, cacheState }) => callApi("GET", `policy-groups/${groupId}/profile`, {
|
|
@@ -4822,7 +4877,7 @@ function registerProfileTools(server, callApi) {
|
|
|
4822
4877
|
{
|
|
4823
4878
|
title: "Rule Latency Detail",
|
|
4824
4879
|
annotations: { readOnlyHint: true },
|
|
4825
|
-
description: `Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (${LATENCY_WINDOW_MILLIS / 1e3}s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 n \u2265
|
|
4880
|
+
description: `Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (${LATENCY_WINDOW_MILLIS / 1e3}s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 n \u2265 ${PERCENTILE_MIN_N.p50}, p95 n \u2265 ${PERCENTILE_MIN_N.p95}, p99 n \u2265 ${PERCENTILE_MIN_N.p99}). ` + RELATIVE_THRESHOLD,
|
|
4826
4881
|
inputSchema: z9.object({
|
|
4827
4882
|
groupId: z9.string().uuid().describe("Policy group ID"),
|
|
4828
4883
|
ruleId: z9.string().uuid().describe("Rule ID (from lexq_profile_overview)"),
|
|
@@ -4939,7 +4994,7 @@ function registerHistoryTools(server, callApi) {
|
|
|
4939
4994
|
traceId: z11.string().optional().describe("Filter by trace ID"),
|
|
4940
4995
|
groupId: z11.string().uuid().optional().describe("Filter by policy group"),
|
|
4941
4996
|
versionId: z11.string().uuid().optional().describe("Filter by version"),
|
|
4942
|
-
status: z11.enum(
|
|
4997
|
+
status: z11.enum(ApiExecutionStatus).optional().describe("Filter by execution status"),
|
|
4943
4998
|
startDate: z11.string().optional().describe(START_DAY),
|
|
4944
4999
|
endDate: z11.string().optional().describe(END_DAY)
|
|
4945
5000
|
})
|
|
@@ -5034,6 +5089,10 @@ function registerProvenanceTools(server, callApi) {
|
|
|
5034
5089
|
|
|
5035
5090
|
// src/mcp/tools/logs.ts
|
|
5036
5091
|
import { z as z13 } from "zod";
|
|
5092
|
+
var ACTION_NOTES = {
|
|
5093
|
+
RESOLVE: "mark as manually fixed",
|
|
5094
|
+
IGNORE: "skip intentionally"
|
|
5095
|
+
};
|
|
5037
5096
|
function registerLogTools(server, callApi) {
|
|
5038
5097
|
server.registerTool(
|
|
5039
5098
|
"lexq_logs_list",
|
|
@@ -5078,7 +5137,7 @@ function registerLogTools(server, callApi) {
|
|
|
5078
5137
|
{
|
|
5079
5138
|
title: "Process Failure Log",
|
|
5080
5139
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
5081
|
-
description:
|
|
5140
|
+
description: `Process a single failure log: ${withNotes(ACTION_NOTES).join(" or ")}.`,
|
|
5082
5141
|
inputSchema: z13.object({
|
|
5083
5142
|
logId: z13.string().uuid().describe("Failure log ID"),
|
|
5084
5143
|
action: z13.enum(FailureAction).describe("Action to take")
|
|
@@ -5107,6 +5166,10 @@ function registerLogTools(server, callApi) {
|
|
|
5107
5166
|
|
|
5108
5167
|
// src/mcp/tools/webhook-subscriptions.ts
|
|
5109
5168
|
import { z as z14 } from "zod";
|
|
5169
|
+
var FORMAT_NOTES2 = {
|
|
5170
|
+
GENERIC: "full JSON",
|
|
5171
|
+
SLACK: '{"text": "..."}'
|
|
5172
|
+
};
|
|
5110
5173
|
function registerWebhookSubscriptionTools(server, callApi) {
|
|
5111
5174
|
server.registerTool(
|
|
5112
5175
|
"lexq_webhook_subscriptions_list",
|
|
@@ -5141,7 +5204,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
5141
5204
|
{
|
|
5142
5205
|
title: "Save Webhook Subscription",
|
|
5143
5206
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
5144
|
-
description: `Create or update a webhook subscription. Omit id to create, provide id to update. Events: ${PlatformEventType.join(", ")}. Formats:
|
|
5207
|
+
description: `Create or update a webhook subscription. Omit id to create, provide id to update. Events: ${PlatformEventType.join(", ")}. Formats: ${withNotes(FORMAT_NOTES2).join(", ")}.`,
|
|
5145
5208
|
inputSchema: z14.object({
|
|
5146
5209
|
id: z14.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
|
|
5147
5210
|
name: z14.string().min(1).describe("Subscription name (unique per tenant)"),
|
package/dist/mcp/register.js
CHANGED
|
@@ -31,9 +31,27 @@ function registerStatusTools(server, callApi) {
|
|
|
31
31
|
import { z as z2 } from "zod";
|
|
32
32
|
|
|
33
33
|
// src/types/enums.ts
|
|
34
|
+
var PolicyGroupStatus = ["ACTIVE", "DISABLED", "ARCHIVED"];
|
|
35
|
+
var LogicalOperator = ["AND", "OR"];
|
|
36
|
+
var MutateOperator = ["ASSIGN", "ADD", "SUB", "MUL", "DIV"];
|
|
37
|
+
var CalculationMethod = ["PERCENTAGE", "AMOUNT"];
|
|
38
|
+
var RoundingPolicy = [
|
|
39
|
+
"UP",
|
|
40
|
+
"DOWN",
|
|
41
|
+
"CEILING",
|
|
42
|
+
"FLOOR",
|
|
43
|
+
"HALF_UP",
|
|
44
|
+
"HALF_DOWN",
|
|
45
|
+
"HALF_EVEN"
|
|
46
|
+
];
|
|
34
47
|
var ValueType = ["STRING", "NUMBER", "BOOLEAN", "LIST_STRING", "LIST_NUMBER"];
|
|
35
48
|
var ConflictResolutionMode = ["NONE", "EXCLUSIVE", "MAX_N"];
|
|
36
49
|
var ConflictResolutionStrategy = ["HIGHEST_PRIORITY"];
|
|
50
|
+
var DeploymentType = ["DEPLOY", "ROLLBACK", "UNDEPLOY"];
|
|
51
|
+
var ApiExecutionStatus = ["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"];
|
|
52
|
+
var ProfileCacheState = ["HIT", "MISS"];
|
|
53
|
+
var SimulationStatus = ["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELED"];
|
|
54
|
+
var SimulationMetricType = ["COUNT", "SUM", "AVG"];
|
|
37
55
|
var ExportFormat = ["CSV", "JSON"];
|
|
38
56
|
var FailureStatus = ["PENDING", "RESOLVED", "IGNORED"];
|
|
39
57
|
var FailureAction = ["IGNORE", "RESOLVE"];
|
|
@@ -47,9 +65,12 @@ var PlatformEventType = [
|
|
|
47
65
|
"DEPLOY_SCHEDULE_CANCELED"
|
|
48
66
|
];
|
|
49
67
|
var WebhookPayloadFormat = ["GENERIC", "SLACK"];
|
|
68
|
+
var DeploymentScheduleStatus = ["PENDING", "EXECUTED", "CANCELED", "FAILED"];
|
|
50
69
|
|
|
51
70
|
// src/types/constants.ts
|
|
52
71
|
var MAX_ROUNDING_SCALE = 34;
|
|
72
|
+
var MIN_TRAFFIC_RATE = 1;
|
|
73
|
+
var MAX_TRAFFIC_RATE = 99;
|
|
53
74
|
var REPLAY_WINDOW_MAX_RECORDS = 5e4;
|
|
54
75
|
var DEFAULT_SIMULATION_RECORDS = 1e3;
|
|
55
76
|
var MAX_SIMULATION_RECORDS = 1e5;
|
|
@@ -66,6 +87,10 @@ var MAX_RULES_PER_VERSION = 100;
|
|
|
66
87
|
var MAX_CONDITIONS_PER_RULE = 25;
|
|
67
88
|
var MAX_CONDITION_DEPTH = 10;
|
|
68
89
|
var MAX_SNAPSHOT_BYTES = 131072;
|
|
90
|
+
var STATS_DEFAULT_WINDOW_DAYS = 30;
|
|
91
|
+
var STATS_MAX_WINDOW_DAYS = 31;
|
|
92
|
+
var FAILURE_LOG_LOOKBACK_DAYS = 7;
|
|
93
|
+
var PURGE_AFTER_DAYS = 30;
|
|
69
94
|
|
|
70
95
|
// src/mcp/tools/groups.ts
|
|
71
96
|
function registerGroupTools(server, callApi) {
|
|
@@ -132,7 +157,7 @@ function registerGroupTools(server, callApi) {
|
|
|
132
157
|
groupId: z2.string().uuid().describe("Policy group ID"),
|
|
133
158
|
name: z2.string().optional().describe("New name"),
|
|
134
159
|
description: z2.string().optional().describe("New description"),
|
|
135
|
-
status: z2.enum(["ACTIVE", "DISABLED"]).optional().describe(
|
|
160
|
+
status: z2.enum(PolicyGroupStatus).extract(["ACTIVE", "DISABLED"]).optional().describe(
|
|
136
161
|
"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."
|
|
137
162
|
),
|
|
138
163
|
activationGroup: z2.string().optional().describe(
|
|
@@ -152,7 +177,7 @@ function registerGroupTools(server, callApi) {
|
|
|
152
177
|
{
|
|
153
178
|
title: "Delete Policy Group",
|
|
154
179
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
155
|
-
description:
|
|
180
|
+
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 ${PURGE_AFTER_DAYS} days.`,
|
|
156
181
|
inputSchema: z2.object({
|
|
157
182
|
groupId: z2.string().uuid().describe("Policy group ID")
|
|
158
183
|
})
|
|
@@ -186,7 +211,9 @@ function registerGroupTools(server, callApi) {
|
|
|
186
211
|
inputSchema: z2.object({
|
|
187
212
|
groupId: z2.string().uuid().describe("Policy group ID"),
|
|
188
213
|
testVersionId: z2.string().uuid().describe("Challenger version ID. Must be a published version of this group"),
|
|
189
|
-
trafficRate: z2.number().int().min(
|
|
214
|
+
trafficRate: z2.number().int().min(MIN_TRAFFIC_RATE).max(MAX_TRAFFIC_RATE).describe(
|
|
215
|
+
`Traffic percentage routed to challenger (${MIN_TRAFFIC_RATE}-${MAX_TRAFFIC_RATE})`
|
|
216
|
+
)
|
|
190
217
|
})
|
|
191
218
|
},
|
|
192
219
|
async ({ groupId, ...body }) => callApi("POST", `policy-groups/${groupId}/ab-test`, { body })
|
|
@@ -211,7 +238,9 @@ function registerGroupTools(server, callApi) {
|
|
|
211
238
|
description: "Adjust traffic rate of a running A/B test.",
|
|
212
239
|
inputSchema: z2.object({
|
|
213
240
|
groupId: z2.string().uuid().describe("Policy group ID"),
|
|
214
|
-
trafficRate: z2.number().int().min(
|
|
241
|
+
trafficRate: z2.number().int().min(MIN_TRAFFIC_RATE).max(MAX_TRAFFIC_RATE).describe(
|
|
242
|
+
`New traffic percentage for challenger (${MIN_TRAFFIC_RATE}-${MAX_TRAFFIC_RATE})`
|
|
243
|
+
)
|
|
215
244
|
})
|
|
216
245
|
},
|
|
217
246
|
async ({ groupId, ...body }) => callApi("PATCH", `policy-groups/${groupId}/ab-test/traffic-rate`, { body })
|
|
@@ -324,9 +353,6 @@ var END_DAY = `End date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
|
324
353
|
var WINDOW_START_DAY = `Window start date, ${ORG_DAY}. Inclusive.`;
|
|
325
354
|
var WINDOW_END_DAY = `Window end date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
326
355
|
var INSTANT = "ISO-8601 instant with Z or an offset, e.g. 2026-10-01T00:00:00Z or 2026-10-01T09:00:00+09:00. A bare date or a time without an offset is rejected.";
|
|
327
|
-
var STATS_DEFAULT_WINDOW_DAYS = 30;
|
|
328
|
-
var STATS_MAX_WINDOW_DAYS = 31;
|
|
329
|
-
var FAILURE_LOG_LOOKBACK_DAYS = 7;
|
|
330
356
|
|
|
331
357
|
// src/mcp/tools/versions.ts
|
|
332
358
|
function registerVersionTools(server, callApi) {
|
|
@@ -427,6 +453,7 @@ function registerVersionTools(server, callApi) {
|
|
|
427
453
|
// src/mcp/tools/rules.ts
|
|
428
454
|
import { z as z4 } from "zod";
|
|
429
455
|
import dedent from "dedent";
|
|
456
|
+
var oneOf = (values) => values.map((v) => `"${v}"`).join("|");
|
|
430
457
|
function registerRuleTools(server, callApi) {
|
|
431
458
|
server.registerTool(
|
|
432
459
|
"lexq_rules_list",
|
|
@@ -476,8 +503,8 @@ function registerRuleTools(server, callApi) {
|
|
|
476
503
|
After saving, lexq_facts_unregistered lists any keys this version references but has not
|
|
477
504
|
defined (non-blocking, version-wide) — use it to decide what to register.
|
|
478
505
|
|
|
479
|
-
Condition: { type: "SINGLE", field, operator, value, valueType } or { type: "GROUP", operator:
|
|
480
|
-
Value types:
|
|
506
|
+
Condition: { type: "SINGLE", field, operator, value, valueType } or { type: "GROUP", operator: ${oneOf(LogicalOperator)}, children: [...] }
|
|
507
|
+
Value types: ${ValueType.join(", ")}
|
|
481
508
|
Limits: a condition tree may hold at most ${MAX_CONDITIONS_PER_RULE} conditions (SINGLE nodes; GROUP nodes do not count)
|
|
482
509
|
and nest at most ${MAX_CONDITION_DEPTH} levels below the root. A version holds at most ${MAX_RULES_PER_VERSION} rules.
|
|
483
510
|
|
|
@@ -500,7 +527,7 @@ function registerRuleTools(server, callApi) {
|
|
|
500
527
|
Actions: [{ type, parameters }]
|
|
501
528
|
|
|
502
529
|
Action parameter schemas:
|
|
503
|
-
- MUTATE_FACT: { targetVar: string, operator:
|
|
530
|
+
- MUTATE_FACT: { targetVar: string, operator: ${oneOf(MutateOperator)}, method: ${oneOf(CalculationMethod)}, operand: number, refVar?: string, rounding?: RoundingOption }
|
|
504
531
|
targetVar is the fact this action reads and writes. It must be a registered fact
|
|
505
532
|
(ACT-032 otherwise), and the request must send it (P-015 otherwise). Its value must be
|
|
506
533
|
a number when the action runs.
|
|
@@ -539,7 +566,7 @@ function registerRuleTools(server, callApi) {
|
|
|
539
566
|
writes, send that value as an input fact in a second request. If a rule should run
|
|
540
567
|
whenever another one does, put its actions in that rule instead.
|
|
541
568
|
|
|
542
|
-
RoundingOption (optional, MUTATE_FACT only): { scale: integer (0..${MAX_ROUNDING_SCALE}), mode?:
|
|
569
|
+
RoundingOption (optional, MUTATE_FACT only): { scale: integer (0..${MAX_ROUNDING_SCALE}), mode?: ${oneOf(RoundingPolicy)} } mode defaults to HALF_UP. When omitted, calculator output is preserved at full precision (lossless).
|
|
543
570
|
`,
|
|
544
571
|
inputSchema: z4.object({
|
|
545
572
|
groupId: z4.string().uuid().describe("Policy group ID"),
|
|
@@ -674,7 +701,7 @@ function registerFactTools(server, callApi) {
|
|
|
674
701
|
{
|
|
675
702
|
title: "Create Fact Definition",
|
|
676
703
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
677
|
-
description:
|
|
704
|
+
description: `Register a new input variable. Key starts with a letter, then letters, numbers, and underscores (e.g. paymentAmount). Casing is not enforced. Types: ${VALUE_TYPES.join(", ")}.`,
|
|
678
705
|
inputSchema: z5.object({
|
|
679
706
|
key: z5.string().regex(FACT_KEY_PATTERN).describe("Variable key. Any casing; must start with a letter."),
|
|
680
707
|
name: z5.string().describe("Display name"),
|
|
@@ -880,7 +907,7 @@ function registerDeployTools(server, callApi) {
|
|
|
880
907
|
{
|
|
881
908
|
title: "List Scheduled Deployments",
|
|
882
909
|
annotations: { readOnlyHint: true },
|
|
883
|
-
description:
|
|
910
|
+
description: `List scheduled deployments across all groups (all statuses: ${DeploymentScheduleStatus.join(", ")}), newest first.`,
|
|
884
911
|
inputSchema: z6.object({
|
|
885
912
|
page: z6.number().int().min(0).default(0).describe("Page number"),
|
|
886
913
|
size: z6.number().int().min(1).max(100).default(20).describe("Page size")
|
|
@@ -898,7 +925,7 @@ function registerDeployTools(server, callApi) {
|
|
|
898
925
|
page: z6.number().int().min(0).default(0).describe("Page number"),
|
|
899
926
|
size: z6.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
900
927
|
groupId: z6.string().uuid().optional().describe("Filter by group ID"),
|
|
901
|
-
types: z6.string().optional().describe(
|
|
928
|
+
types: z6.string().optional().describe(`Filter by deployment types (comma-separated: ${DeploymentType.join(",")})`),
|
|
902
929
|
startDate: z6.string().optional().describe(START_DAY),
|
|
903
930
|
endDate: z6.string().optional().describe(END_DAY)
|
|
904
931
|
})
|
|
@@ -1061,7 +1088,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1061
1088
|
options.baselinePolicyVersionId: uuid (optional, for baseline comparison)
|
|
1062
1089
|
options.includeRuleStats: boolean
|
|
1063
1090
|
options.metricConfig: optional — omit for plain execution count. To aggregate a fact, pass
|
|
1064
|
-
{ "targetVariable": "<fact>", "aggregationType":
|
|
1091
|
+
{ "targetVariable": "<fact>", "aggregationType": ${SimulationMetricType.map((t) => `"${t}"`).join(" | ")} }
|
|
1065
1092
|
|
|
1066
1093
|
Example (uploaded dataset):
|
|
1067
1094
|
{
|
|
@@ -1107,7 +1134,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1107
1134
|
inputSchema: z7.object({
|
|
1108
1135
|
page: z7.number().int().min(0).default(0).describe("Page number"),
|
|
1109
1136
|
size: z7.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
1110
|
-
status: z7.enum(
|
|
1137
|
+
status: z7.enum(SimulationStatus).optional().describe("Filter by status"),
|
|
1111
1138
|
from: z7.string().optional().describe(START_DAY),
|
|
1112
1139
|
to: z7.string().optional().describe(END_DAY)
|
|
1113
1140
|
})
|
|
@@ -1210,6 +1237,15 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1210
1237
|
|
|
1211
1238
|
// src/mcp/tools/profile.ts
|
|
1212
1239
|
import { z as z8 } from "zod";
|
|
1240
|
+
|
|
1241
|
+
// src/lib/percentiles.ts
|
|
1242
|
+
var PERCENTILE_MIN_N = {
|
|
1243
|
+
p50: TAIL_MIN_OBS * 2,
|
|
1244
|
+
p95: TAIL_MIN_OBS * 20,
|
|
1245
|
+
p99: TAIL_MIN_OBS * 100
|
|
1246
|
+
};
|
|
1247
|
+
|
|
1248
|
+
// src/mcp/tools/profile.ts
|
|
1213
1249
|
var RELATIVE_THRESHOLD = `flagged = p50 \u2265 ${SLOW_MULTIPLIER}\xD7 median of per-rule p50s within the group; absolute thresholds are intentionally not supported.`;
|
|
1214
1250
|
function profileParams(opts) {
|
|
1215
1251
|
const params = {};
|
|
@@ -1225,7 +1261,7 @@ function registerProfileTools(server, callApi) {
|
|
|
1225
1261
|
{
|
|
1226
1262
|
title: "Group Latency Profile",
|
|
1227
1263
|
annotations: { readOnlyHint: true },
|
|
1228
|
-
description: "Per-rule latency profile of a policy group over a time window: group TOTAL distribution split by cache state (HIT = compiled ruleset cache hit, MISS = deep-load + compile), a per-rule CONDITION/ACTION percentile table, and slow-rule flags. " + RELATIVE_THRESHOLD + ` Every percentile is accompanied by its sample count n; a percentile is withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 needs n \u2265
|
|
1264
|
+
description: "Per-rule latency profile of a policy group over a time window: group TOTAL distribution split by cache state (HIT = compiled ruleset cache hit, MISS = deep-load + compile), a per-rule CONDITION/ACTION percentile table, and slow-rule flags. " + RELATIVE_THRESHOLD + ` Every percentile is accompanied by its sample count n; a percentile is withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 needs n \u2265 ${PERCENTILE_MIN_N.p50}, p95 n \u2265 ${PERCENTILE_MIN_N.p95}, p99 n \u2265 ${PERCENTILE_MIN_N.p99} \u2014 display gate, separate from the n \u2265 ${MIN_SAMPLES} judgment gate). Baselines report INSUFFICIENT_COHORT when fewer than ${MIN_COHORT_SIZE} rules qualify. Rule detail comes from a deterministic ${PROFILE_SAMPLE_PERMILLE / 10}% sample of calls; TOTAL is recorded for every call. Defaults: last ${PROFILE_DEFAULT_WINDOW_HOURS}h, live version, cacheState HIT.`,
|
|
1229
1265
|
inputSchema: z8.object({
|
|
1230
1266
|
groupId: z8.string().uuid().describe("Policy group ID"),
|
|
1231
1267
|
versionId: z8.string().uuid().optional().describe("Version to inspect (default: live version)"),
|
|
@@ -1233,7 +1269,7 @@ function registerProfileTools(server, callApi) {
|
|
|
1233
1269
|
`Window start, ISO-8601 instant (e.g. 2026-07-01T00:00:00Z). Default: ${PROFILE_DEFAULT_WINDOW_HOURS}h ago`
|
|
1234
1270
|
),
|
|
1235
1271
|
to: z8.string().optional().describe("Window end, ISO-8601 instant. Default: now"),
|
|
1236
|
-
cacheState: z8.enum(
|
|
1272
|
+
cacheState: z8.enum(ProfileCacheState).optional().describe("Cache dimension for the rule table and judgment (default: HIT)")
|
|
1237
1273
|
})
|
|
1238
1274
|
},
|
|
1239
1275
|
async ({ groupId, versionId, from, to, cacheState }) => callApi("GET", `policy-groups/${groupId}/profile`, {
|
|
@@ -1245,7 +1281,7 @@ function registerProfileTools(server, callApi) {
|
|
|
1245
1281
|
{
|
|
1246
1282
|
title: "Rule Latency Detail",
|
|
1247
1283
|
annotations: { readOnlyHint: true },
|
|
1248
|
-
description: `Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (${LATENCY_WINDOW_MILLIS / 1e3}s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 n \u2265
|
|
1284
|
+
description: `Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (${LATENCY_WINDOW_MILLIS / 1e3}s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 n \u2265 ${PERCENTILE_MIN_N.p50}, p95 n \u2265 ${PERCENTILE_MIN_N.p95}, p99 n \u2265 ${PERCENTILE_MIN_N.p99}). ` + RELATIVE_THRESHOLD,
|
|
1249
1285
|
inputSchema: z8.object({
|
|
1250
1286
|
groupId: z8.string().uuid().describe("Policy group ID"),
|
|
1251
1287
|
ruleId: z8.string().uuid().describe("Rule ID (from lexq_profile_overview)"),
|
|
@@ -1362,7 +1398,7 @@ function registerHistoryTools(server, callApi) {
|
|
|
1362
1398
|
traceId: z10.string().optional().describe("Filter by trace ID"),
|
|
1363
1399
|
groupId: z10.string().uuid().optional().describe("Filter by policy group"),
|
|
1364
1400
|
versionId: z10.string().uuid().optional().describe("Filter by version"),
|
|
1365
|
-
status: z10.enum(
|
|
1401
|
+
status: z10.enum(ApiExecutionStatus).optional().describe("Filter by execution status"),
|
|
1366
1402
|
startDate: z10.string().optional().describe(START_DAY),
|
|
1367
1403
|
endDate: z10.string().optional().describe(END_DAY)
|
|
1368
1404
|
})
|
|
@@ -1457,6 +1493,17 @@ function registerProvenanceTools(server, callApi) {
|
|
|
1457
1493
|
|
|
1458
1494
|
// src/mcp/tools/logs.ts
|
|
1459
1495
|
import { z as z12 } from "zod";
|
|
1496
|
+
|
|
1497
|
+
// src/lib/enum-notes.ts
|
|
1498
|
+
function withNotes(notes) {
|
|
1499
|
+
return Object.entries(notes).map(([value, note]) => note ? `${value} (${note})` : value);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// src/mcp/tools/logs.ts
|
|
1503
|
+
var ACTION_NOTES = {
|
|
1504
|
+
RESOLVE: "mark as manually fixed",
|
|
1505
|
+
IGNORE: "skip intentionally"
|
|
1506
|
+
};
|
|
1460
1507
|
function registerLogTools(server, callApi) {
|
|
1461
1508
|
server.registerTool(
|
|
1462
1509
|
"lexq_logs_list",
|
|
@@ -1501,7 +1548,7 @@ function registerLogTools(server, callApi) {
|
|
|
1501
1548
|
{
|
|
1502
1549
|
title: "Process Failure Log",
|
|
1503
1550
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
1504
|
-
description:
|
|
1551
|
+
description: `Process a single failure log: ${withNotes(ACTION_NOTES).join(" or ")}.`,
|
|
1505
1552
|
inputSchema: z12.object({
|
|
1506
1553
|
logId: z12.string().uuid().describe("Failure log ID"),
|
|
1507
1554
|
action: z12.enum(FailureAction).describe("Action to take")
|
|
@@ -1530,6 +1577,10 @@ function registerLogTools(server, callApi) {
|
|
|
1530
1577
|
|
|
1531
1578
|
// src/mcp/tools/webhook-subscriptions.ts
|
|
1532
1579
|
import { z as z13 } from "zod";
|
|
1580
|
+
var FORMAT_NOTES = {
|
|
1581
|
+
GENERIC: "full JSON",
|
|
1582
|
+
SLACK: '{"text": "..."}'
|
|
1583
|
+
};
|
|
1533
1584
|
function registerWebhookSubscriptionTools(server, callApi) {
|
|
1534
1585
|
server.registerTool(
|
|
1535
1586
|
"lexq_webhook_subscriptions_list",
|
|
@@ -1564,7 +1615,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
1564
1615
|
{
|
|
1565
1616
|
title: "Save Webhook Subscription",
|
|
1566
1617
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
1567
|
-
description: `Create or update a webhook subscription. Omit id to create, provide id to update. Events: ${PlatformEventType.join(", ")}. Formats:
|
|
1618
|
+
description: `Create or update a webhook subscription. Omit id to create, provide id to update. Events: ${PlatformEventType.join(", ")}. Formats: ${withNotes(FORMAT_NOTES).join(", ")}.`,
|
|
1568
1619
|
inputSchema: z13.object({
|
|
1569
1620
|
id: z13.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
|
|
1570
1621
|
name: z13.string().min(1).describe("Subscription name (unique per tenant)"),
|