@lexq/cli 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -269,7 +269,7 @@ function registerAuthCommands(program) {
269
269
 
270
270
  Example:
271
271
  $ lexq auth whoami
272
- { "tenantId": "abc-123", "userId": "...", "role": "ADMIN", "apiKey": "lexq_us_****abcd" }
272
+ { "tenantId": "abc-123", "userId": "...", "role": "ADMIN", "tenantTimezone": "Asia/Seoul", "apiKey": "lexq_us_****abcd" }
273
273
  `
274
274
  ).action(async () => {
275
275
  try {
@@ -310,9 +310,10 @@ function registerStatusCommand(program) {
310
310
 
311
311
  Example:
312
312
  $ lexq status
313
- { "status": "ok", "latencyMs": 142, "tenantId": "abc-123", "role": "ADMIN" }
313
+ { "status": "ok", "latencyMs": 142, "tenantId": "abc-123", "role": "ADMIN", "tenantTimezone": "Asia/Seoul" }
314
314
 
315
315
  Use this to verify your API key is valid and the LexQ API is reachable.
316
+ tenantTimezone is the zone every yyyy-MM-dd date option is read in.
316
317
  `
317
318
  ).action(async () => {
318
319
  const globalOpts = program.opts();
@@ -326,7 +327,8 @@ function registerStatusCommand(program) {
326
327
  status: "ok",
327
328
  latencyMs: Date.now() - startTime,
328
329
  tenantId: info.tenantId,
329
- role: info.role
330
+ role: info.role,
331
+ tenantTimezone: info.tenantTimezone
330
332
  });
331
333
  } catch (error) {
332
334
  printError(error);
@@ -357,6 +359,22 @@ var MAX_SNAPSHOT_BYTES = 131072;
357
359
  // src/commands/groups.ts
358
360
  import "commander";
359
361
  import dedent3 from "dedent";
362
+
363
+ // src/lib/dates.ts
364
+ var ORG_DAY = "yyyy-MM-dd, a whole calendar day in the organization time zone (tenantTimezone in whoami), not UTC";
365
+ var START_DAY = `Start date, ${ORG_DAY}. Inclusive.`;
366
+ var END_DAY = `End date, ${ORG_DAY}. Inclusive: the whole day counts.`;
367
+ var WINDOW_START_DAY = `Window start date, ${ORG_DAY}. Inclusive.`;
368
+ var WINDOW_END_DAY = `Window end date, ${ORG_DAY}. Inclusive: the whole day counts.`;
369
+ var INSTANT = "ISO-8601 instant with Z or an offset, e.g. 2026-10-01T00:00:00Z or 2026-10-01T09:00:00+09:00. A bare date or a time without an offset is rejected.";
370
+ var STATS_DEFAULT_WINDOW_DAYS = 30;
371
+ var STATS_MAX_WINDOW_DAYS = 31;
372
+ var FAILURE_LOG_LOOKBACK_DAYS = 7;
373
+ function utcMinute(instant) {
374
+ return instant ? `${new Date(instant).toISOString().substring(0, 16)}Z` : "\u2013";
375
+ }
376
+
377
+ // src/commands/groups.ts
360
378
  function registerGroupCommands(program) {
361
379
  const groups = program.command("groups").description("Manage policy groups").addHelpText(
362
380
  "after",
@@ -396,7 +414,7 @@ function registerGroupCommands(program) {
396
414
  g.status,
397
415
  String(g.priority),
398
416
  g.currentVersionName ?? "\u2013",
399
- g.updatedAt.substring(0, 10)
417
+ utcMinute(g.updatedAt)
400
418
  ]),
401
419
  { truncate: 24 }
402
420
  );
@@ -714,7 +732,7 @@ function registerVersionCommands(program) {
714
732
  v.status,
715
733
  v.commitMessage ?? "\u2013",
716
734
  v.snapshotHash ? v.snapshotHash.substring(0, 8) : "\u2013",
717
- v.createdAt.substring(0, 10)
735
+ utcMinute(v.createdAt)
718
736
  ]),
719
737
  { truncate: 24 }
720
738
  );
@@ -747,7 +765,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
747
765
  process.exit(1);
748
766
  }
749
767
  });
750
- versions.command("create").description("Create a new draft version").requiredOption("--group-id <groupId>", "Policy group ID").option("--commit-message <message>", "Commit message").option("--effective-from <date>", "Effective from (ISO datetime)").option("--effective-to <date>", "Effective to (ISO datetime)").option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
768
+ versions.command("create").description("Create a new draft version").requiredOption("--group-id <groupId>", "Policy group ID").option("--commit-message <message>", "Commit message").option("--effective-from <instant>", `Effective start, ${INSTANT}`).option("--effective-to <instant>", `Effective end, ${INSTANT}`).option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
751
769
  "after",
752
770
  dedent4`
753
771
 
@@ -762,8 +780,8 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
762
780
 
763
781
  Fields:
764
782
  commitMessage string Version description (optional, max 255 chars)
765
- effectiveFrom datetime Start of effective period (optional, ISO-8601)
766
- effectiveTo datetime End of effective period (optional, auto-expires)
783
+ effectiveFrom instant Start of effective period (optional, with Z or an offset)
784
+ effectiveTo instant End of effective period (optional, auto-expires)
767
785
  `
768
786
  ).action(async (opts) => {
769
787
  try {
@@ -786,12 +804,16 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
786
804
  process.exit(1);
787
805
  }
788
806
  });
789
- versions.command("update").description("Update a draft version metadata").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--id <versionId>", "Policy version ID").option("--commit-message <message>", "Commit message").option("--effective-from <date>", "Effective from (ISO datetime)").option("--effective-to <date>", "Effective to (ISO datetime)").option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
807
+ versions.command("update").description("Update a draft version metadata").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--id <versionId>", "Policy version ID").option("--commit-message <message>", "Commit message").option("--effective-from <instant>", `Effective start, ${INSTANT}`).option("--effective-to <instant>", `Effective end, ${INSTANT}`).option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
790
808
  "after",
791
809
  dedent4`
792
810
 
793
811
  Only DRAFT versions can be updated. Published (ACTIVE) versions are immutable.
794
812
 
813
+ An omitted --commit-message keeps its value, but --effective-from and --effective-to
814
+ are replaced together: one you omit is cleared. To keep the effective window, pass
815
+ both current values (read them with lexq versions get).
816
+
795
817
  Example:
796
818
  $ lexq versions update --group-id <gid> --id <vid> --commit-message "Updated rules"
797
819
  `
@@ -1742,7 +1764,7 @@ function registerDeployCommands(program) {
1742
1764
  process.exit(1);
1743
1765
  }
1744
1766
  });
1745
- deploy.command("history").description("List deployment history").option("--group-id <groupId>", "Filter by policy group").option("--types <types>", "Filter by types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)").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(
1767
+ deploy.command("history").description("List deployment history").option("--group-id <groupId>", "Filter by policy group").option("--types <types>", "Filter by types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)").option("--start-date <date>", START_DAY).option("--end-date <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
1746
1768
  "after",
1747
1769
  dedent7`
1748
1770
 
@@ -1774,7 +1796,7 @@ function registerDeployCommands(program) {
1774
1796
  d.policyGroupName,
1775
1797
  d.versionNo != null ? `v${d.versionNo}` : "\u2013",
1776
1798
  d.deployedByName,
1777
- d.deployedAt.substring(0, 16)
1799
+ utcMinute(d.deployedAt)
1778
1800
  ]),
1779
1801
  { truncate: 20 }
1780
1802
  );
@@ -1836,7 +1858,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
1836
1858
  d.groupName,
1837
1859
  d.groupStatus,
1838
1860
  d.currentVersionName ?? "\u2013",
1839
- d.lastDeployedAt?.substring(0, 16) ?? "\u2013"
1861
+ utcMinute(d.lastDeployedAt)
1840
1862
  ])
1841
1863
  );
1842
1864
  } else {
@@ -1903,6 +1925,8 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
1903
1925
 
1904
1926
  The version must be ACTIVE with a future effective start date; the system
1905
1927
  deploys it automatically at that time (within one scheduler tick, ≤60s).
1928
+ That time is the version's effectiveFrom, set on the DRAFT before publishing;
1929
+ this command takes no time of its own.
1906
1930
  One pending schedule per group. Manual deploy/rollback/undeploy, starting
1907
1931
  an A/B test, or archiving the group cancels the pending schedule.
1908
1932
 
@@ -1994,9 +2018,9 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
1994
2018
  s.status,
1995
2019
  s.policyGroupName ?? s.policyGroupId.substring(0, 8),
1996
2020
  s.versionNo != null ? `v${s.versionNo}` : "\u2013",
1997
- s.scheduledFor.substring(0, 16),
2021
+ utcMinute(s.scheduledFor),
1998
2022
  s.scheduledByName,
1999
- s.status === "EXECUTED" ? s.executedAt?.substring(0, 16) ?? "\u2013" : s.status === "CANCELED" ? s.canceledReason ?? "\u2013" : s.status === "FAILED" ? s.failedReason ?? "\u2013" : "\u2013"
2023
+ s.status === "EXECUTED" ? utcMinute(s.executedAt) : s.status === "CANCELED" ? s.canceledReason ?? "\u2013" : s.status === "FAILED" ? s.failedReason ?? "\u2013" : "\u2013"
2000
2024
  ]),
2001
2025
  { truncate: 20 }
2002
2026
  );
@@ -2199,6 +2223,7 @@ function registerAnalyticsCommands(program) {
2199
2223
 
2200
2224
  Dataset types: HISTORICAL, UPLOADED, MANUAL
2201
2225
  Dataset sources: EXECUTION_LOGS, S3_BUCKET, REQUEST_BODY
2226
+ HISTORICAL from / to are yyyy-MM-dd, whole days in the organization time zone, both inclusive.
2202
2227
  `
2203
2228
  ).action(async (opts) => {
2204
2229
  try {
@@ -2315,7 +2340,7 @@ function registerAnalyticsCommands(program) {
2315
2340
  process.exit(1);
2316
2341
  }
2317
2342
  });
2318
- sim.command("list").description("List simulation history").option("--status <status>", "Filter by status (PENDING, RUNNING, COMPLETED, FAILED, CANCELED)").option("--from <date>", "Start date (yyyy-MM-dd)").option("--to <date>", "End date (yyyy-MM-dd)").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
2343
+ sim.command("list").description("List simulation history").option("--status <status>", "Filter by status (PENDING, RUNNING, COMPLETED, FAILED, CANCELED)").option("--from <date>", START_DAY).option("--to <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
2319
2344
  try {
2320
2345
  const globalOpts = program.opts();
2321
2346
  const format = globalOpts.format ?? "json";
@@ -2344,7 +2369,7 @@ function registerAnalyticsCommands(program) {
2344
2369
  s.status,
2345
2370
  `${s.matchRate.toFixed(1)}%`,
2346
2371
  String(s.totalRecords),
2347
- s.createdAt.substring(0, 16)
2372
+ utcMinute(s.createdAt)
2348
2373
  ]),
2349
2374
  { truncate: 20 }
2350
2375
  );
@@ -2665,7 +2690,7 @@ function registerHistoryCommands(program) {
2665
2690
  Statuses: SUCCESS, NO_MATCH, ERROR, TIMEOUT
2666
2691
  `
2667
2692
  );
2668
- 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(
2693
+ 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_DAY).option("--end-date <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
2669
2694
  "after",
2670
2695
  dedent10`
2671
2696
 
@@ -2705,7 +2730,7 @@ function registerHistoryCommands(program) {
2705
2730
  h.status,
2706
2731
  h.isMatched ? "\u2713" : "\u2717",
2707
2732
  `${h.latencyMs}ms`,
2708
- h.createdAt.substring(0, 16)
2733
+ utcMinute(h.createdAt)
2709
2734
  ]),
2710
2735
  { truncate: 20 }
2711
2736
  );
@@ -2745,15 +2770,19 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
2745
2770
  process.exit(1);
2746
2771
  }
2747
2772
  });
2748
- 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(
2773
+ history.command("stats").description("Get execution statistics").option("--group-id <groupId>", "Filter by policy group").option("--start-date <date>", START_DAY).option("--end-date <date>", END_DAY).addHelpText(
2749
2774
  "after",
2750
2775
  dedent10`
2751
2776
 
2752
2777
  Shows total executions, success/no-match/failure counts, success rate, and avg latency.
2753
2778
 
2779
+ Without dates the window is the last ${STATS_DEFAULT_WINDOW_DAYS} days through today, in the organization
2780
+ time zone. At most ${STATS_MAX_WINDOW_DAYS} days per call; a longer range is rejected with AN-039.
2781
+ --start-date alone runs through today.
2782
+
2754
2783
  Example:
2755
2784
  $ lexq history stats --format table
2756
- $ lexq history stats --group-id <gid> --start-date 2026-04-01
2785
+ $ lexq history stats --group-id <gid> --start-date 2026-04-01 --end-date 2026-04-30
2757
2786
  `
2758
2787
  ).action(async (opts) => {
2759
2788
  try {
@@ -2840,7 +2869,7 @@ function registerReplayCommands(program) {
2840
2869
  process.exit(1);
2841
2870
  }
2842
2871
  });
2843
- 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 ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`).addHelpText(
2872
+ 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_DAY).requiredOption("--to <date>", WINDOW_END_DAY).option("--max-records <number>", `Sample cap (hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`).addHelpText(
2844
2873
  "after",
2845
2874
  dedent11`
2846
2875
 
@@ -2884,7 +2913,7 @@ function registerReplayCommands(program) {
2884
2913
  });
2885
2914
  if (format === "table") {
2886
2915
  printTable(
2887
- ["Job", "Version", "Window", "Status", "Progress", "Changed", "At"],
2916
+ ["Job", "Version", "Window (org time zone)", "Status", "Progress", "Changed", "At"],
2888
2917
  data.content.map((j) => [
2889
2918
  j.jobId.substring(0, 12),
2890
2919
  j.candidateVersionName ?? "\u2013",
@@ -2892,7 +2921,7 @@ function registerReplayCommands(program) {
2892
2921
  j.status,
2893
2922
  `${j.progress}%`,
2894
2923
  `${j.changedCount}${j.capped ? " (capped)" : ""}`,
2895
- j.createdAt.substring(0, 16)
2924
+ utcMinute(j.createdAt)
2896
2925
  ]),
2897
2926
  { truncate: 24 }
2898
2927
  );
@@ -2998,7 +3027,7 @@ function registerProvenanceCommands(program) {
2998
3027
  process.exit(1);
2999
3028
  }
3000
3029
  });
3001
- 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(
3030
+ 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_DAY).option("--end-date <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
3002
3031
  "after",
3003
3032
  dedent12`
3004
3033
 
@@ -3032,7 +3061,7 @@ function registerProvenanceCommands(program) {
3032
3061
  printTable(
3033
3062
  ["Revealed At", "By", "Fact Key", "Trace"],
3034
3063
  data.content.map((a) => [
3035
- a.revealedAt.substring(0, 16),
3064
+ utcMinute(a.revealedAt),
3036
3065
  a.revealedByName,
3037
3066
  a.factKey,
3038
3067
  a.traceId.substring(0, 12)
@@ -3070,10 +3099,13 @@ function registerLogCommands(program) {
3070
3099
  Statuses: PENDING (needs attention), RESOLVED, IGNORED
3071
3100
  `
3072
3101
  );
3073
- logs.command("list").description("List failure logs").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(
3102
+ logs.command("list").description("List failure logs").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_DAY).option("--end-date <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
3074
3103
  "after",
3075
3104
  dedent13`
3076
3105
 
3106
+ Without dates the window is ${FAILURE_LOG_LOOKBACK_DAYS} days back through today, in the organization
3107
+ time zone. Pass --start-date to look further back.
3108
+
3077
3109
  Examples:
3078
3110
  $ lexq logs list --status PENDING --format table
3079
3111
  $ lexq logs list --task-type PLATFORM_WEBHOOK
@@ -3104,7 +3136,7 @@ function registerLogCommands(program) {
3104
3136
  l.taskType,
3105
3137
  l.status,
3106
3138
  l.errorMessage?.substring(0, 24) ?? "\u2013",
3107
- l.createdAt.substring(0, 16)
3139
+ utcMinute(l.createdAt)
3108
3140
  ]),
3109
3141
  { truncate: 24 }
3110
3142
  );
@@ -3502,7 +3534,7 @@ function registerStatusTools(server, callApi) {
3502
3534
  {
3503
3535
  title: "Who Am I",
3504
3536
  annotations: { readOnlyHint: true },
3505
- description: "Show current authentication info (tenant ID, user ID, role).",
3537
+ description: 'Show current authentication info (tenant ID, user ID, role) and tenantTimezone, the organization time zone that every yyyy-MM-dd date filter uses. Work out "today" or "yesterday" in that zone, not in UTC.',
3506
3538
  inputSchema: z.object({})
3507
3539
  },
3508
3540
  async () => callApi("GET", "whoami")
@@ -3700,8 +3732,8 @@ function registerVersionTools(server, callApi) {
3700
3732
  inputSchema: z3.object({
3701
3733
  groupId: z3.string().uuid().describe("Policy group ID"),
3702
3734
  commitMessage: z3.string().optional().describe("Commit message describing this version"),
3703
- effectiveFrom: z3.string().optional().describe("Effective start date (ISO 8601)"),
3704
- effectiveTo: z3.string().optional().describe("Effective end date (ISO 8601)")
3735
+ effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT}`),
3736
+ effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT}`)
3705
3737
  })
3706
3738
  },
3707
3739
  async ({ groupId, ...body }) => callApi("POST", `policy-groups/${groupId}/versions`, { body })
@@ -3711,13 +3743,13 @@ function registerVersionTools(server, callApi) {
3711
3743
  {
3712
3744
  title: "Update Policy Version",
3713
3745
  annotations: { readOnlyHint: false, destructiveHint: true },
3714
- description: "Update a DRAFT version. Only DRAFT versions can be modified. Only provided fields are changed.",
3746
+ description: "Update a DRAFT version. Only DRAFT versions can be modified. An omitted commitMessage keeps its value, but effectiveFrom and effectiveTo are replaced together: one you omit is cleared. To keep the effective window, send both current values (read them with lexq_versions_get).",
3715
3747
  inputSchema: z3.object({
3716
3748
  groupId: z3.string().uuid().describe("Policy group ID"),
3717
3749
  versionId: z3.string().uuid().describe("Version ID"),
3718
3750
  commitMessage: z3.string().optional().describe("New commit message"),
3719
- effectiveFrom: z3.string().optional().describe("New effective start date"),
3720
- effectiveTo: z3.string().optional().describe("New effective end date")
3751
+ effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT} Omit it and the start is cleared.`),
3752
+ effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT} Omit it and the end is cleared.`)
3721
3753
  })
3722
3754
  },
3723
3755
  async ({ groupId, versionId, ...body }) => callApi("PUT", `policy-groups/${groupId}/versions/${versionId}`, { body })
@@ -4153,7 +4185,7 @@ function registerDeployTools(server, callApi) {
4153
4185
  {
4154
4186
  title: "Schedule Deployment",
4155
4187
  annotations: { readOnlyHint: false, destructiveHint: false },
4156
- 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).",
4188
+ description: "Schedule an ACTIVE version with a future effective start date to auto-deploy at that time (Scheduled Deployment). The time is the version's effectiveFrom, set on the DRAFT before publishing; this call takes no time of its own. 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).",
4157
4189
  inputSchema: z6.object({
4158
4190
  groupId: z6.string().uuid().describe("Policy group ID"),
4159
4191
  versionId: z6.string().uuid().describe("ACTIVE version ID with a future effective start date"),
@@ -4200,8 +4232,8 @@ function registerDeployTools(server, callApi) {
4200
4232
  size: z6.number().int().min(1).max(100).default(20).describe("Page size"),
4201
4233
  groupId: z6.string().uuid().optional().describe("Filter by group ID"),
4202
4234
  types: z6.string().optional().describe("Filter by deployment types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)"),
4203
- startDate: z6.string().optional().describe("Start date (yyyy-MM-dd)"),
4204
- endDate: z6.string().optional().describe("End date (yyyy-MM-dd)")
4235
+ startDate: z6.string().optional().describe(START_DAY),
4236
+ endDate: z6.string().optional().describe(END_DAY)
4205
4237
  })
4206
4238
  },
4207
4239
  async ({ page, size, groupId, types, startDate, endDate }) => {
@@ -4345,7 +4377,8 @@ function registerAnalyticsTools(server, callApi) {
4345
4377
  Start an Impact Simulation against historical, uploaded, or inline data.
4346
4378
 
4347
4379
  dataset.type and dataset.source are BOTH required, and must be paired:
4348
- HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd)
4380
+ HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd, whole days
4381
+ in the organization time zone, both inclusive)
4349
4382
  UPLOADED → source S3_BUCKET, with dataset.path (the path returned by lexq_dataset_upload)
4350
4383
  MANUAL → source REQUEST_BODY, with dataset.manualData (array of fact records)
4351
4384
 
@@ -4400,8 +4433,8 @@ function registerAnalyticsTools(server, callApi) {
4400
4433
  page: z7.number().int().min(0).default(0).describe("Page number"),
4401
4434
  size: z7.number().int().min(1).max(100).default(20).describe("Page size"),
4402
4435
  status: z7.enum(["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELED"]).optional().describe("Filter by status"),
4403
- from: z7.string().optional().describe("Start date (yyyy-MM-dd)"),
4404
- to: z7.string().optional().describe("End date (yyyy-MM-dd)")
4436
+ from: z7.string().optional().describe(START_DAY),
4437
+ to: z7.string().optional().describe(END_DAY)
4405
4438
  })
4406
4439
  },
4407
4440
  async ({ page, size, status, from, to }) => {
@@ -4579,8 +4612,8 @@ function registerReplayTools(server, callApi) {
4579
4612
  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.",
4580
4613
  inputSchema: z9.object({
4581
4614
  candidateVersionId: z9.string().uuid().describe("Version to re-evaluate against"),
4582
- from: z9.string().describe("Window start date (yyyy-MM-dd)"),
4583
- to: z9.string().describe("Window end date (yyyy-MM-dd)"),
4615
+ from: z9.string().describe(WINDOW_START_DAY),
4616
+ to: z9.string().describe(WINDOW_END_DAY),
4584
4617
  maxRecords: z9.number().int().min(1).optional().describe(
4585
4618
  `Sample cap (server default applies; hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`
4586
4619
  )
@@ -4656,8 +4689,8 @@ function registerHistoryTools(server, callApi) {
4656
4689
  groupId: z10.string().uuid().optional().describe("Filter by policy group"),
4657
4690
  versionId: z10.string().uuid().optional().describe("Filter by version"),
4658
4691
  status: z10.enum(["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"]).optional().describe("Filter by execution status"),
4659
- startDate: z10.string().optional().describe("Start date (yyyy-MM-dd)"),
4660
- endDate: z10.string().optional().describe("End date (yyyy-MM-dd)")
4692
+ startDate: z10.string().optional().describe(START_DAY),
4693
+ endDate: z10.string().optional().describe(END_DAY)
4661
4694
  })
4662
4695
  },
4663
4696
  async ({ page, size, traceId, groupId, versionId, status, startDate, endDate }) => {
@@ -4688,11 +4721,11 @@ function registerHistoryTools(server, callApi) {
4688
4721
  {
4689
4722
  title: "Execution Statistics",
4690
4723
  annotations: { readOnlyHint: true },
4691
- description: "Get execution KPIs: total executions, success/failure counts, success rate, and average latency.",
4724
+ description: `Get execution KPIs: total executions, success/failure counts, success rate, and average latency. Without dates the window is the last ${STATS_DEFAULT_WINDOW_DAYS} days through today, in the organization time zone. At most ${STATS_MAX_WINDOW_DAYS} days per call; a longer range is rejected with AN-039. startDate alone runs through today.`,
4692
4725
  inputSchema: z10.object({
4693
4726
  groupId: z10.string().uuid().optional().describe("Filter by policy group"),
4694
- startDate: z10.string().optional().describe("Start date (yyyy-MM-dd)"),
4695
- endDate: z10.string().optional().describe("End date (yyyy-MM-dd)")
4727
+ startDate: z10.string().optional().describe(START_DAY),
4728
+ endDate: z10.string().optional().describe(END_DAY)
4696
4729
  })
4697
4730
  },
4698
4731
  async ({ groupId, startDate, endDate }) => {
@@ -4732,8 +4765,8 @@ function registerProvenanceTools(server, callApi) {
4732
4765
  traceId: z11.string().optional().describe("Filter by trace ID (exact match)"),
4733
4766
  revealedBy: z11.string().optional().describe("Filter by operator ID (exact match)"),
4734
4767
  factKey: z11.string().optional().describe("Filter by fact key (partial match, case-insensitive)"),
4735
- startDate: z11.string().optional().describe("Start date (yyyy-MM-dd)"),
4736
- endDate: z11.string().optional().describe("End date (yyyy-MM-dd)")
4768
+ startDate: z11.string().optional().describe(START_DAY),
4769
+ endDate: z11.string().optional().describe(END_DAY)
4737
4770
  })
4738
4771
  },
4739
4772
  async ({ page, size, traceId, revealedBy, factKey, startDate, endDate }) => {
@@ -4756,15 +4789,15 @@ function registerLogTools(server, callApi) {
4756
4789
  {
4757
4790
  title: "List Failure Logs",
4758
4791
  annotations: { readOnlyHint: true },
4759
- description: "List system failure logs from background tasks (platform event webhooks, scheduled deployments).",
4792
+ description: `List system failure logs from background tasks (platform event webhooks, scheduled deployments). Without dates the window is ${FAILURE_LOG_LOOKBACK_DAYS} days back through today, in the organization time zone; pass startDate to look further back.`,
4760
4793
  inputSchema: z12.object({
4761
4794
  page: z12.number().int().min(0).default(0).describe("Page number"),
4762
4795
  size: z12.number().int().min(1).max(100).default(20).describe("Page size"),
4763
4796
  taskType: z12.enum(TaskType).optional().describe("Task type"),
4764
4797
  status: z12.enum(FailureStatus).optional().describe("Log status"),
4765
4798
  keyword: z12.string().optional().describe("Search in refId, refSubId, errorMessage"),
4766
- startDate: z12.string().optional().describe("Start date (yyyy-MM-dd)"),
4767
- endDate: z12.string().optional().describe("End date (yyyy-MM-dd)")
4799
+ startDate: z12.string().optional().describe(START_DAY),
4800
+ endDate: z12.string().optional().describe(END_DAY)
4768
4801
  })
4769
4802
  },
4770
4803
  async ({ page, size, taskType, status, keyword, startDate, endDate }) => {
@@ -6,7 +6,7 @@ function registerStatusTools(server, callApi) {
6
6
  {
7
7
  title: "Who Am I",
8
8
  annotations: { readOnlyHint: true },
9
- description: "Show current authentication info (tenant ID, user ID, role).",
9
+ description: 'Show current authentication info (tenant ID, user ID, role) and tenantTimezone, the organization time zone that every yyyy-MM-dd date filter uses. Work out "today" or "yesterday" in that zone, not in UTC.',
10
10
  inputSchema: z.object({})
11
11
  },
12
12
  async () => callApi("GET", "whoami")
@@ -259,6 +259,17 @@ function formatUnregisteredFactWarning(meta) {
259
259
  ].join("\n");
260
260
  }
261
261
 
262
+ // src/lib/dates.ts
263
+ var ORG_DAY = "yyyy-MM-dd, a whole calendar day in the organization time zone (tenantTimezone in whoami), not UTC";
264
+ var START_DAY = `Start date, ${ORG_DAY}. Inclusive.`;
265
+ var END_DAY = `End date, ${ORG_DAY}. Inclusive: the whole day counts.`;
266
+ var WINDOW_START_DAY = `Window start date, ${ORG_DAY}. Inclusive.`;
267
+ var WINDOW_END_DAY = `Window end date, ${ORG_DAY}. Inclusive: the whole day counts.`;
268
+ var INSTANT = "ISO-8601 instant with Z or an offset, e.g. 2026-10-01T00:00:00Z or 2026-10-01T09:00:00+09:00. A bare date or a time without an offset is rejected.";
269
+ var STATS_DEFAULT_WINDOW_DAYS = 30;
270
+ var STATS_MAX_WINDOW_DAYS = 31;
271
+ var FAILURE_LOG_LOOKBACK_DAYS = 7;
272
+
262
273
  // src/mcp/tools/versions.ts
263
274
  function registerVersionTools(server, callApi) {
264
275
  server.registerTool(
@@ -299,8 +310,8 @@ function registerVersionTools(server, callApi) {
299
310
  inputSchema: z3.object({
300
311
  groupId: z3.string().uuid().describe("Policy group ID"),
301
312
  commitMessage: z3.string().optional().describe("Commit message describing this version"),
302
- effectiveFrom: z3.string().optional().describe("Effective start date (ISO 8601)"),
303
- effectiveTo: z3.string().optional().describe("Effective end date (ISO 8601)")
313
+ effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT}`),
314
+ effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT}`)
304
315
  })
305
316
  },
306
317
  async ({ groupId, ...body }) => callApi("POST", `policy-groups/${groupId}/versions`, { body })
@@ -310,13 +321,13 @@ function registerVersionTools(server, callApi) {
310
321
  {
311
322
  title: "Update Policy Version",
312
323
  annotations: { readOnlyHint: false, destructiveHint: true },
313
- description: "Update a DRAFT version. Only DRAFT versions can be modified. Only provided fields are changed.",
324
+ description: "Update a DRAFT version. Only DRAFT versions can be modified. An omitted commitMessage keeps its value, but effectiveFrom and effectiveTo are replaced together: one you omit is cleared. To keep the effective window, send both current values (read them with lexq_versions_get).",
314
325
  inputSchema: z3.object({
315
326
  groupId: z3.string().uuid().describe("Policy group ID"),
316
327
  versionId: z3.string().uuid().describe("Version ID"),
317
328
  commitMessage: z3.string().optional().describe("New commit message"),
318
- effectiveFrom: z3.string().optional().describe("New effective start date"),
319
- effectiveTo: z3.string().optional().describe("New effective end date")
329
+ effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT} Omit it and the start is cleared.`),
330
+ effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT} Omit it and the end is cleared.`)
320
331
  })
321
332
  },
322
333
  async ({ groupId, versionId, ...body }) => callApi("PUT", `policy-groups/${groupId}/versions/${versionId}`, { body })
@@ -756,7 +767,7 @@ function registerDeployTools(server, callApi) {
756
767
  {
757
768
  title: "Schedule Deployment",
758
769
  annotations: { readOnlyHint: false, destructiveHint: false },
759
- 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).",
770
+ description: "Schedule an ACTIVE version with a future effective start date to auto-deploy at that time (Scheduled Deployment). The time is the version's effectiveFrom, set on the DRAFT before publishing; this call takes no time of its own. 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).",
760
771
  inputSchema: z6.object({
761
772
  groupId: z6.string().uuid().describe("Policy group ID"),
762
773
  versionId: z6.string().uuid().describe("ACTIVE version ID with a future effective start date"),
@@ -803,8 +814,8 @@ function registerDeployTools(server, callApi) {
803
814
  size: z6.number().int().min(1).max(100).default(20).describe("Page size"),
804
815
  groupId: z6.string().uuid().optional().describe("Filter by group ID"),
805
816
  types: z6.string().optional().describe("Filter by deployment types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)"),
806
- startDate: z6.string().optional().describe("Start date (yyyy-MM-dd)"),
807
- endDate: z6.string().optional().describe("End date (yyyy-MM-dd)")
817
+ startDate: z6.string().optional().describe(START_DAY),
818
+ endDate: z6.string().optional().describe(END_DAY)
808
819
  })
809
820
  },
810
821
  async ({ page, size, groupId, types, startDate, endDate }) => {
@@ -948,7 +959,8 @@ function registerAnalyticsTools(server, callApi) {
948
959
  Start an Impact Simulation against historical, uploaded, or inline data.
949
960
 
950
961
  dataset.type and dataset.source are BOTH required, and must be paired:
951
- HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd)
962
+ HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd, whole days
963
+ in the organization time zone, both inclusive)
952
964
  UPLOADED → source S3_BUCKET, with dataset.path (the path returned by lexq_dataset_upload)
953
965
  MANUAL → source REQUEST_BODY, with dataset.manualData (array of fact records)
954
966
 
@@ -1003,8 +1015,8 @@ function registerAnalyticsTools(server, callApi) {
1003
1015
  page: z7.number().int().min(0).default(0).describe("Page number"),
1004
1016
  size: z7.number().int().min(1).max(100).default(20).describe("Page size"),
1005
1017
  status: z7.enum(["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELED"]).optional().describe("Filter by status"),
1006
- from: z7.string().optional().describe("Start date (yyyy-MM-dd)"),
1007
- to: z7.string().optional().describe("End date (yyyy-MM-dd)")
1018
+ from: z7.string().optional().describe(START_DAY),
1019
+ to: z7.string().optional().describe(END_DAY)
1008
1020
  })
1009
1021
  },
1010
1022
  async ({ page, size, status, from, to }) => {
@@ -1182,8 +1194,8 @@ function registerReplayTools(server, callApi) {
1182
1194
  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.",
1183
1195
  inputSchema: z9.object({
1184
1196
  candidateVersionId: z9.string().uuid().describe("Version to re-evaluate against"),
1185
- from: z9.string().describe("Window start date (yyyy-MM-dd)"),
1186
- to: z9.string().describe("Window end date (yyyy-MM-dd)"),
1197
+ from: z9.string().describe(WINDOW_START_DAY),
1198
+ to: z9.string().describe(WINDOW_END_DAY),
1187
1199
  maxRecords: z9.number().int().min(1).optional().describe(
1188
1200
  `Sample cap (server default applies; hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`
1189
1201
  )
@@ -1259,8 +1271,8 @@ function registerHistoryTools(server, callApi) {
1259
1271
  groupId: z10.string().uuid().optional().describe("Filter by policy group"),
1260
1272
  versionId: z10.string().uuid().optional().describe("Filter by version"),
1261
1273
  status: z10.enum(["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"]).optional().describe("Filter by execution status"),
1262
- startDate: z10.string().optional().describe("Start date (yyyy-MM-dd)"),
1263
- endDate: z10.string().optional().describe("End date (yyyy-MM-dd)")
1274
+ startDate: z10.string().optional().describe(START_DAY),
1275
+ endDate: z10.string().optional().describe(END_DAY)
1264
1276
  })
1265
1277
  },
1266
1278
  async ({ page, size, traceId, groupId, versionId, status, startDate, endDate }) => {
@@ -1291,11 +1303,11 @@ function registerHistoryTools(server, callApi) {
1291
1303
  {
1292
1304
  title: "Execution Statistics",
1293
1305
  annotations: { readOnlyHint: true },
1294
- description: "Get execution KPIs: total executions, success/failure counts, success rate, and average latency.",
1306
+ description: `Get execution KPIs: total executions, success/failure counts, success rate, and average latency. Without dates the window is the last ${STATS_DEFAULT_WINDOW_DAYS} days through today, in the organization time zone. At most ${STATS_MAX_WINDOW_DAYS} days per call; a longer range is rejected with AN-039. startDate alone runs through today.`,
1295
1307
  inputSchema: z10.object({
1296
1308
  groupId: z10.string().uuid().optional().describe("Filter by policy group"),
1297
- startDate: z10.string().optional().describe("Start date (yyyy-MM-dd)"),
1298
- endDate: z10.string().optional().describe("End date (yyyy-MM-dd)")
1309
+ startDate: z10.string().optional().describe(START_DAY),
1310
+ endDate: z10.string().optional().describe(END_DAY)
1299
1311
  })
1300
1312
  },
1301
1313
  async ({ groupId, startDate, endDate }) => {
@@ -1335,8 +1347,8 @@ function registerProvenanceTools(server, callApi) {
1335
1347
  traceId: z11.string().optional().describe("Filter by trace ID (exact match)"),
1336
1348
  revealedBy: z11.string().optional().describe("Filter by operator ID (exact match)"),
1337
1349
  factKey: z11.string().optional().describe("Filter by fact key (partial match, case-insensitive)"),
1338
- startDate: z11.string().optional().describe("Start date (yyyy-MM-dd)"),
1339
- endDate: z11.string().optional().describe("End date (yyyy-MM-dd)")
1350
+ startDate: z11.string().optional().describe(START_DAY),
1351
+ endDate: z11.string().optional().describe(END_DAY)
1340
1352
  })
1341
1353
  },
1342
1354
  async ({ page, size, traceId, revealedBy, factKey, startDate, endDate }) => {
@@ -1359,15 +1371,15 @@ function registerLogTools(server, callApi) {
1359
1371
  {
1360
1372
  title: "List Failure Logs",
1361
1373
  annotations: { readOnlyHint: true },
1362
- description: "List system failure logs from background tasks (platform event webhooks, scheduled deployments).",
1374
+ description: `List system failure logs from background tasks (platform event webhooks, scheduled deployments). Without dates the window is ${FAILURE_LOG_LOOKBACK_DAYS} days back through today, in the organization time zone; pass startDate to look further back.`,
1363
1375
  inputSchema: z12.object({
1364
1376
  page: z12.number().int().min(0).default(0).describe("Page number"),
1365
1377
  size: z12.number().int().min(1).max(100).default(20).describe("Page size"),
1366
1378
  taskType: z12.enum(TaskType).optional().describe("Task type"),
1367
1379
  status: z12.enum(FailureStatus).optional().describe("Log status"),
1368
1380
  keyword: z12.string().optional().describe("Search in refId, refSubId, errorMessage"),
1369
- startDate: z12.string().optional().describe("Start date (yyyy-MM-dd)"),
1370
- endDate: z12.string().optional().describe("End date (yyyy-MM-dd)")
1381
+ startDate: z12.string().optional().describe(START_DAY),
1382
+ endDate: z12.string().optional().describe(END_DAY)
1371
1383
  })
1372
1384
  },
1373
1385
  async ({ page, size, taskType, status, keyword, startDate, endDate }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexq/cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
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",
@@ -23,8 +23,11 @@ Filter options:
23
23
  | `--group-id <id>` | Filter by policy group | `--group-id <gid>` |
24
24
  | `--version-id <id>` | Filter by version | `--version-id <vid>` |
25
25
  | `--status <status>` | Filter by status | `--status SUCCESS` |
26
- | `--start-date <date>` | Start date (ISO) | `--start-date 2025-01-01` |
27
- | `--end-date <date>` | End date (ISO) | `--end-date 2025-01-31` |
26
+ | `--start-date <date>` | First day, inclusive | `--start-date 2025-01-01` |
27
+ | `--end-date <date>` | Last day, inclusive | `--end-date 2025-01-31` |
28
+
29
+ Dates are `yyyy-MM-dd`, whole days in the organization time zone (`tenantTimezone` in
30
+ `lexq auth whoami`), not UTC.
28
31
 
29
32
  ### Execution Statuses
30
33
 
@@ -53,6 +56,10 @@ Returns full detail including `inputFacts`, `executionTraces`, and `decisionTrac
53
56
  lexq history stats
54
57
  ```
55
58
 
59
+ Without dates the window is the last 30 days through today, in the organization time zone. At most
60
+ 31 days per call; a longer range is rejected with `AN-039`. `--start-date` alone runs through
61
+ today.
62
+
56
63
  Returns KPI summary:
57
64
 
58
65
  ```json
@@ -98,8 +105,12 @@ Filter options:
98
105
  | `--task-type <type>` | Task type | `PLATFORM_WEBHOOK`, `SCHEDULED_DEPLOYMENT` |
99
106
  | `--status <status>` | Log status | `PENDING`, `RESOLVED`, `IGNORED` |
100
107
  | `--keyword <kw>` | Search in refId, refSubId, errorMessage | any string |
101
- | `--start-date <date>` | Start date (ISO) | `2025-01-01` |
102
- | `--end-date <date>` | End date (ISO) | `2025-01-31` |
108
+ | `--start-date <date>` | First day, inclusive | `2025-01-01` |
109
+ | `--end-date <date>` | Last day, inclusive | `2025-01-31` |
110
+
111
+ Without dates the list covers 7 days back through today, in the organization time zone. A failure
112
+ older than that is left out even while it is still `PENDING`. Pass an earlier `--start-date` to
113
+ see it.
103
114
 
104
115
  ### Get Failure Log Detail
105
116
 
@@ -247,7 +258,7 @@ lexq profile <groupId> --version <vid> --cache MISS \
247
258
  ```
248
259
 
249
260
  `--from` / `--to` are ISO-8601 **instants** here (`2026-07-01T00:00:00Z`), unlike `replay start`
250
- which takes `yyyy-MM-dd` dates.
261
+ which takes `yyyy-MM-dd` days in the organization time zone.
251
262
 
252
263
  `p50` / `p95` / `p99` are `null` when the sample is under 100 — the engine reports insufficient
253
264
  data rather than inventing a number. `n` is always present.
@@ -264,7 +275,7 @@ lexq history list --status ERROR --page 0 --size 10
264
275
  # 3. For a specific bad decision, get the deterministic why
265
276
  lexq provenance get --trace-id <traceId>
266
277
 
267
- # 4. Check for system failures
278
+ # 4. Check for system failures (covers 7 days back by default; add --start-date for older ones)
268
279
  lexq logs list --status PENDING --page 0 --size 10
269
280
 
270
281
  # 5. Inspect and mark
@@ -390,6 +390,10 @@ lexq logs bulk-action --ids "id4,id5" --action IGNORE
390
390
  lexq logs list --status PENDING --page 0 --size 10
391
391
  ```
392
392
 
393
+ Without `--start-date`, `logs list` covers 7 days back through today, in the organization time
394
+ zone. An empty list in step 5 means there is no PENDING failure in that window, not that there is
395
+ none at all. Add an earlier `--start-date` to steps 1 and 5 to see older ones.
396
+
393
397
  `RESOLVE` and `IGNORE` differ only in intent — both remove the log from PENDING. Use `RESOLVE`
394
398
  when the underlying problem was fixed, `IGNORE` when it does not need fixing.
395
399
 
@@ -153,7 +153,12 @@ Prefixes you will encounter through the CLI:
153
153
  1. **Fact keys start with a letter,** then letters, numbers, and underscores. Casing is yours to
154
154
  choose and keys are case-sensitive. These docs use `camelCase`: `paymentAmount`, `customerTier`.
155
155
  2. **IDs are UUIDs.** Always copy the full ID from list/create output — do not guess.
156
- 3. **Dates use ISO 8601.** Example: `2025-01-01T00:00:00Z`. Time zone is UTC.
156
+ 3. **Dates come in two shapes.** A date filter (`--start-date`, `--end-date`, `--from` / `--to` on
157
+ `replay start` and `simulation list`) takes `yyyy-MM-dd` and means a whole calendar day in the
158
+ organization time zone, not UTC. `lexq auth whoami` shows that zone as `tenantTimezone`. An
159
+ instant (`--effective-from`, `--effective-to`, `--from` / `--to` on `profile`) takes ISO 8601
160
+ with `Z` or an offset: `2026-10-01T00:00:00Z` or `2026-10-01T09:00:00+09:00`. Tables print
161
+ times in UTC with a trailing `Z`.
157
162
  4. **JSON bodies via `--json`.** Most create/update commands accept `--json '<body>'` for the request body.
158
163
  5. **File input via `--file`.** `analytics dry-run`, `dry-run-compare`, `simulation start` accept
159
164
  `--file path/to/body.json` as an alternative to `--json`. `analytics dataset upload` requires
@@ -251,6 +251,9 @@ lexq analytics simulation start --json '{
251
251
  | `MANUAL` | `REQUEST_BODY` | Provide `manualData` array in the request |
252
252
  | `UPLOADED` | `S3_BUCKET` | Reference an uploaded dataset by `path` |
253
253
 
254
+ `HISTORICAL` takes `from` / `to` as `yyyy-MM-dd`, whole days in the organization time zone, both
255
+ inclusive.
256
+
254
257
  ### File Upload Dataset
255
258
 
256
259
  ```bash