use crate::redaction::FieldClass;
pub const SPAN_SCHEMA_VERSION: u32 = 1;
pub const SCHEMA_URL: &str = "https://opentelemetry.io/schemas/1.41.1";
pub mod span {
pub const DECISION: &str = "rustev.decision";
pub const STEP: &str = "rustev.step";
pub const ATTEMPT: &str = "rustev.attempt";
pub const POLICY: &str = "rustev.policy";
pub const ALL: [&str; 4] = [DECISION, STEP, ATTEMPT, POLICY];
}
pub mod event {
pub const INPUT: &str = "rustev.input";
pub const NOTICE: &str = "rustev.notice";
pub const TRANSITION: &str = "rustev.transition";
pub const EXCLUDED: &str = "rustev.excluded";
pub const POSITION: &str = "rustev.position";
pub const ALL: [&str; 5] = [INPUT, NOTICE, TRANSITION, EXCLUDED, POSITION];
}
pub mod fixed {
pub const SCHEMA_VERSION: &str = "rustev.otel.schema_version";
pub const POLICY_ID: &str = "rustev.redaction.policy_id";
pub const KEY_ID: &str = "rustev.redaction.key_id";
pub const WITHHELD: &str = "rustev.redaction.withheld";
}
pub const PARAM_PREFIX: &str = "rustev.outcome.param.";
pub const EXTRA_PREFIX: &str = "rustev.exchange.extra.";
pub const EXTRA_WITHHELD: &str = "rustev.exchange.extra.*";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClassRule {
Class(FieldClass),
Lineage,
Fixed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CatalogueField {
pub name: &'static str,
pub rule: ClassRule,
pub template: bool,
pub content: bool,
}
const fn f(name: &'static str, class: FieldClass) -> CatalogueField {
CatalogueField {
name,
rule: ClassRule::Class(class),
template: false,
content: false,
}
}
const fn fixed_field(name: &'static str) -> CatalogueField {
CatalogueField {
name,
rule: ClassRule::Fixed,
template: false,
content: false,
}
}
use FieldClass::{
ContentDigest as CD, Enum as E, FreeTextDetail as FT, Id as ID, ModelIdentity as MI,
Number as N, PlanMetadata as PM, ThirdParty as TP,
};
pub const CATALOGUE: &[CatalogueField] = &[
f("error.type", E),
f("gen_ai.operation.name", E),
f("gen_ai.provider.name", MI),
f("gen_ai.request.model", MI),
f("gen_ai.response.id", ID),
f("gen_ai.response.model", MI),
f("gen_ai.token.type", E),
f("gen_ai.usage.input_tokens", N),
f("gen_ai.usage.output_tokens", N),
f("rustev.acquisition.attempt_id", ID),
f("rustev.acquisition.cache", E),
f("rustev.acquisition.cache.age_ms", N),
f("rustev.acquisition.cancellation", E),
f("rustev.acquisition.constructed", E),
f("rustev.acquisition.cost.bound", E),
f("rustev.acquisition.cost.charge", N),
f("rustev.acquisition.cost.reserved", N),
f("rustev.acquisition.derivation", E),
f("rustev.acquisition.digest", CD),
f("rustev.acquisition.dispatched_ms", N),
f("rustev.acquisition.end", E),
f("rustev.acquisition.ended_ms", N),
f("rustev.acquisition.guard", PM),
f("rustev.acquisition.item.digest", CD),
f("rustev.acquisition.operation", PM),
f("rustev.acquisition.outcome", E),
f("rustev.acquisition.page", N),
f("rustev.acquisition.rank", N),
f("rustev.acquisition.relaxation", N),
f("rustev.acquisition.remote_state", E),
f("rustev.acquisition.request", CD),
f("rustev.acquisition.request_constructed", E),
f("rustev.acquisition.source", PM),
f("rustev.acquisition.step", PM),
f("rustev.acquisition.stopped_by", E),
f("rustev.acquisition.tier", PM),
f("rustev.acquisition.tier_index", N),
f("rustev.acquisition.truncated", N),
f("rustev.acquisition.unique_items_added", N),
f("rustev.attempt.artifact", PM),
f("rustev.attempt.backend_id", PM),
f("rustev.attempt.cancel", E),
f("rustev.attempt.charge_kind", E),
f("rustev.attempt.charge_units", N),
f("rustev.attempt.cost.bound", E),
f("rustev.attempt.cost.reserved", N),
f("rustev.attempt.end", E),
f("rustev.attempt.failure_class", E),
f("rustev.attempt.failure_detail", FT),
f("rustev.attempt.id", ID),
f("rustev.attempt.ordinal", N),
f("rustev.attempt.remote_state", E),
f("rustev.attempt.retry_index", N),
f("rustev.attempt.target", N),
f("rustev.batch.id", ID),
f("rustev.batch.member", N),
f("rustev.cost.bound_violations", N),
f("rustev.cost.estimated", N),
f("rustev.cost.final", E),
f("rustev.cost.liability", N),
f("rustev.cost.limit", N),
f("rustev.cost.mode", E),
f("rustev.cost.observed", N),
f("rustev.cost.within_guaranteed_cap", E),
f("rustev.deadline.remaining_ms", N),
f("rustev.decision.id", ID),
f("rustev.definition.id", PM),
f("rustev.derivation", E),
f("rustev.events_cut", N),
f("rustev.evidence.sink.result", E),
f("rustev.exchange.bytes_sent", E),
CatalogueField {
name: EXTRA_PREFIX,
rule: ClassRule::Class(TP),
template: true,
content: false,
},
f("rustev.exchange.late", E),
f("rustev.exchange.member_detail", FT),
f("rustev.exchange.reported_cost", N),
f("rustev.exchange.reported_currency", E),
f("rustev.exchange.request_digest", CD),
f("rustev.exchange.response_digest", CD),
f("rustev.exchange.retry_after", TP),
f("rustev.exchange.route", MI),
f("rustev.exchange.status", N),
f("rustev.exchange.truncated", E),
f("rustev.excluded.candidate", ID),
f("rustev.excluded.reasons", PM),
f("rustev.execution.policy_id", PM),
f("rustev.fallbacks.kind", E),
f("rustev.generation.attempt", ID),
f("rustev.generation.backend_id", PM),
f("rustev.generation.byte_length", N),
CatalogueField {
name: "rustev.generation.context",
rule: ClassRule::Lineage,
template: false,
content: true,
},
f("rustev.generation.context_digest", CD),
f("rustev.generation.counts", N),
f("rustev.generation.derivation", E),
f("rustev.generation.field", PM),
f("rustev.generation.helpers_version", PM),
f("rustev.generation.item", ID),
f("rustev.generation.max_items", N),
f("rustev.generation.notice", E),
f("rustev.generation.origin", E),
f("rustev.generation.original_decision_id", ID),
f("rustev.generation.projection", PM),
f("rustev.generation.reason", E),
f("rustev.generation.source", E),
f("rustev.generation.state", E),
f("rustev.generation.step", PM),
f("rustev.generation.template_id", CD),
f("rustev.generation.template_name", PM),
f("rustev.generation.text_origin", E),
f("rustev.generation.truncated", E),
CatalogueField {
name: "rustev.generation.value",
rule: ClassRule::Class(FieldClass::GeneratedText),
template: false,
content: true,
},
f("rustev.generation.value_digest", CD),
f("rustev.generation.worst_case_projection_bytes", N),
f("rustev.generation.worst_case_requests", N),
f("rustev.input.as_of_ms", N),
f("rustev.input.field", PM),
f("rustev.input.provenance", E),
f("rustev.input.source", ID),
f("rustev.input.status", E),
f("rustev.input.unresolved_reason", E),
f("rustev.lineage.inputs", PM),
f("rustev.lineage.steps", PM),
f("rustev.notice.kind", E),
f("rustev.notice.reason", PM),
f("rustev.notice.subject", PM),
fixed_field(fixed::SCHEMA_VERSION),
f("rustev.outcome", E),
f("rustev.outcome.action", PM),
f("rustev.outcome.escalate_reason", PM),
CatalogueField {
name: PARAM_PREFIX,
rule: ClassRule::Lineage,
template: true,
content: false,
},
f("rustev.outcome.unresolved_detail", FT),
f("rustev.outcome.unresolved_fields", PM),
f("rustev.outcome.unresolved_reason", E),
f("rustev.plan.id", PM),
f("rustev.policy.excluded_count", N),
f("rustev.policy.rules_fired", PM),
f("rustev.policy.truncated", N),
f("rustev.position.candidate", ID),
f("rustev.position.position", N),
CatalogueField {
name: "rustev.position.score",
rule: ClassRule::Lineage,
template: false,
content: false,
},
fixed_field(fixed::KEY_ID),
fixed_field(fixed::POLICY_ID),
fixed_field(fixed::WITHHELD),
f("rustev.screening.artifact", PM),
f("rustev.screening.backend_id", PM),
f("rustev.screening.cache", E),
f("rustev.screening.categories", E),
f("rustev.screening.consequence", E),
f("rustev.screening.descriptor", PM),
f("rustev.screening.entry", PM),
f("rustev.screening.field", PM),
f("rustev.screening.item", ID),
f("rustev.screening.items", ID),
f("rustev.screening.source_attempt", ID),
f("rustev.screening.step", PM),
f("rustev.screening.text_digest", CD),
f("rustev.screening.units.basis", E),
f("rustev.screening.units.bound_violation.computed", N),
f("rustev.screening.units.bound_violation.reported", N),
f("rustev.screening.units.charged", N),
f("rustev.screening.verdict", E),
f("rustev.served.disclosure", E),
f("rustev.snapshot.id", CD),
f("rustev.step.backend_id", PM),
f("rustev.step.derivation", E),
f("rustev.step.id", PM),
f("rustev.step.instance", ID),
f("rustev.step.kind", E),
f("rustev.step.operator", PM),
f("rustev.step.optimization.mechanism", E),
f("rustev.step.optimization.outcome", E),
f("rustev.step.result", E),
f("rustev.step.reused_from", ID),
f("rustev.step.status", E),
f("rustev.step.target", N),
f("rustev.step.unresolved_reason", E),
f("rustev.step.value_kind", E),
f("rustev.telemetry.lost.reason", E),
f("rustev.termination", E),
f("rustev.time_anchor", E),
f("rustev.timing", E),
f("rustev.timing.deadline_expired", E),
f("rustev.timing.deadline_ms", N),
f("rustev.timing.elapsed_ms", N),
f("rustev.timing.queued_ms", N),
f("rustev.transition.after", N),
f("rustev.transition.class", E),
f("rustev.transition.delay_ms", N),
f("rustev.transition.kind", E),
f("rustev.transition.reason", FT),
f("rustev.transition.to", N),
f("rustev.unresolved.scope", E),
];
fn is_segment(s: &str) -> bool {
!s.is_empty()
&& s.len() <= 128
&& !s
.chars()
.any(|c| c == '.' || c == '*' || c.is_whitespace() || c.is_control())
}
pub fn lookup(name: &str) -> Option<&'static CatalogueField> {
CATALOGUE.iter().find(|e| {
if e.template {
name.strip_prefix(e.name).is_some_and(is_segment)
} else {
e.name == name
}
})
}
pub fn event_field(event: &str, key: &str) -> String {
format!("{event}.{key}")
}
pub fn withheld_name(field: &str) -> &str {
if field.starts_with(EXTRA_PREFIX) {
EXTRA_WITHHELD
} else {
field
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_catalogue_is_sorted_and_unique() {
for w in CATALOGUE.windows(2) {
assert!(w[0].name < w[1].name, "{} / {}", w[0].name, w[1].name);
}
}
#[test]
fn templates_match_one_segment() {
assert!(lookup("rustev.outcome.param.amount").is_some());
assert!(lookup("rustev.outcome.param.").is_none());
assert!(lookup("rustev.outcome.param.a.b").is_none());
assert_eq!(
lookup("rustev.exchange.extra.x_cache").map(|e| e.rule),
Some(ClassRule::Class(FieldClass::ThirdParty))
);
assert!(lookup("rustev.exchange.extra.*").is_none());
assert!(lookup("rustev.nope").is_none());
}
#[test]
fn fixed_fields_are_the_four_of_3_4_4() {
let fixed: Vec<_> = CATALOGUE
.iter()
.filter(|e| e.rule == ClassRule::Fixed)
.map(|e| e.name)
.collect();
assert_eq!(
fixed,
vec![
fixed::SCHEMA_VERSION,
fixed::KEY_ID,
fixed::POLICY_ID,
fixed::WITHHELD
]
);
}
#[test]
fn generation_content_is_explicit() {
assert_eq!(
CATALOGUE
.iter()
.filter(|e| e.content)
.map(|e| e.name)
.collect::<Vec<_>>(),
vec!["rustev.generation.context", "rustev.generation.value"]
);
}
#[test]
fn extras_are_withheld_without_their_key() {
assert_eq!(
withheld_name("rustev.exchange.extra.secret"),
EXTRA_WITHHELD
);
assert_eq!(withheld_name("rustev.decision.id"), "rustev.decision.id");
}
}