From ca7282479f166d6ac8446b4651d90a73de6d9765 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:33:08 +0100 Subject: [PATCH] ci: Enforce lower_snake_case attribute keys in OTel naming check Add Rule G to check_otel_naming.py: every span-attribute key must be lower_snake_case (^[a-z][a-z0-9_]*$ per dot-separated segment). This catches camelCase, UPPERCASE, and spaces in keys, which the structural (dotted) and source (literal) rules did not. Document it in the script README and CONTRIBUTING.md. Co-Authored-By: Claude Opus 4.8 --- .github/scripts/otel-naming/README.md | 1 + .../scripts/otel-naming/check_otel_naming.py | 27 +++++++++++++++++++ CONTRIBUTING.md | 4 +++ 3 files changed, 32 insertions(+) diff --git a/.github/scripts/otel-naming/README.md b/.github/scripts/otel-naming/README.md index a510f90dfb..2a7708d512 100644 --- a/.github/scripts/otel-naming/README.md +++ b/.github/scripts/otel-naming/README.md @@ -36,6 +36,7 @@ hardcoded allowlist: | Rule | Check | | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | A | No stray dotted span-attribute key (only the derived resource keys may be dotted). | +| G | Attribute keys are `lower_snake_case` (`^[a-z][a-z0-9_]*$` per dot-segment) — no camelCase, UPPERCASE, or spaces. | | F | No string literals as attribute keys or span-name arguments in `setAttribute`/`addEvent`/`span`/`childSpan`. Attribute _values_ are exempt (runtime data). `*SpanNames.h` definitions are exempt. | | B | Every collector `spanmetrics.dimensions` name exists in the L1 key set. | | C | Every Tempo span-filter tag exists in the L1 key set. | diff --git a/.github/scripts/otel-naming/check_otel_naming.py b/.github/scripts/otel-naming/check_otel_naming.py index 5c8b647986..3a5e157444 100644 --- a/.github/scripts/otel-naming/check_otel_naming.py +++ b/.github/scripts/otel-naming/check_otel_naming.py @@ -41,6 +41,8 @@ Rules (each FAILS the build, when its inputs are present) --------------------------------------------------------- A No stray dotted span-attribute key. A dotted `.` used as a span attribute that is not in the derived resource-key set is a violation. + G Attribute keys must be lower_snake_case (^[a-z][a-z0-9_]*$ per segment). + Flags camelCase, UPPERCASE, spaces, and other stray characters. F No string literals as attribute keys/values or span-name arguments. Every setAttribute/addEvent/span/childSpan argument must reference a *SpanNames.h constant, never a "literal". Definitions inside *SpanNames.h are exempt. @@ -303,6 +305,9 @@ def main() -> None: # --- Rule A: no stray dotted span-attribute keys ----------------------- if l1_keys: run_rule_a(keys_by_header, dotted_allow, report) + # --- Rule G: keys must be lower_snake_case ----------------------------- + if l1_keys: + run_rule_g(keys_by_header, report) # --- Rule F: no string literals at telemetry call-sites ---------------- if headers: run_rule_f(root, report) @@ -363,6 +368,28 @@ def run_rule_a( report.ok("A: no stray dotted span-attribute keys") +# A lower_snake_case identifier segment: starts lowercase, then lowercase / +# digits / underscores. No uppercase, no spaces, no camelCase. +SNAKE_SEGMENT = re.compile(r"^[a-z][a-z0-9_]*$") + + +def run_rule_g(keys_by_header: Dict[Path, Set[str]], report: Report) -> None: + """Every attribute key must be lower_snake_case. Bare/underscore keys must + match ^[a-z][a-z0-9_]*$; dotted resource keys must be lowercase + dot-separated segments (each segment lower_snake_case). Flags camelCase, + UPPERCASE, spaces, and other stray characters.""" + found = False + for h in sorted(keys_by_header): + for key in sorted(keys_by_header[h]): + segments = key.split(".") + if all(SNAKE_SEGMENT.match(seg) for seg in segments): + continue + found = True + report.violation("G", h.name, key, "must be lower_snake_case") + if not found: + report.ok("G: all attribute keys are lower_snake_case") + + # Which argument positions of each call must be a constant (0-based). The # attribute VALUE position is intentionally absent: values are runtime data # (command names, hashes, counts), not naming-convention surface. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7ba59f1f6e..d09b5f504e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -321,6 +321,10 @@ other layer must match them. A CI check enforces this end to end. 5. Span names use `[.]` (dotted). Only attribute _keys_ follow rules 1–4. +All attribute keys are `lower_snake_case` (lowercase letters, digits, and +underscores; each dot-separated segment of a resource key likewise). No +camelCase, uppercase, or spaces. + Standard OpenTelemetry semantic-convention keys keep their canonical dotted form (e.g. `service.*` resource attributes, `http.*` span attributes); the "no dotted form" rule above applies to xrpl-custom keys, not to OTel-standard