@lexq/cli 0.1.35 → 0.1.37

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
 
@@ -2180,13 +2183,140 @@ function resolveBody(opts) {
2180
2183
  return {};
2181
2184
  }
2182
2185
 
2183
- // src/commands/history.ts
2186
+ // src/commands/profile.ts
2184
2187
  import "commander";
2185
2188
  import dedent9 from "dedent";
2189
+
2190
+ // src/types/enums.ts
2191
+ var ProfileCacheState = ["HIT", "MISS"];
2192
+ var FailureStatus = ["PENDING", "RESOLVED", "IGNORED"];
2193
+ var FailureAction = ["RETRY", "IGNORE", "RESOLVE"];
2194
+ var TaskCategory = ["INTEGRATION", "INTERNAL"];
2195
+ var TaskType = [
2196
+ // Integration
2197
+ "COUPON_ISSUE",
2198
+ "COUPON_CANCEL",
2199
+ "POINT_EARN",
2200
+ "POINT_USE",
2201
+ "POINT_REFUND",
2202
+ "NOTIFICATION_SEND",
2203
+ "CRM_SYNC_USER",
2204
+ "CRM_ADD_TAG",
2205
+ "WEBHOOK_EXECUTE",
2206
+ // Internal
2207
+ "IMAGE_PROCESSING",
2208
+ "DAILY_SETTLEMENT",
2209
+ "PLATFORM_WEBHOOK"
2210
+ ];
2211
+ var PlatformEventType = [
2212
+ "VERSION_PUBLISHED",
2213
+ "DEPLOYED",
2214
+ "ROLLED_BACK",
2215
+ "UNDEPLOYED"
2216
+ ];
2217
+ var WebhookPayloadFormat = ["GENERIC", "SLACK"];
2218
+
2219
+ // src/commands/profile.ts
2220
+ var ms = (nanos) => nanos == null ? "\u2013" : (nanos / 1e6).toFixed(2);
2221
+ var msWithUnit = (nanos) => nanos == null ? "\u2013" : `${(nanos / 1e6).toFixed(2)}ms`;
2222
+ function registerProfileCommands(program) {
2223
+ program.command("profile <groupId>").description("Per-rule latency profile with relative slow-rule flags").option("--rule <ruleId>", "Single-rule detail (distributions + 60s window series)").option("--version <versionId>", "Version to inspect (default: live version)").option("--from <instant>", "Window start, ISO-8601 instant (default: 24h ago)").option("--to <instant>", "Window end, ISO-8601 instant (default: now)").option("--cache <state>", "Cache dimension for the rule table: HIT | MISS (default: HIT)").addHelpText(
2224
+ "after",
2225
+ dedent9`
2226
+
2227
+ Slow-rule judgment is relative only: flagged = p50 ≥ 10× the median of
2228
+ per-rule p50s within the group. Absolute ms thresholds are intentionally
2229
+ not supported. Percentiles are withheld (–) when n < 100; TOTAL is
2230
+ recorded for every call, rule detail from a deterministic 1% sample.
2231
+
2232
+ Examples:
2233
+ $ lexq profile <groupId>
2234
+ $ lexq profile <groupId> --cache MISS --from 2026-07-01T00:00:00Z
2235
+ $ lexq profile <groupId> --rule <ruleId> --version <versionId>
2236
+ `
2237
+ ).action(async (groupId, opts) => {
2238
+ try {
2239
+ const globalOpts = program.opts();
2240
+ const format = globalOpts.format ?? "json";
2241
+ if (opts.cache && !ProfileCacheState.includes(opts.cache)) {
2242
+ throw new Error(`--cache must be one of: ${ProfileCacheState.join(" | ")}`);
2243
+ }
2244
+ const params = {};
2245
+ if (opts.version) params.versionId = opts.version;
2246
+ if (opts.from) params.from = opts.from;
2247
+ if (opts.to) params.to = opts.to;
2248
+ if (opts.cache) params.cacheState = opts.cache;
2249
+ const clientOpts = {
2250
+ apiKey: globalOpts.apiKey,
2251
+ baseUrl: globalOpts.baseUrl,
2252
+ dryRun: globalOpts.dryRun,
2253
+ verbose: globalOpts.verbose
2254
+ };
2255
+ if (opts.rule) {
2256
+ const data2 = await apiRequest(
2257
+ "GET",
2258
+ `policy-groups/${groupId}/profile/rules/${opts.rule}`,
2259
+ { ...clientOpts, params }
2260
+ );
2261
+ if (format === "table") {
2262
+ console.error("note: --rule detail is nested; printing JSON (table not supported)");
2263
+ }
2264
+ printJson(data2);
2265
+ return;
2266
+ }
2267
+ const data = await apiRequest("GET", `policy-groups/${groupId}/profile`, {
2268
+ ...clientOpts,
2269
+ params
2270
+ });
2271
+ if (format !== "table") {
2272
+ printJson(data);
2273
+ return;
2274
+ }
2275
+ console.log(`window : ${data.from} ~ ${data.to} (cache: ${data.ruleCacheState})`);
2276
+ console.log(`version : ${data.policyVersionId ?? "\u2013 (no live version)"}`);
2277
+ for (const s of data.summary) {
2278
+ const t = s.total;
2279
+ console.log(
2280
+ `TOTAL ${s.cacheState.padEnd(4)}: n=${t.n} p50=${msWithUnit(t.p50Nanos)} p95=${msWithUnit(t.p95Nanos)} p99=${msWithUnit(t.p99Nanos)}`
2281
+ );
2282
+ }
2283
+ for (const b of data.baselines) {
2284
+ const base = b.status === "OK" ? `${msWithUnit(b.baselineP50Nanos)} (cohort ${b.cohortSize})` : `\u2013 (${b.status}, cohort ${b.cohortSize})`;
2285
+ console.log(`baseline ${b.phase.padEnd(9)}: ${base}`);
2286
+ }
2287
+ if (data.droppedRows > 0) {
2288
+ console.log(`\u26A0 droppedRows=${data.droppedRows} (corrupt histogram rows skipped)`);
2289
+ }
2290
+ printTable(
2291
+ ["Rule", "Phase", "n", "p50(ms)", "p95(ms)", "p99(ms)", "\xD7base", "Flagged"],
2292
+ data.rules.flatMap(
2293
+ (rule) => rule.phases.map((p) => [
2294
+ rule.ruleId.substring(0, 12),
2295
+ p.phase,
2296
+ String(p.stats.n),
2297
+ ms(p.stats.p50Nanos),
2298
+ ms(p.stats.p95Nanos),
2299
+ ms(p.stats.p99Nanos),
2300
+ p.baselineMultiple == null ? "\u2013" : `${p.baselineMultiple.toFixed(1)}\xD7`,
2301
+ p.flagged ? "YES" : ""
2302
+ ])
2303
+ ),
2304
+ { truncate: 24 }
2305
+ );
2306
+ } catch (error) {
2307
+ printError(error);
2308
+ process.exit(1);
2309
+ }
2310
+ });
2311
+ }
2312
+
2313
+ // src/commands/history.ts
2314
+ import "commander";
2315
+ import dedent10 from "dedent";
2186
2316
  function registerHistoryCommands(program) {
2187
2317
  const history = program.command("history").description("Execution history").addHelpText(
2188
2318
  "after",
2189
- dedent9`
2319
+ dedent10`
2190
2320
 
2191
2321
  View and analyze policy execution logs from production traffic.
2192
2322
 
@@ -2200,7 +2330,7 @@ function registerHistoryCommands(program) {
2200
2330
  );
2201
2331
  history.command("list").description("List execution history").option("--trace-id <traceId>", "Filter by trace ID").option("--group-id <groupId>", "Filter by policy group").option("--version-id <versionId>", "Filter by version").option("--status <status>", "Filter by status (SUCCESS, NO_MATCH, ERROR, TIMEOUT)").option("--start-date <date>", "Start 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(
2202
2332
  "after",
2203
- dedent9`
2333
+ dedent10`
2204
2334
 
2205
2335
  Examples:
2206
2336
  $ lexq history list --status ERROR --format table
@@ -2254,7 +2384,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2254
2384
  });
2255
2385
  history.command("get").description("Get execution detail").requiredOption("--id <traceId>", "Trace ID").addHelpText(
2256
2386
  "after",
2257
- dedent9`
2387
+ dedent10`
2258
2388
 
2259
2389
  Returns the full execution detail including request facts, result traces,
2260
2390
  and decision traces (SELECTED, NO_MATCH, BLOCKED, etc.).
@@ -2280,7 +2410,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2280
2410
  });
2281
2411
  history.command("stats").description("Get execution statistics").option("--group-id <groupId>", "Filter by policy group").option("--start-date <date>", "Start date (yyyy-MM-dd)").option("--end-date <date>", "End date (yyyy-MM-dd)").addHelpText(
2282
2412
  "after",
2283
- dedent9`
2413
+ dedent10`
2284
2414
 
2285
2415
  Shows total executions, success/no-match/failure counts, success rate, and avg latency.
2286
2416
 
@@ -2329,11 +2459,11 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2329
2459
 
2330
2460
  // src/commands/replay.ts
2331
2461
  import "commander";
2332
- import dedent10 from "dedent";
2462
+ import dedent11 from "dedent";
2333
2463
  function registerReplayCommands(program) {
2334
2464
  const replay = program.command("replay").description("Decision Replay").addHelpText(
2335
2465
  "after",
2336
- dedent10`
2466
+ dedent11`
2337
2467
 
2338
2468
  Re-evaluate past production executions against a candidate version.
2339
2469
 
@@ -2349,7 +2479,7 @@ function registerReplayCommands(program) {
2349
2479
  );
2350
2480
  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(
2351
2481
  "after",
2352
- dedent10`
2482
+ dedent11`
2353
2483
 
2354
2484
  Free of charge (TPS throttle only). Returns decisionChanged, effect
2355
2485
  changes, fired rules on both sides, and a determinism verdict.
@@ -2375,7 +2505,7 @@ function registerReplayCommands(program) {
2375
2505
  });
2376
2506
  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(
2377
2507
  "after",
2378
- dedent10`
2508
+ dedent11`
2379
2509
 
2380
2510
  Billed per replayed record (REPLAY metric). Poll with "lexq replay get".
2381
2511
 
@@ -2473,11 +2603,11 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2473
2603
 
2474
2604
  // src/commands/provenance.ts
2475
2605
  import "commander";
2476
- import dedent11 from "dedent";
2606
+ import dedent12 from "dedent";
2477
2607
  function registerProvenanceCommands(program) {
2478
2608
  const provenance = program.command("provenance").description("Decision Provenance").addHelpText(
2479
2609
  "after",
2480
- dedent11`
2610
+ dedent12`
2481
2611
 
2482
2612
  Trace who authored, published, and deployed the rules behind a decision.
2483
2613
 
@@ -2503,7 +2633,7 @@ function registerProvenanceCommands(program) {
2503
2633
  });
2504
2634
  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(
2505
2635
  "after",
2506
- dedent11`
2636
+ dedent12`
2507
2637
 
2508
2638
  Metadata only — revealed values are never stored or returned.
2509
2639
 
@@ -2556,11 +2686,11 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2556
2686
 
2557
2687
  // src/commands/integrations.ts
2558
2688
  import "commander";
2559
- import dedent12 from "dedent";
2689
+ import dedent13 from "dedent";
2560
2690
  function registerIntegrationCommands(program) {
2561
2691
  const integrations = program.command("integrations").description("Manage external integrations").addHelpText(
2562
2692
  "after",
2563
- dedent12`
2693
+ dedent13`
2564
2694
 
2565
2695
  Integrations connect rule actions to external services (webhooks, coupons,
2566
2696
  points, notifications, CRM, messengers).
@@ -2630,7 +2760,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2630
2760
  });
2631
2761
  integrations.command("save").description("Create or update an integration").requiredOption("--json <body>", "Request body as JSON string").addHelpText(
2632
2762
  "after",
2633
- dedent12`
2763
+ dedent13`
2634
2764
 
2635
2765
  Examples:
2636
2766
  # Create
@@ -2680,7 +2810,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2680
2810
  });
2681
2811
  integrations.command("delete").description("Delete an integration").requiredOption("--id <integrationId>", "Integration ID").option("--force", "Skip confirmation prompt").addHelpText(
2682
2812
  "after",
2683
- dedent12`
2813
+ dedent13`
2684
2814
 
2685
2815
  Rules referencing this integration will fail at execution time.
2686
2816
  Use --force to skip confirmation.
@@ -2712,7 +2842,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2712
2842
  });
2713
2843
  integrations.command("config-spec").description("Get integration configuration field specs").addHelpText(
2714
2844
  "after",
2715
- dedent12`
2845
+ dedent13`
2716
2846
 
2717
2847
  Shows required and optional configuration fields for each integration type.
2718
2848
 
@@ -2738,41 +2868,11 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2738
2868
 
2739
2869
  // src/commands/logs.ts
2740
2870
  import "commander";
2741
- import dedent13 from "dedent";
2742
-
2743
- // src/types/enums.ts
2744
- var FailureStatus = ["PENDING", "RESOLVED", "IGNORED"];
2745
- var FailureAction = ["RETRY", "IGNORE", "RESOLVE"];
2746
- var TaskCategory = ["INTEGRATION", "INTERNAL"];
2747
- var TaskType = [
2748
- // Integration
2749
- "COUPON_ISSUE",
2750
- "COUPON_CANCEL",
2751
- "POINT_EARN",
2752
- "POINT_USE",
2753
- "POINT_REFUND",
2754
- "NOTIFICATION_SEND",
2755
- "CRM_SYNC_USER",
2756
- "CRM_ADD_TAG",
2757
- "WEBHOOK_EXECUTE",
2758
- // Internal
2759
- "IMAGE_PROCESSING",
2760
- "DAILY_SETTLEMENT",
2761
- "PLATFORM_WEBHOOK"
2762
- ];
2763
- var PlatformEventType = [
2764
- "VERSION_PUBLISHED",
2765
- "DEPLOYED",
2766
- "ROLLED_BACK",
2767
- "UNDEPLOYED"
2768
- ];
2769
- var WebhookPayloadFormat = ["GENERIC", "SLACK"];
2770
-
2771
- // src/commands/logs.ts
2871
+ import dedent14 from "dedent";
2772
2872
  function registerLogCommands(program) {
2773
2873
  const logs = program.command("logs").description("Failure logs").addHelpText(
2774
2874
  "after",
2775
- dedent13`
2875
+ dedent14`
2776
2876
 
2777
2877
  System failure logs (DLQ) for background tasks — webhook calls, coupon issuance,
2778
2878
  point operations, notifications, and platform event webhooks.
@@ -2789,7 +2889,7 @@ function registerLogCommands(program) {
2789
2889
  );
2790
2890
  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(
2791
2891
  "after",
2792
- dedent13`
2892
+ dedent14`
2793
2893
 
2794
2894
  Examples:
2795
2895
  $ lexq logs list --status PENDING --format table
@@ -2840,7 +2940,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2840
2940
  });
2841
2941
  logs.command("get").description("Get failure log detail").requiredOption("--id <logId>", "Log ID").addHelpText(
2842
2942
  "after",
2843
- dedent13`
2943
+ dedent14`
2844
2944
 
2845
2945
  Includes the full payload that was used for the failed operation.
2846
2946
  Use this to inspect what went wrong before deciding to RETRY or RESOLVE.
@@ -2862,7 +2962,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2862
2962
  });
2863
2963
  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(
2864
2964
  "after",
2865
- dedent13`
2965
+ dedent14`
2866
2966
 
2867
2967
  Actions:
2868
2968
  RETRY Re-execute the failed operation with the original payload
@@ -2894,7 +2994,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2894
2994
  });
2895
2995
  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(
2896
2996
  "after",
2897
- dedent13`
2997
+ dedent14`
2898
2998
 
2899
2999
  Processes each log individually. Failures are skipped with a warning.
2900
3000
 
@@ -2922,11 +3022,11 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2922
3022
 
2923
3023
  // src/commands/webhook-subscriptions.ts
2924
3024
  import "commander";
2925
- import dedent14 from "dedent";
3025
+ import dedent15 from "dedent";
2926
3026
  function registerWebhookSubscriptionCommands(program) {
2927
3027
  const webhooks = program.command("webhook-subscriptions").description("Manage platform event webhook subscriptions").addHelpText(
2928
3028
  "after",
2929
- dedent14`
3029
+ dedent15`
2930
3030
 
2931
3031
  Receive notifications when deployment lifecycle events occur
2932
3032
  (publish, deploy, rollback, undeploy).
@@ -3000,7 +3100,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
3000
3100
  });
3001
3101
  webhooks.command("save").description("Create or update a webhook subscription").requiredOption("--json <body>", "Request body as JSON string").addHelpText(
3002
3102
  "after",
3003
- dedent14`
3103
+ dedent15`
3004
3104
 
3005
3105
  Examples:
3006
3106
  # Create (Slack format)
@@ -3055,7 +3155,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
3055
3155
  });
3056
3156
  webhooks.command("delete").description("Delete a webhook subscription").requiredOption("--id <subscriptionId>", "Subscription ID").option("--force", "Skip confirmation prompt").addHelpText(
3057
3157
  "after",
3058
- dedent14`
3158
+ dedent15`
3059
3159
 
3060
3160
  Use --force to skip the confirmation prompt.
3061
3161
 
@@ -3089,7 +3189,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
3089
3189
  });
3090
3190
  webhooks.command("test").description("Send a test event to verify webhook connectivity").requiredOption("--id <subscriptionId>", "Subscription ID").addHelpText(
3091
3191
  "after",
3092
- dedent14`
3192
+ dedent15`
3093
3193
 
3094
3194
  Sends a test event to the webhook URL and reports the HTTP status code.
3095
3195
  Does not record failures in the failure log.
@@ -3125,7 +3225,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
3125
3225
 
3126
3226
  // src/commands/serve.ts
3127
3227
  import "commander";
3128
- import dedent17 from "dedent";
3228
+ import dedent18 from "dedent";
3129
3229
 
3130
3230
  // src/mcp/server.ts
3131
3231
  import { readFileSync as readFileSync3 } from "fs";
@@ -3138,7 +3238,12 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
3138
3238
  function createCallApiFromConfig() {
3139
3239
  return async (method, path, opts) => {
3140
3240
  try {
3141
- const config = loadConfig();
3241
+ const stored = loadConfig();
3242
+ const config = {
3243
+ ...stored,
3244
+ baseUrl: process.env.PARTNER_BASE_URL ?? stored.baseUrl,
3245
+ apiKey: process.env.LEXQ_API_KEY ?? stored.apiKey
3246
+ };
3142
3247
  if (opts?.upload) {
3143
3248
  const url = new URL(
3144
3249
  path,
@@ -3447,7 +3552,7 @@ function registerVersionTools(server, callApi) {
3447
3552
 
3448
3553
  // src/mcp/tools/rules.ts
3449
3554
  import { z as z3 } from "zod";
3450
- import dedent15 from "dedent";
3555
+ import dedent16 from "dedent";
3451
3556
  function registerRuleTools(server, callApi) {
3452
3557
  server.registerTool(
3453
3558
  "lexq_rules_list",
@@ -3478,7 +3583,7 @@ function registerRuleTools(server, callApi) {
3478
3583
  "lexq_rules_create",
3479
3584
  {
3480
3585
  title: "Create Rule",
3481
- description: dedent15`
3586
+ description: dedent16`
3482
3587
  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.
3483
3588
 
3484
3589
  Before creating rules with new fact keys, call lexq_facts_list to check existing facts.
@@ -3611,7 +3716,7 @@ function registerFactTools(server, callApi) {
3611
3716
  "lexq_facts_list",
3612
3717
  {
3613
3718
  title: "List Fact Definitions",
3614
- description: "List all fact definitions (input variable schema). Shows key, type, and required status. Always check this before creating rules.",
3719
+ description: "List all fact definitions (input variable schema). Shows key, type, required, and PII status. Always check this before creating rules.",
3615
3720
  inputSchema: {
3616
3721
  page: z4.number().int().min(0).default(0).describe("Page number"),
3617
3722
  size: z4.number().int().min(1).max(100).default(20).describe("Page size"),
@@ -3634,7 +3739,10 @@ function registerFactTools(server, callApi) {
3634
3739
  name: z4.string().describe("Display name"),
3635
3740
  type: z4.enum(["STRING", "NUMBER", "BOOLEAN", "LIST_STRING", "LIST_NUMBER"]).describe("Value type"),
3636
3741
  description: z4.string().optional().describe("Description"),
3637
- isRequired: z4.boolean().default(false).describe("Whether this fact is required for rule evaluation")
3742
+ isRequired: z4.boolean().default(false).describe("Whether this fact is required for rule evaluation"),
3743
+ isPii: z4.boolean().default(false).describe(
3744
+ "Mark as PII \u2014 masked on every read surface, revealable only in the console (audited)"
3745
+ )
3638
3746
  }
3639
3747
  },
3640
3748
  async (args) => callApi("POST", "schema/facts", { body: args })
@@ -3643,12 +3751,13 @@ function registerFactTools(server, callApi) {
3643
3751
  "lexq_facts_update",
3644
3752
  {
3645
3753
  title: "Update Fact Definition",
3646
- description: "Update a fact definition. Key and type cannot be changed. Only provided fields are updated. System facts only allow name and description changes.",
3754
+ 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.",
3647
3755
  inputSchema: {
3648
3756
  factId: z4.string().uuid().describe("Fact definition ID"),
3649
3757
  name: z4.string().optional().describe("Display name"),
3650
3758
  description: z4.string().optional().describe("Description"),
3651
- isRequired: z4.boolean().optional().describe("Required flag")
3759
+ isRequired: z4.boolean().optional().describe("Required flag"),
3760
+ isPii: z4.boolean().optional().describe("PII flag \u2014 enables/disables masking (changeable even on system facts)")
3652
3761
  }
3653
3762
  },
3654
3763
  async ({ factId, ...body }) => callApi("PUT", `schema/facts/${factId}`, { body })
@@ -3820,13 +3929,13 @@ function registerDeployTools(server, callApi) {
3820
3929
 
3821
3930
  // src/mcp/tools/analytics.ts
3822
3931
  import { z as z6 } from "zod";
3823
- import dedent16 from "dedent";
3932
+ import dedent17 from "dedent";
3824
3933
  function registerAnalyticsTools(server, callApi) {
3825
3934
  server.registerTool(
3826
3935
  "lexq_dry_run",
3827
3936
  {
3828
3937
  title: "Dry Run",
3829
- description: dedent16`
3938
+ description: dedent17`
3830
3939
  Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
3831
3940
 
3832
3941
  Returns:
@@ -3856,7 +3965,7 @@ function registerAnalyticsTools(server, callApi) {
3856
3965
  "lexq_dry_run_compare",
3857
3966
  {
3858
3967
  title: "Dry Run Compare",
3859
- description: dedent16`
3968
+ description: dedent17`
3860
3969
  Compare dry run results between two versions using the same input facts. Useful for validating changes.
3861
3970
 
3862
3971
  Returns:
@@ -3892,7 +4001,7 @@ function registerAnalyticsTools(server, callApi) {
3892
4001
  "lexq_simulation_start",
3893
4002
  {
3894
4003
  title: "Start Simulation",
3895
- description: dedent16`
4004
+ description: dedent17`
3896
4005
  Start an Impact Simulation against historical, uploaded, or inline data.
3897
4006
 
3898
4007
  dataset.type and dataset.source are BOTH required, and must be paired:
@@ -3990,7 +4099,7 @@ function registerAnalyticsTools(server, callApi) {
3990
4099
  "lexq_dataset_upload",
3991
4100
  {
3992
4101
  title: "Upload Dataset",
3993
- description: dedent16`
4102
+ description: dedent17`
3994
4103
  Upload inline CSV or JSON content as a simulation dataset.
3995
4104
  The content is uploaded to S3 and a path is returned in the "path" field.
3996
4105
 
@@ -4046,8 +4155,56 @@ function registerAnalyticsTools(server, callApi) {
4046
4155
  );
4047
4156
  }
4048
4157
 
4049
- // src/mcp/tools/replay.ts
4158
+ // src/mcp/tools/profile.ts
4050
4159
  import { z as z7 } from "zod";
4160
+ var RELATIVE_THRESHOLD = "flagged = p50 \u2265 10\xD7 median of per-rule p50s within the group; absolute thresholds are intentionally not supported.";
4161
+ function profileParams(opts) {
4162
+ const params = {};
4163
+ if (opts.versionId) params.versionId = opts.versionId;
4164
+ if (opts.from) params.from = opts.from;
4165
+ if (opts.to) params.to = opts.to;
4166
+ if (opts.cacheState) params.cacheState = opts.cacheState;
4167
+ return params;
4168
+ }
4169
+ function registerProfileTools(server, callApi) {
4170
+ server.registerTool(
4171
+ "lexq_profile_overview",
4172
+ {
4173
+ title: "Group Latency Profile",
4174
+ description: "Per-rule latency profile of a policy group over a time window: group TOTAL distribution split by cache state (HIT = compiled ruleset cache hit, MISS = deep-load + compile), a per-rule CONDITION/ACTION percentile table, and slow-rule flags. " + RELATIVE_THRESHOLD + " Every percentile is accompanied by its sample count n; percentiles are withheld (null) when n < 100, and baselines report INSUFFICIENT_COHORT when fewer than 3 rules qualify. Rule detail comes from a deterministic 1% sample of calls; TOTAL is recorded for every call. Defaults: last 24h, live version, cacheState HIT.",
4175
+ inputSchema: {
4176
+ groupId: z7.string().uuid().describe("Policy group ID"),
4177
+ versionId: z7.string().uuid().optional().describe("Version to inspect (default: live version)"),
4178
+ from: z7.string().optional().describe("Window start, ISO-8601 instant (e.g. 2026-07-01T00:00:00Z). Default: 24h ago"),
4179
+ to: z7.string().optional().describe("Window end, ISO-8601 instant. Default: now"),
4180
+ cacheState: z7.enum(["HIT", "MISS"]).optional().describe("Cache dimension for the rule table and judgment (default: HIT)")
4181
+ }
4182
+ },
4183
+ async ({ groupId, versionId, from, to, cacheState }) => callApi("GET", `policy-groups/${groupId}/profile`, {
4184
+ params: profileParams({ versionId, from, to, cacheState })
4185
+ })
4186
+ );
4187
+ server.registerTool(
4188
+ "lexq_profile_rule",
4189
+ {
4190
+ title: "Rule Latency Detail",
4191
+ description: "Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (60s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) when n < 100. " + RELATIVE_THRESHOLD,
4192
+ inputSchema: {
4193
+ groupId: z7.string().uuid().describe("Policy group ID"),
4194
+ ruleId: z7.string().uuid().describe("Rule ID (from lexq_profile_overview)"),
4195
+ versionId: z7.string().uuid().optional().describe("Version to inspect (default: live version)"),
4196
+ from: z7.string().optional().describe("Window start, ISO-8601 instant. Default: 24h ago"),
4197
+ to: z7.string().optional().describe("Window end, ISO-8601 instant. Default: now")
4198
+ }
4199
+ },
4200
+ async ({ groupId, ruleId, versionId, from, to }) => callApi("GET", `policy-groups/${groupId}/profile/rules/${ruleId}`, {
4201
+ params: profileParams({ versionId, from, to })
4202
+ })
4203
+ );
4204
+ }
4205
+
4206
+ // src/mcp/tools/replay.ts
4207
+ import { z as z8 } from "zod";
4051
4208
  function registerReplayTools(server, callApi) {
4052
4209
  server.registerTool(
4053
4210
  "lexq_replay_decision",
@@ -4055,8 +4212,8 @@ function registerReplayTools(server, callApi) {
4055
4212
  title: "Replay a Decision",
4056
4213
  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.",
4057
4214
  inputSchema: {
4058
- traceId: z7.string().describe("Trace ID of the past execution to replay"),
4059
- candidateVersionId: z7.string().uuid().describe("Version to re-evaluate against")
4215
+ traceId: z8.string().describe("Trace ID of the past execution to replay"),
4216
+ candidateVersionId: z8.string().uuid().describe("Version to re-evaluate against")
4060
4217
  }
4061
4218
  },
4062
4219
  async ({ traceId, candidateVersionId }) => callApi("POST", "replay/decisions", { body: { traceId, candidateVersionId } })
@@ -4067,10 +4224,10 @@ function registerReplayTools(server, callApi) {
4067
4224
  title: "Start Window Replay (Blast Radius)",
4068
4225
  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.",
4069
4226
  inputSchema: {
4070
- candidateVersionId: z7.string().uuid().describe("Version to re-evaluate against"),
4071
- from: z7.string().describe("Window start date (yyyy-MM-dd)"),
4072
- to: z7.string().describe("Window end date (yyyy-MM-dd)"),
4073
- maxRecords: z7.number().int().min(1).optional().describe("Sample cap (server default applies; hard cap 50k)")
4227
+ candidateVersionId: z8.string().uuid().describe("Version to re-evaluate against"),
4228
+ from: z8.string().describe("Window start date (yyyy-MM-dd)"),
4229
+ to: z8.string().describe("Window end date (yyyy-MM-dd)"),
4230
+ maxRecords: z8.number().int().min(1).optional().describe("Sample cap (server default applies; hard cap 50k)")
4074
4231
  }
4075
4232
  },
4076
4233
  async ({ candidateVersionId, from, to, maxRecords }) => callApi("POST", "replay/jobs", { body: { candidateVersionId, from, to, maxRecords } })
@@ -4081,7 +4238,7 @@ function registerReplayTools(server, callApi) {
4081
4238
  title: "Get Replay Job Status",
4082
4239
  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.",
4083
4240
  inputSchema: {
4084
- jobId: z7.string().describe("Replay job ID from lexq_replay_start")
4241
+ jobId: z8.string().describe("Replay job ID from lexq_replay_start")
4085
4242
  }
4086
4243
  },
4087
4244
  async ({ jobId }) => callApi("GET", `replay/jobs/${jobId}`)
@@ -4092,8 +4249,8 @@ function registerReplayTools(server, callApi) {
4092
4249
  title: "List Replay Jobs",
4093
4250
  description: "List window replay job history (reverse-chronological). Lightweight items \u2014 use lexq_replay_status for summary and changed samples.",
4094
4251
  inputSchema: {
4095
- page: z7.number().int().min(0).default(0).describe("Page number"),
4096
- size: z7.number().int().min(1).max(100).default(20).describe("Page size")
4252
+ page: z8.number().int().min(0).default(0).describe("Page number"),
4253
+ size: z8.number().int().min(1).max(100).default(20).describe("Page size")
4097
4254
  }
4098
4255
  },
4099
4256
  async ({ page, size }) => callApi("GET", "replay/jobs", { params: paginationParams(page, size) })
@@ -4104,7 +4261,7 @@ function registerReplayTools(server, callApi) {
4104
4261
  title: "Cancel Replay Job",
4105
4262
  description: "Cooperatively cancel a PENDING or RUNNING window replay job. Other states are rejected. VIEWER role cannot cancel.",
4106
4263
  inputSchema: {
4107
- jobId: z7.string().describe("Replay job ID")
4264
+ jobId: z8.string().describe("Replay job ID")
4108
4265
  }
4109
4266
  },
4110
4267
  async ({ jobId }) => callApi("POST", `replay/jobs/${jobId}/cancel`)
@@ -4112,7 +4269,7 @@ function registerReplayTools(server, callApi) {
4112
4269
  }
4113
4270
 
4114
4271
  // src/mcp/tools/history.ts
4115
- import { z as z8 } from "zod";
4272
+ import { z as z9 } from "zod";
4116
4273
  function registerHistoryTools(server, callApi) {
4117
4274
  server.registerTool(
4118
4275
  "lexq_history_list",
@@ -4120,14 +4277,14 @@ function registerHistoryTools(server, callApi) {
4120
4277
  title: "List Execution History",
4121
4278
  description: "List policy execution history. Shows trace ID, group, version, status, match result, and latency.",
4122
4279
  inputSchema: {
4123
- page: z8.number().int().min(0).default(0).describe("Page number"),
4124
- size: z8.number().int().min(1).max(100).default(20).describe("Page size"),
4125
- traceId: z8.string().optional().describe("Filter by trace ID"),
4126
- groupId: z8.string().uuid().optional().describe("Filter by policy group"),
4127
- versionId: z8.string().uuid().optional().describe("Filter by version"),
4128
- status: z8.enum(["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"]).optional().describe("Filter by execution status"),
4129
- startDate: z8.string().optional().describe("Start date (yyyy-MM-dd)"),
4130
- endDate: z8.string().optional().describe("End date (yyyy-MM-dd)")
4280
+ page: z9.number().int().min(0).default(0).describe("Page number"),
4281
+ size: z9.number().int().min(1).max(100).default(20).describe("Page size"),
4282
+ traceId: z9.string().optional().describe("Filter by trace ID"),
4283
+ groupId: z9.string().uuid().optional().describe("Filter by policy group"),
4284
+ versionId: z9.string().uuid().optional().describe("Filter by version"),
4285
+ status: z9.enum(["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"]).optional().describe("Filter by execution status"),
4286
+ startDate: z9.string().optional().describe("Start date (yyyy-MM-dd)"),
4287
+ endDate: z9.string().optional().describe("End date (yyyy-MM-dd)")
4131
4288
  }
4132
4289
  },
4133
4290
  async ({ page, size, traceId, groupId, versionId, status, startDate, endDate }) => {
@@ -4147,7 +4304,7 @@ function registerHistoryTools(server, callApi) {
4147
4304
  title: "Get Execution Detail",
4148
4305
  description: "Get full execution detail including inputFacts, mutatedFacts, generatedVariables, executionTraces, and decisionTraces.",
4149
4306
  inputSchema: {
4150
- traceId: z8.string().describe("Trace ID from execution history")
4307
+ traceId: z9.string().describe("Trace ID from execution history")
4151
4308
  }
4152
4309
  },
4153
4310
  async ({ traceId }) => callApi("GET", `execution/history/${traceId}`)
@@ -4158,9 +4315,9 @@ function registerHistoryTools(server, callApi) {
4158
4315
  title: "Execution Statistics",
4159
4316
  description: "Get execution KPIs: total executions, success/failure counts, success rate, and average latency.",
4160
4317
  inputSchema: {
4161
- groupId: z8.string().uuid().optional().describe("Filter by policy group"),
4162
- startDate: z8.string().optional().describe("Start date (yyyy-MM-dd)"),
4163
- endDate: z8.string().optional().describe("End date (yyyy-MM-dd)")
4318
+ groupId: z9.string().uuid().optional().describe("Filter by policy group"),
4319
+ startDate: z9.string().optional().describe("Start date (yyyy-MM-dd)"),
4320
+ endDate: z9.string().optional().describe("End date (yyyy-MM-dd)")
4164
4321
  }
4165
4322
  },
4166
4323
  async ({ groupId, startDate, endDate }) => {
@@ -4174,7 +4331,7 @@ function registerHistoryTools(server, callApi) {
4174
4331
  }
4175
4332
 
4176
4333
  // src/mcp/tools/provenance.ts
4177
- import { z as z9 } from "zod";
4334
+ import { z as z10 } from "zod";
4178
4335
  function registerProvenanceTools(server, callApi) {
4179
4336
  server.registerTool(
4180
4337
  "lexq_provenance_get",
@@ -4182,7 +4339,7 @@ function registerProvenanceTools(server, callApi) {
4182
4339
  title: "Get Decision Provenance",
4183
4340
  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.",
4184
4341
  inputSchema: {
4185
- traceId: z9.string().describe("Trace ID of the execution")
4342
+ traceId: z10.string().describe("Trace ID of the execution")
4186
4343
  }
4187
4344
  },
4188
4345
  async ({ traceId }) => callApi("GET", `provenance/${traceId}`)
@@ -4193,13 +4350,13 @@ function registerProvenanceTools(server, callApi) {
4193
4350
  title: "List PII Reveal Audits",
4194
4351
  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.",
4195
4352
  inputSchema: {
4196
- page: z9.number().int().min(0).default(0).describe("Page number"),
4197
- size: z9.number().int().min(1).max(100).default(20).describe("Page size"),
4198
- traceId: z9.string().optional().describe("Filter by trace ID (exact match)"),
4199
- revealedBy: z9.string().optional().describe("Filter by operator ID (exact match)"),
4200
- factKey: z9.string().optional().describe("Filter by fact key (partial match, case-insensitive)"),
4201
- startDate: z9.string().optional().describe("Start date (yyyy-MM-dd)"),
4202
- endDate: z9.string().optional().describe("End date (yyyy-MM-dd)")
4353
+ page: z10.number().int().min(0).default(0).describe("Page number"),
4354
+ size: z10.number().int().min(1).max(100).default(20).describe("Page size"),
4355
+ traceId: z10.string().optional().describe("Filter by trace ID (exact match)"),
4356
+ revealedBy: z10.string().optional().describe("Filter by operator ID (exact match)"),
4357
+ factKey: z10.string().optional().describe("Filter by fact key (partial match, case-insensitive)"),
4358
+ startDate: z10.string().optional().describe("Start date (yyyy-MM-dd)"),
4359
+ endDate: z10.string().optional().describe("End date (yyyy-MM-dd)")
4203
4360
  }
4204
4361
  },
4205
4362
  async ({ page, size, traceId, revealedBy, factKey, startDate, endDate }) => {
@@ -4215,7 +4372,7 @@ function registerProvenanceTools(server, callApi) {
4215
4372
  }
4216
4373
 
4217
4374
  // src/mcp/tools/integrations.ts
4218
- import { z as z10 } from "zod";
4375
+ import { z as z11 } from "zod";
4219
4376
  function registerIntegrationTools(server, callApi) {
4220
4377
  server.registerTool(
4221
4378
  "lexq_integrations_list",
@@ -4223,9 +4380,9 @@ function registerIntegrationTools(server, callApi) {
4223
4380
  title: "List Integrations",
4224
4381
  description: "List all external integrations (webhooks, CRM, notification, etc.).",
4225
4382
  inputSchema: {
4226
- page: z10.number().int().min(0).default(0).describe("Page number"),
4227
- size: z10.number().int().min(1).max(100).default(20).describe("Page size"),
4228
- type: z10.enum(["COUPON", "POINT", "NOTIFICATION", "CRM", "MESSENGER", "WEBHOOK"]).optional().describe("Filter by integration type")
4383
+ page: z11.number().int().min(0).default(0).describe("Page number"),
4384
+ size: z11.number().int().min(1).max(100).default(20).describe("Page size"),
4385
+ type: z11.enum(["COUPON", "POINT", "NOTIFICATION", "CRM", "MESSENGER", "WEBHOOK"]).optional().describe("Filter by integration type")
4229
4386
  }
4230
4387
  },
4231
4388
  async ({ page, size, type }) => {
@@ -4240,7 +4397,7 @@ function registerIntegrationTools(server, callApi) {
4240
4397
  title: "Get Integration",
4241
4398
  description: "Get integration detail by ID.",
4242
4399
  inputSchema: {
4243
- integrationId: z10.string().uuid().describe("Integration ID")
4400
+ integrationId: z11.string().uuid().describe("Integration ID")
4244
4401
  }
4245
4402
  },
4246
4403
  async ({ integrationId }) => callApi("GET", `integrations/${integrationId}`)
@@ -4251,13 +4408,13 @@ function registerIntegrationTools(server, callApi) {
4251
4408
  title: "Save Integration",
4252
4409
  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.",
4253
4410
  inputSchema: {
4254
- id: z10.string().uuid().optional().describe("Integration ID (omit to create, provide to update)"),
4255
- type: z10.enum(["COUPON", "POINT", "NOTIFICATION", "CRM", "MESSENGER", "WEBHOOK"]).describe("Integration type"),
4256
- name: z10.string().describe("Integration name"),
4257
- baseUrl: z10.string().describe("Base URL of the external service"),
4258
- credential: z10.string().optional().describe("API key or token for the service"),
4259
- additionalConfig: z10.string().optional().describe("JSON string of additional config key-value pairs"),
4260
- isActive: z10.boolean().default(true).describe("Whether the integration is active")
4411
+ id: z11.string().uuid().optional().describe("Integration ID (omit to create, provide to update)"),
4412
+ type: z11.enum(["COUPON", "POINT", "NOTIFICATION", "CRM", "MESSENGER", "WEBHOOK"]).describe("Integration type"),
4413
+ name: z11.string().describe("Integration name"),
4414
+ baseUrl: z11.string().describe("Base URL of the external service"),
4415
+ credential: z11.string().optional().describe("API key or token for the service"),
4416
+ additionalConfig: z11.string().optional().describe("JSON string of additional config key-value pairs"),
4417
+ isActive: z11.boolean().default(true).describe("Whether the integration is active")
4261
4418
  }
4262
4419
  },
4263
4420
  async ({ additionalConfig, ...rest }) => {
@@ -4272,7 +4429,7 @@ function registerIntegrationTools(server, callApi) {
4272
4429
  title: "Delete Integration",
4273
4430
  description: "Delete an integration by ID.",
4274
4431
  inputSchema: {
4275
- integrationId: z10.string().uuid().describe("Integration ID")
4432
+ integrationId: z11.string().uuid().describe("Integration ID")
4276
4433
  }
4277
4434
  },
4278
4435
  async ({ integrationId }) => callApi("DELETE", `integrations/${integrationId}`)
@@ -4289,7 +4446,7 @@ function registerIntegrationTools(server, callApi) {
4289
4446
  }
4290
4447
 
4291
4448
  // src/mcp/tools/logs.ts
4292
- import { z as z11 } from "zod";
4449
+ import { z as z12 } from "zod";
4293
4450
  function registerLogTools(server, callApi) {
4294
4451
  server.registerTool(
4295
4452
  "lexq_logs_list",
@@ -4297,14 +4454,14 @@ function registerLogTools(server, callApi) {
4297
4454
  title: "List Failure Logs",
4298
4455
  description: "List system failure logs from background tasks (webhook calls, coupon issuance, etc.).",
4299
4456
  inputSchema: {
4300
- page: z11.number().int().min(0).default(0).describe("Page number"),
4301
- size: z11.number().int().min(1).max(100).default(20).describe("Page size"),
4302
- category: z11.enum(TaskCategory).optional().describe("Task category"),
4303
- taskType: z11.enum(TaskType).optional().describe("Task type"),
4304
- status: z11.enum(FailureStatus).optional().describe("Log status"),
4305
- keyword: z11.string().optional().describe("Search in refId, refSubId, errorMessage"),
4306
- startDate: z11.string().optional().describe("Start date (yyyy-MM-dd)"),
4307
- endDate: z11.string().optional().describe("End date (yyyy-MM-dd)")
4457
+ page: z12.number().int().min(0).default(0).describe("Page number"),
4458
+ size: z12.number().int().min(1).max(100).default(20).describe("Page size"),
4459
+ category: z12.enum(TaskCategory).optional().describe("Task category"),
4460
+ taskType: z12.enum(TaskType).optional().describe("Task type"),
4461
+ status: z12.enum(FailureStatus).optional().describe("Log status"),
4462
+ keyword: z12.string().optional().describe("Search in refId, refSubId, errorMessage"),
4463
+ startDate: z12.string().optional().describe("Start date (yyyy-MM-dd)"),
4464
+ endDate: z12.string().optional().describe("End date (yyyy-MM-dd)")
4308
4465
  }
4309
4466
  },
4310
4467
  async ({ page, size, category, taskType, status, keyword, startDate, endDate }) => {
@@ -4324,7 +4481,7 @@ function registerLogTools(server, callApi) {
4324
4481
  title: "Get Failure Log",
4325
4482
  description: "Get failure log detail by ID.",
4326
4483
  inputSchema: {
4327
- logId: z11.string().uuid().describe("Failure log ID")
4484
+ logId: z12.string().uuid().describe("Failure log ID")
4328
4485
  }
4329
4486
  },
4330
4487
  async ({ logId }) => callApi("GET", `failure-logs/${logId}`)
@@ -4335,8 +4492,8 @@ function registerLogTools(server, callApi) {
4335
4492
  title: "Process Failure Log",
4336
4493
  description: "Process a single failure log: RETRY (re-execute with original payload), RESOLVE (mark as manually fixed), or IGNORE (skip intentionally).",
4337
4494
  inputSchema: {
4338
- logId: z11.string().uuid().describe("Failure log ID"),
4339
- action: z11.enum(FailureAction).describe("Action to take")
4495
+ logId: z12.string().uuid().describe("Failure log ID"),
4496
+ action: z12.enum(FailureAction).describe("Action to take")
4340
4497
  }
4341
4498
  },
4342
4499
  async ({ logId, action }) => callApi("POST", `failure-logs/${logId}/actions`, {
@@ -4349,8 +4506,8 @@ function registerLogTools(server, callApi) {
4349
4506
  title: "Bulk Process Failure Logs",
4350
4507
  description: "Process multiple failure logs at once. Provide an array of log IDs and the action.",
4351
4508
  inputSchema: {
4352
- logIds: z11.array(z11.string().uuid()).describe("Array of failure log IDs"),
4353
- action: z11.enum(FailureAction).describe("Action to apply to all logs")
4509
+ logIds: z12.array(z12.string().uuid()).describe("Array of failure log IDs"),
4510
+ action: z12.enum(FailureAction).describe("Action to apply to all logs")
4354
4511
  }
4355
4512
  },
4356
4513
  async ({ logIds, action }) => callApi("POST", "failure-logs/bulk-actions", {
@@ -4360,7 +4517,7 @@ function registerLogTools(server, callApi) {
4360
4517
  }
4361
4518
 
4362
4519
  // src/mcp/tools/webhook-subscriptions.ts
4363
- import { z as z12 } from "zod";
4520
+ import { z as z13 } from "zod";
4364
4521
  function registerWebhookSubscriptionTools(server, callApi) {
4365
4522
  server.registerTool(
4366
4523
  "lexq_webhook_subscriptions_list",
@@ -4368,8 +4525,8 @@ function registerWebhookSubscriptionTools(server, callApi) {
4368
4525
  title: "List Webhook Subscriptions",
4369
4526
  description: "List platform event webhook subscriptions. These receive deployment lifecycle notifications (publish, deploy, rollback, undeploy).",
4370
4527
  inputSchema: {
4371
- page: z12.number().int().min(0).default(0).describe("Page number"),
4372
- size: z12.number().int().min(1).max(100).default(20).describe("Page size")
4528
+ page: z13.number().int().min(0).default(0).describe("Page number"),
4529
+ size: z13.number().int().min(1).max(100).default(20).describe("Page size")
4373
4530
  }
4374
4531
  },
4375
4532
  async ({ page, size }) => {
@@ -4383,7 +4540,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
4383
4540
  title: "Get Webhook Subscription",
4384
4541
  description: "Get webhook subscription detail by ID.",
4385
4542
  inputSchema: {
4386
- id: z12.string().uuid().describe("Webhook subscription ID")
4543
+ id: z13.string().uuid().describe("Webhook subscription ID")
4387
4544
  }
4388
4545
  },
4389
4546
  async ({ id }) => callApi("GET", `webhook-subscriptions/${id}`)
@@ -4394,13 +4551,13 @@ function registerWebhookSubscriptionTools(server, callApi) {
4394
4551
  title: "Save Webhook Subscription",
4395
4552
  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": "..."}).',
4396
4553
  inputSchema: {
4397
- id: z12.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
4398
- name: z12.string().min(1).describe("Subscription name (unique per tenant)"),
4399
- webhookUrl: z12.string().url().describe("Webhook endpoint URL"),
4400
- subscribedEvents: z12.array(z12.enum(PlatformEventType)).min(1).describe("Events to subscribe to"),
4401
- payloadFormat: z12.enum(WebhookPayloadFormat).optional().default("GENERIC").describe("Payload format"),
4402
- secret: z12.string().optional().describe("HMAC-SHA256 signing secret"),
4403
- isActive: z12.boolean().optional().default(true).describe("Whether the subscription is active")
4554
+ id: z13.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
4555
+ name: z13.string().min(1).describe("Subscription name (unique per tenant)"),
4556
+ webhookUrl: z13.string().url().describe("Webhook endpoint URL"),
4557
+ subscribedEvents: z13.array(z13.enum(PlatformEventType)).min(1).describe("Events to subscribe to"),
4558
+ payloadFormat: z13.enum(WebhookPayloadFormat).optional().default("GENERIC").describe("Payload format"),
4559
+ secret: z13.string().optional().describe("HMAC-SHA256 signing secret"),
4560
+ isActive: z13.boolean().optional().default(true).describe("Whether the subscription is active")
4404
4561
  }
4405
4562
  },
4406
4563
  async ({ ...body }) => callApi("POST", "webhook-subscriptions", { body })
@@ -4411,7 +4568,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
4411
4568
  title: "Delete Webhook Subscription",
4412
4569
  description: "Delete a webhook subscription by ID.",
4413
4570
  inputSchema: {
4414
- id: z12.string().uuid().describe("Webhook subscription ID")
4571
+ id: z13.string().uuid().describe("Webhook subscription ID")
4415
4572
  }
4416
4573
  },
4417
4574
  async ({ id }) => callApi("DELETE", `webhook-subscriptions/${id}`)
@@ -4422,7 +4579,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
4422
4579
  title: "Test Webhook Subscription",
4423
4580
  description: "Send a test event to verify webhook connectivity. Returns the HTTP status code and success/failure message.",
4424
4581
  inputSchema: {
4425
- id: z12.string().uuid().describe("Webhook subscription ID")
4582
+ id: z13.string().uuid().describe("Webhook subscription ID")
4426
4583
  }
4427
4584
  },
4428
4585
  async ({ id }) => callApi("POST", `webhook-subscriptions/${id}/test`)
@@ -4430,7 +4587,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
4430
4587
  }
4431
4588
 
4432
4589
  // src/mcp/tools/domain-templates.ts
4433
- import { z as z13 } from "zod";
4590
+ import { z as z14 } from "zod";
4434
4591
  function registerDomainTemplateTools(server, callApi) {
4435
4592
  server.registerTool(
4436
4593
  "lexq_domain_templates_list",
@@ -4447,7 +4604,7 @@ function registerDomainTemplateTools(server, callApi) {
4447
4604
  title: "Preview Domain Template",
4448
4605
  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.",
4449
4606
  inputSchema: {
4450
- template: z13.string().describe(
4607
+ template: z14.string().describe(
4451
4608
  "Domain template key (e.g. ECOMMERCE). Use lexq_domain_templates_list to see available keys \u2014 currently only ECOMMERCE is ACTIVE."
4452
4609
  )
4453
4610
  }
@@ -4460,8 +4617,8 @@ function registerDomainTemplateTools(server, callApi) {
4460
4617
  title: "Apply Domain Template",
4461
4618
  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.",
4462
4619
  inputSchema: {
4463
- template: z13.string().describe("Domain template key to apply (e.g. ECOMMERCE)."),
4464
- customName: z13.string().optional().describe(
4620
+ template: z14.string().describe("Domain template key to apply (e.g. ECOMMERCE)."),
4621
+ customName: z14.string().optional().describe(
4465
4622
  "Optional custom name for the policy group that gets created. If omitted, the template's default name is used."
4466
4623
  )
4467
4624
  }
@@ -4483,6 +4640,7 @@ function registerAllTools(server, callApi) {
4483
4640
  registerFactTools(server, callApi);
4484
4641
  registerDeployTools(server, callApi);
4485
4642
  registerAnalyticsTools(server, callApi);
4643
+ registerProfileTools(server, callApi);
4486
4644
  registerReplayTools(server, callApi);
4487
4645
  registerHistoryTools(server, callApi);
4488
4646
  registerProvenanceTools(server, callApi);
@@ -4517,7 +4675,7 @@ async function startMcpServer() {
4517
4675
  function registerServeCommand(program) {
4518
4676
  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(
4519
4677
  "after",
4520
- dedent17`
4678
+ dedent18`
4521
4679
 
4522
4680
  Example:
4523
4681
  $ lexq serve --mcp
@@ -4548,11 +4706,11 @@ function registerServeCommand(program) {
4548
4706
 
4549
4707
  // src/commands/domain-templates.ts
4550
4708
  import "commander";
4551
- import dedent18 from "dedent";
4709
+ import dedent19 from "dedent";
4552
4710
  function registerDomainTemplateCommands(program) {
4553
4711
  const templates = program.command("domain-templates").description("Browse and apply domain templates").addHelpText(
4554
4712
  "after",
4555
- dedent18`
4713
+ dedent19`
4556
4714
 
4557
4715
  A domain template is an industry-specific starter pack of fact
4558
4716
  definitions and sample rules. Applying one provisions a ready-to-use
@@ -4600,7 +4758,7 @@ function registerDomainTemplateCommands(program) {
4600
4758
  });
4601
4759
  templates.command("preview").description("Preview what a domain template provisions").requiredOption("--template <key>", "Domain template key (e.g. ECOMMERCE)").addHelpText(
4602
4760
  "after",
4603
- dedent18`
4761
+ dedent19`
4604
4762
 
4605
4763
  Read-only dry run — shows the fact definitions and sample rules the
4606
4764
  template will create. Nothing is provisioned.
@@ -4629,7 +4787,7 @@ function registerDomainTemplateCommands(program) {
4629
4787
  });
4630
4788
  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(
4631
4789
  "after",
4632
- dedent18`
4790
+ dedent19`
4633
4791
 
4634
4792
  Creates the template's fact definitions and a new DRAFT policy group
4635
4793
  populated with its sample rules. Existing facts are skipped — apply is
@@ -4704,6 +4862,7 @@ function createCli() {
4704
4862
  registerDomainTemplateCommands(program);
4705
4863
  registerDeployCommands(program);
4706
4864
  registerAnalyticsCommands(program);
4865
+ registerProfileCommands(program);
4707
4866
  registerHistoryCommands(program);
4708
4867
  registerReplayCommands(program);
4709
4868
  registerProvenanceCommands(program);