@intentius/chant-lexicon-prometheus 0.102.0 → 0.104.1

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 (124) hide show
  1. package/README.md +3 -1
  2. package/dist/collector-metrics.d.ts +58 -0
  3. package/dist/collector-metrics.d.ts.map +1 -0
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/integrity.json +19 -4
  7. package/dist/lint/audit-catalog.d.ts +2 -0
  8. package/dist/lint/audit-catalog.d.ts.map +1 -1
  9. package/dist/lint/audit-lineage.d.ts +21 -0
  10. package/dist/lint/audit-lineage.d.ts.map +1 -1
  11. package/dist/lint/post-synth/index.d.ts.map +1 -1
  12. package/dist/lint/post-synth/prom-helpers.d.ts +9 -2
  13. package/dist/lint/post-synth/prom-helpers.d.ts.map +1 -1
  14. package/dist/lint/post-synth/prom211.d.ts +12 -0
  15. package/dist/lint/post-synth/prom211.d.ts.map +1 -0
  16. package/dist/lint/post-synth/prom212.d.ts +9 -0
  17. package/dist/lint/post-synth/prom212.d.ts.map +1 -0
  18. package/dist/lint/post-synth/prom213.d.ts +11 -0
  19. package/dist/lint/post-synth/prom213.d.ts.map +1 -0
  20. package/dist/lint/post-synth/prom214.d.ts +9 -0
  21. package/dist/lint/post-synth/prom214.d.ts.map +1 -0
  22. package/dist/lint/post-synth/prom215.d.ts +9 -0
  23. package/dist/lint/post-synth/prom215.d.ts.map +1 -0
  24. package/dist/lint/post-synth/prom216.d.ts +10 -0
  25. package/dist/lint/post-synth/prom216.d.ts.map +1 -0
  26. package/dist/lint/post-synth/prom217.d.ts +9 -0
  27. package/dist/lint/post-synth/prom217.d.ts.map +1 -0
  28. package/dist/lint/post-synth/prom218.d.ts +10 -0
  29. package/dist/lint/post-synth/prom218.d.ts.map +1 -0
  30. package/dist/lint/post-synth/prom219.d.ts +9 -0
  31. package/dist/lint/post-synth/prom219.d.ts.map +1 -0
  32. package/dist/lint/post-synth/prom220.d.ts +9 -0
  33. package/dist/lint/post-synth/prom220.d.ts.map +1 -0
  34. package/dist/lint/post-synth/prom221.d.ts +10 -0
  35. package/dist/lint/post-synth/prom221.d.ts.map +1 -0
  36. package/dist/lint/post-synth/prom222.d.ts +9 -0
  37. package/dist/lint/post-synth/prom222.d.ts.map +1 -0
  38. package/dist/lint/post-synth/prom223.d.ts +10 -0
  39. package/dist/lint/post-synth/prom223.d.ts.map +1 -0
  40. package/dist/lint/post-synth/prom224.d.ts +9 -0
  41. package/dist/lint/post-synth/prom224.d.ts.map +1 -0
  42. package/dist/lint/post-synth/prom301.d.ts +14 -0
  43. package/dist/lint/post-synth/prom301.d.ts.map +1 -0
  44. package/dist/lint/rules/literal-credential.d.ts.map +1 -1
  45. package/dist/manifest.json +1 -1
  46. package/dist/okf/index.md +15 -0
  47. package/dist/okf/rules/PROM211.md +11 -0
  48. package/dist/okf/rules/PROM212.md +11 -0
  49. package/dist/okf/rules/PROM213.md +11 -0
  50. package/dist/okf/rules/PROM214.md +11 -0
  51. package/dist/okf/rules/PROM215.md +11 -0
  52. package/dist/okf/rules/PROM216.md +11 -0
  53. package/dist/okf/rules/PROM217.md +11 -0
  54. package/dist/okf/rules/PROM218.md +11 -0
  55. package/dist/okf/rules/PROM219.md +11 -0
  56. package/dist/okf/rules/PROM220.md +11 -0
  57. package/dist/okf/rules/PROM221.md +11 -0
  58. package/dist/okf/rules/PROM222.md +11 -0
  59. package/dist/okf/rules/PROM223.md +11 -0
  60. package/dist/okf/rules/PROM224.md +11 -0
  61. package/dist/okf/rules/PROM301.md +11 -0
  62. package/dist/plugin.d.ts.map +1 -1
  63. package/dist/promql-analysis.d.ts +82 -0
  64. package/dist/promql-analysis.d.ts.map +1 -0
  65. package/dist/rules/literal-credential.ts +4 -0
  66. package/dist/rules/prom-helpers.ts +41 -2
  67. package/dist/rules/prom211.ts +21 -0
  68. package/dist/rules/prom212.ts +18 -0
  69. package/dist/rules/prom213.ts +20 -0
  70. package/dist/rules/prom214.ts +18 -0
  71. package/dist/rules/prom215.ts +18 -0
  72. package/dist/rules/prom216.ts +19 -0
  73. package/dist/rules/prom217.ts +18 -0
  74. package/dist/rules/prom218.ts +19 -0
  75. package/dist/rules/prom219.ts +18 -0
  76. package/dist/rules/prom220.ts +18 -0
  77. package/dist/rules/prom221.ts +19 -0
  78. package/dist/rules/prom222.ts +18 -0
  79. package/dist/rules/prom223.ts +19 -0
  80. package/dist/rules/prom224.ts +18 -0
  81. package/dist/rules/prom301.ts +23 -0
  82. package/dist/validate-config.d.ts +9 -3
  83. package/dist/validate-config.d.ts.map +1 -1
  84. package/dist/validate-integrations.d.ts +1 -0
  85. package/dist/validate-integrations.d.ts.map +1 -1
  86. package/dist/validate-security.d.ts +13 -0
  87. package/dist/validate-security.d.ts.map +1 -0
  88. package/package.json +2 -2
  89. package/src/collector-metrics.ts +262 -0
  90. package/src/composites/composites.test.ts +2 -5
  91. package/src/import/testdata/alertmanager-integrations.yml +5 -1
  92. package/src/index.ts +1 -0
  93. package/src/init-templates.test.ts +3 -1
  94. package/src/lint/audit-catalog.ts +102 -0
  95. package/src/lint/audit-lineage.test.ts +74 -0
  96. package/src/lint/audit-lineage.ts +28 -0
  97. package/src/lint/post-synth/checks.test.ts +257 -0
  98. package/src/lint/post-synth/index.ts +30 -0
  99. package/src/lint/post-synth/post-synth.test.ts +3 -1
  100. package/src/lint/post-synth/prom-helpers.ts +41 -2
  101. package/src/lint/post-synth/prom211.ts +21 -0
  102. package/src/lint/post-synth/prom212.ts +18 -0
  103. package/src/lint/post-synth/prom213.ts +20 -0
  104. package/src/lint/post-synth/prom214.ts +18 -0
  105. package/src/lint/post-synth/prom215.ts +18 -0
  106. package/src/lint/post-synth/prom216.ts +19 -0
  107. package/src/lint/post-synth/prom217.ts +18 -0
  108. package/src/lint/post-synth/prom218.ts +19 -0
  109. package/src/lint/post-synth/prom219.ts +18 -0
  110. package/src/lint/post-synth/prom220.ts +18 -0
  111. package/src/lint/post-synth/prom221.ts +19 -0
  112. package/src/lint/post-synth/prom222.ts +18 -0
  113. package/src/lint/post-synth/prom223.ts +19 -0
  114. package/src/lint/post-synth/prom224.ts +18 -0
  115. package/src/lint/post-synth/prom301.test.ts +157 -0
  116. package/src/lint/post-synth/prom301.ts +23 -0
  117. package/src/lint/rules/literal-credential.ts +4 -0
  118. package/src/lint/rules/rules.test.ts +16 -0
  119. package/src/plugin.test.ts +22 -0
  120. package/src/plugin.ts +12 -1
  121. package/src/promql-analysis.ts +365 -0
  122. package/src/validate-config.ts +274 -5
  123. package/src/validate-integrations.ts +1 -1
  124. package/src/validate-security.ts +143 -0
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: post-synth-check
3
+ title: PROM219
4
+ description: An alerting rule sets the alertname label
5
+ id: PROM219
6
+ severity: error
7
+ category: post-synth
8
+ lexicon: prometheus
9
+ docs: https://intentius.io/chant/lexicons/prometheus/rules/
10
+ ---
11
+ An alerting rule sets the alertname label
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: post-synth-check
3
+ title: PROM220
4
+ description: A receiver turns off TLS certificate verification
5
+ id: PROM220
6
+ severity: error
7
+ category: post-synth
8
+ lexicon: prometheus
9
+ docs: https://intentius.io/chant/lexicons/prometheus/rules/
10
+ ---
11
+ A receiver turns off TLS certificate verification
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: post-synth-check
3
+ title: PROM221
4
+ description: SMTP credentials sent with require_tls false
5
+ id: PROM221
6
+ severity: error
7
+ category: post-synth
8
+ lexicon: prometheus
9
+ docs: https://intentius.io/chant/lexicons/prometheus/rules/
10
+ ---
11
+ SMTP credentials sent with require_tls false
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: post-synth-check
3
+ title: PROM222
4
+ description: Credentials sent to an http:// receiver URL
5
+ id: PROM222
6
+ severity: error
7
+ category: post-synth
8
+ lexicon: prometheus
9
+ docs: https://intentius.io/chant/lexicons/prometheus/rules/
10
+ ---
11
+ Credentials sent to an http:// receiver URL
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: post-synth-check
3
+ title: PROM223
4
+ description: repeat_interval is shorter than group_interval
5
+ id: PROM223
6
+ severity: error
7
+ category: post-synth
8
+ lexicon: prometheus
9
+ docs: https://intentius.io/chant/lexicons/prometheus/rules/
10
+ ---
11
+ repeat_interval is shorter than group_interval
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: post-synth-check
3
+ title: PROM224
4
+ description: An inhibit rule matches one alert as source and target, with no equal
5
+ id: PROM224
6
+ severity: error
7
+ category: post-synth
8
+ lexicon: prometheus
9
+ docs: https://intentius.io/chant/lexicons/prometheus/rules/
10
+ ---
11
+ An inhibit rule matches one alert as source and target, with no equal
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: post-synth-check
3
+ title: PROM301
4
+ description: A rule reads a connector metric no collector in the build emits
5
+ id: PROM301
6
+ severity: error
7
+ category: post-synth
8
+ lexicon: prometheus
9
+ docs: https://intentius.io/chant/lexicons/prometheus/rules/
10
+ ---
11
+ A rule reads a connector metric no collector in the build emits
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AA+B9D;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,aA6G9B,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AA+B9D;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,aAwH9B,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Reading a parsed PromQL expression: the selectors it reads, the labels its
3
+ * result keeps, and whether it filters with a comparison. The rule-file
4
+ * checks PROM211-PROM219 (./validate-config.ts) are built on these.
5
+ *
6
+ * The tree is the one `checkPromql` (./promql.ts) parses with
7
+ * `@prometheus-io/lezer-promql`. Every function here returns nothing for an
8
+ * expression that does not parse; PROM104 reports that.
9
+ */
10
+ import { parser } from "@prometheus-io/lezer-promql";
11
+ import type { MatchOp } from "./matchers.js";
12
+ type SyntaxNode = ReturnType<typeof parser.parse>["topNode"];
13
+ /** The expression under the tree's `PromQL` root, or undefined when it does not parse. */
14
+ export declare function parsePromql(expr: string): SyntaxNode | undefined;
15
+ /** The value of a PromQL string literal: double- or single-quoted with escapes, or a raw backtick string. */
16
+ export declare function stringValue(literal: string): string;
17
+ export interface SelectorMatcher {
18
+ name: string;
19
+ op: MatchOp;
20
+ value: string;
21
+ }
22
+ /** One vector selector in an expression. */
23
+ export interface Selector {
24
+ /** The metric name, from the identifier or a `__name__="..."` matcher; undefined when neither names one. */
25
+ name?: string;
26
+ matchers: SelectorMatcher[];
27
+ /** The selector as written. */
28
+ text: string;
29
+ /** The functions it sits inside, innermost first, e.g. `["rate", "sum"]` for `sum(rate(x[5m]))`. */
30
+ within: string[];
31
+ }
32
+ /** Every vector selector in the expression, with the functions around it. */
33
+ export declare function selectors(expr: string): Selector[];
34
+ /**
35
+ * Whether the expression filters its result: a comparison without `bool`,
36
+ * `absent()`/`absent_over_time()`, or `unless`, anywhere a series can pass
37
+ * through to the result. An expression without one returns every series it
38
+ * reads, so an alert on it fires for all of them (pint alerts/comparison).
39
+ * `or` needs a condition on both sides, since either side alone reaches the
40
+ * result.
41
+ */
42
+ export declare function hasCondition(expr: string): boolean | undefined;
43
+ /**
44
+ * The labels an expression's result carries, as far as the expression says:
45
+ * `{ only }` when it keeps exactly these labels (`sum by (job)`), `{ except }`
46
+ * when it keeps every label but these (`sum without (instance)`), or
47
+ * undefined when it keeps whatever the series it reads carry.
48
+ */
49
+ export type LabelScope = {
50
+ only: Set<string>;
51
+ } | {
52
+ except: Set<string>;
53
+ };
54
+ /** The labels the expression's result keeps; see {@link LabelScope}. */
55
+ export declare function resultLabels(expr: string): LabelScope | undefined;
56
+ /** Whether a label survives a scope. An undefined scope keeps every label. */
57
+ export declare function keepsLabel(scope: LabelScope | undefined, label: string): boolean;
58
+ /** One `histogram_quantile` call and what is wrong with its histogram argument. */
59
+ export interface HistogramProblem {
60
+ call: string;
61
+ problem: "no-bucket" | "no-le";
62
+ }
63
+ /** `histogram_quantile` calls over a selector with no `_bucket` in its name, or an aggregation that drops `le`. */
64
+ export declare function histogramProblems(expr: string): HistogramProblem[];
65
+ /** `rate`, `irate` or `increase` read straight from a named selector whose name does not end in a counter suffix. */
66
+ export declare function nonCounterRates(expr: string): Array<{
67
+ fn: string;
68
+ name: string;
69
+ }>;
70
+ /** A regex matcher that could be an equality matcher, or that carries anchors Prometheus already adds. */
71
+ export interface RegexProblem {
72
+ matcher: string;
73
+ problem: "literal" | "anchored";
74
+ }
75
+ /** The `=~` and `!~` matchers in an expression that need no regex, or that are anchored (pint promql/regexp). */
76
+ export declare function regexProblems(expr: string): RegexProblem[];
77
+ /** Whether every selector in the expression is read through a `*_over_time` function, so the expression already spans a window. */
78
+ export declare function readsOnlyOverTime(expr: string): boolean;
79
+ /** Whether the expression is two conditions joined by `and`: the multi-window form, where the short window does what `for` would. */
80
+ export declare function isMultiWindow(expr: string): boolean;
81
+ export {};
82
+ //# sourceMappingURL=promql-analysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promql-analysis.d.ts","sourceRoot":"","sources":["../src/promql-analysis.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC;AAE7D,0FAA0F;AAC1F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAWhE;AAMD,6GAA6G;AAC7G,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQnD;AAmBD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,4CAA4C;AAC5C,MAAM,WAAW,QAAQ;IACvB,4GAA4G;IAC5G,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,oGAAoG;IACpG,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAiCD,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAelD;AAkBD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAgC9D;AAED;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC;AAuBzE,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAiEjE;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAGhF;AAED,mFAAmF;AACnF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC;CAChC;AAED,mHAAmH;AACnH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAmBlE;AAKD,qHAAqH;AACrH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAMjF;AAID,0GAA0G;AAC1G,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,SAAS,GAAG,UAAU,CAAC;CACjC;AAED,iHAAiH;AACjH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CAW1D;AAED,mIAAmI;AACnI,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED,qIAAqI;AACrI,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOnD"}
@@ -1,5 +1,6 @@
1
1
  import * as ts from "typescript";
2
2
  import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
3
+ import { SLACK_APP_URL } from "../../validate-integrations";
3
4
  import { calleeName, constInitializers, CREDENTIAL_CLASS, literalText, position, propertyName, resolveConst } from "./prom-ast";
4
5
 
5
6
  /**
@@ -89,6 +90,9 @@ export const literalCredentialRule: LintRule = {
89
90
  if (only && integration !== undefined && integration !== only) return;
90
91
  const text = literalText(init);
91
92
  if (text === undefined || text === "") return;
93
+ // A Slack bot posts to the public chat.postMessage endpoint with its token
94
+ // in http_config, and update_message needs exactly that api_url (PROM210).
95
+ if (key === "api_url" && text === SLACK_APP_URL) return;
92
96
  diagnostics.push({
93
97
  ruleId: "PROM001",
94
98
  severity: "error",
@@ -13,10 +13,12 @@
13
13
  import type { PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
14
14
  import type { SerializerResult } from "@intentius/chant/serializer";
15
15
  import { loadAll } from "js-yaml";
16
+ import { buildCollectorConfigs, collectorMetricIssues, collectorMetrics } from "../../collector-metrics";
16
17
  import { looksLikeAlertmanagerConfig, looksLikeRuleFile, type AlertmanagerConfig, type RuleFileConfig } from "../../model";
17
18
  import {
18
19
  validateAlertmanagerConfig,
19
20
  validateRuleFile,
21
+ validateRunbookUrls,
20
22
  validateSeverityRouting,
21
23
  type PrometheusIssue,
22
24
  type PrometheusIssueCode,
@@ -62,7 +64,7 @@ function toDiagnostic(issue: PrometheusIssue, source: string): PostSynthDiagnost
62
64
  };
63
65
  }
64
66
 
65
- /** Diagnostics for one rule-file code (PROM101-PROM107) across every rule file in the output. */
67
+ /** Diagnostics for one rule-file code (PROM101-PROM107, PROM211, PROM213-PROM219) across every rule file in the output. */
66
68
  export function ruleFileDiagnostics(ctx: PostSynthContext, code: PrometheusIssueCode): PostSynthDiagnostic[] {
67
69
  return prometheusDocs(ctx).ruleFiles.flatMap(({ source, config }) =>
68
70
  validateRuleFile(config)
@@ -71,7 +73,7 @@ export function ruleFileDiagnostics(ctx: PostSynthContext, code: PrometheusIssue
71
73
  );
72
74
  }
73
75
 
74
- /** Diagnostics for one Alertmanager code (PROM201, PROM203-PROM210) across every `alertmanager.yml` in the output. */
76
+ /** Diagnostics for one Alertmanager code (PROM201, PROM203-PROM210, PROM220-PROM224) across every `alertmanager.yml` in the output. */
75
77
  export function alertmanagerDiagnostics(ctx: PostSynthContext, code: PrometheusIssueCode): PostSynthDiagnostic[] {
76
78
  return prometheusDocs(ctx).alertmanager.flatMap(({ source, config }) =>
77
79
  validateAlertmanagerConfig(config)
@@ -80,6 +82,43 @@ export function alertmanagerDiagnostics(ctx: PostSynthContext, code: PrometheusI
80
82
  );
81
83
  }
82
84
 
85
+ /** PROM212 across every rule file in the output. Opt-in: the lexicon's `recommended` lint preset leaves it out (see ../../plugin.ts). */
86
+ export function runbookDiagnostics(ctx: PostSynthContext): PostSynthDiagnostic[] {
87
+ return prometheusDocs(ctx).ruleFiles.flatMap(({ source, config }) => validateRunbookUrls(config).map((i) => toDiagnostic(i, source)));
88
+ }
89
+
90
+ /**
91
+ * PROM301: every rule expression in the output against the metrics the
92
+ * build's collector configs emit. Silent when the build has none.
93
+ */
94
+ export function collectorMetricDiagnostics(ctx: PostSynthContext): PostSynthDiagnostic[] {
95
+ const { ruleFiles } = prometheusDocs(ctx);
96
+ if (ruleFiles.length === 0) return [];
97
+ const emitted = collectorMetrics(buildCollectorConfigs(ctx));
98
+ if (!emitted) return [];
99
+ const out: PostSynthDiagnostic[] = [];
100
+ for (const { source, config } of ruleFiles) {
101
+ for (const group of Array.isArray(config?.groups) ? config.groups : []) {
102
+ for (const rule of Array.isArray(group?.rules) ? group.rules : []) {
103
+ const r = rule as unknown as Record<string, unknown>;
104
+ if (typeof r.expr !== "string") continue;
105
+ const subject = `${group.name}/${typeof r.record === "string" ? r.record : String(r.alert ?? "")}`;
106
+ for (const issue of collectorMetricIssues(r.expr, emitted)) {
107
+ const message = `rule ${subject} ${issue.message}`;
108
+ out.push({
109
+ checkId: "PROM301",
110
+ severity: "warning",
111
+ message: source && source !== "prometheus" ? `${source}: ${message}` : message,
112
+ entity: subject,
113
+ lexicon: "prometheus",
114
+ });
115
+ }
116
+ }
117
+ }
118
+ }
119
+ return out;
120
+ }
121
+
83
122
  /** PROM202: joins every rule file in the output against every Alertmanager config in it. Silent when either is absent. */
84
123
  export function routingDiagnostics(ctx: PostSynthContext): PostSynthDiagnostic[] {
85
124
  const { ruleFiles, alertmanager } = prometheusDocs(ctx);
@@ -0,0 +1,21 @@
1
+ /**
2
+ * PROM211: An alerting rule has no for, or for: 0s
3
+ *
4
+ * The alert fires on the first evaluation that matches, so one bad scrape
5
+ * pages. Left alone when the expression reads no series (vector(1), a dead
6
+ * man's switch) or already spans a window: every selector read through a
7
+ * *_over_time function, or two conditions joined by and (the multi-window
8
+ * burn-rate form, where the short window does what for would).
9
+ */
10
+
11
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
12
+ import { ruleFileDiagnostics } from "./prom-helpers";
13
+
14
+ export const prom211: PostSynthCheck = {
15
+ id: "PROM211",
16
+ description: "An alerting rule has no for, or for: 0s",
17
+
18
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
19
+ return ruleFileDiagnostics(ctx, "PROM211");
20
+ },
21
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * PROM212: An alerting rule has no runbook_url annotation (opt-in)
3
+ *
4
+ * Off unless enabled: the lint preset all, or a lint.rules entry for PROM212,
5
+ * turns it on. See validateRunbookUrls.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { runbookDiagnostics } from "./prom-helpers";
10
+
11
+ export const prom212: PostSynthCheck = {
12
+ id: "PROM212",
13
+ description: "An alerting rule has no runbook_url annotation (opt-in)",
14
+
15
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
16
+ return runbookDiagnostics(ctx);
17
+ },
18
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * PROM213: An alert expression has no comparison
3
+ *
4
+ * Every series the expression returns is an alert, so an expression with no
5
+ * comparison fires for all of them. absent(), absent_over_time() and unless
6
+ * count as a condition; a comparison with bool does not. An expression that
7
+ * reads no series (vector(1), a dead man's switch) is left alone.
8
+ */
9
+
10
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
11
+ import { ruleFileDiagnostics } from "./prom-helpers";
12
+
13
+ export const prom213: PostSynthCheck = {
14
+ id: "PROM213",
15
+ description: "An alert expression has no comparison",
16
+
17
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
18
+ return ruleFileDiagnostics(ctx, "PROM213");
19
+ },
20
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * PROM214: An alert template reads a label the expression aggregates away
3
+ *
4
+ * A $labels.x whose x the expression's by (...) leaves out, or its without
5
+ * (...) names, renders empty. Labels the rule or its group set are left alone.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { ruleFileDiagnostics } from "./prom-helpers";
10
+
11
+ export const prom214: PostSynthCheck = {
12
+ id: "PROM214",
13
+ description: "An alert template reads a label the expression aggregates away",
14
+
15
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
16
+ return ruleFileDiagnostics(ctx, "PROM214");
17
+ },
18
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * PROM215: rate, irate or increase over a name that is not a counter's
3
+ *
4
+ * These functions treat every drop as a counter reset. A name not ending in
5
+ * _total, _count, _sum or _bucket is read as a gauge.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { ruleFileDiagnostics } from "./prom-helpers";
10
+
11
+ export const prom215: PostSynthCheck = {
12
+ id: "PROM215",
13
+ description: "rate, irate or increase over a name that is not a counter's",
14
+
15
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
16
+ return ruleFileDiagnostics(ctx, "PROM215");
17
+ },
18
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * PROM216: histogram_quantile over a series without _bucket, or without le
3
+ *
4
+ * A classic histogram's quantile needs its _bucket series with the le label
5
+ * kept. A native histogram has no _bucket series, so this check does not apply
6
+ * to one.
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { ruleFileDiagnostics } from "./prom-helpers";
11
+
12
+ export const prom216: PostSynthCheck = {
13
+ id: "PROM216",
14
+ description: "histogram_quantile over a series without _bucket, or without le",
15
+
16
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
+ return ruleFileDiagnostics(ctx, "PROM216");
18
+ },
19
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * PROM217: A recording rule name is not in level:metric:operations form
3
+ *
4
+ * Prometheus's naming convention for recorded series, e.g.
5
+ * job:http_requests:rate5m.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { ruleFileDiagnostics } from "./prom-helpers";
10
+
11
+ export const prom217: PostSynthCheck = {
12
+ id: "PROM217",
13
+ description: "A recording rule name is not in level:metric:operations form",
14
+
15
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
16
+ return ruleFileDiagnostics(ctx, "PROM217");
17
+ },
18
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * PROM218: A regex matcher needs no regex, or is anchored
3
+ *
4
+ * A =~ or !~ with no regex metacharacters is an equality matcher written the
5
+ * slow way. Prometheus anchors every regex, so a leading ^ or trailing $ does
6
+ * nothing.
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { ruleFileDiagnostics } from "./prom-helpers";
11
+
12
+ export const prom218: PostSynthCheck = {
13
+ id: "PROM218",
14
+ description: "A regex matcher needs no regex, or is anchored",
15
+
16
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
+ return ruleFileDiagnostics(ctx, "PROM218");
18
+ },
19
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * PROM219: An alerting rule sets the alertname label
3
+ *
4
+ * Prometheus sets alertname to the rule's name after applying the rule's
5
+ * labels, so the value is overwritten.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { ruleFileDiagnostics } from "./prom-helpers";
10
+
11
+ export const prom219: PostSynthCheck = {
12
+ id: "PROM219",
13
+ description: "An alerting rule sets the alertname label",
14
+
15
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
16
+ return ruleFileDiagnostics(ctx, "PROM219");
17
+ },
18
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * PROM220: A receiver turns off TLS certificate verification
3
+ *
4
+ * tls_config.insecure_skip_verify: true on a receiver, or in global, accepts
5
+ * any certificate the endpoint presents.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { alertmanagerDiagnostics } from "./prom-helpers";
10
+
11
+ export const prom220: PostSynthCheck = {
12
+ id: "PROM220",
13
+ description: "A receiver turns off TLS certificate verification",
14
+
15
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
16
+ return alertmanagerDiagnostics(ctx, "PROM220");
17
+ },
18
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * PROM221: SMTP credentials sent with require_tls false
3
+ *
4
+ * An email receiver with SMTP auth and require_tls false (its own, or
5
+ * global.smtp_require_tls) can send the credentials in clear text. Implicit
6
+ * TLS is left alone.
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { alertmanagerDiagnostics } from "./prom-helpers";
11
+
12
+ export const prom221: PostSynthCheck = {
13
+ id: "PROM221",
14
+ description: "SMTP credentials sent with require_tls false",
15
+
16
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
+ return alertmanagerDiagnostics(ctx, "PROM221");
18
+ },
19
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * PROM222: Credentials sent to an http:// receiver URL
3
+ *
4
+ * An integration whose URL is http:// and whose request carries a credential:
5
+ * user info in the URL, http_config auth, or a key or token field.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { alertmanagerDiagnostics } from "./prom-helpers";
10
+
11
+ export const prom222: PostSynthCheck = {
12
+ id: "PROM222",
13
+ description: "Credentials sent to an http:// receiver URL",
14
+
15
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
16
+ return alertmanagerDiagnostics(ctx, "PROM222");
17
+ },
18
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * PROM223: repeat_interval is shorter than group_interval
3
+ *
4
+ * Alertmanager resends only when it flushes a group, every group_interval, so
5
+ * a shorter repeat_interval never takes effect. Intervals are inherited down
6
+ * the routing tree from Alertmanager's defaults.
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { alertmanagerDiagnostics } from "./prom-helpers";
11
+
12
+ export const prom223: PostSynthCheck = {
13
+ id: "PROM223",
14
+ description: "repeat_interval is shorter than group_interval",
15
+
16
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
+ return alertmanagerDiagnostics(ctx, "PROM223");
18
+ },
19
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * PROM224: An inhibit rule matches one alert as source and target, with no equal
3
+ *
4
+ * With no equal, any alert matching the source mutes every alert matching the
5
+ * target. When one alert can match both, alerts of that kind mute each other.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { alertmanagerDiagnostics } from "./prom-helpers";
10
+
11
+ export const prom224: PostSynthCheck = {
12
+ id: "PROM224",
13
+ description: "An inhibit rule matches one alert as source and target, with no equal",
14
+
15
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
16
+ return alertmanagerDiagnostics(ctx, "PROM224");
17
+ },
18
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * PROM301: A rule reads a connector metric no collector in the build emits
3
+ *
4
+ * For each selector in a rule whose name falls under a namespace the build's
5
+ * spanmetrics, servicegraph or GenAI connectors own (spanmetrics' and
6
+ * servicegraph's defaults included), reports a name no collector config in
7
+ * the build emits, and a by (...) label that is not a dimension the config
8
+ * declares for the metrics aggregated. Silent for names outside those
9
+ * namespaces, and when the build has no collector config. See
10
+ * ../../collector-metrics.ts.
11
+ */
12
+
13
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
14
+ import { collectorMetricDiagnostics } from "./prom-helpers";
15
+
16
+ export const prom301: PostSynthCheck = {
17
+ id: "PROM301",
18
+ description: "A rule reads a connector metric no collector in the build emits",
19
+
20
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
21
+ return collectorMetricDiagnostics(ctx);
22
+ },
23
+ };
@@ -7,7 +7,7 @@
7
7
  * `PrometheusRule`'s groups, or on a file parsed by some other tool.
8
8
  */
9
9
  import { type AlertmanagerConfig, type RuleFileConfig } from "./model.js";
10
- export type PrometheusIssueCode = "PROM101" | "PROM102" | "PROM103" | "PROM104" | "PROM105" | "PROM106" | "PROM107" | "PROM201" | "PROM202" | "PROM203" | "PROM204" | "PROM205" | "PROM206" | "PROM207" | "PROM208" | "PROM209" | "PROM210";
10
+ export type PrometheusIssueCode = "PROM101" | "PROM102" | "PROM103" | "PROM104" | "PROM105" | "PROM106" | "PROM107" | "PROM201" | "PROM202" | "PROM203" | "PROM204" | "PROM205" | "PROM206" | "PROM207" | "PROM208" | "PROM209" | "PROM210" | "PROM211" | "PROM212" | "PROM213" | "PROM214" | "PROM215" | "PROM216" | "PROM217" | "PROM218" | "PROM219" | "PROM220" | "PROM221" | "PROM222" | "PROM223" | "PROM224";
11
11
  export interface PrometheusIssue {
12
12
  code: PrometheusIssueCode;
13
13
  severity: "error" | "warning";
@@ -15,11 +15,17 @@ export interface PrometheusIssue {
15
15
  /** What the issue is about: a group, a rule (`group/rule`), a receiver or a route. */
16
16
  subject?: string;
17
17
  }
18
- /** Check a rule file (PROM101-PROM107). */
18
+ /** Check a rule file (PROM101-PROM107, PROM211 and PROM213-PROM219). PROM212 is opt-in; see {@link validateRunbookUrls}. */
19
19
  export declare function validateRuleFile(file: RuleFileConfig): PrometheusIssue[];
20
+ /**
21
+ * PROM212: alerts without a `runbook_url` annotation. Not part of
22
+ * {@link validateRuleFile}, because the check is opt-in: the lint preset
23
+ * `all`, or a `lint.rules` entry for PROM212, turns it on.
24
+ */
25
+ export declare function validateRunbookUrls(file: RuleFileConfig): PrometheusIssue[];
20
26
  /** Every severity value an alert in these rule files carries, with the alerts carrying it. */
21
27
  export declare function alertSeverities(files: RuleFileConfig[]): Map<string, string[]>;
22
- /** Check an `alertmanager.yml` on its own (PROM201, PROM203-PROM210). */
28
+ /** Check an `alertmanager.yml` on its own (PROM201, PROM203-PROM210, PROM220-PROM224). */
23
29
  export declare function validateAlertmanagerConfig(config: AlertmanagerConfig): PrometheusIssue[];
24
30
  /**
25
31
  * PROM202: every severity an alert carries is taken by some route below the
@@ -1 +1 @@
1
- {"version":3,"file":"validate-config.d.ts","sourceRoot":"","sources":["../src/validate-config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAGL,KAAK,kBAAkB,EAIvB,KAAK,cAAc,EAEpB,MAAM,SAAS,CAAC;AAIjB,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAmBD,2CAA2C;AAC3C,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,eAAe,EAAE,CAsHxE;AAED,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAa9E;AAsBD,yEAAyE;AACzE,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,eAAe,EAAE,CAmGxF;AAYD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,cAAc,EAAE,EAAE,MAAM,EAAE,kBAAkB,GAAG,eAAe,EAAE,CA6BlH"}
1
+ {"version":3,"file":"validate-config.d.ts","sourceRoot":"","sources":["../src/validate-config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAGL,KAAK,kBAAkB,EAIvB,KAAK,cAAc,EAEpB,MAAM,SAAS,CAAC;AAiBjB,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAwID,4HAA4H;AAC5H,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,eAAe,EAAE,CAmIxE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,cAAc,GAAG,eAAe,EAAE,CAU3E;AAED,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAa9E;AAkGD,0FAA0F;AAC1F,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,eAAe,EAAE,CAsHxF;AAYD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,cAAc,EAAE,EAAE,MAAM,EAAE,kBAAkB,GAAG,eAAe,EAAE,CA6BlH"}
@@ -30,6 +30,7 @@ import type { AlertmanagerGlobalConfig, ReceiverConfig } from "./model.js";
30
30
  import type { PrometheusIssue } from "./validate-config.js";
31
31
  /** True when `value` is a Go `time.ParseDuration` duration (`0`, `1.5s`, `1h30m`, `300ms`). */
32
32
  export declare function isValidGoDuration(value: unknown): boolean;
33
+ export declare const SLACK_APP_URL = "https://slack.com/api/chat.postMessage";
33
34
  /** PROM208-PROM210 findings for every integration of one receiver. */
34
35
  export declare function validateReceiverIntegrations(receiver: ReceiverConfig, global: AlertmanagerGlobalConfig): PrometheusIssue[];
35
36
  /** PROM208 and PROM210 findings for `global:` (config/config.go `Config.UnmarshalYAML`). */
@@ -1 +1 @@
1
- {"version":3,"file":"validate-integrations.d.ts","sourceRoot":"","sources":["../src/validate-integrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,wBAAwB,EAAoB,cAAc,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAwBzD,+FAA+F;AAC/F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKzD;AA6RD,sEAAsE;AACtE,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,wBAAwB,GAAG,eAAe,EAAE,CAS1H;AAgBD,4FAA4F;AAC5F,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,eAAe,EAAE,CAe1F"}
1
+ {"version":3,"file":"validate-integrations.d.ts","sourceRoot":"","sources":["../src/validate-integrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,wBAAwB,EAAoB,cAAc,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAwBzD,+FAA+F;AAC/F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKzD;AAkBD,eAAO,MAAM,aAAa,2CAA2C,CAAC;AA2QtE,sEAAsE;AACtE,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,wBAAwB,GAAG,eAAe,EAAE,CAS1H;AAgBD,4FAA4F;AAC5F,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,eAAe,EAAE,CAe1F"}