Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .chloggen/group-stability-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
change_type: enhancement
component: docs
Comment thread
lmolkova marked this conversation as resolved.
note: >
Don't allow signal definition to reference attributes with lower stability
than the signal itself (unless they are opt-in).
issues: [3752]
14 changes: 9 additions & 5 deletions docs/general/semantic-convention-groups.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ to preserve code generation and documentation for legacy instrumentations.

When group is renamed or no longer recommended, it SHOULD be deprecated.

A group SHOULD NOT reference attributes with a lower maturity level than the
group itself, unless the requirement level of that attribute is `opt_in`.
The requirement level of such an attribute reference MAY be changed once the
attribute reaches the group's stability level, in cases allowed by
[Versioning and Stability][Stability].
For example, a `stable` event may reference a `development` attribute only at
the `opt_in` requirement level; that requirement level may change once the
attribute becomes stable as well.

See [Versioning and Stability][Stability] for the details on stability guarantees
provided for semantic convention groups of different types.

Expand All @@ -71,11 +80,6 @@ Stability guarantees on a group level **do not** apply to unstable attribute ref

- MAY add or remove references to unstable attributes with `opt_in`
requirement level.
- SHOULD NOT have references to unstable attributes with requirement level
other than `opt_in`.
The requirement level of an unstable attribute reference
MAY be changed when this attribute becomes stable in cases allowed by the
[Versioning and Stability][Stability].
- MUST NOT remove references to stable attributes.

Stable instrumentations MUST NOT report telemetry following the unstable part
Expand Down
28 changes: 23 additions & 5 deletions policies/group_stability.rego
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package after_resolution
import rego.v1
# checks that stable group does not have experimental attributes with requirement levels other than opt_in

# Maturity ranking of stability levels. Higher number == more mature/stable.
stability_rank := {
"development": 1,
"experimental": 1,
"alpha": 2,
"beta": 3,
"release_candidate": 4,
"stable": 5,
}

# checks that a group's stability is not higher than the stability of any of its
# attributes, unless that attribute is opt_in
deny contains group_stability_violation(description, group.id, name) if {
group := input.groups[_]
# ignore attribute_groups
group.type != "attribute_group"
group.stability == "stable"

exceptions = {
# TODO: https://github.com/open-telemetry/semantic-conventions/issues/1514
Expand All @@ -14,17 +25,24 @@ deny contains group_stability_violation(description, group.id, name) if {
"entity.service",
# TODO: https://github.com/open-telemetry/semantic-conventions/issues/1616
"metric.dotnet.process.cpu.time",
# TODO: https://github.com/open-telemetry/semantic-conventions/issues/3753
"span.db.oracledb.client",
}
not exceptions[group.id]

group_rank := stability_rank[group.stability]

attr := group.attributes[_]
not attr.requirement_level == "opt_in"

attr_rank := stability_rank[attr.stability]

attr.stability != "stable"
attr.requirement_level != "opt_in"
# group claims a higher maturity than the attribute it references
group_rank > attr_rank

name := attr.name

description := sprintf("Stable group '%s' references experimental attribute with requirement level '%s', only 'opt_in' level is allowed", [group.id, name])
description := sprintf("Group '%s' has stability '%s' which is higher than the stability '%s' of attribute '%s'; lower-stability attributes are only allowed at 'opt_in' requirement level", [group.id, group.stability, attr.stability, name])
}

group_stability_violation(description, group, attr) = violation if {
Expand Down
87 changes: 61 additions & 26 deletions policies_test/group_stability_test.rego
Original file line number Diff line number Diff line change
@@ -1,34 +1,69 @@
package after_resolution
import future.keywords

test_fails_on_experimental_not_opt_in_attribute_in_stable_group if {
experimental_stabilities := ["experimental", "development", "alpha", "beta", "release_candidate"]
every stability in experimental_stabilities {
count(deny) == 1 with input as {"groups": [{ "id": "span.foo",
"type": "span",
"stability": "stable",
"brief": "brief.",
"attributes": [{
"name": "test.experimental",
"stability": stability,
"requirement_level": "required",
"brief": "brief.",
}]}]}
gs_group_types := ["span", "metric", "event", "entity"]

gs_lower_stabilities := ["experimental", "development", "alpha", "beta", "release_candidate"]

# Builds an input with a single group of the given type referencing a single attribute.
gs_input(group_type, group_stability, attr_stability, requirement_level) := {"groups": [{
"id": sprintf("%s.foo", [group_type]),
"type": group_type,
"stability": group_stability,
"brief": "brief.",
"attributes": [{
"name": "test.attr",
"stability": attr_stability,
"requirement_level": requirement_level,
"brief": "brief.",
}],
}]}

# A group must not reference a lower-maturity attribute unless it's opt_in.
test_fails_on_lower_maturity_not_opt_in_attribute if {
every group_type in gs_group_types {
every stability in gs_lower_stabilities {
count(deny) == 1 with input as gs_input(group_type, "stable", stability, "required")
}
}
}

test_passes_on_lower_maturity_opt_in_attribute if {
Comment thread
lmolkova marked this conversation as resolved.
every group_type in gs_group_types {
every stability in gs_lower_stabilities {
count(deny) == 0 with input as gs_input(group_type, "stable", stability, "opt_in")
}
}
}

# Group stability higher than the attribute's (non-stable group) still fails.
test_fails_when_group_stability_higher_than_attribute if {
every group_type in gs_group_types {
count(deny) == 1 with input as gs_input(group_type, "release_candidate", "development", "required")
}
}

test_passes_on_experimental_opt_in_attribute_in_stable_group if {
experimental_stabilities := ["experimental", "development", "alpha", "beta", "release_candidate"]
every stability in experimental_stabilities {
count(deny) == 0 with input as {"groups": [{ "id": "span.foo",
"type": "span",
"stability": "stable",
"brief": "brief.",
"attributes": [{
"name": "test.experimental",
"stability": stability,
"requirement_level": "opt_in",
"brief": "brief.",
}]}]}
# A lower-stability group referencing a higher-stability attribute is fine.
test_passes_when_group_stability_lower_than_attribute if {
every group_type in gs_group_types {
count(deny) == 0 with input as gs_input(group_type, "development", "stable", "required")
}
}

test_passes_when_group_and_attribute_have_equal_stability if {
every group_type in gs_group_types {
count(deny) == 0 with input as gs_input(group_type, "release_candidate", "release_candidate", "required")
}
}

# A lower-stability attribute is allowed at opt_in in a more stable group.
test_passes_on_lower_stability_opt_in_attribute if {
every group_type in gs_group_types {
count(deny) == 0 with input as gs_input(group_type, "release_candidate", "development", "opt_in")
}
}

# attribute_group is exempt from the check regardless of stability.
test_passes_on_attribute_group if {
count(deny) == 0 with input as gs_input("attribute_group", "stable", "development", "required")
}
Loading