use crate::adapter::net::behavior::capability::CapabilitySet;
use crate::adapter::net::behavior::tag::{Tag, TaxonomyAxis};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueType {
Presence,
Number,
String,
Enumeration,
Bool,
Csv,
}
#[derive(Debug, Clone, Copy)]
pub struct KeyEntry {
pub key: &'static str,
pub value_type: ValueType,
}
#[derive(Debug, Clone, Copy)]
pub struct KeyShape {
pub prefix: &'static str,
pub kind: KeyShapeKind,
pub sub_keys: &'static [KeyEntry],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyShapeKind {
IndexedCollection,
KeyedMap {
value_type: ValueType,
},
}
#[derive(Debug, Clone, Copy)]
pub struct AxisEntry {
pub keys: &'static [KeyEntry],
pub shapes: &'static [KeyShape],
}
#[derive(Debug, Clone, Copy)]
pub struct AxisSchema {
pub hardware: AxisEntry,
pub software: AxisEntry,
pub devices: AxisEntry,
pub dataforts: AxisEntry,
pub metadata_reserved: &'static [&'static str],
pub metadata_reserved_prefixes: &'static [&'static str],
}
const HARDWARE_KEYS: &[KeyEntry] = &[
KeyEntry {
key: "cpu_cores",
value_type: ValueType::Number,
},
KeyEntry {
key: "cpu_threads",
value_type: ValueType::Number,
},
KeyEntry {
key: "memory_gb",
value_type: ValueType::Number,
},
KeyEntry {
key: "gpu",
value_type: ValueType::Presence,
},
KeyEntry {
key: "gpu.vendor",
value_type: ValueType::Enumeration,
},
KeyEntry {
key: "gpu.model",
value_type: ValueType::String,
},
KeyEntry {
key: "gpu.vram_gb",
value_type: ValueType::Number,
},
KeyEntry {
key: "gpu.compute_units",
value_type: ValueType::Number,
},
KeyEntry {
key: "gpu.tensor_cores",
value_type: ValueType::Number,
},
KeyEntry {
key: "gpu.fp16_tflops_x10",
value_type: ValueType::Number,
},
KeyEntry {
key: "storage_gb",
value_type: ValueType::Number,
},
KeyEntry {
key: "network_gbps",
value_type: ValueType::Number,
},
KeyEntry {
key: "limits.max_concurrent_requests",
value_type: ValueType::Number,
},
KeyEntry {
key: "limits.max_tokens_per_request",
value_type: ValueType::Number,
},
KeyEntry {
key: "limits.rate_limit_rpm",
value_type: ValueType::Number,
},
KeyEntry {
key: "limits.max_batch_size",
value_type: ValueType::Number,
},
KeyEntry {
key: "limits.max_input_bytes",
value_type: ValueType::Number,
},
KeyEntry {
key: "limits.max_output_bytes",
value_type: ValueType::Number,
},
];
const SOFTWARE_KEYS: &[KeyEntry] = &[
KeyEntry {
key: "os",
value_type: ValueType::String,
},
KeyEntry {
key: "os_version",
value_type: ValueType::String,
},
KeyEntry {
key: "cuda_version",
value_type: ValueType::String,
},
];
const SOFTWARE_SHAPES: &[KeyShape] = &[
KeyShape {
prefix: "runtime.",
kind: KeyShapeKind::KeyedMap {
value_type: ValueType::String,
},
sub_keys: &[],
},
KeyShape {
prefix: "framework.",
kind: KeyShapeKind::KeyedMap {
value_type: ValueType::String,
},
sub_keys: &[],
},
KeyShape {
prefix: "driver.",
kind: KeyShapeKind::KeyedMap {
value_type: ValueType::String,
},
sub_keys: &[],
},
KeyShape {
prefix: "model.",
kind: KeyShapeKind::IndexedCollection,
sub_keys: &[
KeyEntry {
key: "id",
value_type: ValueType::String,
},
KeyEntry {
key: "family",
value_type: ValueType::String,
},
KeyEntry {
key: "parameters_b_x10",
value_type: ValueType::Number,
},
KeyEntry {
key: "context_length",
value_type: ValueType::Number,
},
KeyEntry {
key: "quantization",
value_type: ValueType::String,
},
KeyEntry {
key: "modalities",
value_type: ValueType::Csv,
},
KeyEntry {
key: "tokens_per_sec",
value_type: ValueType::Number,
},
KeyEntry {
key: "loaded",
value_type: ValueType::Bool,
},
],
},
KeyShape {
prefix: "tool.",
kind: KeyShapeKind::IndexedCollection,
sub_keys: &[
KeyEntry {
key: "tool_id",
value_type: ValueType::String,
},
KeyEntry {
key: "name",
value_type: ValueType::String,
},
KeyEntry {
key: "version",
value_type: ValueType::String,
},
KeyEntry {
key: "requires",
value_type: ValueType::Csv,
},
KeyEntry {
key: "estimated_time_ms",
value_type: ValueType::Number,
},
KeyEntry {
key: "stateless",
value_type: ValueType::Bool,
},
],
},
];
const METADATA_RESERVED_KEYS: &[&str] = &[
"intent",
"colocate-with",
"colocate-with-strict",
"priority",
"owner",
];
const METADATA_RESERVED_PREFIXES: &[&str] = &["tool::"];
pub const AXIS_SCHEMA: AxisSchema = AxisSchema {
hardware: AxisEntry {
keys: HARDWARE_KEYS,
shapes: &[],
},
software: AxisEntry {
keys: SOFTWARE_KEYS,
shapes: SOFTWARE_SHAPES,
},
devices: AxisEntry {
keys: &[],
shapes: &[],
},
dataforts: AxisEntry {
keys: &[],
shapes: &[],
},
metadata_reserved: METADATA_RESERVED_KEYS,
metadata_reserved_prefixes: METADATA_RESERVED_PREFIXES,
};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ValidationReport {
pub errors: Vec<SchemaError>,
pub warnings: Vec<ValidationWarning>,
}
impl ValidationReport {
pub fn is_clean(&self) -> bool {
self.errors.is_empty() && self.warnings.is_empty()
}
pub fn is_valid(&self) -> bool {
self.errors.is_empty()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SchemaError {
UnknownAxis {
axis_prefix: String,
tag: String,
},
TypeMismatch {
axis: TaxonomyAxis,
key: String,
expected: ValueType,
actual: String,
},
IndexMalformed {
axis: TaxonomyAxis,
prefix: String,
index: String,
tag: String,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum ValidationWarning {
UnknownKey {
axis: TaxonomyAxis,
key: String,
},
MetadataOversize {
soft_cap_bytes: usize,
actual_bytes: usize,
},
LegacyTag {
tag: String,
},
MetadataReservedKey {
key: String,
},
MetadataReservedPrefix {
key: String,
prefix: String,
},
}
pub const METADATA_SOFT_CAP_BYTES: usize = 4 * 1024;
pub fn validate_capabilities(caps: &CapabilitySet) -> ValidationReport {
validate_capabilities_against(caps, &AXIS_SCHEMA)
}
pub fn validate_capabilities_against(
caps: &CapabilitySet,
schema: &AxisSchema,
) -> ValidationReport {
let mut report = ValidationReport::default();
for tag in &caps.tags {
validate_tag(tag, schema, &mut report);
}
for key in caps.metadata.keys() {
if schema.metadata_reserved.contains(&key.as_str()) {
report
.warnings
.push(ValidationWarning::MetadataReservedKey { key: key.clone() });
continue;
}
if let Some(prefix) = schema
.metadata_reserved_prefixes
.iter()
.find(|p| key.starts_with(*p))
{
report
.warnings
.push(ValidationWarning::MetadataReservedPrefix {
key: key.clone(),
prefix: (*prefix).to_string(),
});
}
}
let metadata_bytes: usize = caps.metadata.iter().map(|(k, v)| k.len() + v.len()).sum();
if metadata_bytes > METADATA_SOFT_CAP_BYTES {
report.warnings.push(ValidationWarning::MetadataOversize {
soft_cap_bytes: METADATA_SOFT_CAP_BYTES,
actual_bytes: metadata_bytes,
});
}
report
}
fn validate_tag(tag: &Tag, schema: &AxisSchema, report: &mut ValidationReport) {
match tag {
Tag::AxisPresent { axis, key } => {
validate_axis_key(*axis, key, ValueType::Presence, None, schema, report, tag);
}
Tag::AxisValue {
axis, key, value, ..
} => {
validate_axis_key(
*axis,
key,
ValueType::String,
Some(value),
schema,
report,
tag,
);
}
Tag::Reserved { .. } => {
}
Tag::Legacy(s) => {
report
.warnings
.push(ValidationWarning::LegacyTag { tag: s.clone() });
}
}
}
fn validate_axis_key(
axis: TaxonomyAxis,
key: &str,
observed_type: ValueType,
observed_value: Option<&str>,
schema: &AxisSchema,
report: &mut ValidationReport,
tag: &Tag,
) {
let axis_entry = match axis {
TaxonomyAxis::Hardware => &schema.hardware,
TaxonomyAxis::Software => &schema.software,
TaxonomyAxis::Devices => &schema.devices,
TaxonomyAxis::Dataforts => &schema.dataforts,
};
if let Some(entry) = axis_entry.keys.iter().find(|e| e.key == key) {
check_value(entry, observed_type, observed_value, axis, report);
return;
}
for shape in axis_entry.shapes {
if let Some(rest) = key.strip_prefix(shape.prefix) {
match shape.kind {
KeyShapeKind::IndexedCollection => {
if let Some((idx, sub)) = rest.split_once('.') {
if idx.parse::<u32>().is_err() {
report.errors.push(SchemaError::IndexMalformed {
axis,
prefix: shape.prefix.to_string(),
index: idx.to_string(),
tag: tag.to_string(),
});
return;
}
if let Some(sub_entry) = shape.sub_keys.iter().find(|e| e.key == sub) {
check_value(sub_entry, observed_type, observed_value, axis, report);
return;
}
report.warnings.push(ValidationWarning::UnknownKey {
axis,
key: key.to_string(),
});
return;
}
}
KeyShapeKind::KeyedMap { value_type } => {
if !rest.is_empty() {
let synth = KeyEntry {
key: shape.prefix,
value_type,
};
check_value(&synth, observed_type, observed_value, axis, report);
return;
}
}
}
}
}
report.warnings.push(ValidationWarning::UnknownKey {
axis,
key: key.to_string(),
});
}
fn check_value(
entry: &KeyEntry,
observed_type: ValueType,
observed_value: Option<&str>,
axis: TaxonomyAxis,
report: &mut ValidationReport,
) {
if entry.value_type == ValueType::Presence {
if observed_type != ValueType::Presence {
report.errors.push(SchemaError::TypeMismatch {
axis,
key: entry.key.to_string(),
expected: ValueType::Presence,
actual: observed_value.unwrap_or("").to_string(),
});
}
return;
}
let Some(value) = observed_value else {
report.errors.push(SchemaError::TypeMismatch {
axis,
key: entry.key.to_string(),
expected: entry.value_type,
actual: "<no value>".to_string(),
});
return;
};
let parses = match entry.value_type {
ValueType::Presence => unreachable!("handled above"),
ValueType::Number => value.parse::<u64>().is_ok(),
ValueType::String | ValueType::Enumeration | ValueType::Csv => !value.is_empty(),
ValueType::Bool => matches!(value, "true" | "false"),
};
if !parses {
report.errors.push(SchemaError::TypeMismatch {
axis,
key: entry.key.to_string(),
expected: entry.value_type,
actual: value.to_string(),
});
}
}
#[allow(dead_code)]
fn detect_unknown_axis_typo(_legacy: &str) -> Option<String> {
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::adapter::net::behavior::capability::{
GpuInfo, GpuVendor, HardwareCapabilities, Modality, ModelCapability, SoftwareCapabilities,
};
use crate::adapter::net::behavior::tag::AxisSeparator;
use std::collections::HashSet;
#[test]
fn axis_schema_const_covers_every_documented_hardware_key() {
let keys: HashSet<&str> = HARDWARE_KEYS.iter().map(|e| e.key).collect();
let expected: HashSet<&str> = [
"cpu_cores",
"cpu_threads",
"memory_gb",
"gpu",
"gpu.vendor",
"gpu.model",
"gpu.vram_gb",
"gpu.compute_units",
"gpu.tensor_cores",
"gpu.fp16_tflops_x10",
"storage_gb",
"network_gbps",
"limits.max_concurrent_requests",
"limits.max_tokens_per_request",
"limits.rate_limit_rpm",
"limits.max_batch_size",
"limits.max_input_bytes",
"limits.max_output_bytes",
]
.into_iter()
.collect();
assert_eq!(keys, expected);
}
#[test]
fn axis_schema_const_covers_every_documented_software_shape() {
let prefixes: HashSet<&str> = SOFTWARE_SHAPES.iter().map(|s| s.prefix).collect();
let expected: HashSet<&str> = ["runtime.", "framework.", "driver.", "model.", "tool."]
.into_iter()
.collect();
assert_eq!(prefixes, expected);
}
#[test]
fn validate_default_capability_set_is_clean() {
let caps = CapabilitySet::default();
let report = validate_capabilities(&caps);
assert!(report.is_clean(), "report not clean: {report:?}");
}
#[test]
fn validate_well_formed_capability_set_is_clean() {
let gpu = GpuInfo::new(GpuVendor::Nvidia, "h100", 80);
let hw = HardwareCapabilities::new()
.with_cpu(16, 32)
.with_memory(64)
.with_gpu(gpu);
let sw = SoftwareCapabilities::new()
.with_os("linux", "6.5")
.add_runtime("python", "3.11")
.add_framework("pytorch", "2.1");
let model = ModelCapability::new("llama-3.1-70b", "llama")
.with_parameters(70.0)
.with_context_length(128000)
.add_modality(Modality::Text)
.with_loaded(true);
let caps = CapabilitySet::new()
.with_hardware(hw)
.with_software(sw)
.add_model(model)
.with_metadata("intent", "ml-training");
let report = validate_capabilities(&caps);
assert!(
report.errors.is_empty(),
"errors on a well-formed set: {:?}",
report.errors
);
}
#[test]
fn validate_unknown_key_under_known_axis_is_warning_not_error() {
let caps = CapabilitySet::new().add_tag("hardware.future_key=42");
let report = validate_capabilities(&caps);
assert!(report.errors.is_empty(), "errors: {:?}", report.errors);
assert!(
report
.warnings
.iter()
.any(|w| matches!(w, ValidationWarning::UnknownKey { axis, key }
if *axis == TaxonomyAxis::Hardware && key == "future_key")),
"missing UnknownKey warning: {:?}",
report.warnings,
);
}
#[test]
fn validate_legacy_untyped_tag_is_warning_not_error() {
let caps = CapabilitySet::new().add_tag("nat:full-cone");
let report = validate_capabilities(&caps);
assert!(report.errors.is_empty());
assert!(
report.warnings.iter().any(|w| matches!(w,
ValidationWarning::LegacyTag { tag } if tag == "nat:full-cone"
)),
"missing LegacyTag warning: {:?}",
report.warnings,
);
}
#[test]
fn validate_indexed_collection_unknown_subkey_is_warning() {
let caps = CapabilitySet::new().add_tag("software.model.0.future_field=v");
let report = validate_capabilities(&caps);
assert!(report.errors.is_empty(), "errors: {:?}", report.errors);
assert!(
report.warnings.iter().any(|w| matches!(w,
ValidationWarning::UnknownKey { axis, key }
if *axis == TaxonomyAxis::Software && key == "model.0.future_field"
)),
"missing UnknownKey warning: {:?}",
report.warnings,
);
}
#[test]
fn validate_indexed_collection_non_numeric_index_is_error() {
let caps = CapabilitySet::new().add_tag("software.model.bogus.id=foo");
let report = validate_capabilities(&caps);
assert!(
report.errors.iter().any(|e| matches!(e,
SchemaError::IndexMalformed { axis, prefix, index, .. }
if *axis == TaxonomyAxis::Software
&& prefix == "model."
&& index == "bogus"
)),
"missing IndexMalformed error: {:?}",
report.errors,
);
}
#[test]
fn validate_metadata_oversize_is_warning() {
let mut caps = CapabilitySet::new();
caps.metadata.insert("big".into(), "x".repeat(8 * 1024));
let report = validate_capabilities(&caps);
assert!(report.errors.is_empty());
assert!(
report.warnings.iter().any(|w| matches!(w,
ValidationWarning::MetadataOversize { actual_bytes, .. }
if *actual_bytes > METADATA_SOFT_CAP_BYTES
)),
"missing MetadataOversize warning: {:?}",
report.warnings,
);
}
#[test]
fn validate_metadata_reserved_key_is_warning() {
let mut caps = CapabilitySet::new();
caps.metadata
.insert("intent".to_string(), "scheduler-hint".to_string());
caps.metadata.insert("benign".to_string(), "ok".to_string());
let report = validate_capabilities(&caps);
assert!(report.errors.is_empty(), "errors: {:?}", report.errors);
assert!(
report.warnings.iter().any(|w| matches!(w,
ValidationWarning::MetadataReservedKey { key } if key == "intent"
)),
"missing MetadataReservedKey warning: {:?}",
report.warnings,
);
assert!(
!report.warnings.iter().any(|w| matches!(w,
ValidationWarning::MetadataReservedKey { key } if key == "benign"
)),
"benign key wrongly flagged: {:?}",
report.warnings,
);
}
#[test]
fn validate_metadata_reserved_prefix_is_warning() {
let mut caps = CapabilitySet::new();
caps.metadata
.insert("tool::evil::input_schema".to_string(), "spoof".to_string());
let report = validate_capabilities(&caps);
assert!(report.errors.is_empty());
assert!(
report.warnings.iter().any(|w| matches!(w,
ValidationWarning::MetadataReservedPrefix { key, prefix }
if key == "tool::evil::input_schema" && prefix == "tool::"
)),
"missing MetadataReservedPrefix warning: {:?}",
report.warnings,
);
}
#[test]
fn validate_number_rejects_negative_values() {
let mut caps = CapabilitySet::new();
caps.tags.insert(Tag::AxisValue {
axis: TaxonomyAxis::Hardware,
key: "memory_gb".to_string(),
value: "-1".to_string(),
separator: AxisSeparator::Eq,
});
let report = validate_capabilities(&caps);
assert!(
report.errors.iter().any(|e| matches!(e,
SchemaError::TypeMismatch { axis, key, expected, actual }
if *axis == TaxonomyAxis::Hardware
&& key == "memory_gb"
&& *expected == ValueType::Number
&& actual == "-1"
)),
"negative value should fail Number validation: {:?}",
report.errors,
);
}
#[test]
fn validate_keyed_map_accepts_arbitrary_runtime_name() {
let caps = CapabilitySet::new()
.with_software(SoftwareCapabilities::new().add_runtime("custom-runtime", "1.0"));
let report = validate_capabilities(&caps);
assert!(report.is_valid(), "errors: {:?}", report.errors);
}
#[test]
fn validate_report_is_valid_allows_warnings() {
let mut report = ValidationReport::default();
report
.warnings
.push(ValidationWarning::LegacyTag { tag: "x".into() });
assert!(report.is_valid());
assert!(!report.is_clean());
}
#[test]
fn validate_report_is_valid_rejects_errors() {
let mut report = ValidationReport::default();
report.errors.push(SchemaError::UnknownAxis {
axis_prefix: "compute".into(),
tag: "compute.gpu".into(),
});
assert!(!report.is_valid());
assert!(!report.is_clean());
}
#[test]
fn validate_metadata_reserved_keys_pinned() {
let pinned: HashSet<&str> = METADATA_RESERVED_KEYS.iter().copied().collect();
let expected: HashSet<&str> = [
"intent",
"colocate-with",
"colocate-with-strict",
"priority",
"owner",
]
.into_iter()
.collect();
assert_eq!(pinned, expected);
}
#[test]
fn validate_metadata_reserved_prefixes_pinned() {
let pinned: HashSet<&str> = METADATA_RESERVED_PREFIXES.iter().copied().collect();
let expected: HashSet<&str> = ["tool::"].into_iter().collect();
assert_eq!(pinned, expected);
}
}