@lexq/cli 1.1.6 → 2.0.0
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 +55 -23
- package/dist/mcp/register.js +19 -12
- package/package.json +8 -3
- package/skills/lexq-groups/SKILL.md +3 -2
- package/skills/lexq-recipes/SKILL.md +11 -4
package/dist/index.js
CHANGED
|
@@ -608,7 +608,10 @@ ${data.length} total`);
|
|
|
608
608
|
The remaining traffic continues to the current live version.
|
|
609
609
|
`
|
|
610
610
|
);
|
|
611
|
-
abTest.command("start").description("Start an A/B test").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption(
|
|
611
|
+
abTest.command("start").description("Start an A/B test").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption(
|
|
612
|
+
"--version-id <versionId>",
|
|
613
|
+
"Challenger version ID (a published version of this group)"
|
|
614
|
+
).requiredOption(
|
|
612
615
|
"--traffic-rate <rate>",
|
|
613
616
|
`Traffic rate for challenger (${MIN_TRAFFIC_RATE}-${MAX_TRAFFIC_RATE})`
|
|
614
617
|
).addHelpText(
|
|
@@ -1770,7 +1773,13 @@ function registerDeployCommands(program) {
|
|
|
1770
1773
|
process.exit(1);
|
|
1771
1774
|
}
|
|
1772
1775
|
});
|
|
1773
|
-
deploy.command("rollback").description("Rollback to the previous deployed version").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--memo <memo>", "Rollback reason").option(
|
|
1776
|
+
deploy.command("rollback").description("Rollback to the previous deployed version").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--memo <memo>", "Rollback reason").option(
|
|
1777
|
+
"--expected-current-version-id <versionId>",
|
|
1778
|
+
"currentVersionId from your last read of the group"
|
|
1779
|
+
).option(
|
|
1780
|
+
"--expected-current-deployment-id <deploymentId>",
|
|
1781
|
+
"currentDeploymentId from your last read of the group"
|
|
1782
|
+
).option("--force", "Skip confirmation prompt").addHelpText(
|
|
1774
1783
|
"after",
|
|
1775
1784
|
dedent7`
|
|
1776
1785
|
|
|
@@ -1778,8 +1787,20 @@ function registerDeployCommands(program) {
|
|
|
1778
1787
|
Only available if the previous version is still ACTIVE and its effective end
|
|
1779
1788
|
has not passed (P-058).
|
|
1780
1789
|
|
|
1790
|
+
Run lexq groups get first: currentVersionName is live now and
|
|
1791
|
+
previousVersionName is what a rollback restores. Pass the group's
|
|
1792
|
+
currentVersionId and currentDeploymentId with the two --expected-* options.
|
|
1793
|
+
The server checks only the values you pass. If anyone deployed, rolled back
|
|
1794
|
+
or undeployed in between, the rollback is rejected with 409 and nothing
|
|
1795
|
+
changes: P-062 if another version or no version is live, P-063 if the same
|
|
1796
|
+
version is live but from a newer deployment. Read the group again before
|
|
1797
|
+
retrying.
|
|
1798
|
+
|
|
1781
1799
|
Example:
|
|
1782
|
-
$ lexq
|
|
1800
|
+
$ lexq groups get --id <gid>
|
|
1801
|
+
$ lexq deploy rollback --group-id <gid> --memo "High error rate" \
|
|
1802
|
+
--expected-current-version-id <currentVersionId> \
|
|
1803
|
+
--expected-current-deployment-id <currentDeploymentId> --force
|
|
1783
1804
|
`
|
|
1784
1805
|
).action(async (opts) => {
|
|
1785
1806
|
try {
|
|
@@ -1799,7 +1820,11 @@ function registerDeployCommands(program) {
|
|
|
1799
1820
|
baseUrl: globalOpts.baseUrl,
|
|
1800
1821
|
dryRun: globalOpts.dryRun,
|
|
1801
1822
|
verbose: globalOpts.verbose,
|
|
1802
|
-
body: {
|
|
1823
|
+
body: {
|
|
1824
|
+
memo: opts.memo,
|
|
1825
|
+
expectedCurrentVersionId: opts.expectedCurrentVersionId,
|
|
1826
|
+
expectedCurrentDeploymentId: opts.expectedCurrentDeploymentId
|
|
1827
|
+
}
|
|
1803
1828
|
});
|
|
1804
1829
|
console.log(`\u2713 Group ${opts.groupId} rolled back.`);
|
|
1805
1830
|
} catch (error) {
|
|
@@ -2156,7 +2181,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2156
2181
|
dedent8`
|
|
2157
2182
|
|
|
2158
2183
|
Examples:
|
|
2159
|
-
$ lexq analytics dry-run --version-id <vid> --debug
|
|
2184
|
+
$ lexq analytics dry-run --version-id <vid> --debug \
|
|
2160
2185
|
--json '{"facts": {"paymentAmount": 150000, "customerTier": "VIP"}}'
|
|
2161
2186
|
|
|
2162
2187
|
$ lexq analytics dry-run --version-id <vid> --file test-input.json
|
|
@@ -3523,7 +3548,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
3523
3548
|
|
|
3524
3549
|
// src/commands/serve.ts
|
|
3525
3550
|
import "commander";
|
|
3526
|
-
import
|
|
3551
|
+
import dedent18 from "dedent";
|
|
3527
3552
|
|
|
3528
3553
|
// src/mcp/server.ts
|
|
3529
3554
|
import { readFileSync as readFileSync3 } from "fs";
|
|
@@ -3662,7 +3687,7 @@ function registerGroupTools(server, callApi) {
|
|
|
3662
3687
|
{
|
|
3663
3688
|
title: "Get Policy Group",
|
|
3664
3689
|
annotations: { readOnlyHint: true },
|
|
3665
|
-
description: "Get a single policy group by ID.",
|
|
3690
|
+
description: "Get a single policy group by ID. Its currentVersionId and currentDeploymentId are what lexq_deploy_rollback expects, and previousVersionName is the version a rollback restores.",
|
|
3666
3691
|
inputSchema: z2.object({
|
|
3667
3692
|
groupId: z2.string().uuid().describe("Policy group ID")
|
|
3668
3693
|
})
|
|
@@ -3760,10 +3785,10 @@ function registerGroupTools(server, callApi) {
|
|
|
3760
3785
|
{
|
|
3761
3786
|
title: "Start A/B Test",
|
|
3762
3787
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
3763
|
-
description: "Start an A/B test on a policy group. Requires a challenger version ID and traffic rate. The split is computed from context.trafficKey on each execution request; requests that omit it never reach the challenger and the test stays at 0%.",
|
|
3788
|
+
description: "Start an A/B test on a policy group. Requires a challenger version ID and traffic rate. The challenger must be a published version of the same group; a version from another group is rejected with P-016. The split is computed from context.trafficKey on each execution request; requests that omit it never reach the challenger and the test stays at 0%.",
|
|
3764
3789
|
inputSchema: z2.object({
|
|
3765
3790
|
groupId: z2.string().uuid().describe("Policy group ID"),
|
|
3766
|
-
testVersionId: z2.string().uuid().describe("Challenger version ID
|
|
3791
|
+
testVersionId: z2.string().uuid().describe("Challenger version ID. Must be a published version of this group"),
|
|
3767
3792
|
trafficRate: z2.number().int().min(1).max(99).describe("Traffic percentage routed to challenger (1-99)")
|
|
3768
3793
|
})
|
|
3769
3794
|
},
|
|
@@ -4243,6 +4268,7 @@ function registerFactTools(server, callApi) {
|
|
|
4243
4268
|
}
|
|
4244
4269
|
|
|
4245
4270
|
// src/mcp/tools/deploy.ts
|
|
4271
|
+
import dedent16 from "dedent";
|
|
4246
4272
|
import { z as z6 } from "zod";
|
|
4247
4273
|
function registerDeployTools(server, callApi) {
|
|
4248
4274
|
server.registerTool(
|
|
@@ -4280,14 +4306,20 @@ function registerDeployTools(server, callApi) {
|
|
|
4280
4306
|
{
|
|
4281
4307
|
title: "Rollback Deployment",
|
|
4282
4308
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4283
|
-
description:
|
|
4309
|
+
description: dedent16`
|
|
4310
|
+
Roll the group back to the version that was live before the current one. Call lexq_groups_get first: currentVersionName is live now, previousVersionName is what a rollback restores, and a rollback is rejected while canRollback is false. A rollback needs a previous version that is still ACTIVE and whose effective end has not passed (P-058).
|
|
4311
|
+
|
|
4312
|
+
Pass the currentVersionId and currentDeploymentId from that call as expectedCurrentVersionId and expectedCurrentDeploymentId. If anyone deployed, rolled back or undeployed in between, the rollback is rejected with 409 and nothing changes: P-062 if another version or no version is live, P-063 if the same version is live but from a newer deployment. Read the group again and check with the user before retrying.
|
|
4313
|
+
`,
|
|
4284
4314
|
inputSchema: z6.object({
|
|
4285
4315
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4316
|
+
expectedCurrentVersionId: z6.string().uuid().describe("currentVersionId from your last read of the group"),
|
|
4317
|
+
expectedCurrentDeploymentId: z6.string().uuid().describe("currentDeploymentId from your last read of the group"),
|
|
4286
4318
|
memo: z6.string().min(1).describe("Rollback reason (required)")
|
|
4287
4319
|
})
|
|
4288
4320
|
},
|
|
4289
|
-
async ({ groupId, memo }) => callApi("POST", `policy-groups/${groupId}/rollback`, {
|
|
4290
|
-
body: { memo }
|
|
4321
|
+
async ({ groupId, expectedCurrentVersionId, expectedCurrentDeploymentId, memo }) => callApi("POST", `policy-groups/${groupId}/rollback`, {
|
|
4322
|
+
body: { memo, expectedCurrentVersionId, expectedCurrentDeploymentId }
|
|
4291
4323
|
})
|
|
4292
4324
|
);
|
|
4293
4325
|
server.registerTool(
|
|
@@ -4387,7 +4419,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4387
4419
|
{
|
|
4388
4420
|
title: "Deployment Overview",
|
|
4389
4421
|
annotations: { readOnlyHint: true },
|
|
4390
|
-
description: "Show current deployment status of all groups \u2014 which version is live, last deployment type, and deployer.",
|
|
4422
|
+
description: "Show current deployment status of all groups \u2014 which version is live, last deployment type, and deployer. Each group also carries currentDeploymentId, previousVersionName (the version a rollback restores) and canRollback, as in lexq_groups_get.",
|
|
4391
4423
|
inputSchema: z6.object({})
|
|
4392
4424
|
},
|
|
4393
4425
|
async () => callApi("GET", "deployments/overview")
|
|
@@ -4423,14 +4455,14 @@ function registerDeployTools(server, callApi) {
|
|
|
4423
4455
|
|
|
4424
4456
|
// src/mcp/tools/analytics.ts
|
|
4425
4457
|
import { z as z7 } from "zod";
|
|
4426
|
-
import
|
|
4458
|
+
import dedent17 from "dedent";
|
|
4427
4459
|
function registerAnalyticsTools(server, callApi) {
|
|
4428
4460
|
server.registerTool(
|
|
4429
4461
|
"lexq_dry_run",
|
|
4430
4462
|
{
|
|
4431
4463
|
title: "Dry Run",
|
|
4432
4464
|
annotations: { readOnlyHint: true },
|
|
4433
|
-
description:
|
|
4465
|
+
description: dedent17`
|
|
4434
4466
|
Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
|
|
4435
4467
|
|
|
4436
4468
|
Returns:
|
|
@@ -4460,7 +4492,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4460
4492
|
{
|
|
4461
4493
|
title: "Dry Run Compare",
|
|
4462
4494
|
annotations: { readOnlyHint: true },
|
|
4463
|
-
description:
|
|
4495
|
+
description: dedent17`
|
|
4464
4496
|
Compare dry run results between two versions using the same input facts. Useful for validating changes.
|
|
4465
4497
|
|
|
4466
4498
|
Returns:
|
|
@@ -4498,7 +4530,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4498
4530
|
{
|
|
4499
4531
|
title: "Start Simulation",
|
|
4500
4532
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
4501
|
-
description:
|
|
4533
|
+
description: dedent17`
|
|
4502
4534
|
Start an Impact Simulation against historical, uploaded, or inline data.
|
|
4503
4535
|
|
|
4504
4536
|
dataset.type and dataset.source are BOTH required, and must be paired:
|
|
@@ -4603,7 +4635,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4603
4635
|
{
|
|
4604
4636
|
title: "Upload Dataset",
|
|
4605
4637
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
4606
|
-
description:
|
|
4638
|
+
description: dedent17`
|
|
4607
4639
|
Upload inline CSV or JSON content as a simulation dataset.
|
|
4608
4640
|
The content is uploaded to S3 and a path is returned in the "path" field.
|
|
4609
4641
|
|
|
@@ -5156,7 +5188,7 @@ function startMcpServer() {
|
|
|
5156
5188
|
function registerServeCommand(program) {
|
|
5157
5189
|
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(
|
|
5158
5190
|
"after",
|
|
5159
|
-
|
|
5191
|
+
dedent18`
|
|
5160
5192
|
|
|
5161
5193
|
Example:
|
|
5162
5194
|
$ lexq serve --mcp
|
|
@@ -5187,11 +5219,11 @@ function registerServeCommand(program) {
|
|
|
5187
5219
|
|
|
5188
5220
|
// src/commands/domain-templates.ts
|
|
5189
5221
|
import "commander";
|
|
5190
|
-
import
|
|
5222
|
+
import dedent19 from "dedent";
|
|
5191
5223
|
function registerDomainTemplateCommands(program) {
|
|
5192
5224
|
const templates = program.command("domain-templates").description("Browse and apply domain templates").addHelpText(
|
|
5193
5225
|
"after",
|
|
5194
|
-
|
|
5226
|
+
dedent19`
|
|
5195
5227
|
|
|
5196
5228
|
A domain template is an industry-specific starter pack of fact
|
|
5197
5229
|
definitions and sample rules. Applying one provisions a ready-to-use
|
|
@@ -5239,7 +5271,7 @@ function registerDomainTemplateCommands(program) {
|
|
|
5239
5271
|
});
|
|
5240
5272
|
templates.command("preview").description("Preview what a domain template provisions").requiredOption("--template <key>", "Domain template key (e.g. ECOMMERCE)").addHelpText(
|
|
5241
5273
|
"after",
|
|
5242
|
-
|
|
5274
|
+
dedent19`
|
|
5243
5275
|
|
|
5244
5276
|
Read-only dry run — shows the fact definitions and sample rules the
|
|
5245
5277
|
template will create. Nothing is provisioned.
|
|
@@ -5268,7 +5300,7 @@ function registerDomainTemplateCommands(program) {
|
|
|
5268
5300
|
});
|
|
5269
5301
|
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(
|
|
5270
5302
|
"after",
|
|
5271
|
-
|
|
5303
|
+
dedent19`
|
|
5272
5304
|
|
|
5273
5305
|
Creates the template's fact definitions and a new DRAFT policy group
|
|
5274
5306
|
populated with its sample rules. A fact that already exists with the
|
package/dist/mcp/register.js
CHANGED
|
@@ -84,7 +84,7 @@ function registerGroupTools(server, callApi) {
|
|
|
84
84
|
{
|
|
85
85
|
title: "Get Policy Group",
|
|
86
86
|
annotations: { readOnlyHint: true },
|
|
87
|
-
description: "Get a single policy group by ID.",
|
|
87
|
+
description: "Get a single policy group by ID. Its currentVersionId and currentDeploymentId are what lexq_deploy_rollback expects, and previousVersionName is the version a rollback restores.",
|
|
88
88
|
inputSchema: z2.object({
|
|
89
89
|
groupId: z2.string().uuid().describe("Policy group ID")
|
|
90
90
|
})
|
|
@@ -182,10 +182,10 @@ function registerGroupTools(server, callApi) {
|
|
|
182
182
|
{
|
|
183
183
|
title: "Start A/B Test",
|
|
184
184
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
185
|
-
description: "Start an A/B test on a policy group. Requires a challenger version ID and traffic rate. The split is computed from context.trafficKey on each execution request; requests that omit it never reach the challenger and the test stays at 0%.",
|
|
185
|
+
description: "Start an A/B test on a policy group. Requires a challenger version ID and traffic rate. The challenger must be a published version of the same group; a version from another group is rejected with P-016. The split is computed from context.trafficKey on each execution request; requests that omit it never reach the challenger and the test stays at 0%.",
|
|
186
186
|
inputSchema: z2.object({
|
|
187
187
|
groupId: z2.string().uuid().describe("Policy group ID"),
|
|
188
|
-
testVersionId: z2.string().uuid().describe("Challenger version ID
|
|
188
|
+
testVersionId: z2.string().uuid().describe("Challenger version ID. Must be a published version of this group"),
|
|
189
189
|
trafficRate: z2.number().int().min(1).max(99).describe("Traffic percentage routed to challenger (1-99)")
|
|
190
190
|
})
|
|
191
191
|
},
|
|
@@ -778,6 +778,7 @@ function registerFactTools(server, callApi) {
|
|
|
778
778
|
}
|
|
779
779
|
|
|
780
780
|
// src/mcp/tools/deploy.ts
|
|
781
|
+
import dedent2 from "dedent";
|
|
781
782
|
import { z as z6 } from "zod";
|
|
782
783
|
function registerDeployTools(server, callApi) {
|
|
783
784
|
server.registerTool(
|
|
@@ -815,14 +816,20 @@ function registerDeployTools(server, callApi) {
|
|
|
815
816
|
{
|
|
816
817
|
title: "Rollback Deployment",
|
|
817
818
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
818
|
-
description:
|
|
819
|
+
description: dedent2`
|
|
820
|
+
Roll the group back to the version that was live before the current one. Call lexq_groups_get first: currentVersionName is live now, previousVersionName is what a rollback restores, and a rollback is rejected while canRollback is false. A rollback needs a previous version that is still ACTIVE and whose effective end has not passed (P-058).
|
|
821
|
+
|
|
822
|
+
Pass the currentVersionId and currentDeploymentId from that call as expectedCurrentVersionId and expectedCurrentDeploymentId. If anyone deployed, rolled back or undeployed in between, the rollback is rejected with 409 and nothing changes: P-062 if another version or no version is live, P-063 if the same version is live but from a newer deployment. Read the group again and check with the user before retrying.
|
|
823
|
+
`,
|
|
819
824
|
inputSchema: z6.object({
|
|
820
825
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
826
|
+
expectedCurrentVersionId: z6.string().uuid().describe("currentVersionId from your last read of the group"),
|
|
827
|
+
expectedCurrentDeploymentId: z6.string().uuid().describe("currentDeploymentId from your last read of the group"),
|
|
821
828
|
memo: z6.string().min(1).describe("Rollback reason (required)")
|
|
822
829
|
})
|
|
823
830
|
},
|
|
824
|
-
async ({ groupId, memo }) => callApi("POST", `policy-groups/${groupId}/rollback`, {
|
|
825
|
-
body: { memo }
|
|
831
|
+
async ({ groupId, expectedCurrentVersionId, expectedCurrentDeploymentId, memo }) => callApi("POST", `policy-groups/${groupId}/rollback`, {
|
|
832
|
+
body: { memo, expectedCurrentVersionId, expectedCurrentDeploymentId }
|
|
826
833
|
})
|
|
827
834
|
);
|
|
828
835
|
server.registerTool(
|
|
@@ -922,7 +929,7 @@ function registerDeployTools(server, callApi) {
|
|
|
922
929
|
{
|
|
923
930
|
title: "Deployment Overview",
|
|
924
931
|
annotations: { readOnlyHint: true },
|
|
925
|
-
description: "Show current deployment status of all groups \u2014 which version is live, last deployment type, and deployer.",
|
|
932
|
+
description: "Show current deployment status of all groups \u2014 which version is live, last deployment type, and deployer. Each group also carries currentDeploymentId, previousVersionName (the version a rollback restores) and canRollback, as in lexq_groups_get.",
|
|
926
933
|
inputSchema: z6.object({})
|
|
927
934
|
},
|
|
928
935
|
async () => callApi("GET", "deployments/overview")
|
|
@@ -958,14 +965,14 @@ function registerDeployTools(server, callApi) {
|
|
|
958
965
|
|
|
959
966
|
// src/mcp/tools/analytics.ts
|
|
960
967
|
import { z as z7 } from "zod";
|
|
961
|
-
import
|
|
968
|
+
import dedent3 from "dedent";
|
|
962
969
|
function registerAnalyticsTools(server, callApi) {
|
|
963
970
|
server.registerTool(
|
|
964
971
|
"lexq_dry_run",
|
|
965
972
|
{
|
|
966
973
|
title: "Dry Run",
|
|
967
974
|
annotations: { readOnlyHint: true },
|
|
968
|
-
description:
|
|
975
|
+
description: dedent3`
|
|
969
976
|
Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
|
|
970
977
|
|
|
971
978
|
Returns:
|
|
@@ -995,7 +1002,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
995
1002
|
{
|
|
996
1003
|
title: "Dry Run Compare",
|
|
997
1004
|
annotations: { readOnlyHint: true },
|
|
998
|
-
description:
|
|
1005
|
+
description: dedent3`
|
|
999
1006
|
Compare dry run results between two versions using the same input facts. Useful for validating changes.
|
|
1000
1007
|
|
|
1001
1008
|
Returns:
|
|
@@ -1033,7 +1040,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1033
1040
|
{
|
|
1034
1041
|
title: "Start Simulation",
|
|
1035
1042
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
1036
|
-
description:
|
|
1043
|
+
description: dedent3`
|
|
1037
1044
|
Start an Impact Simulation against historical, uploaded, or inline data.
|
|
1038
1045
|
|
|
1039
1046
|
dataset.type and dataset.source are BOTH required, and must be paired:
|
|
@@ -1138,7 +1145,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1138
1145
|
{
|
|
1139
1146
|
title: "Upload Dataset",
|
|
1140
1147
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
1141
|
-
description:
|
|
1148
|
+
description: dedent3`
|
|
1142
1149
|
Upload inline CSV or JSON content as a simulation dataset.
|
|
1143
1150
|
The content is uploaded to S3 and a path is returned in the "path" field.
|
|
1144
1151
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexq/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"mcpName": "io.lexq/lexq",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "The Decision Operations Platform for engineering teams",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"lexq": "./dist/index.js"
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"test:command-options": "node tests/command-options.mjs",
|
|
45
45
|
"test:value-domain": "pnpm build && node tests/value-domain.mjs",
|
|
46
46
|
"test:fact-update": "pnpm build && node tests/fact-update.mjs",
|
|
47
|
+
"test:rollback": "pnpm build && node tests/rollback.mjs",
|
|
47
48
|
"prepublishOnly": "pnpm build",
|
|
48
49
|
"knip": "knip",
|
|
49
50
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
@@ -57,7 +58,11 @@
|
|
|
57
58
|
"ai-agent",
|
|
58
59
|
"agent-skills",
|
|
59
60
|
"simulation",
|
|
60
|
-
"business-rules"
|
|
61
|
+
"business-rules",
|
|
62
|
+
"developer-tools",
|
|
63
|
+
"decision-management",
|
|
64
|
+
"mcp",
|
|
65
|
+
"mcp-server"
|
|
61
66
|
],
|
|
62
67
|
"author": "LexQ, LLC",
|
|
63
68
|
"license": "Apache-2.0",
|
|
@@ -220,8 +220,9 @@ lexq groups ab-test start --group-id <groupId> --version-id <activeVersionId> --
|
|
|
220
220
|
|
|
221
221
|
`--traffic-rate` is the percentage (1–99) of traffic routed to the test version.
|
|
222
222
|
|
|
223
|
-
The test version must be `ACTIVE` (published) and different from the live version.
|
|
224
|
-
scheduled deployment for the
|
|
223
|
+
The test version must be a version of the same group, `ACTIVE` (published), and different from the live version. A
|
|
224
|
+
version from another group is rejected with `P-016`. Starting a test cancels any pending scheduled deployment for the
|
|
225
|
+
group.
|
|
225
226
|
|
|
226
227
|
### Adjust Traffic
|
|
227
228
|
|
|
@@ -352,17 +352,24 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
352
352
|
**Goal:** Something went wrong in production — revert to the previous version.
|
|
353
353
|
|
|
354
354
|
```bash
|
|
355
|
-
# 1.
|
|
356
|
-
|
|
355
|
+
# 1. Read the group: currentVersionName is live now, previousVersionName is what a
|
|
356
|
+
# rollback restores, and a rollback is rejected while canRollback is false
|
|
357
|
+
lexq groups get --id <gid>
|
|
357
358
|
|
|
358
|
-
# 2.
|
|
359
|
-
lexq deploy rollback --group-id <gid> --memo "Reverting due to increased error rate"
|
|
359
|
+
# 2. Roll back with the IDs from step 1
|
|
360
|
+
lexq deploy rollback --group-id <gid> --memo "Reverting due to increased error rate" \
|
|
361
|
+
--expected-current-version-id <currentVersionId> \
|
|
362
|
+
--expected-current-deployment-id <currentDeploymentId>
|
|
360
363
|
|
|
361
364
|
# 3. Verify
|
|
362
365
|
lexq deploy overview
|
|
363
366
|
lexq history stats
|
|
364
367
|
```
|
|
365
368
|
|
|
369
|
+
If anyone deployed, rolled back or undeployed after step 1, step 2 is rejected with `409` and
|
|
370
|
+
nothing changes: `P-062` if another version or no version is live, `P-063` if the same version is
|
|
371
|
+
live but from a newer deployment. Start again from step 1.
|
|
372
|
+
|
|
366
373
|
---
|
|
367
374
|
|
|
368
375
|
## Recipe 7: Monitoring + Failure Triage
|