@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 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/integrations.ts
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
- dedent10`
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
- dedent10`
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
- dedent10`
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
- dedent10`
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 dedent11 from "dedent";
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
- dedent11`
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
- dedent11`
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
- dedent11`
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
- dedent11`
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
- dedent11`
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 dedent12 from "dedent";
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
- dedent12`
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
- dedent12`
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
- dedent12`
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
- dedent12`
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 dedent15 from "dedent";
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 config = loadConfig();
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 dedent13 from "dedent";
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: dedent13`
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 required status. Always check this before creating rules.",
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 description changes.",
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 dedent14 from "dedent";
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: dedent14`
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: dedent14`
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: dedent14`
3669
- Start an Impact Simulation against historical or uploaded data.
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 body:
3921
+ Example (uploaded dataset):
3679
3922
  {
3680
3923
  "policyVersionId": "<uuid>",
3681
- "dataset": { "type": "HISTORICAL", "source": "EXECUTION_LOGS", "from": "2025-01-01", "to": "2025-01-31" },
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: dedent14`
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
- Use this path in simulation start with dataset type UPLOADED.
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 }) => callApi("POST", "analytics/datasets/upload", {
3773
- upload: { content, filename, fieldName: "file" }
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/history.ts
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: z7.number().int().min(0).default(0).describe("Page number"),
3803
- size: z7.number().int().min(1).max(100).default(20).describe("Page size"),
3804
- traceId: z7.string().optional().describe("Filter by trace ID"),
3805
- groupId: z7.string().uuid().optional().describe("Filter by policy group"),
3806
- versionId: z7.string().uuid().optional().describe("Filter by version"),
3807
- status: z7.enum(["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"]).optional().describe("Filter by execution status"),
3808
- startDate: z7.string().optional().describe("Start date (yyyy-MM-dd)"),
3809
- endDate: z7.string().optional().describe("End date (yyyy-MM-dd)")
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: z7.string().describe("Trace ID from execution history")
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: z7.string().uuid().optional().describe("Filter by policy group"),
3841
- startDate: z7.string().optional().describe("Start date (yyyy-MM-dd)"),
3842
- endDate: z7.string().optional().describe("End date (yyyy-MM-dd)")
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 z8 } from "zod";
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: z8.number().int().min(0).default(0).describe("Page number"),
3865
- size: z8.number().int().min(1).max(100).default(20).describe("Page size"),
3866
- type: z8.enum(["COUPON", "POINT", "NOTIFICATION", "CRM", "MESSENGER", "WEBHOOK"]).optional().describe("Filter by integration 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: z8.string().uuid().describe("Integration ID")
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: z8.string().uuid().optional().describe("Integration ID (omit to create, provide to update)"),
3893
- type: z8.enum(["COUPON", "POINT", "NOTIFICATION", "CRM", "MESSENGER", "WEBHOOK"]).describe("Integration type"),
3894
- name: z8.string().describe("Integration name"),
3895
- baseUrl: z8.string().describe("Base URL of the external service"),
3896
- credential: z8.string().optional().describe("API key or token for the service"),
3897
- additionalConfig: z8.string().optional().describe("JSON string of additional config key-value pairs"),
3898
- isActive: z8.boolean().default(true).describe("Whether the integration is active")
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: z8.string().uuid().describe("Integration ID")
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 z9 } from "zod";
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: z9.number().int().min(0).default(0).describe("Page number"),
3939
- size: z9.number().int().min(1).max(100).default(20).describe("Page size"),
3940
- category: z9.enum(TaskCategory).optional().describe("Task category"),
3941
- taskType: z9.enum(TaskType).optional().describe("Task type"),
3942
- status: z9.enum(FailureStatus).optional().describe("Log status"),
3943
- keyword: z9.string().optional().describe("Search in refId, refSubId, errorMessage"),
3944
- startDate: z9.string().optional().describe("Start date (yyyy-MM-dd)"),
3945
- endDate: z9.string().optional().describe("End date (yyyy-MM-dd)")
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: z9.string().uuid().describe("Failure log ID")
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: z9.string().uuid().describe("Failure log ID"),
3977
- action: z9.enum(FailureAction).describe("Action to take")
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: z9.array(z9.string().uuid()).describe("Array of failure log IDs"),
3991
- action: z9.enum(FailureAction).describe("Action to apply to all logs")
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 z10 } from "zod";
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: z10.number().int().min(0).default(0).describe("Page number"),
4010
- size: z10.number().int().min(1).max(100).default(20).describe("Page 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: z10.string().uuid().describe("Webhook subscription 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: z10.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
4036
- name: z10.string().min(1).describe("Subscription name (unique per tenant)"),
4037
- webhookUrl: z10.string().url().describe("Webhook endpoint URL"),
4038
- subscribedEvents: z10.array(z10.enum(PlatformEventType)).min(1).describe("Events to subscribe to"),
4039
- payloadFormat: z10.enum(WebhookPayloadFormat).optional().default("GENERIC").describe("Payload format"),
4040
- secret: z10.string().optional().describe("HMAC-SHA256 signing secret"),
4041
- isActive: z10.boolean().optional().default(true).describe("Whether the subscription is active")
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: z10.string().uuid().describe("Webhook subscription 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: z10.string().uuid().describe("Webhook subscription 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 z11 } from "zod";
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: z11.string().describe(
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: z11.string().describe("Domain template key to apply (e.g. ECOMMERCE)."),
4102
- customName: z11.string().optional().describe(
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
- dedent15`
4532
+ dedent17`
4157
4533
 
4158
4534
  Example:
4159
4535
  $ lexq serve --mcp
4160
4536
 
4161
- Starts a stdio MCP server that exposes 60 tools for policy management.
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 dedent16 from "dedent";
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
- dedent16`
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
- dedent16`
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
- dedent16`
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("--base-url <url>", "Override API base URL").option("--dry-run", "Preview the HTTP request without executing").option("--verbose", "Show request/response details").option("--no-color", "Disable colored output");
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);