@intentius/chant-lexicon-prometheus 0.98.0 → 0.99.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/dist/import/embedded.d.ts +14 -0
  3. package/dist/import/embedded.d.ts.map +1 -0
  4. package/dist/import/generator.d.ts +15 -0
  5. package/dist/import/generator.d.ts.map +1 -1
  6. package/dist/import/parser.d.ts.map +1 -1
  7. package/dist/integrations.d.ts +531 -0
  8. package/dist/integrations.d.ts.map +1 -0
  9. package/dist/integrity.json +4 -4
  10. package/dist/lint/rules/literal-credential.d.ts.map +1 -1
  11. package/dist/manifest.json +1 -1
  12. package/dist/model.d.ts +58 -123
  13. package/dist/model.d.ts.map +1 -1
  14. package/dist/plugin.d.ts.map +1 -1
  15. package/dist/rules/literal-credential.ts +41 -10
  16. package/dist/skills/chant-prometheus-alertmanager.md +1 -1
  17. package/package.json +2 -2
  18. package/src/import/embedded.test.ts +56 -0
  19. package/src/import/embedded.ts +44 -0
  20. package/src/import/generated-types.e2e.test.ts +3 -1
  21. package/src/import/generator.test.ts +21 -4
  22. package/src/import/generator.ts +32 -24
  23. package/src/import/parser.test.ts +13 -0
  24. package/src/import/parser.ts +18 -1
  25. package/src/import/roundtrip.test.ts +46 -9
  26. package/src/import/testdata/alertmanager-full.yml +3 -3
  27. package/src/import/testdata/alertmanager-integrations.yml +215 -0
  28. package/src/import/testdata/fixtures.ts +8 -0
  29. package/src/import/testdata/upstream/alertmanager-conf-good.yml +120 -0
  30. package/src/import/testdata/upstream/alertmanager-mattermost-default-webhook-url-file.yml +16 -0
  31. package/src/import/testdata/upstream/alertmanager-opsgenie-default-apikey-file.yml +27 -0
  32. package/src/import/testdata/upstream/alertmanager-rocketchat-default-token-file.yml +25 -0
  33. package/src/import/testdata/upstream/alertmanager-sns-topic-arn.yml +17 -0
  34. package/src/import/testdata/upstream/alertmanager-telegram-default-bot-token-file.yml +16 -0
  35. package/src/import/testdata/upstream/alertmanager-victorops-default-apikey-file.yml +23 -0
  36. package/src/import/testdata/upstream/alertmanager-wechat-default-api-secret-file.yml +18 -0
  37. package/src/integrations.ts +538 -0
  38. package/src/lint/rules/literal-credential.ts +41 -10
  39. package/src/lint/rules/rules.test.ts +31 -1
  40. package/src/model.ts +136 -110
  41. package/src/plugin.ts +5 -0
  42. package/src/skills/chant-prometheus-alertmanager.md +1 -1
@@ -7,20 +7,47 @@ import { calleeName, constInitializers, CREDENTIAL_CLASS, literalText, position,
7
7
  * reads it from a mounted file instead.
8
8
  */
9
9
  export const SECRET_FIELDS = new Set([
10
- "api_url", // slack_configs: the incoming-webhook URL is the credential
11
- "slack_api_url",
12
- "routing_key",
10
+ "api_url", // slack_configs only: the incoming-webhook URL is the credential (see SECRET_ONLY_IN)
11
+ "webhook_url", // discord, msteams, msteamsv2 and mattermost
12
+ "app_token",
13
+ "routing_key", // pagerduty (victorops' routing_key names a route, see SECRET_ONLY_IN)
13
14
  "service_key",
15
+ "api_key",
16
+ "api_secret",
17
+ "bot_token",
18
+ "token",
19
+ "token_id",
20
+ "user_key",
21
+ "alert_source_token",
14
22
  "auth_password",
15
23
  "auth_secret",
16
24
  "smtp_auth_password",
17
25
  "smtp_auth_secret",
26
+ "slack_api_url",
27
+ "slack_app_token",
28
+ "opsgenie_api_key",
29
+ "wechat_api_secret",
30
+ "victorops_api_key",
31
+ "telegram_bot_token",
32
+ "rocketchat_token",
33
+ "rocketchat_token_id",
34
+ "mattermost_webhook_url",
18
35
  "password",
19
36
  "credentials",
20
37
  "bearer_token",
21
38
  "client_secret",
22
39
  ]);
23
40
 
41
+ /**
42
+ * Fields that are a secret in one integration and plain elsewhere: `api_url`
43
+ * is Slack's webhook credential but only an endpoint for the others, and a
44
+ * VictorOps `routing_key` names a route.
45
+ */
46
+ const SECRET_ONLY_IN: Record<string, string> = {
47
+ api_url: "slack_configs",
48
+ routing_key: "pagerduty_configs",
49
+ };
50
+
24
51
  /**
25
52
  * PROM001: a credential written as a literal in a `Receiver` or
26
53
  * `AlertmanagerSettings`.
@@ -46,16 +73,20 @@ export const literalCredentialRule: LintRule = {
46
73
 
47
74
  // A value is followed to the const it names, so a list lifted out of the
48
75
  // constructor (`email_configs: heartbeatEmail`) is scanned where it is used.
49
- const scanValue = (key: string | undefined, value: ts.Expression) => {
76
+ // `integration` is the `*_configs` list a value sits in, when that is known.
77
+ const scanValue = (key: string | undefined, value: ts.Expression, integration?: string) => {
50
78
  const init = resolveConst(value, consts);
79
+ const within = key?.endsWith("_configs") ? key : integration;
51
80
  if (ts.isObjectLiteralExpression(init) || ts.isArrayLiteralExpression(init)) {
52
81
  if (scanned.has(init)) return;
53
82
  scanned.add(init);
54
- if (ts.isObjectLiteralExpression(init)) scan(init);
55
- else for (const el of init.elements) scanValue(undefined, el);
83
+ if (ts.isObjectLiteralExpression(init)) scan(init, within);
84
+ else for (const el of init.elements) scanValue(undefined, el, within);
56
85
  return;
57
86
  }
58
87
  if (!key || !SECRET_FIELDS.has(key)) return;
88
+ const only = SECRET_ONLY_IN[key];
89
+ if (only && integration !== undefined && integration !== only) return;
59
90
  const text = literalText(init);
60
91
  if (text === undefined || text === "") return;
61
92
  diagnostics.push({
@@ -67,11 +98,11 @@ export const literalCredentialRule: LintRule = {
67
98
  });
68
99
  };
69
100
 
70
- const scan = (obj: ts.ObjectLiteralExpression) => {
101
+ const scan = (obj: ts.ObjectLiteralExpression, integration?: string) => {
71
102
  for (const prop of obj.properties) {
72
- if (ts.isPropertyAssignment(prop)) scanValue(propertyName(prop), prop.initializer);
73
- else if (ts.isShorthandPropertyAssignment(prop)) scanValue(prop.name.text, prop.name);
74
- else if (ts.isSpreadAssignment(prop)) scanValue(undefined, prop.expression);
103
+ if (ts.isPropertyAssignment(prop)) scanValue(propertyName(prop), prop.initializer, integration);
104
+ else if (ts.isShorthandPropertyAssignment(prop)) scanValue(prop.name.text, prop.name, integration);
105
+ else if (ts.isSpreadAssignment(prop)) scanValue(undefined, prop.expression, integration);
75
106
  }
76
107
  };
77
108
 
@@ -58,7 +58,37 @@ describe("PROM001 literal credential", () => {
58
58
  const pager = new Receiver({ name: "pager", pagerduty_configs: safe });
59
59
  `),
60
60
  );
61
- expect(diags.map((d) => d.message.split("`")[1])).toEqual(["auth_password", "service_key", "smtp_auth_password"]);
61
+ expect(diags.map((d) => d.message.split("`")[1])).toEqual(["auth_password", "api_key", "service_key", "smtp_auth_password"]);
62
+ });
63
+
64
+ test("covers every integration's credential, and api_url and routing_key only where they are one", () => {
65
+ const diags = literalCredentialRule.check(
66
+ ctx(`
67
+ const chatSlack: SlackConfig[] = [{ api_url: "https://hooks.slack.com/services/T/B/X", app_token: "xoxb-1" }];
68
+ new Receiver({
69
+ name: "chat",
70
+ slack_configs: chatSlack,
71
+ discord_configs: [{ webhook_url: "https://discord.com/api/webhooks/1/x" }],
72
+ telegram_configs: [{ bot_token: "123:abc", chat_id: 1 }],
73
+ webex_configs: [{ api_url: "https://webexapis.com/v1/messages", room_id: "r" }],
74
+ victorops_configs: [{ api_key: "k", routing_key: "ops" }],
75
+ pushover_configs: [{ user_key: "u", token: "t" }],
76
+ rocketchat_configs: [{ token_id: "i", token_file: "/etc/rc" }],
77
+ });
78
+ new AlertmanagerSettings({ global: { telegram_bot_token: "123:abc", webex_api_url: "https://webexapis.com/v1/messages" } });
79
+ `),
80
+ );
81
+ expect(diags.map((d) => d.message.split("`")[1])).toEqual([
82
+ "api_url",
83
+ "app_token",
84
+ "webhook_url",
85
+ "bot_token",
86
+ "api_key",
87
+ "user_key",
88
+ "token",
89
+ "token_id",
90
+ "telegram_bot_token",
91
+ ]);
62
92
  });
63
93
  });
64
94
 
package/src/model.ts CHANGED
@@ -13,6 +13,31 @@
13
13
  * docs apply word for word.
14
14
  */
15
15
 
16
+ import {
17
+ RECEIVER_INTEGRATION_TYPES,
18
+ type DiscordConfig,
19
+ type EmailConfig,
20
+ type HttpClientConfig,
21
+ type IncidentioConfig,
22
+ type JiraConfig,
23
+ type MattermostConfig,
24
+ type MSTeamsConfig,
25
+ type MSTeamsV2Config,
26
+ type OpsGenieConfig,
27
+ type PagerDutyConfig,
28
+ type PushoverConfig,
29
+ type ReceiverIntegration,
30
+ type RocketchatConfig,
31
+ type SlackConfig,
32
+ type SNSConfig,
33
+ type TelegramConfig,
34
+ type TlsConfig,
35
+ type VictorOpsConfig,
36
+ type WebexConfig,
37
+ type WebhookConfig,
38
+ type WechatConfig,
39
+ } from "./integrations";
40
+
16
41
  /** Labels or annotations: string keys to string values. */
17
42
  export type LabelSet = Record<string, string>;
18
43
 
@@ -78,114 +103,31 @@ export function ruleName(rule: RuleConfig): string {
78
103
 
79
104
  // ── Alertmanager ────────────────────────────────────────────────────
80
105
 
81
- /** HTTP client settings shared by the receivers that make HTTP calls. */
82
- export interface HttpClientConfig {
83
- basic_auth?: { username?: string; username_file?: string; password?: string; password_file?: string };
84
- authorization?: { type?: string; credentials?: string; credentials_file?: string };
85
- bearer_token?: string;
86
- bearer_token_file?: string;
87
- oauth2?: Record<string, unknown>;
88
- proxy_url?: string;
89
- follow_redirects?: boolean;
90
- enable_http2?: boolean;
91
- tls_config?: {
92
- ca_file?: string;
93
- cert_file?: string;
94
- key_file?: string;
95
- server_name?: string;
96
- insecure_skip_verify?: boolean;
97
- min_version?: string;
98
- };
99
- }
100
-
101
- interface NotifierBase {
102
- /** Whether to notify about resolved alerts too. */
103
- send_resolved?: boolean;
104
- }
105
-
106
- export interface WebhookConfig extends NotifierBase {
107
- /** Where alerts are POSTed. Set this or `url_file`. */
108
- url?: string;
109
- url_file?: string;
110
- http_config?: HttpClientConfig;
111
- /** Most alerts per message; 0 sends them all. */
112
- max_alerts?: number;
113
- timeout?: string;
114
- }
115
-
116
- export interface EmailConfig extends NotifierBase {
117
- to: string;
118
- from?: string;
119
- /** `host:port` of the SMTP server. Falls back to `global.smtp_smarthost`. */
120
- smarthost?: string;
121
- hello?: string;
122
- auth_username?: string;
123
- auth_password?: string;
124
- auth_password_file?: string;
125
- auth_secret?: string;
126
- auth_secret_file?: string;
127
- auth_identity?: string;
128
- require_tls?: boolean;
129
- tls_config?: HttpClientConfig["tls_config"];
130
- html?: string;
131
- text?: string;
132
- headers?: Record<string, string>;
133
- }
134
-
135
- export interface SlackConfig extends NotifierBase {
136
- /** The incoming-webhook URL. It is a credential: prefer `api_url_file`. */
137
- api_url?: string;
138
- api_url_file?: string;
139
- channel?: string;
140
- username?: string;
141
- color?: string;
142
- title?: string;
143
- title_link?: string;
144
- pretext?: string;
145
- text?: string;
146
- footer?: string;
147
- fallback?: string;
148
- icon_emoji?: string;
149
- icon_url?: string;
150
- link_names?: boolean;
151
- short_fields?: boolean;
152
- mrkdwn_in?: string[];
153
- fields?: Array<{ title: string; value: string; short?: boolean }>;
154
- actions?: Array<Record<string, unknown>>;
155
- http_config?: HttpClientConfig;
156
- }
157
-
158
- export interface PagerDutyConfig extends NotifierBase {
159
- /** Events API v2 integration key. A credential: prefer `routing_key_file`. */
160
- routing_key?: string;
161
- routing_key_file?: string;
162
- /** Events API v1 integration key. A credential: prefer `service_key_file`. */
163
- service_key?: string;
164
- service_key_file?: string;
165
- url?: string;
166
- client?: string;
167
- client_url?: string;
168
- description?: string;
169
- severity?: string;
170
- class?: string;
171
- component?: string;
172
- group?: string;
173
- source?: string;
174
- details?: Record<string, string>;
175
- links?: Array<{ href: string; text?: string }>;
176
- images?: Array<{ src: string; alt?: string; href?: string }>;
177
- http_config?: HttpClientConfig;
178
- }
106
+ export * from "./integrations";
179
107
 
180
108
  /** One entry under `receivers:`. */
181
109
  export interface ReceiverConfig {
182
110
  name: string;
183
111
  /** Labels on the receiver, exposed to notification templates. */
184
112
  labels?: LabelSet;
185
- webhook_configs?: WebhookConfig[];
113
+ discord_configs?: DiscordConfig[];
186
114
  email_configs?: EmailConfig[];
187
- slack_configs?: SlackConfig[];
115
+ incidentio_configs?: IncidentioConfig[];
188
116
  pagerduty_configs?: PagerDutyConfig[];
117
+ slack_configs?: SlackConfig[];
118
+ webhook_configs?: WebhookConfig[];
119
+ opsgenie_configs?: OpsGenieConfig[];
120
+ wechat_configs?: WechatConfig[];
121
+ pushover_configs?: PushoverConfig[];
122
+ victorops_configs?: VictorOpsConfig[];
123
+ sns_configs?: SNSConfig[];
124
+ telegram_configs?: TelegramConfig[];
125
+ webex_configs?: WebexConfig[];
126
+ msteams_configs?: MSTeamsConfig[];
127
+ msteamsv2_configs?: MSTeamsV2Config[];
128
+ jira_configs?: JiraConfig[];
129
+ rocketchat_configs?: RocketchatConfig[];
130
+ mattermost_configs?: MattermostConfig[];
189
131
  }
190
132
 
191
133
  /** A route in the routing tree. The top-level `route:` is the root. */
@@ -244,12 +186,18 @@ export interface TimeIntervalConfig {
244
186
  time_intervals: TimePeriodConfig[];
245
187
  }
246
188
 
247
- /** `global:` settings. */
189
+ /**
190
+ * `global:` settings: defaults the receivers fall back to. Credentials with
191
+ * a `*_file` sibling are best set through it; PROM001 flags a literal.
192
+ */
248
193
  export interface AlertmanagerGlobalConfig {
194
+ /** How long an alert that stops being updated stays firing. */
249
195
  resolve_timeout?: string;
196
+ http_config?: HttpClientConfig;
197
+ jira_api_url?: string;
250
198
  smtp_from?: string;
251
- smtp_smarthost?: string;
252
199
  smtp_hello?: string;
200
+ smtp_smarthost?: string;
253
201
  smtp_auth_username?: string;
254
202
  smtp_auth_password?: string;
255
203
  smtp_auth_password_file?: string;
@@ -257,12 +205,83 @@ export interface AlertmanagerGlobalConfig {
257
205
  smtp_auth_secret_file?: string;
258
206
  smtp_auth_identity?: string;
259
207
  smtp_require_tls?: boolean;
208
+ smtp_tls_config?: TlsConfig;
209
+ smtp_force_implicit_tls?: boolean;
260
210
  slack_api_url?: string;
261
211
  slack_api_url_file?: string;
212
+ slack_app_token?: string;
213
+ slack_app_token_file?: string;
214
+ slack_app_url?: string;
262
215
  pagerduty_url?: string;
263
- http_config?: HttpClientConfig;
216
+ opsgenie_api_url?: string;
217
+ opsgenie_api_key?: string;
218
+ opsgenie_api_key_file?: string;
219
+ wechat_api_url?: string;
220
+ wechat_api_secret?: string;
221
+ wechat_api_secret_file?: string;
222
+ wechat_api_corp_id?: string;
223
+ victorops_api_url?: string;
224
+ victorops_api_key?: string;
225
+ victorops_api_key_file?: string;
226
+ telegram_api_url?: string;
227
+ telegram_bot_token?: string;
228
+ telegram_bot_token_file?: string;
229
+ webex_api_url?: string;
230
+ rocketchat_api_url?: string;
231
+ rocketchat_token?: string;
232
+ rocketchat_token_file?: string;
233
+ rocketchat_token_id?: string;
234
+ rocketchat_token_id_file?: string;
235
+ mattermost_webhook_url?: string;
236
+ mattermost_webhook_url_file?: string;
264
237
  }
265
238
 
239
+ /** Every `global:` field, as a list, for code that reads a parsed file. */
240
+ export const ALERTMANAGER_GLOBAL_FIELDS = Object.freeze(Object.keys({
241
+ resolve_timeout: true,
242
+ http_config: true,
243
+ jira_api_url: true,
244
+ smtp_from: true,
245
+ smtp_hello: true,
246
+ smtp_smarthost: true,
247
+ smtp_auth_username: true,
248
+ smtp_auth_password: true,
249
+ smtp_auth_password_file: true,
250
+ smtp_auth_secret: true,
251
+ smtp_auth_secret_file: true,
252
+ smtp_auth_identity: true,
253
+ smtp_require_tls: true,
254
+ smtp_tls_config: true,
255
+ smtp_force_implicit_tls: true,
256
+ slack_api_url: true,
257
+ slack_api_url_file: true,
258
+ slack_app_token: true,
259
+ slack_app_token_file: true,
260
+ slack_app_url: true,
261
+ pagerduty_url: true,
262
+ opsgenie_api_url: true,
263
+ opsgenie_api_key: true,
264
+ opsgenie_api_key_file: true,
265
+ wechat_api_url: true,
266
+ wechat_api_secret: true,
267
+ wechat_api_secret_file: true,
268
+ wechat_api_corp_id: true,
269
+ victorops_api_url: true,
270
+ victorops_api_key: true,
271
+ victorops_api_key_file: true,
272
+ telegram_api_url: true,
273
+ telegram_bot_token: true,
274
+ telegram_bot_token_file: true,
275
+ webex_api_url: true,
276
+ rocketchat_api_url: true,
277
+ rocketchat_token: true,
278
+ rocketchat_token_file: true,
279
+ rocketchat_token_id: true,
280
+ rocketchat_token_id_file: true,
281
+ mattermost_webhook_url: true,
282
+ mattermost_webhook_url_file: true,
283
+ } satisfies Record<keyof AlertmanagerGlobalConfig, true>)) as readonly (keyof AlertmanagerGlobalConfig)[];
284
+
266
285
  /** `tracing:` settings: where Alertmanager sends its own traces. */
267
286
  export interface AlertmanagerTracingConfig {
268
287
  client_type?: "grpc" | "http";
@@ -272,7 +291,7 @@ export interface AlertmanagerTracingConfig {
272
291
  headers?: Record<string, string>;
273
292
  compression?: string;
274
293
  timeout?: string;
275
- tls_config?: HttpClientConfig["tls_config"];
294
+ tls_config?: TlsConfig;
276
295
  }
277
296
 
278
297
  /** A whole `alertmanager.yml`, the file `amtool check-config` reads. */
@@ -286,17 +305,24 @@ export interface AlertmanagerConfig {
286
305
  tracing?: AlertmanagerTracingConfig;
287
306
  }
288
307
 
289
- /** The four receiver integrations this lexicon types. */
290
- export const RECEIVER_INTEGRATIONS = ["webhook_configs", "email_configs", "slack_configs", "pagerduty_configs"] as const;
308
+ /** The receiver integrations, every one Alertmanager defines. */
309
+ export const RECEIVER_INTEGRATIONS = Object.keys(RECEIVER_INTEGRATION_TYPES) as readonly ReceiverIntegration[];
291
310
 
292
311
  /** True when a parsed document has the shape of a Prometheus rule file. */
293
312
  export function looksLikeRuleFile(value: unknown): value is RuleFileConfig {
294
313
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
295
- const groups = (value as Record<string, unknown>).groups;
314
+ const v = value as Record<string, unknown>;
315
+ const groups = v.groups;
296
316
  if (!Array.isArray(groups)) return false;
297
- return groups.every(
298
- (g) => typeof g === "object" && g !== null && typeof (g as Record<string, unknown>).name === "string" && Array.isArray((g as Record<string, unknown>).rules),
299
- );
317
+ // Grafana's alerting provisioning files also hold `groups:` of named rule lists. Prometheus reads rule
318
+ // files strictly (unknown keys are errors), so `apiVersion`, a group `folder` or a rule's `data` means Grafana.
319
+ if ("apiVersion" in v) return false;
320
+ return groups.every((g) => {
321
+ if (typeof g !== "object" || g === null) return false;
322
+ const group = g as Record<string, unknown>;
323
+ if (typeof group.name !== "string" || !Array.isArray(group.rules) || "folder" in group) return false;
324
+ return !group.rules.some((r) => typeof r === "object" && r !== null && "data" in r);
325
+ });
300
326
  }
301
327
 
302
328
  /** True when a parsed document has the shape of an `alertmanager.yml`. */
package/src/plugin.ts CHANGED
@@ -11,6 +11,7 @@ import { hover } from "./lsp/hover";
11
11
  import { detectTemplate } from "./detect";
12
12
  import { PrometheusParser } from "./import/parser";
13
13
  import { PrometheusGenerator } from "./import/generator";
14
+ import { ruleGroupsImporter } from "./import/embedded";
14
15
  import { initTemplates } from "./init-templates";
15
16
  import { prometheusSkills } from "./skill-defs";
16
17
  import { CATALOG } from "./catalog";
@@ -120,6 +121,10 @@ export const prometheusPlugin: LexiconPlugin = {
120
121
  return new PrometheusGenerator();
121
122
  },
122
123
 
124
+ embeddedImporters() {
125
+ return [ruleGroupsImporter];
126
+ },
127
+
123
128
  initTemplates(template?: string) {
124
129
  return initTemplates(template);
125
130
  },
@@ -49,7 +49,7 @@ export const pageMutesTicket = new InhibitRule({
49
49
 
50
50
  ## Starting from an existing alertmanager.yml
51
51
 
52
- Import it rather than retyping it: `chant import alertmanager.yml --output src` (with `--lexicon prometheus` outside a chant project). The importer writes `receivers.ts`, `time-intervals.ts`, `routes.ts` (the root `Route`, children as `RouteProps` that reference receivers and time intervals by variable), `inhibit-rules.ts` and `settings.ts`. `*_file` paths and Go templates are kept as written; a literal credential is imported as found and PROM001 reports it, so offer to move it to the `*_file` field. Integrations the lexicon doesn't type (`opsgenie_configs`, `msteams_configs`, ...) are spread in from an untyped const. `match`/`match_re` and the top-level `mute_time_intervals` come back in their current spelling (`matchers`, `time_intervals`), and `chant import` prints a warning for each.
52
+ Import it rather than retyping it: `chant import alertmanager.yml --output src` (with `--lexicon prometheus` outside a chant project). The importer writes `receivers.ts`, `time-intervals.ts`, `routes.ts` (the root `Route`, children as `RouteProps` that reference receivers and time intervals by variable), `inhibit-rules.ts` and `settings.ts`. `*_file` paths and Go templates are kept as written; a literal credential is imported as found and PROM001 reports it, so offer to move it to the `*_file` field. Every integration comes back typed (`OpsGenieConfig[]`, `MSTeamsV2Config[]`, ...); a receiver or `global` key Alertmanager doesn't define is spread in from an untyped const, with a warning. `match`/`match_re` and the top-level `mute_time_intervals` come back in their current spelling (`matchers`, `time_intervals`), and `chant import` prints a warning for each.
53
53
 
54
54
  ## What the checks hold you to
55
55