@lexq/cli 0.3.1 → 1.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.
@@ -5,6 +5,7 @@ function registerStatusTools(server, callApi) {
5
5
  "lexq_whoami",
6
6
  {
7
7
  title: "Who Am I",
8
+ annotations: { readOnlyHint: true },
8
9
  description: "Show current authentication info (tenant ID, user ID, role).",
9
10
  inputSchema: z.object({})
10
11
  },
@@ -39,6 +40,7 @@ function registerGroupTools(server, callApi) {
39
40
  "lexq_groups_list",
40
41
  {
41
42
  title: "List Policy Groups",
43
+ annotations: { readOnlyHint: true },
42
44
  description: "List all policy groups (tenant-wide, priority ASC).",
43
45
  inputSchema: z2.object({})
44
46
  },
@@ -48,6 +50,7 @@ function registerGroupTools(server, callApi) {
48
50
  "lexq_groups_get",
49
51
  {
50
52
  title: "Get Policy Group",
53
+ annotations: { readOnlyHint: true },
51
54
  description: "Get a single policy group by ID.",
52
55
  inputSchema: z2.object({
53
56
  groupId: z2.string().uuid().describe("Policy group ID")
@@ -59,6 +62,7 @@ function registerGroupTools(server, callApi) {
59
62
  "lexq_groups_create",
60
63
  {
61
64
  title: "Create Policy Group",
65
+ annotations: { readOnlyHint: false, destructiveHint: false },
62
66
  description: "Create a new policy group. Requires name. Priority is auto-assigned (appended last, tenant-wide); use lexq_groups_reorder to change order. Optionally set conflict resolution, activation group, and description. Policy groups that share an activationGroup form a cluster and must share the same activationMode / activationStrategy / executionLimit; executionLimit is how many of those groups run, not how many rules.",
63
67
  inputSchema: z2.object({
64
68
  name: z2.string().describe("Group name (unique among non-ARCHIVED)"),
@@ -89,6 +93,7 @@ function registerGroupTools(server, callApi) {
89
93
  "lexq_groups_update",
90
94
  {
91
95
  title: "Update Policy Group",
96
+ annotations: { readOnlyHint: false, destructiveHint: true },
92
97
  description: "Update a policy group. Only provided fields are updated; omitted fields remain unchanged.",
93
98
  inputSchema: z2.object({
94
99
  groupId: z2.string().uuid().describe("Policy group ID"),
@@ -111,6 +116,7 @@ function registerGroupTools(server, callApi) {
111
116
  "lexq_groups_delete",
112
117
  {
113
118
  title: "Delete Policy Group",
119
+ annotations: { readOnlyHint: false, destructiveHint: true },
114
120
  description: "Archive a policy group. Only non-live groups can be deleted. This is irreversible.",
115
121
  inputSchema: z2.object({
116
122
  groupId: z2.string().uuid().describe("Policy group ID")
@@ -122,6 +128,7 @@ function registerGroupTools(server, callApi) {
122
128
  "lexq_groups_reorder",
123
129
  {
124
130
  title: "Reorder Policy Groups",
131
+ annotations: { readOnlyHint: false, destructiveHint: true },
125
132
  description: "Reorder policy groups by priority. Priority is tenant-wide and flat (1...N continuous); array index 0 = priority 1 (highest precedence). activationGroup is not affected \u2014 this only changes priority.",
126
133
  inputSchema: z2.object({
127
134
  groupIds: z2.array(z2.string().uuid()).describe("Group IDs in desired priority order (index 0 = priority 1)")
@@ -139,6 +146,7 @@ function registerGroupTools(server, callApi) {
139
146
  "lexq_ab_test_start",
140
147
  {
141
148
  title: "Start A/B Test",
149
+ annotations: { readOnlyHint: false, destructiveHint: false },
142
150
  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%.",
143
151
  inputSchema: z2.object({
144
152
  groupId: z2.string().uuid().describe("Policy group ID"),
@@ -152,6 +160,7 @@ function registerGroupTools(server, callApi) {
152
160
  "lexq_ab_test_stop",
153
161
  {
154
162
  title: "Stop A/B Test",
163
+ annotations: { readOnlyHint: false, destructiveHint: true },
155
164
  description: "Stop a running A/B test. All traffic is restored to the control (current) version.",
156
165
  inputSchema: z2.object({
157
166
  groupId: z2.string().uuid().describe("Policy group ID")
@@ -163,6 +172,7 @@ function registerGroupTools(server, callApi) {
163
172
  "lexq_ab_test_adjust",
164
173
  {
165
174
  title: "Adjust A/B Test",
175
+ annotations: { readOnlyHint: false, destructiveHint: true },
166
176
  description: "Adjust traffic rate of a running A/B test.",
167
177
  inputSchema: z2.object({
168
178
  groupId: z2.string().uuid().describe("Policy group ID"),
@@ -238,6 +248,7 @@ function registerVersionTools(server, callApi) {
238
248
  "lexq_versions_list",
239
249
  {
240
250
  title: "List Policy Versions",
251
+ annotations: { readOnlyHint: true },
241
252
  description: "List all versions of a policy group.",
242
253
  inputSchema: z3.object({
243
254
  groupId: z3.string().uuid().describe("Policy group ID"),
@@ -253,6 +264,7 @@ function registerVersionTools(server, callApi) {
253
264
  "lexq_versions_get",
254
265
  {
255
266
  title: "Get Policy Version",
267
+ annotations: { readOnlyHint: true },
256
268
  description: "Get a single version by ID, including its rules and fact requirements.",
257
269
  inputSchema: z3.object({
258
270
  groupId: z3.string().uuid().describe("Policy group ID"),
@@ -265,6 +277,7 @@ function registerVersionTools(server, callApi) {
265
277
  "lexq_versions_create",
266
278
  {
267
279
  title: "Create Policy Version",
280
+ annotations: { readOnlyHint: false, destructiveHint: false },
268
281
  description: "Create a new DRAFT version in a policy group. Optionally provide a commit message and effective date range.",
269
282
  inputSchema: z3.object({
270
283
  groupId: z3.string().uuid().describe("Policy group ID"),
@@ -279,6 +292,7 @@ function registerVersionTools(server, callApi) {
279
292
  "lexq_versions_update",
280
293
  {
281
294
  title: "Update Policy Version",
295
+ annotations: { readOnlyHint: false, destructiveHint: true },
282
296
  description: "Update a DRAFT version. Only DRAFT versions can be modified. Only provided fields are changed.",
283
297
  inputSchema: z3.object({
284
298
  groupId: z3.string().uuid().describe("Policy group ID"),
@@ -294,6 +308,7 @@ function registerVersionTools(server, callApi) {
294
308
  "lexq_versions_delete",
295
309
  {
296
310
  title: "Delete Policy Version",
311
+ annotations: { readOnlyHint: false, destructiveHint: true },
297
312
  description: "Delete a DRAFT version. Only DRAFT versions can be deleted.",
298
313
  inputSchema: z3.object({
299
314
  groupId: z3.string().uuid().describe("Policy group ID"),
@@ -306,6 +321,7 @@ function registerVersionTools(server, callApi) {
306
321
  "lexq_versions_clone",
307
322
  {
308
323
  title: "Clone Policy Version",
324
+ annotations: { readOnlyHint: false, destructiveHint: false },
309
325
  description: "Clone an existing version to create a new DRAFT. Useful when the source version is already published.",
310
326
  inputSchema: z3.object({
311
327
  groupId: z3.string().uuid().describe("Policy group ID"),
@@ -339,6 +355,7 @@ function registerRuleTools(server, callApi) {
339
355
  "lexq_rules_list",
340
356
  {
341
357
  title: "List Rules",
358
+ annotations: { readOnlyHint: true },
342
359
  description: "List all rules in a version (priority ASC). Returns summary with conditionSummary and actionSummary.",
343
360
  inputSchema: z4.object({
344
361
  groupId: z4.string().uuid().describe("Policy group ID"),
@@ -351,6 +368,7 @@ function registerRuleTools(server, callApi) {
351
368
  "lexq_rules_get",
352
369
  {
353
370
  title: "Get Rule Detail",
371
+ annotations: { readOnlyHint: true },
354
372
  description: "Get full rule detail including condition tree and action definitions.",
355
373
  inputSchema: z4.object({
356
374
  groupId: z4.string().uuid().describe("Policy group ID"),
@@ -364,6 +382,7 @@ function registerRuleTools(server, callApi) {
364
382
  "lexq_rules_create",
365
383
  {
366
384
  title: "Create Rule",
385
+ annotations: { readOnlyHint: false, destructiveHint: false },
367
386
  description: dedent`
368
387
  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.
369
388
 
@@ -442,6 +461,7 @@ function registerRuleTools(server, callApi) {
442
461
  "lexq_rules_update",
443
462
  {
444
463
  title: "Update Rule",
464
+ annotations: { readOnlyHint: false, destructiveHint: true },
445
465
  description: "Update an existing rule in a DRAFT version. Only provided fields are changed.",
446
466
  inputSchema: z4.object({
447
467
  groupId: z4.string().uuid().describe("Policy group ID"),
@@ -463,6 +483,7 @@ function registerRuleTools(server, callApi) {
463
483
  "lexq_rules_delete",
464
484
  {
465
485
  title: "Delete Rule",
486
+ annotations: { readOnlyHint: false, destructiveHint: true },
466
487
  description: "Delete a rule from a DRAFT version.",
467
488
  inputSchema: z4.object({
468
489
  groupId: z4.string().uuid().describe("Policy group ID"),
@@ -483,6 +504,7 @@ function registerRuleTools(server, callApi) {
483
504
  "lexq_rules_reorder",
484
505
  {
485
506
  title: "Reorder Rules",
507
+ annotations: { readOnlyHint: false, destructiveHint: true },
486
508
  description: "Reorder rules by specifying rule IDs in desired order. Priorities are assigned 1...N (1-based, continuous); array index 0 = priority 1 (highest precedence).",
487
509
  inputSchema: z4.object({
488
510
  groupId: z4.string().uuid().describe("Policy group ID"),
@@ -504,6 +526,7 @@ function registerRuleTools(server, callApi) {
504
526
  "lexq_rules_toggle",
505
527
  {
506
528
  title: "Toggle Rule",
529
+ annotations: { readOnlyHint: false, destructiveHint: true },
507
530
  description: "Enable or disable a rule without deleting it.",
508
531
  inputSchema: z4.object({
509
532
  groupId: z4.string().uuid().describe("Policy group ID"),
@@ -535,6 +558,7 @@ function registerFactTools(server, callApi) {
535
558
  "lexq_facts_list",
536
559
  {
537
560
  title: "List Fact Definitions",
561
+ annotations: { readOnlyHint: true },
538
562
  description: "List all fact definitions (input variable schema). Shows key, type, required, and PII status. Always check this before creating rules.",
539
563
  inputSchema: z5.object({
540
564
  page: z5.number().int().min(0).default(0).describe("Page number"),
@@ -552,6 +576,7 @@ function registerFactTools(server, callApi) {
552
576
  "lexq_facts_create",
553
577
  {
554
578
  title: "Create Fact Definition",
579
+ annotations: { readOnlyHint: false, destructiveHint: false },
555
580
  description: "Register a new input variable. Key starts with a letter, then letters, numbers, and underscores (e.g. paymentAmount). Casing is not enforced. Types: STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER.",
556
581
  inputSchema: z5.object({
557
582
  key: z5.string().regex(FACT_KEY_PATTERN).describe("Variable key. Any casing; must start with a letter."),
@@ -561,6 +586,15 @@ function registerFactTools(server, callApi) {
561
586
  isRequired: z5.boolean().default(false).describe("Whether this fact is required for rule evaluation"),
562
587
  isPii: z5.boolean().default(false).describe(
563
588
  "Mark as PII \u2014 masked on every read surface, revealable only in the console (audited)"
589
+ ),
590
+ valueDomain: z5.object({
591
+ allowedValues: z5.array(z5.union([z5.string(), z5.number()])).optional().describe(
592
+ "Accepted values. For list types the constraint applies to each element, not to the list as a whole."
593
+ ),
594
+ min: z5.number().optional().describe("Lower bound, inclusive. Numeric types only."),
595
+ max: z5.number().optional().describe("Upper bound, inclusive. Numeric types only.")
596
+ }).optional().describe(
597
+ "Which values this fact accepts. Omit for no constraint. STRING and LIST_STRING take allowedValues only; NUMBER and LIST_NUMBER take all three; BOOLEAN takes none. Declaring it lets the rule editor offer a dropdown and rejects a rule literal that could never match."
564
598
  )
565
599
  })
566
600
  },
@@ -570,6 +604,7 @@ function registerFactTools(server, callApi) {
570
604
  "lexq_facts_update",
571
605
  {
572
606
  title: "Update Fact Definition",
607
+ annotations: { readOnlyHint: false, destructiveHint: true },
573
608
  description: "Update a fact definition. The key is immutable. The type can change only while no rule references the fact; if any does, the call fails with FD-007 and reports the count. Only the fields you send are changed. System facts accept name, description, and PII only.",
574
609
  inputSchema: z5.object({
575
610
  factId: z5.string().uuid().describe("Fact definition ID"),
@@ -579,7 +614,16 @@ function registerFactTools(server, callApi) {
579
614
  "Value type. Omit to leave it unchanged. Changing it fails with FD-007 while any rule references the fact."
580
615
  ),
581
616
  isRequired: z5.boolean().optional().describe("Required flag"),
582
- isPii: z5.boolean().optional().describe("PII flag \u2014 enables/disables masking (changeable even on system facts)")
617
+ isPii: z5.boolean().optional().describe("PII flag \u2014 enables/disables masking (changeable even on system facts)"),
618
+ valueDomain: z5.object({
619
+ allowedValues: z5.array(z5.union([z5.string(), z5.number()])).optional().describe(
620
+ "Accepted values. For list types the constraint applies to each element, not to the list as a whole."
621
+ ),
622
+ min: z5.number().optional().describe("Lower bound, inclusive. Numeric types only."),
623
+ max: z5.number().optional().describe("Upper bound, inclusive. Numeric types only.")
624
+ }).optional().describe(
625
+ "Which values this fact accepts. Three states: omit the field to leave the constraint alone, send {} to remove it, or send a populated object to replace it. Narrowing it does not rewrite rules that already reference this fact \u2014 they keep working, and the next save is judged against the new constraint."
626
+ )
583
627
  })
584
628
  },
585
629
  async ({ factId, ...body }) => callApi("PUT", `schema/facts/${factId}`, { body })
@@ -588,6 +632,7 @@ function registerFactTools(server, callApi) {
588
632
  "lexq_facts_delete",
589
633
  {
590
634
  title: "Delete Fact Definition",
635
+ annotations: { readOnlyHint: false, destructiveHint: true },
591
636
  description: "Delete a fact definition. System facts cannot be deleted. Neither can a fact that any rule references: that call fails with FD-006 and reports the count. Remove the references first.",
592
637
  inputSchema: z5.object({
593
638
  factId: z5.string().uuid().describe("Fact definition ID")
@@ -599,6 +644,7 @@ function registerFactTools(server, callApi) {
599
644
  "lexq_facts_action_metadata",
600
645
  {
601
646
  title: "Get Action Runtime Fact Metadata",
647
+ annotations: { readOnlyHint: true },
602
648
  description: "Retrieve runtime fact requirements per Action type. For each Action, shows which input facts must be present in the execution payload \u2014 e.g. MUTATE_FACT always requires its targetVar fact, plus refVar when one is specified. The factRequired flag describes the FACT, not the parameter: refVar is an optional parameter, but if you specify it the named fact must exist. A required fact absent at runtime throws \u2014 the engine never defaults to 0. Facts are supplied as input or written by a prior action in the same rule; only SET_FACT creates a fact from nothing. Static data, safe to cache in-session.",
603
649
  inputSchema: z5.object({})
604
650
  },
@@ -608,6 +654,7 @@ function registerFactTools(server, callApi) {
608
654
  "lexq_facts_unregistered",
609
655
  {
610
656
  title: "List Unregistered Facts",
657
+ annotations: { readOnlyHint: true },
611
658
  description: "List facts referenced by a version's rules but not yet defined (read-only \u2014 does not block publish/deploy, INV-4). Version-wide: covers every rule in the version. Each entry carries the inferred type, suggested name, and where it is referenced (condition/action). Register them with lexq_facts_create to enable type validation and the dry-run requirements analyzer.",
612
659
  inputSchema: z5.object({
613
660
  groupId: z5.string().uuid().describe("Policy group ID"),
@@ -620,6 +667,7 @@ function registerFactTools(server, callApi) {
620
667
  "lexq_facts_export",
621
668
  {
622
669
  title: "Export Fact Catalog",
670
+ annotations: { readOnlyHint: true },
623
671
  description: "Export the fact catalog. The two formats carry different things: CSV is the catalog as it stands, system facts included, for reading in a spreadsheet; JSON matches the shape that batch create accepts, so it can be fed straight back in, which is why it leaves out the fields that endpoint does not take. Returns the file contents as text.",
624
672
  inputSchema: z5.object({
625
673
  format: z5.enum(EXPORT_FORMATS).default("csv").describe("Export format"),
@@ -639,6 +687,7 @@ function registerDeployTools(server, callApi) {
639
687
  "lexq_deploy_publish",
640
688
  {
641
689
  title: "Publish Version",
690
+ annotations: { readOnlyHint: false, destructiveHint: false },
642
691
  description: "Publish a DRAFT version (DRAFT \u2192 ACTIVE). Locks the version from further edits. Must have at least one rule. Undefined facts referenced by rules do not block publishing (INV-4); call lexq_facts_unregistered first to review them.",
643
692
  inputSchema: z6.object({
644
693
  groupId: z6.string().uuid().describe("Policy group ID"),
@@ -652,6 +701,7 @@ function registerDeployTools(server, callApi) {
652
701
  "lexq_deploy_live",
653
702
  {
654
703
  title: "Deploy to Live",
704
+ annotations: { readOnlyHint: false, destructiveHint: true },
655
705
  description: "Deploy an ACTIVE (published) version to live traffic. Takes effect immediately. Versions whose effective start date has not arrived are rejected (P-037) \u2014 use lexq_deploy_schedule for those. Undefined facts do not block deployment (INV-4); use lexq_facts_unregistered to review what the version references but has not defined.",
656
706
  inputSchema: z6.object({
657
707
  groupId: z6.string().uuid().describe("Policy group ID"),
@@ -667,6 +717,7 @@ function registerDeployTools(server, callApi) {
667
717
  "lexq_deploy_rollback",
668
718
  {
669
719
  title: "Rollback Deployment",
720
+ annotations: { readOnlyHint: false, destructiveHint: true },
670
721
  description: "Rollback to the previous deployed version. Only available if there is a previous version.",
671
722
  inputSchema: z6.object({
672
723
  groupId: z6.string().uuid().describe("Policy group ID"),
@@ -681,6 +732,7 @@ function registerDeployTools(server, callApi) {
681
732
  "lexq_deploy_undeploy",
682
733
  {
683
734
  title: "Undeploy",
735
+ annotations: { readOnlyHint: false, destructiveHint: true },
684
736
  description: "Remove the live version from traffic. The version stays ACTIVE but no longer serves requests.",
685
737
  inputSchema: z6.object({
686
738
  groupId: z6.string().uuid().describe("Policy group ID"),
@@ -695,6 +747,7 @@ function registerDeployTools(server, callApi) {
695
747
  "lexq_deploy_schedule",
696
748
  {
697
749
  title: "Schedule Deployment",
750
+ annotations: { readOnlyHint: false, destructiveHint: false },
698
751
  description: "Schedule an ACTIVE version with a future effective start date to auto-deploy at that time (Scheduled Deployment). One pending schedule per group; manual deploy/rollback/undeploy, starting an A/B test, or archiving the group cancels it. The snapshot hash is sealed at scheduling and re-verified at execution (fail-closed).",
699
752
  inputSchema: z6.object({
700
753
  groupId: z6.string().uuid().describe("Policy group ID"),
@@ -710,6 +763,7 @@ function registerDeployTools(server, callApi) {
710
763
  "lexq_deploy_unschedule",
711
764
  {
712
765
  title: "Cancel Scheduled Deployment",
766
+ annotations: { readOnlyHint: false, destructiveHint: true },
713
767
  description: "Cancel the pending scheduled deployment for a group. The version itself is not affected. Fails with P-039 if no pending schedule exists.",
714
768
  inputSchema: z6.object({
715
769
  groupId: z6.string().uuid().describe("Policy group ID")
@@ -721,6 +775,7 @@ function registerDeployTools(server, callApi) {
721
775
  "lexq_deploy_schedules",
722
776
  {
723
777
  title: "List Scheduled Deployments",
778
+ annotations: { readOnlyHint: true },
724
779
  description: "List scheduled deployments across all groups (all statuses: PENDING, EXECUTED, CANCELED, FAILED), newest first.",
725
780
  inputSchema: z6.object({
726
781
  page: z6.number().int().min(0).default(0).describe("Page number"),
@@ -733,6 +788,7 @@ function registerDeployTools(server, callApi) {
733
788
  "lexq_deploy_history",
734
789
  {
735
790
  title: "Deployment History",
791
+ annotations: { readOnlyHint: true },
736
792
  description: "List deployment history across all groups.",
737
793
  inputSchema: z6.object({
738
794
  page: z6.number().int().min(0).default(0).describe("Page number"),
@@ -756,6 +812,7 @@ function registerDeployTools(server, callApi) {
756
812
  "lexq_deploy_detail",
757
813
  {
758
814
  title: "Deployment Detail",
815
+ annotations: { readOnlyHint: true },
759
816
  description: "Get detailed info about a specific deployment including snapshot hash and integrity check.",
760
817
  inputSchema: z6.object({
761
818
  deploymentId: z6.string().uuid().describe("Deployment ID")
@@ -767,6 +824,7 @@ function registerDeployTools(server, callApi) {
767
824
  "lexq_deploy_overview",
768
825
  {
769
826
  title: "Deployment Overview",
827
+ annotations: { readOnlyHint: true },
770
828
  description: "Show current deployment status of all groups \u2014 which version is live, last deployment type, and deployer.",
771
829
  inputSchema: z6.object({})
772
830
  },
@@ -776,6 +834,7 @@ function registerDeployTools(server, callApi) {
776
834
  "lexq_deploy_deployable",
777
835
  {
778
836
  title: "List Deployable Versions",
837
+ annotations: { readOnlyHint: true },
779
838
  description: "List ACTIVE (published) versions that can be deployed for a group. Use this to find which versions are available before calling deploy live.",
780
839
  inputSchema: z6.object({
781
840
  groupId: z6.string().uuid().describe("Policy group ID")
@@ -787,6 +846,7 @@ function registerDeployTools(server, callApi) {
787
846
  "lexq_deploy_diff",
788
847
  {
789
848
  title: "Deployment Diff",
849
+ annotations: { readOnlyHint: true },
790
850
  description: "Compare rule snapshots between two versions. Shows added, removed, and modified rules. Useful for reviewing changes before deploying a new version.",
791
851
  inputSchema: z6.object({
792
852
  baseVersionId: z6.string().uuid().describe("Base version ID (typically the current live)"),
@@ -807,6 +867,7 @@ function registerAnalyticsTools(server, callApi) {
807
867
  "lexq_dry_run",
808
868
  {
809
869
  title: "Dry Run",
870
+ annotations: { readOnlyHint: true },
810
871
  description: dedent2`
811
872
  Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
812
873
 
@@ -836,6 +897,7 @@ function registerAnalyticsTools(server, callApi) {
836
897
  "lexq_dry_run_compare",
837
898
  {
838
899
  title: "Dry Run Compare",
900
+ annotations: { readOnlyHint: true },
839
901
  description: dedent2`
840
902
  Compare dry run results between two versions using the same input facts. Useful for validating changes.
841
903
 
@@ -860,6 +922,7 @@ function registerAnalyticsTools(server, callApi) {
860
922
  "lexq_requirements",
861
923
  {
862
924
  title: "Analyze Requirements",
925
+ annotations: { readOnlyHint: true },
863
926
  description: "Analyze which input facts a version requires. Returns required keys, types, and an example request body.",
864
927
  inputSchema: z7.object({
865
928
  groupId: z7.string().uuid().describe("Policy group ID"),
@@ -872,6 +935,7 @@ function registerAnalyticsTools(server, callApi) {
872
935
  "lexq_simulation_start",
873
936
  {
874
937
  title: "Start Simulation",
938
+ annotations: { readOnlyHint: false, destructiveHint: false },
875
939
  description: dedent2`
876
940
  Start an Impact Simulation against historical, uploaded, or inline data.
877
941
 
@@ -913,6 +977,7 @@ function registerAnalyticsTools(server, callApi) {
913
977
  "lexq_simulation_status",
914
978
  {
915
979
  title: "Simulation Status",
980
+ annotations: { readOnlyHint: true },
916
981
  description: "Get simulation status and results. Poll until status is COMPLETED or FAILED.",
917
982
  inputSchema: z7.object({
918
983
  simulationId: z7.string().uuid().describe("Simulation ID")
@@ -924,6 +989,7 @@ function registerAnalyticsTools(server, callApi) {
924
989
  "lexq_simulation_list",
925
990
  {
926
991
  title: "List Simulations",
992
+ annotations: { readOnlyHint: true },
927
993
  description: "List simulation history with optional filters.",
928
994
  inputSchema: z7.object({
929
995
  page: z7.number().int().min(0).default(0).describe("Page number"),
@@ -945,6 +1011,7 @@ function registerAnalyticsTools(server, callApi) {
945
1011
  "lexq_simulation_cancel",
946
1012
  {
947
1013
  title: "Cancel Simulation",
1014
+ annotations: { readOnlyHint: false, destructiveHint: true },
948
1015
  description: "Cancel a running or pending simulation.",
949
1016
  inputSchema: z7.object({
950
1017
  simulationId: z7.string().uuid().describe("Simulation ID")
@@ -956,6 +1023,7 @@ function registerAnalyticsTools(server, callApi) {
956
1023
  "lexq_simulation_export",
957
1024
  {
958
1025
  title: "Export Simulation",
1026
+ annotations: { readOnlyHint: true },
959
1027
  description: "Export simulation results as JSON or CSV. Returns the raw data.",
960
1028
  inputSchema: z7.object({
961
1029
  simulationId: z7.string().uuid().describe("Simulation ID"),
@@ -970,6 +1038,7 @@ function registerAnalyticsTools(server, callApi) {
970
1038
  "lexq_dataset_upload",
971
1039
  {
972
1040
  title: "Upload Dataset",
1041
+ annotations: { readOnlyHint: false, destructiveHint: false },
973
1042
  description: dedent2`
974
1043
  Upload inline CSV or JSON content as a simulation dataset.
975
1044
  The content is uploaded to S3 and a path is returned in the "path" field.
@@ -1013,6 +1082,7 @@ function registerAnalyticsTools(server, callApi) {
1013
1082
  "lexq_dataset_template",
1014
1083
  {
1015
1084
  title: "Download Dataset Template",
1085
+ annotations: { readOnlyHint: true },
1016
1086
  description: "Generate a sample CSV or JSON template based on the required facts of a version. Use this to understand the expected data format before uploading a dataset.",
1017
1087
  inputSchema: z7.object({
1018
1088
  groupId: z7.string().uuid().describe("Policy group ID"),
@@ -1042,6 +1112,7 @@ function registerProfileTools(server, callApi) {
1042
1112
  "lexq_profile_overview",
1043
1113
  {
1044
1114
  title: "Group Latency Profile",
1115
+ annotations: { readOnlyHint: true },
1045
1116
  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; a percentile is withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 needs n \u2265 6, p95 n \u2265 60, p99 n \u2265 300 \u2014 display gate, separate from the n \u2265 ${MIN_SAMPLES} judgment gate). Baselines report INSUFFICIENT_COHORT when fewer than ${MIN_COHORT_SIZE} rules qualify. Rule detail comes from a deterministic ${PROFILE_SAMPLE_PERMILLE / 10}% sample of calls; TOTAL is recorded for every call. Defaults: last ${PROFILE_DEFAULT_WINDOW_HOURS}h, live version, cacheState HIT.`,
1046
1117
  inputSchema: z8.object({
1047
1118
  groupId: z8.string().uuid().describe("Policy group ID"),
@@ -1061,6 +1132,7 @@ function registerProfileTools(server, callApi) {
1061
1132
  "lexq_profile_rule",
1062
1133
  {
1063
1134
  title: "Rule Latency Detail",
1135
+ annotations: { readOnlyHint: true },
1064
1136
  description: `Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (${LATENCY_WINDOW_MILLIS / 1e3}s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 n \u2265 6, p95 n \u2265 60, p99 n \u2265 300). ` + RELATIVE_THRESHOLD,
1065
1137
  inputSchema: z8.object({
1066
1138
  groupId: z8.string().uuid().describe("Policy group ID"),
@@ -1085,6 +1157,7 @@ function registerReplayTools(server, callApi) {
1085
1157
  "lexq_replay_decision",
1086
1158
  {
1087
1159
  title: "Replay a Decision",
1160
+ annotations: { readOnlyHint: true },
1088
1161
  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). A replay sends no webhook, notification, or event: rule actions produce no outward effects.",
1089
1162
  inputSchema: z9.object({
1090
1163
  traceId: z9.string().describe("Trace ID of the past execution to replay"),
@@ -1097,6 +1170,7 @@ function registerReplayTools(server, callApi) {
1097
1170
  "lexq_replay_start",
1098
1171
  {
1099
1172
  title: "Start Window Replay (Blast Radius)",
1173
+ annotations: { readOnlyHint: false, destructiveHint: false },
1100
1174
  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.",
1101
1175
  inputSchema: z9.object({
1102
1176
  candidateVersionId: z9.string().uuid().describe("Version to re-evaluate against"),
@@ -1113,6 +1187,7 @@ function registerReplayTools(server, callApi) {
1113
1187
  "lexq_replay_status",
1114
1188
  {
1115
1189
  title: "Get Replay Job Status",
1190
+ annotations: { readOnlyHint: true },
1116
1191
  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.",
1117
1192
  inputSchema: z9.object({
1118
1193
  jobId: z9.string().describe("Replay job ID from lexq_replay_start")
@@ -1124,6 +1199,7 @@ function registerReplayTools(server, callApi) {
1124
1199
  "lexq_replay_list",
1125
1200
  {
1126
1201
  title: "List Replay Jobs",
1202
+ annotations: { readOnlyHint: true },
1127
1203
  description: "List window replay job history (reverse-chronological). Lightweight items \u2014 use lexq_replay_status for summary and changed samples.",
1128
1204
  inputSchema: z9.object({
1129
1205
  page: z9.number().int().min(0).default(0).describe("Page number"),
@@ -1136,6 +1212,7 @@ function registerReplayTools(server, callApi) {
1136
1212
  "lexq_replay_cancel",
1137
1213
  {
1138
1214
  title: "Cancel Replay Job",
1215
+ annotations: { readOnlyHint: false, destructiveHint: true },
1139
1216
  description: "Cooperatively cancel a PENDING or RUNNING window replay job. Other states are rejected. VIEWER role cannot cancel.",
1140
1217
  inputSchema: z9.object({
1141
1218
  jobId: z9.string().describe("Replay job ID")
@@ -1147,6 +1224,7 @@ function registerReplayTools(server, callApi) {
1147
1224
  "lexq_replay_export",
1148
1225
  {
1149
1226
  title: "Export Replay Result",
1227
+ annotations: { readOnlyHint: true },
1150
1228
  description: "Export a COMPLETED window replay job. A running job is rejected \u2014 a partial result reads as the whole thing on the receiving end. The two formats carry different things: CSV holds the effect blast radius, whose columns are fixed; JSON holds the changed samples and action parameters, which nest and whose keys differ per tenant. JSON is not a superset of CSV. Returns the file contents as text.",
1151
1229
  inputSchema: z9.object({
1152
1230
  jobId: z9.string().describe("Replay job ID"),
@@ -1164,6 +1242,7 @@ function registerHistoryTools(server, callApi) {
1164
1242
  "lexq_history_list",
1165
1243
  {
1166
1244
  title: "List Execution History",
1245
+ annotations: { readOnlyHint: true },
1167
1246
  description: "List policy execution history. Shows trace ID, group, version, status, match result, and latency.",
1168
1247
  inputSchema: z10.object({
1169
1248
  page: z10.number().int().min(0).default(0).describe("Page number"),
@@ -1191,6 +1270,7 @@ function registerHistoryTools(server, callApi) {
1191
1270
  "lexq_history_get",
1192
1271
  {
1193
1272
  title: "Get Execution Detail",
1273
+ annotations: { readOnlyHint: true },
1194
1274
  description: "Get full execution detail including inputFacts, mutatedFacts, generatedVariables, executionTraces, and decisionTraces.",
1195
1275
  inputSchema: z10.object({
1196
1276
  traceId: z10.string().describe("Trace ID from execution history")
@@ -1202,6 +1282,7 @@ function registerHistoryTools(server, callApi) {
1202
1282
  "lexq_history_stats",
1203
1283
  {
1204
1284
  title: "Execution Statistics",
1285
+ annotations: { readOnlyHint: true },
1205
1286
  description: "Get execution KPIs: total executions, success/failure counts, success rate, and average latency.",
1206
1287
  inputSchema: z10.object({
1207
1288
  groupId: z10.string().uuid().optional().describe("Filter by policy group"),
@@ -1226,6 +1307,7 @@ function registerProvenanceTools(server, callApi) {
1226
1307
  "lexq_provenance_get",
1227
1308
  {
1228
1309
  title: "Get Decision Provenance",
1310
+ annotations: { readOnlyHint: true },
1229
1311
  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.",
1230
1312
  inputSchema: z11.object({
1231
1313
  traceId: z11.string().describe("Trace ID of the execution")
@@ -1237,6 +1319,7 @@ function registerProvenanceTools(server, callApi) {
1237
1319
  "lexq_pii_reveals_list",
1238
1320
  {
1239
1321
  title: "List PII Reveal Audits",
1322
+ annotations: { readOnlyHint: true },
1240
1323
  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.",
1241
1324
  inputSchema: z11.object({
1242
1325
  page: z11.number().int().min(0).default(0).describe("Page number"),
@@ -1267,6 +1350,7 @@ function registerLogTools(server, callApi) {
1267
1350
  "lexq_logs_list",
1268
1351
  {
1269
1352
  title: "List Failure Logs",
1353
+ annotations: { readOnlyHint: true },
1270
1354
  description: "List system failure logs from background tasks (platform event webhooks, scheduled deployments).",
1271
1355
  inputSchema: z12.object({
1272
1356
  page: z12.number().int().min(0).default(0).describe("Page number"),
@@ -1292,6 +1376,7 @@ function registerLogTools(server, callApi) {
1292
1376
  "lexq_logs_get",
1293
1377
  {
1294
1378
  title: "Get Failure Log",
1379
+ annotations: { readOnlyHint: true },
1295
1380
  description: "Get failure log detail by ID.",
1296
1381
  inputSchema: z12.object({
1297
1382
  logId: z12.string().uuid().describe("Failure log ID")
@@ -1303,6 +1388,7 @@ function registerLogTools(server, callApi) {
1303
1388
  "lexq_logs_action",
1304
1389
  {
1305
1390
  title: "Process Failure Log",
1391
+ annotations: { readOnlyHint: false, destructiveHint: false },
1306
1392
  description: "Process a single failure log: RESOLVE (mark as manually fixed) or IGNORE (skip intentionally).",
1307
1393
  inputSchema: z12.object({
1308
1394
  logId: z12.string().uuid().describe("Failure log ID"),
@@ -1317,6 +1403,7 @@ function registerLogTools(server, callApi) {
1317
1403
  "lexq_logs_bulk_action",
1318
1404
  {
1319
1405
  title: "Bulk Process Failure Logs",
1406
+ annotations: { readOnlyHint: false, destructiveHint: false },
1320
1407
  description: "Process multiple failure logs at once. Provide an array of log IDs and the action.",
1321
1408
  inputSchema: z12.object({
1322
1409
  logIds: z12.array(z12.string().uuid()).describe("Array of failure log IDs"),
@@ -1336,6 +1423,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
1336
1423
  "lexq_webhook_subscriptions_list",
1337
1424
  {
1338
1425
  title: "List Webhook Subscriptions",
1426
+ annotations: { readOnlyHint: true },
1339
1427
  description: "List platform event webhook subscriptions. These receive deployment lifecycle notifications (publish, deploy, rollback, undeploy).",
1340
1428
  inputSchema: z13.object({
1341
1429
  page: z13.number().int().min(0).default(0).describe("Page number"),
@@ -1351,6 +1439,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
1351
1439
  "lexq_webhook_subscriptions_get",
1352
1440
  {
1353
1441
  title: "Get Webhook Subscription",
1442
+ annotations: { readOnlyHint: true },
1354
1443
  description: "Get webhook subscription detail by ID.",
1355
1444
  inputSchema: z13.object({
1356
1445
  id: z13.string().uuid().describe("Webhook subscription ID")
@@ -1362,6 +1451,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
1362
1451
  "lexq_webhook_subscriptions_save",
1363
1452
  {
1364
1453
  title: "Save Webhook Subscription",
1454
+ annotations: { readOnlyHint: false, destructiveHint: true },
1365
1455
  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": "..."}).',
1366
1456
  inputSchema: z13.object({
1367
1457
  id: z13.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
@@ -1379,6 +1469,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
1379
1469
  "lexq_webhook_subscriptions_delete",
1380
1470
  {
1381
1471
  title: "Delete Webhook Subscription",
1472
+ annotations: { readOnlyHint: false, destructiveHint: true },
1382
1473
  description: "Delete a webhook subscription by ID.",
1383
1474
  inputSchema: z13.object({
1384
1475
  id: z13.string().uuid().describe("Webhook subscription ID")
@@ -1390,6 +1481,7 @@ function registerWebhookSubscriptionTools(server, callApi) {
1390
1481
  "lexq_webhook_subscriptions_test",
1391
1482
  {
1392
1483
  title: "Test Webhook Subscription",
1484
+ annotations: { readOnlyHint: false, destructiveHint: false },
1393
1485
  description: "Send a test event to verify webhook connectivity. Returns the HTTP status code and success/failure message.",
1394
1486
  inputSchema: z13.object({
1395
1487
  id: z13.string().uuid().describe("Webhook subscription ID")
@@ -1406,6 +1498,7 @@ function registerDomainTemplateTools(server, callApi) {
1406
1498
  "lexq_domain_templates_list",
1407
1499
  {
1408
1500
  title: "List Domain Templates",
1501
+ annotations: { readOnlyHint: true },
1409
1502
  description: "List all domain templates. A domain template is a curated, industry-specific starter pack of fact definitions and sample rules (e.g. ECOMMERCE). Each entry reports its key, status (ACTIVE or COMING_SOON), and a summary of what it provisions. Call this before preview or apply to discover which templates can currently be applied.",
1410
1503
  inputSchema: z14.object({})
1411
1504
  },
@@ -1415,6 +1508,7 @@ function registerDomainTemplateTools(server, callApi) {
1415
1508
  "lexq_domain_templates_preview",
1416
1509
  {
1417
1510
  title: "Preview Domain Template",
1511
+ annotations: { readOnlyHint: true },
1418
1512
  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.",
1419
1513
  inputSchema: z14.object({
1420
1514
  template: z14.string().describe(
@@ -1428,6 +1522,7 @@ function registerDomainTemplateTools(server, callApi) {
1428
1522
  "lexq_domain_templates_apply",
1429
1523
  {
1430
1524
  title: "Apply Domain Template",
1525
+ annotations: { readOnlyHint: false, destructiveHint: false },
1431
1526
  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.",
1432
1527
  inputSchema: z14.object({
1433
1528
  template: z14.string().describe("Domain template key to apply (e.g. ECOMMERCE)."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexq/cli",
3
- "version": "0.3.1",
3
+ "version": "1.0.0",
4
4
  "mcpName": "io.lexq/lexq",
5
5
  "description": "LexQ CLI — manage policies, simulate rules, and deploy from the terminal. Built for humans and AI agents.",
6
6
  "type": "module",
@@ -30,16 +30,18 @@
30
30
  "typecheck": "tsc --noEmit",
31
31
  "enums": "node scripts/gen-enums.mjs",
32
32
  "enums:check": "node scripts/gen-enums.mjs --check",
33
- "constants:check": "node scripts/check-constants.mjs",
33
+ "prose:check": "node scripts/check-prose-mirrors.mjs",
34
34
  "surface:check": "node scripts/check-public-surface.mjs",
35
35
  "ab-key:check": "node scripts/check-ab-key.mjs",
36
36
  "registry:check": "node scripts/check-registry-manifest.mjs",
37
+ "protocol:check": "node scripts/check-protocol-revisions.mjs",
37
38
  "schemas:check": "node scripts/check-tool-schemas.mjs",
38
39
  "schemas:update": "node scripts/check-tool-schemas.mjs --update",
39
40
  "start": "node dist/index.js",
40
41
  "test:decimals": "pnpm build && node tests/exact-decimals.mjs",
41
42
  "test:fact-key": "node tests/fact-key.mjs",
42
43
  "test:export-format": "node tests/export-format.mjs",
44
+ "test:value-domain": "pnpm build && node tests/value-domain.mjs",
43
45
  "prepublishOnly": "pnpm build",
44
46
  "knip": "knip",
45
47
  "format": "prettier --write \"src/**/*.ts\"",