@lexq/cli 0.1.34 → 0.1.36
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 +491 -110
- package/dist/mcp/register.js +214 -73
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1144,14 +1144,15 @@ function registerFactCommands(program) {
|
|
|
1144
1144
|
});
|
|
1145
1145
|
if (format === "table") {
|
|
1146
1146
|
printTable(
|
|
1147
|
-
["ID", "Key", "Name", "Type", "System", "Required"],
|
|
1147
|
+
["ID", "Key", "Name", "Type", "System", "Required", "PII"],
|
|
1148
1148
|
data.content.map((f) => [
|
|
1149
1149
|
f.id,
|
|
1150
1150
|
f.key,
|
|
1151
1151
|
f.name,
|
|
1152
1152
|
f.type,
|
|
1153
1153
|
f.isSystem ? "\u2713" : "\u2013",
|
|
1154
|
-
f.isRequired ? "\u2713" : "\u2013"
|
|
1154
|
+
f.isRequired ? "\u2713" : "\u2013",
|
|
1155
|
+
f.isPii ? "\u2713" : "\u2013"
|
|
1155
1156
|
]),
|
|
1156
1157
|
{ truncate: 28 }
|
|
1157
1158
|
);
|
|
@@ -1211,7 +1212,7 @@ ${data.length} unregistered`);
|
|
|
1211
1212
|
process.exit(1);
|
|
1212
1213
|
}
|
|
1213
1214
|
});
|
|
1214
|
-
facts.command("create").description("Create a new fact definition").option("--key <key>", "Fact key (lowercase, underscores)").option("--name <n>", "Display name").option("--type <type>", "Value type: STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER").option("--description <desc>", "Description").option("--required", "Mark as required", false).option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
1215
|
+
facts.command("create").description("Create a new fact definition").option("--key <key>", "Fact key (lowercase, underscores)").option("--name <n>", "Display name").option("--type <type>", "Value type: STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER").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("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
1215
1216
|
"after",
|
|
1216
1217
|
dedent6`
|
|
1217
1218
|
|
|
@@ -1247,7 +1248,7 @@ ${data.length} unregistered`);
|
|
|
1247
1248
|
process.exit(1);
|
|
1248
1249
|
}
|
|
1249
1250
|
});
|
|
1250
|
-
facts.command("update").description("Update a fact definition").requiredOption("--id <factId>", "Fact definition ID").option("--name <n>", "Display name").option("--description <desc>", "Description").option("--required", "Mark as required").option("--no-required", "Mark as not required").option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
1251
|
+
facts.command("update").description("Update a fact definition").requiredOption("--id <factId>", "Fact definition ID").option("--name <n>", "Display name").option("--description <desc>", "Description").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("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
1251
1252
|
"after",
|
|
1252
1253
|
dedent6`
|
|
1253
1254
|
|
|
@@ -1334,7 +1335,8 @@ function buildCreateBody2(opts) {
|
|
|
1334
1335
|
key: opts.key,
|
|
1335
1336
|
name: opts.name,
|
|
1336
1337
|
type: opts.type,
|
|
1337
|
-
isRequired: opts.required === true
|
|
1338
|
+
isRequired: opts.required === true,
|
|
1339
|
+
isPii: opts.pii === true
|
|
1338
1340
|
};
|
|
1339
1341
|
if (opts.description) body.description = opts.description;
|
|
1340
1342
|
return body;
|
|
@@ -1344,6 +1346,7 @@ function buildUpdateBody2(opts) {
|
|
|
1344
1346
|
if (opts.name) body.name = opts.name;
|
|
1345
1347
|
if (opts.description !== void 0) body.description = opts.description;
|
|
1346
1348
|
if (typeof opts.required === "boolean") body.isRequired = opts.required;
|
|
1349
|
+
if (typeof opts.isPii === "boolean") body.isPii = opts.isPii;
|
|
1347
1350
|
return body;
|
|
1348
1351
|
}
|
|
1349
1352
|
|
|
@@ -2327,13 +2330,240 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2327
2330
|
});
|
|
2328
2331
|
}
|
|
2329
2332
|
|
|
2330
|
-
// src/commands/
|
|
2333
|
+
// src/commands/replay.ts
|
|
2331
2334
|
import "commander";
|
|
2332
2335
|
import dedent10 from "dedent";
|
|
2336
|
+
function registerReplayCommands(program) {
|
|
2337
|
+
const replay = program.command("replay").description("Decision Replay").addHelpText(
|
|
2338
|
+
"after",
|
|
2339
|
+
dedent10`
|
|
2340
|
+
|
|
2341
|
+
Re-evaluate past production executions against a candidate version.
|
|
2342
|
+
|
|
2343
|
+
Commands:
|
|
2344
|
+
decision Replay one execution and show the decision diff (sync, free)
|
|
2345
|
+
start Submit a window replay job — blast radius (async, billed)
|
|
2346
|
+
list List replay job history
|
|
2347
|
+
get Poll a job's status, summary, and changed samples
|
|
2348
|
+
cancel Cancel a PENDING/RUNNING job
|
|
2349
|
+
|
|
2350
|
+
External effects (webhooks, notifications) are always mocked during replay.
|
|
2351
|
+
`
|
|
2352
|
+
);
|
|
2353
|
+
replay.command("decision").description("Replay a single execution against a candidate version").requiredOption("--trace-id <traceId>", "Trace ID of the past execution").requiredOption("--version-id <versionId>", "Candidate version to re-evaluate against").addHelpText(
|
|
2354
|
+
"after",
|
|
2355
|
+
dedent10`
|
|
2356
|
+
|
|
2357
|
+
Free of charge (TPS throttle only). Returns decisionChanged, effect
|
|
2358
|
+
changes, fired rules on both sides, and a determinism verdict.
|
|
2359
|
+
|
|
2360
|
+
Example:
|
|
2361
|
+
$ lexq replay decision --trace-id <tid> --version-id <vid>
|
|
2362
|
+
`
|
|
2363
|
+
).action(async (opts) => {
|
|
2364
|
+
try {
|
|
2365
|
+
const globalOpts = program.opts();
|
|
2366
|
+
const data = await apiRequest("POST", "replay/decisions", {
|
|
2367
|
+
apiKey: globalOpts.apiKey,
|
|
2368
|
+
baseUrl: globalOpts.baseUrl,
|
|
2369
|
+
dryRun: globalOpts.dryRun,
|
|
2370
|
+
verbose: globalOpts.verbose,
|
|
2371
|
+
body: { traceId: opts.traceId, candidateVersionId: opts.versionId }
|
|
2372
|
+
});
|
|
2373
|
+
printJson(data);
|
|
2374
|
+
} catch (error) {
|
|
2375
|
+
printError(error);
|
|
2376
|
+
process.exit(1);
|
|
2377
|
+
}
|
|
2378
|
+
});
|
|
2379
|
+
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 date (yyyy-MM-dd)").requiredOption("--to <date>", "Window end date (yyyy-MM-dd)").option("--max-records <number>", "Sample cap (hard cap 50k)").addHelpText(
|
|
2380
|
+
"after",
|
|
2381
|
+
dedent10`
|
|
2382
|
+
|
|
2383
|
+
Billed per replayed record (REPLAY metric). Poll with "lexq replay get".
|
|
2384
|
+
|
|
2385
|
+
Example:
|
|
2386
|
+
$ lexq replay start --version-id <vid> --from 2026-06-01 --to 2026-06-30
|
|
2387
|
+
`
|
|
2388
|
+
).action(async (opts) => {
|
|
2389
|
+
try {
|
|
2390
|
+
const globalOpts = program.opts();
|
|
2391
|
+
const body = {
|
|
2392
|
+
candidateVersionId: opts.versionId,
|
|
2393
|
+
from: opts.from,
|
|
2394
|
+
to: opts.to
|
|
2395
|
+
};
|
|
2396
|
+
if (opts.maxRecords) body.maxRecords = Number(opts.maxRecords);
|
|
2397
|
+
const data = await apiRequest("POST", "replay/jobs", {
|
|
2398
|
+
apiKey: globalOpts.apiKey,
|
|
2399
|
+
baseUrl: globalOpts.baseUrl,
|
|
2400
|
+
dryRun: globalOpts.dryRun,
|
|
2401
|
+
verbose: globalOpts.verbose,
|
|
2402
|
+
body
|
|
2403
|
+
});
|
|
2404
|
+
printJson(data);
|
|
2405
|
+
} catch (error) {
|
|
2406
|
+
printError(error);
|
|
2407
|
+
process.exit(1);
|
|
2408
|
+
}
|
|
2409
|
+
});
|
|
2410
|
+
replay.command("list").description("List replay jobs").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
2411
|
+
try {
|
|
2412
|
+
const globalOpts = program.opts();
|
|
2413
|
+
const format = globalOpts.format ?? "json";
|
|
2414
|
+
const data = await apiRequest("GET", "replay/jobs", {
|
|
2415
|
+
apiKey: globalOpts.apiKey,
|
|
2416
|
+
baseUrl: globalOpts.baseUrl,
|
|
2417
|
+
dryRun: globalOpts.dryRun,
|
|
2418
|
+
verbose: globalOpts.verbose,
|
|
2419
|
+
params: { page: opts.page, size: opts.size }
|
|
2420
|
+
});
|
|
2421
|
+
if (format === "table") {
|
|
2422
|
+
printTable(
|
|
2423
|
+
["Job", "Version", "Window", "Status", "Progress", "Changed", "At"],
|
|
2424
|
+
data.content.map((j) => [
|
|
2425
|
+
j.jobId.substring(0, 12),
|
|
2426
|
+
j.candidateVersionName ?? "\u2013",
|
|
2427
|
+
`${j.fromDate}~${j.toDate}`,
|
|
2428
|
+
j.status,
|
|
2429
|
+
`${j.progress}%`,
|
|
2430
|
+
`${j.changedCount}${j.capped ? " (capped)" : ""}`,
|
|
2431
|
+
j.createdAt.substring(0, 16)
|
|
2432
|
+
]),
|
|
2433
|
+
{ truncate: 24 }
|
|
2434
|
+
);
|
|
2435
|
+
console.log(`
|
|
2436
|
+
${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
2437
|
+
} else {
|
|
2438
|
+
printJson(data);
|
|
2439
|
+
}
|
|
2440
|
+
} catch (error) {
|
|
2441
|
+
printError(error);
|
|
2442
|
+
process.exit(1);
|
|
2443
|
+
}
|
|
2444
|
+
});
|
|
2445
|
+
replay.command("get").description("Get replay job status and results").requiredOption("--id <jobId>", "Replay job ID").action(async (opts) => {
|
|
2446
|
+
try {
|
|
2447
|
+
const globalOpts = program.opts();
|
|
2448
|
+
const data = await apiRequest("GET", `replay/jobs/${opts.id}`, {
|
|
2449
|
+
apiKey: globalOpts.apiKey,
|
|
2450
|
+
baseUrl: globalOpts.baseUrl,
|
|
2451
|
+
dryRun: globalOpts.dryRun,
|
|
2452
|
+
verbose: globalOpts.verbose
|
|
2453
|
+
});
|
|
2454
|
+
printJson(data);
|
|
2455
|
+
} catch (error) {
|
|
2456
|
+
printError(error);
|
|
2457
|
+
process.exit(1);
|
|
2458
|
+
}
|
|
2459
|
+
});
|
|
2460
|
+
replay.command("cancel").description("Cancel a PENDING/RUNNING replay job").requiredOption("--id <jobId>", "Replay job ID").action(async (opts) => {
|
|
2461
|
+
try {
|
|
2462
|
+
const globalOpts = program.opts();
|
|
2463
|
+
const data = await apiRequest("POST", `replay/jobs/${opts.id}/cancel`, {
|
|
2464
|
+
apiKey: globalOpts.apiKey,
|
|
2465
|
+
baseUrl: globalOpts.baseUrl,
|
|
2466
|
+
dryRun: globalOpts.dryRun,
|
|
2467
|
+
verbose: globalOpts.verbose
|
|
2468
|
+
});
|
|
2469
|
+
printJson(data);
|
|
2470
|
+
} catch (error) {
|
|
2471
|
+
printError(error);
|
|
2472
|
+
process.exit(1);
|
|
2473
|
+
}
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
// src/commands/provenance.ts
|
|
2478
|
+
import "commander";
|
|
2479
|
+
import dedent11 from "dedent";
|
|
2480
|
+
function registerProvenanceCommands(program) {
|
|
2481
|
+
const provenance = program.command("provenance").description("Decision Provenance").addHelpText(
|
|
2482
|
+
"after",
|
|
2483
|
+
dedent11`
|
|
2484
|
+
|
|
2485
|
+
Trace who authored, published, and deployed the rules behind a decision.
|
|
2486
|
+
|
|
2487
|
+
Commands:
|
|
2488
|
+
get Get the lineage of one decision (PII facts masked)
|
|
2489
|
+
reveal-audits List the PII reveal audit ledger (metadata only)
|
|
2490
|
+
`
|
|
2491
|
+
);
|
|
2492
|
+
provenance.command("get").description("Get decision lineage").requiredOption("--trace-id <traceId>", "Trace ID of the execution").action(async (opts) => {
|
|
2493
|
+
try {
|
|
2494
|
+
const globalOpts = program.opts();
|
|
2495
|
+
const data = await apiRequest("GET", `provenance/${opts.traceId}`, {
|
|
2496
|
+
apiKey: globalOpts.apiKey,
|
|
2497
|
+
baseUrl: globalOpts.baseUrl,
|
|
2498
|
+
dryRun: globalOpts.dryRun,
|
|
2499
|
+
verbose: globalOpts.verbose
|
|
2500
|
+
});
|
|
2501
|
+
printJson(data);
|
|
2502
|
+
} catch (error) {
|
|
2503
|
+
printError(error);
|
|
2504
|
+
process.exit(1);
|
|
2505
|
+
}
|
|
2506
|
+
});
|
|
2507
|
+
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 date (yyyy-MM-dd)").option("--end-date <date>", "End date (yyyy-MM-dd)").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
|
|
2508
|
+
"after",
|
|
2509
|
+
dedent11`
|
|
2510
|
+
|
|
2511
|
+
Metadata only — revealed values are never stored or returned.
|
|
2512
|
+
|
|
2513
|
+
Example:
|
|
2514
|
+
$ lexq provenance reveal-audits --start-date 2026-07-01 --format table
|
|
2515
|
+
`
|
|
2516
|
+
).action(async (opts) => {
|
|
2517
|
+
try {
|
|
2518
|
+
const globalOpts = program.opts();
|
|
2519
|
+
const format = globalOpts.format ?? "json";
|
|
2520
|
+
const params = { page: opts.page, size: opts.size };
|
|
2521
|
+
if (opts.traceId) params.traceId = opts.traceId;
|
|
2522
|
+
if (opts.factKey) params.factKey = opts.factKey;
|
|
2523
|
+
if (opts.revealedBy) params.revealedBy = opts.revealedBy;
|
|
2524
|
+
if (opts.startDate) params.startDate = opts.startDate;
|
|
2525
|
+
if (opts.endDate) params.endDate = opts.endDate;
|
|
2526
|
+
const data = await apiRequest(
|
|
2527
|
+
"GET",
|
|
2528
|
+
"provenance/reveal-audits",
|
|
2529
|
+
{
|
|
2530
|
+
apiKey: globalOpts.apiKey,
|
|
2531
|
+
baseUrl: globalOpts.baseUrl,
|
|
2532
|
+
dryRun: globalOpts.dryRun,
|
|
2533
|
+
verbose: globalOpts.verbose,
|
|
2534
|
+
params
|
|
2535
|
+
}
|
|
2536
|
+
);
|
|
2537
|
+
if (format === "table") {
|
|
2538
|
+
printTable(
|
|
2539
|
+
["Revealed At", "By", "Fact Key", "Trace"],
|
|
2540
|
+
data.content.map((a) => [
|
|
2541
|
+
a.revealedAt.substring(0, 16),
|
|
2542
|
+
a.revealedByName,
|
|
2543
|
+
a.factKey,
|
|
2544
|
+
a.traceId.substring(0, 12)
|
|
2545
|
+
]),
|
|
2546
|
+
{ truncate: 24 }
|
|
2547
|
+
);
|
|
2548
|
+
console.log(`
|
|
2549
|
+
${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
2550
|
+
} else {
|
|
2551
|
+
printJson(data);
|
|
2552
|
+
}
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
printError(error);
|
|
2555
|
+
process.exit(1);
|
|
2556
|
+
}
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
// src/commands/integrations.ts
|
|
2561
|
+
import "commander";
|
|
2562
|
+
import dedent12 from "dedent";
|
|
2333
2563
|
function registerIntegrationCommands(program) {
|
|
2334
2564
|
const integrations = program.command("integrations").description("Manage external integrations").addHelpText(
|
|
2335
2565
|
"after",
|
|
2336
|
-
|
|
2566
|
+
dedent12`
|
|
2337
2567
|
|
|
2338
2568
|
Integrations connect rule actions to external services (webhooks, coupons,
|
|
2339
2569
|
points, notifications, CRM, messengers).
|
|
@@ -2403,7 +2633,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2403
2633
|
});
|
|
2404
2634
|
integrations.command("save").description("Create or update an integration").requiredOption("--json <body>", "Request body as JSON string").addHelpText(
|
|
2405
2635
|
"after",
|
|
2406
|
-
|
|
2636
|
+
dedent12`
|
|
2407
2637
|
|
|
2408
2638
|
Examples:
|
|
2409
2639
|
# Create
|
|
@@ -2453,7 +2683,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2453
2683
|
});
|
|
2454
2684
|
integrations.command("delete").description("Delete an integration").requiredOption("--id <integrationId>", "Integration ID").option("--force", "Skip confirmation prompt").addHelpText(
|
|
2455
2685
|
"after",
|
|
2456
|
-
|
|
2686
|
+
dedent12`
|
|
2457
2687
|
|
|
2458
2688
|
Rules referencing this integration will fail at execution time.
|
|
2459
2689
|
Use --force to skip confirmation.
|
|
@@ -2485,7 +2715,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2485
2715
|
});
|
|
2486
2716
|
integrations.command("config-spec").description("Get integration configuration field specs").addHelpText(
|
|
2487
2717
|
"after",
|
|
2488
|
-
|
|
2718
|
+
dedent12`
|
|
2489
2719
|
|
|
2490
2720
|
Shows required and optional configuration fields for each integration type.
|
|
2491
2721
|
|
|
@@ -2511,7 +2741,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2511
2741
|
|
|
2512
2742
|
// src/commands/logs.ts
|
|
2513
2743
|
import "commander";
|
|
2514
|
-
import
|
|
2744
|
+
import dedent13 from "dedent";
|
|
2515
2745
|
|
|
2516
2746
|
// src/types/enums.ts
|
|
2517
2747
|
var FailureStatus = ["PENDING", "RESOLVED", "IGNORED"];
|
|
@@ -2545,7 +2775,7 @@ var WebhookPayloadFormat = ["GENERIC", "SLACK"];
|
|
|
2545
2775
|
function registerLogCommands(program) {
|
|
2546
2776
|
const logs = program.command("logs").description("Failure logs").addHelpText(
|
|
2547
2777
|
"after",
|
|
2548
|
-
|
|
2778
|
+
dedent13`
|
|
2549
2779
|
|
|
2550
2780
|
System failure logs (DLQ) for background tasks — webhook calls, coupon issuance,
|
|
2551
2781
|
point operations, notifications, and platform event webhooks.
|
|
@@ -2562,7 +2792,7 @@ function registerLogCommands(program) {
|
|
|
2562
2792
|
);
|
|
2563
2793
|
logs.command("list").description("List failure logs").option("--category <category>", "Filter by category (INTEGRATION, INTERNAL)").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 date (yyyy-MM-dd)").option("--end-date <date>", "End date (yyyy-MM-dd)").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
|
|
2564
2794
|
"after",
|
|
2565
|
-
|
|
2795
|
+
dedent13`
|
|
2566
2796
|
|
|
2567
2797
|
Examples:
|
|
2568
2798
|
$ lexq logs list --status PENDING --format table
|
|
@@ -2613,7 +2843,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2613
2843
|
});
|
|
2614
2844
|
logs.command("get").description("Get failure log detail").requiredOption("--id <logId>", "Log ID").addHelpText(
|
|
2615
2845
|
"after",
|
|
2616
|
-
|
|
2846
|
+
dedent13`
|
|
2617
2847
|
|
|
2618
2848
|
Includes the full payload that was used for the failed operation.
|
|
2619
2849
|
Use this to inspect what went wrong before deciding to RETRY or RESOLVE.
|
|
@@ -2635,7 +2865,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2635
2865
|
});
|
|
2636
2866
|
logs.command("action").description("Process a failure log action (RETRY, IGNORE, RESOLVE)").requiredOption("--id <logId>", "Log ID").requiredOption("--action <action>", "Action: RETRY, IGNORE, or RESOLVE").addHelpText(
|
|
2637
2867
|
"after",
|
|
2638
|
-
|
|
2868
|
+
dedent13`
|
|
2639
2869
|
|
|
2640
2870
|
Actions:
|
|
2641
2871
|
RETRY Re-execute the failed operation with the original payload
|
|
@@ -2667,7 +2897,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2667
2897
|
});
|
|
2668
2898
|
logs.command("bulk-action").description("Bulk process failure logs").requiredOption("--ids <logIds>", "Comma-separated log IDs").requiredOption("--action <action>", "Action: RETRY, IGNORE, or RESOLVE").addHelpText(
|
|
2669
2899
|
"after",
|
|
2670
|
-
|
|
2900
|
+
dedent13`
|
|
2671
2901
|
|
|
2672
2902
|
Processes each log individually. Failures are skipped with a warning.
|
|
2673
2903
|
|
|
@@ -2695,11 +2925,11 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2695
2925
|
|
|
2696
2926
|
// src/commands/webhook-subscriptions.ts
|
|
2697
2927
|
import "commander";
|
|
2698
|
-
import
|
|
2928
|
+
import dedent14 from "dedent";
|
|
2699
2929
|
function registerWebhookSubscriptionCommands(program) {
|
|
2700
2930
|
const webhooks = program.command("webhook-subscriptions").description("Manage platform event webhook subscriptions").addHelpText(
|
|
2701
2931
|
"after",
|
|
2702
|
-
|
|
2932
|
+
dedent14`
|
|
2703
2933
|
|
|
2704
2934
|
Receive notifications when deployment lifecycle events occur
|
|
2705
2935
|
(publish, deploy, rollback, undeploy).
|
|
@@ -2773,7 +3003,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2773
3003
|
});
|
|
2774
3004
|
webhooks.command("save").description("Create or update a webhook subscription").requiredOption("--json <body>", "Request body as JSON string").addHelpText(
|
|
2775
3005
|
"after",
|
|
2776
|
-
|
|
3006
|
+
dedent14`
|
|
2777
3007
|
|
|
2778
3008
|
Examples:
|
|
2779
3009
|
# Create (Slack format)
|
|
@@ -2828,7 +3058,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2828
3058
|
});
|
|
2829
3059
|
webhooks.command("delete").description("Delete a webhook subscription").requiredOption("--id <subscriptionId>", "Subscription ID").option("--force", "Skip confirmation prompt").addHelpText(
|
|
2830
3060
|
"after",
|
|
2831
|
-
|
|
3061
|
+
dedent14`
|
|
2832
3062
|
|
|
2833
3063
|
Use --force to skip the confirmation prompt.
|
|
2834
3064
|
|
|
@@ -2862,7 +3092,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2862
3092
|
});
|
|
2863
3093
|
webhooks.command("test").description("Send a test event to verify webhook connectivity").requiredOption("--id <subscriptionId>", "Subscription ID").addHelpText(
|
|
2864
3094
|
"after",
|
|
2865
|
-
|
|
3095
|
+
dedent14`
|
|
2866
3096
|
|
|
2867
3097
|
Sends a test event to the webhook URL and reports the HTTP status code.
|
|
2868
3098
|
Does not record failures in the failure log.
|
|
@@ -2898,7 +3128,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2898
3128
|
|
|
2899
3129
|
// src/commands/serve.ts
|
|
2900
3130
|
import "commander";
|
|
2901
|
-
import
|
|
3131
|
+
import dedent17 from "dedent";
|
|
2902
3132
|
|
|
2903
3133
|
// src/mcp/server.ts
|
|
2904
3134
|
import { readFileSync as readFileSync3 } from "fs";
|
|
@@ -2911,7 +3141,12 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
2911
3141
|
function createCallApiFromConfig() {
|
|
2912
3142
|
return async (method, path, opts) => {
|
|
2913
3143
|
try {
|
|
2914
|
-
const
|
|
3144
|
+
const stored = loadConfig();
|
|
3145
|
+
const config = {
|
|
3146
|
+
...stored,
|
|
3147
|
+
baseUrl: process.env.PARTNER_BASE_URL ?? stored.baseUrl,
|
|
3148
|
+
apiKey: process.env.LEXQ_API_KEY ?? stored.apiKey
|
|
3149
|
+
};
|
|
2915
3150
|
if (opts?.upload) {
|
|
2916
3151
|
const url = new URL(
|
|
2917
3152
|
path,
|
|
@@ -3220,7 +3455,7 @@ function registerVersionTools(server, callApi) {
|
|
|
3220
3455
|
|
|
3221
3456
|
// src/mcp/tools/rules.ts
|
|
3222
3457
|
import { z as z3 } from "zod";
|
|
3223
|
-
import
|
|
3458
|
+
import dedent15 from "dedent";
|
|
3224
3459
|
function registerRuleTools(server, callApi) {
|
|
3225
3460
|
server.registerTool(
|
|
3226
3461
|
"lexq_rules_list",
|
|
@@ -3251,7 +3486,7 @@ function registerRuleTools(server, callApi) {
|
|
|
3251
3486
|
"lexq_rules_create",
|
|
3252
3487
|
{
|
|
3253
3488
|
title: "Create Rule",
|
|
3254
|
-
description:
|
|
3489
|
+
description: dedent15`
|
|
3255
3490
|
Create a rule in a DRAFT version. Requires name, condition tree, and actions array. priority is auto-assigned (appended last); use lexq_rules_reorder to change order.
|
|
3256
3491
|
|
|
3257
3492
|
Before creating rules with new fact keys, call lexq_facts_list to check existing facts.
|
|
@@ -3384,7 +3619,7 @@ function registerFactTools(server, callApi) {
|
|
|
3384
3619
|
"lexq_facts_list",
|
|
3385
3620
|
{
|
|
3386
3621
|
title: "List Fact Definitions",
|
|
3387
|
-
description: "List all fact definitions (input variable schema). Shows key, type, and
|
|
3622
|
+
description: "List all fact definitions (input variable schema). Shows key, type, required, and PII status. Always check this before creating rules.",
|
|
3388
3623
|
inputSchema: {
|
|
3389
3624
|
page: z4.number().int().min(0).default(0).describe("Page number"),
|
|
3390
3625
|
size: z4.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
@@ -3407,7 +3642,10 @@ function registerFactTools(server, callApi) {
|
|
|
3407
3642
|
name: z4.string().describe("Display name"),
|
|
3408
3643
|
type: z4.enum(["STRING", "NUMBER", "BOOLEAN", "LIST_STRING", "LIST_NUMBER"]).describe("Value type"),
|
|
3409
3644
|
description: z4.string().optional().describe("Description"),
|
|
3410
|
-
isRequired: z4.boolean().default(false).describe("Whether this fact is required for rule evaluation")
|
|
3645
|
+
isRequired: z4.boolean().default(false).describe("Whether this fact is required for rule evaluation"),
|
|
3646
|
+
isPii: z4.boolean().default(false).describe(
|
|
3647
|
+
"Mark as PII \u2014 masked on every read surface, revealable only in the console (audited)"
|
|
3648
|
+
)
|
|
3411
3649
|
}
|
|
3412
3650
|
},
|
|
3413
3651
|
async (args) => callApi("POST", "schema/facts", { body: args })
|
|
@@ -3416,12 +3654,13 @@ function registerFactTools(server, callApi) {
|
|
|
3416
3654
|
"lexq_facts_update",
|
|
3417
3655
|
{
|
|
3418
3656
|
title: "Update Fact Definition",
|
|
3419
|
-
description: "Update a fact definition. Key and type cannot be changed. Only provided fields are updated. System facts only allow name and
|
|
3657
|
+
description: "Update a fact definition. Key and type cannot be changed. Only provided fields are updated. System facts only allow name, description, and PII changes.",
|
|
3420
3658
|
inputSchema: {
|
|
3421
3659
|
factId: z4.string().uuid().describe("Fact definition ID"),
|
|
3422
3660
|
name: z4.string().optional().describe("Display name"),
|
|
3423
3661
|
description: z4.string().optional().describe("Description"),
|
|
3424
|
-
isRequired: z4.boolean().optional().describe("Required flag")
|
|
3662
|
+
isRequired: z4.boolean().optional().describe("Required flag"),
|
|
3663
|
+
isPii: z4.boolean().optional().describe("PII flag \u2014 enables/disables masking (changeable even on system facts)")
|
|
3425
3664
|
}
|
|
3426
3665
|
},
|
|
3427
3666
|
async ({ factId, ...body }) => callApi("PUT", `schema/facts/${factId}`, { body })
|
|
@@ -3593,13 +3832,13 @@ function registerDeployTools(server, callApi) {
|
|
|
3593
3832
|
|
|
3594
3833
|
// src/mcp/tools/analytics.ts
|
|
3595
3834
|
import { z as z6 } from "zod";
|
|
3596
|
-
import
|
|
3835
|
+
import dedent16 from "dedent";
|
|
3597
3836
|
function registerAnalyticsTools(server, callApi) {
|
|
3598
3837
|
server.registerTool(
|
|
3599
3838
|
"lexq_dry_run",
|
|
3600
3839
|
{
|
|
3601
3840
|
title: "Dry Run",
|
|
3602
|
-
description:
|
|
3841
|
+
description: dedent16`
|
|
3603
3842
|
Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
|
|
3604
3843
|
|
|
3605
3844
|
Returns:
|
|
@@ -3629,7 +3868,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
3629
3868
|
"lexq_dry_run_compare",
|
|
3630
3869
|
{
|
|
3631
3870
|
title: "Dry Run Compare",
|
|
3632
|
-
description:
|
|
3871
|
+
description: dedent16`
|
|
3633
3872
|
Compare dry run results between two versions using the same input facts. Useful for validating changes.
|
|
3634
3873
|
|
|
3635
3874
|
Returns:
|
|
@@ -3665,22 +3904,33 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
3665
3904
|
"lexq_simulation_start",
|
|
3666
3905
|
{
|
|
3667
3906
|
title: "Start Simulation",
|
|
3668
|
-
description:
|
|
3669
|
-
Start an Impact Simulation against historical or
|
|
3907
|
+
description: dedent16`
|
|
3908
|
+
Start an Impact Simulation against historical, uploaded, or inline data.
|
|
3909
|
+
|
|
3910
|
+
dataset.type and dataset.source are BOTH required, and must be paired:
|
|
3911
|
+
HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd)
|
|
3912
|
+
UPLOADED → source S3_BUCKET, with dataset.path (the path returned by lexq_dataset_upload)
|
|
3913
|
+
MANUAL → source REQUEST_BODY, with dataset.manualData (array of fact records)
|
|
3670
3914
|
|
|
3671
|
-
dataset.type: "HISTORICAL" or "UPLOADED"
|
|
3672
|
-
dataset.source (when HISTORICAL): "EXECUTION_LOGS"
|
|
3673
|
-
dataset.from / dataset.to: date range (yyyy-MM-dd, when HISTORICAL)
|
|
3674
3915
|
options.maxRecords: number (max 100000, default 10000)
|
|
3675
|
-
options.baselinePolicyVersionId: uuid (optional, for comparison)
|
|
3916
|
+
options.baselinePolicyVersionId: uuid (optional, for baseline comparison)
|
|
3676
3917
|
options.includeRuleStats: boolean
|
|
3918
|
+
options.metricConfig: optional — omit for plain execution count. To aggregate a fact, pass
|
|
3919
|
+
{ "targetVariable": "<fact>", "aggregationType": "COUNT" | "SUM" | "AVG" }
|
|
3677
3920
|
|
|
3678
|
-
Example
|
|
3921
|
+
Example (uploaded dataset):
|
|
3679
3922
|
{
|
|
3680
3923
|
"policyVersionId": "<uuid>",
|
|
3681
|
-
"dataset": { "type": "
|
|
3924
|
+
"dataset": { "type": "UPLOADED", "source": "S3_BUCKET", "path": "<path from lexq_dataset_upload>" },
|
|
3682
3925
|
"options": { "baselinePolicyVersionId": "<uuid>", "includeRuleStats": true, "maxRecords": 10000 }
|
|
3683
3926
|
}
|
|
3927
|
+
|
|
3928
|
+
Example (historical):
|
|
3929
|
+
{
|
|
3930
|
+
"policyVersionId": "<uuid>",
|
|
3931
|
+
"dataset": { "type": "HISTORICAL", "source": "EXECUTION_LOGS", "from": "2026-01-01", "to": "2026-01-31" },
|
|
3932
|
+
"options": { "baselinePolicyVersionId": "<uuid>", "includeRuleStats": true }
|
|
3933
|
+
}
|
|
3684
3934
|
`,
|
|
3685
3935
|
inputSchema: {
|
|
3686
3936
|
body: z6.string().describe("JSON string of SimulationRequest")
|
|
@@ -3752,10 +4002,12 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
3752
4002
|
"lexq_dataset_upload",
|
|
3753
4003
|
{
|
|
3754
4004
|
title: "Upload Dataset",
|
|
3755
|
-
description:
|
|
4005
|
+
description: dedent16`
|
|
3756
4006
|
Upload inline CSV or JSON content as a simulation dataset.
|
|
3757
|
-
The content is uploaded to S3 and a path is returned.
|
|
3758
|
-
|
|
4007
|
+
The content is uploaded to S3 and a path is returned in the "path" field.
|
|
4008
|
+
|
|
4009
|
+
To use the returned path in lexq_simulation_start, set:
|
|
4010
|
+
dataset: { "type": "UPLOADED", "source": "S3_BUCKET", "path": "<returned path>" }
|
|
3759
4011
|
|
|
3760
4012
|
CSV example:
|
|
3761
4013
|
user_id,payment_amount
|
|
@@ -3769,9 +4021,25 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
3769
4021
|
filename: z6.string().default("dataset.csv").describe("Filename with extension (.csv or .json)")
|
|
3770
4022
|
}
|
|
3771
4023
|
},
|
|
3772
|
-
async ({ content, filename }) =>
|
|
3773
|
-
|
|
3774
|
-
|
|
4024
|
+
async ({ content, filename }) => {
|
|
4025
|
+
const result = await callApi("POST", "analytics/datasets/upload", {
|
|
4026
|
+
upload: { content, filename, fieldName: "file" }
|
|
4027
|
+
});
|
|
4028
|
+
if (!result.isError) {
|
|
4029
|
+
try {
|
|
4030
|
+
const uploaded = JSON.parse(result.content[0]?.text ?? "{}");
|
|
4031
|
+
if (uploaded.path) {
|
|
4032
|
+
const dataset = { type: "UPLOADED", source: "S3_BUCKET", path: uploaded.path };
|
|
4033
|
+
result.content.push({
|
|
4034
|
+
type: "text",
|
|
4035
|
+
text: "Ready-to-use dataset block for lexq_simulation_start:\n" + JSON.stringify({ dataset }, null, 2)
|
|
4036
|
+
});
|
|
4037
|
+
}
|
|
4038
|
+
} catch {
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
return result;
|
|
4042
|
+
}
|
|
3775
4043
|
);
|
|
3776
4044
|
server.registerTool(
|
|
3777
4045
|
"lexq_dataset_template",
|
|
@@ -3790,8 +4058,73 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
3790
4058
|
);
|
|
3791
4059
|
}
|
|
3792
4060
|
|
|
3793
|
-
// src/mcp/tools/
|
|
4061
|
+
// src/mcp/tools/replay.ts
|
|
3794
4062
|
import { z as z7 } from "zod";
|
|
4063
|
+
function registerReplayTools(server, callApi) {
|
|
4064
|
+
server.registerTool(
|
|
4065
|
+
"lexq_replay_decision",
|
|
4066
|
+
{
|
|
4067
|
+
title: "Replay a Decision",
|
|
4068
|
+
description: "Re-evaluate a past execution (traceId) against a candidate version and return the decision diff (decisionChanged, effect changes, fired rules) plus a determinism verdict. Synchronous and free of charge (TPS throttle only). External effects (webhooks, notifications) are always mocked \u2014 nothing fires.",
|
|
4069
|
+
inputSchema: {
|
|
4070
|
+
traceId: z7.string().describe("Trace ID of the past execution to replay"),
|
|
4071
|
+
candidateVersionId: z7.string().uuid().describe("Version to re-evaluate against")
|
|
4072
|
+
}
|
|
4073
|
+
},
|
|
4074
|
+
async ({ traceId, candidateVersionId }) => callApi("POST", "replay/decisions", { body: { traceId, candidateVersionId } })
|
|
4075
|
+
);
|
|
4076
|
+
server.registerTool(
|
|
4077
|
+
"lexq_replay_start",
|
|
4078
|
+
{
|
|
4079
|
+
title: "Start Window Replay (Blast Radius)",
|
|
4080
|
+
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.",
|
|
4081
|
+
inputSchema: {
|
|
4082
|
+
candidateVersionId: z7.string().uuid().describe("Version to re-evaluate against"),
|
|
4083
|
+
from: z7.string().describe("Window start date (yyyy-MM-dd)"),
|
|
4084
|
+
to: z7.string().describe("Window end date (yyyy-MM-dd)"),
|
|
4085
|
+
maxRecords: z7.number().int().min(1).optional().describe("Sample cap (server default applies; hard cap 50k)")
|
|
4086
|
+
}
|
|
4087
|
+
},
|
|
4088
|
+
async ({ candidateVersionId, from, to, maxRecords }) => callApi("POST", "replay/jobs", { body: { candidateVersionId, from, to, maxRecords } })
|
|
4089
|
+
);
|
|
4090
|
+
server.registerTool(
|
|
4091
|
+
"lexq_replay_status",
|
|
4092
|
+
{
|
|
4093
|
+
title: "Get Replay Job Status",
|
|
4094
|
+
description: "Poll a window replay job. RUNNING shows progress 0\u2013100; COMPLETED fills summary and changedSamples; FAILED carries errorMessage. capped=true means the window exceeded the sample cap and only part was replayed.",
|
|
4095
|
+
inputSchema: {
|
|
4096
|
+
jobId: z7.string().describe("Replay job ID from lexq_replay_start")
|
|
4097
|
+
}
|
|
4098
|
+
},
|
|
4099
|
+
async ({ jobId }) => callApi("GET", `replay/jobs/${jobId}`)
|
|
4100
|
+
);
|
|
4101
|
+
server.registerTool(
|
|
4102
|
+
"lexq_replay_list",
|
|
4103
|
+
{
|
|
4104
|
+
title: "List Replay Jobs",
|
|
4105
|
+
description: "List window replay job history (reverse-chronological). Lightweight items \u2014 use lexq_replay_status for summary and changed samples.",
|
|
4106
|
+
inputSchema: {
|
|
4107
|
+
page: z7.number().int().min(0).default(0).describe("Page number"),
|
|
4108
|
+
size: z7.number().int().min(1).max(100).default(20).describe("Page size")
|
|
4109
|
+
}
|
|
4110
|
+
},
|
|
4111
|
+
async ({ page, size }) => callApi("GET", "replay/jobs", { params: paginationParams(page, size) })
|
|
4112
|
+
);
|
|
4113
|
+
server.registerTool(
|
|
4114
|
+
"lexq_replay_cancel",
|
|
4115
|
+
{
|
|
4116
|
+
title: "Cancel Replay Job",
|
|
4117
|
+
description: "Cooperatively cancel a PENDING or RUNNING window replay job. Other states are rejected. VIEWER role cannot cancel.",
|
|
4118
|
+
inputSchema: {
|
|
4119
|
+
jobId: z7.string().describe("Replay job ID")
|
|
4120
|
+
}
|
|
4121
|
+
},
|
|
4122
|
+
async ({ jobId }) => callApi("POST", `replay/jobs/${jobId}/cancel`)
|
|
4123
|
+
);
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
// src/mcp/tools/history.ts
|
|
4127
|
+
import { z as z8 } from "zod";
|
|
3795
4128
|
function registerHistoryTools(server, callApi) {
|
|
3796
4129
|
server.registerTool(
|
|
3797
4130
|
"lexq_history_list",
|
|
@@ -3799,14 +4132,14 @@ function registerHistoryTools(server, callApi) {
|
|
|
3799
4132
|
title: "List Execution History",
|
|
3800
4133
|
description: "List policy execution history. Shows trace ID, group, version, status, match result, and latency.",
|
|
3801
4134
|
inputSchema: {
|
|
3802
|
-
page:
|
|
3803
|
-
size:
|
|
3804
|
-
traceId:
|
|
3805
|
-
groupId:
|
|
3806
|
-
versionId:
|
|
3807
|
-
status:
|
|
3808
|
-
startDate:
|
|
3809
|
-
endDate:
|
|
4135
|
+
page: z8.number().int().min(0).default(0).describe("Page number"),
|
|
4136
|
+
size: z8.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
4137
|
+
traceId: z8.string().optional().describe("Filter by trace ID"),
|
|
4138
|
+
groupId: z8.string().uuid().optional().describe("Filter by policy group"),
|
|
4139
|
+
versionId: z8.string().uuid().optional().describe("Filter by version"),
|
|
4140
|
+
status: z8.enum(["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"]).optional().describe("Filter by execution status"),
|
|
4141
|
+
startDate: z8.string().optional().describe("Start date (yyyy-MM-dd)"),
|
|
4142
|
+
endDate: z8.string().optional().describe("End date (yyyy-MM-dd)")
|
|
3810
4143
|
}
|
|
3811
4144
|
},
|
|
3812
4145
|
async ({ page, size, traceId, groupId, versionId, status, startDate, endDate }) => {
|
|
@@ -3826,7 +4159,7 @@ function registerHistoryTools(server, callApi) {
|
|
|
3826
4159
|
title: "Get Execution Detail",
|
|
3827
4160
|
description: "Get full execution detail including inputFacts, mutatedFacts, generatedVariables, executionTraces, and decisionTraces.",
|
|
3828
4161
|
inputSchema: {
|
|
3829
|
-
traceId:
|
|
4162
|
+
traceId: z8.string().describe("Trace ID from execution history")
|
|
3830
4163
|
}
|
|
3831
4164
|
},
|
|
3832
4165
|
async ({ traceId }) => callApi("GET", `execution/history/${traceId}`)
|
|
@@ -3837,9 +4170,9 @@ function registerHistoryTools(server, callApi) {
|
|
|
3837
4170
|
title: "Execution Statistics",
|
|
3838
4171
|
description: "Get execution KPIs: total executions, success/failure counts, success rate, and average latency.",
|
|
3839
4172
|
inputSchema: {
|
|
3840
|
-
groupId:
|
|
3841
|
-
startDate:
|
|
3842
|
-
endDate:
|
|
4173
|
+
groupId: z8.string().uuid().optional().describe("Filter by policy group"),
|
|
4174
|
+
startDate: z8.string().optional().describe("Start date (yyyy-MM-dd)"),
|
|
4175
|
+
endDate: z8.string().optional().describe("End date (yyyy-MM-dd)")
|
|
3843
4176
|
}
|
|
3844
4177
|
},
|
|
3845
4178
|
async ({ groupId, startDate, endDate }) => {
|
|
@@ -3852,8 +4185,49 @@ function registerHistoryTools(server, callApi) {
|
|
|
3852
4185
|
);
|
|
3853
4186
|
}
|
|
3854
4187
|
|
|
4188
|
+
// src/mcp/tools/provenance.ts
|
|
4189
|
+
import { z as z9 } from "zod";
|
|
4190
|
+
function registerProvenanceTools(server, callApi) {
|
|
4191
|
+
server.registerTool(
|
|
4192
|
+
"lexq_provenance_get",
|
|
4193
|
+
{
|
|
4194
|
+
title: "Get Decision Provenance",
|
|
4195
|
+
description: "Get the lineage of a single decision: what was decided, deterministic why per rule, input facts (PII facts are masked as \u2022\u2022\u2022\u2022\u2022\u2022 with maskedKeys listing them \u2014 values are revealable only in the console, audited), the authored/published/deployed responsibility chain, and the rule snapshot fingerprint.",
|
|
4196
|
+
inputSchema: {
|
|
4197
|
+
traceId: z9.string().describe("Trace ID of the execution")
|
|
4198
|
+
}
|
|
4199
|
+
},
|
|
4200
|
+
async ({ traceId }) => callApi("GET", `provenance/${traceId}`)
|
|
4201
|
+
);
|
|
4202
|
+
server.registerTool(
|
|
4203
|
+
"lexq_pii_reveals_list",
|
|
4204
|
+
{
|
|
4205
|
+
title: "List PII Reveal Audits",
|
|
4206
|
+
description: "List the PII reveal audit ledger \u2014 who revealed which fact of which trace, and when. Metadata only; revealed values are never stored or returned. Use for monthly access-log inspection and SIEM collection.",
|
|
4207
|
+
inputSchema: {
|
|
4208
|
+
page: z9.number().int().min(0).default(0).describe("Page number"),
|
|
4209
|
+
size: z9.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
4210
|
+
traceId: z9.string().optional().describe("Filter by trace ID (exact match)"),
|
|
4211
|
+
revealedBy: z9.string().optional().describe("Filter by operator ID (exact match)"),
|
|
4212
|
+
factKey: z9.string().optional().describe("Filter by fact key (partial match, case-insensitive)"),
|
|
4213
|
+
startDate: z9.string().optional().describe("Start date (yyyy-MM-dd)"),
|
|
4214
|
+
endDate: z9.string().optional().describe("End date (yyyy-MM-dd)")
|
|
4215
|
+
}
|
|
4216
|
+
},
|
|
4217
|
+
async ({ page, size, traceId, revealedBy, factKey, startDate, endDate }) => {
|
|
4218
|
+
const params = paginationParams(page, size);
|
|
4219
|
+
if (traceId) params.traceId = traceId;
|
|
4220
|
+
if (revealedBy) params.revealedBy = revealedBy;
|
|
4221
|
+
if (factKey) params.factKey = factKey;
|
|
4222
|
+
if (startDate) params.startDate = startDate;
|
|
4223
|
+
if (endDate) params.endDate = endDate;
|
|
4224
|
+
return callApi("GET", "provenance/reveal-audits", { params });
|
|
4225
|
+
}
|
|
4226
|
+
);
|
|
4227
|
+
}
|
|
4228
|
+
|
|
3855
4229
|
// src/mcp/tools/integrations.ts
|
|
3856
|
-
import { z as
|
|
4230
|
+
import { z as z10 } from "zod";
|
|
3857
4231
|
function registerIntegrationTools(server, callApi) {
|
|
3858
4232
|
server.registerTool(
|
|
3859
4233
|
"lexq_integrations_list",
|
|
@@ -3861,9 +4235,9 @@ function registerIntegrationTools(server, callApi) {
|
|
|
3861
4235
|
title: "List Integrations",
|
|
3862
4236
|
description: "List all external integrations (webhooks, CRM, notification, etc.).",
|
|
3863
4237
|
inputSchema: {
|
|
3864
|
-
page:
|
|
3865
|
-
size:
|
|
3866
|
-
type:
|
|
4238
|
+
page: z10.number().int().min(0).default(0).describe("Page number"),
|
|
4239
|
+
size: z10.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
4240
|
+
type: z10.enum(["COUPON", "POINT", "NOTIFICATION", "CRM", "MESSENGER", "WEBHOOK"]).optional().describe("Filter by integration type")
|
|
3867
4241
|
}
|
|
3868
4242
|
},
|
|
3869
4243
|
async ({ page, size, type }) => {
|
|
@@ -3878,7 +4252,7 @@ function registerIntegrationTools(server, callApi) {
|
|
|
3878
4252
|
title: "Get Integration",
|
|
3879
4253
|
description: "Get integration detail by ID.",
|
|
3880
4254
|
inputSchema: {
|
|
3881
|
-
integrationId:
|
|
4255
|
+
integrationId: z10.string().uuid().describe("Integration ID")
|
|
3882
4256
|
}
|
|
3883
4257
|
},
|
|
3884
4258
|
async ({ integrationId }) => callApi("GET", `integrations/${integrationId}`)
|
|
@@ -3889,13 +4263,13 @@ function registerIntegrationTools(server, callApi) {
|
|
|
3889
4263
|
title: "Save Integration",
|
|
3890
4264
|
description: "Create or update an integration. Provide id to update an existing one; omit id to create new. Types: COUPON, POINT, NOTIFICATION, CRM, MESSENGER, WEBHOOK.",
|
|
3891
4265
|
inputSchema: {
|
|
3892
|
-
id:
|
|
3893
|
-
type:
|
|
3894
|
-
name:
|
|
3895
|
-
baseUrl:
|
|
3896
|
-
credential:
|
|
3897
|
-
additionalConfig:
|
|
3898
|
-
isActive:
|
|
4266
|
+
id: z10.string().uuid().optional().describe("Integration ID (omit to create, provide to update)"),
|
|
4267
|
+
type: z10.enum(["COUPON", "POINT", "NOTIFICATION", "CRM", "MESSENGER", "WEBHOOK"]).describe("Integration type"),
|
|
4268
|
+
name: z10.string().describe("Integration name"),
|
|
4269
|
+
baseUrl: z10.string().describe("Base URL of the external service"),
|
|
4270
|
+
credential: z10.string().optional().describe("API key or token for the service"),
|
|
4271
|
+
additionalConfig: z10.string().optional().describe("JSON string of additional config key-value pairs"),
|
|
4272
|
+
isActive: z10.boolean().default(true).describe("Whether the integration is active")
|
|
3899
4273
|
}
|
|
3900
4274
|
},
|
|
3901
4275
|
async ({ additionalConfig, ...rest }) => {
|
|
@@ -3910,7 +4284,7 @@ function registerIntegrationTools(server, callApi) {
|
|
|
3910
4284
|
title: "Delete Integration",
|
|
3911
4285
|
description: "Delete an integration by ID.",
|
|
3912
4286
|
inputSchema: {
|
|
3913
|
-
integrationId:
|
|
4287
|
+
integrationId: z10.string().uuid().describe("Integration ID")
|
|
3914
4288
|
}
|
|
3915
4289
|
},
|
|
3916
4290
|
async ({ integrationId }) => callApi("DELETE", `integrations/${integrationId}`)
|
|
@@ -3927,7 +4301,7 @@ function registerIntegrationTools(server, callApi) {
|
|
|
3927
4301
|
}
|
|
3928
4302
|
|
|
3929
4303
|
// src/mcp/tools/logs.ts
|
|
3930
|
-
import { z as
|
|
4304
|
+
import { z as z11 } from "zod";
|
|
3931
4305
|
function registerLogTools(server, callApi) {
|
|
3932
4306
|
server.registerTool(
|
|
3933
4307
|
"lexq_logs_list",
|
|
@@ -3935,14 +4309,14 @@ function registerLogTools(server, callApi) {
|
|
|
3935
4309
|
title: "List Failure Logs",
|
|
3936
4310
|
description: "List system failure logs from background tasks (webhook calls, coupon issuance, etc.).",
|
|
3937
4311
|
inputSchema: {
|
|
3938
|
-
page:
|
|
3939
|
-
size:
|
|
3940
|
-
category:
|
|
3941
|
-
taskType:
|
|
3942
|
-
status:
|
|
3943
|
-
keyword:
|
|
3944
|
-
startDate:
|
|
3945
|
-
endDate:
|
|
4312
|
+
page: z11.number().int().min(0).default(0).describe("Page number"),
|
|
4313
|
+
size: z11.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
4314
|
+
category: z11.enum(TaskCategory).optional().describe("Task category"),
|
|
4315
|
+
taskType: z11.enum(TaskType).optional().describe("Task type"),
|
|
4316
|
+
status: z11.enum(FailureStatus).optional().describe("Log status"),
|
|
4317
|
+
keyword: z11.string().optional().describe("Search in refId, refSubId, errorMessage"),
|
|
4318
|
+
startDate: z11.string().optional().describe("Start date (yyyy-MM-dd)"),
|
|
4319
|
+
endDate: z11.string().optional().describe("End date (yyyy-MM-dd)")
|
|
3946
4320
|
}
|
|
3947
4321
|
},
|
|
3948
4322
|
async ({ page, size, category, taskType, status, keyword, startDate, endDate }) => {
|
|
@@ -3962,7 +4336,7 @@ function registerLogTools(server, callApi) {
|
|
|
3962
4336
|
title: "Get Failure Log",
|
|
3963
4337
|
description: "Get failure log detail by ID.",
|
|
3964
4338
|
inputSchema: {
|
|
3965
|
-
logId:
|
|
4339
|
+
logId: z11.string().uuid().describe("Failure log ID")
|
|
3966
4340
|
}
|
|
3967
4341
|
},
|
|
3968
4342
|
async ({ logId }) => callApi("GET", `failure-logs/${logId}`)
|
|
@@ -3973,8 +4347,8 @@ function registerLogTools(server, callApi) {
|
|
|
3973
4347
|
title: "Process Failure Log",
|
|
3974
4348
|
description: "Process a single failure log: RETRY (re-execute with original payload), RESOLVE (mark as manually fixed), or IGNORE (skip intentionally).",
|
|
3975
4349
|
inputSchema: {
|
|
3976
|
-
logId:
|
|
3977
|
-
action:
|
|
4350
|
+
logId: z11.string().uuid().describe("Failure log ID"),
|
|
4351
|
+
action: z11.enum(FailureAction).describe("Action to take")
|
|
3978
4352
|
}
|
|
3979
4353
|
},
|
|
3980
4354
|
async ({ logId, action }) => callApi("POST", `failure-logs/${logId}/actions`, {
|
|
@@ -3987,8 +4361,8 @@ function registerLogTools(server, callApi) {
|
|
|
3987
4361
|
title: "Bulk Process Failure Logs",
|
|
3988
4362
|
description: "Process multiple failure logs at once. Provide an array of log IDs and the action.",
|
|
3989
4363
|
inputSchema: {
|
|
3990
|
-
logIds:
|
|
3991
|
-
action:
|
|
4364
|
+
logIds: z11.array(z11.string().uuid()).describe("Array of failure log IDs"),
|
|
4365
|
+
action: z11.enum(FailureAction).describe("Action to apply to all logs")
|
|
3992
4366
|
}
|
|
3993
4367
|
},
|
|
3994
4368
|
async ({ logIds, action }) => callApi("POST", "failure-logs/bulk-actions", {
|
|
@@ -3998,7 +4372,7 @@ function registerLogTools(server, callApi) {
|
|
|
3998
4372
|
}
|
|
3999
4373
|
|
|
4000
4374
|
// src/mcp/tools/webhook-subscriptions.ts
|
|
4001
|
-
import { z as
|
|
4375
|
+
import { z as z12 } from "zod";
|
|
4002
4376
|
function registerWebhookSubscriptionTools(server, callApi) {
|
|
4003
4377
|
server.registerTool(
|
|
4004
4378
|
"lexq_webhook_subscriptions_list",
|
|
@@ -4006,8 +4380,8 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
4006
4380
|
title: "List Webhook Subscriptions",
|
|
4007
4381
|
description: "List platform event webhook subscriptions. These receive deployment lifecycle notifications (publish, deploy, rollback, undeploy).",
|
|
4008
4382
|
inputSchema: {
|
|
4009
|
-
page:
|
|
4010
|
-
size:
|
|
4383
|
+
page: z12.number().int().min(0).default(0).describe("Page number"),
|
|
4384
|
+
size: z12.number().int().min(1).max(100).default(20).describe("Page size")
|
|
4011
4385
|
}
|
|
4012
4386
|
},
|
|
4013
4387
|
async ({ page, size }) => {
|
|
@@ -4021,7 +4395,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
4021
4395
|
title: "Get Webhook Subscription",
|
|
4022
4396
|
description: "Get webhook subscription detail by ID.",
|
|
4023
4397
|
inputSchema: {
|
|
4024
|
-
id:
|
|
4398
|
+
id: z12.string().uuid().describe("Webhook subscription ID")
|
|
4025
4399
|
}
|
|
4026
4400
|
},
|
|
4027
4401
|
async ({ id }) => callApi("GET", `webhook-subscriptions/${id}`)
|
|
@@ -4032,13 +4406,13 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
4032
4406
|
title: "Save Webhook Subscription",
|
|
4033
4407
|
description: 'Create or update a webhook subscription. Omit id to create, provide id to update. Events: VERSION_PUBLISHED, DEPLOYED, ROLLED_BACK, UNDEPLOYED. Formats: GENERIC (full JSON), SLACK ({"text": "..."}).',
|
|
4034
4408
|
inputSchema: {
|
|
4035
|
-
id:
|
|
4036
|
-
name:
|
|
4037
|
-
webhookUrl:
|
|
4038
|
-
subscribedEvents:
|
|
4039
|
-
payloadFormat:
|
|
4040
|
-
secret:
|
|
4041
|
-
isActive:
|
|
4409
|
+
id: z12.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
|
|
4410
|
+
name: z12.string().min(1).describe("Subscription name (unique per tenant)"),
|
|
4411
|
+
webhookUrl: z12.string().url().describe("Webhook endpoint URL"),
|
|
4412
|
+
subscribedEvents: z12.array(z12.enum(PlatformEventType)).min(1).describe("Events to subscribe to"),
|
|
4413
|
+
payloadFormat: z12.enum(WebhookPayloadFormat).optional().default("GENERIC").describe("Payload format"),
|
|
4414
|
+
secret: z12.string().optional().describe("HMAC-SHA256 signing secret"),
|
|
4415
|
+
isActive: z12.boolean().optional().default(true).describe("Whether the subscription is active")
|
|
4042
4416
|
}
|
|
4043
4417
|
},
|
|
4044
4418
|
async ({ ...body }) => callApi("POST", "webhook-subscriptions", { body })
|
|
@@ -4049,7 +4423,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
4049
4423
|
title: "Delete Webhook Subscription",
|
|
4050
4424
|
description: "Delete a webhook subscription by ID.",
|
|
4051
4425
|
inputSchema: {
|
|
4052
|
-
id:
|
|
4426
|
+
id: z12.string().uuid().describe("Webhook subscription ID")
|
|
4053
4427
|
}
|
|
4054
4428
|
},
|
|
4055
4429
|
async ({ id }) => callApi("DELETE", `webhook-subscriptions/${id}`)
|
|
@@ -4060,7 +4434,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
4060
4434
|
title: "Test Webhook Subscription",
|
|
4061
4435
|
description: "Send a test event to verify webhook connectivity. Returns the HTTP status code and success/failure message.",
|
|
4062
4436
|
inputSchema: {
|
|
4063
|
-
id:
|
|
4437
|
+
id: z12.string().uuid().describe("Webhook subscription ID")
|
|
4064
4438
|
}
|
|
4065
4439
|
},
|
|
4066
4440
|
async ({ id }) => callApi("POST", `webhook-subscriptions/${id}/test`)
|
|
@@ -4068,7 +4442,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
4068
4442
|
}
|
|
4069
4443
|
|
|
4070
4444
|
// src/mcp/tools/domain-templates.ts
|
|
4071
|
-
import { z as
|
|
4445
|
+
import { z as z13 } from "zod";
|
|
4072
4446
|
function registerDomainTemplateTools(server, callApi) {
|
|
4073
4447
|
server.registerTool(
|
|
4074
4448
|
"lexq_domain_templates_list",
|
|
@@ -4085,7 +4459,7 @@ function registerDomainTemplateTools(server, callApi) {
|
|
|
4085
4459
|
title: "Preview Domain Template",
|
|
4086
4460
|
description: "Preview exactly what a domain template will provision before applying it: the fact definitions it registers, the sample rules it creates, and an apply plan. This is a read-only dry run \u2014 nothing is created. Only ACTIVE templates can be previewed.",
|
|
4087
4461
|
inputSchema: {
|
|
4088
|
-
template:
|
|
4462
|
+
template: z13.string().describe(
|
|
4089
4463
|
"Domain template key (e.g. ECOMMERCE). Use lexq_domain_templates_list to see available keys \u2014 currently only ECOMMERCE is ACTIVE."
|
|
4090
4464
|
)
|
|
4091
4465
|
}
|
|
@@ -4098,8 +4472,8 @@ function registerDomainTemplateTools(server, callApi) {
|
|
|
4098
4472
|
title: "Apply Domain Template",
|
|
4099
4473
|
description: "Apply a domain template to the current tenant. Creates the template's fact definitions and a new policy group pre-populated with its sample rules as a DRAFT version. Existing facts are skipped \u2014 apply is additive and never overwrites existing schema. Run lexq_domain_templates_preview first to review what will be created. Only ACTIVE templates can be applied.",
|
|
4100
4474
|
inputSchema: {
|
|
4101
|
-
template:
|
|
4102
|
-
customName:
|
|
4475
|
+
template: z13.string().describe("Domain template key to apply (e.g. ECOMMERCE)."),
|
|
4476
|
+
customName: z13.string().optional().describe(
|
|
4103
4477
|
"Optional custom name for the policy group that gets created. If omitted, the template's default name is used."
|
|
4104
4478
|
)
|
|
4105
4479
|
}
|
|
@@ -4121,7 +4495,9 @@ function registerAllTools(server, callApi) {
|
|
|
4121
4495
|
registerFactTools(server, callApi);
|
|
4122
4496
|
registerDeployTools(server, callApi);
|
|
4123
4497
|
registerAnalyticsTools(server, callApi);
|
|
4498
|
+
registerReplayTools(server, callApi);
|
|
4124
4499
|
registerHistoryTools(server, callApi);
|
|
4500
|
+
registerProvenanceTools(server, callApi);
|
|
4125
4501
|
registerIntegrationTools(server, callApi);
|
|
4126
4502
|
registerLogTools(server, callApi);
|
|
4127
4503
|
registerDomainTemplateTools(server, callApi);
|
|
@@ -4153,12 +4529,12 @@ async function startMcpServer() {
|
|
|
4153
4529
|
function registerServeCommand(program) {
|
|
4154
4530
|
program.command("serve").description("Start LexQ as a server for AI agent integrations").option("--mcp", "Start as MCP (Model Context Protocol) server over stdio").addHelpText(
|
|
4155
4531
|
"after",
|
|
4156
|
-
|
|
4532
|
+
dedent17`
|
|
4157
4533
|
|
|
4158
4534
|
Example:
|
|
4159
4535
|
$ lexq serve --mcp
|
|
4160
4536
|
|
|
4161
|
-
Starts a stdio MCP server that exposes
|
|
4537
|
+
Starts a stdio MCP server that exposes the full LexQ toolset for policy management.
|
|
4162
4538
|
Used by Claude Desktop, Claude.ai, Cursor, and other MCP-compatible clients.
|
|
4163
4539
|
|
|
4164
4540
|
Claude Desktop config (~/.claude/claude_desktop_config.json):
|
|
@@ -4184,11 +4560,11 @@ function registerServeCommand(program) {
|
|
|
4184
4560
|
|
|
4185
4561
|
// src/commands/domain-templates.ts
|
|
4186
4562
|
import "commander";
|
|
4187
|
-
import
|
|
4563
|
+
import dedent18 from "dedent";
|
|
4188
4564
|
function registerDomainTemplateCommands(program) {
|
|
4189
4565
|
const templates = program.command("domain-templates").description("Browse and apply domain templates").addHelpText(
|
|
4190
4566
|
"after",
|
|
4191
|
-
|
|
4567
|
+
dedent18`
|
|
4192
4568
|
|
|
4193
4569
|
A domain template is an industry-specific starter pack of fact
|
|
4194
4570
|
definitions and sample rules. Applying one provisions a ready-to-use
|
|
@@ -4236,7 +4612,7 @@ function registerDomainTemplateCommands(program) {
|
|
|
4236
4612
|
});
|
|
4237
4613
|
templates.command("preview").description("Preview what a domain template provisions").requiredOption("--template <key>", "Domain template key (e.g. ECOMMERCE)").addHelpText(
|
|
4238
4614
|
"after",
|
|
4239
|
-
|
|
4615
|
+
dedent18`
|
|
4240
4616
|
|
|
4241
4617
|
Read-only dry run — shows the fact definitions and sample rules the
|
|
4242
4618
|
template will create. Nothing is provisioned.
|
|
@@ -4265,7 +4641,7 @@ function registerDomainTemplateCommands(program) {
|
|
|
4265
4641
|
});
|
|
4266
4642
|
templates.command("apply").description("Apply a domain template to the current tenant").requiredOption("--template <key>", "Domain template key (e.g. ECOMMERCE)").option("--name <n>", "Custom name for the policy group that gets created").option("--force", "Skip confirmation prompt").addHelpText(
|
|
4267
4643
|
"after",
|
|
4268
|
-
|
|
4644
|
+
dedent18`
|
|
4269
4645
|
|
|
4270
4646
|
Creates the template's fact definitions and a new DRAFT policy group
|
|
4271
4647
|
populated with its sample rules. Existing facts are skipped — apply is
|
|
@@ -4327,7 +4703,10 @@ function createCli() {
|
|
|
4327
4703
|
const program = new Command();
|
|
4328
4704
|
program.name("lexq").description(
|
|
4329
4705
|
"Command-line interface for the LexQ Decision Operations Platform. Manage policies, run Impact Simulation, and deploy from your terminal."
|
|
4330
|
-
).version(getVersion2(), "-V, --version").option("--format <format>", "Output format: json or table", "json").option("--api-key <key>", "Override stored API key").option(
|
|
4706
|
+
).version(getVersion2(), "-V, --version").option("--format <format>", "Output format: json or table", "json").option("--api-key <key>", "Override stored API key").option(
|
|
4707
|
+
"--base-url <url>",
|
|
4708
|
+
"Override API base URL \u2014 must include the API prefix, e.g. http://localhost:8080/api/v1/partners"
|
|
4709
|
+
).option("--dry-run", "Preview the HTTP request without executing").option("--verbose", "Show request/response details").option("--no-color", "Disable colored output");
|
|
4331
4710
|
registerAuthCommands(program);
|
|
4332
4711
|
registerStatusCommand(program);
|
|
4333
4712
|
registerGroupCommands(program);
|
|
@@ -4338,6 +4717,8 @@ function createCli() {
|
|
|
4338
4717
|
registerDeployCommands(program);
|
|
4339
4718
|
registerAnalyticsCommands(program);
|
|
4340
4719
|
registerHistoryCommands(program);
|
|
4720
|
+
registerReplayCommands(program);
|
|
4721
|
+
registerProvenanceCommands(program);
|
|
4341
4722
|
registerIntegrationCommands(program);
|
|
4342
4723
|
registerLogCommands(program);
|
|
4343
4724
|
registerWebhookSubscriptionCommands(program);
|