use serde::Serialize;
use serde_json::{Map, Value};
pub const PROJECTION_SCHEMA: &str = "assay.otel_projection.v0";
pub const OTEL_GENAI_SEMCONV: &str = "1.37.0-development";
pub const OPENINFERENCE_SEMCONV: &str = "pinned";
pub const SOURCE_OF_TRUTH: &str = "assay artifacts";
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Semconv {
pub otel_genai: String,
pub openinference: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ProjectedSpan {
pub name: String,
pub kind: String,
pub attributes: Map<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Projection {
pub schema: String,
pub semconv: Semconv,
pub spans: Vec<ProjectedSpan>,
pub resource_attributes: Map<String, Value>,
pub lossy: bool,
pub source_of_truth: String,
pub non_claims: Vec<String>,
}
fn str_field(v: &Value, key: &str) -> Option<String> {
v.get(key).and_then(|x| x.as_str()).map(|s| s.to_string())
}
fn str_array(v: &Value, key: &str) -> Vec<String> {
v.get(key)
.and_then(|x| x.as_array())
.map(|a| {
a.iter()
.filter_map(|e| e.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default()
}
pub fn project(
capability_surface: &Value,
observation_health: Option<&Value>,
enforcement_health: Option<&Value>,
) -> Projection {
let mut resource: Map<String, Value> = Map::new();
resource.insert("service.name".into(), Value::String("assay".into()));
let mut spans: Vec<ProjectedSpan> = Vec::new();
for tool in str_array(capability_surface, "mcp_tools") {
let mut attrs = Map::new();
attrs.insert(
"gen_ai.operation.name".into(),
Value::String("execute_tool".into()),
);
attrs.insert("gen_ai.tool.name".into(), Value::String(tool.clone()));
attrs.insert(
"openinference.span.kind".into(),
Value::String("TOOL".into()),
);
attrs.insert("tool.name".into(), Value::String(tool.clone()));
attrs.insert("assay.claim_class".into(), Value::String("observed".into()));
spans.push(ProjectedSpan {
name: format!("execute_tool {tool}"),
kind: "INTERNAL".into(),
attributes: attrs,
});
}
for decision in str_array(capability_surface, "policy_decisions") {
let (verdict, key) = match decision.split_once(':') {
Some((v, k)) => (v.to_string(), k.to_string()),
None => (decision.clone(), String::new()),
};
let mut attrs = Map::new();
attrs.insert(
"openinference.span.kind".into(),
Value::String("GUARDRAIL".into()),
);
attrs.insert("assay.decision".into(), Value::String(verdict));
if !key.is_empty() {
attrs.insert("assay.tool".into(), Value::String(key.clone()));
}
attrs.insert("assay.claim_class".into(), Value::String("observed".into()));
spans.push(ProjectedSpan {
name: format!("guardrail {key}"),
kind: "INTERNAL".into(),
attributes: attrs,
});
}
if let Some(eh) = enforcement_health {
let scope = str_field(eh, "scope").unwrap_or_else(|| "unknown".into());
let mut attrs = Map::new();
attrs.insert(
"openinference.span.kind".into(),
Value::String("GUARDRAIL".into()),
);
attrs.insert(
"assay.claim_class".into(),
Value::String("enforcement".into()),
);
if let Some(v) = str_field(eh, "network_enforcement") {
attrs.insert("assay.enforcement.network".into(), Value::String(v));
}
if let Some(v) = eh.get("attach_confirmed").and_then(|x| x.as_bool()) {
attrs.insert("assay.enforcement.attach_confirmed".into(), Value::Bool(v));
}
attrs.insert(
"assay.enforcement.scope".into(),
Value::String(scope.clone()),
);
for (src_key, attr_key) in [
("blocked_count", "assay.enforcement.blocked_count"),
("allowed_count", "assay.enforcement.allowed_count"),
] {
if let Some(n) = eh.get(src_key).and_then(|x| x.as_u64()) {
attrs.insert(attr_key.into(), Value::Number(n.into()));
}
}
spans.push(ProjectedSpan {
name: format!("enforcement {scope}"),
kind: "INTERNAL".into(),
attributes: attrs,
});
}
for (src_key, attr_key) in [
("network_endpoints", "assay.capability.network_endpoints"),
("filesystem_paths", "assay.capability.filesystem_paths"),
("process_execs", "assay.capability.process_execs"),
] {
let set = str_array(capability_surface, src_key);
if !set.is_empty() {
resource.insert(
attr_key.into(),
Value::Array(set.into_iter().map(Value::String).collect()),
);
}
}
if let Some(oh) = observation_health {
for (src_key, attr_key) in [
("kernel_layer", "assay.observation.kernel_layer"),
(
"network_protocol_coverage",
"assay.observation.network_protocol_coverage",
),
("policy_layer", "assay.observation.policy_layer"),
] {
if let Some(v) = str_field(oh, src_key) {
resource.insert(attr_key.into(), Value::String(v));
}
}
}
let non_claims = vec![
"This projection is a one-directional, lossy view; the assay artifacts remain authoritative."
.to_string(),
"gen_ai.* and openinference.* fields are a projection, not the source of truth.".to_string(),
"Observed is not enforced: capability-surface tools carry assay.claim_class=observed, and \
enforcement is a separate span read only from assay.enforcement_health.v0."
.to_string(),
"Absence of an enforcement span means no enforcement_health was supplied, not that \
enforcement was absent."
.to_string(),
format!(
"Pinned to OTel GenAI semconv {OTEL_GENAI_SEMCONV} and OpenInference {OPENINFERENCE_SEMCONV}; \
a version bump is explicit."
),
];
Projection {
schema: PROJECTION_SCHEMA.to_string(),
semconv: Semconv {
otel_genai: OTEL_GENAI_SEMCONV.to_string(),
openinference: OPENINFERENCE_SEMCONV.to_string(),
},
spans,
resource_attributes: resource,
lossy: true,
source_of_truth: SOURCE_OF_TRUTH.to_string(),
non_claims,
}
}