@intentius/chant-lexicon-otel 0.124.31 → 0.124.32

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 (96) hide show
  1. package/dist/attribution.js +391 -0
  2. package/dist/catalog.js +42 -0
  3. package/dist/codegen/docs-cli.js +3 -0
  4. package/dist/codegen/docs.js +101 -0
  5. package/dist/codegen/generate-cli.js +7 -0
  6. package/dist/codegen/generate.js +43 -0
  7. package/dist/codegen/package.js +28 -0
  8. package/dist/collector.js +150 -0
  9. package/dist/components/common.js +50 -0
  10. package/dist/components/connectors.js +242 -0
  11. package/dist/components/exporters.js +57 -0
  12. package/dist/components/extensions.js +58 -0
  13. package/dist/components/filtering.js +106 -0
  14. package/dist/components/index.js +9 -0
  15. package/dist/components/k8s-receivers.js +36 -0
  16. package/dist/components/processors.js +103 -0
  17. package/dist/components/receivers.js +61 -0
  18. package/dist/components/sampling.js +257 -0
  19. package/dist/composites/catalog.js +368 -0
  20. package/dist/composites/genai-pipeline.js +28 -0
  21. package/dist/composites/index.js +8 -0
  22. package/dist/composites/node-agent.js +156 -0
  23. package/dist/composites/otlp-collector.js +57 -0
  24. package/dist/composites/red-metrics.js +95 -0
  25. package/dist/composites/tail-sampling-tier.js +132 -0
  26. package/dist/config-hygiene.js +464 -0
  27. package/dist/configmap.js +86 -0
  28. package/dist/define.js +204 -0
  29. package/dist/detect.js +22 -0
  30. package/dist/genai.js +569 -0
  31. package/dist/import/embedded.js +57 -0
  32. package/dist/import/generator.js +459 -0
  33. package/dist/import/parser.js +199 -0
  34. package/dist/index.js +34 -0
  35. package/dist/init-templates.js +119 -0
  36. package/dist/integrity.json +2 -2
  37. package/dist/lint/audit-catalog.js +49 -0
  38. package/dist/lint/post-synth/index.js +52 -0
  39. package/dist/lint/post-synth/otel-helpers.js +103 -0
  40. package/dist/lint/post-synth/otel101.js +13 -0
  41. package/dist/lint/post-synth/otel102.js +13 -0
  42. package/dist/lint/post-synth/otel103.js +13 -0
  43. package/dist/lint/post-synth/otel104.js +13 -0
  44. package/dist/lint/post-synth/otel105.js +13 -0
  45. package/dist/lint/post-synth/otel106.js +13 -0
  46. package/dist/lint/post-synth/otel107.js +13 -0
  47. package/dist/lint/post-synth/otel108.js +13 -0
  48. package/dist/lint/post-synth/otel109.js +13 -0
  49. package/dist/lint/post-synth/otel112.js +13 -0
  50. package/dist/lint/post-synth/otel113.js +13 -0
  51. package/dist/lint/post-synth/otel114.js +13 -0
  52. package/dist/lint/post-synth/otel115.js +13 -0
  53. package/dist/lint/post-synth/otel116.js +13 -0
  54. package/dist/lint/post-synth/otel117.js +13 -0
  55. package/dist/lint/post-synth/otel118.js +13 -0
  56. package/dist/lint/post-synth/otel119.js +13 -0
  57. package/dist/lint/post-synth/otel120.js +13 -0
  58. package/dist/lint/post-synth/otel121.js +13 -0
  59. package/dist/lint/post-synth/otel122.js +13 -0
  60. package/dist/lint/post-synth/otel123.js +13 -0
  61. package/dist/lint/post-synth/otel124.js +13 -0
  62. package/dist/lint/post-synth/otel125.js +13 -0
  63. package/dist/lint/post-synth/otel126.js +13 -0
  64. package/dist/lint/post-synth/otel127.js +13 -0
  65. package/dist/lint/rules/component-id-syntax.js +66 -0
  66. package/dist/lint/rules/index.js +6 -0
  67. package/dist/lint/rules/literal-credential.js +91 -0
  68. package/dist/lint/rules/otel-ast.js +30 -0
  69. package/dist/lsp/completions.js +11 -0
  70. package/dist/lsp/hover.js +23 -0
  71. package/dist/manifest.json +1 -1
  72. package/dist/metric-names.js +244 -0
  73. package/dist/model.js +90 -0
  74. package/dist/op/activities/collector-audit.js +213 -0
  75. package/dist/op/activities/collector-health.js +176 -0
  76. package/dist/op/activities/exec.js +16 -0
  77. package/dist/op/activities/github.js +110 -0
  78. package/dist/op/activities/index.js +16 -0
  79. package/dist/op/activities/otelcol.js +173 -0
  80. package/dist/op/activity-contracts.js +64 -0
  81. package/dist/op/audit-op.js +33 -0
  82. package/dist/op/builders.js +38 -0
  83. package/dist/package-cli.js +14 -0
  84. package/dist/pipeline.js +31 -0
  85. package/dist/platform.js +144 -0
  86. package/dist/plugin.js +126 -0
  87. package/dist/semconv.js +50 -0
  88. package/dist/serializer.js +26 -0
  89. package/dist/skill-defs.js +34 -0
  90. package/dist/topology.js +104 -0
  91. package/dist/validate-cli.js +7 -0
  92. package/dist/validate-config.js +706 -0
  93. package/dist/validate.js +85 -0
  94. package/dist/yaml.js +157 -0
  95. package/package.json +8 -8
  96. package/src/codegen/package.ts +2 -2
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ // OpenTelemetry Collector lexicon.
2
+ // Plugin and serializer
3
+ export { otelPlugin } from "./plugin.js";
4
+ export { otelSerializer } from "./serializer.js";
5
+ // Built-in components and their config types
6
+ export * from "./components/index.js";
7
+ // Pipelines and the service block
8
+ export { Pipeline, Service, PIPELINE_TYPE, SERVICE_TYPE, isPipelineEntity, isServiceEntity, } from "./pipeline.js";
9
+ // The extension point: components chant doesn't ship
10
+ export { defineComponent, definitionFor, definitionOf, registeredDefinitions, componentEntityType, isOTelComponent, COLLECTOR_PIN, SEMCONV_PIN, GENAI_SEMCONV_PIN, } from "./define.js";
11
+ // The plain-data model, YAML, checks and topology
12
+ export * from "./model.js";
13
+ export { buildCollectorConfig, collectorConfig, collectorYaml, componentConfig } from "./collector.js";
14
+ export { emitCollectorYaml } from "./yaml.js";
15
+ export { configMapCollectorConfigs, describeConfigMapConfig, describeOperatorCollectorConfig, operatorCollectorConfig, parseCollectorConfig, } from "./configmap.js";
16
+ export { validateCollectorConfig, validateCollectorEntities, } from "./validate-config.js";
17
+ export { K8S_ATTRIBUTES_METADATA, RESOURCE_DETECTORS } from "./config-hygiene.js";
18
+ export { collectorTopology, collectorTopologyOf, } from "./topology.js";
19
+ export { semconvUsage, SEMCONV_VOCABULARIES } from "./semconv.js";
20
+ export { attributionIssues, isProtectedResourceAttribute, PROTECTED_RESOURCE_ATTRIBUTES } from "./attribution.js";
21
+ // Composites
22
+ export { NodeAgent, nodeAgentPropsProblem } from "./composites/index.js";
23
+ export { RedMetrics, redMetricsNames, redMetricsPropsProblem, GenAiPipeline, OtlpCollector, otlpCollectorPropsProblem, TailSamplingTier, tailSamplingTierPropsProblem, tailSamplingLoadBalancer, } from "./composites/index.js";
24
+ // What the platform collector composites (docker, k8s, fly) share
25
+ export { otlpCollector, otlpCollectorParts, collectorEndpoints, COLLECTOR_IMAGE, COLLECTOR_CONFIG_PATH, } from "./platform.js";
26
+ // The GenAI preset: content removal and agent RED and token metrics
27
+ export { genAiPipeline, genAiPipelineParts, genAiComponents, genAiMetrics, GENAI_ATTRIBUTES, GENAI_CONTENT_ATTRIBUTES, GENAI_CONTENT_EVENTS, GENAI_INDEXED_CONTENT_PATTERN, GENAI_HIGH_CARDINALITY_ATTRIBUTES, genAiCardinalityRisk, GENAI_SPAN_METRIC_DIMENSIONS, GENAI_TOKEN_DIMENSIONS, GENAI_DURATION_BUCKETS, GENAI_UNKNOWN_MODEL, GENAI_PROVIDER_DIMENSIONS, GENAI_CLIENT_METRIC_NAMES, GENAI_CLIENT_METRIC_ATTRIBUTES, GENAI_CLIENT_DURATION_BUCKETS, GENAI_CLIENT_TOKEN_BUCKETS, GENAI_TOKEN_TYPES, DELTA_READY_EXPORTERS, genAiNeedsDeltaToCumulative, } from "./genai.js";
28
+ // Metric names as Prometheus serves them, for dashboards and SLOs built from a declaration
29
+ export { spanMetricsNames, prometheusMetricName, prometheusLabel, SPANMETRICS_DEFAULT_DIMENSIONS, SPANMETRICS_DEFAULT_NAMESPACE, SPAN_STATUS_ERROR, SERVICEGRAPH_NAMESPACE, serviceGraphNames, promSelector, promSumRate, promErrorRatio, promQuantile, promNumber, spanKindMatchers, spanMetricsRedQueries, SPAN_METRICS_KINDS, RED_DEFAULT_SPAN_KINDS, } from "./metric-names.js";
30
+ // Op steps (#3369): the collector binary's checks, collectors as resources a
31
+ // ConvergeOp observes, and the pin audit. The activities resolve from
32
+ // `@intentius/chant-lexicon-otel/op/activities`.
33
+ export { otelcolValidate, otelcolComponents, collectorHealthObserve, collectorAudit } from "./op/builders.js";
34
+ export { CollectorAuditOp } from "./op/audit-op.js";
@@ -0,0 +1,119 @@
1
+ // ── default ────────────────────────────────────────────────────────────
2
+ const DEFAULT_COLLECTOR = `/**
3
+ * A collector: OTLP in, batched, traces out to an OTLP backend and logs to
4
+ * the collector's own output. \`chant build\` writes the config
5
+ * \`otelcol --config\` reads.
6
+ *
7
+ * Nested settings are named consts typed with the lexicon's config types, so
8
+ * each constructor stays flat.
9
+ */
10
+ import {
11
+ OtlpReceiver,
12
+ MemoryLimiterProcessor,
13
+ BatchProcessor,
14
+ OtlpExporter,
15
+ DebugExporter,
16
+ HealthCheckExtension,
17
+ Pipeline,
18
+ type OtlpReceiverConfig,
19
+ type TLSClientSettings,
20
+ type QueueSettings,
21
+ } from "@intentius/chant-lexicon-otel";
22
+
23
+ const protocols: OtlpReceiverConfig["protocols"] = {
24
+ grpc: { endpoint: "0.0.0.0:4317" },
25
+ http: { endpoint: "0.0.0.0:4318" },
26
+ };
27
+ const otlp = new OtlpReceiver({ protocols });
28
+
29
+ const memoryLimiter = new MemoryLimiterProcessor({ check_interval: "1s", limit_percentage: 80, spike_limit_percentage: 20 });
30
+ const batch = new BatchProcessor({ timeout: "5s" });
31
+
32
+ // Point this at your tracing backend. \`otlp/backend\`: the instance name leaves room for a second otlp exporter.
33
+ const plaintext: TLSClientSettings = { insecure: true };
34
+ const queue: QueueSettings = { enabled: true, queue_size: 5000 };
35
+ const backend = new OtlpExporter({ name: "backend", endpoint: "tempo:4317", tls: plaintext, sending_queue: queue });
36
+
37
+ const debug = new DebugExporter({ verbosity: "basic" });
38
+
39
+ const health = new HealthCheckExtension({ endpoint: "0.0.0.0:13133" });
40
+
41
+ const traces = new Pipeline({ signal: "traces", receivers: [otlp], processors: [memoryLimiter, batch], exporters: [backend] });
42
+ const logs = new Pipeline({ signal: "logs", receivers: [otlp], processors: [memoryLimiter, batch], exporters: [debug] });
43
+
44
+ export { otlp, memoryLimiter, batch, backend, debug, health, traces, logs };
45
+ `;
46
+ // ── k8s-agent ──────────────────────────────────────────────────────────
47
+ const AGENT_EXPORTERS = `/**
48
+ * Where the agent sends what it collects: traces and logs to the gateway
49
+ * collectors, metrics served on each node for Prometheus to scrape.
50
+ */
51
+ import { OtlpExporter, PrometheusExporter, type RetrySettings, type TLSClientSettings } from "@intentius/chant-lexicon-otel";
52
+
53
+ // The gateway Service. With the k8s lexicon's OtelCollectorGateway, use its gatewayExporter() instead.
54
+ const inCluster: TLSClientSettings = { insecure: true };
55
+ const retry: RetrySettings = { enabled: true, max_elapsed_time: "300s" };
56
+ const gateway = new OtlpExporter({ name: "gateway", endpoint: "otel-gateway.observability.svc:4317", tls: inCluster, retry_on_failure: retry });
57
+
58
+ // Resource attributes (k8s.namespace.name, k8s.pod.name, ...) become metric labels.
59
+ const asLabels = { enabled: true };
60
+ const scrape = new PrometheusExporter({ endpoint: "0.0.0.0:8889", resource_to_telemetry_conversion: asLabels });
61
+
62
+ export { gateway, scrape };
63
+ `;
64
+ const AGENT = `/**
65
+ * The config each node's collector runs. The pod needs the node name in
66
+ * K8S_NODE_NAME (from spec.nodeName), the host root mounted read-only at
67
+ * /hostfs, /var/log/pods mounted read-only, and a service account that can
68
+ * read pods, namespaces, nodes and replicasets (and nodes/stats, for kubelet
69
+ * stats). Pass \`Object.values(agent.members)\` as the k8s lexicon's
70
+ * OtelCollector \`config\` to run it as a DaemonSet: it adds all of these,
71
+ * worked out from the config.
72
+ */
73
+ import { NodeAgent } from "@intentius/chant-lexicon-otel";
74
+ import { gateway, scrape } from "./exporters";
75
+
76
+ export const agent = NodeAgent({
77
+ exporters: [gateway],
78
+ metricExporters: [scrape],
79
+ clusterName: "my-cluster",
80
+ kubeletStats: true,
81
+ });
82
+ `;
83
+ // ── genai ──────────────────────────────────────────────────────────────
84
+ const GENAI_COLLECTOR = `/**
85
+ * A collector for an agent service instrumented with the OpenTelemetry GenAI
86
+ * conventions. Prompts, completions and tool payloads are removed before
87
+ * anything leaves the collector. Every GenAI span also becomes call, error,
88
+ * duration and token metrics, and the conventions' own client metrics
89
+ * (gen_ai.client.operation.duration, gen_ai.client.token.usage), served for
90
+ * Prometheus to scrape. Traces go to Tempo.
91
+ */
92
+ import { genAiPipeline, OtlpExporter, PrometheusExporter, type TLSClientSettings } from "@intentius/chant-lexicon-otel";
93
+
94
+ const plaintext: TLSClientSettings = { insecure: true };
95
+ const tempo = new OtlpExporter({ name: "tempo", endpoint: "tempo:4317", tls: plaintext });
96
+
97
+ const prometheus = new PrometheusExporter({ endpoint: "0.0.0.0:8889" });
98
+
99
+ // Content stays out unless keepContent: true is set here. Card-number-like values are masked in whatever is left.
100
+ const cardNumbers = ["\\\\b[0-9]{13,16}\\\\b"];
101
+
102
+ export const collector = genAiPipeline({
103
+ traceExporters: [tempo],
104
+ metricExporters: [prometheus],
105
+ clientMetrics: "derive",
106
+ maskValues: cardNumbers,
107
+ });
108
+ `;
109
+ export const DEFAULT_TEMPLATE = {
110
+ src: { "collector.ts": DEFAULT_COLLECTOR },
111
+ };
112
+ export const K8S_AGENT_TEMPLATE = {
113
+ src: { "exporters.ts": AGENT_EXPORTERS, "agent.ts": AGENT },
114
+ };
115
+ export const GENAI_TEMPLATE = {
116
+ src: { "collector.ts": GENAI_COLLECTOR },
117
+ };
118
+ /** The template names `chant init --lexicon otel --template <name>` takes, besides the default. */
119
+ export const TEMPLATE_NAMES = ["k8s-agent", "genai"];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "9b8ceae301c6c60ae68b4788234ddabe3158959f7a6caca52e087019ffb2b51a",
4
+ "manifest.json": "83651d5f0444ccab5144c34f0b19ac93d488b65267eb532b5c6de8ac21ab8d47",
5
5
  "meta.json": "884042882639c4dd91a8a97b9826869de90553ec4420fd1e6a0b08bb16fc5181",
6
6
  "types/index.d.ts": "a1070fd1cb3e8d8a9002fc5a75a80b3bdda02965be351e8aed2dbc2f63d36e3c",
7
7
  "rules/component-id-syntax.ts": "5d01dc32a10843e47e27c4350c841af5638b5423c653bbe8397f1e4f57271756",
@@ -37,5 +37,5 @@
37
37
  "skills/chant-otel-custom-components.md": "82e53bb482281f79b428e66fb80a1c194ead74a1a34a88bbebb11c6ad5493258",
38
38
  "skills/chant-otel-platforms.md": "c90836952dc5c88d668b890152a5063bdb656bce73fc7fa3233a3ea58ad85686"
39
39
  },
40
- "composite": "2c87a4fb076d2b4e6ec079da3a955039bef4283845c355af1e9f7d65f6837afe"
40
+ "composite": "04842558ab398b92b3aa388bdd4d5f1ad4c90ff7988ab9f760eb030d87aa5dbd"
41
41
  }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The otel lexicon's chant audit catalog, contributed via
3
+ * `otelPlugin.auditCatalog()` (#687, #1346).
4
+ *
5
+ * OTEL101-OTEL106, OTEL112-OTEL117 and OTEL119-OTEL127 read the emitted collector YAML, so they are
6
+ * `yamlBased`. OTEL118 reads it too, but only in a build that stamps
7
+ * telemetry attribution, so it is not.
8
+ * OTEL107-OTEL109 read the declared entities (a component's definition, its
9
+ * schema pin), which a standalone YAML file does not carry, so they are
10
+ * constructed with `yamlBased: false`. The two source-level lint rules are
11
+ * listed too, for a reader who meets them in a lint report.
12
+ */
13
+ import { auditRule } from "@intentius/chant/audit/catalog";
14
+ function entityRule(id, category, title, remediation) {
15
+ return { id, tier: "merge-worthy", fixKind: "guidance", category, title, remediation, yamlBased: false };
16
+ }
17
+ export const otelAuditCatalog = {
18
+ OTEL001: entityRule("OTEL001", "correctness", "Collector component id or instance name is not valid syntax", 'Write component ids as `type` or `type/name` (e.g. "otlp/backend") and give instances a non-empty name with no whitespace.'),
19
+ OTEL002: entityRule("OTEL002", "security", "Collector credential declared as a literal", 'Replace the value with `${env:NAME}` or `${file:/path}` so the collector reads it at start-up.'),
20
+ OTEL101: auditRule("OTEL101", "merge-worthy", "guidance", "Pipeline uses an undeclared component", "Declare the receiver, processor or exporter under its section, or reference the declared entity instead of an id string. List a connector as an exporter in one pipeline and a receiver in another.", { category: "correctness" }),
21
+ OTEL102: auditRule("OTEL102", "merge-worthy", "guidance", "Pipeline has no receivers or no exporters", "Give every pipeline at least one receiver and at least one exporter, or remove it.", { category: "correctness" }),
22
+ OTEL103: auditRule("OTEL103", "report-only", "guidance", "Declared collector component is never used", "List the component in a pipeline (or the extension in service.extensions), or remove its declaration.", { category: "best-practice" }),
23
+ OTEL104: auditRule("OTEL104", "merge-worthy", "guidance", "service.extensions enables an undeclared extension", "Declare the extension under extensions, or drop it from service.extensions.", { category: "correctness" }),
24
+ OTEL105: auditRule("OTEL105", "report-only", "guidance", "memory_limiter is not the first processor", "Move memory_limiter to the front of the pipeline's processors list.", { category: "best-practice" }),
25
+ OTEL106: auditRule("OTEL106", "merge-worthy", "guidance", "Pipeline id or component reference is not valid collector syntax", "Name pipelines traces, metrics or logs (optionally /name) and reference components as type or type/name.", { category: "correctness" }),
26
+ OTEL107: entityRule("OTEL107", "correctness", "Collector component config breaks its definition's rules", "Fix the config the message names; for a custom component the rule comes from its defineComponent validate."),
27
+ OTEL108: entityRule("OTEL108", "correctness", "Two collector components or pipelines declare the same id", "Give each instance its own name so their ids (type/name) differ."),
28
+ OTEL109: entityRule("OTEL109", "correctness", "Custom collector component has no schema pin", "Pass defineComponent a pin with the source and version the component's config type follows."),
29
+ OTEL112: auditRule("OTEL112", "merge-worthy", "guidance", "Connector joins pipelines whose signals it does not convert", "Feed the connector from, and receive from it into, pipelines of the signals it supports (spanmetrics: traces in, metrics out).", { category: "correctness" }),
30
+ OTEL113: auditRule("OTEL113", "merge-worthy", "guidance", "Pipelines form a cycle through connectors", "Break the cycle the message names: drop one connector hop, or send that pipeline's data to an exporter instead of back upstream.", { category: "correctness" }),
31
+ OTEL114: auditRule("OTEL114", "merge-worthy", "guidance", "Connector id also declared as a receiver or exporter", 'Give the connector, or the receiver or exporter, its own name (e.g. "datadog/connector") and update the pipelines that list it.', { category: "correctness" }),
32
+ OTEL115: auditRule("OTEL115", "merge-worthy", "guidance", "Routing connector routes to a pipeline that does not receive from it", "List the routing connector in the receivers of every pipeline its table and default_pipelines name, or remove the pipeline from the route.", { category: "correctness" }),
33
+ OTEL116: auditRule("OTEL116", "merge-worthy", "guidance", "Connector splits metrics by a high-cardinality GenAI attribute", "Remove the conversation, response, tool call, session or user id, or the content key, from the connector's dimensions or attributes. Keep it on spans and logs, where a per-request value costs nothing extra.", { category: "efficiency" }),
34
+ OTEL117: auditRule("OTEL117", "merge-worthy", "guidance", "Two started components listen on the same address", "Give one of them another port, or a specific host that doesn't overlap the other's. The collector's own metrics listen on localhost:8888 unless service.telemetry.metrics sets a reader or level none.", { category: "correctness" }),
35
+ // Runs only in a build that stamps telemetry attribution, which a standalone YAML file never is.
36
+ OTEL118: {
37
+ ...auditRule("OTEL118", "report-only", "guidance", "Pipeline processor can remove or replace a telemetry attribution key", "Leave service.name, service.version, deployment.environment.name, vcs.ref.head.revision and chant.* keys alone: use insert rather than upsert or update, keep them in keep_keys and in redaction's allowed_keys, and set override: false on resourcedetection.", { category: "correctness" }),
38
+ yamlBased: false,
39
+ },
40
+ OTEL119: auditRule("OTEL119", "report-only", "guidance", "Collector config sets a field deprecated before the pinned release", "Replace invert_match with a drop policy, service.telemetry.metrics.address with readers, and spanmetrics dimensions_cache_size with aggregation_cardinality_limit.", { category: "best-practice" }),
41
+ OTEL120: auditRule("OTEL120", "merge-worthy", "guidance", "Collector credential written as a literal in the config", 'Replace the value with `${env:NAME}` or `${file:/path}` so the collector reads it at start-up.', { category: "security" }),
42
+ OTEL121: auditRule("OTEL121", "merge-worthy", "guidance", "Exporter sends a credential over plaintext", "Use an https:// endpoint, or drop tls.insecure, for an exporter that sends an authorization header or API key.", { category: "security" }),
43
+ OTEL122: auditRule("OTEL122", "merge-worthy", "guidance", "zpages or pprof listens on a non-loopback address", "Bind zpages and pprof to localhost and reach them with a port-forward.", { category: "security" }),
44
+ OTEL123: auditRule("OTEL123", "report-only", "guidance", "debug exporter at verbosity detailed beside a real exporter", "Set the debug exporter to verbosity basic, or keep detailed output to a pipeline that exports nowhere else.", { category: "security" }),
45
+ OTEL124: auditRule("OTEL124", "report-only", "guidance", "Remote exporter has its sending queue or retries turned off", "Remove sending_queue.enabled: false and retry_on_failure.enabled: false from exporters that send over the network.", { category: "best-practice" }),
46
+ OTEL125: auditRule("OTEL125", "report-only", "guidance", "Pipeline sends to a remote otlp or otlphttp exporter without batching", "Add a batch processor to the pipeline, or set sending_queue.batch on the exporter.", { category: "best-practice" }),
47
+ OTEL126: auditRule("OTEL126", "merge-worthy", "guidance", "k8sattributes extracts an unsupported metadata field", "Use a field from the k8sattributes list at the pinned release, or extract it as a label or annotation.", { category: "correctness" }),
48
+ OTEL127: auditRule("OTEL127", "merge-worthy", "guidance", "resourcedetection lists an unknown detector", "Name detectors as the processor registers them (elastic_beanstalk, not elasticbeanstalk), or remove the entry.", { category: "correctness" }),
49
+ };
@@ -0,0 +1,52 @@
1
+ import { otel101 } from "./otel101.js";
2
+ import { otel102 } from "./otel102.js";
3
+ import { otel103 } from "./otel103.js";
4
+ import { otel104 } from "./otel104.js";
5
+ import { otel105 } from "./otel105.js";
6
+ import { otel106 } from "./otel106.js";
7
+ import { otel107 } from "./otel107.js";
8
+ import { otel108 } from "./otel108.js";
9
+ import { otel109 } from "./otel109.js";
10
+ import { otel112 } from "./otel112.js";
11
+ import { otel113 } from "./otel113.js";
12
+ import { otel114 } from "./otel114.js";
13
+ import { otel115 } from "./otel115.js";
14
+ import { otel116 } from "./otel116.js";
15
+ import { otel117 } from "./otel117.js";
16
+ import { otel118 } from "./otel118.js";
17
+ import { otel119 } from "./otel119.js";
18
+ import { otel120 } from "./otel120.js";
19
+ import { otel121 } from "./otel121.js";
20
+ import { otel122 } from "./otel122.js";
21
+ import { otel123 } from "./otel123.js";
22
+ import { otel124 } from "./otel124.js";
23
+ import { otel125 } from "./otel125.js";
24
+ import { otel126 } from "./otel126.js";
25
+ import { otel127 } from "./otel127.js";
26
+ export const postSynthChecks = [
27
+ otel101,
28
+ otel102,
29
+ otel103,
30
+ otel104,
31
+ otel105,
32
+ otel106,
33
+ otel107,
34
+ otel108,
35
+ otel109,
36
+ otel112,
37
+ otel113,
38
+ otel114,
39
+ otel115,
40
+ otel116,
41
+ otel117,
42
+ otel118,
43
+ otel119,
44
+ otel120,
45
+ otel121,
46
+ otel122,
47
+ otel123,
48
+ otel124,
49
+ otel125,
50
+ otel126,
51
+ otel127,
52
+ ];
@@ -0,0 +1,103 @@
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
+ import { loadAll } from "js-yaml";
6
+ import { looksLikeCollectorConfig } from "../../model.js";
7
+ import { configMapCollectorConfigs, describeConfigMapConfig, describeOperatorCollectorConfig, operatorCollectorConfig } from "../../configmap.js";
8
+ import { validateCollectorConfig, validateCollectorEntities } from "../../validate-config.js";
9
+ import { attributionIssues } from "../../attribution.js";
10
+ /**
11
+ * Every collector config in the output: the otel lexicon's own, any YAML
12
+ * document shaped like one, and any ConfigMap data value that parses as one
13
+ * (how `OtelCollector`, `OtelCollectorGateway` and `GkeOtelCollector` carry
14
+ * theirs), and the `spec.config` of an OpenTelemetry Operator
15
+ * `OpenTelemetryCollector` (chant #3367). Parsed with js-yaml rather than `ctx.docs`, because collector
16
+ * configs are written with flow lists (`[otlp, batch]`) that core's small
17
+ * YAML reader keeps as strings.
18
+ *
19
+ * `chant build` hands each lexicon's checks only that lexicon's output, so
20
+ * the otel checks see ConfigMaps only in a context that includes k8s output.
21
+ * The k8s lexicon's WK8604 runs the same config checks over its own output.
22
+ */
23
+ export function collectorConfigs(ctx) {
24
+ const out = [];
25
+ for (const [lexicon, output] of ctx.outputs) {
26
+ const texts = typeof output === "string"
27
+ ? [[lexicon, output]]
28
+ : [[lexicon, output.primary], ...Object.entries(output.files ?? {})];
29
+ for (const [source, text] of texts) {
30
+ if (!text)
31
+ continue;
32
+ let docs;
33
+ try {
34
+ docs = loadAll(text);
35
+ }
36
+ catch {
37
+ continue; // not YAML, or not ours
38
+ }
39
+ for (const doc of docs) {
40
+ if (typeof doc !== "object" || doc === null || Array.isArray(doc))
41
+ continue;
42
+ if ((lexicon === "otel" && source === lexicon) || looksLikeCollectorConfig(doc)) {
43
+ out.push({ source, config: doc });
44
+ continue;
45
+ }
46
+ for (const { config, ...configMap } of configMapCollectorConfigs(doc)) {
47
+ out.push({ source, config, configMap });
48
+ }
49
+ const cr = operatorCollectorConfig(doc);
50
+ if (cr)
51
+ out.push({ source, config: cr.config, operator: { namespace: cr.namespace, name: cr.name } });
52
+ }
53
+ }
54
+ }
55
+ return out;
56
+ }
57
+ function toDiagnostic(issue, found) {
58
+ const where = found?.configMap
59
+ ? describeConfigMapConfig(found.configMap)
60
+ : found?.operator
61
+ ? describeOperatorCollectorConfig(found.operator)
62
+ : found && found.source !== "otel" ? found.source : undefined;
63
+ return {
64
+ checkId: issue.code,
65
+ severity: issue.severity,
66
+ message: where ? `${where}: ${issue.message}` : issue.message,
67
+ ...(issue.component ? { entity: issue.component } : issue.pipeline ? { entity: issue.pipeline } : {}),
68
+ lexicon: "otel",
69
+ };
70
+ }
71
+ /**
72
+ * The config-level issues for one config. OTEL118 joins them only when the
73
+ * build stamps the telemetry attribution (`ctx.telemetry`), so a level-0
74
+ * project never sees it.
75
+ */
76
+ function configIssues(ctx, config) {
77
+ const issues = validateCollectorConfig(config);
78
+ return ctx.telemetry ? [...issues, ...attributionIssues(config)] : issues;
79
+ }
80
+ /**
81
+ * Every config-level diagnostic (OTEL101-OTEL106, OTEL112-OTEL127 and any later
82
+ * config check) over the collector configs in the output. `configMapsOnly`
83
+ * keeps the configs held in ConfigMaps. `hostedOnly` keeps those and the `spec.config` of an operator `OpenTelemetryCollector`, which is what WK8604 reads.
84
+ */
85
+ export function collectorConfigDiagnostics(ctx, opts = {}) {
86
+ return collectorConfigs(ctx)
87
+ .filter((found) => !opts.configMapsOnly || found.configMap)
88
+ .filter((found) => !opts.hostedOnly || found.configMap || found.operator)
89
+ .flatMap(({ config, ...found }) => configIssues(ctx, config).map((i) => toDiagnostic(i, found)));
90
+ }
91
+ /** Diagnostics for one config-level code (OTEL101-OTEL106, OTEL112-OTEL127) across every collector config in the output. */
92
+ export function configDiagnostics(ctx, code) {
93
+ return collectorConfigs(ctx).flatMap(({ config, ...found }) => configIssues(ctx, config)
94
+ .filter((i) => i.code === code)
95
+ .map((i) => toDiagnostic(i, found)));
96
+ }
97
+ /** Diagnostics for one entity-level code (OTEL107-OTEL109) over the build's otel entities. */
98
+ export function entityDiagnostics(ctx, code) {
99
+ const otel = [...(ctx.entities?.values() ?? [])].filter((e) => e?.lexicon === "otel");
100
+ return validateCollectorEntities(otel)
101
+ .filter((i) => i.code === code)
102
+ .map((i) => toDiagnostic(i));
103
+ }
@@ -0,0 +1,13 @@
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, but a connector counts as used only when one pipeline lists it as an exporter and another lists it as a receiver; listed on one side only, it fails this check.
5
+ */
6
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel101 = {
8
+ id: "OTEL101",
9
+ description: "A pipeline uses a receiver, processor or exporter that is not declared",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL101");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
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
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel102 = {
8
+ id: "OTEL102",
9
+ description: "A pipeline has no receivers or no exporters",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL102");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OTEL103: A declared component is never used
3
+ *
4
+ * A receiver, processor, exporter or connector 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
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel103 = {
8
+ id: "OTEL103",
9
+ description: "A declared component is never used",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL103");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
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
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel104 = {
8
+ id: "OTEL104",
9
+ description: "service.extensions enables an extension that is not declared",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL104");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
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
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel105 = {
8
+ id: "OTEL105",
9
+ description: "memory_limiter is not the first processor in a pipeline",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL105");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
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
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel106 = {
8
+ id: "OTEL106",
9
+ description: "A pipeline id or component reference is not valid collector syntax",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL106");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
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
+ import { entityDiagnostics } from "./otel-helpers.js";
7
+ export const otel107 = {
8
+ id: "OTEL107",
9
+ description: "A component's config breaks its definition's rules",
10
+ check(ctx) {
11
+ return entityDiagnostics(ctx, "OTEL107");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
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
+ import { entityDiagnostics } from "./otel-helpers.js";
7
+ export const otel108 = {
8
+ id: "OTEL108",
9
+ description: "Two components or two pipelines declare the same id",
10
+ check(ctx) {
11
+ return entityDiagnostics(ctx, "OTEL108");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
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
+ import { entityDiagnostics } from "./otel-helpers.js";
7
+ export const otel109 = {
8
+ id: "OTEL109",
9
+ description: "A custom component has no schema pin",
10
+ check(ctx) {
11
+ return entityDiagnostics(ctx, "OTEL109");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OTEL112: A connector joins pipelines whose signals it does not convert
3
+ *
4
+ * Each connector supports fixed signal pairs: `spanmetrics` and `servicegraph` read traces and write metrics, `count` writes metrics from any signal, `routing` and `forward` keep the signal. Every pipeline that feeds a connector must pair with a pipeline it feeds through one of those pairs, and the other way round, or the collector refuses to start. A traces pipeline that receives from `spanmetrics` fails this check. Custom connectors are checked when their `defineComponent` call lists `connects`.
5
+ */
6
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel112 = {
8
+ id: "OTEL112",
9
+ description: "A connector joins pipelines whose signals it does not convert",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL112");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OTEL113: Pipelines form a cycle through connectors
3
+ *
4
+ * A connector carries data from the pipeline that lists it as an exporter to the pipeline that lists it as a receiver. When those hops lead back to a pipeline already on the path (traces/a feeds traces/b through one connector and traces/b feeds traces/a through another, or one pipeline lists the same `forward` connector on both sides), the collector refuses to start. The check walks the `edges` `collectorTopology()` returns and names one cycle per group of pipelines caught in it.
5
+ */
6
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel113 = {
8
+ id: "OTEL113",
9
+ description: "Pipelines form a cycle through connectors",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL113");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OTEL114: A connector id is also declared as a receiver or exporter
3
+ *
4
+ * A pipeline names a connector the same way it names a receiver or an exporter, so the collector refuses a config where a connector id is also declared under `receivers` or `exporters`, whether or not any pipeline uses it.
5
+ */
6
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel114 = {
8
+ id: "OTEL114",
9
+ description: "A connector id is also declared as a receiver or exporter",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL114");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OTEL115: A routing connector routes to a pipeline that does not receive from it
3
+ *
4
+ * Every pipeline a `routing` connector names in `table[].pipelines` or `default_pipelines` must list that connector in its receivers. The connector can only hand data to the pipelines it feeds, and the collector refuses to start when a route names any other pipeline or one that does not exist.
5
+ */
6
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel115 = {
8
+ id: "OTEL115",
9
+ description: "A routing connector routes to a pipeline that does not receive from it",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL115");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OTEL116: A connector splits metrics by a high-cardinality GenAI attribute
3
+ *
4
+ * `gen_ai.conversation.id`, `gen_ai.response.id`, `gen_ai.tool.call.id`, `session.id`, `user.id`, `enduser.id` and the other keys in `GENAI_HIGH_CARDINALITY_ATTRIBUTES` take a new value per response, conversation or user, and the content keys (`gen_ai.input.messages` and the rest of `GENAI_CONTENT_ATTRIBUTES`) are unbounded. Used as a metric attribute in `spanmetrics` or `servicegraph` dimensions, `sum` or `count` attributes, or `signaltometrics` attributes or `include_resource_attributes`, each value starts new time series. The collector accepts such a config without a warning.
5
+ */
6
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel116 = {
8
+ id: "OTEL116",
9
+ description: "A connector splits metrics by a high-cardinality GenAI attribute",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL116");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OTEL117: Two components the collector starts listen on the same address
3
+ *
4
+ * A receiver or exporter a pipeline lists, an extension in `service.extensions`, and the collector's own metrics endpoint (`localhost:8888` unless `service.telemetry.metrics` says otherwise) each bind an address when the collector starts. Two on the same port, on the same host or with either on a wildcard host (`0.0.0.0`, `::`), make the second bind fail with "address already in use", and the collector exits. `otelcol validate` does not bind, so it accepts such a config. The listeners checked are the otlp, zipkin and jaeger receivers, the prometheus exporter, and the health_check, zpages and pprof extensions, with their defaults at the pinned collector release.
5
+ */
6
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel117 = {
8
+ id: "OTEL117",
9
+ description: "Two components the collector starts listen on the same address",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL117");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OTEL118: A pipeline processor can remove or replace a resource attribute chant's telemetry attribution stamps
3
+ *
4
+ * A workload built in a workspace, or with `telemetry.attribution: true`, carries `service.name`, `service.version`, `deployment.environment.name`, `vcs.ref.head.revision` and `chant.*` resource attributes. A `resource` processor that deletes, updates, upserts or hashes one, a `transform` statement that sets, deletes or fails to keep one, or a `resourcedetection` processor with `override` on and a detector that writes one undoes that. The `transform` case matches OTTL text. The check runs only when the build stamps the attribution, so a level-0 project sees nothing.
5
+ */
6
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel118 = {
8
+ id: "OTEL118",
9
+ description: "A pipeline processor can remove or replace a resource attribute chant's telemetry attribution stamps",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL118");
12
+ },
13
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OTEL119: A collector config sets a field deprecated at or before the pinned release
3
+ *
4
+ * `invert_match: true` in a `tail_sampling` policy (inverted decisions are deprecated in collector-contrib v0.126.0 for a `drop` policy), `service.telemetry.metrics.address` (deprecated in collector v0.111.0 for `readers`), and `dimensions_cache_size` on `spanmetrics` (deprecated in collector-contrib v0.125.0 for `aggregation_cardinality_limit`). The collector still reads each at v0.130.0, and a later release drops it.
5
+ */
6
+ import { configDiagnostics } from "./otel-helpers.js";
7
+ export const otel119 = {
8
+ id: "OTEL119",
9
+ description: "A collector config sets a field deprecated at or before the pinned release",
10
+ check(ctx) {
11
+ return configDiagnostics(ctx, "OTEL119");
12
+ },
13
+ };