@lexq/cli 1.0.0 → 1.0.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/dist/index.js
CHANGED
|
@@ -269,7 +269,7 @@ function registerAuthCommands(program) {
|
|
|
269
269
|
|
|
270
270
|
Example:
|
|
271
271
|
$ lexq auth whoami
|
|
272
|
-
{ "tenantId": "abc-123", "userId": "...", "role": "ADMIN", "apiKey": "lexq_us_****abcd" }
|
|
272
|
+
{ "tenantId": "abc-123", "userId": "...", "role": "ADMIN", "tenantTimezone": "Asia/Seoul", "apiKey": "lexq_us_****abcd" }
|
|
273
273
|
`
|
|
274
274
|
).action(async () => {
|
|
275
275
|
try {
|
|
@@ -310,9 +310,10 @@ function registerStatusCommand(program) {
|
|
|
310
310
|
|
|
311
311
|
Example:
|
|
312
312
|
$ lexq status
|
|
313
|
-
{ "status": "ok", "latencyMs": 142, "tenantId": "abc-123", "role": "ADMIN" }
|
|
313
|
+
{ "status": "ok", "latencyMs": 142, "tenantId": "abc-123", "role": "ADMIN", "tenantTimezone": "Asia/Seoul" }
|
|
314
314
|
|
|
315
315
|
Use this to verify your API key is valid and the LexQ API is reachable.
|
|
316
|
+
tenantTimezone is the zone every yyyy-MM-dd date option is read in.
|
|
316
317
|
`
|
|
317
318
|
).action(async () => {
|
|
318
319
|
const globalOpts = program.opts();
|
|
@@ -326,7 +327,8 @@ function registerStatusCommand(program) {
|
|
|
326
327
|
status: "ok",
|
|
327
328
|
latencyMs: Date.now() - startTime,
|
|
328
329
|
tenantId: info.tenantId,
|
|
329
|
-
role: info.role
|
|
330
|
+
role: info.role,
|
|
331
|
+
tenantTimezone: info.tenantTimezone
|
|
330
332
|
});
|
|
331
333
|
} catch (error) {
|
|
332
334
|
printError(error);
|
|
@@ -347,10 +349,32 @@ var SLOW_MULTIPLIER = 10;
|
|
|
347
349
|
var MIN_SAMPLES = 100;
|
|
348
350
|
var MIN_COHORT_SIZE = 3;
|
|
349
351
|
var PROFILE_DEFAULT_WINDOW_HOURS = 24;
|
|
352
|
+
var MAX_GROUPS_PER_TENANT = 20;
|
|
353
|
+
var MAX_DRAFT_VERSIONS_PER_GROUP = 20;
|
|
354
|
+
var MAX_RULES_PER_VERSION = 100;
|
|
355
|
+
var MAX_CONDITIONS_PER_RULE = 25;
|
|
356
|
+
var MAX_CONDITION_DEPTH = 10;
|
|
357
|
+
var MAX_SNAPSHOT_BYTES = 131072;
|
|
350
358
|
|
|
351
359
|
// src/commands/groups.ts
|
|
352
360
|
import "commander";
|
|
353
361
|
import dedent3 from "dedent";
|
|
362
|
+
|
|
363
|
+
// src/lib/dates.ts
|
|
364
|
+
var ORG_DAY = "yyyy-MM-dd, a whole calendar day in the organization time zone (tenantTimezone in whoami), not UTC";
|
|
365
|
+
var START_DAY = `Start date, ${ORG_DAY}. Inclusive.`;
|
|
366
|
+
var END_DAY = `End date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
367
|
+
var WINDOW_START_DAY = `Window start date, ${ORG_DAY}. Inclusive.`;
|
|
368
|
+
var WINDOW_END_DAY = `Window end date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
369
|
+
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.";
|
|
370
|
+
var STATS_DEFAULT_WINDOW_DAYS = 30;
|
|
371
|
+
var STATS_MAX_WINDOW_DAYS = 31;
|
|
372
|
+
var FAILURE_LOG_LOOKBACK_DAYS = 7;
|
|
373
|
+
function utcMinute(instant) {
|
|
374
|
+
return instant ? `${new Date(instant).toISOString().substring(0, 16)}Z` : "\u2013";
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/commands/groups.ts
|
|
354
378
|
function registerGroupCommands(program) {
|
|
355
379
|
const groups = program.command("groups").description("Manage policy groups").addHelpText(
|
|
356
380
|
"after",
|
|
@@ -390,7 +414,7 @@ function registerGroupCommands(program) {
|
|
|
390
414
|
g.status,
|
|
391
415
|
String(g.priority),
|
|
392
416
|
g.currentVersionName ?? "\u2013",
|
|
393
|
-
g.updatedAt
|
|
417
|
+
utcMinute(g.updatedAt)
|
|
394
418
|
]),
|
|
395
419
|
{ truncate: 24 }
|
|
396
420
|
);
|
|
@@ -708,7 +732,7 @@ function registerVersionCommands(program) {
|
|
|
708
732
|
v.status,
|
|
709
733
|
v.commitMessage ?? "\u2013",
|
|
710
734
|
v.snapshotHash ? v.snapshotHash.substring(0, 8) : "\u2013",
|
|
711
|
-
v.createdAt
|
|
735
|
+
utcMinute(v.createdAt)
|
|
712
736
|
]),
|
|
713
737
|
{ truncate: 24 }
|
|
714
738
|
);
|
|
@@ -741,7 +765,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
741
765
|
process.exit(1);
|
|
742
766
|
}
|
|
743
767
|
});
|
|
744
|
-
versions.command("create").description("Create a new draft version").requiredOption("--group-id <groupId>", "Policy group ID").option("--commit-message <message>", "Commit message").option("--effective-from <
|
|
768
|
+
versions.command("create").description("Create a new draft version").requiredOption("--group-id <groupId>", "Policy group ID").option("--commit-message <message>", "Commit message").option("--effective-from <instant>", `Effective start, ${INSTANT}`).option("--effective-to <instant>", `Effective end, ${INSTANT}`).option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
745
769
|
"after",
|
|
746
770
|
dedent4`
|
|
747
771
|
|
|
@@ -756,8 +780,8 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
756
780
|
|
|
757
781
|
Fields:
|
|
758
782
|
commitMessage string Version description (optional, max 255 chars)
|
|
759
|
-
effectiveFrom
|
|
760
|
-
effectiveTo
|
|
783
|
+
effectiveFrom instant Start of effective period (optional, with Z or an offset)
|
|
784
|
+
effectiveTo instant End of effective period (optional, auto-expires)
|
|
761
785
|
`
|
|
762
786
|
).action(async (opts) => {
|
|
763
787
|
try {
|
|
@@ -780,12 +804,16 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
780
804
|
process.exit(1);
|
|
781
805
|
}
|
|
782
806
|
});
|
|
783
|
-
versions.command("update").description("Update a draft version metadata").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--id <versionId>", "Policy version ID").option("--commit-message <message>", "Commit message").option("--effective-from <
|
|
807
|
+
versions.command("update").description("Update a draft version metadata").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--id <versionId>", "Policy version ID").option("--commit-message <message>", "Commit message").option("--effective-from <instant>", `Effective start, ${INSTANT}`).option("--effective-to <instant>", `Effective end, ${INSTANT}`).option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
784
808
|
"after",
|
|
785
809
|
dedent4`
|
|
786
810
|
|
|
787
811
|
Only DRAFT versions can be updated. Published (ACTIVE) versions are immutable.
|
|
788
812
|
|
|
813
|
+
An omitted --commit-message keeps its value, but --effective-from and --effective-to
|
|
814
|
+
are replaced together: one you omit is cleared. To keep the effective window, pass
|
|
815
|
+
both current values (read them with lexq versions get).
|
|
816
|
+
|
|
789
817
|
Example:
|
|
790
818
|
$ lexq versions update --group-id <gid> --id <vid> --commit-message "Updated rules"
|
|
791
819
|
`
|
|
@@ -1736,7 +1764,7 @@ function registerDeployCommands(program) {
|
|
|
1736
1764
|
process.exit(1);
|
|
1737
1765
|
}
|
|
1738
1766
|
});
|
|
1739
|
-
deploy.command("history").description("List deployment history").option("--group-id <groupId>", "Filter by policy group").option("--types <types>", "Filter by types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)").option("--start-date <date>",
|
|
1767
|
+
deploy.command("history").description("List deployment history").option("--group-id <groupId>", "Filter by policy group").option("--types <types>", "Filter by types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)").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(
|
|
1740
1768
|
"after",
|
|
1741
1769
|
dedent7`
|
|
1742
1770
|
|
|
@@ -1768,7 +1796,7 @@ function registerDeployCommands(program) {
|
|
|
1768
1796
|
d.policyGroupName,
|
|
1769
1797
|
d.versionNo != null ? `v${d.versionNo}` : "\u2013",
|
|
1770
1798
|
d.deployedByName,
|
|
1771
|
-
d.deployedAt
|
|
1799
|
+
utcMinute(d.deployedAt)
|
|
1772
1800
|
]),
|
|
1773
1801
|
{ truncate: 20 }
|
|
1774
1802
|
);
|
|
@@ -1830,7 +1858,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
1830
1858
|
d.groupName,
|
|
1831
1859
|
d.groupStatus,
|
|
1832
1860
|
d.currentVersionName ?? "\u2013",
|
|
1833
|
-
d.lastDeployedAt
|
|
1861
|
+
utcMinute(d.lastDeployedAt)
|
|
1834
1862
|
])
|
|
1835
1863
|
);
|
|
1836
1864
|
} else {
|
|
@@ -1897,6 +1925,8 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
1897
1925
|
|
|
1898
1926
|
The version must be ACTIVE with a future effective start date; the system
|
|
1899
1927
|
deploys it automatically at that time (within one scheduler tick, ≤60s).
|
|
1928
|
+
That time is the version's effectiveFrom, set on the DRAFT before publishing;
|
|
1929
|
+
this command takes no time of its own.
|
|
1900
1930
|
One pending schedule per group. Manual deploy/rollback/undeploy, starting
|
|
1901
1931
|
an A/B test, or archiving the group cancels the pending schedule.
|
|
1902
1932
|
|
|
@@ -1988,9 +2018,9 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
1988
2018
|
s.status,
|
|
1989
2019
|
s.policyGroupName ?? s.policyGroupId.substring(0, 8),
|
|
1990
2020
|
s.versionNo != null ? `v${s.versionNo}` : "\u2013",
|
|
1991
|
-
s.scheduledFor
|
|
2021
|
+
utcMinute(s.scheduledFor),
|
|
1992
2022
|
s.scheduledByName,
|
|
1993
|
-
s.status === "EXECUTED" ? s.executedAt
|
|
2023
|
+
s.status === "EXECUTED" ? utcMinute(s.executedAt) : s.status === "CANCELED" ? s.canceledReason ?? "\u2013" : s.status === "FAILED" ? s.failedReason ?? "\u2013" : "\u2013"
|
|
1994
2024
|
]),
|
|
1995
2025
|
{ truncate: 20 }
|
|
1996
2026
|
);
|
|
@@ -2193,6 +2223,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2193
2223
|
|
|
2194
2224
|
Dataset types: HISTORICAL, UPLOADED, MANUAL
|
|
2195
2225
|
Dataset sources: EXECUTION_LOGS, S3_BUCKET, REQUEST_BODY
|
|
2226
|
+
HISTORICAL from / to are yyyy-MM-dd, whole days in the organization time zone, both inclusive.
|
|
2196
2227
|
`
|
|
2197
2228
|
).action(async (opts) => {
|
|
2198
2229
|
try {
|
|
@@ -2309,7 +2340,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2309
2340
|
process.exit(1);
|
|
2310
2341
|
}
|
|
2311
2342
|
});
|
|
2312
|
-
sim.command("list").description("List simulation history").option("--status <status>", "Filter by status (PENDING, RUNNING, COMPLETED, FAILED, CANCELED)").option("--from <date>",
|
|
2343
|
+
sim.command("list").description("List simulation history").option("--status <status>", "Filter by status (PENDING, RUNNING, COMPLETED, FAILED, CANCELED)").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) => {
|
|
2313
2344
|
try {
|
|
2314
2345
|
const globalOpts = program.opts();
|
|
2315
2346
|
const format = globalOpts.format ?? "json";
|
|
@@ -2338,7 +2369,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2338
2369
|
s.status,
|
|
2339
2370
|
`${s.matchRate.toFixed(1)}%`,
|
|
2340
2371
|
String(s.totalRecords),
|
|
2341
|
-
s.createdAt
|
|
2372
|
+
utcMinute(s.createdAt)
|
|
2342
2373
|
]),
|
|
2343
2374
|
{ truncate: 20 }
|
|
2344
2375
|
);
|
|
@@ -2659,7 +2690,7 @@ function registerHistoryCommands(program) {
|
|
|
2659
2690
|
Statuses: SUCCESS, NO_MATCH, ERROR, TIMEOUT
|
|
2660
2691
|
`
|
|
2661
2692
|
);
|
|
2662
|
-
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 (SUCCESS, NO_MATCH, ERROR, TIMEOUT)").option("--start-date <date>",
|
|
2693
|
+
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 (SUCCESS, NO_MATCH, ERROR, TIMEOUT)").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(
|
|
2663
2694
|
"after",
|
|
2664
2695
|
dedent10`
|
|
2665
2696
|
|
|
@@ -2699,7 +2730,7 @@ function registerHistoryCommands(program) {
|
|
|
2699
2730
|
h.status,
|
|
2700
2731
|
h.isMatched ? "\u2713" : "\u2717",
|
|
2701
2732
|
`${h.latencyMs}ms`,
|
|
2702
|
-
h.createdAt
|
|
2733
|
+
utcMinute(h.createdAt)
|
|
2703
2734
|
]),
|
|
2704
2735
|
{ truncate: 20 }
|
|
2705
2736
|
);
|
|
@@ -2739,15 +2770,19 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2739
2770
|
process.exit(1);
|
|
2740
2771
|
}
|
|
2741
2772
|
});
|
|
2742
|
-
history.command("stats").description("Get execution statistics").option("--group-id <groupId>", "Filter by policy group").option("--start-date <date>",
|
|
2773
|
+
history.command("stats").description("Get execution statistics").option("--group-id <groupId>", "Filter by policy group").option("--start-date <date>", START_DAY).option("--end-date <date>", END_DAY).addHelpText(
|
|
2743
2774
|
"after",
|
|
2744
2775
|
dedent10`
|
|
2745
2776
|
|
|
2746
2777
|
Shows total executions, success/no-match/failure counts, success rate, and avg latency.
|
|
2747
2778
|
|
|
2779
|
+
Without dates the window is the last ${STATS_DEFAULT_WINDOW_DAYS} days through today, in the organization
|
|
2780
|
+
time zone. At most ${STATS_MAX_WINDOW_DAYS} days per call; a longer range is rejected with AN-039.
|
|
2781
|
+
--start-date alone runs through today.
|
|
2782
|
+
|
|
2748
2783
|
Example:
|
|
2749
2784
|
$ lexq history stats --format table
|
|
2750
|
-
$ lexq history stats --group-id <gid> --start-date 2026-04-01
|
|
2785
|
+
$ lexq history stats --group-id <gid> --start-date 2026-04-01 --end-date 2026-04-30
|
|
2751
2786
|
`
|
|
2752
2787
|
).action(async (opts) => {
|
|
2753
2788
|
try {
|
|
@@ -2834,7 +2869,7 @@ function registerReplayCommands(program) {
|
|
|
2834
2869
|
process.exit(1);
|
|
2835
2870
|
}
|
|
2836
2871
|
});
|
|
2837
|
-
replay.command("start").description("Submit a window replay job (blast radius)").requiredOption("--version-id <versionId>", "Candidate version to re-evaluate against").requiredOption("--from <date>",
|
|
2872
|
+
replay.command("start").description("Submit a window replay job (blast radius)").requiredOption("--version-id <versionId>", "Candidate version to re-evaluate against").requiredOption("--from <date>", WINDOW_START_DAY).requiredOption("--to <date>", WINDOW_END_DAY).option("--max-records <number>", `Sample cap (hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`).addHelpText(
|
|
2838
2873
|
"after",
|
|
2839
2874
|
dedent11`
|
|
2840
2875
|
|
|
@@ -2878,7 +2913,7 @@ function registerReplayCommands(program) {
|
|
|
2878
2913
|
});
|
|
2879
2914
|
if (format === "table") {
|
|
2880
2915
|
printTable(
|
|
2881
|
-
["Job", "Version", "Window", "Status", "Progress", "Changed", "At"],
|
|
2916
|
+
["Job", "Version", "Window (org time zone)", "Status", "Progress", "Changed", "At"],
|
|
2882
2917
|
data.content.map((j) => [
|
|
2883
2918
|
j.jobId.substring(0, 12),
|
|
2884
2919
|
j.candidateVersionName ?? "\u2013",
|
|
@@ -2886,7 +2921,7 @@ function registerReplayCommands(program) {
|
|
|
2886
2921
|
j.status,
|
|
2887
2922
|
`${j.progress}%`,
|
|
2888
2923
|
`${j.changedCount}${j.capped ? " (capped)" : ""}`,
|
|
2889
|
-
j.createdAt
|
|
2924
|
+
utcMinute(j.createdAt)
|
|
2890
2925
|
]),
|
|
2891
2926
|
{ truncate: 24 }
|
|
2892
2927
|
);
|
|
@@ -2992,7 +3027,7 @@ function registerProvenanceCommands(program) {
|
|
|
2992
3027
|
process.exit(1);
|
|
2993
3028
|
}
|
|
2994
3029
|
});
|
|
2995
|
-
provenance.command("reveal-audits").description("List PII reveal audits (who revealed what, when)").option("--trace-id <traceId>", "Filter by trace ID (exact)").option("--fact-key <factKey>", "Filter by fact key (partial, case-insensitive)").option("--revealed-by <operatorId>", "Filter by operator ID (exact)").option("--start-date <date>",
|
|
3030
|
+
provenance.command("reveal-audits").description("List PII reveal audits (who revealed what, when)").option("--trace-id <traceId>", "Filter by trace ID (exact)").option("--fact-key <factKey>", "Filter by fact key (partial, case-insensitive)").option("--revealed-by <operatorId>", "Filter by operator ID (exact)").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(
|
|
2996
3031
|
"after",
|
|
2997
3032
|
dedent12`
|
|
2998
3033
|
|
|
@@ -3026,7 +3061,7 @@ function registerProvenanceCommands(program) {
|
|
|
3026
3061
|
printTable(
|
|
3027
3062
|
["Revealed At", "By", "Fact Key", "Trace"],
|
|
3028
3063
|
data.content.map((a) => [
|
|
3029
|
-
a.revealedAt
|
|
3064
|
+
utcMinute(a.revealedAt),
|
|
3030
3065
|
a.revealedByName,
|
|
3031
3066
|
a.factKey,
|
|
3032
3067
|
a.traceId.substring(0, 12)
|
|
@@ -3064,10 +3099,13 @@ function registerLogCommands(program) {
|
|
|
3064
3099
|
Statuses: PENDING (needs attention), RESOLVED, IGNORED
|
|
3065
3100
|
`
|
|
3066
3101
|
);
|
|
3067
|
-
logs.command("list").description("List failure logs").option("--task-type <taskType>", `Filter by task type (${TaskType.join(", ")})`).option("--status <status>", "Filter by status (PENDING, RESOLVED, IGNORED)").option("--keyword <keyword>", "Search keyword").option("--start-date <date>",
|
|
3102
|
+
logs.command("list").description("List failure logs").option("--task-type <taskType>", `Filter by task type (${TaskType.join(", ")})`).option("--status <status>", "Filter by status (PENDING, RESOLVED, IGNORED)").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(
|
|
3068
3103
|
"after",
|
|
3069
3104
|
dedent13`
|
|
3070
3105
|
|
|
3106
|
+
Without dates the window is ${FAILURE_LOG_LOOKBACK_DAYS} days back through today, in the organization
|
|
3107
|
+
time zone. Pass --start-date to look further back.
|
|
3108
|
+
|
|
3071
3109
|
Examples:
|
|
3072
3110
|
$ lexq logs list --status PENDING --format table
|
|
3073
3111
|
$ lexq logs list --task-type PLATFORM_WEBHOOK
|
|
@@ -3098,7 +3136,7 @@ function registerLogCommands(program) {
|
|
|
3098
3136
|
l.taskType,
|
|
3099
3137
|
l.status,
|
|
3100
3138
|
l.errorMessage?.substring(0, 24) ?? "\u2013",
|
|
3101
|
-
l.createdAt
|
|
3139
|
+
utcMinute(l.createdAt)
|
|
3102
3140
|
]),
|
|
3103
3141
|
{ truncate: 24 }
|
|
3104
3142
|
);
|
|
@@ -3496,7 +3534,7 @@ function registerStatusTools(server, callApi) {
|
|
|
3496
3534
|
{
|
|
3497
3535
|
title: "Who Am I",
|
|
3498
3536
|
annotations: { readOnlyHint: true },
|
|
3499
|
-
description:
|
|
3537
|
+
description: 'Show current authentication info (tenant ID, user ID, role) and tenantTimezone, the organization time zone that every yyyy-MM-dd date filter uses. Work out "today" or "yesterday" in that zone, not in UTC.',
|
|
3500
3538
|
inputSchema: z.object({})
|
|
3501
3539
|
},
|
|
3502
3540
|
async () => callApi("GET", "whoami")
|
|
@@ -3533,7 +3571,7 @@ function registerGroupTools(server, callApi) {
|
|
|
3533
3571
|
{
|
|
3534
3572
|
title: "Create Policy Group",
|
|
3535
3573
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
3536
|
-
description:
|
|
3574
|
+
description: `Create a new policy group. Requires name. Priority is auto-assigned (appended last, tenant-wide); use lexq_groups_reorder to change order. Optionally set conflict resolution, activation group, and description. Policy groups that share an activationGroup form a cluster and must share the same activationMode / activationStrategy / executionLimit; executionLimit is how many of those groups run, not how many rules. A tenant can have at most ${MAX_GROUPS_PER_TENANT} groups that are not ARCHIVED; archive groups you no longer use to free a slot.`,
|
|
3537
3575
|
inputSchema: z2.object({
|
|
3538
3576
|
name: z2.string().describe("Group name (unique among non-ARCHIVED)"),
|
|
3539
3577
|
description: z2.string().optional().describe("Group description"),
|
|
@@ -3690,12 +3728,12 @@ function registerVersionTools(server, callApi) {
|
|
|
3690
3728
|
{
|
|
3691
3729
|
title: "Create Policy Version",
|
|
3692
3730
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
3693
|
-
description:
|
|
3731
|
+
description: `Create a new DRAFT version in a policy group. Optionally provide a commit message and effective date range. A group can hold at most ${MAX_DRAFT_VERSIONS_PER_GROUP} DRAFT versions at a time; published versions do not count. Publish or delete drafts to make room.`,
|
|
3694
3732
|
inputSchema: z3.object({
|
|
3695
3733
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
3696
3734
|
commitMessage: z3.string().optional().describe("Commit message describing this version"),
|
|
3697
|
-
effectiveFrom: z3.string().optional().describe(
|
|
3698
|
-
effectiveTo: z3.string().optional().describe(
|
|
3735
|
+
effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT}`),
|
|
3736
|
+
effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT}`)
|
|
3699
3737
|
})
|
|
3700
3738
|
},
|
|
3701
3739
|
async ({ groupId, ...body }) => callApi("POST", `policy-groups/${groupId}/versions`, { body })
|
|
@@ -3705,13 +3743,13 @@ function registerVersionTools(server, callApi) {
|
|
|
3705
3743
|
{
|
|
3706
3744
|
title: "Update Policy Version",
|
|
3707
3745
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
3708
|
-
description: "Update a DRAFT version. Only DRAFT versions can be modified.
|
|
3746
|
+
description: "Update a DRAFT version. Only DRAFT versions can be modified. An omitted commitMessage keeps its value, but effectiveFrom and effectiveTo are replaced together: one you omit is cleared. To keep the effective window, send both current values (read them with lexq_versions_get).",
|
|
3709
3747
|
inputSchema: z3.object({
|
|
3710
3748
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
3711
3749
|
versionId: z3.string().uuid().describe("Version ID"),
|
|
3712
3750
|
commitMessage: z3.string().optional().describe("New commit message"),
|
|
3713
|
-
effectiveFrom: z3.string().optional().describe(
|
|
3714
|
-
effectiveTo: z3.string().optional().describe(
|
|
3751
|
+
effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT} Omit it and the start is cleared.`),
|
|
3752
|
+
effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT} Omit it and the end is cleared.`)
|
|
3715
3753
|
})
|
|
3716
3754
|
},
|
|
3717
3755
|
async ({ groupId, versionId, ...body }) => callApi("PUT", `policy-groups/${groupId}/versions/${versionId}`, { body })
|
|
@@ -3734,7 +3772,7 @@ function registerVersionTools(server, callApi) {
|
|
|
3734
3772
|
{
|
|
3735
3773
|
title: "Clone Policy Version",
|
|
3736
3774
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
3737
|
-
description:
|
|
3775
|
+
description: `Clone an existing version to create a new DRAFT. Useful when the source version is already published. The new DRAFT counts toward the limit of ${MAX_DRAFT_VERSIONS_PER_GROUP} DRAFT versions per group, and a source with more than ${MAX_RULES_PER_VERSION} rules cannot be cloned.`,
|
|
3738
3776
|
inputSchema: z3.object({
|
|
3739
3777
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
3740
3778
|
versionId: z3.string().uuid().describe("Source version ID to clone")
|
|
@@ -3797,7 +3835,9 @@ function registerRuleTools(server, callApi) {
|
|
|
3797
3835
|
|
|
3798
3836
|
Condition: { type: "SINGLE", field, operator, value, valueType } or { type: "GROUP", operator: "AND"|"OR", children: [...] }
|
|
3799
3837
|
Value types: STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER
|
|
3800
|
-
|
|
3838
|
+
Limits: a condition tree may hold at most ${MAX_CONDITIONS_PER_RULE} conditions (SINGLE nodes; GROUP nodes do not count)
|
|
3839
|
+
and nest at most ${MAX_CONDITION_DEPTH} levels below the root. A version holds at most ${MAX_RULES_PER_VERSION} rules.
|
|
3840
|
+
|
|
3801
3841
|
Operators are constrained by the LEFT fact's type (from lexq_facts_list). Using one outside
|
|
3802
3842
|
its type is rejected by the server — check the fact type before choosing an operator.
|
|
3803
3843
|
- STRING fact: EQUALS, NOT_EQUALS, CONTAINS, IN, NOT_IN
|
|
@@ -3863,7 +3903,7 @@ function registerRuleTools(server, callApi) {
|
|
|
3863
3903
|
{
|
|
3864
3904
|
title: "Update Rule",
|
|
3865
3905
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
3866
|
-
description:
|
|
3906
|
+
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.`,
|
|
3867
3907
|
inputSchema: z4.object({
|
|
3868
3908
|
groupId: z4.string().uuid().describe("Policy group ID"),
|
|
3869
3909
|
versionId: z4.string().uuid().describe("Version ID"),
|
|
@@ -4085,7 +4125,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4085
4125
|
{
|
|
4086
4126
|
title: "Publish Version",
|
|
4087
4127
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
4088
|
-
description:
|
|
4128
|
+
description: `Publish a DRAFT version (DRAFT \u2192 ACTIVE). Locks the version from further edits. Must have at least one rule. Undefined facts referenced by rules do not block publishing (INV-4); call lexq_facts_unregistered first to review them. Publishing fails if the version's compiled rules exceed ${MAX_SNAPSHOT_BYTES / 1024} KB.`,
|
|
4089
4129
|
inputSchema: z6.object({
|
|
4090
4130
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4091
4131
|
versionId: z6.string().uuid().describe("Version ID to publish"),
|
|
@@ -4145,7 +4185,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4145
4185
|
{
|
|
4146
4186
|
title: "Schedule Deployment",
|
|
4147
4187
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
4148
|
-
description: "Schedule an ACTIVE version with a future effective start date to auto-deploy at that time (Scheduled Deployment). One pending schedule per group; manual deploy/rollback/undeploy, starting an A/B test, or archiving the group cancels it. The snapshot hash is sealed at scheduling and re-verified at execution (fail-closed).",
|
|
4188
|
+
description: "Schedule an ACTIVE version with a future effective start date to auto-deploy at that time (Scheduled Deployment). The time is the version's effectiveFrom, set on the DRAFT before publishing; this call takes no time of its own. One pending schedule per group; manual deploy/rollback/undeploy, starting an A/B test, or archiving the group cancels it. The snapshot hash is sealed at scheduling and re-verified at execution (fail-closed).",
|
|
4149
4189
|
inputSchema: z6.object({
|
|
4150
4190
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4151
4191
|
versionId: z6.string().uuid().describe("ACTIVE version ID with a future effective start date"),
|
|
@@ -4192,8 +4232,8 @@ function registerDeployTools(server, callApi) {
|
|
|
4192
4232
|
size: z6.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
4193
4233
|
groupId: z6.string().uuid().optional().describe("Filter by group ID"),
|
|
4194
4234
|
types: z6.string().optional().describe("Filter by deployment types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)"),
|
|
4195
|
-
startDate: z6.string().optional().describe(
|
|
4196
|
-
endDate: z6.string().optional().describe(
|
|
4235
|
+
startDate: z6.string().optional().describe(START_DAY),
|
|
4236
|
+
endDate: z6.string().optional().describe(END_DAY)
|
|
4197
4237
|
})
|
|
4198
4238
|
},
|
|
4199
4239
|
async ({ page, size, groupId, types, startDate, endDate }) => {
|
|
@@ -4337,7 +4377,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4337
4377
|
Start an Impact Simulation against historical, uploaded, or inline data.
|
|
4338
4378
|
|
|
4339
4379
|
dataset.type and dataset.source are BOTH required, and must be paired:
|
|
4340
|
-
HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd
|
|
4380
|
+
HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd, whole days
|
|
4381
|
+
in the organization time zone, both inclusive)
|
|
4341
4382
|
UPLOADED → source S3_BUCKET, with dataset.path (the path returned by lexq_dataset_upload)
|
|
4342
4383
|
MANUAL → source REQUEST_BODY, with dataset.manualData (array of fact records)
|
|
4343
4384
|
|
|
@@ -4392,8 +4433,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4392
4433
|
page: z7.number().int().min(0).default(0).describe("Page number"),
|
|
4393
4434
|
size: z7.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
4394
4435
|
status: z7.enum(["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELED"]).optional().describe("Filter by status"),
|
|
4395
|
-
from: z7.string().optional().describe(
|
|
4396
|
-
to: z7.string().optional().describe(
|
|
4436
|
+
from: z7.string().optional().describe(START_DAY),
|
|
4437
|
+
to: z7.string().optional().describe(END_DAY)
|
|
4397
4438
|
})
|
|
4398
4439
|
},
|
|
4399
4440
|
async ({ page, size, status, from, to }) => {
|
|
@@ -4571,8 +4612,8 @@ function registerReplayTools(server, callApi) {
|
|
|
4571
4612
|
description: "Submit an async job that replays a date window of past executions against a candidate version and measures the blast radius (how many decisions change). Billed per replayed record (REPLAY metric); VIEWER role cannot submit. Poll with lexq_replay_status.",
|
|
4572
4613
|
inputSchema: z9.object({
|
|
4573
4614
|
candidateVersionId: z9.string().uuid().describe("Version to re-evaluate against"),
|
|
4574
|
-
from: z9.string().describe(
|
|
4575
|
-
to: z9.string().describe(
|
|
4615
|
+
from: z9.string().describe(WINDOW_START_DAY),
|
|
4616
|
+
to: z9.string().describe(WINDOW_END_DAY),
|
|
4576
4617
|
maxRecords: z9.number().int().min(1).optional().describe(
|
|
4577
4618
|
`Sample cap (server default applies; hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`
|
|
4578
4619
|
)
|
|
@@ -4648,8 +4689,8 @@ function registerHistoryTools(server, callApi) {
|
|
|
4648
4689
|
groupId: z10.string().uuid().optional().describe("Filter by policy group"),
|
|
4649
4690
|
versionId: z10.string().uuid().optional().describe("Filter by version"),
|
|
4650
4691
|
status: z10.enum(["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"]).optional().describe("Filter by execution status"),
|
|
4651
|
-
startDate: z10.string().optional().describe(
|
|
4652
|
-
endDate: z10.string().optional().describe(
|
|
4692
|
+
startDate: z10.string().optional().describe(START_DAY),
|
|
4693
|
+
endDate: z10.string().optional().describe(END_DAY)
|
|
4653
4694
|
})
|
|
4654
4695
|
},
|
|
4655
4696
|
async ({ page, size, traceId, groupId, versionId, status, startDate, endDate }) => {
|
|
@@ -4680,11 +4721,11 @@ function registerHistoryTools(server, callApi) {
|
|
|
4680
4721
|
{
|
|
4681
4722
|
title: "Execution Statistics",
|
|
4682
4723
|
annotations: { readOnlyHint: true },
|
|
4683
|
-
description:
|
|
4724
|
+
description: `Get execution KPIs: total executions, success/failure counts, success rate, and average latency. Without dates the window is the last ${STATS_DEFAULT_WINDOW_DAYS} days through today, in the organization time zone. At most ${STATS_MAX_WINDOW_DAYS} days per call; a longer range is rejected with AN-039. startDate alone runs through today.`,
|
|
4684
4725
|
inputSchema: z10.object({
|
|
4685
4726
|
groupId: z10.string().uuid().optional().describe("Filter by policy group"),
|
|
4686
|
-
startDate: z10.string().optional().describe(
|
|
4687
|
-
endDate: z10.string().optional().describe(
|
|
4727
|
+
startDate: z10.string().optional().describe(START_DAY),
|
|
4728
|
+
endDate: z10.string().optional().describe(END_DAY)
|
|
4688
4729
|
})
|
|
4689
4730
|
},
|
|
4690
4731
|
async ({ groupId, startDate, endDate }) => {
|
|
@@ -4724,8 +4765,8 @@ function registerProvenanceTools(server, callApi) {
|
|
|
4724
4765
|
traceId: z11.string().optional().describe("Filter by trace ID (exact match)"),
|
|
4725
4766
|
revealedBy: z11.string().optional().describe("Filter by operator ID (exact match)"),
|
|
4726
4767
|
factKey: z11.string().optional().describe("Filter by fact key (partial match, case-insensitive)"),
|
|
4727
|
-
startDate: z11.string().optional().describe(
|
|
4728
|
-
endDate: z11.string().optional().describe(
|
|
4768
|
+
startDate: z11.string().optional().describe(START_DAY),
|
|
4769
|
+
endDate: z11.string().optional().describe(END_DAY)
|
|
4729
4770
|
})
|
|
4730
4771
|
},
|
|
4731
4772
|
async ({ page, size, traceId, revealedBy, factKey, startDate, endDate }) => {
|
|
@@ -4748,15 +4789,15 @@ function registerLogTools(server, callApi) {
|
|
|
4748
4789
|
{
|
|
4749
4790
|
title: "List Failure Logs",
|
|
4750
4791
|
annotations: { readOnlyHint: true },
|
|
4751
|
-
description:
|
|
4792
|
+
description: `List system failure logs from background tasks (platform event webhooks, scheduled deployments). Without dates the window is ${FAILURE_LOG_LOOKBACK_DAYS} days back through today, in the organization time zone; pass startDate to look further back.`,
|
|
4752
4793
|
inputSchema: z12.object({
|
|
4753
4794
|
page: z12.number().int().min(0).default(0).describe("Page number"),
|
|
4754
4795
|
size: z12.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
4755
4796
|
taskType: z12.enum(TaskType).optional().describe("Task type"),
|
|
4756
4797
|
status: z12.enum(FailureStatus).optional().describe("Log status"),
|
|
4757
4798
|
keyword: z12.string().optional().describe("Search in refId, refSubId, errorMessage"),
|
|
4758
|
-
startDate: z12.string().optional().describe(
|
|
4759
|
-
endDate: z12.string().optional().describe(
|
|
4799
|
+
startDate: z12.string().optional().describe(START_DAY),
|
|
4800
|
+
endDate: z12.string().optional().describe(END_DAY)
|
|
4760
4801
|
})
|
|
4761
4802
|
},
|
|
4762
4803
|
async ({ page, size, taskType, status, keyword, startDate, endDate }) => {
|
package/dist/mcp/register.js
CHANGED
|
@@ -6,7 +6,7 @@ function registerStatusTools(server, callApi) {
|
|
|
6
6
|
{
|
|
7
7
|
title: "Who Am I",
|
|
8
8
|
annotations: { readOnlyHint: true },
|
|
9
|
-
description:
|
|
9
|
+
description: 'Show current authentication info (tenant ID, user ID, role) and tenantTimezone, the organization time zone that every yyyy-MM-dd date filter uses. Work out "today" or "yesterday" in that zone, not in UTC.',
|
|
10
10
|
inputSchema: z.object({})
|
|
11
11
|
},
|
|
12
12
|
async () => callApi("GET", "whoami")
|
|
@@ -34,6 +34,23 @@ var PlatformEventType = [
|
|
|
34
34
|
];
|
|
35
35
|
var WebhookPayloadFormat = ["GENERIC", "SLACK"];
|
|
36
36
|
|
|
37
|
+
// src/types/constants.ts
|
|
38
|
+
var MAX_ROUNDING_SCALE = 34;
|
|
39
|
+
var REPLAY_WINDOW_MAX_RECORDS = 5e4;
|
|
40
|
+
var PROFILE_SAMPLE_PERMILLE = 10;
|
|
41
|
+
var LATENCY_WINDOW_MILLIS = 6e4;
|
|
42
|
+
var TAIL_MIN_OBS = 3;
|
|
43
|
+
var SLOW_MULTIPLIER = 10;
|
|
44
|
+
var MIN_SAMPLES = 100;
|
|
45
|
+
var MIN_COHORT_SIZE = 3;
|
|
46
|
+
var PROFILE_DEFAULT_WINDOW_HOURS = 24;
|
|
47
|
+
var MAX_GROUPS_PER_TENANT = 20;
|
|
48
|
+
var MAX_DRAFT_VERSIONS_PER_GROUP = 20;
|
|
49
|
+
var MAX_RULES_PER_VERSION = 100;
|
|
50
|
+
var MAX_CONDITIONS_PER_RULE = 25;
|
|
51
|
+
var MAX_CONDITION_DEPTH = 10;
|
|
52
|
+
var MAX_SNAPSHOT_BYTES = 131072;
|
|
53
|
+
|
|
37
54
|
// src/mcp/tools/groups.ts
|
|
38
55
|
function registerGroupTools(server, callApi) {
|
|
39
56
|
server.registerTool(
|
|
@@ -63,7 +80,7 @@ function registerGroupTools(server, callApi) {
|
|
|
63
80
|
{
|
|
64
81
|
title: "Create Policy Group",
|
|
65
82
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
66
|
-
description:
|
|
83
|
+
description: `Create a new policy group. Requires name. Priority is auto-assigned (appended last, tenant-wide); use lexq_groups_reorder to change order. Optionally set conflict resolution, activation group, and description. Policy groups that share an activationGroup form a cluster and must share the same activationMode / activationStrategy / executionLimit; executionLimit is how many of those groups run, not how many rules. A tenant can have at most ${MAX_GROUPS_PER_TENANT} groups that are not ARCHIVED; archive groups you no longer use to free a slot.`,
|
|
67
84
|
inputSchema: z2.object({
|
|
68
85
|
name: z2.string().describe("Group name (unique among non-ARCHIVED)"),
|
|
69
86
|
description: z2.string().optional().describe("Group description"),
|
|
@@ -242,6 +259,17 @@ function formatUnregisteredFactWarning(meta) {
|
|
|
242
259
|
].join("\n");
|
|
243
260
|
}
|
|
244
261
|
|
|
262
|
+
// src/lib/dates.ts
|
|
263
|
+
var ORG_DAY = "yyyy-MM-dd, a whole calendar day in the organization time zone (tenantTimezone in whoami), not UTC";
|
|
264
|
+
var START_DAY = `Start date, ${ORG_DAY}. Inclusive.`;
|
|
265
|
+
var END_DAY = `End date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
266
|
+
var WINDOW_START_DAY = `Window start date, ${ORG_DAY}. Inclusive.`;
|
|
267
|
+
var WINDOW_END_DAY = `Window end date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
268
|
+
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.";
|
|
269
|
+
var STATS_DEFAULT_WINDOW_DAYS = 30;
|
|
270
|
+
var STATS_MAX_WINDOW_DAYS = 31;
|
|
271
|
+
var FAILURE_LOG_LOOKBACK_DAYS = 7;
|
|
272
|
+
|
|
245
273
|
// src/mcp/tools/versions.ts
|
|
246
274
|
function registerVersionTools(server, callApi) {
|
|
247
275
|
server.registerTool(
|
|
@@ -278,12 +306,12 @@ function registerVersionTools(server, callApi) {
|
|
|
278
306
|
{
|
|
279
307
|
title: "Create Policy Version",
|
|
280
308
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
281
|
-
description:
|
|
309
|
+
description: `Create a new DRAFT version in a policy group. Optionally provide a commit message and effective date range. A group can hold at most ${MAX_DRAFT_VERSIONS_PER_GROUP} DRAFT versions at a time; published versions do not count. Publish or delete drafts to make room.`,
|
|
282
310
|
inputSchema: z3.object({
|
|
283
311
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
284
312
|
commitMessage: z3.string().optional().describe("Commit message describing this version"),
|
|
285
|
-
effectiveFrom: z3.string().optional().describe(
|
|
286
|
-
effectiveTo: z3.string().optional().describe(
|
|
313
|
+
effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT}`),
|
|
314
|
+
effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT}`)
|
|
287
315
|
})
|
|
288
316
|
},
|
|
289
317
|
async ({ groupId, ...body }) => callApi("POST", `policy-groups/${groupId}/versions`, { body })
|
|
@@ -293,13 +321,13 @@ function registerVersionTools(server, callApi) {
|
|
|
293
321
|
{
|
|
294
322
|
title: "Update Policy Version",
|
|
295
323
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
296
|
-
description: "Update a DRAFT version. Only DRAFT versions can be modified.
|
|
324
|
+
description: "Update a DRAFT version. Only DRAFT versions can be modified. An omitted commitMessage keeps its value, but effectiveFrom and effectiveTo are replaced together: one you omit is cleared. To keep the effective window, send both current values (read them with lexq_versions_get).",
|
|
297
325
|
inputSchema: z3.object({
|
|
298
326
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
299
327
|
versionId: z3.string().uuid().describe("Version ID"),
|
|
300
328
|
commitMessage: z3.string().optional().describe("New commit message"),
|
|
301
|
-
effectiveFrom: z3.string().optional().describe(
|
|
302
|
-
effectiveTo: z3.string().optional().describe(
|
|
329
|
+
effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT} Omit it and the start is cleared.`),
|
|
330
|
+
effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT} Omit it and the end is cleared.`)
|
|
303
331
|
})
|
|
304
332
|
},
|
|
305
333
|
async ({ groupId, versionId, ...body }) => callApi("PUT", `policy-groups/${groupId}/versions/${versionId}`, { body })
|
|
@@ -322,7 +350,7 @@ function registerVersionTools(server, callApi) {
|
|
|
322
350
|
{
|
|
323
351
|
title: "Clone Policy Version",
|
|
324
352
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
325
|
-
description:
|
|
353
|
+
description: `Clone an existing version to create a new DRAFT. Useful when the source version is already published. The new DRAFT counts toward the limit of ${MAX_DRAFT_VERSIONS_PER_GROUP} DRAFT versions per group, and a source with more than ${MAX_RULES_PER_VERSION} rules cannot be cloned.`,
|
|
326
354
|
inputSchema: z3.object({
|
|
327
355
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
328
356
|
versionId: z3.string().uuid().describe("Source version ID to clone")
|
|
@@ -336,17 +364,6 @@ function registerVersionTools(server, callApi) {
|
|
|
336
364
|
);
|
|
337
365
|
}
|
|
338
366
|
|
|
339
|
-
// src/types/constants.ts
|
|
340
|
-
var MAX_ROUNDING_SCALE = 34;
|
|
341
|
-
var REPLAY_WINDOW_MAX_RECORDS = 5e4;
|
|
342
|
-
var PROFILE_SAMPLE_PERMILLE = 10;
|
|
343
|
-
var LATENCY_WINDOW_MILLIS = 6e4;
|
|
344
|
-
var TAIL_MIN_OBS = 3;
|
|
345
|
-
var SLOW_MULTIPLIER = 10;
|
|
346
|
-
var MIN_SAMPLES = 100;
|
|
347
|
-
var MIN_COHORT_SIZE = 3;
|
|
348
|
-
var PROFILE_DEFAULT_WINDOW_HOURS = 24;
|
|
349
|
-
|
|
350
367
|
// src/mcp/tools/rules.ts
|
|
351
368
|
import { z as z4 } from "zod";
|
|
352
369
|
import dedent from "dedent";
|
|
@@ -396,7 +413,9 @@ function registerRuleTools(server, callApi) {
|
|
|
396
413
|
|
|
397
414
|
Condition: { type: "SINGLE", field, operator, value, valueType } or { type: "GROUP", operator: "AND"|"OR", children: [...] }
|
|
398
415
|
Value types: STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER
|
|
399
|
-
|
|
416
|
+
Limits: a condition tree may hold at most ${MAX_CONDITIONS_PER_RULE} conditions (SINGLE nodes; GROUP nodes do not count)
|
|
417
|
+
and nest at most ${MAX_CONDITION_DEPTH} levels below the root. A version holds at most ${MAX_RULES_PER_VERSION} rules.
|
|
418
|
+
|
|
400
419
|
Operators are constrained by the LEFT fact's type (from lexq_facts_list). Using one outside
|
|
401
420
|
its type is rejected by the server — check the fact type before choosing an operator.
|
|
402
421
|
- STRING fact: EQUALS, NOT_EQUALS, CONTAINS, IN, NOT_IN
|
|
@@ -462,7 +481,7 @@ function registerRuleTools(server, callApi) {
|
|
|
462
481
|
{
|
|
463
482
|
title: "Update Rule",
|
|
464
483
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
465
|
-
description:
|
|
484
|
+
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.`,
|
|
466
485
|
inputSchema: z4.object({
|
|
467
486
|
groupId: z4.string().uuid().describe("Policy group ID"),
|
|
468
487
|
versionId: z4.string().uuid().describe("Version ID"),
|
|
@@ -688,7 +707,7 @@ function registerDeployTools(server, callApi) {
|
|
|
688
707
|
{
|
|
689
708
|
title: "Publish Version",
|
|
690
709
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
691
|
-
description:
|
|
710
|
+
description: `Publish a DRAFT version (DRAFT \u2192 ACTIVE). Locks the version from further edits. Must have at least one rule. Undefined facts referenced by rules do not block publishing (INV-4); call lexq_facts_unregistered first to review them. Publishing fails if the version's compiled rules exceed ${MAX_SNAPSHOT_BYTES / 1024} KB.`,
|
|
692
711
|
inputSchema: z6.object({
|
|
693
712
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
694
713
|
versionId: z6.string().uuid().describe("Version ID to publish"),
|
|
@@ -748,7 +767,7 @@ function registerDeployTools(server, callApi) {
|
|
|
748
767
|
{
|
|
749
768
|
title: "Schedule Deployment",
|
|
750
769
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
751
|
-
description: "Schedule an ACTIVE version with a future effective start date to auto-deploy at that time (Scheduled Deployment). One pending schedule per group; manual deploy/rollback/undeploy, starting an A/B test, or archiving the group cancels it. The snapshot hash is sealed at scheduling and re-verified at execution (fail-closed).",
|
|
770
|
+
description: "Schedule an ACTIVE version with a future effective start date to auto-deploy at that time (Scheduled Deployment). The time is the version's effectiveFrom, set on the DRAFT before publishing; this call takes no time of its own. One pending schedule per group; manual deploy/rollback/undeploy, starting an A/B test, or archiving the group cancels it. The snapshot hash is sealed at scheduling and re-verified at execution (fail-closed).",
|
|
752
771
|
inputSchema: z6.object({
|
|
753
772
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
754
773
|
versionId: z6.string().uuid().describe("ACTIVE version ID with a future effective start date"),
|
|
@@ -795,8 +814,8 @@ function registerDeployTools(server, callApi) {
|
|
|
795
814
|
size: z6.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
796
815
|
groupId: z6.string().uuid().optional().describe("Filter by group ID"),
|
|
797
816
|
types: z6.string().optional().describe("Filter by deployment types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)"),
|
|
798
|
-
startDate: z6.string().optional().describe(
|
|
799
|
-
endDate: z6.string().optional().describe(
|
|
817
|
+
startDate: z6.string().optional().describe(START_DAY),
|
|
818
|
+
endDate: z6.string().optional().describe(END_DAY)
|
|
800
819
|
})
|
|
801
820
|
},
|
|
802
821
|
async ({ page, size, groupId, types, startDate, endDate }) => {
|
|
@@ -940,7 +959,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
940
959
|
Start an Impact Simulation against historical, uploaded, or inline data.
|
|
941
960
|
|
|
942
961
|
dataset.type and dataset.source are BOTH required, and must be paired:
|
|
943
|
-
HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd
|
|
962
|
+
HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd, whole days
|
|
963
|
+
in the organization time zone, both inclusive)
|
|
944
964
|
UPLOADED → source S3_BUCKET, with dataset.path (the path returned by lexq_dataset_upload)
|
|
945
965
|
MANUAL → source REQUEST_BODY, with dataset.manualData (array of fact records)
|
|
946
966
|
|
|
@@ -995,8 +1015,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
995
1015
|
page: z7.number().int().min(0).default(0).describe("Page number"),
|
|
996
1016
|
size: z7.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
997
1017
|
status: z7.enum(["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELED"]).optional().describe("Filter by status"),
|
|
998
|
-
from: z7.string().optional().describe(
|
|
999
|
-
to: z7.string().optional().describe(
|
|
1018
|
+
from: z7.string().optional().describe(START_DAY),
|
|
1019
|
+
to: z7.string().optional().describe(END_DAY)
|
|
1000
1020
|
})
|
|
1001
1021
|
},
|
|
1002
1022
|
async ({ page, size, status, from, to }) => {
|
|
@@ -1174,8 +1194,8 @@ function registerReplayTools(server, callApi) {
|
|
|
1174
1194
|
description: "Submit an async job that replays a date window of past executions against a candidate version and measures the blast radius (how many decisions change). Billed per replayed record (REPLAY metric); VIEWER role cannot submit. Poll with lexq_replay_status.",
|
|
1175
1195
|
inputSchema: z9.object({
|
|
1176
1196
|
candidateVersionId: z9.string().uuid().describe("Version to re-evaluate against"),
|
|
1177
|
-
from: z9.string().describe(
|
|
1178
|
-
to: z9.string().describe(
|
|
1197
|
+
from: z9.string().describe(WINDOW_START_DAY),
|
|
1198
|
+
to: z9.string().describe(WINDOW_END_DAY),
|
|
1179
1199
|
maxRecords: z9.number().int().min(1).optional().describe(
|
|
1180
1200
|
`Sample cap (server default applies; hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`
|
|
1181
1201
|
)
|
|
@@ -1251,8 +1271,8 @@ function registerHistoryTools(server, callApi) {
|
|
|
1251
1271
|
groupId: z10.string().uuid().optional().describe("Filter by policy group"),
|
|
1252
1272
|
versionId: z10.string().uuid().optional().describe("Filter by version"),
|
|
1253
1273
|
status: z10.enum(["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"]).optional().describe("Filter by execution status"),
|
|
1254
|
-
startDate: z10.string().optional().describe(
|
|
1255
|
-
endDate: z10.string().optional().describe(
|
|
1274
|
+
startDate: z10.string().optional().describe(START_DAY),
|
|
1275
|
+
endDate: z10.string().optional().describe(END_DAY)
|
|
1256
1276
|
})
|
|
1257
1277
|
},
|
|
1258
1278
|
async ({ page, size, traceId, groupId, versionId, status, startDate, endDate }) => {
|
|
@@ -1283,11 +1303,11 @@ function registerHistoryTools(server, callApi) {
|
|
|
1283
1303
|
{
|
|
1284
1304
|
title: "Execution Statistics",
|
|
1285
1305
|
annotations: { readOnlyHint: true },
|
|
1286
|
-
description:
|
|
1306
|
+
description: `Get execution KPIs: total executions, success/failure counts, success rate, and average latency. Without dates the window is the last ${STATS_DEFAULT_WINDOW_DAYS} days through today, in the organization time zone. At most ${STATS_MAX_WINDOW_DAYS} days per call; a longer range is rejected with AN-039. startDate alone runs through today.`,
|
|
1287
1307
|
inputSchema: z10.object({
|
|
1288
1308
|
groupId: z10.string().uuid().optional().describe("Filter by policy group"),
|
|
1289
|
-
startDate: z10.string().optional().describe(
|
|
1290
|
-
endDate: z10.string().optional().describe(
|
|
1309
|
+
startDate: z10.string().optional().describe(START_DAY),
|
|
1310
|
+
endDate: z10.string().optional().describe(END_DAY)
|
|
1291
1311
|
})
|
|
1292
1312
|
},
|
|
1293
1313
|
async ({ groupId, startDate, endDate }) => {
|
|
@@ -1327,8 +1347,8 @@ function registerProvenanceTools(server, callApi) {
|
|
|
1327
1347
|
traceId: z11.string().optional().describe("Filter by trace ID (exact match)"),
|
|
1328
1348
|
revealedBy: z11.string().optional().describe("Filter by operator ID (exact match)"),
|
|
1329
1349
|
factKey: z11.string().optional().describe("Filter by fact key (partial match, case-insensitive)"),
|
|
1330
|
-
startDate: z11.string().optional().describe(
|
|
1331
|
-
endDate: z11.string().optional().describe(
|
|
1350
|
+
startDate: z11.string().optional().describe(START_DAY),
|
|
1351
|
+
endDate: z11.string().optional().describe(END_DAY)
|
|
1332
1352
|
})
|
|
1333
1353
|
},
|
|
1334
1354
|
async ({ page, size, traceId, revealedBy, factKey, startDate, endDate }) => {
|
|
@@ -1351,15 +1371,15 @@ function registerLogTools(server, callApi) {
|
|
|
1351
1371
|
{
|
|
1352
1372
|
title: "List Failure Logs",
|
|
1353
1373
|
annotations: { readOnlyHint: true },
|
|
1354
|
-
description:
|
|
1374
|
+
description: `List system failure logs from background tasks (platform event webhooks, scheduled deployments). Without dates the window is ${FAILURE_LOG_LOOKBACK_DAYS} days back through today, in the organization time zone; pass startDate to look further back.`,
|
|
1355
1375
|
inputSchema: z12.object({
|
|
1356
1376
|
page: z12.number().int().min(0).default(0).describe("Page number"),
|
|
1357
1377
|
size: z12.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
1358
1378
|
taskType: z12.enum(TaskType).optional().describe("Task type"),
|
|
1359
1379
|
status: z12.enum(FailureStatus).optional().describe("Log status"),
|
|
1360
1380
|
keyword: z12.string().optional().describe("Search in refId, refSubId, errorMessage"),
|
|
1361
|
-
startDate: z12.string().optional().describe(
|
|
1362
|
-
endDate: z12.string().optional().describe(
|
|
1381
|
+
startDate: z12.string().optional().describe(START_DAY),
|
|
1382
|
+
endDate: z12.string().optional().describe(END_DAY)
|
|
1363
1383
|
})
|
|
1364
1384
|
},
|
|
1365
1385
|
async ({ page, size, taskType, status, keyword, startDate, endDate }) => {
|
package/package.json
CHANGED
|
@@ -23,8 +23,11 @@ Filter options:
|
|
|
23
23
|
| `--group-id <id>` | Filter by policy group | `--group-id <gid>` |
|
|
24
24
|
| `--version-id <id>` | Filter by version | `--version-id <vid>` |
|
|
25
25
|
| `--status <status>` | Filter by status | `--status SUCCESS` |
|
|
26
|
-
| `--start-date <date>` |
|
|
27
|
-
| `--end-date <date>` |
|
|
26
|
+
| `--start-date <date>` | First day, inclusive | `--start-date 2025-01-01` |
|
|
27
|
+
| `--end-date <date>` | Last day, inclusive | `--end-date 2025-01-31` |
|
|
28
|
+
|
|
29
|
+
Dates are `yyyy-MM-dd`, whole days in the organization time zone (`tenantTimezone` in
|
|
30
|
+
`lexq auth whoami`), not UTC.
|
|
28
31
|
|
|
29
32
|
### Execution Statuses
|
|
30
33
|
|
|
@@ -53,6 +56,10 @@ Returns full detail including `inputFacts`, `executionTraces`, and `decisionTrac
|
|
|
53
56
|
lexq history stats
|
|
54
57
|
```
|
|
55
58
|
|
|
59
|
+
Without dates the window is the last 30 days through today, in the organization time zone. At most
|
|
60
|
+
31 days per call; a longer range is rejected with `AN-039`. `--start-date` alone runs through
|
|
61
|
+
today.
|
|
62
|
+
|
|
56
63
|
Returns KPI summary:
|
|
57
64
|
|
|
58
65
|
```json
|
|
@@ -98,8 +105,12 @@ Filter options:
|
|
|
98
105
|
| `--task-type <type>` | Task type | `PLATFORM_WEBHOOK`, `SCHEDULED_DEPLOYMENT` |
|
|
99
106
|
| `--status <status>` | Log status | `PENDING`, `RESOLVED`, `IGNORED` |
|
|
100
107
|
| `--keyword <kw>` | Search in refId, refSubId, errorMessage | any string |
|
|
101
|
-
| `--start-date <date>` |
|
|
102
|
-
| `--end-date <date>` |
|
|
108
|
+
| `--start-date <date>` | First day, inclusive | `2025-01-01` |
|
|
109
|
+
| `--end-date <date>` | Last day, inclusive | `2025-01-31` |
|
|
110
|
+
|
|
111
|
+
Without dates the list covers 7 days back through today, in the organization time zone. A failure
|
|
112
|
+
older than that is left out even while it is still `PENDING`. Pass an earlier `--start-date` to
|
|
113
|
+
see it.
|
|
103
114
|
|
|
104
115
|
### Get Failure Log Detail
|
|
105
116
|
|
|
@@ -247,7 +258,7 @@ lexq profile <groupId> --version <vid> --cache MISS \
|
|
|
247
258
|
```
|
|
248
259
|
|
|
249
260
|
`--from` / `--to` are ISO-8601 **instants** here (`2026-07-01T00:00:00Z`), unlike `replay start`
|
|
250
|
-
which takes `yyyy-MM-dd`
|
|
261
|
+
which takes `yyyy-MM-dd` days in the organization time zone.
|
|
251
262
|
|
|
252
263
|
`p50` / `p95` / `p99` are `null` when the sample is under 100 — the engine reports insufficient
|
|
253
264
|
data rather than inventing a number. `n` is always present.
|
|
@@ -264,7 +275,7 @@ lexq history list --status ERROR --page 0 --size 10
|
|
|
264
275
|
# 3. For a specific bad decision, get the deterministic why
|
|
265
276
|
lexq provenance get --trace-id <traceId>
|
|
266
277
|
|
|
267
|
-
# 4. Check for system failures
|
|
278
|
+
# 4. Check for system failures (covers 7 days back by default; add --start-date for older ones)
|
|
268
279
|
lexq logs list --status PENDING --page 0 --size 10
|
|
269
280
|
|
|
270
281
|
# 5. Inspect and mark
|
|
@@ -390,6 +390,10 @@ lexq logs bulk-action --ids "id4,id5" --action IGNORE
|
|
|
390
390
|
lexq logs list --status PENDING --page 0 --size 10
|
|
391
391
|
```
|
|
392
392
|
|
|
393
|
+
Without `--start-date`, `logs list` covers 7 days back through today, in the organization time
|
|
394
|
+
zone. An empty list in step 5 means there is no PENDING failure in that window, not that there is
|
|
395
|
+
none at all. Add an earlier `--start-date` to steps 1 and 5 to see older ones.
|
|
396
|
+
|
|
393
397
|
`RESOLVE` and `IGNORE` differ only in intent — both remove the log from PENDING. Use `RESOLVE`
|
|
394
398
|
when the underlying problem was fixed, `IGNORE` when it does not need fixing.
|
|
395
399
|
|
|
@@ -153,7 +153,12 @@ Prefixes you will encounter through the CLI:
|
|
|
153
153
|
1. **Fact keys start with a letter,** then letters, numbers, and underscores. Casing is yours to
|
|
154
154
|
choose and keys are case-sensitive. These docs use `camelCase`: `paymentAmount`, `customerTier`.
|
|
155
155
|
2. **IDs are UUIDs.** Always copy the full ID from list/create output — do not guess.
|
|
156
|
-
3. **Dates
|
|
156
|
+
3. **Dates come in two shapes.** A date filter (`--start-date`, `--end-date`, `--from` / `--to` on
|
|
157
|
+
`replay start` and `simulation list`) takes `yyyy-MM-dd` and means a whole calendar day in the
|
|
158
|
+
organization time zone, not UTC. `lexq auth whoami` shows that zone as `tenantTimezone`. An
|
|
159
|
+
instant (`--effective-from`, `--effective-to`, `--from` / `--to` on `profile`) takes ISO 8601
|
|
160
|
+
with `Z` or an offset: `2026-10-01T00:00:00Z` or `2026-10-01T09:00:00+09:00`. Tables print
|
|
161
|
+
times in UTC with a trailing `Z`.
|
|
157
162
|
4. **JSON bodies via `--json`.** Most create/update commands accept `--json '<body>'` for the request body.
|
|
158
163
|
5. **File input via `--file`.** `analytics dry-run`, `dry-run-compare`, `simulation start` accept
|
|
159
164
|
`--file path/to/body.json` as an alternative to `--json`. `analytics dataset upload` requires
|
|
@@ -251,6 +251,9 @@ lexq analytics simulation start --json '{
|
|
|
251
251
|
| `MANUAL` | `REQUEST_BODY` | Provide `manualData` array in the request |
|
|
252
252
|
| `UPLOADED` | `S3_BUCKET` | Reference an uploaded dataset by `path` |
|
|
253
253
|
|
|
254
|
+
`HISTORICAL` takes `from` / `to` as `yyyy-MM-dd`, whole days in the organization time zone, both
|
|
255
|
+
inclusive.
|
|
256
|
+
|
|
254
257
|
### File Upload Dataset
|
|
255
258
|
|
|
256
259
|
```bash
|