@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 +317 -158
- package/dist/mcp/register.js +134 -81
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1144,14 +1144,15 @@ function registerFactCommands(program) {
|
|
|
1144
1144
|
});
|
|
1145
1145
|
if (format === "table") {
|
|
1146
1146
|
printTable(
|
|
1147
|
-
["ID", "Key", "Name", "Type", "System", "Required"],
|
|
1147
|
+
["ID", "Key", "Name", "Type", "System", "Required", "PII"],
|
|
1148
1148
|
data.content.map((f) => [
|
|
1149
1149
|
f.id,
|
|
1150
1150
|
f.key,
|
|
1151
1151
|
f.name,
|
|
1152
1152
|
f.type,
|
|
1153
1153
|
f.isSystem ? "\u2713" : "\u2013",
|
|
1154
|
-
f.isRequired ? "\u2713" : "\u2013"
|
|
1154
|
+
f.isRequired ? "\u2713" : "\u2013",
|
|
1155
|
+
f.isPii ? "\u2713" : "\u2013"
|
|
1155
1156
|
]),
|
|
1156
1157
|
{ truncate: 28 }
|
|
1157
1158
|
);
|
|
@@ -1211,7 +1212,7 @@ ${data.length} unregistered`);
|
|
|
1211
1212
|
process.exit(1);
|
|
1212
1213
|
}
|
|
1213
1214
|
});
|
|
1214
|
-
facts.command("create").description("Create a new fact definition").option("--key <key>", "Fact key (lowercase, underscores)").option("--name <n>", "Display name").option("--type <type>", "Value type: STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER").option("--description <desc>", "Description").option("--required", "Mark as required", false).option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
1215
|
+
facts.command("create").description("Create a new fact definition").option("--key <key>", "Fact key (lowercase, underscores)").option("--name <n>", "Display name").option("--type <type>", "Value type: STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER").option("--description <desc>", "Description").option("--required", "Mark as required", false).option("--pii", "Mark as PII \u2014 value is masked on every read surface", false).option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
1215
1216
|
"after",
|
|
1216
1217
|
dedent6`
|
|
1217
1218
|
|
|
@@ -1247,7 +1248,7 @@ ${data.length} unregistered`);
|
|
|
1247
1248
|
process.exit(1);
|
|
1248
1249
|
}
|
|
1249
1250
|
});
|
|
1250
|
-
facts.command("update").description("Update a fact definition").requiredOption("--id <factId>", "Fact definition ID").option("--name <n>", "Display name").option("--description <desc>", "Description").option("--required", "Mark as required").option("--no-required", "Mark as not required").option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
1251
|
+
facts.command("update").description("Update a fact definition").requiredOption("--id <factId>", "Fact definition ID").option("--name <n>", "Display name").option("--description <desc>", "Description").option("--required", "Mark as required").option("--no-required", "Mark as not required").option("--pii", "Mark as PII (enables masking)").option("--no-pii", "Unmark as PII (disables masking \u2014 value becomes visible)").option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
1251
1252
|
"after",
|
|
1252
1253
|
dedent6`
|
|
1253
1254
|
|
|
@@ -1334,7 +1335,8 @@ function buildCreateBody2(opts) {
|
|
|
1334
1335
|
key: opts.key,
|
|
1335
1336
|
name: opts.name,
|
|
1336
1337
|
type: opts.type,
|
|
1337
|
-
isRequired: opts.required === true
|
|
1338
|
+
isRequired: opts.required === true,
|
|
1339
|
+
isPii: opts.pii === true
|
|
1338
1340
|
};
|
|
1339
1341
|
if (opts.description) body.description = opts.description;
|
|
1340
1342
|
return body;
|
|
@@ -1344,6 +1346,7 @@ function buildUpdateBody2(opts) {
|
|
|
1344
1346
|
if (opts.name) body.name = opts.name;
|
|
1345
1347
|
if (opts.description !== void 0) body.description = opts.description;
|
|
1346
1348
|
if (typeof opts.required === "boolean") body.isRequired = opts.required;
|
|
1349
|
+
if (typeof opts.isPii === "boolean") body.isPii = opts.isPii;
|
|
1347
1350
|
return body;
|
|
1348
1351
|
}
|
|
1349
1352
|
|
|
@@ -2180,13 +2183,140 @@ function resolveBody(opts) {
|
|
|
2180
2183
|
return {};
|
|
2181
2184
|
}
|
|
2182
2185
|
|
|
2183
|
-
// src/commands/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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/
|
|
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:
|
|
4059
|
-
candidateVersionId:
|
|
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:
|
|
4071
|
-
from:
|
|
4072
|
-
to:
|
|
4073
|
-
maxRecords:
|
|
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:
|
|
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:
|
|
4096
|
-
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:
|
|
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
|
|
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:
|
|
4124
|
-
size:
|
|
4125
|
-
traceId:
|
|
4126
|
-
groupId:
|
|
4127
|
-
versionId:
|
|
4128
|
-
status:
|
|
4129
|
-
startDate:
|
|
4130
|
-
endDate:
|
|
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:
|
|
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:
|
|
4162
|
-
startDate:
|
|
4163
|
-
endDate:
|
|
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
|
|
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:
|
|
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:
|
|
4197
|
-
size:
|
|
4198
|
-
traceId:
|
|
4199
|
-
revealedBy:
|
|
4200
|
-
factKey:
|
|
4201
|
-
startDate:
|
|
4202
|
-
endDate:
|
|
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
|
|
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:
|
|
4227
|
-
size:
|
|
4228
|
-
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:
|
|
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:
|
|
4255
|
-
type:
|
|
4256
|
-
name:
|
|
4257
|
-
baseUrl:
|
|
4258
|
-
credential:
|
|
4259
|
-
additionalConfig:
|
|
4260
|
-
isActive:
|
|
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:
|
|
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
|
|
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:
|
|
4301
|
-
size:
|
|
4302
|
-
category:
|
|
4303
|
-
taskType:
|
|
4304
|
-
status:
|
|
4305
|
-
keyword:
|
|
4306
|
-
startDate:
|
|
4307
|
-
endDate:
|
|
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:
|
|
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:
|
|
4339
|
-
action:
|
|
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:
|
|
4353
|
-
action:
|
|
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
|
|
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:
|
|
4372
|
-
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:
|
|
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:
|
|
4398
|
-
name:
|
|
4399
|
-
webhookUrl:
|
|
4400
|
-
subscribedEvents:
|
|
4401
|
-
payloadFormat:
|
|
4402
|
-
secret:
|
|
4403
|
-
isActive:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
4464
|
-
customName:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|