@intentius/chant-lexicon-grafana 0.98.0 → 0.100.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 (532) hide show
  1. package/README.md +24 -8
  2. package/dist/actions/grafana.d.ts +24 -0
  3. package/dist/actions/grafana.d.ts.map +1 -0
  4. package/dist/actions/index.d.ts +2 -0
  5. package/dist/actions/index.d.ts.map +1 -0
  6. package/dist/alerting-build.d.ts +128 -0
  7. package/dist/alerting-build.d.ts.map +1 -0
  8. package/dist/alerting.d.ts +273 -0
  9. package/dist/alerting.d.ts.map +1 -0
  10. package/dist/annotations.d.ts +61 -0
  11. package/dist/annotations.d.ts.map +1 -0
  12. package/dist/api/apply.d.ts +160 -0
  13. package/dist/api/apply.d.ts.map +1 -0
  14. package/dist/api/bind.d.ts +32 -0
  15. package/dist/api/bind.d.ts.map +1 -0
  16. package/dist/api/client.d.ts +76 -0
  17. package/dist/api/client.d.ts.map +1 -0
  18. package/dist/api/converge.d.ts +33 -0
  19. package/dist/api/converge.d.ts.map +1 -0
  20. package/dist/api/dashboards.d.ts +102 -0
  21. package/dist/api/dashboards.d.ts.map +1 -0
  22. package/dist/api/datasources.d.ts +26 -0
  23. package/dist/api/datasources.d.ts.map +1 -0
  24. package/dist/api/fake-grafana-writes.d.ts +41 -0
  25. package/dist/api/fake-grafana-writes.d.ts.map +1 -0
  26. package/dist/api/fake-grafana.d.ts +63 -0
  27. package/dist/api/fake-grafana.d.ts.map +1 -0
  28. package/dist/api/folders.d.ts +146 -0
  29. package/dist/api/folders.d.ts.map +1 -0
  30. package/dist/api/library-panels.d.ts +52 -0
  31. package/dist/api/library-panels.d.ts.map +1 -0
  32. package/dist/build.d.ts +55 -6
  33. package/dist/build.d.ts.map +1 -1
  34. package/dist/catalog.d.ts +1 -1
  35. package/dist/catalog.d.ts.map +1 -1
  36. package/dist/codegen/docs.d.ts.map +1 -1
  37. package/dist/codegen/generate.d.ts +7 -1
  38. package/dist/codegen/generate.d.ts.map +1 -1
  39. package/dist/codegen/schema-types.d.ts +3 -3
  40. package/dist/codegen/schema-types.d.ts.map +1 -1
  41. package/dist/composites/agent-dashboard.d.ts.map +1 -1
  42. package/dist/composites/catalog.d.ts.map +1 -1
  43. package/dist/composites/index.d.ts +4 -2
  44. package/dist/composites/index.d.ts.map +1 -1
  45. package/dist/composites/red-dashboard.d.ts +21 -2
  46. package/dist/composites/red-dashboard.d.ts.map +1 -1
  47. package/dist/composites/shared.d.ts +11 -3
  48. package/dist/composites/shared.d.ts.map +1 -1
  49. package/dist/composites/slo-alert-rules.d.ts +92 -0
  50. package/dist/composites/slo-alert-rules.d.ts.map +1 -0
  51. package/dist/config.d.ts +111 -0
  52. package/dist/config.d.ts.map +1 -0
  53. package/dist/contact-point-secrets.d.ts +17 -0
  54. package/dist/contact-point-secrets.d.ts.map +1 -0
  55. package/dist/dashboard.d.ts +51 -9
  56. package/dist/dashboard.d.ts.map +1 -1
  57. package/dist/datasource-refs.d.ts +99 -0
  58. package/dist/datasource-refs.d.ts.map +1 -0
  59. package/dist/datasource-settings.d.ts +834 -0
  60. package/dist/datasource-settings.d.ts.map +1 -0
  61. package/dist/datasource.d.ts +82 -11
  62. package/dist/datasource.d.ts.map +1 -1
  63. package/dist/deep-observe-hooks.d.ts +64 -0
  64. package/dist/deep-observe-hooks.d.ts.map +1 -0
  65. package/dist/deep-observe.d.ts +47 -0
  66. package/dist/deep-observe.d.ts.map +1 -0
  67. package/dist/describe-resources.d.ts +50 -0
  68. package/dist/describe-resources.d.ts.map +1 -0
  69. package/dist/detect.d.ts +32 -3
  70. package/dist/detect.d.ts.map +1 -1
  71. package/dist/export-resources.d.ts +38 -0
  72. package/dist/export-resources.d.ts.map +1 -0
  73. package/dist/folder.d.ts +68 -0
  74. package/dist/folder.d.ts.map +1 -0
  75. package/dist/import/alerting.d.ts +40 -0
  76. package/dist/import/alerting.d.ts.map +1 -0
  77. package/dist/import/edits.d.ts +47 -0
  78. package/dist/import/edits.d.ts.map +1 -0
  79. package/dist/import/embedded.d.ts +14 -0
  80. package/dist/import/embedded.d.ts.map +1 -0
  81. package/dist/import/generator.d.ts +60 -0
  82. package/dist/import/generator.d.ts.map +1 -0
  83. package/dist/import/live-export.d.ts +108 -0
  84. package/dist/import/live-export.d.ts.map +1 -0
  85. package/dist/import/mappings.d.ts +89 -0
  86. package/dist/import/mappings.d.ts.map +1 -0
  87. package/dist/import/model.d.ts +105 -0
  88. package/dist/import/model.d.ts.map +1 -0
  89. package/dist/import/normalize-alerting.d.ts +26 -0
  90. package/dist/import/normalize-alerting.d.ts.map +1 -0
  91. package/dist/import/normalize.d.ts +74 -0
  92. package/dist/import/normalize.d.ts.map +1 -0
  93. package/dist/import/parser.d.ts +125 -0
  94. package/dist/import/parser.d.ts.map +1 -0
  95. package/dist/import/report.d.ts +26 -0
  96. package/dist/import/report.d.ts.map +1 -0
  97. package/dist/import/testdata/fixtures.d.ts +41 -0
  98. package/dist/import/testdata/fixtures.d.ts.map +1 -0
  99. package/dist/import/v2.d.ts +73 -0
  100. package/dist/import/v2.d.ts.map +1 -0
  101. package/dist/index.d.ts +31 -12
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/init-templates.d.ts +27 -0
  104. package/dist/init-templates.d.ts.map +1 -0
  105. package/dist/integrity.json +22 -12
  106. package/dist/k8s.d.ts +107 -0
  107. package/dist/k8s.d.ts.map +1 -0
  108. package/dist/lint/audit-catalog.d.ts +2 -1
  109. package/dist/lint/audit-catalog.d.ts.map +1 -1
  110. package/dist/lint/post-synth/graf101.d.ts +2 -2
  111. package/dist/lint/post-synth/graf102.d.ts +1 -1
  112. package/dist/lint/post-synth/graf104.d.ts +2 -2
  113. package/dist/lint/post-synth/graf106.d.ts +2 -2
  114. package/dist/lint/post-synth/graf107.d.ts +1 -1
  115. package/dist/lint/post-synth/graf108.d.ts +8 -0
  116. package/dist/lint/post-synth/graf108.d.ts.map +1 -0
  117. package/dist/lint/post-synth/graf109.d.ts +8 -0
  118. package/dist/lint/post-synth/graf109.d.ts.map +1 -0
  119. package/dist/lint/post-synth/graf110.d.ts +8 -0
  120. package/dist/lint/post-synth/graf110.d.ts.map +1 -0
  121. package/dist/lint/post-synth/graf111.d.ts +8 -0
  122. package/dist/lint/post-synth/graf111.d.ts.map +1 -0
  123. package/dist/lint/post-synth/graf112.d.ts +8 -0
  124. package/dist/lint/post-synth/graf112.d.ts.map +1 -0
  125. package/dist/lint/post-synth/graf113.d.ts +8 -0
  126. package/dist/lint/post-synth/graf113.d.ts.map +1 -0
  127. package/dist/lint/post-synth/graf114.d.ts +8 -0
  128. package/dist/lint/post-synth/graf114.d.ts.map +1 -0
  129. package/dist/lint/post-synth/graf115.d.ts +8 -0
  130. package/dist/lint/post-synth/graf115.d.ts.map +1 -0
  131. package/dist/lint/post-synth/grafana-helpers.d.ts +7 -5
  132. package/dist/lint/post-synth/grafana-helpers.d.ts.map +1 -1
  133. package/dist/lint/post-synth/index.d.ts.map +1 -1
  134. package/dist/lint/rules/literal-secret.d.ts +7 -3
  135. package/dist/lint/rules/literal-secret.d.ts.map +1 -1
  136. package/dist/lsp/hover.d.ts.map +1 -1
  137. package/dist/manifest.json +1 -1
  138. package/dist/members.d.ts +27 -0
  139. package/dist/members.d.ts.map +1 -0
  140. package/dist/meta.json +205 -0
  141. package/dist/okf/index.md +53 -4
  142. package/dist/okf/rules/GRAF002.md +3 -2
  143. package/dist/okf/rules/GRAF101.md +3 -2
  144. package/dist/okf/rules/GRAF102.md +1 -0
  145. package/dist/okf/rules/GRAF104.md +2 -2
  146. package/dist/okf/rules/GRAF106.md +2 -2
  147. package/dist/okf/rules/GRAF108.md +11 -0
  148. package/dist/okf/rules/GRAF109.md +15 -0
  149. package/dist/okf/rules/GRAF110.md +11 -0
  150. package/dist/okf/rules/GRAF111.md +11 -0
  151. package/dist/okf/rules/GRAF112.md +16 -0
  152. package/dist/okf/rules/GRAF113.md +16 -0
  153. package/dist/okf/rules/GRAF114.md +11 -0
  154. package/dist/okf/rules/GRAF115.md +11 -0
  155. package/dist/okf/types/AdhocVariable.md +9 -0
  156. package/dist/okf/types/AlertListPanel.md +9 -0
  157. package/dist/okf/types/AlertQuery.md +9 -0
  158. package/dist/okf/types/AlertRule.md +9 -0
  159. package/dist/okf/types/AlertRuleGroup.md +9 -0
  160. package/dist/okf/types/AzureMonitorQuery.md +9 -0
  161. package/dist/okf/types/BarChartPanel.md +9 -0
  162. package/dist/okf/types/BarGaugePanel.md +9 -0
  163. package/dist/okf/types/BigQueryQuery.md +9 -0
  164. package/dist/okf/types/CanvasPanel.md +9 -0
  165. package/dist/okf/types/ClassicConditionsExpression.md +9 -0
  166. package/dist/okf/types/CloudMonitoringQuery.md +9 -0
  167. package/dist/okf/types/CloudWatchQuery.md +9 -0
  168. package/dist/okf/types/ContactPoint.md +14 -0
  169. package/dist/okf/types/Dashboard.md +2 -1
  170. package/dist/okf/types/Datasource.md +3 -2
  171. package/dist/okf/types/DatasourceProvisioning.md +9 -0
  172. package/dist/okf/types/ElasticsearchQuery.md +9 -0
  173. package/dist/okf/types/ExternalDatasource.md +15 -0
  174. package/dist/okf/types/FlameGraphPanel.md +9 -0
  175. package/dist/okf/types/Folder.md +9 -0
  176. package/dist/okf/types/GeomapPanel.md +9 -0
  177. package/dist/okf/types/GroupByVariable.md +9 -0
  178. package/dist/okf/types/HistogramPanel.md +9 -0
  179. package/dist/okf/types/MSSQLQuery.md +9 -0
  180. package/dist/okf/types/MathExpression.md +9 -0
  181. package/dist/okf/types/MuteTiming.md +13 -0
  182. package/dist/okf/types/MySQLQuery.md +9 -0
  183. package/dist/okf/types/NodeGraphPanel.md +9 -0
  184. package/dist/okf/types/NotificationPolicy.md +9 -0
  185. package/dist/okf/types/NotificationTemplate.md +9 -0
  186. package/dist/okf/types/PieChartPanel.md +9 -0
  187. package/dist/okf/types/PostgresQuery.md +9 -0
  188. package/dist/okf/types/PyroscopeQuery.md +9 -0
  189. package/dist/okf/types/ReduceExpression.md +9 -0
  190. package/dist/okf/types/ResampleExpression.md +9 -0
  191. package/dist/okf/types/SqlExpression.md +9 -0
  192. package/dist/okf/types/StateTimelinePanel.md +9 -0
  193. package/dist/okf/types/StatusHistoryPanel.md +9 -0
  194. package/dist/okf/types/SwitchVariable.md +9 -0
  195. package/dist/okf/types/ThresholdExpression.md +9 -0
  196. package/dist/okf/types/TrendPanel.md +9 -0
  197. package/dist/okf/types/XYChartPanel.md +9 -0
  198. package/dist/op/activities/grafana-apply.d.ts +86 -0
  199. package/dist/op/activities/grafana-apply.d.ts.map +1 -0
  200. package/dist/op/activities/index.d.ts +9 -0
  201. package/dist/op/activities/index.d.ts.map +1 -0
  202. package/dist/op/builders.d.ts +24 -0
  203. package/dist/op/builders.d.ts.map +1 -0
  204. package/dist/ownership.d.ts +78 -0
  205. package/dist/ownership.d.ts.map +1 -0
  206. package/dist/panel-options.d.ts +65 -0
  207. package/dist/panel-options.d.ts.map +1 -0
  208. package/dist/panels.d.ts +46 -9
  209. package/dist/panels.d.ts.map +1 -1
  210. package/dist/pin.d.ts +29 -7
  211. package/dist/pin.d.ts.map +1 -1
  212. package/dist/plugin.d.ts.map +1 -1
  213. package/dist/promql-check.d.ts +51 -0
  214. package/dist/promql-check.d.ts.map +1 -0
  215. package/dist/query-models.d.ts +95 -0
  216. package/dist/query-models.d.ts.map +1 -0
  217. package/dist/query.d.ts +116 -18
  218. package/dist/query.d.ts.map +1 -1
  219. package/dist/rules/graf101.ts +3 -3
  220. package/dist/rules/graf102.ts +1 -1
  221. package/dist/rules/graf104.ts +3 -3
  222. package/dist/rules/graf106.ts +3 -3
  223. package/dist/rules/graf107.ts +1 -1
  224. package/dist/rules/graf108.ts +17 -0
  225. package/dist/rules/graf109.ts +17 -0
  226. package/dist/rules/graf110.ts +17 -0
  227. package/dist/rules/graf111.ts +17 -0
  228. package/dist/rules/graf112.ts +17 -0
  229. package/dist/rules/graf113.ts +17 -0
  230. package/dist/rules/graf114.ts +17 -0
  231. package/dist/rules/graf115.ts +17 -0
  232. package/dist/rules/grafana-helpers.ts +27 -10
  233. package/dist/rules/literal-secret.ts +60 -6
  234. package/dist/schema/azuremonitor.gen.d.ts +354 -0
  235. package/dist/schema/azuremonitor.gen.d.ts.map +1 -0
  236. package/dist/schema/barchart.gen.d.ts +198 -0
  237. package/dist/schema/barchart.gen.d.ts.map +1 -0
  238. package/dist/schema/bargauge.gen.d.ts +97 -0
  239. package/dist/schema/bargauge.gen.d.ts.map +1 -0
  240. package/dist/schema/bigquery.gen.d.ts +93 -0
  241. package/dist/schema/bigquery.gen.d.ts.map +1 -0
  242. package/dist/schema/canvas.gen.d.ts +152 -0
  243. package/dist/schema/canvas.gen.d.ts.map +1 -0
  244. package/dist/schema/cloudwatch.gen.d.ts +394 -0
  245. package/dist/schema/cloudwatch.gen.d.ts.map +1 -0
  246. package/dist/schema/dashboard.gen.d.ts +196 -21
  247. package/dist/schema/dashboard.gen.d.ts.map +1 -1
  248. package/dist/schema/elasticsearch.gen.d.ts +444 -0
  249. package/dist/schema/elasticsearch.gen.d.ts.map +1 -0
  250. package/dist/schema/expr.gen.d.ts +606 -0
  251. package/dist/schema/expr.gen.d.ts.map +1 -0
  252. package/dist/schema/gauge.gen.d.ts.map +1 -1
  253. package/dist/schema/geomap.gen.d.ts +100 -0
  254. package/dist/schema/geomap.gen.d.ts.map +1 -0
  255. package/dist/schema/googlecloudmonitoring.gen.d.ts +312 -0
  256. package/dist/schema/googlecloudmonitoring.gen.d.ts.map +1 -0
  257. package/dist/schema/grafanapyroscope.gen.d.ts +60 -0
  258. package/dist/schema/grafanapyroscope.gen.d.ts.map +1 -0
  259. package/dist/schema/heatmap.gen.d.ts.map +1 -1
  260. package/dist/schema/histogram.gen.d.ts +132 -0
  261. package/dist/schema/histogram.gen.d.ts.map +1 -0
  262. package/dist/schema/index.d.ts +18 -0
  263. package/dist/schema/index.d.ts.map +1 -1
  264. package/dist/schema/logs.gen.d.ts +1 -0
  265. package/dist/schema/logs.gen.d.ts.map +1 -1
  266. package/dist/schema/loki.gen.d.ts.map +1 -1
  267. package/dist/schema/nodegraph.gen.d.ts +49 -0
  268. package/dist/schema/nodegraph.gen.d.ts.map +1 -0
  269. package/dist/schema/piechart.gen.d.ts +127 -0
  270. package/dist/schema/piechart.gen.d.ts.map +1 -0
  271. package/dist/schema/prometheus.gen.d.ts.map +1 -1
  272. package/dist/schema/stat.gen.d.ts.map +1 -1
  273. package/dist/schema/statetimeline.gen.d.ts +124 -0
  274. package/dist/schema/statetimeline.gen.d.ts.map +1 -0
  275. package/dist/schema/statushistory.gen.d.ts +114 -0
  276. package/dist/schema/statushistory.gen.d.ts.map +1 -0
  277. package/dist/schema/table.gen.d.ts +35 -3
  278. package/dist/schema/table.gen.d.ts.map +1 -1
  279. package/dist/schema/tempo.gen.d.ts.map +1 -1
  280. package/dist/schema/text.gen.d.ts.map +1 -1
  281. package/dist/schema/timeseries.gen.d.ts +5 -1
  282. package/dist/schema/timeseries.gen.d.ts.map +1 -1
  283. package/dist/schema/trend.gen.d.ts +180 -0
  284. package/dist/schema/trend.gen.d.ts.map +1 -0
  285. package/dist/schema/xychart.gen.d.ts +145 -0
  286. package/dist/schema/xychart.gen.d.ts.map +1 -0
  287. package/dist/schema-validate.d.ts +39 -4
  288. package/dist/schema-validate.d.ts.map +1 -1
  289. package/dist/serializer.d.ts +11 -3
  290. package/dist/serializer.d.ts.map +1 -1
  291. package/dist/skill-defs.d.ts.map +1 -1
  292. package/dist/skills/chant-grafana-alerting.md +49 -0
  293. package/dist/skills/chant-grafana-operations.md +108 -0
  294. package/dist/skills/chant-grafana-provisioning.md +3 -3
  295. package/dist/skills/chant-grafana.md +15 -8
  296. package/dist/spec/fetch-units-cli.d.ts +3 -0
  297. package/dist/spec/fetch-units-cli.d.ts.map +1 -0
  298. package/dist/spec/overlay.d.ts +43 -0
  299. package/dist/spec/overlay.d.ts.map +1 -0
  300. package/dist/spec/schemas.d.ts +21 -13
  301. package/dist/spec/schemas.d.ts.map +1 -1
  302. package/dist/spec/schemas.gen.d.ts +3 -0
  303. package/dist/spec/schemas.gen.d.ts.map +1 -0
  304. package/dist/spec/units.d.ts +35 -0
  305. package/dist/spec/units.d.ts.map +1 -0
  306. package/dist/spec/units.gen.d.ts +2 -0
  307. package/dist/spec/units.gen.d.ts.map +1 -0
  308. package/dist/transformations.d.ts +459 -0
  309. package/dist/transformations.d.ts.map +1 -0
  310. package/dist/util.d.ts +6 -0
  311. package/dist/util.d.ts.map +1 -1
  312. package/dist/validate-alerting.d.ts +65 -0
  313. package/dist/validate-alerting.d.ts.map +1 -0
  314. package/dist/validate-output.d.ts +62 -9
  315. package/dist/validate-output.d.ts.map +1 -1
  316. package/dist/validate.d.ts.map +1 -1
  317. package/dist/validation.d.ts +14 -0
  318. package/dist/validation.d.ts.map +1 -0
  319. package/dist/variables.d.ts +109 -7
  320. package/dist/variables.d.ts.map +1 -1
  321. package/package.json +12 -4
  322. package/src/actions/actions.test.ts +24 -0
  323. package/src/actions/grafana.ts +40 -0
  324. package/src/actions/index.ts +1 -0
  325. package/src/alerting-build.ts +376 -0
  326. package/src/alerting.e2e.test.ts +212 -0
  327. package/src/alerting.test.ts +272 -0
  328. package/src/alerting.ts +435 -0
  329. package/src/annotations.ts +84 -0
  330. package/src/api/apply.test.ts +256 -0
  331. package/src/api/apply.ts +394 -0
  332. package/src/api/bind.ts +58 -0
  333. package/src/api/client.ts +148 -0
  334. package/src/api/converge.ts +63 -0
  335. package/src/api/dashboards.ts +247 -0
  336. package/src/api/datasources.ts +33 -0
  337. package/src/api/fake-grafana-writes.ts +201 -0
  338. package/src/api/fake-grafana.ts +190 -0
  339. package/src/api/folders.ts +339 -0
  340. package/src/api/library-panels.ts +101 -0
  341. package/src/build.test.ts +351 -10
  342. package/src/build.ts +294 -55
  343. package/src/catalog.ts +104 -2
  344. package/src/codegen/docs.ts +50 -10
  345. package/src/codegen/generate.ts +27 -1
  346. package/src/codegen/schema-types.ts +16 -6
  347. package/src/composites/agent-dashboard.ts +6 -3
  348. package/src/composites/catalog.test.ts +2 -2
  349. package/src/composites/catalog.ts +82 -0
  350. package/src/composites/composites.test.ts +73 -9
  351. package/src/composites/index.ts +4 -2
  352. package/src/composites/queries.e2e.test.ts +374 -0
  353. package/src/composites/red-dashboard.ts +83 -12
  354. package/src/composites/shared.ts +14 -2
  355. package/src/composites/slo-alert-rules.ts +149 -0
  356. package/src/config.test.ts +61 -0
  357. package/src/config.ts +138 -0
  358. package/src/contact-point-secrets.ts +63 -0
  359. package/src/dashboard.ts +68 -9
  360. package/src/datasource-refs.test.ts +56 -0
  361. package/src/datasource-refs.ts +213 -0
  362. package/src/datasource-settings.test.ts +106 -0
  363. package/src/datasource-settings.ts +941 -0
  364. package/src/datasource.ts +123 -11
  365. package/src/deep-observe-hooks.ts +267 -0
  366. package/src/deep-observe.test.ts +311 -0
  367. package/src/deep-observe.ts +187 -0
  368. package/src/describe-resources.test.ts +271 -0
  369. package/src/describe-resources.ts +244 -0
  370. package/src/detect.ts +82 -5
  371. package/src/export-resources.test.ts +129 -0
  372. package/src/export-resources.ts +84 -0
  373. package/src/folder.ts +118 -0
  374. package/src/generated/lexicon-grafana.json +205 -0
  375. package/src/import/alerting-roundtrip.test.ts +161 -0
  376. package/src/import/alerting.ts +500 -0
  377. package/src/import/edits.ts +95 -0
  378. package/src/import/embedded.test.ts +45 -0
  379. package/src/import/embedded.ts +47 -0
  380. package/src/import/generated-types.e2e.test.ts +130 -0
  381. package/src/import/generator.test.ts +210 -0
  382. package/src/import/generator.ts +463 -0
  383. package/src/import/live-export.ts +264 -0
  384. package/src/import/mappings.ts +336 -0
  385. package/src/import/model.ts +117 -0
  386. package/src/import/normalize-alerting.ts +101 -0
  387. package/src/import/normalize.test.ts +117 -0
  388. package/src/import/normalize.ts +393 -0
  389. package/src/import/parser.test.ts +516 -0
  390. package/src/import/parser.ts +1387 -0
  391. package/src/import/report.ts +62 -0
  392. package/src/import/roundtrip.test.ts +563 -0
  393. package/src/import/testdata/fixtures.ts +137 -0
  394. package/src/import/v2.test.ts +292 -0
  395. package/src/import/v2.ts +804 -0
  396. package/src/import.e2e.test.ts +31 -84
  397. package/src/import.test.ts +9 -3
  398. package/src/index.ts +308 -14
  399. package/src/init-templates.test.ts +74 -0
  400. package/src/init-templates.ts +349 -0
  401. package/src/k8s.test.ts +98 -0
  402. package/src/k8s.ts +197 -0
  403. package/src/lint/audit-catalog.ts +68 -3
  404. package/src/lint/core-lint.test.ts +43 -0
  405. package/src/lint/post-synth/graf101.ts +3 -3
  406. package/src/lint/post-synth/graf102.ts +1 -1
  407. package/src/lint/post-synth/graf104.ts +3 -3
  408. package/src/lint/post-synth/graf106.ts +3 -3
  409. package/src/lint/post-synth/graf107.ts +1 -1
  410. package/src/lint/post-synth/graf108.ts +17 -0
  411. package/src/lint/post-synth/graf109.ts +17 -0
  412. package/src/lint/post-synth/graf110.ts +17 -0
  413. package/src/lint/post-synth/graf111.ts +17 -0
  414. package/src/lint/post-synth/graf112.ts +17 -0
  415. package/src/lint/post-synth/graf113.ts +17 -0
  416. package/src/lint/post-synth/graf114.ts +17 -0
  417. package/src/lint/post-synth/graf115.ts +17 -0
  418. package/src/lint/post-synth/grafana-helpers.ts +27 -10
  419. package/src/lint/post-synth/index.ts +16 -0
  420. package/src/lint/post-synth/post-synth.test.ts +333 -12
  421. package/src/lint/rules/literal-secret.ts +60 -6
  422. package/src/lint/rules/rules.test.ts +27 -0
  423. package/src/load-cost.test.ts +35 -0
  424. package/src/lsp/completions.test.ts +5 -0
  425. package/src/lsp/hover.test.ts +7 -0
  426. package/src/lsp/hover.ts +8 -1
  427. package/src/members.ts +68 -0
  428. package/src/observe.e2e.test.ts +255 -0
  429. package/src/op/activities/grafana-apply.e2e.test.ts +184 -0
  430. package/src/op/activities/grafana-apply.test.ts +243 -0
  431. package/src/op/activities/grafana-apply.ts +197 -0
  432. package/src/op/activities/index.ts +8 -0
  433. package/src/op/builders.test.ts +40 -0
  434. package/src/op/builders.ts +34 -0
  435. package/src/ownership.ts +122 -0
  436. package/src/panel-options.ts +66 -0
  437. package/src/panels.test.ts +53 -0
  438. package/src/panels.ts +148 -10
  439. package/src/pin.ts +69 -6
  440. package/src/plugin.test.ts +12 -2
  441. package/src/plugin.ts +75 -0
  442. package/src/promql-check.test.ts +108 -0
  443. package/src/promql-check.ts +193 -0
  444. package/src/query-models.ts +94 -0
  445. package/src/query.test.ts +166 -0
  446. package/src/query.ts +190 -20
  447. package/src/schema/azuremonitor.gen.ts +381 -0
  448. package/src/schema/barchart.gen.ts +221 -0
  449. package/src/schema/bargauge.gen.ts +111 -0
  450. package/src/schema/bigquery.gen.ts +110 -0
  451. package/src/schema/canvas.gen.ts +176 -0
  452. package/src/schema/cloudwatch.gen.ts +423 -0
  453. package/src/schema/dashboard.gen.ts +206 -22
  454. package/src/schema/elasticsearch.gen.ts +508 -0
  455. package/src/schema/expr.gen.ts +618 -0
  456. package/src/schema/gauge.gen.ts +2 -1
  457. package/src/schema/geomap.gen.ts +112 -0
  458. package/src/schema/googlecloudmonitoring.gen.ts +330 -0
  459. package/src/schema/grafanapyroscope.gen.ts +66 -0
  460. package/src/schema/heatmap.gen.ts +2 -1
  461. package/src/schema/histogram.gen.ts +151 -0
  462. package/src/schema/index.ts +18 -0
  463. package/src/schema/logs.gen.ts +3 -1
  464. package/src/schema/loki.gen.ts +2 -1
  465. package/src/schema/nodegraph.gen.ts +58 -0
  466. package/src/schema/piechart.gen.ts +146 -0
  467. package/src/schema/prometheus.gen.ts +2 -1
  468. package/src/schema/stat.gen.ts +2 -1
  469. package/src/schema/statetimeline.gen.ts +143 -0
  470. package/src/schema/statushistory.gen.ts +132 -0
  471. package/src/schema/table.gen.ts +41 -4
  472. package/src/schema/tempo.gen.ts +2 -1
  473. package/src/schema/text.gen.ts +2 -1
  474. package/src/schema/timeseries.gen.ts +7 -2
  475. package/src/schema/trend.gen.ts +208 -0
  476. package/src/schema/xychart.gen.ts +167 -0
  477. package/src/schema-overlay.test.ts +282 -0
  478. package/src/schema-validate.ts +199 -57
  479. package/src/schema.test.ts +4 -2
  480. package/src/serializer.test.ts +13 -1
  481. package/src/serializer.ts +11 -3
  482. package/src/skill-defs.ts +34 -0
  483. package/src/skills/chant-grafana-alerting.md +49 -0
  484. package/src/skills/chant-grafana-operations.md +108 -0
  485. package/src/skills/chant-grafana-provisioning.md +3 -3
  486. package/src/skills/chant-grafana.md +15 -8
  487. package/src/spec/fetch-cli.ts +3 -3
  488. package/src/spec/fetch-units-cli.ts +37 -0
  489. package/src/spec/overlay/canvas.overlay.json +142 -0
  490. package/src/spec/overlay/dashboard.overlay.json +378 -0
  491. package/src/spec/overlay/expr.overlay.json +14 -0
  492. package/src/spec/overlay/geomap.overlay.json +35 -0
  493. package/src/spec/overlay/logs.overlay.json +14 -0
  494. package/src/spec/overlay/nodegraph.overlay.json +31 -0
  495. package/src/spec/overlay/piechart.overlay.json +17 -0
  496. package/src/spec/overlay/table.overlay.json +103 -0
  497. package/src/spec/overlay/timeseries.overlay.json +21 -0
  498. package/src/spec/overlay/trend.overlay.json +17 -0
  499. package/src/spec/overlay/xychart.overlay.json +16 -0
  500. package/src/spec/overlay.ts +105 -0
  501. package/src/spec/schemas/azuremonitor.jsonschema.json +679 -0
  502. package/src/spec/schemas/barchart.jsonschema.json +419 -0
  503. package/src/spec/schemas/bargauge.jsonschema.json +224 -0
  504. package/src/spec/schemas/bigquery.jsonschema.json +290 -0
  505. package/src/spec/schemas/canvas.jsonschema.json +356 -0
  506. package/src/spec/schemas/cloudwatch.jsonschema.json +721 -0
  507. package/src/spec/schemas/dashboardv2.jsonschema.json +3387 -0
  508. package/src/spec/schemas/elasticsearch.jsonschema.json +1695 -0
  509. package/src/spec/schemas/expr.jsonschema.json +987 -0
  510. package/src/spec/schemas/geomap.jsonschema.json +220 -0
  511. package/src/spec/schemas/googlecloudmonitoring.jsonschema.json +488 -0
  512. package/src/spec/schemas/grafanapyroscope.jsonschema.json +93 -0
  513. package/src/spec/schemas/histogram.jsonschema.json +308 -0
  514. package/src/spec/schemas/nodegraph.jsonschema.json +77 -0
  515. package/src/spec/schemas/piechart.jsonschema.json +275 -0
  516. package/src/spec/schemas/statetimeline.jsonschema.json +324 -0
  517. package/src/spec/schemas/statushistory.jsonschema.json +292 -0
  518. package/src/spec/schemas/trend.jsonschema.json +449 -0
  519. package/src/spec/schemas/xychart.jsonschema.json +428 -0
  520. package/src/spec/schemas.gen.ts +40 -0
  521. package/src/spec/schemas.ts +30 -16
  522. package/src/spec/units.gen.ts +281 -0
  523. package/src/spec/units.ts +77 -0
  524. package/src/transformations.test.ts +65 -0
  525. package/src/transformations.ts +574 -0
  526. package/src/util.ts +8 -0
  527. package/src/validate-alerting.test.ts +255 -0
  528. package/src/validate-alerting.ts +430 -0
  529. package/src/validate-output.ts +427 -104
  530. package/src/validate.ts +28 -0
  531. package/src/validation.ts +27 -0
  532. package/src/variables.ts +120 -8
@@ -1,17 +1,36 @@
1
1
  /**
2
2
  * Validate built Grafana JSON against the vendored schemas at
3
- * `GRAFANA_SCHEMA_PIN`, offline.
3
+ * `GRAFANA_SCHEMA_PIN`, with the correction overlay applied, offline.
4
4
  *
5
5
  * The dashboard is checked against the dashboard schema as it stands. Panel
6
6
  * options, `fieldConfig.defaults.custom` and each query are checked against
7
7
  * their plugin's schema with every `required` list removed, because Grafana
8
- * fills in options a panel leaves out: what still fails is a key the schema
9
- * doesn't have or a value it doesn't allow.
8
+ * fills in options a panel leaves out.
9
+ *
10
+ * Two kinds of problem come back. A value the schema does not allow (a
11
+ * wrong type, an enum value it does not list, a required field missing) is
12
+ * an error. A key the pinned schema does not know is a warning: Grafana
13
+ * adds fields every minor release and keeps what it does not understand, so
14
+ * an unknown key is more often a newer Grafana than a mistake. To tell the
15
+ * two apart each value is validated twice, once with every object opened
16
+ * (`additionalProperties: false` dropped; what fails is an error) and once
17
+ * as pinned (what fails only for an unknown key is a warning).
18
+ *
19
+ * An export shared externally embeds its library panels in `__elements`;
20
+ * each element's `model` is checked the same way as a panel on the grid.
21
+ *
22
+ * `oneOf` is read as `anyOf` throughout. The CUE these schemas come from
23
+ * has disjunctions, which accept a value matching any branch; cog writes
24
+ * them as `oneOf`, which rejects a value matching two, and loosely
25
+ * discriminated unions (a table's `cellOptions: { type: "auto" }` matches
26
+ * several variants) match more than one.
10
27
  */
11
28
 
12
- import Ajv, { type ValidateFunction, type ErrorObject } from "ajv";
29
+ import { createRequire } from "module";
30
+ import type AjvClass from "ajv";
31
+ import type { ValidateFunction, ErrorObject } from "ajv";
13
32
  import type { SchemaName } from "./pin";
14
- import { loadSchema } from "./spec/schemas";
33
+ import { SCHEMA_JSON } from "./spec/schemas.gen";
15
34
  import { panelDefinitionFor } from "./panels";
16
35
  import { queryDefinitionFor } from "./query";
17
36
 
@@ -19,35 +38,94 @@ export interface SchemaProblem {
19
38
  /** JSON pointer into the dashboard. */
20
39
  path: string;
21
40
  message: string;
41
+ /** `error` for a value the schema does not allow, `warning` for a key it does not know. */
42
+ severity: "error" | "warning";
22
43
  }
23
44
 
24
- let ajv: Ajv | undefined;
25
- function getAjv(): Ajv {
26
- ajv ??= new Ajv({ strict: false, allErrors: true, validateFormats: false });
45
+ let ajv: AjvClass | undefined;
46
+ let ajvError: string | undefined;
47
+
48
+ /**
49
+ * ajv, loaded on first use.
50
+ *
51
+ * Every `chant build`, `chant lint` and `chant list` loads the plugin, and the
52
+ * plugin's post-synth barrel reaches this module, so a top-level import would
53
+ * put ajv on every run that has grafana installed, validating or not.
54
+ * `check()` is synchronous, so `await import()` is not available;
55
+ * `createRequire` is (the same as the cedar lexicon's wasm, and ajv is
56
+ * CommonJS). Where it cannot load (a bundle with no `node_modules` beside
57
+ * it), {@link schemaValidationUnavailable} says why and GRAF107 reports that
58
+ * instead of throwing.
59
+ */
60
+ function getAjv(): AjvClass | undefined {
61
+ if (ajv || ajvError) return ajv;
62
+ try {
63
+ const mod = createRequire(import.meta.url)("ajv") as { default?: typeof AjvClass } & typeof AjvClass;
64
+ const Ajv = mod.default ?? mod;
65
+ ajv = new Ajv({ strict: false, allErrors: true, validateFormats: false });
66
+ } catch (err) {
67
+ ajvError = (err instanceof Error ? err.message : String(err)).split("\n")[0];
68
+ }
27
69
  return ajv;
28
70
  }
29
71
 
72
+ /** Why schema validation cannot run here (ajv did not load), or undefined when it can. */
73
+ export function schemaValidationUnavailable(): string | undefined {
74
+ getAjv();
75
+ return ajvError === undefined ? undefined : `ajv could not be loaded: ${ajvError}`;
76
+ }
77
+
78
+ function requireAjv(): AjvClass {
79
+ const a = getAjv();
80
+ if (!a) throw new Error(`grafana schema validation: ${schemaValidationUnavailable()}`);
81
+ return a;
82
+ }
83
+
84
+ const parsed = new Map<SchemaName, Record<string, unknown>>();
85
+
86
+ /**
87
+ * One schema as validation uses it: the vendored file with its overlay
88
+ * applied, from the generated `spec/schemas.gen.ts` (no filesystem read, so
89
+ * it works bundled), parsed on first use.
90
+ */
91
+ export function bundledSchema(name: SchemaName): Record<string, unknown> {
92
+ let schema = parsed.get(name);
93
+ if (!schema) {
94
+ schema = JSON.parse(SCHEMA_JSON[name]) as Record<string, unknown>;
95
+ parsed.set(name, schema);
96
+ }
97
+ return schema;
98
+ }
99
+
30
100
  const compiled = new Map<string, ValidateFunction>();
31
101
 
32
- function stripRequired(node: unknown): unknown {
33
- if (Array.isArray(node)) return node.map(stripRequired);
102
+ interface Mode {
103
+ /** Drop every `required` list (panel options and queries: Grafana fills them in). */
104
+ lenient: boolean;
105
+ /** Drop every `additionalProperties: false`, so unknown keys pass. */
106
+ open: boolean;
107
+ }
108
+
109
+ function prepare(node: unknown, mode: Mode): unknown {
110
+ if (Array.isArray(node)) return node.map((n) => prepare(n, mode));
34
111
  if (node && typeof node === "object") {
35
112
  const out: Record<string, unknown> = {};
36
113
  for (const [k, v] of Object.entries(node)) {
37
- if (k === "required" && Array.isArray(v)) continue;
38
- out[k] = stripRequired(v);
114
+ if (mode.lenient && k === "required" && Array.isArray(v)) continue;
115
+ if (mode.open && k === "additionalProperties" && v === false) continue;
116
+ out[k === "oneOf" && Array.isArray(v) ? "anyOf" : k] = prepare(v, mode);
39
117
  }
40
118
  return out;
41
119
  }
42
120
  return node;
43
121
  }
44
122
 
45
- /** A validator for one definition of one vendored schema (the root `$ref` when `def` is left out). */
46
- function validatorFor(name: SchemaName, def: string | undefined, lenient: boolean): ValidateFunction | undefined {
47
- const key = `${name}#${def ?? ""}#${lenient}`;
123
+ /** A validator for one definition of one schema (the root `$ref` when `def` is left out). */
124
+ function validatorFor(name: SchemaName, def: string | undefined, mode: Mode): ValidateFunction | undefined {
125
+ const key = `${name}#${def ?? ""}#${mode.lenient}#${mode.open}`;
48
126
  const hit = compiled.get(key);
49
127
  if (hit) return hit;
50
- const schema = loadSchema(name);
128
+ const schema = bundledSchema(name);
51
129
  const definitions = (schema.definitions ?? {}) as Record<string, unknown>;
52
130
  const ref = def ? `#/definitions/${def}` : (schema.$ref as string | undefined);
53
131
  if (!ref) return undefined;
@@ -55,31 +133,48 @@ function validatorFor(name: SchemaName, def: string | undefined, lenient: boolea
55
133
  const root: Record<string, unknown> = {
56
134
  $schema: schema.$schema,
57
135
  $ref: ref,
58
- definitions: lenient ? stripRequired(definitions) : definitions,
136
+ definitions: prepare(definitions, mode),
59
137
  };
60
- const fn = getAjv().compile(root);
138
+ const fn = requireAjv().compile(root);
61
139
  compiled.set(key, fn);
62
140
  return fn;
63
141
  }
64
142
 
65
- function problems(errors: ErrorObject[] | null | undefined, prefix: string): SchemaProblem[] {
143
+ function describe(e: ErrorObject): string {
144
+ const extra =
145
+ e.keyword === "enum" ? `: ${(e.params as { allowedValues: unknown[] }).allowedValues.map((v) => JSON.stringify(v)).join(", ")}` : "";
146
+ return `${e.message ?? "is invalid"}${extra}`;
147
+ }
148
+
149
+ /**
150
+ * Everything wrong with one value against one definition: errors from the
151
+ * opened schema, then unknown keys from the pinned one.
152
+ */
153
+ function check(value: unknown, name: SchemaName, def: string | undefined, lenient: boolean, prefix: string): SchemaProblem[] {
154
+ const open = validatorFor(name, def, { lenient, open: true });
155
+ const closed = validatorFor(name, def, { lenient, open: false });
156
+ if (!open || !closed) return [];
66
157
  const seen = new Set<string>();
67
158
  const out: SchemaProblem[] = [];
68
- for (const e of errors ?? []) {
69
- // oneOf/anyOf wrappers repeat what their branches already said.
70
- if (e.keyword === "oneOf" || e.keyword === "anyOf" || e.keyword === "if") continue;
71
- const extra =
72
- e.keyword === "additionalProperties"
73
- ? ` ("${(e.params as { additionalProperty: string }).additionalProperty}")`
74
- : e.keyword === "enum"
75
- ? `: ${(e.params as { allowedValues: unknown[] }).allowedValues.map((v) => JSON.stringify(v)).join(", ")}`
76
- : "";
77
- const path = `${prefix}${e.instancePath}` || "/";
78
- const message = `${e.message ?? "is invalid"}${extra}`;
159
+ const push = (path: string, message: string, severity: SchemaProblem["severity"]) => {
79
160
  const k = `${path} ${message}`;
80
- if (seen.has(k)) continue;
161
+ if (seen.has(k)) return;
81
162
  seen.add(k);
82
- out.push({ path, message });
163
+ out.push({ path, message, severity });
164
+ };
165
+ if (!open(value)) {
166
+ for (const e of open.errors ?? []) {
167
+ // anyOf/if wrappers repeat what their branches already said.
168
+ if (e.keyword === "oneOf" || e.keyword === "anyOf" || e.keyword === "if") continue;
169
+ push(`${prefix}${e.instancePath}` || "/", describe(e), "error");
170
+ }
171
+ }
172
+ if (!closed(value)) {
173
+ for (const e of closed.errors ?? []) {
174
+ if (e.keyword !== "additionalProperties") continue;
175
+ const key = (e.params as { additionalProperty: string }).additionalProperty;
176
+ push(`${prefix}${e.instancePath}` || "/", `unknown key "${key}" (not in the pinned schema)`, "warning");
177
+ }
83
178
  }
84
179
  return out;
85
180
  }
@@ -98,46 +193,93 @@ function panelsOf(dashboard: Json): Array<{ panel: Json; path: string }> {
98
193
  return out;
99
194
  }
100
195
 
101
- /** Everything in one dashboard that the pinned schemas reject. */
196
+ /** A panel (not a row) against the dashboard's Panel definition and its plugin's options, custom field config and queries. */
197
+ function checkPanel(panel: Json, path: string): SchemaProblem[] {
198
+ const out = check(panel, "dashboard", "Panel", false, path);
199
+ const def = typeof panel.type === "string" ? panelDefinitionFor(panel.type) : undefined;
200
+ if (def?.schema) {
201
+ if (panel.options !== undefined) out.push(...check(panel.options, def.schema, "Options", true, `${path}/options`));
202
+ const custom = (panel.fieldConfig as { defaults?: { custom?: unknown } } | undefined)?.defaults?.custom;
203
+ if (custom !== undefined) out.push(...check(custom, def.schema, "FieldConfig", true, `${path}/fieldConfig/defaults/custom`));
204
+ }
205
+ const panelType = (panel.datasource as { type?: string } | undefined)?.type;
206
+ const targets = Array.isArray(panel.targets) ? (panel.targets as Json[]) : [];
207
+ targets.forEach((t, k) => {
208
+ const type = (t?.datasource as { type?: string } | undefined)?.type ?? panelType;
209
+ const qdef = type ? queryDefinitionFor(type) : undefined;
210
+ if (!qdef?.schema) return;
211
+ out.push(...check(t, qdef.schema, undefined, true, `${path}/targets/${k}`));
212
+ });
213
+ return out;
214
+ }
215
+
216
+ /** `LibraryElementKind.Panel` (public/app/features/library-panels/types.ts:8-10 at v13.2.2, its only member). */
217
+ const LIBRARY_PANEL_KIND = 1;
218
+
219
+ /**
220
+ * The library panel models an external export embeds in `__elements`. The
221
+ * envelope check covers each element's own fields (uid, name, kind, model
222
+ * present); the model is a whole panel, checked like one.
223
+ */
224
+ function libraryPanelModels(dashboard: Json): Array<{ panel: Json; path: string }> {
225
+ const elements = dashboard.__elements;
226
+ if (!elements || typeof elements !== "object" || Array.isArray(elements)) return [];
227
+ const out: Array<{ panel: Json; path: string }> = [];
228
+ for (const [key, el] of Object.entries(elements as Record<string, unknown>)) {
229
+ if (!el || typeof el !== "object") continue;
230
+ const { kind, model } = el as { kind?: unknown; model?: unknown };
231
+ if (kind !== undefined && kind !== LIBRARY_PANEL_KIND) continue;
232
+ if (!model || typeof model !== "object" || Array.isArray(model)) continue;
233
+ out.push({ panel: model as Json, path: `/__elements/${key.replace(/~/g, "~0").replace(/\//g, "~1")}/model` });
234
+ }
235
+ return out;
236
+ }
237
+
238
+ /** Everything in one dashboard that the pinned schemas reject or do not know. */
102
239
  export function validateDashboardSchema(dashboard: Json): SchemaProblem[] {
103
240
  const out: SchemaProblem[] = [];
104
241
  // The envelope and each panel are checked separately: the schema's
105
242
  // `oneOf [Panel, RowPanel]` would otherwise report every panel error twice,
106
243
  // once per branch.
107
- const whole = validatorFor("dashboard", undefined, false)!;
108
244
  const envelope = Array.isArray(dashboard.panels) ? { ...dashboard, panels: [] } : dashboard;
109
- if (!whole(envelope)) out.push(...problems(whole.errors, ""));
110
- const panelSchema = validatorFor("dashboard", "Panel", false)!;
111
- const rowSchema = validatorFor("dashboard", "RowPanel", false)!;
245
+ out.push(...check(envelope, "dashboard", undefined, false, ""));
112
246
 
113
247
  for (const { panel, path } of panelsOf(dashboard)) {
114
248
  if (!panel || typeof panel !== "object") {
115
- out.push({ path, message: "must be object" });
249
+ out.push({ path, message: "must be object", severity: "error" });
116
250
  continue;
117
251
  }
118
252
  if (panel.type === "row") {
119
253
  const header = Array.isArray(panel.panels) ? { ...panel, panels: [] } : panel;
120
- if (!rowSchema(header)) out.push(...problems(rowSchema.errors, path));
254
+ out.push(...check(header, "dashboard", "RowPanel", false, path));
121
255
  continue;
122
256
  }
123
- if (!panelSchema(panel)) out.push(...problems(panelSchema.errors, path));
124
- const def = typeof panel.type === "string" ? panelDefinitionFor(panel.type) : undefined;
125
- if (def?.schema) {
126
- const options = validatorFor(def.schema, "Options", true);
127
- if (options && panel.options !== undefined && !options(panel.options)) out.push(...problems(options.errors, `${path}/options`));
128
- const custom = (panel.fieldConfig as { defaults?: { custom?: unknown } } | undefined)?.defaults?.custom;
129
- const fc = validatorFor(def.schema, "FieldConfig", true);
130
- if (fc && custom !== undefined && !fc(custom)) out.push(...problems(fc.errors, `${path}/fieldConfig/defaults/custom`));
131
- }
132
- const panelType = (panel.datasource as { type?: string } | undefined)?.type;
133
- const targets = Array.isArray(panel.targets) ? (panel.targets as Json[]) : [];
134
- targets.forEach((t, k) => {
135
- const type = (t?.datasource as { type?: string } | undefined)?.type ?? panelType;
136
- const qdef = type ? queryDefinitionFor(type) : undefined;
137
- if (!qdef?.schema) return;
138
- const v = validatorFor(qdef.schema, undefined, true);
139
- if (v && !v(t)) out.push(...problems(v.errors, `${path}/targets/${k}`));
140
- });
257
+ out.push(...checkPanel(panel, path));
141
258
  }
259
+ for (const { panel, path } of libraryPanelModels(dashboard)) out.push(...checkPanel(panel, path));
142
260
  return out;
143
261
  }
262
+
263
+ /** The `expr` schema definition for each server-side expression type. */
264
+ const EXPRESSION_DEFINITIONS: Record<string, string> = {
265
+ math: "TypeMath",
266
+ reduce: "TypeReduce",
267
+ resample: "TypeResample",
268
+ classic_conditions: "TypeClassicConditions",
269
+ threshold: "TypeThreshold",
270
+ sql: "TypeSql",
271
+ };
272
+
273
+ /**
274
+ * Check one server-side expression model (an alert rule's `data[].model`
275
+ * whose datasource is `__expr__`) against the pinned `expr` schema, as
276
+ * written: its `type` picks the definition, and every required field must
277
+ * be there, since Grafana fills none in.
278
+ */
279
+ export function validateExpressionSchema(model: Json, prefix = ""): SchemaProblem[] {
280
+ const def = typeof model.type === "string" ? EXPRESSION_DEFINITIONS[model.type] : undefined;
281
+ if (!def) {
282
+ return [{ path: `${prefix}/type`, message: `is ${JSON.stringify(model.type)}, not one of ${Object.keys(EXPRESSION_DEFINITIONS).join(", ")}`, severity: "error" }];
283
+ }
284
+ return check(model, "expr", def, false, prefix);
285
+ }
@@ -2,7 +2,7 @@ import { describe, expect, test } from "vitest";
2
2
  import { readFileSync } from "fs";
3
3
  import { dirname, join } from "path";
4
4
  import { fileURLToPath } from "url";
5
- import { GRAFANA_SCHEMA_PIN, SCHEMA_NAMES, schemaUrl } from "./pin";
5
+ import { GRAFANA_SCHEMA_PIN, SCHEMA_NAMES, VENDORED_SCHEMA_NAMES, schemaUrl } from "./pin";
6
6
  import { digestMismatches, loadSchema } from "./spec/schemas";
7
7
  import { schemaModules } from "./codegen/generate";
8
8
  import { schemaModule, tsType } from "./codegen/schema-types";
@@ -13,7 +13,9 @@ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
13
13
  describe("the schema pin", () => {
14
14
  test("names a commit and a digest for every vendored schema", () => {
15
15
  expect(GRAFANA_SCHEMA_PIN.commit).toMatch(/^[0-9a-f]{40}$/);
16
- expect(Object.keys(GRAFANA_SCHEMA_PIN.files).sort()).toEqual([...SCHEMA_NAMES].sort());
16
+ expect(Object.keys(GRAFANA_SCHEMA_PIN.files).sort()).toEqual([...VENDORED_SCHEMA_NAMES].sort());
17
+ // Types come from the classic schemas only; dashboardv2 is vendored for the importer (#2947).
18
+ expect(VENDORED_SCHEMA_NAMES.filter((n) => !(SCHEMA_NAMES as readonly string[]).includes(n))).toEqual(["dashboardv2"]);
17
19
  expect(schemaUrl("stat")).toBe(`https://raw.githubusercontent.com/grafana/grafana-foundation-sdk/${GRAFANA_SCHEMA_PIN.commit}/jsonschema/stat.jsonschema.json`);
18
20
  });
19
21
 
@@ -7,7 +7,7 @@ import { load } from "js-yaml";
7
7
  import type { Declarable } from "@intentius/chant/declarable";
8
8
  import type { SerializerResult } from "@intentius/chant/serializer";
9
9
  import { grafanaSerializer } from "./serializer";
10
- import { Datasource } from "./datasource";
10
+ import { Datasource, ExternalDatasource } from "./datasource";
11
11
  import { Dashboard, DashboardProvider } from "./dashboard";
12
12
  import { StatPanel, TimeSeriesPanel } from "./panels";
13
13
  import { PromQuery } from "./query";
@@ -150,6 +150,18 @@ describe("grafana serializer", () => {
150
150
  expect(JSON.parse(out.primary).files).toEqual(Object.keys(out.files!).sort());
151
151
  });
152
152
 
153
+ test("an ExternalDatasource is referenced by uid, in panels and in another datasource's settings, and not provisioned", () => {
154
+ const loki = new ExternalDatasource({ type: "loki", uid: "loki-prod", name: "Loki (prod)" });
155
+ const tempo = new Datasource({ name: "Tempo", type: "tempo", jsonData: { tracesToLogsV2: { datasourceUid: loki } } });
156
+ const logs = new StatPanel({ datasource: loki });
157
+ const out = run({ loki, tempo, d: new Dashboard({ title: "D", panels: [logs] }) });
158
+ expect(JSON.parse(out.files!["dashboards/d.json"]).panels[0].datasource).toEqual({ type: "loki", uid: "loki-prod" });
159
+ const ds = load(out.files![DATASOURCES_FILE]) as { datasources: Array<{ name: string; jsonData?: unknown }> };
160
+ expect(ds.datasources.map((d) => d.name)).toEqual(["Tempo"]);
161
+ expect(ds.datasources[0].jsonData).toEqual({ tracesToLogsV2: { datasourceUid: "loki-prod" } });
162
+ expect(JSON.parse(out.primary).externalDatasources).toEqual([{ type: "loki", uid: "loki-prod", name: "Loki (prod)" }]);
163
+ });
164
+
153
165
  test("round trip: the emitted text parses back to the declared values", () => {
154
166
  const panel = new TimeSeriesPanel({ title: "Rate", datasource: prometheus, targets: [up], interval: "30s" });
155
167
  const out = run({ d: new Dashboard({ title: "Round trip", description: "desc", tags: ["a", "b"], panels: [panel] }) });
package/src/serializer.ts CHANGED
@@ -9,13 +9,21 @@
9
9
  * - `provisioning/datasources/chant.yaml`: every declared datasource.
10
10
  * - `provisioning/dashboards/chant.yaml`: the provider that loads the
11
11
  * dashboard files, a default one unless a `DashboardProvider` is declared.
12
+ * - `provisioning/alerting/chant.yaml`: rule groups, contact points, the
13
+ * notification policy tree, mute timings and templates, when the build
14
+ * declares any (see `alerting-build.ts`).
12
15
  *
13
16
  * Mount `provisioning/` at `/etc/grafana/provisioning` and `dashboards/` at
14
17
  * the provider's path (`/var/lib/grafana/dashboards` by default).
15
18
  *
16
- * The primary output is a small JSON index of what was built. Grafana's
17
- * files have no metadata channel chant could use without changing what
18
- * Grafana imports, so no ownership marker is stamped.
19
+ * The primary output is a small JSON index of what was built. The dashboard
20
+ * JSON carries no ownership marker, because Grafana keeps who manages a
21
+ * dashboard outside it, in the resource's metadata on its
22
+ * `dashboard.grafana.app` API. For a file-provisioned dashboard that metadata
23
+ * is the `grafana.app/managerId` annotation Grafana writes, whose value is the
24
+ * provider name in `provisioning/dashboards/chant.yaml`: `chant` unless a
25
+ * `DashboardProvider` names another. That name is the marker chant stamps at
26
+ * synthesis, and ./ownership.ts reads it back.
19
27
  */
20
28
 
21
29
  import type { Declarable } from "@intentius/chant/declarable";
package/src/skill-defs.ts CHANGED
@@ -28,4 +28,38 @@ export const grafanaSkills = createSkillsLoader(import.meta.url, [
28
28
  description: "Load chant-built Grafana dashboards and datasources into a running Grafana through its provisioning directories, in Docker or Kubernetes",
29
29
  triggers: [{ type: "context" as const, value: "grafana provisioning" }],
30
30
  },
31
+ {
32
+ file: "chant-grafana-alerting.md",
33
+ name: "chant-grafana-alerting",
34
+ description:
35
+ "Declare Grafana-managed alert rules, server-side expressions, contact points, notification policies and mute timings, turn an Slo into burn-rate rules, and import Grafana's alerting exports",
36
+ triggers: [
37
+ { type: "context" as const, value: "grafana alerting" },
38
+ { type: "context" as const, value: "grafana alert rule" },
39
+ { type: "context" as const, value: "contact point" },
40
+ ],
41
+ examples: [
42
+ {
43
+ title: "An SLO's burn-rate alerts as Grafana-managed rules",
44
+ output: 'export const checkoutBurn = SloAlertRules({ slo: checkout, datasource: prometheus, folder: "SLOs" });',
45
+ },
46
+ ],
47
+ },
48
+ {
49
+ file: "chant-grafana-operations.md",
50
+ name: "chant-grafana-operations",
51
+ description:
52
+ "Start a Grafana project from a template, bring existing dashboards into chant with chant import, find what changed in a running Grafana with chant lifecycle diff --live, export a Grafana as TypeScript, and apply a build over the HTTP API",
53
+ triggers: [
54
+ { type: "context" as const, value: "grafana drift" },
55
+ { type: "context" as const, value: "import grafana dashboard" },
56
+ { type: "context" as const, value: "grafana apply" },
57
+ ],
58
+ examples: [
59
+ {
60
+ title: "See what was edited in a running Grafana since the last build",
61
+ output: "chant lifecycle diff staging --live",
62
+ },
63
+ ],
64
+ },
31
65
  ]);
@@ -0,0 +1,49 @@
1
+ ---
2
+ skill: chant-grafana-alerting
3
+ description: Declare Grafana-managed alert rules, server-side expressions, contact points, notification policies and mute timings with chant, turn an Slo into burn-rate rules, and import Grafana's alerting exports
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Grafana-managed alerting with chant
8
+
9
+ Everything is written to `provisioning/alerting/chant.yaml`, Grafana's alerting provisioning file. Mount `dist/grafana/provisioning` at `/etc/grafana/provisioning`.
10
+
11
+ ## A rule
12
+
13
+ ```ts
14
+ import { AlertRule, AlertRuleGroup, PromQuery, ReduceExpression, ThresholdExpression } from "@intentius/chant-lexicon-grafana";
15
+
16
+ const errors = new PromQuery({ datasource: prometheus, expr: 'sum(rate(http_requests_total{code=~"5.."}[5m]))', instant: true });
17
+ const last = new ReduceExpression({ expression: "A", reducer: "last" });
18
+ const high = new ThresholdExpression({ expression: "B", conditions: [{ evaluator: { type: "gt", params: [5] } }] });
19
+ const rule = new AlertRule({ title: "5xx above 5/s", uid: "checkout-5xx", data: [errors, last, high], for: "5m", labels: { severity: "page" } });
20
+ export const checkoutAlerts = new AlertRuleGroup({ name: "checkout", folder: "Checkout", interval: "1m", rules: [rule] });
21
+ ```
22
+
23
+ - refIds follow `data` order (A, B, C); expressions name inputs by refId (`"A"`, or `$A` in a `MathExpression`). `condition` defaults to the last refId.
24
+ - Queries need a `Datasource`, `ExternalDatasource` or `{ type, uid }`; never a `DatasourceVariable`.
25
+ - Any plugin's model: `new AlertQuery({ datasource, model: { ... } })`. A typed query with its own range: `new AlertQuery({ query, relativeTimeRange: { from: "5m" } })`.
26
+ - Set `uid` explicitly on rules whose title may change.
27
+
28
+ ## Notifications
29
+
30
+ - `ContactPoint({ name, receivers: [{ type: "slack", settings: { url: "$__env{SLACK_URL}" } }] })`. Secrets always as `$__env{...}` or `$__file{...}`; GRAF002 flags literals.
31
+ - `NotificationPolicy({ receiver: oncall, routes: [{ receiver: tickets, object_matchers: [["severity", "=", "ticket"]], mute_time_intervals: [weekends] }] })`. One per organisation: it replaces the whole tree.
32
+ - `MuteTiming({ name: "weekends", time_intervals: [{ weekdays: ["saturday", "sunday"] }] })`, `NotificationTemplate({ name, template })`.
33
+ - Pass entities rather than names where you can; GRAF113 checks names against what the build declares.
34
+
35
+ ## An SLO's burn-rate alerts
36
+
37
+ ```ts
38
+ export const checkoutBurn = SloAlertRules({ slo: checkout, datasource: prometheus, folder: "SLOs" });
39
+ ```
40
+
41
+ One rule per window pair, reading the `Slo`'s recorded `slo:sli_error:ratio_rate<window>` series, so the `Slo`'s Prometheus rule group must be loaded. Rules carry `severity: page|ticket` for the policy tree, or pass `contactPoint`.
42
+
43
+ ## Import
44
+
45
+ `chant import alert-rules.yaml` reads a provisioning file or Grafana's export (`/api/v1/provisioning/alert-rules/export?format=yaml`, and the contact-points, policies and mute-timings exports). Read every warning: guessed datasource types, `[REDACTED]` secrets turned into `$__env{...}`, and delete/reset lists that are not carried.
46
+
47
+ ## Checks
48
+
49
+ GRAF108 (PromQL), GRAF111 (condition and expression refIds), GRAF112 (datasources), GRAF113 (contact points, mute timings, matchers), GRAF114 (uids, intervals as multiples of 10s, duplicates).
@@ -0,0 +1,108 @@
1
+ ---
2
+ skill: chant-grafana-operations
3
+ description: Start a Grafana project from a template, bring existing dashboards into chant with chant import, find what changed in a running Grafana with chant lifecycle diff --live, export a Grafana as TypeScript, and apply a build over the HTTP API
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Operating Grafana with chant
8
+
9
+ Use this skill when a Grafana already exists: dashboards made in the UI to bring under chant, a provisioned dashboard somebody edited, or a build to push without mounting files. For writing dashboards see the chant-grafana skill; for provisioning files and ConfigMaps, chant-grafana-provisioning; for alert rules, chant-grafana-alerting.
10
+
11
+ ## Start a project
12
+
13
+ ```bash
14
+ chant init --lexicon grafana my-dashboards # a Prometheus datasource and one dashboard
15
+ chant init --lexicon grafana --template red my-dashboards # RedDashboard over span metrics, ExternalDatasource, Folder
16
+ chant init --lexicon grafana --template k8s-pods my-dashboards # pod dashboard delivered as sidecar ConfigMaps (k8s + grafana)
17
+ chant init --lexicon grafana --template slo my-dashboards # Slo rules, SloDashboard and SloAlertRules (prometheus + grafana)
18
+ ```
19
+
20
+ Every template builds clean and passes the GRAF checks as written. `k8s-pods` and `slo` ship a `chant.config.ts` naming both lexicons and a `build` script without `--lexicon`, so both outputs and both lexicons' checks run.
21
+
22
+ ## Import dashboards you already have
23
+
24
+ Export the dashboard in Grafana (Export > Export as JSON, classic or V2 resource), then:
25
+
26
+ ```bash
27
+ chant import checkout.json --output src # inside a project that lists grafana
28
+ chant import checkout.json --lexicon grafana --output src # anywhere else
29
+ chant build src --lexicon grafana -o dist/grafana/index.json
30
+ ```
31
+
32
+ The importer also reads `GET /api/dashboards/uid/<uid>` responses, `dashboard.grafana.app` v1 and v2 resources, and datasource, dashboard and alerting provisioning files. Each dashboard lands in `src/<uid>/`: `dashboard.ts`, `panels.ts`, one `row-<title>.ts` per row, `variables.ts`, and `datasources.ts` with an `ExternalDatasource` per datasource named by uid. Replace an `ExternalDatasource` with a `Datasource` of the same uid to provision it from chant too.
33
+
34
+ Read every import warning. Anything the lexicon can't express is reported, never dropped. Common ones: a v2 dashboard's tabs become rows, an export made for sharing turns `${DS_*}` inputs into `DatasourceVariable`s, and two imported dashboards naming one datasource both declare it (GRAF104; delete one declaration and import it from the other file).
35
+
36
+ A dashboard inside a Kubernetes ConfigMap imports with the k8s lexicon: `chant import grafana-configmaps.yaml` gives a grafana `Dashboard` as the ConfigMap's value.
37
+
38
+ ## Point environments at a Grafana
39
+
40
+ Observe, export and apply read `grafana.profiles.<env>` in `chant.config.ts`. Credentials are named by environment variable, never written in the file:
41
+
42
+ ```ts
43
+ import type { ChantConfig } from "@intentius/chant/config";
44
+ import "@intentius/chant-lexicon-grafana";
45
+
46
+ export default {
47
+ lexicons: ["grafana"],
48
+ ownership: { stack: "shop", env: "prod" },
49
+ grafana: {
50
+ profiles: {
51
+ staging: { url: "https://grafana.staging.example.com", token: { env: "GRAFANA_STAGING_TOKEN" } },
52
+ prod: { url: "https://grafana.example.com", token: { env: "GRAFANA_PROD_TOKEN" }, orgId: 2 },
53
+ },
54
+ },
55
+ } satisfies ChantConfig;
56
+ ```
57
+
58
+ With no profile, `GRAFANA_URL` plus `GRAFANA_TOKEN` (or `GRAFANA_USER` and `GRAFANA_PASSWORD`) is used. Grafana Cloud also needs `namespace: "stacks-<id>"`. A Viewer service account can observe and export. Applying needs Editor, or a custom role with `grafanaActionsFor("Apply", "Prune")`.
59
+
60
+ ## Find what changed in Grafana
61
+
62
+ ```bash
63
+ chant lifecycle diff staging --live
64
+ chant lifecycle diff staging --live --json # the same report as data
65
+ chant lifecycle diff staging --live --update-baseline # accept what it reported
66
+ ```
67
+
68
+ Paths are in your source's terms: `panels[0].panels[1].title` is the second panel of the first row. A value you declared that differs is drift. A value you never declared, such as a panel added in the UI, is listed as unclaimed and never proposed as a change. What the build fills in (panel ids, auto grid positions, refIds) and what Grafana adds on read are not reported. A dashboard Grafana 13 stores as v2 is read at v2 and diffed after conversion, so a tab added in the UI shows as drift in the rows.
69
+
70
+ Ownership decides what counts as chant's. A dashboard loaded by one of the project's providers (named `chant` unless you declared another `DashboardProvider`), or carrying the `app.kubernetes.io/managed-by: chant` label the applier writes, is `owned`. Anything else is `foreign`. Datasources and dashboards on Grafana 11 are `unknown`, and `--owned` leaves them out.
71
+
72
+ A `DashboardProvider` can't be observed (Grafana serves no API for provisioning files) and is reported as not observed, not missing.
73
+
74
+ ## Export a Grafana as TypeScript
75
+
76
+ ```bash
77
+ chant import --from staging --lexicon grafana --output src
78
+ chant import --from staging --lexicon grafana --type Grafana::Dashboard --name checkout --output src
79
+ chant import --from staging --lexicon grafana --owned --output src
80
+ ```
81
+
82
+ It writes every dashboard and datasource in the organisation, laid out like a file import. Datasource secrets come back as `"[REDACTED]"`, each with a warning. Replace them with `$__env{NAME}` or `$__file{path}`, which GRAF002 requires.
83
+
84
+ ## Apply over the HTTP API
85
+
86
+ When the build's files can't be mounted into Grafana, or you want real folders with pinned uids, apply from an Op:
87
+
88
+ ```ts
89
+ // ops/grafana.op.ts, with "build:grafana": "chant build src --lexicon grafana -o dist/grafana.json" in package.json
90
+ import { Op, phase, build } from "@intentius/chant/op";
91
+ import { grafanaApply } from "@intentius/chant-lexicon-grafana";
92
+
93
+ export default Op({
94
+ name: "grafana-prod",
95
+ phases: [
96
+ phase("Build", [build(".", { script: "build:grafana" })]),
97
+ phase("Apply", [grafanaApply("dist/grafana.json", { environment: "prod", prune: true })]),
98
+ ],
99
+ });
100
+ ```
101
+
102
+ Run it with `chant run grafana-prod`, or from an agent through the MCP `op-run`, `op-status` and `op-signal` tools. The applier creates folders parents first with their uids, writes library panels found in `__elements`, and sends each dashboard with the project's labels. A dashboard whose live content already matches is `unchanged` and not written; a panel title edited in the UI is put back. `prune` deletes only dashboards and folders labelled with this project's stack and env. It never touches UI-saved or file-provisioned dashboards, library panels, or anything on Grafana 11, and does nothing when no `ownership.stack` is set. Datasources still come from the provisioning file.
103
+
104
+ ## A loop that keeps UI edits
105
+
106
+ 1. `chant lifecycle diff prod --live` shows the edit.
107
+ 2. Either change the source to match and rebuild, or `chant import --from prod --name <uid>` to pull the dashboard, then copy the changed panel over.
108
+ 3. Apply, or let provisioning reload, and diff again. It should report nothing.
@@ -25,7 +25,7 @@ docker run -p 3000:3000 \
25
25
 
26
26
  ## Kubernetes
27
27
 
28
- Put the two provisioning files in one ConfigMap mounted at `/etc/grafana/provisioning/datasources` and `/etc/grafana/provisioning/dashboards` (a `subPath` each, or two ConfigMaps), and the dashboard JSON in another mounted at `/var/lib/grafana/dashboards`. `grafanaFiles(entities)` returns every file by path, so a k8s composite can build those ConfigMaps from the same declarations without a second build.
28
+ Use `GrafanaConfigMaps({ entities, namespace })` from `@intentius/chant-lexicon-grafana/k8s` in a k8s build root. It writes one ConfigMap per dashboard labelled `grafana_dashboard: "1"`, with the folder in the `k8s-sidecar-target-directory` annotation. The datasource file goes in a ConfigMap labelled `grafana_datasource: "1"`, and the provider file in a third. The Grafana Helm chart's sidecar picks these up as they are. For a Grafana without the sidecar, spread `grafanaVolumes({ entities })`'s `volumes` and `volumeMounts` into the Deployment: provisioning goes under `/etc/grafana/provisioning`, and each dashboard folder gets a projected volume of its own. `grafanaFiles(entities)` still returns every file by path for any other layout.
29
29
 
30
30
  ## Changing where dashboards live
31
31
 
@@ -35,8 +35,8 @@ Declare a `DashboardProvider` to change the mount path, pin a folder, or allow U
35
35
  export const provider = new DashboardProvider({ name: "team", path: "/dashboards", allowUiUpdates: true });
36
36
  ```
37
37
 
38
- With no provider declared, chant writes one named `chant` that reads `/var/lib/grafana/dashboards` and maps subdirectories to folders.
38
+ With no provider declared, chant writes one named `chant` that reads `/var/lib/grafana/dashboards` and maps subdirectories to folders. A provider with `folder` set puts every dashboard in that folder and ignores the dashboards' own `folder`; GRAF109 warns. Two providers on the same or nested paths load every dashboard twice, which GRAF109 reports as an error. A dashboard `folder` of `"Platform/Kubernetes"` nests on Grafana 13.1 and later.
39
39
 
40
40
  ## Checking before Grafana does
41
41
 
42
- `chant lint` and `chant build` run GRAF101-GRAF107 on the output, including a check against Grafana's dashboard schema at the pinned version. To check against a real Grafana, the lexicon's `import.test.ts` boots `grafana/grafana` with Docker, provisions the example and reads each dashboard back over the HTTP API.
42
+ `chant lint` and `chant build` run the GRAF1xx checks on the output, including a check against Grafana's dashboard schema at the pinned version, a PromQL syntax check on every query sent to a Prometheus, and a check that the providers put each dashboard in its declared folder, once. To check against a real Grafana, the lexicon's `import.test.ts` boots `grafana/grafana` with Docker, provisions the example and reads each dashboard back over the HTTP API.