package comparison_after_resolution import rego.v1 # Semantic Convention Registry Compatibility Checker # # This file contains rules for checking backward compatibility # between different versions of semantic convention registries. # It builds upon the data structures and rules defined in the # semconv package. # Import the previous release (baseline) and current sets of attributes, metrics. baseline_attributes := [attr | some g in data.semconv.registry_baseline_groups some attr in g.attributes ] registry_attributes := [attr | some g in data.semconv.registry_groups some attr in g.attributes ] registry_attribute_names := {attr.name | some g in data.semconv.registry_groups some attr in g.attributes } baseline_metrics := [ g | some g in data.semconv.baseline_groups g.type == "metric" ] registry_metrics := [ g | some g in data.semconv.groups g.type == "metric" ] registry_metric_names := { g.metric_name | some g in registry_metrics } baseline_resources := [ g | some g in data.semconv.baseline_groups g.type == "resource" ] registry_resources := [g | some g in data.semconv.groups g.type == "resource" ] registry_resource_names := { g.name | some g in registry_resources } baseline_events := [ g | some g in data.semconv.baseline_groups g.type == "event" ] registry_events := [g | some g in data.semconv.groups g.type == "event" ] registry_event_names := { g.name | some g in registry_events } # Rules we enforce: # - Attributes # - [x] Attributes cannot be removed # - [x] Attributes cannot "degrade" stability (stable->experimental) # - [x] Stable attributes cannot change type # - Enum members # - [x] Stable members cannot change stability # - [x] Values cannot change # - [x] ids cannot be removed # - Metrics # - [x] metrics cannot be removed # - [x] Stable metrics cannot become unstable # - [x] Stable Metric units cannot change # - [x] Stable Metric instruments cannot change # - [x] Set of required/recommended attributes must remain the same # - Resources # - [x] resources cannot be removed # - [x] Stable Resources cannot become unstable # - [x] Stable attributes cannot be dropped from stable resource # - Events # - [x] events cannot be removed # - [x] Stable events cannot become unstable # - [x] Stable attributes cannot be dropped from stable event # - Spans - no enforcement yet since id format is not finalized # - [ ] spans cannot be removed # - [ ] Stable spans cannot become unstable # - [ ] Stable attributes cannot be dropped from stable span # Rule: Detect Removed Attributes # # This rule checks for attributes that existed in the baseline registry # but are no longer present in the current registry. Removing attributes # is considered a backward compatibility violation. # # In other words, we do not allow the removal of an attribute once added # to the registry. It must exist SOMEWHERE in a group, but may be deprecated. deny contains back_comp_violation(description, group_id, attr.name) if { # Check if an attribute from the baseline is missing in the current registry some attr in baseline_attributes not registry_attribute_names[attr.name] # Generate human readable error. group_id := data.semconv.baseline_group_ids_by_attribute[attr.name] description := sprintf("Attribute '%s' no longer exists in the attribute registry", [attr.name]) } # Rule: Detect Stable Attributes moving to unstable # # This rule checks for attributes that were stable in the baseline registry # but are no longer stable in the current registry. Once stable, attributes # remain forever but may be deprecated. deny contains back_comp_violation(description, group_id, attr.name) if { # Find stable baseline attributes in latest registry. some attr in baseline_attributes attr.stability == "stable" some nattr in registry_attributes attr.name == nattr.name # Enforce the policy attr.stability != nattr.stability # Generate human readable error. group_id := data.semconv.baseline_group_ids_by_attribute[attr.name] description := sprintf("Attribute '%s' was stable, but has new stability marker", [attr.name]) } # Rule: Detect Stable Attributes changing type # # This rule checks for attributes that were stable in the baseline registry # but are no longer stable in the current registry. Once stable, attributes # remain forever but may be deprecated. deny contains back_comp_violation(description, group_id, attr.name) if { # Find stable baseline attributes in latest registry. some attr in baseline_attributes attr.stability == "stable" some nattr in registry_attributes attr.name == nattr.name # Enforce the policy # TODO - deal with enum type changes, probably in enum sections not is_enum(attr) attr.type != nattr.type # Generate human readable error. group_id := data.semconv.baseline_group_ids_by_attribute[attr.name] attr_type_string := type_string(attr) nattr_type_string := type_string(nattr) description := sprintf("Attribute '%s' was '%s', but has new type '%s'", [attr.name, attr_type_string, nattr_type_string]) } # Rule: Detect Stable enum Attributes changing type # # This rule checks for attributes that were stable in the baseline registry # but are no longer stable in the current registry. Once stable, attributes # remain forever but may be deprecated. deny contains back_comp_violation(description, group_id, attr.name) if { # Find stable baseline attributes in latest registry. some attr in baseline_attributes attr.stability == "stable" some nattr in registry_attributes attr.name == nattr.name # Enforce the policy attr.type != nattr.type is_enum(attr) not is_enum(nattr) # Generate human readable error. group_id := data.semconv.baseline_group_ids_by_attribute[attr.name] nattr_type_string := type_string(nattr) description := sprintf("Attribute '%s' was enum, but has new type '%s'", [attr.name, nattr_type_string]) } # Rule: Detect Stable Enum members changing stability level # # This rule checks for enum values that were stable in the baseline registry # but are no longer stable in the current registry. deny contains back_comp_violation(description, group_id, attr.name) if { # Find data we need to enforce: Enums in baseline/current. some attr in baseline_attributes attr.stability == "stable" some nattr in registry_attributes attr.name == nattr.name is_enum(attr) some member in attr.type.members some nmember in nattr.type.members member.id == nmember.id # Enforce the policy member.stability == "stable" nmember.stability != "stable" # Generate human readable error. group_id := data.semconv.baseline_group_ids_by_attribute[attr.name] description := sprintf("Enum '%s' had stable member '%s', but is no longer stable", [attr.name, member.id]) } # Rule: Enum member values cannot change # # This rule checks for enum values that have the same id, but values # are different. deny contains back_comp_violation(description, group_id, attr.name) if { # Find data we need to enforce: Enums in baseline/current. some attr in baseline_attributes attr.stability == "stable" some nattr in registry_attributes attr.name == nattr.name is_enum(attr) some member in attr.type.members some nmember in nattr.type.members member.id == nmember.id # Enforce the policy member.value != nmember.value # Generate human readable error. group_id := data.semconv.baseline_group_ids_by_attribute[attr.name] description := sprintf("Enum '%s' had stable value '%s', but is now '%s'", [attr.name, member.value, nmember.value]) } # Rule: Detect Stable Enum members missing # # This rule checks for enum values that were stable in the baseline registry # but are no longer have the same values in the current registry. Once stable, # enum values remain forever but may be deprecated. deny contains back_comp_violation(description, group_id, attr.name) if { # Find data we need to enforce: Enums in baseline/current. some attr in baseline_attributes attr.stability == "stable" some nattr in registry_attributes attr.name == nattr.name is_enum(attr) current_member_ids := {member.id | some member in nattr.type.members} # Enforce the policy some member in attr.type.members not current_member_ids[member.id] # Generate human readable error. group_id := data.semconv.baseline_group_ids_by_attribute[attr.name] description := sprintf("Enum '%s' had member '%s', but is no longer defined", [attr.name, member.id]) } # Rule: Detect Removed Metrics # # This rule checks for stable metrics that existed in the baseline registry # but are no longer present in the current registry. Removing metrics # is considered a backward compatibility violation. # # In other words, we do not allow the removal of an metrics once added # to semantic conventions. They, however, may be deprecated. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some metric in baseline_metrics metric.stability == "stable" # Enforce the policy not registry_metric_names[metric.metric_name] # Generate human readable error. group_id := metric.id description := sprintf("Metric '%s' no longer exists in semantic conventions", [metric.metric_name]) } # Rule: Stable metrics cannot become unstable # # This rule checks that stable metrics cannot have their stability level changed. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some metric in baseline_metrics metric.stability == "stable" some nmetric in registry_metrics metric.metric_name = nmetric.metric_name # Enforce the policy nmetric.stability != "stable" # Generate human readable error. group_id := metric.id description := sprintf("Metric '%s' cannot change from stable", [metric.metric_name]) } # Rule: Stable metrics units cannot change # # This rule checks that stable metrics cannot change the unit type. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some metric in baseline_metrics metric.stability == "stable" some nmetric in registry_metrics metric.metric_name = nmetric.metric_name # Enforce the policy nmetric.unit != metric.unit # Generate human readable error. group_id := metric.id description := sprintf("Metric '%s' cannot change unit (was '%s', now: '%s')", [metric.metric_name, metric.unit, nmetric.unit]) } # Rule: Stable Metric instruments cannot change # # This rule checks that stable metrics cannot change the instrument type. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some metric in baseline_metrics metric.stability == "stable" some nmetric in registry_metrics metric.metric_name = nmetric.metric_name # Enforce the policy nmetric.instrument != metric.instrument # Generate human readable error. group_id := metric.id description := sprintf("Metric '%s' cannot change instrument (was '%s', now: '%s')", [metric.metric_name, metric.instrument, nmetric.instrument]) } # Rule: Stable attributes on stable metric cannot be dropped. # # This rule checks that stable metrics have stable sets of attributes. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some metric in baseline_metrics metric.stability == "stable" some nmetric in registry_metrics metric.metric_name = nmetric.metric_name baseline_attributes := { attr.name | some attr in metric.attributes attr.stability == "stable" } new_attributes := { attr.name | some attr in nmetric.attributes attr.stability == "stable" } missing_attributes := baseline_attributes - new_attributes # Enforce the policy count(missing_attributes) > 0 # Generate human readable error. group_id := metric.id description := sprintf("Metric '%s' cannot change required/recommended attributes (missing '%s')", [metric.metric_name, missing_attributes]) } # Rule: Stable Metric required/recommended attributes cannot be added # # This rule checks that stable metrics have stable sets of attributes. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some metric in baseline_metrics metric.stability == "stable" some nmetric in registry_metrics metric.metric_name = nmetric.metric_name baseline_attributes := { attr.name | some attr in metric.attributes not is_opt_in(attr) } new_attributes := { attr.name | some attr in nmetric.attributes not is_opt_in(attr) } added_attributes := new_attributes - baseline_attributes # Enforce the policy count(added_attributes) > 0 # Generate human readable error. group_id := metric.id description := sprintf("Metric '%s' cannot change required/recommended attributes (added '%s')", [metric.metric_name, added_attributes]) } # Rule: Detect Removed Resources # # This rule checks for stable resources that existed in the baseline registry # but are no longer present in the current registry. Removing resources # is considered a backward compatibility violation. # # In other words, we do not allow the removal of a resource once added # to semantic conventions. They, however, may be deprecated. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some resource in baseline_resources resource.stability == "stable" # remove after https://github.com/open-telemetry/semantic-conventions/pull/1423 is merged # Enforce the policy not registry_resource_names[resource.name] # Generate human readable error. group_id := resource.id description := sprintf("Resource '%s' no longer exists in semantic conventions", [resource.name]) } # Rule: Stable resources cannot become unstable # # This rule checks that stable resources cannot have their stability level changed. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some resource in baseline_resources resource.stability == "stable" some nresource in registry_resources resource.name = nresource.name # Enforce the policy nresource.stability != "stable" # Generate human readable error. group_id := resource.id description := sprintf("Resource '%s' cannot change from stable", [resource.name]) } # Rule: Stable attributes cannot be dropped from stable resource. # # This rule checks that stable resources have stable sets of attributes. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some resource in baseline_resources resource.stability == "stable" some nresource in registry_resources resource.name == nresource.name nresource.stability == "stable" # remove after https://github.com/open-telemetry/semantic-conventions/pull/1423 is merged baseline_attributes := { attr.name | some attr in resource.attributes attr.stability == "stable" } new_attributes := { attr.name | some attr in nresource.attributes attr.stability == "stable" } missing_attributes := baseline_attributes - new_attributes # Enforce the policy count(missing_attributes) > 0 # Generate human readable error. group_id := resource.id description := sprintf("Resource '%s' cannot remove required/recommended attributes (missing '%s')", [resource.name, missing_attributes]) } # Rule: Detect Removed events # # This rule checks for stable events that existed in the baseline registry # but are no longer present in the current registry. Removing events # is considered a backward compatibility violation. # # In other words, we do not allow the removal of a events once added # to semantic conventions. They, however, may be deprecated. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some event in baseline_events # Enforce the policy not registry_event_names[event.name] # Generate human readable error. group_id := event.id description := sprintf("Event '%s' no longer exists in semantic conventions", [event.name]) } # Rule: Stable events cannot become unstable # # This rule checks that stable events cannot have their stability level changed. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some event in baseline_events event.stability == "stable" some nevent in registry_events event.name = nevent.name # Enforce the policy nevent.stability != "stable" # Generate human readable error. group_id := event.id description := sprintf("Event '%s' cannot change from stable", [event.name]) } # Rule: Stable attributes on stable event cannot be dropped. # # This rule checks that stable events have stable sets of attributes. deny contains back_comp_violation(description, group_id, "") if { # Find data we need to enforce some event in baseline_events event.stability == "stable" some nevent in registry_events event.name == nevent.name baseline_attributes := { attr.name | some attr in event.attributes attr.stability == "stable" } new_attributes := { attr.name | some attr in event.attributes attr.stability == "stable" } missing_attributes := baseline_attributes - new_attributes # Enforce the policy count(missing_attributes) > 0 # Generate human readable error. group_id := event.id description := sprintf("Event '%s' cannot remove stable attributes (missing '%s')", [event.name, missing_attributes]) } # Helper Function: Create Backward Compatibility Violation Object # # This function generates a structured violation object for each # detected backward compatibility issue. back_comp_violation(description, group_id, attr_id) := violation if { violation := { "id": description, "type": "semconv_attribute", "category": "backward_compatibility", "group": group_id, "attr": attr_id, } } # Helpers for enum values and type strings is_enum(attr) := true if count(attr.type.members) > 0 type_string(attr) := attr.type if not is_enum(attr) type_string(attr) := "enum" if is_enum(attr) is_opt_in(attr) := true if attr.requirement_level == "opt_in"