@intentius/chant-lexicon-otel 0.81.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 (206) hide show
  1. package/README.md +49 -0
  2. package/dist/catalog.d.ts +18 -0
  3. package/dist/catalog.d.ts.map +1 -0
  4. package/dist/codegen/docs-cli.d.ts +3 -0
  5. package/dist/codegen/docs-cli.d.ts.map +1 -0
  6. package/dist/codegen/docs.d.ts +8 -0
  7. package/dist/codegen/docs.d.ts.map +1 -0
  8. package/dist/codegen/generate-cli.d.ts +3 -0
  9. package/dist/codegen/generate-cli.d.ts.map +1 -0
  10. package/dist/codegen/generate.d.ts +19 -0
  11. package/dist/codegen/generate.d.ts.map +1 -0
  12. package/dist/codegen/package.d.ts +7 -0
  13. package/dist/codegen/package.d.ts.map +1 -0
  14. package/dist/collector.d.ts +29 -0
  15. package/dist/collector.d.ts.map +1 -0
  16. package/dist/components/common.d.ts +144 -0
  17. package/dist/components/common.d.ts.map +1 -0
  18. package/dist/components/exporters.d.ts +86 -0
  19. package/dist/components/exporters.d.ts.map +1 -0
  20. package/dist/components/extensions.d.ts +32 -0
  21. package/dist/components/extensions.d.ts.map +1 -0
  22. package/dist/components/index.d.ts +6 -0
  23. package/dist/components/index.d.ts.map +1 -0
  24. package/dist/components/processors.d.ts +118 -0
  25. package/dist/components/processors.d.ts.map +1 -0
  26. package/dist/components/receivers.d.ts +118 -0
  27. package/dist/components/receivers.d.ts.map +1 -0
  28. package/dist/define.d.ts +136 -0
  29. package/dist/define.d.ts.map +1 -0
  30. package/dist/detect.d.ts +2 -0
  31. package/dist/detect.d.ts.map +1 -0
  32. package/dist/index.d.ts +12 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/integrity.json +25 -0
  35. package/dist/lint/audit-catalog.d.ts +13 -0
  36. package/dist/lint/audit-catalog.d.ts.map +1 -0
  37. package/dist/lint/post-synth/index.d.ts +3 -0
  38. package/dist/lint/post-synth/index.d.ts.map +1 -0
  39. package/dist/lint/post-synth/otel-helpers.d.ts +22 -0
  40. package/dist/lint/post-synth/otel-helpers.d.ts.map +1 -0
  41. package/dist/lint/post-synth/otel101.d.ts +8 -0
  42. package/dist/lint/post-synth/otel101.d.ts.map +1 -0
  43. package/dist/lint/post-synth/otel102.d.ts +8 -0
  44. package/dist/lint/post-synth/otel102.d.ts.map +1 -0
  45. package/dist/lint/post-synth/otel103.d.ts +8 -0
  46. package/dist/lint/post-synth/otel103.d.ts.map +1 -0
  47. package/dist/lint/post-synth/otel104.d.ts +8 -0
  48. package/dist/lint/post-synth/otel104.d.ts.map +1 -0
  49. package/dist/lint/post-synth/otel105.d.ts +8 -0
  50. package/dist/lint/post-synth/otel105.d.ts.map +1 -0
  51. package/dist/lint/post-synth/otel106.d.ts +8 -0
  52. package/dist/lint/post-synth/otel106.d.ts.map +1 -0
  53. package/dist/lint/post-synth/otel107.d.ts +8 -0
  54. package/dist/lint/post-synth/otel107.d.ts.map +1 -0
  55. package/dist/lint/post-synth/otel108.d.ts +8 -0
  56. package/dist/lint/post-synth/otel108.d.ts.map +1 -0
  57. package/dist/lint/post-synth/otel109.d.ts +8 -0
  58. package/dist/lint/post-synth/otel109.d.ts.map +1 -0
  59. package/dist/lint/rules/component-id-syntax.d.ts +14 -0
  60. package/dist/lint/rules/component-id-syntax.d.ts.map +1 -0
  61. package/dist/lint/rules/index.d.ts +6 -0
  62. package/dist/lint/rules/index.d.ts.map +1 -0
  63. package/dist/lint/rules/literal-credential.d.ts +13 -0
  64. package/dist/lint/rules/literal-credential.d.ts.map +1 -0
  65. package/dist/lint/rules/otel-ast.d.ts +14 -0
  66. package/dist/lint/rules/otel-ast.d.ts.map +1 -0
  67. package/dist/lsp/completions.d.ts +4 -0
  68. package/dist/lsp/completions.d.ts.map +1 -0
  69. package/dist/lsp/hover.d.ts +4 -0
  70. package/dist/lsp/hover.d.ts.map +1 -0
  71. package/dist/manifest.json +8 -0
  72. package/dist/meta.json +102 -0
  73. package/dist/model.d.ts +53 -0
  74. package/dist/model.d.ts.map +1 -0
  75. package/dist/okf/index.md +40 -0
  76. package/dist/okf/rules/OTEL001.md +15 -0
  77. package/dist/okf/rules/OTEL002.md +11 -0
  78. package/dist/okf/rules/OTEL101.md +11 -0
  79. package/dist/okf/rules/OTEL102.md +11 -0
  80. package/dist/okf/rules/OTEL103.md +11 -0
  81. package/dist/okf/rules/OTEL104.md +11 -0
  82. package/dist/okf/rules/OTEL105.md +11 -0
  83. package/dist/okf/rules/OTEL106.md +11 -0
  84. package/dist/okf/rules/OTEL107.md +11 -0
  85. package/dist/okf/rules/OTEL108.md +11 -0
  86. package/dist/okf/rules/OTEL109.md +11 -0
  87. package/dist/okf/types/AttributesProcessor.md +9 -0
  88. package/dist/okf/types/BatchProcessor.md +9 -0
  89. package/dist/okf/types/DebugExporter.md +9 -0
  90. package/dist/okf/types/FileLogReceiver.md +9 -0
  91. package/dist/okf/types/GoogleCloudExporter.md +9 -0
  92. package/dist/okf/types/HealthCheckExtension.md +9 -0
  93. package/dist/okf/types/HostMetricsReceiver.md +9 -0
  94. package/dist/okf/types/K8sAttributesProcessor.md +9 -0
  95. package/dist/okf/types/MemoryLimiterProcessor.md +9 -0
  96. package/dist/okf/types/OtlpExporter.md +9 -0
  97. package/dist/okf/types/OtlpHttpExporter.md +9 -0
  98. package/dist/okf/types/OtlpReceiver.md +9 -0
  99. package/dist/okf/types/Pipeline.md +13 -0
  100. package/dist/okf/types/PprofExtension.md +9 -0
  101. package/dist/okf/types/PrometheusExporter.md +9 -0
  102. package/dist/okf/types/PrometheusReceiver.md +9 -0
  103. package/dist/okf/types/ResourceDetectionProcessor.md +9 -0
  104. package/dist/okf/types/ResourceProcessor.md +9 -0
  105. package/dist/okf/types/Service.md +9 -0
  106. package/dist/okf/types/ZPagesExtension.md +9 -0
  107. package/dist/package-cli.d.ts +3 -0
  108. package/dist/package-cli.d.ts.map +1 -0
  109. package/dist/pipeline.d.ts +66 -0
  110. package/dist/pipeline.d.ts.map +1 -0
  111. package/dist/platform.d.ts +57 -0
  112. package/dist/platform.d.ts.map +1 -0
  113. package/dist/plugin.d.ts +11 -0
  114. package/dist/plugin.d.ts.map +1 -0
  115. package/dist/rules/component-id-syntax.ts +68 -0
  116. package/dist/rules/literal-credential.ts +62 -0
  117. package/dist/rules/otel-ast.ts +30 -0
  118. package/dist/rules/otel-helpers.ts +70 -0
  119. package/dist/rules/otel101.ts +17 -0
  120. package/dist/rules/otel102.ts +17 -0
  121. package/dist/rules/otel103.ts +17 -0
  122. package/dist/rules/otel104.ts +17 -0
  123. package/dist/rules/otel105.ts +17 -0
  124. package/dist/rules/otel106.ts +17 -0
  125. package/dist/rules/otel107.ts +17 -0
  126. package/dist/rules/otel108.ts +17 -0
  127. package/dist/rules/otel109.ts +17 -0
  128. package/dist/serializer.d.ts +16 -0
  129. package/dist/serializer.d.ts.map +1 -0
  130. package/dist/skill-defs.d.ts +3 -0
  131. package/dist/skill-defs.d.ts.map +1 -0
  132. package/dist/skills/chant-otel-custom-components.md +51 -0
  133. package/dist/skills/chant-otel-platforms.md +34 -0
  134. package/dist/skills/chant-otel.md +60 -0
  135. package/dist/topology.d.ts +56 -0
  136. package/dist/topology.d.ts.map +1 -0
  137. package/dist/types/index.d.ts +3 -0
  138. package/dist/validate-cli.d.ts +3 -0
  139. package/dist/validate-cli.d.ts.map +1 -0
  140. package/dist/validate-config.d.ts +30 -0
  141. package/dist/validate-config.d.ts.map +1 -0
  142. package/dist/validate.d.ts +11 -0
  143. package/dist/validate.d.ts.map +1 -0
  144. package/dist/yaml.d.ts +27 -0
  145. package/dist/yaml.d.ts.map +1 -0
  146. package/package.json +74 -0
  147. package/src/catalog.ts +56 -0
  148. package/src/codegen/docs-cli.ts +4 -0
  149. package/src/codegen/docs.ts +90 -0
  150. package/src/codegen/generate-cli.ts +8 -0
  151. package/src/codegen/generate.ts +50 -0
  152. package/src/codegen/package.ts +41 -0
  153. package/src/collector.ts +147 -0
  154. package/src/components/common.ts +151 -0
  155. package/src/components/exporters.ts +141 -0
  156. package/src/components/extensions.ts +51 -0
  157. package/src/components/index.ts +5 -0
  158. package/src/components/processors.ts +208 -0
  159. package/src/components/receivers.ts +177 -0
  160. package/src/define.test.ts +131 -0
  161. package/src/define.ts +249 -0
  162. package/src/detect.ts +11 -0
  163. package/src/generated/lexicon-otel.json +102 -0
  164. package/src/index.ts +73 -0
  165. package/src/lint/audit-catalog.ts +102 -0
  166. package/src/lint/post-synth/index.ts +23 -0
  167. package/src/lint/post-synth/otel-helpers.ts +70 -0
  168. package/src/lint/post-synth/otel101.ts +17 -0
  169. package/src/lint/post-synth/otel102.ts +17 -0
  170. package/src/lint/post-synth/otel103.ts +17 -0
  171. package/src/lint/post-synth/otel104.ts +17 -0
  172. package/src/lint/post-synth/otel105.ts +17 -0
  173. package/src/lint/post-synth/otel106.ts +17 -0
  174. package/src/lint/post-synth/otel107.ts +17 -0
  175. package/src/lint/post-synth/otel108.ts +17 -0
  176. package/src/lint/post-synth/otel109.ts +17 -0
  177. package/src/lint/post-synth/post-synth.test.ts +184 -0
  178. package/src/lint/rules/component-id-syntax.ts +68 -0
  179. package/src/lint/rules/index.ts +9 -0
  180. package/src/lint/rules/literal-credential.ts +62 -0
  181. package/src/lint/rules/otel-ast.ts +30 -0
  182. package/src/lint/rules/rules.test.ts +70 -0
  183. package/src/lsp/completions.test.ts +23 -0
  184. package/src/lsp/completions.ts +15 -0
  185. package/src/lsp/hover.test.ts +20 -0
  186. package/src/lsp/hover.ts +30 -0
  187. package/src/model.ts +88 -0
  188. package/src/package-cli.ts +17 -0
  189. package/src/pipeline.ts +89 -0
  190. package/src/platform.test.ts +79 -0
  191. package/src/platform.ts +143 -0
  192. package/src/plugin.test.ts +55 -0
  193. package/src/plugin.ts +111 -0
  194. package/src/serializer.test.ts +187 -0
  195. package/src/serializer.ts +31 -0
  196. package/src/skill-defs.ts +36 -0
  197. package/src/skills/chant-otel-custom-components.md +51 -0
  198. package/src/skills/chant-otel-platforms.md +34 -0
  199. package/src/skills/chant-otel.md +60 -0
  200. package/src/topology.test.ts +80 -0
  201. package/src/topology.ts +122 -0
  202. package/src/validate-cli.ts +7 -0
  203. package/src/validate-config.ts +232 -0
  204. package/src/validate.ts +82 -0
  205. package/src/yaml.test.ts +56 -0
  206. package/src/yaml.ts +145 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * What the platform composites share: a default collector config, the image
3
+ * that runs it, where the config is mounted, and the ports a rendered config
4
+ * listens on.
5
+ *
6
+ * The docker, k8s and fly collector composites build on this. A composite
7
+ * declares its config with this lexicon, renders it with `collectorYaml`, and
8
+ * reads the listening ports back from the built config, so the ports it
9
+ * publishes follow whatever config the caller declared.
10
+ */
11
+ import type { Declarable } from "@intentius/chant/declarable";
12
+ import { type OTelComponent } from "./define.js";
13
+ import { type CollectorConfig, type Signal } from "./model.js";
14
+ /** The contrib collector image at the version the built-in components are typed against. */
15
+ export declare const COLLECTOR_IMAGE: string;
16
+ /** Where the platform composites mount the rendered config inside the container. */
17
+ export declare const COLLECTOR_CONFIG_PATH = "/etc/otel/config.yaml";
18
+ export interface OtlpCollectorOptions {
19
+ /** Where telemetry goes. Default: one `debug` exporter at `basic` verbosity. */
20
+ exporters?: OTelComponent<"exporter", string, any>[];
21
+ /** Which signals get a pipeline. Default: traces, metrics and logs. */
22
+ signals?: Signal[];
23
+ /** Serve `health_check` on 0.0.0.0:13133. Default: true. */
24
+ healthCheck?: boolean;
25
+ }
26
+ /**
27
+ * The entities of a small OTLP collector: an `otlp` receiver on 4317 (gRPC)
28
+ * and 4318 (HTTP), `memory_limiter` then `batch`, the given exporters, and a
29
+ * `health_check` extension. Pass the result to `collectorYaml`.
30
+ */
31
+ export declare function otlpCollector(options?: OtlpCollectorOptions): Declarable[];
32
+ /** A port a collector config listens on. */
33
+ export interface CollectorPort {
34
+ /** A name usable as a k8s port name: lowercase, `-` separated, at most 15 characters. */
35
+ name: string;
36
+ port: number;
37
+ }
38
+ export interface CollectorEndpoints {
39
+ /** Ports the receivers listen on, in config order, one entry per port. */
40
+ ports: CollectorPort[];
41
+ /**
42
+ * The `health_check` extension, when the service enables one and it listens
43
+ * on an address other than localhost. A probe from outside the container
44
+ * cannot reach a localhost listener.
45
+ */
46
+ healthCheck?: {
47
+ port: number;
48
+ path: string;
49
+ };
50
+ }
51
+ /**
52
+ * The ports a built collector config listens on: every receiver `endpoint`,
53
+ * named after the receiver and the protocol key above it (`otlp-grpc`,
54
+ * `otlp-http`), and the `health_check` port if the service enables it.
55
+ */
56
+ export declare function collectorEndpoints(config: CollectorConfig): CollectorEndpoints;
57
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAM7D,OAAO,EAA6B,KAAK,eAAe,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAEvF,4FAA4F;AAC5F,eAAO,MAAM,eAAe,QAAoF,CAAC;AAEjH,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAE7D,MAAM,WAAW,oBAAoB;IACnC,gFAAgF;IAChF,SAAS,CAAC,EAAE,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;IACrD,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,UAAU,EAAE,CAoB9E;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC5B,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,0EAA0E;IAC1E,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB;;;;OAIG;IACH,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AA8BD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,GAAG,kBAAkB,CA6B9E"}
@@ -0,0 +1,11 @@
1
+ import type { LexiconPlugin } from "@intentius/chant/lexicon";
2
+ /**
3
+ * OpenTelemetry Collector lexicon plugin.
4
+ *
5
+ * Typed receivers, processors, exporters and extensions, pipelines and the
6
+ * service block, serialized to one collector config file. A component chant
7
+ * doesn't ship comes in through `defineComponent`, and is serialized and
8
+ * checked the same way as the built-ins.
9
+ */
10
+ export declare const otelPlugin: LexiconPlugin;
11
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAyB9D;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,aA6ExB,CAAC"}
@@ -0,0 +1,68 @@
1
+ import * as ts from "typescript";
2
+ import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
3
+ import { isComponentId } from "../../model";
4
+ import { calleeName, COMPONENT_CLASS, literalText, position, propertyName } from "./otel-ast";
5
+
6
+ /**
7
+ * OTEL001: a component id or instance name the collector can't parse.
8
+ *
9
+ * A pipeline may name a component by id string (`"otlp/backend"`), and every
10
+ * component takes an instance `name`. Both end up in the collector's
11
+ * `type[/name]` syntax, where a space, an empty name or a stray `/` is a
12
+ * config error at collector start. This catches the literal cases in source,
13
+ * before a build: a string in a `Pipeline`'s receivers, processors or
14
+ * exporters that isn't `type` or `type/name`, and a component `name` that is
15
+ * empty, contains whitespace or starts with `/`.
16
+ */
17
+ export const componentIdSyntaxRule: LintRule = {
18
+ id: "OTEL001",
19
+ severity: "error",
20
+ category: "correctness",
21
+ description: "Collector component id or instance name is not valid type[/name] syntax",
22
+
23
+ check(context: LintContext): LintDiagnostic[] {
24
+ const diagnostics: LintDiagnostic[] = [];
25
+ const source = context.sourceFile;
26
+
27
+ const flag = (node: ts.Node, message: string) => {
28
+ diagnostics.push({ ruleId: "OTEL001", severity: "error", message, file: context.filePath, ...position(source, node) });
29
+ };
30
+
31
+ const visit = (node: ts.Node) => {
32
+ if (ts.isNewExpression(node) || ts.isCallExpression(node)) {
33
+ const name = calleeName(node);
34
+ const arg = node.arguments?.[0];
35
+ if (name && arg && ts.isObjectLiteralExpression(arg)) {
36
+ if (name === "Pipeline") {
37
+ for (const prop of arg.properties) {
38
+ const key = propertyName(prop);
39
+ if (key !== "receivers" && key !== "processors" && key !== "exporters") continue;
40
+ const init = (prop as ts.PropertyAssignment).initializer;
41
+ if (!ts.isArrayLiteralExpression(init)) continue;
42
+ for (const el of init.elements) {
43
+ const text = literalText(el as ts.Expression);
44
+ if (text !== undefined && !isComponentId(text)) {
45
+ flag(el, `"${text}" in ${key} is not a collector component id; write type or type/name, e.g. "otlp" or "otlp/backend"`);
46
+ }
47
+ }
48
+ }
49
+ }
50
+ if (name === "Pipeline" || COMPONENT_CLASS.test(name)) {
51
+ for (const prop of arg.properties) {
52
+ if (propertyName(prop) !== "name") continue;
53
+ const text = literalText((prop as ts.PropertyAssignment).initializer);
54
+ if (text === undefined) continue;
55
+ if (text === "" || /\s/.test(text) || text.startsWith("/")) {
56
+ flag(prop, `instance name "${text}" makes an invalid id; a name is non-empty, has no whitespace and does not start with "/"`);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+ ts.forEachChild(node, visit);
63
+ };
64
+
65
+ visit(source);
66
+ return diagnostics;
67
+ },
68
+ };
@@ -0,0 +1,62 @@
1
+ import * as ts from "typescript";
2
+ import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
3
+ import { calleeName, COMPONENT_CLASS, literalText, position, propertyName } from "./otel-ast";
4
+
5
+ /** Keys whose value is a credential. `*_file` keys name a path, which is fine. */
6
+ const SECRET_KEY = /(authorization|api[-_]?key|password|passwd|secret|token|key_pem|x-honeycomb-team)/i;
7
+
8
+ /**
9
+ * OTEL002: a credential written as a literal in a component's config.
10
+ *
11
+ * Exporters authenticate with headers (`authorization`, `api-key`, vendor
12
+ * headers) or keys (`api_key`, `password`, `key_pem`). A literal value there
13
+ * is a secret in the repository and in the emitted YAML. The collector reads
14
+ * `${env:NAME}` and `${file:/path}` at start-up, so the declaration never
15
+ * needs the value itself. Any string containing `${` is treated as such a
16
+ * reference, and keys ending in `_file` name a path and are left alone.
17
+ */
18
+ export const literalCredentialRule: LintRule = {
19
+ id: "OTEL002",
20
+ severity: "error",
21
+ category: "security",
22
+ description: "Collector credential declared as a literal instead of ${env:...}",
23
+
24
+ check(context: LintContext): LintDiagnostic[] {
25
+ const diagnostics: LintDiagnostic[] = [];
26
+ const source = context.sourceFile;
27
+
28
+ const scan = (obj: ts.ObjectLiteralExpression) => {
29
+ for (const prop of obj.properties) {
30
+ if (!ts.isPropertyAssignment(prop)) continue;
31
+ const key = propertyName(prop);
32
+ const init = prop.initializer;
33
+ if (ts.isObjectLiteralExpression(init)) {
34
+ scan(init);
35
+ continue;
36
+ }
37
+ if (!key || !SECRET_KEY.test(key) || /_file$/i.test(key)) continue;
38
+ const text = literalText(init);
39
+ if (text === undefined || text === "" || text.includes("${")) continue;
40
+ diagnostics.push({
41
+ ruleId: "OTEL002",
42
+ severity: "error",
43
+ message: `\`${key}\` is a literal credential; write "\${env:NAME}" (or "\${file:/path}") and let the collector read it at start-up`,
44
+ file: context.filePath,
45
+ ...position(source, init),
46
+ });
47
+ }
48
+ };
49
+
50
+ const visit = (node: ts.Node) => {
51
+ if (ts.isNewExpression(node) || ts.isCallExpression(node)) {
52
+ const name = calleeName(node);
53
+ const arg = node.arguments?.[0];
54
+ if (name && COMPONENT_CLASS.test(name) && arg && ts.isObjectLiteralExpression(arg)) scan(arg);
55
+ }
56
+ ts.forEachChild(node, visit);
57
+ };
58
+
59
+ visit(source);
60
+ return diagnostics;
61
+ },
62
+ };
@@ -0,0 +1,30 @@
1
+ import * as ts from "typescript";
2
+
3
+ /** The constructor name of a `new X(...)` or `X(...)` expression, if it is a plain or dotted name. */
4
+ export function calleeName(node: ts.CallExpression | ts.NewExpression): string | undefined {
5
+ const callee = node.expression;
6
+ if (ts.isIdentifier(callee)) return callee.text;
7
+ if (ts.isPropertyAccessExpression(callee)) return callee.name.text;
8
+ return undefined;
9
+ }
10
+
11
+ /** Built-in and custom collector component classes follow this naming, e.g. `OtlpExporter`. */
12
+ export const COMPONENT_CLASS = /(Receiver|Processor|Exporter|Extension)$/;
13
+
14
+ /** A property's key as text, for identifier and string-literal keys. */
15
+ export function propertyName(prop: ts.ObjectLiteralElementLike): string | undefined {
16
+ if (!ts.isPropertyAssignment(prop)) return undefined;
17
+ if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) return prop.name.text;
18
+ return undefined;
19
+ }
20
+
21
+ /** The text of a string literal or substitution-free template, else undefined. */
22
+ export function literalText(node: ts.Expression): string | undefined {
23
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
24
+ return undefined;
25
+ }
26
+
27
+ export function position(source: ts.SourceFile, node: ts.Node): { line: number; column: number } {
28
+ const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
29
+ return { line: line + 1, column: character + 1 };
30
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Shared plumbing for the otel post-synth checks: find the collector configs
3
+ * in a build's output, and run the plain-function checks over them.
4
+ */
5
+
6
+ import type { PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
7
+ import type { SerializerResult } from "@intentius/chant/serializer";
8
+ import { loadAll } from "js-yaml";
9
+ import type { Declarable } from "@intentius/chant/declarable";
10
+ import { looksLikeCollectorConfig, type CollectorConfig } from "../../model";
11
+ import { validateCollectorConfig, validateCollectorEntities, type CollectorIssueCode, type CollectorIssue } from "../../validate-config";
12
+
13
+ /**
14
+ * Every collector config in the output: the otel lexicon's own, and any YAML
15
+ * document shaped like one. Parsed with js-yaml rather than `ctx.docs`,
16
+ * because collector configs are written with flow lists (`[otlp, batch]`)
17
+ * that core's small YAML reader keeps as strings.
18
+ */
19
+ export function collectorConfigs(ctx: PostSynthContext): Array<{ source: string; config: CollectorConfig }> {
20
+ const out: Array<{ source: string; config: CollectorConfig }> = [];
21
+ for (const [lexicon, output] of ctx.outputs) {
22
+ const texts: Array<[string, string]> =
23
+ typeof output === "string"
24
+ ? [[lexicon, output]]
25
+ : [[lexicon, (output as SerializerResult).primary], ...Object.entries((output as SerializerResult).files ?? {})];
26
+ for (const [source, text] of texts) {
27
+ if (!text) continue;
28
+ let docs: unknown[];
29
+ try {
30
+ docs = loadAll(text);
31
+ } catch {
32
+ continue; // not YAML, or not ours
33
+ }
34
+ for (const doc of docs) {
35
+ if (typeof doc !== "object" || doc === null || Array.isArray(doc)) continue;
36
+ if ((lexicon === "otel" && source === lexicon) || looksLikeCollectorConfig(doc)) {
37
+ out.push({ source, config: doc as CollectorConfig });
38
+ }
39
+ }
40
+ }
41
+ }
42
+ return out;
43
+ }
44
+
45
+ function toDiagnostic(issue: CollectorIssue, source?: string): PostSynthDiagnostic {
46
+ return {
47
+ checkId: issue.code,
48
+ severity: issue.severity,
49
+ message: source && source !== "otel" ? `${source}: ${issue.message}` : issue.message,
50
+ ...(issue.component ? { entity: issue.component } : issue.pipeline ? { entity: issue.pipeline } : {}),
51
+ lexicon: "otel",
52
+ };
53
+ }
54
+
55
+ /** Diagnostics for one config-level code (OTEL101-OTEL106) across every collector config in the output. */
56
+ export function configDiagnostics(ctx: PostSynthContext, code: CollectorIssueCode): PostSynthDiagnostic[] {
57
+ return collectorConfigs(ctx).flatMap(({ source, config }) =>
58
+ validateCollectorConfig(config)
59
+ .filter((i) => i.code === code)
60
+ .map((i) => toDiagnostic(i, source)),
61
+ );
62
+ }
63
+
64
+ /** Diagnostics for one entity-level code (OTEL107-OTEL109) over the build's otel entities. */
65
+ export function entityDiagnostics(ctx: PostSynthContext, code: CollectorIssueCode): PostSynthDiagnostic[] {
66
+ const otel: Declarable[] = [...(ctx.entities?.values() ?? [])].filter((e) => e?.lexicon === "otel");
67
+ return validateCollectorEntities(otel)
68
+ .filter((i) => i.code === code)
69
+ .map((i) => toDiagnostic(i));
70
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OTEL101: A pipeline uses a receiver, processor or exporter that is not declared
3
+ *
4
+ * The collector refuses to start on this. Typed entity references cannot go wrong this way, so it mostly catches id strings (`"otlp/backend"`) and hand-edited or parsed configs. A connector id is accepted in receivers and exporters.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { configDiagnostics } from "./otel-helpers";
9
+
10
+ export const otel101: PostSynthCheck = {
11
+ id: "OTEL101",
12
+ description: "A pipeline uses a receiver, processor or exporter that is not declared",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return configDiagnostics(ctx, "OTEL101");
16
+ },
17
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OTEL102: A pipeline has no receivers or no exporters
3
+ *
4
+ * A pipeline with no receivers takes in nothing, and one with no exporters sends what it takes in nowhere. The collector rejects both at start.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { configDiagnostics } from "./otel-helpers";
9
+
10
+ export const otel102: PostSynthCheck = {
11
+ id: "OTEL102",
12
+ description: "A pipeline has no receivers or no exporters",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return configDiagnostics(ctx, "OTEL102");
16
+ },
17
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OTEL103: A declared component is never used
3
+ *
4
+ * A receiver, processor or exporter no pipeline lists, or an extension missing from service.extensions, is ignored by the collector. Usually it means a pipeline was meant to list it. A warning, since the config still runs.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { configDiagnostics } from "./otel-helpers";
9
+
10
+ export const otel103: PostSynthCheck = {
11
+ id: "OTEL103",
12
+ description: "A declared component is never used",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return configDiagnostics(ctx, "OTEL103");
16
+ },
17
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OTEL104: service.extensions enables an extension that is not declared
3
+ *
4
+ * The collector refuses to start when service.extensions names an id with no entry under extensions.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { configDiagnostics } from "./otel-helpers";
9
+
10
+ export const otel104: PostSynthCheck = {
11
+ id: "OTEL104",
12
+ description: "service.extensions enables an extension that is not declared",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return configDiagnostics(ctx, "OTEL104");
16
+ },
17
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OTEL105: memory_limiter is not the first processor in a pipeline
3
+ *
4
+ * The collector's own guidance puts memory_limiter first, so it can refuse data before any other processor buffers it. Later in the chain it limits too late to protect the process.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { configDiagnostics } from "./otel-helpers";
9
+
10
+ export const otel105: PostSynthCheck = {
11
+ id: "OTEL105",
12
+ description: "memory_limiter is not the first processor in a pipeline",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return configDiagnostics(ctx, "OTEL105");
16
+ },
17
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OTEL106: A pipeline id or component reference is not valid collector syntax
3
+ *
4
+ * A pipeline id is a signal (traces, metrics, logs) optionally followed by /name, and every reference is type or type/name. Anything else is a config error at collector start.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { configDiagnostics } from "./otel-helpers";
9
+
10
+ export const otel106: PostSynthCheck = {
11
+ id: "OTEL106",
12
+ description: "A pipeline id or component reference is not valid collector syntax",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return configDiagnostics(ctx, "OTEL106");
16
+ },
17
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OTEL107: A component's config breaks its definition's rules
3
+ *
4
+ * Each component definition, built-in or custom, can check its own config beyond what the TypeScript type says: memory_limiter needs a limit, filelog needs an include, and so on. A custom component supplies its checks through defineComponent's validate.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { entityDiagnostics } from "./otel-helpers";
9
+
10
+ export const otel107: PostSynthCheck = {
11
+ id: "OTEL107",
12
+ description: "A component's config breaks its definition's rules",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return entityDiagnostics(ctx, "OTEL107");
16
+ },
17
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OTEL108: Two components or two pipelines declare the same id
3
+ *
4
+ * The collector config is keyed by id, so the second declaration would silently replace the first. The serializer keeps the first and this check fails the build.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { entityDiagnostics } from "./otel-helpers";
9
+
10
+ export const otel108: PostSynthCheck = {
11
+ id: "OTEL108",
12
+ description: "Two components or two pipelines declare the same id",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return entityDiagnostics(ctx, "OTEL108");
16
+ },
17
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OTEL109: A custom component has no schema pin
3
+ *
4
+ * defineComponent requires a pin naming the source and version its config type follows, so a reader can tell which schema the component was checked against. This check fails a build whose custom component lost its pin, for instance through an untyped call.
5
+ */
6
+
7
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
8
+ import { entityDiagnostics } from "./otel-helpers";
9
+
10
+ export const otel109: PostSynthCheck = {
11
+ id: "OTEL109",
12
+ description: "A custom component has no schema pin",
13
+
14
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ return entityDiagnostics(ctx, "OTEL109");
16
+ },
17
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * OpenTelemetry Collector serializer.
3
+ *
4
+ * Emits one collector config file from every otel entity in the build: the
5
+ * YAML `otelcol --config` reads as it is. Section order is fixed (receivers,
6
+ * processors, exporters, extensions, service); components and pipelines keep
7
+ * the order they are declared in, and each component's config keeps the key
8
+ * order it was written in, since collector docs and diffs read that way.
9
+ *
10
+ * A collector config has no metadata channel, so there is no ownership marker
11
+ * to stamp. A custom component's schema pin is written as a `# chant:` comment
12
+ * line above the config (see `define.ts`).
13
+ */
14
+ import type { Serializer } from "@intentius/chant/serializer";
15
+ export declare const otelSerializer: Serializer;
16
+ //# sourceMappingURL=serializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAoB,MAAM,6BAA6B,CAAC;AAKhF,eAAO,MAAM,cAAc,EAAE,UAU5B,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** The otel lexicon's AI skills, read from src/skills/. */
2
+ export declare const otelSkills: () => import("packages/core/src").SkillDefinition[];
3
+ //# sourceMappingURL=skill-defs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-defs.d.ts","sourceRoot":"","sources":["../src/skill-defs.ts"],"names":[],"mappings":"AAEA,2DAA2D;AAC3D,eAAO,MAAM,UAAU,qDAgCrB,CAAC"}
@@ -0,0 +1,51 @@
1
+ ---
2
+ skill: chant-otel-custom-components
3
+ description: Add a collector component chant doesn't ship (a vendor exporter, an in-house processor) with defineComponent, and pin its schema
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Custom collector components
8
+
9
+ chant ships a core set of collector components. For anything else, define the component once and use it like a built-in.
10
+
11
+ ```ts
12
+ import { defineComponent, Pipeline, OtlpReceiver } from "@intentius/chant-lexicon-otel";
13
+
14
+ export interface DatadogExporterConfig {
15
+ api: { key: string; site?: string };
16
+ traces?: { span_name_as_resource_name?: boolean };
17
+ }
18
+
19
+ export const DatadogExporter = defineComponent<DatadogExporterConfig>()({
20
+ kind: "exporter",
21
+ type: "datadog",
22
+ pin: {
23
+ source: "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter",
24
+ version: "v0.130.0",
25
+ },
26
+ validate: (c) => (c.api.key.includes("${") ? [] : ["api.key must come from ${env:...}"]),
27
+ endpoints: (c) => [`https://api.${c.api.site ?? "datadoghq.com"}`],
28
+ });
29
+
30
+ export const dd = new DatadogExporter({ name: "eu", api: { key: "${env:DD_API_KEY}", site: "datadoghq.eu" } });
31
+ export const traces = new Pipeline({ signal: "traces", receivers: [new OtlpReceiver({ protocols: { grpc: {} } })], exporters: [dd] });
32
+ ```
33
+
34
+ ## What you supply
35
+
36
+ | Field | Required | Meaning |
37
+ |---|---|---|
38
+ | `kind` | yes | receiver, processor, exporter or extension |
39
+ | `type` | yes | the collector type, the part of the id before `/` |
40
+ | `pin` | yes | `{ source, version, digest? }`: where the config schema comes from and which release the type follows |
41
+ | `validate` | no | a function returning problems, or any zod-compatible schema (anything with `safeParse`) |
42
+ | `endpoints` | no | where the component sends or listens, for `collectorTopology()` |
43
+ | `description` | no | one line for docs |
44
+
45
+ ## How it is checked and pinned
46
+
47
+ - The same serializer emits it, and OTEL101-OTEL108 apply to it as to any built-in. `validate` failures are OTEL107.
48
+ - The pin is written as a `# chant: exporter datadog/eu schema <source>@<version>` comment above the emitted config, and returned by `collectorTopology()`.
49
+ - A definition without a usable pin fails the build (OTEL109).
50
+ - chant records the pin; it does not fetch or verify the schema. Ship the definition in a package and the package version pins it for everyone who imports it.
51
+ - You cannot redefine a built-in type. Declare a named instance instead (`new OtlpExporter({ name: "vendor", ... })`).
@@ -0,0 +1,34 @@
1
+ ---
2
+ skill: chant-otel-platforms
3
+ description: Run a declared collector config on Kubernetes, GKE, Docker or Fly by rendering it into the platform's own config file
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Running the collector config on a platform
8
+
9
+ The otel lexicon produces collector YAML. A platform lexicon runs the collector and carries the YAML to it.
10
+
11
+ ## Rendering the YAML inside another lexicon
12
+
13
+ `collectorYaml(entities)` returns exactly what the otel serializer would emit for those entities. Use it wherever a platform needs the config as a string:
14
+
15
+ ```ts
16
+ import { OtlpReceiver, DebugExporter, Pipeline, collectorYaml } from "@intentius/chant-lexicon-otel";
17
+ import { ConfigMap } from "@intentius/chant-lexicon-k8s";
18
+
19
+ const otlp = new OtlpReceiver({ protocols: { grpc: { endpoint: "0.0.0.0:4317" } } });
20
+ const debug = new DebugExporter({ verbosity: "basic" });
21
+
22
+ export const collectorConfig = new ConfigMap({
23
+ metadata: { name: "otel-collector-config" },
24
+ data: { "config.yaml": collectorYaml([otlp, debug, new Pipeline({ signal: "traces", receivers: [otlp], exporters: [debug] })]) },
25
+ });
26
+ ```
27
+
28
+ ## GKE
29
+
30
+ The k8s lexicon's `GkeOtelCollector` composite is built this way: it declares an otlp receiver, batch and resourcedetection processors and a googlecloud exporter, renders them with `collectorYaml`, and mounts the result from a ConfigMap into a DaemonSet running under Workload Identity.
31
+
32
+ ## Checking a rendered config
33
+
34
+ `validateCollectorConfig(config)` runs OTEL101-OTEL106 over any parsed collector config, so a composite's generated YAML can be checked in its own tests.
@@ -0,0 +1,60 @@
1
+ ---
2
+ skill: chant-otel
3
+ description: Declare OpenTelemetry Collector config (receivers, processors, exporters, pipelines) as typed chant entities and build collector YAML
4
+ user-invocable: true
5
+ ---
6
+
7
+ # OpenTelemetry Collector config with chant
8
+
9
+ The otel lexicon (`@intentius/chant-lexicon-otel`) types collector config. Each receiver, processor, exporter and extension is an entity, pipelines reference them, and `chant build` writes one collector config file.
10
+
11
+ ## Project setup
12
+
13
+ ```ts
14
+ // chant.config.ts
15
+ export default { lexicons: ["otel"] };
16
+ ```
17
+
18
+ ## Declaring components
19
+
20
+ Every component class takes the collector's own config keys (snake_case, as in the collector docs) plus an optional `name`. The id is `type`, or `type/name` when `name` is set.
21
+
22
+ ```ts
23
+ import {
24
+ OtlpReceiver, MemoryLimiterProcessor, BatchProcessor, OtlpExporter, DebugExporter, Pipeline,
25
+ } from "@intentius/chant-lexicon-otel";
26
+
27
+ export const otlp = new OtlpReceiver({
28
+ protocols: { grpc: { endpoint: "0.0.0.0:4317" }, http: { endpoint: "0.0.0.0:4318" } },
29
+ });
30
+ export const limiter = new MemoryLimiterProcessor({ check_interval: "1s", limit_percentage: 80, spike_limit_percentage: 20 });
31
+ export const batch = new BatchProcessor({ timeout: "5s" });
32
+ export const backend = new OtlpExporter({ name: "backend", endpoint: "tempo.observability:4317", tls: { insecure: true } });
33
+
34
+ export const traces = new Pipeline({ signal: "traces", receivers: [otlp], processors: [limiter, batch], exporters: [backend] });
35
+ ```
36
+
37
+ Emits `receivers.otlp`, `processors.memory_limiter` and `processors.batch`, `exporters.otlp/backend`, and `service.pipelines.traces`.
38
+
39
+ ## Built-in set
40
+
41
+ | Kind | Types |
42
+ |---|---|
43
+ | receivers | otlp, prometheus, hostmetrics, filelog |
44
+ | processors | batch, memory_limiter, resource, attributes, k8sattributes, resourcedetection |
45
+ | exporters | otlp, otlphttp, debug, prometheus, googlecloud |
46
+ | extensions | health_check, pprof, zpages |
47
+
48
+ Anything else goes through `defineComponent`; see the `chant-otel-custom-components` skill.
49
+
50
+ ## Rules
51
+
52
+ - Reference components by entity where you can. A string id (`"otlp/backend"`) is allowed for a component declared elsewhere, and OTEL101 fails the build if nothing declares it.
53
+ - Put `memory_limiter` first in `processors` (OTEL105).
54
+ - Never write a credential literally. Use `"${env:NAME}"` and the collector reads it at start-up (OTEL002).
55
+ - Every pipeline needs at least one receiver and one exporter (OTEL102). A declared component no pipeline uses is a warning (OTEL103).
56
+ - Declared extensions are enabled in declaration order unless a `Service` lists `extensions` itself.
57
+
58
+ ## Reading the result
59
+
60
+ `collectorTopologyOf(entities)` returns the pipelines, each component's endpoints and schema pin, and for each exporter the signals it carries, as plain data.