From 91e5b584246b10e6da599012d42010573314a12d Mon Sep 17 00:00:00 2001 From: ysyneu Date: Wed, 5 Aug 2026 21:08:14 -0700 Subject: [PATCH] Fix cligen schema walker to recurse through nested array items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The request/response field-tree walker only unwrapped one array level before checking whether the element schema was an object, so a nested array field like an OR-of-AND filter tree (array>) never expanded its item fields the way a plain array field did — and its rendered type label collapsed to the uninformative "array". Both --help and the skill-card generator read from this same walker, so every filters/source_filters/target_filters field across silence, escalate, inhibit, and unsubscribe rules (and any other nested-array field) silently dropped its key/oper/vals schema. Generalize the array case to unwrap items through any depth of nested arrays (bounded, not per-command special-cased) before testing for an object leaf, and apply the same fix to the type-label formatter. Add unit tests covering the nested-object, nested-scalar, and single-level regression cases. Also rewrite the channel card's maintenance-silence example, which built a rule with only a time window — servers reject that as having no matching scope — and add a general rule for constructing `filters` from an incident's own labels (severity + distinguishing labels, with the value/key exclusions and key vocabulary needed to avoid silently building a rule that never matches). --- internal/cli/zz_generated_alerts.go | 27 +++-- internal/cli/zz_generated_channels.go | 33 ++++-- internal/cli/zz_generated_response_help.go | 8 +- internal/cmd/cligen/main.go | 37 ++++--- internal/cmd/cligen/nested_array_test.go | 114 +++++++++++++++++++++ skills/flashduty/reference/channel.md | 59 +++++++++-- 6 files changed, 239 insertions(+), 39 deletions(-) create mode 100644 internal/cmd/cligen/nested_array_test.go diff --git a/internal/cli/zz_generated_alerts.go b/internal/cli/zz_generated_alerts.go index d0ae944..8ec11cc 100644 --- a/internal/cli/zz_generated_alerts.go +++ b/internal/cli/zz_generated_alerts.go @@ -786,13 +786,19 @@ Response fields ('data' envelope is unwrapped — these fields are at the top le - creator_id (integer) — Member ID who created the pipeline. - integration_id (integer) — Integration ID this pipeline applies to. - rules (array) — Ordered list of processing rules. - - if (array) — Optional OR-of-AND filter. When omitted, the rule applies to all alerts. + - if (array>) — Optional OR-of-AND filter. When omitted, the rule applies to all alerts. + - key (string) (required) — Field name to filter on. Use plain names for built-in alert fields (e.g. 'alert_severity', 'alert_key', 'check', 'resource', 'service', 'cluster') or the 'labels.' prefix for custom alert labels (e.g. 'labels.env', 'labels.region'). + - oper (string) (required) — Filter operator. 'IN' — value must match one of 'vals'; 'NOTIN' — value must not match any of 'vals'. Supports regex patterns wrapped in '/pattern/'. [IN, NOTIN] + - vals (array) (required) — List of values to match against. Each entry is a plain string or a '/regex/' pattern. - kind (string) — Rule type. [title_reset, description_reset, severity_reset, alert_drop, alert_inhibit] - settings (object) — Kind-specific settings. Shape depends on 'kind': - 'title_reset': '{ "title": "" }' - 'description_reset': '{ "description": "" }' - 'severity_reset': '{ "severity": "Critical"|"Warning"|"Info" }' - 'alert_drop': '{}' (empty object) - 'alert_inhibit': '{ "equals": ["", ...], "source_filters": }' - description (string) — New description template. - equals (array) — Label keys whose values must be equal between the source and current alert for inhibition to apply. - severity (string) — Target severity level. [Critical, Warning, Info] - - source_filters (array) — Filter that identifies the source alerts to inhibit. + - source_filters (array>) — Filter that identifies the source alerts to inhibit. + - key (string) (required) — Field name to filter on. Use plain names for built-in alert fields (e.g. 'alert_severity', 'alert_key', 'check', 'resource', 'service', 'cluster') or the 'labels.' prefix for custom alert labels (e.g. 'labels.env', 'labels.region'). + - oper (string) (required) — Filter operator. 'IN' — value must match one of 'vals'; 'NOTIN' — value must not match any of 'vals'. Supports regex patterns wrapped in '/pattern/'. [IN, NOTIN] + - vals (array) (required) — List of values to match against. Each entry is a plain string or a '/regex/' pattern. - title (string) — New title template. Supports Golang template syntax referencing alert fields. - status (string) — Pipeline status. Possible values: 'enabled', 'disabled'. - updated_at (integer) — Last update timestamp, Unix epoch seconds. @@ -852,13 +858,16 @@ Response fields ('data' envelope is unwrapped — rows are nested under items[]; - creator_id (integer) — Member ID who created the pipeline. - integration_id (integer) — Integration ID this pipeline applies to. - rules (array) — Ordered list of processing rules. - - if (array) — Optional OR-of-AND filter. When omitted, the rule applies to all alerts. + - if (array>) — Optional OR-of-AND filter. When omitted, the rule applies to all alerts. + - key (string) (required) — Field name to filter on. Use plain names for built-in alert fields (e.g. 'alert_severity', 'alert_key', 'check', 'resource', 'service', 'cluster') or the 'labels.' prefix for custom alert labels (e.g. 'labels.env', 'labels.region'). + - oper (string) (required) — Filter operator. 'IN' — value must match one of 'vals'; 'NOTIN' — value must not match any of 'vals'. Supports regex patterns wrapped in '/pattern/'. [IN, NOTIN] + - vals (array) (required) — List of values to match against. Each entry is a plain string or a '/regex/' pattern. - kind (string) — Rule type. [title_reset, description_reset, severity_reset, alert_drop, alert_inhibit] - settings (object) — Kind-specific settings. Shape depends on 'kind': - 'title_reset': '{ "title": "" }' - 'description_reset': '{ "description": "" }' - 'severity_reset': '{ "severity": "Critical"|"Warning"|"Info" }' - 'alert_drop': '{}' (empty object) - 'alert_inhibit': '{ "equals": ["", ...], "source_filters": }' - description (string) — New description template. - equals (array) — Label keys whose values must be equal between the source and current alert for inhibition to apply. - severity (string) — Target severity level. [Critical, Warning, Info] - - source_filters (array) — Filter that identifies the source alerts to inhibit. + - source_filters (array>) — Filter that identifies the source alerts to inhibit. - title (string) — New title template. Supports Golang template syntax referencing alert fields. - status (string) — Pipeline status. Possible values: 'enabled', 'disabled'. - updated_at (integer) — Last update timestamp, Unix epoch seconds. @@ -988,13 +997,19 @@ API: POST /alert/pipeline/upsert (alert-write-pipeline-upsert) Request fields: --integration-id int (required) — Integration ID to configure. rules (array, via --data) (required) — Rules to apply. Max 50. - - if (array) — Optional OR-of-AND filter. When omitted, the rule applies to all alerts. + - if (array>) — Optional OR-of-AND filter. When omitted, the rule applies to all alerts. + - key (string) (required) — Field name to filter on. Use plain names for built-in alert fields (e.g. 'alert_severity', 'alert_key', 'check', 'resource', 'service', 'cluster') or the 'labels.' prefix for custom alert labels (e.g. 'labels.env', 'labels.region'). + - oper (string) (required) — Filter operator. 'IN' — value must match one of 'vals'; 'NOTIN' — value must not match any of 'vals'. Supports regex patterns wrapped in '/pattern/'. [IN, NOTIN] + - vals (array) (required) — List of values to match against. Each entry is a plain string or a '/regex/' pattern. - kind (string) — Rule type. [title_reset, description_reset, severity_reset, alert_drop, alert_inhibit] - settings (object) — Kind-specific settings. Shape depends on 'kind': - 'title_reset': '{ "title": "" }' - 'description_reset': '{ "description": "" }' - 'severity_reset': '{ "severity": "Critical"|"Warning"|"Info" }' - 'alert_drop': '{}' (empty object) - 'alert_inhibit': '{ "equals": ["", ...], "source_filters": }' - description (string) — New description template. - equals (array) — Label keys whose values must be equal between the source and current alert for inhibition to apply. - severity (string) — Target severity level. [Critical, Warning, Info] - - source_filters (array) — Filter that identifies the source alerts to inhibit. + - source_filters (array>) — Filter that identifies the source alerts to inhibit. + - key (string) (required) — Field name to filter on. Use plain names for built-in alert fields (e.g. 'alert_severity', 'alert_key', 'check', 'resource', 'service', 'cluster') or the 'labels.' prefix for custom alert labels (e.g. 'labels.env', 'labels.region'). + - oper (string) (required) — Filter operator. 'IN' — value must match one of 'vals'; 'NOTIN' — value must not match any of 'vals'. Supports regex patterns wrapped in '/pattern/'. [IN, NOTIN] + - vals (array) (required) — List of values to match against. Each entry is a plain string or a '/regex/' pattern. - title (string) — New title template. Supports Golang template syntax referencing alert fields. `, Args: requireBodyFieldOrExactArg("integration_id", "integration-id"), diff --git a/internal/cli/zz_generated_channels.go b/internal/cli/zz_generated_channels.go index bf284ef..5507aef 100644 --- a/internal/cli/zz_generated_channels.go +++ b/internal/cli/zz_generated_channels.go @@ -66,7 +66,7 @@ Request fields: group (object, via --data) — Alert grouping configuration. - all_equals_required (boolean) — When true, all listed keys must be present for grouping. - cases (array) — Per-filter grouping overrides. - - equals (array) — Groups of label keys whose equality defines a bucket. + - equals (array>) — Groups of label keys whose equality defines a bucket. - i_keys (array) — Label keys used for intelligent grouping embeddings. - i_score_threshold (number) — Intelligent grouping similarity threshold. (0.5-1) - method (string) (required) — Grouping method: 'i' intelligent, 'p' pattern, 'n' none. [i, p, n] @@ -329,7 +329,10 @@ Request fields: --priority int — Evaluation priority. Lower runs first. (0-200) --rule-name string (required) — Rule name, 1 to 39 characters. (1-39 chars) --template-id string (required) — Notification template ID (MongoDB ObjectID). - filters (array, via --data) — Or-of-and filter tree. Each outer element is an AND group; within each group, all conditions must match. + filters (array>, via --data) — Or-of-and filter tree. Each outer element is an AND group; within each group, all conditions must match. + - key (string) (required) — Field key (e.g. 'alert_severity', 'labels.service'). + - oper (string) (required) — Filter operator. [IN, NOTIN] + - vals (array) (required) — Values to match. layers (array, via --data) (required) — Escalation levels in order. At least one level is required. - escalate_window (integer) — Wait before moving to the next level, in minutes. (0-720) - force_escalate (boolean) — When true, always escalate regardless of acknowledgement. @@ -892,7 +895,7 @@ Response fields ('data' envelope is unwrapped — these fields are at the top le - group (object) — Alert grouping configuration. - all_equals_required (boolean) — When true, all listed keys must be present for grouping. - cases (array) — Per-filter grouping overrides. - - equals (array) — Groups of label keys whose equality defines a bucket. + - equals (array>) — Groups of label keys whose equality defines a bucket. - i_keys (array) — Label keys used for intelligent grouping embeddings. - i_score_threshold (number) — Intelligent grouping similarity threshold. (0.5-1) - method (string) (required) — Grouping method: 'i' intelligent, 'p' pattern, 'n' none. [i, p, n] @@ -1024,8 +1027,14 @@ Request fields: --is-directly-discard bool — When true, suppressed target alerts are dropped instead of merged. --priority int — Evaluation priority. Lower runs first. --rule-name string (required) — Rule name, 1 to 39 characters. (1-39 chars) - source_filters (array, via --data) — Or-of-and filter tree. Each outer element is an AND group; within each group, all conditions must match. - target_filters (array, via --data) — Or-of-and filter tree. Each outer element is an AND group; within each group, all conditions must match. + source_filters (array>, via --data) — Or-of-and filter tree. Each outer element is an AND group; within each group, all conditions must match. + - key (string) (required) — Field key (e.g. 'alert_severity', 'labels.service'). + - oper (string) (required) — Filter operator. [IN, NOTIN] + - vals (array) (required) — Values to match. + target_filters (array>, via --data) — Or-of-and filter tree. Each outer element is an AND group; within each group, all conditions must match. + - key (string) (required) — Field key (e.g. 'alert_severity', 'labels.service'). + - oper (string) (required) — Filter operator. [IN, NOTIN] + - vals (array) (required) — Values to match. Response fields ('data' envelope is unwrapped — these fields are at the top level): - rule_id (string) (required) — Newly created rule ID (MongoDB ObjectID). @@ -1462,7 +1471,7 @@ Response fields ('data' envelope is unwrapped — rows are nested under items[]; - group (object) — Alert grouping configuration. - all_equals_required (boolean) — When true, all listed keys must be present for grouping. - cases (array) — Per-filter grouping overrides. - - equals (array) — Groups of label keys whose equality defines a bucket. + - equals (array>) — Groups of label keys whose equality defines a bucket. - i_keys (array) — Label keys used for intelligent grouping embeddings. - i_score_threshold (number) — Intelligent grouping similarity threshold. (0.5-1) - method (string) (required) — Grouping method: 'i' intelligent, 'p' pattern, 'n' none. [i, p, n] @@ -1587,7 +1596,10 @@ Request fields: --is-directly-discard bool — When true, silenced alerts are dropped instead of suppressed into incidents. --priority int — Evaluation priority. Lower runs first. --rule-name string (required) — Rule name, 1 to 39 characters. (1-39 chars) - filters (array, via --data) — Or-of-and filter tree. Each outer element is an AND group; within each group, all conditions must match. + filters (array>, via --data) — Or-of-and filter tree. Each outer element is an AND group; within each group, all conditions must match. + - key (string) (required) — Field key (e.g. 'alert_severity', 'labels.service'). + - oper (string) (required) — Filter operator. [IN, NOTIN] + - vals (array) (required) — Values to match. time_filter (object, via --data) — One-off time window defined by unix seconds. - end_time (integer) (required) — Window end (unix seconds). - start_time (integer) (required) — Window start (unix seconds). Must be less than 'end_time'. @@ -2011,7 +2023,10 @@ Request fields: --description string — Rule description, up to 500 characters. (≤500 chars) --priority int — Evaluation priority. Lower runs first. --rule-name string (required) — Rule name, 1 to 39 characters. (1-39 chars) - filters (array, via --data) — Or-of-and filter tree. Each outer element is an AND group; within each group, all conditions must match. + filters (array>, via --data) — Or-of-and filter tree. Each outer element is an AND group; within each group, all conditions must match. + - key (string) (required) — Field key (e.g. 'alert_severity', 'labels.service'). + - oper (string) (required) — Filter operator. [IN, NOTIN] + - vals (array) (required) — Values to match. Response fields ('data' envelope is unwrapped — these fields are at the top level): - rule_id (string) (required) — Newly created rule ID (MongoDB ObjectID). @@ -2402,7 +2417,7 @@ Request fields: group (object, via --data) — Alert grouping configuration. - all_equals_required (boolean) — When true, all listed keys must be present for grouping. - cases (array) — Per-filter grouping overrides. - - equals (array) — Groups of label keys whose equality defines a bucket. + - equals (array>) — Groups of label keys whose equality defines a bucket. - i_keys (array) — Label keys used for intelligent grouping embeddings. - i_score_threshold (number) — Intelligent grouping similarity threshold. (0.5-1) - method (string) (required) — Grouping method: 'i' intelligent, 'p' pattern, 'n' none. [i, p, n] diff --git a/internal/cli/zz_generated_response_help.go b/internal/cli/zz_generated_response_help.go index 2709736..784565c 100644 --- a/internal/cli/zz_generated_response_help.go +++ b/internal/cli/zz_generated_response_help.go @@ -43,8 +43,8 @@ var responseHelpBySDKMethod = map[string]string{ "Alerts.ReadInfo": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - account_id (integer) — Account ID.\n - alert_id (string) — Unique alert ID (ObjectID hex string).\n - alert_key (string) — Deduplication key.\n - alert_severity (string) — Current severity. [Critical, Warning, Info, Ok]\n - alert_status (string) — Current status. [Critical, Warning, Info, Ok]\n - channel_id (integer) — ID of the channel the alert belongs to.\n - channel_name (string) — Display name of the channel.\n - channel_status (string) — Status of the channel (e.g. `enabled`, `disabled`).\n - created_at (integer) — Creation timestamp, Unix epoch seconds.\n - data_source_id (integer) — Deprecated. Use `integration_id` instead. Deprecated: use `integration_id` instead.\n - data_source_name (string) — Deprecated. Use `integration_name` instead.\n - data_source_ref_id (string) — Deprecated. Use `integration_ref_id` instead.\n - data_source_type (string) — Deprecated. Use `integration_type` instead.\n - description (string) — Alert description.\n - end_time (integer) — Resolution time, Unix epoch seconds. 0 if still active.\n - event_cnt (integer) — Total number of raw events received by this alert.\n - events (array) — Recent raw events attached to this alert. Populated only by some endpoints.\n - account_id (integer) — Account ID.\n - alert_id (string) — Parent alert ID (MongoDB ObjectID).\n - alert_key (string) — Deduplication key used to merge events into an alert.\n - channel_id (integer) — Channel ID the event is routed to.\n - created_at (integer) — Record creation time, Unix epoch seconds.\n - data_source_id (integer) — Deprecated. Use `integration_id` instead.\n - deleted_at (integer) — Soft-delete timestamp (seconds). Zero if not deleted.\n - description (string) — Event description.\n - event_id (string) — Event ID (MongoDB ObjectID).\n - event_severity (string) — Severity of this event. [Critical, Warning, Info, Ok]\n - event_status (string) — Status of this event. [Critical, Warning, Info, Ok]\n - event_time (integer) — Event timestamp, Unix epoch seconds.\n - images (array) — Images attached to the event.\n - alt (string) — Alt text.\n - href (string) — Optional link URL when the image is clicked.\n - src (string) (required) — Image source URL or internal image reference (starts with `img_` or `http`).\n - integration_id (integer) — Integration that produced this event.\n - integration_type (string) — Type/plugin key of the integration that produced this event.\n - labels (object) — Label key-value pairs.\n - title (string) — Event title.\n - title_rule (string) — Title template used to derive `title` from labels.\n - updated_at (integer) — Record update time, Unix epoch seconds.\n - ever_muted (boolean) — True if this alert has ever been silenced.\n - images (array) — Images attached to the alert.\n - alt (string) — Alt text.\n - href (string) — Optional link URL when the image is clicked.\n - src (string) (required) — Image source URL or internal image reference (starts with `img_` or `http`).\n - incident (object) — Associated incident, if any.\n - incident_id (string) — Incident ID (ObjectID hex string).\n - progress (string) — Incident progress — one of `Triggered`, `Processing`, `Closed`.\n - title (string) — Incident title.\n - integration_id (integer) — ID of the integration that produced this alert.\n - integration_name (string) — Display name of the integration.\n - integration_ref_id (string) — External reference ID of the integration.\n - integration_type (string) — Type/plugin key of the integration.\n - labels (object) — Label key-value pairs.\n - last_time (integer) — Last-event time, Unix epoch seconds.\n - responder_email (string) — Email of the current responder (from the associated incident).\n - responder_name (string) — Display name of the current responder (from the associated incident).\n - start_time (integer) — First-seen time, Unix epoch seconds.\n - title (string) — Alert title.\n - title_rule (string) — Title template used to derive `title` from the event labels (e.g. `$service::$cluster`).\n - updated_at (integer) — Last update timestamp, Unix epoch seconds.\n", "Alerts.ReadList": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer) — Account ID.\n - alert_id (string) — Unique alert ID (ObjectID hex string).\n - alert_key (string) — Deduplication key.\n - alert_severity (string) — Current severity. [Critical, Warning, Info, Ok]\n - alert_status (string) — Current status. [Critical, Warning, Info, Ok]\n - channel_id (integer) — ID of the channel the alert belongs to.\n - channel_name (string) — Display name of the channel.\n - channel_status (string) — Status of the channel (e.g. `enabled`, `disabled`).\n - created_at (integer) — Creation timestamp, Unix epoch seconds.\n - data_source_id (integer) — Deprecated. Use `integration_id` instead. Deprecated: use `integration_id` instead.\n - data_source_name (string) — Deprecated. Use `integration_name` instead.\n - data_source_ref_id (string) — Deprecated. Use `integration_ref_id` instead.\n - data_source_type (string) — Deprecated. Use `integration_type` instead.\n - description (string) — Alert description.\n - end_time (integer) — Resolution time, Unix epoch seconds. 0 if still active.\n - event_cnt (integer) — Total number of raw events received by this alert.\n - events (array) — Recent raw events attached to this alert. Populated only by some endpoints.\n - account_id (integer) — Account ID.\n - alert_id (string) — Parent alert ID (MongoDB ObjectID).\n - alert_key (string) — Deduplication key used to merge events into an alert.\n - channel_id (integer) — Channel ID the event is routed to.\n - created_at (integer) — Record creation time, Unix epoch seconds.\n - data_source_id (integer) — Deprecated. Use `integration_id` instead.\n - deleted_at (integer) — Soft-delete timestamp (seconds). Zero if not deleted.\n - description (string) — Event description.\n - event_id (string) — Event ID (MongoDB ObjectID).\n - event_severity (string) — Severity of this event. [Critical, Warning, Info, Ok]\n - event_status (string) — Status of this event. [Critical, Warning, Info, Ok]\n - event_time (integer) — Event timestamp, Unix epoch seconds.\n - images (array) — Images attached to the event.\n - alt (string) — Alt text.\n - href (string) — Optional link URL when the image is clicked.\n - src (string) (required) — Image source URL or internal image reference (starts with `img_` or `http`).\n - integration_id (integer) — Integration that produced this event.\n - integration_type (string) — Type/plugin key of the integration that produced this event.\n - labels (object) — Label key-value pairs.\n - title (string) — Event title.\n - title_rule (string) — Title template used to derive `title` from labels.\n - updated_at (integer) — Record update time, Unix epoch seconds.\n - ever_muted (boolean) — True if this alert has ever been silenced.\n - images (array) — Images attached to the alert.\n - alt (string) — Alt text.\n - href (string) — Optional link URL when the image is clicked.\n - src (string) (required) — Image source URL or internal image reference (starts with `img_` or `http`).\n - incident (object) — Associated incident, if any.\n - incident_id (string) — Incident ID (ObjectID hex string).\n - progress (string) — Incident progress — one of `Triggered`, `Processing`, `Closed`.\n - title (string) — Incident title.\n - integration_id (integer) — ID of the integration that produced this alert.\n - integration_name (string) — Display name of the integration.\n - integration_ref_id (string) — External reference ID of the integration.\n - integration_type (string) — Type/plugin key of the integration.\n - labels (object) — Label key-value pairs.\n - last_time (integer) — Last-event time, Unix epoch seconds.\n - responder_email (string) — Email of the current responder (from the associated incident).\n - responder_name (string) — Display name of the current responder (from the associated incident).\n - start_time (integer) — First-seen time, Unix epoch seconds.\n - title (string) — Alert title.\n - title_rule (string) — Title template used to derive `title` from the event labels (e.g. `$service::$cluster`).\n - updated_at (integer) — Last update timestamp, Unix epoch seconds.\n", "Alerts.ReadListByIDs": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer) — Account ID.\n - alert_id (string) — Unique alert ID (ObjectID hex string).\n - alert_key (string) — Deduplication key.\n - alert_severity (string) — Current severity. [Critical, Warning, Info, Ok]\n - alert_status (string) — Current status. [Critical, Warning, Info, Ok]\n - channel_id (integer) — ID of the channel the alert belongs to.\n - channel_name (string) — Display name of the channel.\n - channel_status (string) — Status of the channel (e.g. `enabled`, `disabled`).\n - created_at (integer) — Creation timestamp, Unix epoch seconds.\n - data_source_id (integer) — Deprecated. Use `integration_id` instead. Deprecated: use `integration_id` instead.\n - data_source_name (string) — Deprecated. Use `integration_name` instead.\n - data_source_ref_id (string) — Deprecated. Use `integration_ref_id` instead.\n - data_source_type (string) — Deprecated. Use `integration_type` instead.\n - description (string) — Alert description.\n - end_time (integer) — Resolution time, Unix epoch seconds. 0 if still active.\n - event_cnt (integer) — Total number of raw events received by this alert.\n - events (array) — Recent raw events attached to this alert. Populated only by some endpoints.\n - account_id (integer) — Account ID.\n - alert_id (string) — Parent alert ID (MongoDB ObjectID).\n - alert_key (string) — Deduplication key used to merge events into an alert.\n - channel_id (integer) — Channel ID the event is routed to.\n - created_at (integer) — Record creation time, Unix epoch seconds.\n - data_source_id (integer) — Deprecated. Use `integration_id` instead.\n - deleted_at (integer) — Soft-delete timestamp (seconds). Zero if not deleted.\n - description (string) — Event description.\n - event_id (string) — Event ID (MongoDB ObjectID).\n - event_severity (string) — Severity of this event. [Critical, Warning, Info, Ok]\n - event_status (string) — Status of this event. [Critical, Warning, Info, Ok]\n - event_time (integer) — Event timestamp, Unix epoch seconds.\n - images (array) — Images attached to the event.\n - alt (string) — Alt text.\n - href (string) — Optional link URL when the image is clicked.\n - src (string) (required) — Image source URL or internal image reference (starts with `img_` or `http`).\n - integration_id (integer) — Integration that produced this event.\n - integration_type (string) — Type/plugin key of the integration that produced this event.\n - labels (object) — Label key-value pairs.\n - title (string) — Event title.\n - title_rule (string) — Title template used to derive `title` from labels.\n - updated_at (integer) — Record update time, Unix epoch seconds.\n - ever_muted (boolean) — True if this alert has ever been silenced.\n - images (array) — Images attached to the alert.\n - alt (string) — Alt text.\n - href (string) — Optional link URL when the image is clicked.\n - src (string) (required) — Image source URL or internal image reference (starts with `img_` or `http`).\n - incident (object) — Associated incident, if any.\n - incident_id (string) — Incident ID (ObjectID hex string).\n - progress (string) — Incident progress — one of `Triggered`, `Processing`, `Closed`.\n - title (string) — Incident title.\n - integration_id (integer) — ID of the integration that produced this alert.\n - integration_name (string) — Display name of the integration.\n - integration_ref_id (string) — External reference ID of the integration.\n - integration_type (string) — Type/plugin key of the integration.\n - labels (object) — Label key-value pairs.\n - last_time (integer) — Last-event time, Unix epoch seconds.\n - responder_email (string) — Email of the current responder (from the associated incident).\n - responder_name (string) — Display name of the current responder (from the associated incident).\n - start_time (integer) — First-seen time, Unix epoch seconds.\n - title (string) — Alert title.\n - title_rule (string) — Title template used to derive `title` from the event labels (e.g. `$service::$cluster`).\n - updated_at (integer) — Last update timestamp, Unix epoch seconds.\n", - "Alerts.ReadPipelineInfo": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - created_at (integer) — Creation timestamp, Unix epoch seconds.\n - creator_id (integer) — Member ID who created the pipeline.\n - integration_id (integer) — Integration ID this pipeline applies to.\n - rules (array) — Ordered list of processing rules.\n - if (array) — Optional OR-of-AND filter. When omitted, the rule applies to all alerts.\n - kind (string) — Rule type. [title_reset, description_reset, severity_reset, alert_drop, alert_inhibit]\n - settings (object) — Kind-specific settings. Shape depends on `kind`: - `title_reset`: `{ \"title\": \"\" }` - `description_reset`: `{ \"description\": \"\" }` - `severity_reset`: `{ \"severity\": \"Critical\"|\"Warning\"|\"Info\" }` - `alert_drop`: `{}` (empty object) - `alert_inhibit`: `{ \"equals\": [\"\", ...], \"source_filters\": }`\n - description (string) — New description template.\n - equals (array) — Label keys whose values must be equal between the source and current alert for inhibition to apply.\n - severity (string) — Target severity level. [Critical, Warning, Info]\n - source_filters (array) — Filter that identifies the source alerts to inhibit.\n - title (string) — New title template. Supports Golang template syntax referencing alert fields.\n - status (string) — Pipeline status. Possible values: `enabled`, `disabled`.\n - updated_at (integer) — Last update timestamp, Unix epoch seconds.\n - updated_by (integer) — Member ID who last updated the pipeline.\n", - "Alerts.ReadPipelineList": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - created_at (integer) — Creation timestamp, Unix epoch seconds.\n - creator_id (integer) — Member ID who created the pipeline.\n - integration_id (integer) — Integration ID this pipeline applies to.\n - rules (array) — Ordered list of processing rules.\n - if (array) — Optional OR-of-AND filter. When omitted, the rule applies to all alerts.\n - kind (string) — Rule type. [title_reset, description_reset, severity_reset, alert_drop, alert_inhibit]\n - settings (object) — Kind-specific settings. Shape depends on `kind`: - `title_reset`: `{ \"title\": \"\" }` - `description_reset`: `{ \"description\": \"\" }` - `severity_reset`: `{ \"severity\": \"Critical\"|\"Warning\"|\"Info\" }` - `alert_drop`: `{}` (empty object) - `alert_inhibit`: `{ \"equals\": [\"\", ...], \"source_filters\": }`\n - description (string) — New description template.\n - equals (array) — Label keys whose values must be equal between the source and current alert for inhibition to apply.\n - severity (string) — Target severity level. [Critical, Warning, Info]\n - source_filters (array) — Filter that identifies the source alerts to inhibit.\n - title (string) — New title template. Supports Golang template syntax referencing alert fields.\n - status (string) — Pipeline status. Possible values: `enabled`, `disabled`.\n - updated_at (integer) — Last update timestamp, Unix epoch seconds.\n - updated_by (integer) — Member ID who last updated the pipeline.\n", + "Alerts.ReadPipelineInfo": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - created_at (integer) — Creation timestamp, Unix epoch seconds.\n - creator_id (integer) — Member ID who created the pipeline.\n - integration_id (integer) — Integration ID this pipeline applies to.\n - rules (array) — Ordered list of processing rules.\n - if (array>) — Optional OR-of-AND filter. When omitted, the rule applies to all alerts.\n - key (string) (required) — Field name to filter on. Use plain names for built-in alert fields (e.g. `alert_severity`, `alert_key`, `check`, `resource`, `service`, `cluster`) or the `labels.` prefix for custom alert labels (e.g. `labels.env`, `labels.region`).\n - oper (string) (required) — Filter operator. `IN` — value must match one of `vals`; `NOTIN` — value must not match any of `vals`. Supports regex patterns wrapped in `/pattern/`. [IN, NOTIN]\n - vals (array) (required) — List of values to match against. Each entry is a plain string or a `/regex/` pattern.\n - kind (string) — Rule type. [title_reset, description_reset, severity_reset, alert_drop, alert_inhibit]\n - settings (object) — Kind-specific settings. Shape depends on `kind`: - `title_reset`: `{ \"title\": \"\" }` - `description_reset`: `{ \"description\": \"\" }` - `severity_reset`: `{ \"severity\": \"Critical\"|\"Warning\"|\"Info\" }` - `alert_drop`: `{}` (empty object) - `alert_inhibit`: `{ \"equals\": [\"\", ...], \"source_filters\": }`\n - description (string) — New description template.\n - equals (array) — Label keys whose values must be equal between the source and current alert for inhibition to apply.\n - severity (string) — Target severity level. [Critical, Warning, Info]\n - source_filters (array>) — Filter that identifies the source alerts to inhibit.\n - key (string) (required) — Field name to filter on. Use plain names for built-in alert fields (e.g. `alert_severity`, `alert_key`, `check`, `resource`, `service`, `cluster`) or the `labels.` prefix for custom alert labels (e.g. `labels.env`, `labels.region`).\n - oper (string) (required) — Filter operator. `IN` — value must match one of `vals`; `NOTIN` — value must not match any of `vals`. Supports regex patterns wrapped in `/pattern/`. [IN, NOTIN]\n - vals (array) (required) — List of values to match against. Each entry is a plain string or a `/regex/` pattern.\n - title (string) — New title template. Supports Golang template syntax referencing alert fields.\n - status (string) — Pipeline status. Possible values: `enabled`, `disabled`.\n - updated_at (integer) — Last update timestamp, Unix epoch seconds.\n - updated_by (integer) — Member ID who last updated the pipeline.\n", + "Alerts.ReadPipelineList": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - created_at (integer) — Creation timestamp, Unix epoch seconds.\n - creator_id (integer) — Member ID who created the pipeline.\n - integration_id (integer) — Integration ID this pipeline applies to.\n - rules (array) — Ordered list of processing rules.\n - if (array>) — Optional OR-of-AND filter. When omitted, the rule applies to all alerts.\n - key (string) (required) — Field name to filter on. Use plain names for built-in alert fields (e.g. `alert_severity`, `alert_key`, `check`, `resource`, `service`, `cluster`) or the `labels.` prefix for custom alert labels (e.g. `labels.env`, `labels.region`).\n - oper (string) (required) — Filter operator. `IN` — value must match one of `vals`; `NOTIN` — value must not match any of `vals`. Supports regex patterns wrapped in `/pattern/`. [IN, NOTIN]\n - vals (array) (required) — List of values to match against. Each entry is a plain string or a `/regex/` pattern.\n - kind (string) — Rule type. [title_reset, description_reset, severity_reset, alert_drop, alert_inhibit]\n - settings (object) — Kind-specific settings. Shape depends on `kind`: - `title_reset`: `{ \"title\": \"\" }` - `description_reset`: `{ \"description\": \"\" }` - `severity_reset`: `{ \"severity\": \"Critical\"|\"Warning\"|\"Info\" }` - `alert_drop`: `{}` (empty object) - `alert_inhibit`: `{ \"equals\": [\"\", ...], \"source_filters\": }`\n - description (string) — New description template.\n - equals (array) — Label keys whose values must be equal between the source and current alert for inhibition to apply.\n - severity (string) — Target severity level. [Critical, Warning, Info]\n - source_filters (array>) — Filter that identifies the source alerts to inhibit.\n - title (string) — New title template. Supports Golang template syntax referencing alert fields.\n - status (string) — Pipeline status. Possible values: `enabled`, `disabled`.\n - updated_at (integer) — Last update timestamp, Unix epoch seconds.\n - updated_by (integer) — Member ID who last updated the pipeline.\n", "Analytics.ByAccount": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer)\n - acknowledgement_pct (number)\n - channel_id (integer)\n - channel_name (string)\n - hours (string) — Hour bucket when `split_hours` is enabled. [work, sleep, off]\n - mean_seconds_to_ack (number)\n - mean_seconds_to_close (number)\n - noise_reduction_pct (number)\n - responder_id (integer)\n - responder_name (string)\n - team_id (integer)\n - team_name (string)\n - total_alert_cnt (integer)\n - total_alert_event_cnt (integer)\n - total_engaged_seconds (integer)\n - total_incident_cnt (integer)\n - total_incidents_acknowledged (integer)\n - total_incidents_auto_closed (integer)\n - total_incidents_closed (integer)\n - total_incidents_escalated (integer)\n - total_incidents_manually_closed (integer)\n - total_incidents_manually_escalated (integer)\n - total_incidents_reassigned (integer)\n - total_incidents_timeout_closed (integer)\n - total_incidents_timeout_escalated (integer)\n - total_interruptions (integer)\n - total_notifications (integer)\n - total_seconds_to_ack (integer)\n - total_seconds_to_close (integer)\n - ts (integer) — Aggregation bucket start time, Unix seconds. Present when `aggregate_unit` is used.\n", "Analytics.ByChannel": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer)\n - acknowledgement_pct (number)\n - channel_id (integer)\n - channel_name (string)\n - hours (string) — Hour bucket when `split_hours` is enabled. [work, sleep, off]\n - mean_seconds_to_ack (number)\n - mean_seconds_to_close (number)\n - noise_reduction_pct (number)\n - responder_id (integer)\n - responder_name (string)\n - team_id (integer)\n - team_name (string)\n - total_alert_cnt (integer)\n - total_alert_event_cnt (integer)\n - total_engaged_seconds (integer)\n - total_incident_cnt (integer)\n - total_incidents_acknowledged (integer)\n - total_incidents_auto_closed (integer)\n - total_incidents_closed (integer)\n - total_incidents_escalated (integer)\n - total_incidents_manually_closed (integer)\n - total_incidents_manually_escalated (integer)\n - total_incidents_reassigned (integer)\n - total_incidents_timeout_closed (integer)\n - total_incidents_timeout_escalated (integer)\n - total_interruptions (integer)\n - total_notifications (integer)\n - total_seconds_to_ack (integer)\n - total_seconds_to_close (integer)\n - ts (integer) — Aggregation bucket start time, Unix seconds. Present when `aggregate_unit` is used.\n", "Analytics.ByResponder": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer)\n - acknowledgement_pct (number)\n - channel_id (integer)\n - channel_name (string)\n - hours (string) — Hour bucket when `split_hours` is enabled. [work, sleep, off]\n - mean_seconds_to_ack (number)\n - responder_id (integer)\n - responder_name (string)\n - team_id (integer)\n - team_name (string)\n - total_engaged_seconds (integer)\n - total_incident_cnt (integer)\n - total_incidents_acknowledged (integer)\n - total_incidents_escalated (integer)\n - total_incidents_manually_escalated (integer)\n - total_incidents_reassigned (integer)\n - total_incidents_timeout_escalated (integer)\n - total_interruptions (integer)\n - total_notifications (integer)\n - total_seconds_to_ack (integer)\n - ts (integer) — Aggregation bucket start time, Unix seconds. Present when `aggregate_unit` is used.\n", @@ -75,11 +75,11 @@ var responseHelpBySDKMethod = map[string]string{ "Channels.ChannelEscalateRuleCreate": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - rule_id (string) (required) — Newly created rule ID (MongoDB ObjectID).\n - rule_name (string) (required) — Rule name echoed back from the request.\n", "Channels.ChannelEscalateRuleInfo": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - account_id (integer) (required) — Owning account ID.\n - aggr_window (integer) (required) — Delay window in seconds.\n - channel_id (integer) (required) — Channel the rule belongs to.\n - channel_name (string) — Channel name, populated for cross-channel listing responses.\n - created_at (integer) (required) — Creation timestamp (unix seconds).\n - deleted_at (integer) — Deletion timestamp (unix seconds). Emitted only for soft-deleted rules.\n - description (string) (required) — Rule description.\n - filters (object) (required)\n - layers (array) (required) — Escalation levels in order.\n - escalate_window (integer) — Wait before moving to the next level, in minutes. (0-720)\n - force_escalate (boolean) — When true, always escalate regardless of acknowledgement.\n - max_times (integer) — Max repeat notifications within the level. (0-6)\n - notify_step (number) — Repeat interval in minutes. (0.5-120)\n - target (object) (required) — Notification target. At least one of `person_ids`, `team_ids`, `schedule_to_role_ids`, or `emails` must be set, together with either `by` or `webhooks`.\n - by (object) — Per-severity personal notification channels. Required unless `webhooks` is provided.\n - critical (array) — Channels for Critical events (e.g. `voice`, `sms`, `email`, `feishu`).\n - follow_preference (boolean) — When true, use each responder's personal preference instead of the lists below.\n - info (array) — Channels for Info events.\n - warning (array) — Channels for Warning events.\n - emails (array) — Email addresses to notify (push-only scenarios).\n - person_ids (array) — Member IDs to notify directly.\n - schedule_to_role_ids (object) — Map of schedule ID to the role IDs on that schedule to notify.\n - team_ids (array) — Team IDs to notify.\n - webhooks (array) — Group chat / webhook targets. Required unless `by` is provided.\n - settings (object) (required) — Type-specific settings (chat IDs, URLs, etc.).\n - type (string) (required) — Webhook type (e.g. `feishu`, `dingtalk_app`, `wecom_app`, `slack`, `teams`, `custom`).\n - priority (integer) (required) — Evaluation priority. Lower runs first.\n - rule_id (string) (required) — Escalation rule ID (MongoDB ObjectID).\n - rule_name (string) (required) — Rule name.\n - status (string) (required) — Rule status. [enabled, disabled]\n - template_id (string) (required) — Notification template ID (MongoDB ObjectID).\n - time_filters (array) (required) — Recurring time windows during which the rule applies.\n - cal_id (string) — Optional calendar ID; restricts the window to days matching the calendar.\n - end (string) — End of the window in `HH:MM`.\n - is_off (boolean) — When true, match days marked as days-off in the calendar.\n - repeat (array) — Days of the week this window repeats on. Empty means every day.\n - start (string) — Start of the window in `HH:MM`.\n - updated_at (integer) (required) — Last update timestamp (unix seconds).\n - updated_by (integer) (required) — Member ID that last updated the rule.\n", "Channels.ChannelEscalateRuleList": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer) (required) — Owning account ID.\n - aggr_window (integer) (required) — Delay window in seconds.\n - channel_id (integer) (required) — Channel the rule belongs to.\n - channel_name (string) — Channel name, populated for cross-channel listing responses.\n - created_at (integer) (required) — Creation timestamp (unix seconds).\n - deleted_at (integer) — Deletion timestamp (unix seconds). Emitted only for soft-deleted rules.\n - description (string) (required) — Rule description.\n - filters (object) (required)\n - layers (array) (required) — Escalation levels in order.\n - escalate_window (integer) — Wait before moving to the next level, in minutes. (0-720)\n - force_escalate (boolean) — When true, always escalate regardless of acknowledgement.\n - max_times (integer) — Max repeat notifications within the level. (0-6)\n - notify_step (number) — Repeat interval in minutes. (0.5-120)\n - target (object) (required) — Notification target. At least one of `person_ids`, `team_ids`, `schedule_to_role_ids`, or `emails` must be set, together with either `by` or `webhooks`.\n - by (object) — Per-severity personal notification channels. Required unless `webhooks` is provided.\n - emails (array) — Email addresses to notify (push-only scenarios).\n - person_ids (array) — Member IDs to notify directly.\n - schedule_to_role_ids (object) — Map of schedule ID to the role IDs on that schedule to notify.\n - team_ids (array) — Team IDs to notify.\n - webhooks (array) — Group chat / webhook targets. Required unless `by` is provided.\n - priority (integer) (required) — Evaluation priority. Lower runs first.\n - rule_id (string) (required) — Escalation rule ID (MongoDB ObjectID).\n - rule_name (string) (required) — Rule name.\n - status (string) (required) — Rule status. [enabled, disabled]\n - template_id (string) (required) — Notification template ID (MongoDB ObjectID).\n - time_filters (array) (required) — Recurring time windows during which the rule applies.\n - cal_id (string) — Optional calendar ID; restricts the window to days matching the calendar.\n - end (string) — End of the window in `HH:MM`.\n - is_off (boolean) — When true, match days marked as days-off in the calendar.\n - repeat (array) — Days of the week this window repeats on. Empty means every day.\n - start (string) — Start of the window in `HH:MM`.\n - updated_at (integer) (required) — Last update timestamp (unix seconds).\n - updated_by (integer) (required) — Member ID that last updated the rule.\n", - "Channels.ChannelInfo": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - account_id (integer) — Owning account ID.\n - active_incident_highest_severity (string) — Highest severity among active incidents in the channel.\n - auto_resolve_mode (string) — Auto-resolve timer reset mode. [trigger, update]\n - auto_resolve_timeout (integer) — Auto-resolve timeout in seconds. 0 disables auto-resolve.\n - channel_id (integer) — Channel ID.\n - channel_name (string) — Channel name.\n - created_at (integer) — Creation timestamp (unix seconds).\n - creator_id (integer) — Member ID who created the channel.\n - creator_name (string) — Name of the member who created the channel (resolved from the member directory; empty when unavailable).\n - deleted_at (integer) — Deletion timestamp (unix seconds). Non-zero only for soft-deleted channels.\n - description (string) — Free-form description.\n - disable_auto_close (boolean) — When true, automatic incident closing is disabled.\n - disable_outlier_detection (boolean) — When true, outlier incident detection is disabled.\n - external_report_token (string) — Token granted to external reporters when external reporting is enabled.\n - flapping (object) — Flapping detection configuration.\n - in_mins (integer) — Observation window in minutes. (1-1440)\n - is_disabled (boolean) — Disable flapping detection.\n - max_changes (integer) — Max state changes allowed within `in_mins`. (2-100)\n - mute_mins (integer) — Mute duration in minutes after flapping is detected. (0-1440)\n - group (object) — Alert grouping configuration.\n - all_equals_required (boolean) — When true, all listed keys must be present for grouping.\n - cases (array) — Per-filter grouping overrides.\n - equals (array) — Groups of label keys whose equality defines a bucket.\n - i_keys (array) — Label keys used for intelligent grouping embeddings.\n - i_score_threshold (number) — Intelligent grouping similarity threshold. (0.5-1)\n - method (string) (required) — Grouping method: `i` intelligent, `p` pattern, `n` none. [i, p, n]\n - storm_threshold (integer) — Alert storm threshold. (0-10000)\n - storm_thresholds (array) — Multi-level storm thresholds.\n - time_window (integer) — Grouping time window in minutes. Default max is 1440 minutes (24 h); extended accounts may allow up to 43200 minutes (30 days). (min 0)\n - window_type (string) — Window type. Defaults to `tumbling`. [tumbling, sliding]\n - is_external_report_enabled (boolean) — Whether external reporters can file incidents into this channel.\n - is_private (boolean) — When true, the channel is visible only to its managing teams.\n - is_starred (boolean) — Whether the current user has starred this channel.\n - last_incident_at (integer) — Timestamp of the most recent incident (unix seconds).\n - managing_team_ids (array) — Additional teams that can manage the channel.\n - progress_to_incident_cnts (object)\n - Processing (integer) (required) — Count of processing incidents in the last 30 days.\n - Triggered (integer) (required) — Count of triggered incidents in the last 30 days.\n - status (string) — Channel status. [enabled, disabled, deleted]\n - team_id (integer) — Owning team ID.\n - team_name (string) — Owning team name (resolved from the team directory; empty when unavailable).\n - updated_at (integer) — Last update timestamp (unix seconds).\n", + "Channels.ChannelInfo": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - account_id (integer) — Owning account ID.\n - active_incident_highest_severity (string) — Highest severity among active incidents in the channel.\n - auto_resolve_mode (string) — Auto-resolve timer reset mode. [trigger, update]\n - auto_resolve_timeout (integer) — Auto-resolve timeout in seconds. 0 disables auto-resolve.\n - channel_id (integer) — Channel ID.\n - channel_name (string) — Channel name.\n - created_at (integer) — Creation timestamp (unix seconds).\n - creator_id (integer) — Member ID who created the channel.\n - creator_name (string) — Name of the member who created the channel (resolved from the member directory; empty when unavailable).\n - deleted_at (integer) — Deletion timestamp (unix seconds). Non-zero only for soft-deleted channels.\n - description (string) — Free-form description.\n - disable_auto_close (boolean) — When true, automatic incident closing is disabled.\n - disable_outlier_detection (boolean) — When true, outlier incident detection is disabled.\n - external_report_token (string) — Token granted to external reporters when external reporting is enabled.\n - flapping (object) — Flapping detection configuration.\n - in_mins (integer) — Observation window in minutes. (1-1440)\n - is_disabled (boolean) — Disable flapping detection.\n - max_changes (integer) — Max state changes allowed within `in_mins`. (2-100)\n - mute_mins (integer) — Mute duration in minutes after flapping is detected. (0-1440)\n - group (object) — Alert grouping configuration.\n - all_equals_required (boolean) — When true, all listed keys must be present for grouping.\n - cases (array) — Per-filter grouping overrides.\n - equals (array>) — Groups of label keys whose equality defines a bucket.\n - i_keys (array) — Label keys used for intelligent grouping embeddings.\n - i_score_threshold (number) — Intelligent grouping similarity threshold. (0.5-1)\n - method (string) (required) — Grouping method: `i` intelligent, `p` pattern, `n` none. [i, p, n]\n - storm_threshold (integer) — Alert storm threshold. (0-10000)\n - storm_thresholds (array) — Multi-level storm thresholds.\n - time_window (integer) — Grouping time window in minutes. Default max is 1440 minutes (24 h); extended accounts may allow up to 43200 minutes (30 days). (min 0)\n - window_type (string) — Window type. Defaults to `tumbling`. [tumbling, sliding]\n - is_external_report_enabled (boolean) — Whether external reporters can file incidents into this channel.\n - is_private (boolean) — When true, the channel is visible only to its managing teams.\n - is_starred (boolean) — Whether the current user has starred this channel.\n - last_incident_at (integer) — Timestamp of the most recent incident (unix seconds).\n - managing_team_ids (array) — Additional teams that can manage the channel.\n - progress_to_incident_cnts (object)\n - Processing (integer) (required) — Count of processing incidents in the last 30 days.\n - Triggered (integer) (required) — Count of triggered incidents in the last 30 days.\n - status (string) — Channel status. [enabled, disabled, deleted]\n - team_id (integer) — Owning team ID.\n - team_name (string) — Owning team name (resolved from the team directory; empty when unavailable).\n - updated_at (integer) — Last update timestamp (unix seconds).\n", "Channels.ChannelInfos": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - channel_id (integer) (required) — Channel ID.\n - channel_name (string) (required) — Channel name.\n - status (string) — Channel status. [enabled, disabled]\n", "Channels.ChannelInhibitRuleCreate": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - rule_id (string) (required) — Newly created rule ID (MongoDB ObjectID).\n - rule_name (string) (required) — Rule name echoed back from the request.\n", "Channels.ChannelInhibitRuleList": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer) (required)\n - channel_id (integer) (required)\n - created_at (integer) (required)\n - deleted_at (integer)\n - description (string) (required)\n - equals (array) (required) — Label keys used to pair source and target alerts.\n - is_directly_discard (boolean) (required)\n - priority (integer) (required)\n - rule_id (string) (required)\n - rule_name (string) (required)\n - source_filters (object) (required)\n - status (string) (required) [enabled, disabled]\n - target_filters (object) (required)\n - updated_at (integer) (required)\n - updated_by (integer) (required)\n", - "Channels.ChannelList": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer) — Owning account ID.\n - active_incident_highest_severity (string) — Highest severity among active incidents in the channel.\n - auto_resolve_mode (string) — Auto-resolve timer reset mode. [trigger, update]\n - auto_resolve_timeout (integer) — Auto-resolve timeout in seconds. 0 disables auto-resolve.\n - channel_id (integer) — Channel ID.\n - channel_name (string) — Channel name.\n - created_at (integer) — Creation timestamp (unix seconds).\n - creator_id (integer) — Member ID who created the channel.\n - creator_name (string) — Name of the member who created the channel (resolved from the member directory; empty when unavailable).\n - deleted_at (integer) — Deletion timestamp (unix seconds). Non-zero only for soft-deleted channels.\n - description (string) — Free-form description.\n - disable_auto_close (boolean) — When true, automatic incident closing is disabled.\n - disable_outlier_detection (boolean) — When true, outlier incident detection is disabled.\n - external_report_token (string) — Token granted to external reporters when external reporting is enabled.\n - flapping (object) — Flapping detection configuration.\n - in_mins (integer) — Observation window in minutes. (1-1440)\n - is_disabled (boolean) — Disable flapping detection.\n - max_changes (integer) — Max state changes allowed within `in_mins`. (2-100)\n - mute_mins (integer) — Mute duration in minutes after flapping is detected. (0-1440)\n - group (object) — Alert grouping configuration.\n - all_equals_required (boolean) — When true, all listed keys must be present for grouping.\n - cases (array) — Per-filter grouping overrides.\n - equals (array) — Groups of label keys whose equality defines a bucket.\n - i_keys (array) — Label keys used for intelligent grouping embeddings.\n - i_score_threshold (number) — Intelligent grouping similarity threshold. (0.5-1)\n - method (string) (required) — Grouping method: `i` intelligent, `p` pattern, `n` none. [i, p, n]\n - storm_threshold (integer) — Alert storm threshold. (0-10000)\n - storm_thresholds (array) — Multi-level storm thresholds.\n - time_window (integer) — Grouping time window in minutes. Default max is 1440 minutes (24 h); extended accounts may allow up to 43200 minutes (30 days). (min 0)\n - window_type (string) — Window type. Defaults to `tumbling`. [tumbling, sliding]\n - is_external_report_enabled (boolean) — Whether external reporters can file incidents into this channel.\n - is_private (boolean) — When true, the channel is visible only to its managing teams.\n - is_starred (boolean) — Whether the current user has starred this channel.\n - last_incident_at (integer) — Timestamp of the most recent incident (unix seconds).\n - managing_team_ids (array) — Additional teams that can manage the channel.\n - progress_to_incident_cnts (object)\n - Processing (integer) (required) — Count of processing incidents in the last 30 days.\n - Triggered (integer) (required) — Count of triggered incidents in the last 30 days.\n - status (string) — Channel status. [enabled, disabled, deleted]\n - team_id (integer) — Owning team ID.\n - team_name (string) — Owning team name (resolved from the team directory; empty when unavailable).\n - updated_at (integer) — Last update timestamp (unix seconds).\n", + "Channels.ChannelList": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer) — Owning account ID.\n - active_incident_highest_severity (string) — Highest severity among active incidents in the channel.\n - auto_resolve_mode (string) — Auto-resolve timer reset mode. [trigger, update]\n - auto_resolve_timeout (integer) — Auto-resolve timeout in seconds. 0 disables auto-resolve.\n - channel_id (integer) — Channel ID.\n - channel_name (string) — Channel name.\n - created_at (integer) — Creation timestamp (unix seconds).\n - creator_id (integer) — Member ID who created the channel.\n - creator_name (string) — Name of the member who created the channel (resolved from the member directory; empty when unavailable).\n - deleted_at (integer) — Deletion timestamp (unix seconds). Non-zero only for soft-deleted channels.\n - description (string) — Free-form description.\n - disable_auto_close (boolean) — When true, automatic incident closing is disabled.\n - disable_outlier_detection (boolean) — When true, outlier incident detection is disabled.\n - external_report_token (string) — Token granted to external reporters when external reporting is enabled.\n - flapping (object) — Flapping detection configuration.\n - in_mins (integer) — Observation window in minutes. (1-1440)\n - is_disabled (boolean) — Disable flapping detection.\n - max_changes (integer) — Max state changes allowed within `in_mins`. (2-100)\n - mute_mins (integer) — Mute duration in minutes after flapping is detected. (0-1440)\n - group (object) — Alert grouping configuration.\n - all_equals_required (boolean) — When true, all listed keys must be present for grouping.\n - cases (array) — Per-filter grouping overrides.\n - equals (array>) — Groups of label keys whose equality defines a bucket.\n - i_keys (array) — Label keys used for intelligent grouping embeddings.\n - i_score_threshold (number) — Intelligent grouping similarity threshold. (0.5-1)\n - method (string) (required) — Grouping method: `i` intelligent, `p` pattern, `n` none. [i, p, n]\n - storm_threshold (integer) — Alert storm threshold. (0-10000)\n - storm_thresholds (array) — Multi-level storm thresholds.\n - time_window (integer) — Grouping time window in minutes. Default max is 1440 minutes (24 h); extended accounts may allow up to 43200 minutes (30 days). (min 0)\n - window_type (string) — Window type. Defaults to `tumbling`. [tumbling, sliding]\n - is_external_report_enabled (boolean) — Whether external reporters can file incidents into this channel.\n - is_private (boolean) — When true, the channel is visible only to its managing teams.\n - is_starred (boolean) — Whether the current user has starred this channel.\n - last_incident_at (integer) — Timestamp of the most recent incident (unix seconds).\n - managing_team_ids (array) — Additional teams that can manage the channel.\n - progress_to_incident_cnts (object)\n - Processing (integer) (required) — Count of processing incidents in the last 30 days.\n - Triggered (integer) (required) — Count of triggered incidents in the last 30 days.\n - status (string) — Channel status. [enabled, disabled, deleted]\n - team_id (integer) — Owning team ID.\n - team_name (string) — Owning team name (resolved from the team directory; empty when unavailable).\n - updated_at (integer) — Last update timestamp (unix seconds).\n", "Channels.ChannelSilenceRuleCreate": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - rule_id (string) (required) — Newly created rule ID (MongoDB ObjectID).\n - rule_name (string) (required) — Rule name echoed back from the request.\n", "Channels.ChannelSilenceRuleList": "Response fields (this command's `--json` is a TOP-LEVEL array of these row objects — pipe `jq '.[]'`, NOT `.items[]`):\n - account_id (integer) (required)\n - channel_id (integer) (required)\n - created_at (integer) (required)\n - deleted_at (integer)\n - description (string) (required)\n - filters (object) (required)\n - from_incident_id (string) — Source incident ID when the silence was created from an incident.\n - is_auto_delete (boolean) — When true, the silence rule is automatically deleted after its time window expires. Defaults to false.\n - is_directly_discard (boolean) (required) — When true, silenced alerts are dropped instead of suppressed into incidents.\n - is_effective (boolean) (required) — Whether the rule is currently in effect.\n - priority (integer) (required) — Evaluation priority. Lower runs first.\n - rule_id (string) (required)\n - rule_name (string) (required)\n - status (string) (required) [enabled, disabled]\n - time_filter (object) (required) — One-off time window defined by unix seconds.\n - end_time (integer) (required) — Window end (unix seconds). Must be > 0.\n - start_time (integer) (required) — Window start (unix seconds). Must be > 0 and less than `end_time`.\n - time_filters (array) (required) — Recurring time windows.\n - cal_id (string) — Optional calendar ID; restricts the window to days matching the calendar.\n - end (string) — End of the window in `HH:MM`.\n - is_off (boolean) — When true, match days marked as days-off in the calendar.\n - repeat (array) — Days of the week this window repeats on. Empty means every day.\n - start (string) — Start of the window in `HH:MM`.\n - updated_at (integer) (required)\n - updated_by (integer) (required)\n", "Channels.ChannelUnsubscribeRuleCreate": "Response fields (`data` envelope is unwrapped — these fields are at the top level):\n - rule_id (string) (required) — Newly created rule ID (MongoDB ObjectID).\n - rule_name (string) (required) — Rule name echoed back from the request.\n", diff --git a/internal/cmd/cligen/main.go b/internal/cmd/cligen/main.go index 4a0f70c..83b85cc 100644 --- a/internal/cmd/cligen/main.go +++ b/internal/cmd/cligen/main.go @@ -481,19 +481,14 @@ func (w *specWalker) enumOf(s map[string]any) []string { return nil } -// schemaType renders a compact type label for a (deref'd) property schema. +// schemaType renders a compact type label for a (deref'd) property schema, +// recursing through nested array `items` so an "Or-of-AND" filter tree +// (array>) renders its true element type instead of collapsing +// to the uninformative "array" one level down. func schemaType(s map[string]any) string { switch t := str(s, "type"); t { case "array": - it := asMap(s["items"]) - et := str(it, "type") - if et == "" && (it["properties"] != nil || it["allOf"] != nil || it["$ref"] != nil) { - et = "object" - } - if et == "" { - et = "any" - } - return "array<" + et + ">" + return "array<" + schemaType(asMap(s["items"])) + ">" case "": if s["properties"] != nil || s["allOf"] != nil || s["$ref"] != nil { return "object" @@ -552,8 +547,24 @@ func numStr(v any) (string, bool) { // maxSchemaDepth bounds how deep request/response trees are expanded in --help. const maxSchemaDepth = 3 +// arrayLeafSchema unwraps an array schema's `items` (deref'ing $ref at each +// level) through any number of nested array layers — e.g. the "Or-of-AND" +// filter trees (`array>`) — until it reaches the first +// non-array element schema. A plain `array` schema returns its object +// items unchanged (zero unwraps), so tree()'s array case handles any nesting +// depth the same way it always handled one level. Bounded by maxSchemaDepth as +// a defensive stop against a cyclic schema. +func (w *specWalker) arrayLeafSchema(s map[string]any) map[string]any { + it := w.deref(asMap(s["items"])) + for levels := 0; str(it, "type") == "array" && levels < maxSchemaDepth; levels++ { + it = w.deref(asMap(it["items"])) + } + return it +} + // tree walks an object schema (resolving $ref/allOf) into a sorted field tree, -// recursing into nested objects and array-element objects up to maxSchemaDepth. +// recursing into nested objects and array-element objects (through any depth +// of nested arrays, see arrayLeafSchema) up to maxSchemaDepth. func (w *specWalker) tree(schema map[string]any, depth int) []schemaField { if depth > maxSchemaDepth { return nil @@ -576,11 +587,11 @@ func (w *specWalker) tree(schema map[string]any, depth int) []schemaField { f.Type = "object" f.Children = w.tree(pv, depth+1) case str(pv, "type") == "array": - it := w.deref(asMap(pv["items"])) + it := w.arrayLeafSchema(pv) if w.isObjectSchema(it) { f.Children = w.tree(it, depth+1) } else if len(f.Enum) == 0 { - f.Enum = enumStrings(it) // array of constrained scalars + f.Enum = enumStrings(it) // array (possibly nested) of constrained scalars } } out = append(out, f) diff --git a/internal/cmd/cligen/nested_array_test.go b/internal/cmd/cligen/nested_array_test.go new file mode 100644 index 0000000..c43aeb0 --- /dev/null +++ b/internal/cmd/cligen/nested_array_test.go @@ -0,0 +1,114 @@ +package main + +import "testing" + +// TestTreeExpandsNestedArrayOfObject covers the "Or-of-AND" filter tree shape +// (array>, e.g. silence-rule-create's `filters`): the object +// item fields two array layers down must render as children exactly like a +// plain array field does one layer down. +func TestTreeExpandsNestedArrayOfObject(t *testing.T) { + w := &specWalker{schemas: map[string]any{ + "FilterCondition": map[string]any{ + "type": "object", + "required": []any{"key", "oper", "vals"}, + "properties": map[string]any{ + "key": map[string]any{"type": "string", "description": "e.g. `alert_severity`, `labels.service`"}, + "oper": map[string]any{"type": "string", "enum": []any{"IN", "NOTIN"}}, + "vals": map[string]any{"type": "array", "items": map[string]any{"type": "string"}}, + }, + }, + }} + + fields := w.tree(map[string]any{"properties": map[string]any{ + "filters": map[string]any{ + "type": "array", + "items": map[string]any{ + "type": "array", + "items": map[string]any{"$ref": "#/components/schemas/FilterCondition"}, + }, + }, + }}, 0) + + if len(fields) != 1 || fields[0].Wire != "filters" { + t.Fatalf("fields = %#v, want single 'filters' field", fields) + } + children := fields[0].Children + if len(children) != 3 { + t.Fatalf("filters.Children = %#v, want 3 (key, oper, vals)", children) + } + byWire := map[string]schemaField{} + for _, c := range children { + byWire[c.Wire] = c + } + if byWire["key"].Type != "string" || byWire["key"].Desc == "" { + t.Fatalf("key child = %#v, want string with description", byWire["key"]) + } + if len(byWire["oper"].Enum) != 2 { + t.Fatalf("oper.Enum = %#v, want [IN NOTIN]", byWire["oper"].Enum) + } + if byWire["vals"].Type != "array" { + t.Fatalf("vals.Type = %q, want array", byWire["vals"].Type) + } +} + +// TestTreeLeavesNestedArrayOfScalarWithoutChildren guards the sibling shape +// (array>, e.g. the alert-grouping `equals` field): with no +// object at the bottom, tree() must not synthesize children. +func TestTreeLeavesNestedArrayOfScalarWithoutChildren(t *testing.T) { + w := &specWalker{} + + fields := w.tree(map[string]any{"properties": map[string]any{ + "equals": map[string]any{ + "type": "array", + "items": map[string]any{"type": "array", "items": map[string]any{"type": "string"}}, + }, + }}, 0) + + if len(fields) != 1 || fields[0].Wire != "equals" { + t.Fatalf("fields = %#v, want single 'equals' field", fields) + } + if len(fields[0].Children) != 0 { + t.Fatalf("equals.Children = %#v, want none", fields[0].Children) + } +} + +// TestSchemaTypeLabelsNestedArrayDepth guards the type label alongside the +// child-expansion behavior above: "array" hid the same missing-depth +// information for the flag/summary line that the missing children hid for +// the field list, and both stem from the array case only unwrapping one +// `items` level. +func TestSchemaTypeLabelsNestedArrayDepth(t *testing.T) { + cases := []struct { + name string + s map[string]any + want string + }{ + {"scalar array", map[string]any{"type": "array", "items": map[string]any{"type": "string"}}, "array"}, + {"array of object", map[string]any{"type": "array", "items": map[string]any{"type": "object"}}, "array"}, + {"array of array of object (filters)", map[string]any{"type": "array", "items": map[string]any{"type": "array", "items": map[string]any{"$ref": "#/components/schemas/FilterCondition"}}}, "array>"}, + {"array of array of string (equals)", map[string]any{"type": "array", "items": map[string]any{"type": "array", "items": map[string]any{"type": "string"}}}, "array>"}, + } + for _, c := range cases { + if got := schemaType(c.s); got != c.want { + t.Errorf("%s: schemaType() = %q, want %q", c.name, got, c.want) + } + } +} + +// TestTreeExpandsPlainArrayOfObjectUnchanged is a regression guard: the +// existing single-level array behavior (e.g. `layers`) must be +// unaffected by generalizing the array case to unwrap nested arrays. +func TestTreeExpandsPlainArrayOfObjectUnchanged(t *testing.T) { + w := &specWalker{} + + fields := w.tree(map[string]any{"properties": map[string]any{ + "layers": map[string]any{ + "type": "array", + "items": map[string]any{"type": "object", "properties": map[string]any{"target": map[string]any{"type": "string"}}}, + }, + }}, 0) + + if len(fields) != 1 || len(fields[0].Children) != 1 || fields[0].Children[0].Wire != "target" { + t.Fatalf("layers field = %#v, want single child 'target'", fields[0]) + } +} diff --git a/skills/flashduty/reference/channel.md b/skills/flashduty/reference/channel.md index 82f98a4..7afe08f 100644 --- a/skills/flashduty/reference/channel.md +++ b/skills/flashduty/reference/channel.md @@ -48,13 +48,26 @@ fduty channel escalate-rule-create \ ## Hot flow — add a silence rule during maintenance +A silence rule needs BOTH a time window (`time_filter` or `time_filters`) AND +`filters` naming which alerts the window applies to — a `time_filter`-only +rule matches nothing and the server rejects it. Build `filters` from the +target incident's own labels (see "Building `filters` from incident labels" +below for the general rule). + ```bash -# channel-id is POSITIONAL on silence-rule-create (see use: "silence-rule-create ") +# 1. inspect the incident to silence around — pulls incident_severity + labels +fduty incident detail --output-format toon + +# 2. channel-id is POSITIONAL on silence-rule-create (see use: "silence-rule-create ") +# filters is one AND group: a severity condition plus one labels. condition +# per distinguishing label — id-shaped/long/date-shaped/noise-key label values are +# dropped, not passed through (see "Building filters from incident labels" below). fduty channel silence-rule-create \ --rule-name "planned-maintenance-2026-07-01" \ --is-auto-delete \ - --data '{"time_filter":{"start_time":1751328000,"end_time":1751371200}}' -# verify + --data '{"time_filter":{"start_time":1751328000,"end_time":1751371200},"filters":[[{"key":"alert_severity","oper":"IN","vals":["Critical"]},{"key":"labels.service","oper":"IN","vals":["payments-api"]},{"key":"labels.env","oper":"IN","vals":["prod"]}]]}' + +# 3. verify — read back `filters` to confirm the conditions round-tripped fduty channel silence-rule-list --output-format toon ``` @@ -96,7 +109,7 @@ Create escalation rule - `--priority` int64 — Evaluation priority. Lower runs first. (0-200) - `--rule-name` string (required) — Rule name, 1 to 39 characters. (1-39 chars) - `--template-id` string (required) — Notification template ID (MongoDB ObjectID). -- body-only (`--data`): filters (array); layers (array) (required); time_filters (array) +- body-only (`--data`): filters (array>); layers (array) (required); time_filters (array) - response: single object (`data` unwrapped to the top level) — fields: rule_id (string); rule_name (string) ### escalate-rule-delete @@ -154,7 +167,7 @@ Create inhibit rule - `--is-directly-discard` bool — When true, suppressed target alerts are dropped instead of merged. - `--priority` int64 — Evaluation priority. Lower runs first. - `--rule-name` string (required) — Rule name, 1 to 39 characters. (1-39 chars) -- body-only (`--data`): source_filters (array); target_filters (array) +- body-only (`--data`): source_filters (array>); target_filters (array>) - response: single object (`data` unwrapped to the top level) — fields: rule_id (string); rule_name (string) ### inhibit-rule-delete @@ -203,7 +216,7 @@ Create silence rule - `--is-directly-discard` bool — When true, silenced alerts are dropped instead of suppressed into incidents. - `--priority` int64 — Evaluation priority. Lower runs first. - `--rule-name` string (required) — Rule name, 1 to 39 characters. (1-39 chars) -- body-only (`--data`): filters (array); time_filter (object); time_filters (array) +- body-only (`--data`): filters (array>); time_filter (object); time_filters (array) - response: single object (`data` unwrapped to the top level) — fields: rule_id (string); rule_name (string) ### silence-rule-delete @@ -243,7 +256,7 @@ Create drop rule - `--description` string — Rule description, up to 500 characters. (≤500 chars) - `--priority` int64 — Evaluation priority. Lower runs first. - `--rule-name` string (required) — Rule name, 1 to 39 characters. (1-39 chars) -- body-only (`--data`): filters (array) +- body-only (`--data`): filters (array>) - response: single object (`data` unwrapped to the top level) — fields: rule_id (string); rule_name (string) ### unsubscribe-rule-delete @@ -302,6 +315,38 @@ Update channel - **Silence time windows**: `time_filter` (one-off, unix seconds, mutually exclusive) vs `time_filters` (recurring weekly HH:MM windows). Pass via `--data`. - **Escalation `layers`** (required via `--data` on create/update): each layer needs `target` (with `person_ids`/`team_ids`/`schedule_to_role_ids`/`emails` + `by` OR `webhooks`) and optionally `notify_step`, `max_times`, `escalate_window`, `force_escalate`. +### Building `filters` from incident labels + +`filters` (silence-rule, inhibit-rule's `source_filters`/`target_filters`, +unsubscribe-rule) is an OR-of-AND condition tree: the outer array holds AND +groups, each inner array holds `{key, oper, vals}` conditions that must ALL +match. To scope a rule to one incident's blast radius, build a single AND +group from that incident's own data (`fduty incident detail `): + +1. Start the group with a severity condition: + `{"key":"alert_severity","oper":"IN","vals":[""]}`. +2. For each entry in the incident's `labels` object, add one more condition + `{"key":"labels.","oper":"IN","vals":[""]}` — but + only when the label is actually distinguishing. Drop a label if its value + is: + - purely numeric (any kind of ID — `instance_id`, `pod_id`, …), + - longer than 256 characters (embedded JSON, stack traces, long text), + - a date/time value (`2026-07-01T10:00:00Z`, unix timestamps, …), or + - under a generically noisy key regardless of value — e.g. `trigger_value`, + `prom_ql`, `detail_url`, any `*_url` key, `first_trigger_time`, other + `*timestamp*` keys, `rule_config`. +3. `oper` is `IN` (value must match one of `vals`) or `NOTIN` (must not match + any); `vals` entries also accept `/regex/` patterns. The valid `key` set is + any `labels.` for a custom label, or one of the fixed built-in names: + `severity`, `event_severity`, `alert_severity`, `status`, `title`, + `title_rule`, `description`, `alert_key`, `data_source_id`, + `integration_id`. A `key` outside this set (e.g. `dedup_key`) is not + rejected at create time — it silently produces a rule that never matches + anything, so check the `key` against this list before creating. +4. After creating the rule, confirm it with the matching `*-rule-list` + command and read back its `filters` to make sure the conditions + round-tripped as intended. + ## Gotchas - **Positional trap**: `channel-id` is **positional** on `info`, `infos`, `update`, `delete`, `disable`, `enable`, `escalate-rule-list`, `inhibit-rule-create`, `inhibit-rule-list`, `silence-rule-create`, `silence-rule-list`, `unsubscribe-rule-create`, `unsubscribe-rule-list`. It is a **flag** (`--channel-id`) on all `escalate-rule-*`, `inhibit-rule-update/delete/enable/disable`, `silence-rule-update/delete/enable/disable`, `unsubscribe-rule-update/delete/enable/disable`. When in doubt, the fence heading `### verb ` = positional; heading without `<…>` = flag.