@intentius/chant-lexicon-prometheus 0.106.4 → 0.108.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 (111) hide show
  1. package/README.md +6 -1
  2. package/dist/api/alertmanager.d.ts +56 -0
  3. package/dist/api/alertmanager.d.ts.map +1 -0
  4. package/dist/api/apply.d.ts +126 -0
  5. package/dist/api/apply.d.ts.map +1 -0
  6. package/dist/api/bind.d.ts +49 -0
  7. package/dist/api/bind.d.ts.map +1 -0
  8. package/dist/api/client.d.ts +76 -0
  9. package/dist/api/client.d.ts.map +1 -0
  10. package/dist/api/evaluated.d.ts +36 -0
  11. package/dist/api/evaluated.d.ts.map +1 -0
  12. package/dist/api/fake-servers.d.ts +74 -0
  13. package/dist/api/fake-servers.d.ts.map +1 -0
  14. package/dist/api/index.d.ts +18 -0
  15. package/dist/api/index.d.ts.map +1 -0
  16. package/dist/api/ruler.d.ts +87 -0
  17. package/dist/api/ruler.d.ts.map +1 -0
  18. package/dist/composites/alert-routing.d.ts +104 -0
  19. package/dist/composites/alert-routing.d.ts.map +1 -0
  20. package/dist/composites/catalog.d.ts.map +1 -1
  21. package/dist/composites/index.d.ts +6 -0
  22. package/dist/composites/index.d.ts.map +1 -1
  23. package/dist/composites/red-alerts.d.ts +96 -0
  24. package/dist/composites/red-alerts.d.ts.map +1 -0
  25. package/dist/composites/watchdog.d.ts +69 -0
  26. package/dist/composites/watchdog.d.ts.map +1 -0
  27. package/dist/config.d.ts +273 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/describe-resources.d.ts +48 -0
  30. package/dist/describe-resources.d.ts.map +1 -0
  31. package/dist/export-resources.d.ts +41 -0
  32. package/dist/export-resources.d.ts.map +1 -0
  33. package/dist/import/alertmanager-live.d.ts +35 -0
  34. package/dist/import/alertmanager-live.d.ts.map +1 -0
  35. package/dist/import/parser.d.ts +12 -0
  36. package/dist/import/parser.d.ts.map +1 -1
  37. package/dist/index.d.ts +6 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/integrity.json +3 -3
  40. package/dist/lint/post-synth/prom-helpers.d.ts +11 -3
  41. package/dist/lint/post-synth/prom-helpers.d.ts.map +1 -1
  42. package/dist/manifest.json +1 -1
  43. package/dist/op/activities/index.d.ts +25 -0
  44. package/dist/op/activities/index.d.ts.map +1 -0
  45. package/dist/op/activities/promtool.d.ts +78 -0
  46. package/dist/op/activities/promtool.d.ts.map +1 -0
  47. package/dist/op/activities/rule-audit.d.ts +65 -0
  48. package/dist/op/activities/rule-audit.d.ts.map +1 -0
  49. package/dist/op/activities/rules-loaded.d.ts +60 -0
  50. package/dist/op/activities/rules-loaded.d.ts.map +1 -0
  51. package/dist/op/activities/silence.d.ts +80 -0
  52. package/dist/op/activities/silence.d.ts.map +1 -0
  53. package/dist/op/activity-contracts.d.ts +113 -0
  54. package/dist/op/activity-contracts.d.ts.map +1 -0
  55. package/dist/op/audit-op.d.ts +38 -0
  56. package/dist/op/audit-op.d.ts.map +1 -0
  57. package/dist/op/builders.d.ts +57 -0
  58. package/dist/op/builders.d.ts.map +1 -0
  59. package/dist/op/slo-rule-tests.d.ts +44 -0
  60. package/dist/op/slo-rule-tests.d.ts.map +1 -0
  61. package/dist/plugin.d.ts.map +1 -1
  62. package/dist/rules/prom-helpers.ts +38 -3
  63. package/package.json +19 -3
  64. package/src/api/alertmanager.ts +122 -0
  65. package/src/api/apply.test.ts +218 -0
  66. package/src/api/apply.ts +375 -0
  67. package/src/api/bind.ts +87 -0
  68. package/src/api/client.ts +138 -0
  69. package/src/api/evaluated.test.ts +78 -0
  70. package/src/api/evaluated.ts +106 -0
  71. package/src/api/fake-servers.ts +209 -0
  72. package/src/api/index.ts +29 -0
  73. package/src/api/ruler.ts +168 -0
  74. package/src/api/testdata/am-status-original.yml +85 -0
  75. package/src/composites/alert-routing.test.ts +163 -0
  76. package/src/composites/alert-routing.ts +277 -0
  77. package/src/composites/catalog.test.ts +10 -2
  78. package/src/composites/catalog.ts +214 -0
  79. package/src/composites/index.ts +14 -0
  80. package/src/composites/red-alerts.test.ts +182 -0
  81. package/src/composites/red-alerts.ts +214 -0
  82. package/src/composites/watchdog.test.ts +85 -0
  83. package/src/composites/watchdog.ts +142 -0
  84. package/src/config.test.ts +93 -0
  85. package/src/config.ts +288 -0
  86. package/src/describe-resources.test.ts +325 -0
  87. package/src/describe-resources.ts +328 -0
  88. package/src/export-resources.test.ts +253 -0
  89. package/src/export-resources.ts +193 -0
  90. package/src/import/alertmanager-live.test.ts +66 -0
  91. package/src/import/alertmanager-live.ts +291 -0
  92. package/src/import/parser.ts +18 -1
  93. package/src/import/roundtrip.test.ts +1 -0
  94. package/src/index.ts +37 -0
  95. package/src/lint/post-synth/prom-helpers.ts +38 -3
  96. package/src/op/activities/index.ts +31 -0
  97. package/src/op/activities/promtool.test.ts +107 -0
  98. package/src/op/activities/promtool.ts +149 -0
  99. package/src/op/activities/rule-audit.test.ts +89 -0
  100. package/src/op/activities/rule-audit.ts +168 -0
  101. package/src/op/activities/rules-loaded.test.ts +61 -0
  102. package/src/op/activities/rules-loaded.ts +98 -0
  103. package/src/op/activities/silence.test.ts +107 -0
  104. package/src/op/activities/silence.ts +175 -0
  105. package/src/op/activity-contracts.test.ts +141 -0
  106. package/src/op/activity-contracts.ts +96 -0
  107. package/src/op/audit-op.ts +54 -0
  108. package/src/op/builders.ts +82 -0
  109. package/src/op/slo-rule-tests.test.ts +84 -0
  110. package/src/op/slo-rule-tests.ts +173 -0
  111. package/src/plugin.ts +16 -0
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Activity contracts for this lexicon's `op/activities` (#3369).
3
+ *
4
+ * Resolved by convention at
5
+ * `@intentius/chant-lexicon-prometheus/op/activity-contracts`:
6
+ * `loadActivityContracts` merges them into the map OPS012 checks each step's
7
+ * args against and OPS013 checks each step-output reference against. Every
8
+ * activity here has one; `rulesLoadedObserve`'s return schema is what lets a
9
+ * `ConvergeOp({ observe: rulesLoadedObserve(...) })` pass OPS013, and
10
+ * `alertmanagerSilence`'s is what a later step reads `.out.silenceId` from.
11
+ *
12
+ * Each `args` schema mirrors the `*Args` interface in `./activities/`,
13
+ * without the `_`-prefixed test seams, and is a `z.strictObject`, so a
14
+ * misspelled key fails the build.
15
+ */
16
+ export declare const promtoolCheckRulesContract: import("@intentius/chant/op").ActivityContract<{
17
+ rules: string | string[];
18
+ bin?: string | undefined;
19
+ }, {
20
+ files: string[];
21
+ ok: boolean;
22
+ output: string;
23
+ }>;
24
+ export declare const promtoolTestRulesContract: import("@intentius/chant/op").ActivityContract<{
25
+ rules: string;
26
+ tests?: string | string[] | undefined;
27
+ testYaml?: string | string[] | undefined;
28
+ bin?: string | undefined;
29
+ }, {
30
+ files: string[];
31
+ ok: boolean;
32
+ output: string;
33
+ tests: number;
34
+ }>;
35
+ export declare const amtoolCheckConfigContract: import("@intentius/chant/op").ActivityContract<{
36
+ config: string;
37
+ bin?: string | undefined;
38
+ }, {
39
+ files: string[];
40
+ ok: boolean;
41
+ output: string;
42
+ }>;
43
+ export declare const amtoolRoutesTestContract: import("@intentius/chant/op").ActivityContract<{
44
+ config: string;
45
+ labels: Record<string, string>;
46
+ expect: string | string[];
47
+ bin?: string | undefined;
48
+ }, {
49
+ config: string;
50
+ labels: Record<string, string>;
51
+ receivers: string[];
52
+ ok: boolean;
53
+ }>;
54
+ export declare const alertmanagerSilenceContract: import("@intentius/chant/op").ActivityContract<{
55
+ record?: string | undefined;
56
+ recordDir?: string | undefined;
57
+ url?: string | undefined;
58
+ matchers: string[] | Record<string, string>;
59
+ duration: string;
60
+ comment?: string | undefined;
61
+ createdBy?: string | undefined;
62
+ }, {
63
+ silenceId: string;
64
+ url: string;
65
+ startsAt: string;
66
+ endsAt: string;
67
+ record: string;
68
+ }>;
69
+ export declare const alertmanagerUnsilenceContract: import("@intentius/chant/op").ActivityContract<{
70
+ record?: string | undefined;
71
+ recordDir?: string | undefined;
72
+ url?: string | undefined;
73
+ silenceId?: string | undefined;
74
+ }, {
75
+ expired: string[];
76
+ failed: {
77
+ silenceId: string;
78
+ detail: string;
79
+ }[];
80
+ }>;
81
+ export declare const rulesLoadedObserveContract: import("@intentius/chant/op").ActivityContract<{
82
+ url?: string | undefined;
83
+ groups?: string[] | undefined;
84
+ rules?: string | string[] | undefined;
85
+ timeoutMs?: number | undefined;
86
+ }, {
87
+ resources: {
88
+ name: string;
89
+ status: "drifted" | "in-sync" | "unknown";
90
+ detail: string;
91
+ }[];
92
+ }>;
93
+ export declare const ruleAuditContract: import("@intentius/chant/op").ActivityContract<{
94
+ url?: string | undefined;
95
+ pendingFor?: string | undefined;
96
+ firingFor?: string | undefined;
97
+ selectorBudget?: number | undefined;
98
+ lookback?: string | undefined;
99
+ mode?: "issue" | "report" | undefined;
100
+ issueTitle?: string | undefined;
101
+ }, {
102
+ mode: "issue" | "report";
103
+ findings: {
104
+ kind: "firing-too-long" | "pending-too-long" | "rule-error" | "selector-no-series";
105
+ subject: string;
106
+ detail: string;
107
+ }[];
108
+ queried: number;
109
+ unchecked: number;
110
+ summary: string;
111
+ issueUrl?: string | undefined;
112
+ }>;
113
+ //# sourceMappingURL=activity-contracts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-contracts.d.ts","sourceRoot":"","sources":["../../src/op/activity-contracts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AASH,eAAO,MAAM,0BAA0B;;;;;;;EAItC,CAAC;AAEF,eAAO,MAAM,yBAAyB;;;;;;;;;;EAIrC,CAAC;AAEF,eAAO,MAAM,yBAAyB;;;;;;;EAIrC,CAAC;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;EAIpC,CAAC;AAIF,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;EAWvC,CAAC;AAEF,eAAO,MAAM,6BAA6B;;;;;;;;;;;EAIzC,CAAC;AAEF,eAAO,MAAM,0BAA0B;;;;;;;;;;;EAItC,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;EAqB7B,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `RuleAuditOp`: what a live Prometheus says about its rules, as an Op with
3
+ * an optional schedule (#3369). One `ruleAudit` step reports groups with
4
+ * health errors, alerts pending or firing past a threshold, and selectors
5
+ * no live target emits (one query per selector, within a budget).
6
+ * `onFinding` is `report` (the default) or `issue`, which keeps one open
7
+ * issue current.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * export const { op } = RuleAuditOp({ name: "rule-audit", url: "http://prometheus:9090", schedule: "0 * * * *" });
12
+ * ```
13
+ */
14
+ import { type OpResource } from "@intentius/chant/op";
15
+ import type { RuleAuditMode } from "./activities/rule-audit.js";
16
+ export interface RuleAuditOpConfig {
17
+ /** Op name (kebab-case). */
18
+ name: string;
19
+ /** Prometheus's base URL. Default `$PROMETHEUS_URL`, else `http://localhost:9090`. */
20
+ url?: string;
21
+ /** Cron expression; omit for one-shot `chant run`. */
22
+ schedule?: string;
23
+ /** @default "report" */
24
+ onFinding?: RuleAuditMode;
25
+ /** @default "1h" */
26
+ pendingFor?: string;
27
+ /** @default "24h" */
28
+ firingFor?: string;
29
+ /** @default 50 */
30
+ selectorBudget?: number;
31
+ /** @default "1h" */
32
+ lookback?: string;
33
+ }
34
+ export interface RuleAuditOpResources {
35
+ op: InstanceType<typeof OpResource>;
36
+ }
37
+ export declare function RuleAuditOp(config: RuleAuditOpConfig): RuleAuditOpResources;
38
+ //# sourceMappingURL=audit-op.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-op.d.ts","sourceRoot":"","sources":["../../src/op/audit-op.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IAChC,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,oBAAoB;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,YAAY,CAAC,OAAO,UAAU,CAAC,CAAC;CACrC;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,oBAAoB,CAY3E"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Typed step builders for this lexicon's Op activities (#3369), following
3
+ * the helm and fly builders: each one's options ARE the activity's own
4
+ * `*Args` interface (minus its `_`-prefixed test seams), every field also
5
+ * takes a `StepOutputRef`, and `profile` and `id` go to the step.
6
+ *
7
+ * `promtoolCheckRules`, `promtoolTestRules` and `amtoolCheckConfig` share
8
+ * their names with the plain functions in `../tools.ts`, which the package
9
+ * root already exports, so those three builders are reached here, at
10
+ * `@intentius/chant-lexicon-prometheus/op/builders`. The root exports the
11
+ * rest.
12
+ */
13
+ import { type ActivityStep, type NamedActivityStep, type WithStepRefs } from "@intentius/chant/op";
14
+ import type { AmtoolCheckConfigArgs, AmtoolRoutesTestArgs, PromtoolCheckRulesArgs, PromtoolTestRulesArgs } from "./activities/promtool.js";
15
+ import type { AlertmanagerSilenceArgs, AlertmanagerUnsilenceArgs } from "./activities/silence.js";
16
+ import type { RulesLoadedObserveArgs } from "./activities/rules-loaded.js";
17
+ import type { RuleAuditArgs } from "./activities/rule-audit.js";
18
+ import { type SloTestInput } from "./slo-rule-tests.js";
19
+ type StepOpts = {
20
+ profile?: ActivityStep["profile"];
21
+ id?: string;
22
+ };
23
+ type Authored<T> = Omit<T, `_${string}`>;
24
+ /** `promtool check rules` over built rule files. Defaults to the `fastIdempotent` profile. */
25
+ export declare const promtoolCheckRules: (args: WithStepRefs<Authored<PromtoolCheckRulesArgs>> & StepOpts) => NamedActivityStep;
26
+ /**
27
+ * `promtool test rules` over a built rule file. `slos` generates the tests
28
+ * from `Slo` declarations at build time (`sloRuleTests`): each burn-rate
29
+ * pair fires at its rate and not below it. Defaults to the `fastIdempotent`
30
+ * profile.
31
+ */
32
+ export declare const promtoolTestRules: (args: WithStepRefs<Authored<PromtoolTestRulesArgs>> & StepOpts & {
33
+ slos?: SloTestInput[];
34
+ }) => NamedActivityStep;
35
+ /** `amtool check-config` over a built Alertmanager config. Defaults to the `fastIdempotent` profile. */
36
+ export declare const amtoolCheckConfig: (args: WithStepRefs<Authored<AmtoolCheckConfigArgs>> & StepOpts) => NamedActivityStep;
37
+ /** `amtool config routes test`: `labels` route to `expect`. Defaults to the `fastIdempotent` profile. */
38
+ export declare const amtoolRoutesTest: (args: WithStepRefs<Authored<AmtoolRoutesTestArgs>> & StepOpts) => NamedActivityStep;
39
+ /**
40
+ * Silence alerts through Alertmanager's API for `duration`, recording the
41
+ * silence id under `record` so `alertmanagerUnsilence` can expire it from
42
+ * a final phase or an `onFailure` phase. Defaults to the `atMostOnce`
43
+ * profile: a retry after a lost answer would create a second silence.
44
+ */
45
+ export declare const alertmanagerSilence: (args: WithStepRefs<Authored<AlertmanagerSilenceArgs>> & StepOpts) => NamedActivityStep;
46
+ /** Expire the silences `record` holds (or `silenceId`). Defaults to the `fastIdempotent` profile. */
47
+ export declare const alertmanagerUnsilence: (args?: WithStepRefs<Authored<AlertmanagerUnsilenceArgs>> & StepOpts) => NamedActivityStep;
48
+ /**
49
+ * Observe declared rule groups for a `ConvergeOp({ observe })`: one resource
50
+ * per group, drifted when Prometheus has not loaded it or a rule in it has
51
+ * `health: "err"`. Defaults to the `fastIdempotent` profile.
52
+ */
53
+ export declare const rulesLoadedObserve: (args: WithStepRefs<Authored<RulesLoadedObserveArgs>> & StepOpts) => NamedActivityStep;
54
+ /** Audit a live Prometheus's rules, alerts and selectors (the step of `RuleAuditOp`). Defaults to the `fastIdempotent` profile. */
55
+ export declare const ruleAudit: (args?: WithStepRefs<Authored<RuleAuditArgs>> & StepOpts) => NamedActivityStep;
56
+ export {};
57
+ //# sourceMappingURL=builders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../../src/op/builders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAA8B,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC/H,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAC/F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEnE,KAAK,QAAQ,GAAG;IAAE,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACnE,KAAK,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,IAAI,MAAM,EAAE,CAAC,CAAC;AASzC,8FAA8F;AAC9F,eAAO,MAAM,kBAAkB,uEAP2B,iBAO4C,CAAC;AAEvG;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,SACtB,YAAY,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC,GAAG,QAAQ,GAAG;IAAE,IAAI,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,KACzF,iBAMF,CAAC;AAEF,wGAAwG;AACxG,eAAO,MAAM,iBAAiB,sEA1B4B,iBA0ByC,CAAC;AAEpG,yGAAyG;AACzG,eAAO,MAAM,gBAAgB,qEA7B6B,iBA6BsC,CAAC;AAEjG;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,wEArC0B,iBAqC2C,CAAC;AAEtG,qGAAqG;AACrG,eAAO,MAAM,qBAAqB,UAAU,YAAY,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC,GAAG,QAAQ,KAAQ,iBAC9B,CAAC;AAEnF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,uEAhD2B,iBAgD4C,CAAC;AAEvG,mIAAmI;AACnI,eAAO,MAAM,SAAS,UAAU,YAAY,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,GAAG,QAAQ,KAAQ,iBAC9B,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `promtool test rules` input generated from `Slo` declarations (#3369),
3
+ * lifted from `composites/slo-burn.test.ts`.
4
+ *
5
+ * For each burn-rate pair an `Slo` builds, two synthetic scenarios over a
6
+ * good and a bad request counter: healthy for the pair's long window, then
7
+ * burning the error budget at 1.25 times the pair's factor (it must fire)
8
+ * and at 0.9 times it (it must not). The small evaluator in `rule-eval.ts`
9
+ * runs the SLO's rules over each scenario and says which alerts fire at a
10
+ * few evaluation times around the expected onset; those become the
11
+ * `alert_rule_test` expectations, so `promtool test rules` holds Prometheus
12
+ * to the same answer.
13
+ *
14
+ * The SLI expressions are the author's, so the generator cannot invent the
15
+ * series they read: `good` and `bad` name one series each (`metric{labels}`),
16
+ * counters such that the SLI counts `good` as good events and `bad` as bad
17
+ * ones. The evaluator throws on PromQL it does not implement, which then
18
+ * fails here, at authoring time, rather than in promtool.
19
+ *
20
+ * Pairs whose long window is a day or more step in five minutes, the rest in
21
+ * one, and promtool's `evaluation_interval` is per file, so the result is
22
+ * one test file per step size.
23
+ */
24
+ import { type SloInstance } from "../composites/slo.js";
25
+ export interface SloTestInput {
26
+ /** The `Slo(...)` result. */
27
+ slo: SloInstance;
28
+ /** The good-events counter, e.g. `requests_total{code="200"}`. */
29
+ good: string;
30
+ /** The bad-events counter, e.g. `requests_total{code="500"}`. */
31
+ bad: string;
32
+ /** Events per minute. Default 1000. */
33
+ rate?: number;
34
+ }
35
+ /** `metric{a="1",b="2"}` as a label set with `__name__`. */
36
+ export declare function seriesLabels(series: string): Record<string, string>;
37
+ /** Counter samples as promtool's `a+bxn` segments. */
38
+ export declare function seriesValues(values: number[]): string;
39
+ /**
40
+ * The `promtool test rules` files for these SLOs, one per step size, each
41
+ * naming `rules.yml` (where `promtoolTestRules` writes the rule file).
42
+ */
43
+ export declare function sloRuleTests(inputs: SloTestInput[]): string[];
44
+ //# sourceMappingURL=slo-rule-tests.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slo-rule-tests.d.ts","sourceRoot":"","sources":["../../src/op/slo-rule-tests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAgC,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAQnF,MAAM,WAAW,YAAY;IAC3B,6BAA6B;IAC7B,GAAG,EAAE,WAAW,CAAC;IACjB,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AASD,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAiBnE;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAgBrD;AA6DD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,CAmB7D"}
@@ -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,aAyH9B,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAgC9D;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,aAwI9B,CAAC"}
@@ -5,9 +5,9 @@
5
5
  *
6
6
  * Documents are recognized by shape, not by which lexicon emitted them, so a
7
7
  * rule file that reaches the output some other way (a hand-written YAML
8
- * sidecar) is checked too. `PrometheusRule` manifests are not read here;
9
- * their groups are the same `RuleGroup`s and are checked where they are
10
- * declared.
8
+ * sidecar) is checked too. `PrometheusRule` manifests are not read here:
9
+ * `chant build` hands each lexicon's checks only that lexicon's output, so the
10
+ * k8s lexicon's WK8606 runs `prometheusRuleDiagnostics` (below) over them.
11
11
  */
12
12
 
13
13
  import type { PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
@@ -135,3 +135,38 @@ export function routingDiagnostics(ctx: PostSynthContext): PostSynthDiagnostic[]
135
135
  const files = ruleFiles.map((r) => r.config);
136
136
  return alertmanager.flatMap(({ source, config }) => validateSeverityRouting(files, config).map((i) => toDiagnostic(i, source)));
137
137
  }
138
+
139
+ /**
140
+ * The rule-file checks (PROM101-PROM107, PROM211, PROM213-PROM219) over the
141
+ * groups of every `PrometheusRule` in the build, whichever lexicon emitted
142
+ * it. `spec.groups` is a rule file's `groups`, so `validateRuleFile` reads it
143
+ * as is. Findings keep their PROM ids and name the PrometheusRule's namespace
144
+ * and name. PROM212 (runbook URLs) stays opt-in and is not run here.
145
+ */
146
+ export function prometheusRuleDiagnostics(ctx: PostSynthContext): PostSynthDiagnostic[] {
147
+ const out: PostSynthDiagnostic[] = [];
148
+ for (const output of ctx.outputs.values()) {
149
+ const text = typeof output === "string" ? output : (output as SerializerResult).primary;
150
+ if (!text) continue;
151
+ let docs: unknown[];
152
+ try {
153
+ docs = loadAll(text);
154
+ } catch {
155
+ continue;
156
+ }
157
+ for (const raw of docs) {
158
+ const doc = raw as Record<string, any> | null;
159
+ if (!doc || typeof doc !== "object" || doc.kind !== "PrometheusRule") continue;
160
+ if (typeof doc.apiVersion !== "string" || !doc.apiVersion.startsWith("monitoring.coreos.com/")) continue;
161
+ const groups = doc.spec?.groups;
162
+ if (!Array.isArray(groups)) continue;
163
+ const name = String(doc.metadata?.name ?? "");
164
+ const source = `PrometheusRule ${doc.metadata?.namespace ? `${doc.metadata.namespace}/` : ""}${name}`;
165
+ for (const issue of validateRuleFile({ groups } as RuleFileConfig)) {
166
+ if (issue.code === "PROM212") continue;
167
+ out.push({ ...toDiagnostic(issue, source), ...(issue.subject ? {} : { entity: name }) });
168
+ }
169
+ }
170
+ }
171
+ return out;
172
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-prometheus",
3
- "version": "0.106.4",
3
+ "version": "0.108.0",
4
4
  "description": "Prometheus lexicon for chant — typed recording and alerting rule groups and Alertmanager routing, serialized to a rule file and alertmanager.yml",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -41,6 +41,21 @@
41
41
  "types": "./dist/*.d.ts",
42
42
  "default": "./src/*.ts"
43
43
  },
44
+ "./api": {
45
+ "development": "./src/api/index.ts",
46
+ "types": "./dist/api/index.d.ts",
47
+ "default": "./src/api/index.ts"
48
+ },
49
+ "./op/activities": {
50
+ "development": "./src/op/activities/index.ts",
51
+ "types": "./dist/op/activities/index.d.ts",
52
+ "default": "./src/op/activities/index.ts"
53
+ },
54
+ "./op/activity-contracts": {
55
+ "development": "./src/op/activity-contracts.ts",
56
+ "types": "./dist/op/activity-contracts.d.ts",
57
+ "default": "./src/op/activity-contracts.ts"
58
+ },
44
59
  "./lint/post-synth": {
45
60
  "development": "./src/lint/post-synth/index.ts",
46
61
  "types": "./dist/lint/post-synth/index.d.ts",
@@ -63,7 +78,8 @@
63
78
  "@lezer/highlight": "^1.2.4",
64
79
  "@lezer/lr": "^1.4.10",
65
80
  "@prometheus-io/lezer-promql": "0.315.0",
66
- "js-yaml": "^4.3.1"
81
+ "js-yaml": "^4.3.1",
82
+ "zod": "^4.3.6"
67
83
  },
68
84
  "devDependencies": {
69
85
  "@intentius/chant": "*",
@@ -72,7 +88,7 @@
72
88
  "typescript": "^5.9.3"
73
89
  },
74
90
  "peerDependencies": {
75
- "@intentius/chant": "^0.106.4",
91
+ "@intentius/chant": "^0.108.0",
76
92
  "typescript": "^5.9.3"
77
93
  }
78
94
  }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The Alertmanager config APIs (#3371).
3
+ *
4
+ * | Kind | Config | Health |
5
+ * |---|---|---|
6
+ * | `alertmanager` | `/api/v2/status` `config.original` | the same answer: `cluster.status`, `versionInfo.version` |
7
+ * | `mimir`, `cortex` | `/api/v1/alerts` (YAML: `alertmanager_config`, `template_files`) | `<prefix>/api/v2/status`, best effort |
8
+ *
9
+ * A plain Alertmanager's `config.original` is not the file it loaded. It is
10
+ * the loaded config marshalled again (`Config.String()` in Alertmanager's
11
+ * `config/config.go`): secrets read `<secret>`, the global defaults are
12
+ * written out, and every receiver carries the settings it inherited from
13
+ * `global:`. ../import/alertmanager-live.ts takes those back out. Mimir and
14
+ * Cortex return the config as the tenant uploaded it.
15
+ *
16
+ * `setConfig` is for the ruler apply target (#3372): `POST /api/v1/alerts`,
17
+ * which Mimir and Cortex serve with `-alertmanager.enable-api`. A plain
18
+ * Alertmanager has no config write API.
19
+ */
20
+
21
+ import { dump } from "js-yaml";
22
+ import { loadPrometheusYaml } from "../import/parser";
23
+ import type { AlertmanagerTarget } from "../config";
24
+ import { PromApiError, type PromClient } from "./client";
25
+
26
+ /** What `/api/v2/status` says about the Alertmanager itself. */
27
+ export interface AlertmanagerHealth {
28
+ /** `ready`, `settling` or `disabled` (no clustering). */
29
+ cluster?: string;
30
+ peers?: number;
31
+ version?: string;
32
+ }
33
+
34
+ /** A live Alertmanager config, as text, with where it came from. */
35
+ export interface LiveAlertmanagerConfig {
36
+ /** The config YAML. */
37
+ text: string;
38
+ /** Where it was read. */
39
+ address: string;
40
+ /** True when it is Alertmanager's own re-marshalled form (`/api/v2/status`), with defaults written out and secrets masked. */
41
+ remarshalled: boolean;
42
+ /** Mimir and Cortex: the notification templates uploaded with it, by file name. */
43
+ templateFiles?: Record<string, string>;
44
+ }
45
+
46
+ function isObject(v: unknown): v is Record<string, unknown> {
47
+ return typeof v === "object" && v !== null && !Array.isArray(v);
48
+ }
49
+
50
+ /** A bound Alertmanager: the transport and the target it reaches. */
51
+ export class AlertmanagerApi {
52
+ constructor(
53
+ readonly client: PromClient,
54
+ readonly target: AlertmanagerTarget,
55
+ ) {}
56
+
57
+ /** Where `/api/v2/status` is served. */
58
+ get statusPath(): string {
59
+ return this.target.kind === "alertmanager" ? "/api/v2/status" : `${this.target.alertmanagerPrefix}/api/v2/status`;
60
+ }
61
+
62
+ /** The status answer, read once per client; undefined on 404. */
63
+ private status(): Promise<Record<string, unknown> | undefined> {
64
+ return this.client.once("status", () => this.client.getJson<Record<string, unknown>>(this.statusPath));
65
+ }
66
+
67
+ /** The loaded config; undefined when there is none (Mimir answers 404 for a tenant with no config). */
68
+ async readConfig(): Promise<LiveAlertmanagerConfig | undefined> {
69
+ return this.client.once("config", async () => {
70
+ if (this.target.kind === "alertmanager") {
71
+ const status = await this.status();
72
+ if (status === undefined) return undefined;
73
+ const original = isObject(status.config) ? status.config.original : undefined;
74
+ if (typeof original !== "string") throw new PromApiError(502, "GET", this.statusPath, "the status has no config.original");
75
+ return { text: original, address: this.statusPath, remarshalled: true };
76
+ }
77
+ const path = "/api/v1/alerts";
78
+ const text = await this.client.getText(path);
79
+ if (text === undefined) return undefined;
80
+ const doc = loadPrometheusYaml(text);
81
+ if (!isObject(doc) || typeof doc.alertmanager_config !== "string") {
82
+ throw new PromApiError(502, "GET", path, "the answer has no alertmanager_config");
83
+ }
84
+ const templateFiles = isObject(doc.template_files)
85
+ ? Object.fromEntries(Object.entries(doc.template_files).map(([k, v]) => [k, String(v)]))
86
+ : undefined;
87
+ return {
88
+ text: doc.alertmanager_config,
89
+ address: path,
90
+ remarshalled: false,
91
+ ...(templateFiles && Object.keys(templateFiles).length > 0 ? { templateFiles } : {}),
92
+ };
93
+ });
94
+ }
95
+
96
+ /** Cluster status and version. Never throws: a failed read is no health, since health is an attribute, not presence. */
97
+ async health(): Promise<AlertmanagerHealth | undefined> {
98
+ let status: Record<string, unknown> | undefined;
99
+ try {
100
+ status = await this.status();
101
+ } catch {
102
+ return undefined;
103
+ }
104
+ if (!status) return undefined;
105
+ const cluster = isObject(status.cluster) ? status.cluster : {};
106
+ const version = isObject(status.versionInfo) ? status.versionInfo.version : undefined;
107
+ return {
108
+ ...(typeof cluster.status === "string" ? { cluster: cluster.status } : {}),
109
+ ...(Array.isArray(cluster.peers) ? { peers: cluster.peers.length } : {}),
110
+ ...(typeof version === "string" ? { version } : {}),
111
+ };
112
+ }
113
+
114
+ /** Upload a config (Mimir and Cortex, `POST /api/v1/alerts`, 201). */
115
+ async setConfig(config: string, templateFiles: Record<string, string> = {}): Promise<void> {
116
+ if (this.target.kind === "alertmanager") {
117
+ throw new Error(`a plain Alertmanager (${this.target.source}) has no config API; it reads alertmanager.yml from disk`);
118
+ }
119
+ const body = dump({ template_files: templateFiles, alertmanager_config: config }, { lineWidth: -1, noRefs: true });
120
+ await this.client.send("POST", "/api/v1/alerts", { text: body, contentType: "application/yaml" });
121
+ }
122
+ }