use std::collections::BTreeMap;
use crate::adapter::net::behavior::tag::{Tag, TagKey};
#[derive(Debug, Clone, Copy)]
pub struct EvalContext<'a> {
pub tags: &'a [Tag],
pub metadata: &'a BTreeMap<String, String>,
}
impl<'a> EvalContext<'a> {
pub fn new(tags: &'a [Tag], metadata: &'a BTreeMap<String, String>) -> Self {
Self { tags, metadata }
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Predicate {
Exists {
key: TagKey,
},
Equals {
key: TagKey,
value: String,
},
NumericAtLeast {
key: TagKey,
threshold: f64,
},
NumericAtMost {
key: TagKey,
threshold: f64,
},
NumericInRange {
key: TagKey,
min: f64,
max: f64,
},
SemverAtLeast {
key: TagKey,
version: String,
},
SemverAtMost {
key: TagKey,
version: String,
},
SemverCompatible {
key: TagKey,
version: String,
},
StringPrefix {
key: TagKey,
prefix: String,
},
StringMatches {
key: TagKey,
pattern: String,
},
MetadataExists {
key: String,
},
MetadataEquals {
key: String,
value: String,
},
MetadataMatches {
key: String,
pattern: String,
},
MetadataNumericAtLeast {
key: String,
threshold: f64,
},
And(Vec<Predicate>),
Or(Vec<Predicate>),
Not(Box<Predicate>),
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum PredicateNodeWire {
Exists {
key: TagKey,
},
Equals {
key: TagKey,
value: String,
},
NumericAtLeast {
key: TagKey,
threshold: f64,
},
NumericAtMost {
key: TagKey,
threshold: f64,
},
NumericInRange {
key: TagKey,
min: f64,
max: f64,
},
SemverAtLeast {
key: TagKey,
version: String,
},
SemverAtMost {
key: TagKey,
version: String,
},
SemverCompatible {
key: TagKey,
version: String,
},
StringPrefix {
key: TagKey,
prefix: String,
},
StringMatches {
key: TagKey,
pattern: String,
},
MetadataExists {
key: String,
},
MetadataEquals {
key: String,
value: String,
},
MetadataMatches {
key: String,
pattern: String,
},
MetadataNumericAtLeast {
key: String,
threshold: f64,
},
And {
children: Vec<u32>,
},
Or {
children: Vec<u32>,
},
Not {
child: u32,
},
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PredicateWire {
pub nodes: Vec<PredicateNodeWire>,
pub root_idx: u32,
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum PredicateWireError {
#[error("predicate wire has empty nodes table")]
Empty,
#[error("predicate wire root_idx {root_idx} >= nodes len {len}")]
RootOutOfBounds {
root_idx: u32,
len: usize,
},
#[error("predicate wire child index {child} out of bounds for nodes len {len}")]
ChildOutOfBounds {
child: u32,
len: usize,
},
#[error("predicate wire child index {child} >= parent index {parent} (cycle)")]
CycleDetected {
parent: u32,
child: u32,
},
}
impl Predicate {
pub fn to_wire(&self) -> PredicateWire {
let mut nodes = Vec::new();
let root_idx = self.append_to_wire(&mut nodes);
PredicateWire { nodes, root_idx }
}
#[expect(
clippy::expect_used,
reason = "predicate tree-walk invariants: every FinishX step is preceded by a matching number of Begin steps that push children; the final pop yields the root that the walk always pushes"
)]
fn append_to_wire(&self, nodes: &mut Vec<PredicateNodeWire>) -> u32 {
enum Step<'a> {
Visit(&'a Predicate),
FinishAnd(usize),
FinishOr(usize),
FinishNot,
}
let mut work: Vec<Step<'_>> = Vec::with_capacity(8);
work.push(Step::Visit(self));
let mut child_stack: Vec<u32> = Vec::with_capacity(8);
let emit = |nodes: &mut Vec<PredicateNodeWire>,
child_stack: &mut Vec<u32>,
node: PredicateNodeWire| {
let idx = nodes.len() as u32;
nodes.push(node);
child_stack.push(idx);
};
while let Some(step) = work.pop() {
match step {
Step::Visit(p) => match p {
Self::Exists { key } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::Exists { key: key.clone() },
),
Self::Equals { key, value } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::Equals {
key: key.clone(),
value: value.clone(),
},
),
Self::NumericAtLeast { key, threshold } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::NumericAtLeast {
key: key.clone(),
threshold: *threshold,
},
),
Self::NumericAtMost { key, threshold } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::NumericAtMost {
key: key.clone(),
threshold: *threshold,
},
),
Self::NumericInRange { key, min, max } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::NumericInRange {
key: key.clone(),
min: *min,
max: *max,
},
),
Self::SemverAtLeast { key, version } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::SemverAtLeast {
key: key.clone(),
version: version.clone(),
},
),
Self::SemverAtMost { key, version } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::SemverAtMost {
key: key.clone(),
version: version.clone(),
},
),
Self::SemverCompatible { key, version } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::SemverCompatible {
key: key.clone(),
version: version.clone(),
},
),
Self::StringPrefix { key, prefix } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::StringPrefix {
key: key.clone(),
prefix: prefix.clone(),
},
),
Self::StringMatches { key, pattern } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::StringMatches {
key: key.clone(),
pattern: pattern.clone(),
},
),
Self::MetadataExists { key } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::MetadataExists { key: key.clone() },
),
Self::MetadataEquals { key, value } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::MetadataEquals {
key: key.clone(),
value: value.clone(),
},
),
Self::MetadataMatches { key, pattern } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::MetadataMatches {
key: key.clone(),
pattern: pattern.clone(),
},
),
Self::MetadataNumericAtLeast { key, threshold } => emit(
nodes,
&mut child_stack,
PredicateNodeWire::MetadataNumericAtLeast {
key: key.clone(),
threshold: *threshold,
},
),
Self::And(children) => {
work.push(Step::FinishAnd(children.len()));
for c in children.iter().rev() {
work.push(Step::Visit(c));
}
}
Self::Or(children) => {
work.push(Step::FinishOr(children.len()));
for c in children.iter().rev() {
work.push(Step::Visit(c));
}
}
Self::Not(inner) => {
work.push(Step::FinishNot);
work.push(Step::Visit(inner));
}
},
Step::FinishAnd(n) => {
let start = child_stack.len() - n;
let kids: Vec<u32> = child_stack.drain(start..).collect();
let idx = nodes.len() as u32;
nodes.push(PredicateNodeWire::And { children: kids });
child_stack.push(idx);
}
Step::FinishOr(n) => {
let start = child_stack.len() - n;
let kids: Vec<u32> = child_stack.drain(start..).collect();
let idx = nodes.len() as u32;
nodes.push(PredicateNodeWire::Or { children: kids });
child_stack.push(idx);
}
Step::FinishNot => {
let child = child_stack
.pop()
.expect("Not body must emit one child before FinishNot");
let idx = nodes.len() as u32;
nodes.push(PredicateNodeWire::Not { child });
child_stack.push(idx);
}
}
}
child_stack
.pop()
.expect("predicate must produce at least one node")
}
}
impl PredicateWire {
pub fn into_predicate(self) -> Result<Predicate, PredicateWireError> {
if self.nodes.is_empty() {
return Err(PredicateWireError::Empty);
}
let len = self.nodes.len();
if (self.root_idx as usize) >= len {
return Err(PredicateWireError::RootOutOfBounds {
root_idx: self.root_idx,
len,
});
}
rebuild_predicate(&self.nodes, self.root_idx)
}
}
fn rebuild_predicate(
nodes: &[PredicateNodeWire],
idx: u32,
) -> Result<Predicate, PredicateWireError> {
let len = nodes.len();
let node = nodes
.get(idx as usize)
.ok_or(PredicateWireError::ChildOutOfBounds { child: idx, len })?;
let result = match node {
PredicateNodeWire::Exists { key } => Predicate::Exists { key: key.clone() },
PredicateNodeWire::Equals { key, value } => Predicate::Equals {
key: key.clone(),
value: value.clone(),
},
PredicateNodeWire::NumericAtLeast { key, threshold } => Predicate::NumericAtLeast {
key: key.clone(),
threshold: *threshold,
},
PredicateNodeWire::NumericAtMost { key, threshold } => Predicate::NumericAtMost {
key: key.clone(),
threshold: *threshold,
},
PredicateNodeWire::NumericInRange { key, min, max } => Predicate::NumericInRange {
key: key.clone(),
min: *min,
max: *max,
},
PredicateNodeWire::SemverAtLeast { key, version } => Predicate::SemverAtLeast {
key: key.clone(),
version: version.clone(),
},
PredicateNodeWire::SemverAtMost { key, version } => Predicate::SemverAtMost {
key: key.clone(),
version: version.clone(),
},
PredicateNodeWire::SemverCompatible { key, version } => Predicate::SemverCompatible {
key: key.clone(),
version: version.clone(),
},
PredicateNodeWire::StringPrefix { key, prefix } => Predicate::StringPrefix {
key: key.clone(),
prefix: prefix.clone(),
},
PredicateNodeWire::StringMatches { key, pattern } => Predicate::StringMatches {
key: key.clone(),
pattern: pattern.clone(),
},
PredicateNodeWire::MetadataExists { key } => Predicate::MetadataExists { key: key.clone() },
PredicateNodeWire::MetadataEquals { key, value } => Predicate::MetadataEquals {
key: key.clone(),
value: value.clone(),
},
PredicateNodeWire::MetadataMatches { key, pattern } => Predicate::MetadataMatches {
key: key.clone(),
pattern: pattern.clone(),
},
PredicateNodeWire::MetadataNumericAtLeast { key, threshold } => {
Predicate::MetadataNumericAtLeast {
key: key.clone(),
threshold: *threshold,
}
}
PredicateNodeWire::And { children } => {
check_children_below(children, idx)?;
let kids: Result<Vec<_>, _> = children
.iter()
.map(|&c| rebuild_predicate(nodes, c))
.collect();
Predicate::And(kids?)
}
PredicateNodeWire::Or { children } => {
check_children_below(children, idx)?;
let kids: Result<Vec<_>, _> = children
.iter()
.map(|&c| rebuild_predicate(nodes, c))
.collect();
Predicate::Or(kids?)
}
PredicateNodeWire::Not { child } => {
if *child >= idx {
return Err(PredicateWireError::CycleDetected {
parent: idx,
child: *child,
});
}
Predicate::Not(Box::new(rebuild_predicate(nodes, *child)?))
}
};
Ok(result)
}
fn check_children_below(children: &[u32], parent: u32) -> Result<(), PredicateWireError> {
for &child in children {
if child >= parent {
return Err(PredicateWireError::CycleDetected { parent, child });
}
}
Ok(())
}
pub const RPC_WHERE_HEADER: &str = "net-where";
pub const MAX_PREDICATE_RPC_HEADER_VALUE_LEN: usize = 4096;
#[derive(Debug, thiserror::Error)]
pub enum PredicateRpcEncodeError {
#[error("predicate wire encode failed: {0}")]
Encode(#[from] serde_json::Error),
#[error("predicate wire encoding {actual} bytes exceeds header cap {limit}")]
TooLarge {
actual: usize,
limit: usize,
},
}
#[derive(Debug, thiserror::Error)]
pub enum PredicateRpcDecodeError {
#[error("predicate wire decode failed: {0}")]
Json(#[from] serde_json::Error),
#[error("predicate wire malformed: {0}")]
Wire(#[from] PredicateWireError),
#[error("predicate wire payload {actual} bytes exceeds header cap {limit}")]
Oversize {
actual: usize,
limit: usize,
},
}
pub fn predicate_to_rpc_header(
pred: &Predicate,
) -> Result<(String, Vec<u8>), PredicateRpcEncodeError> {
let wire = pred.to_wire();
let bytes = serde_json::to_vec(&wire)?;
if bytes.len() > MAX_PREDICATE_RPC_HEADER_VALUE_LEN {
return Err(PredicateRpcEncodeError::TooLarge {
actual: bytes.len(),
limit: MAX_PREDICATE_RPC_HEADER_VALUE_LEN,
});
}
Ok((RPC_WHERE_HEADER.to_string(), bytes))
}
pub fn predicate_from_rpc_headers<H>(
headers: &[H],
) -> Option<Result<Predicate, PredicateRpcDecodeError>>
where
H: AsRpcHeader,
{
let value = headers
.iter()
.find(|h| h.name() == RPC_WHERE_HEADER)?
.value();
if value.len() > MAX_PREDICATE_RPC_HEADER_VALUE_LEN {
return Some(Err(PredicateRpcDecodeError::Oversize {
actual: value.len(),
limit: MAX_PREDICATE_RPC_HEADER_VALUE_LEN,
}));
}
let result = serde_json::from_slice::<PredicateWire>(value)
.map_err(PredicateRpcDecodeError::Json)
.and_then(|wire| wire.into_predicate().map_err(PredicateRpcDecodeError::Wire));
Some(result)
}
pub trait AsRpcHeader {
fn name(&self) -> &str;
fn value(&self) -> &[u8];
}
impl AsRpcHeader for (String, Vec<u8>) {
fn name(&self) -> &str {
&self.0
}
fn value(&self) -> &[u8] {
&self.1
}
}
impl AsRpcHeader for &(String, Vec<u8>) {
fn name(&self) -> &str {
&self.0
}
fn value(&self) -> &[u8] {
&self.1
}
}
impl Predicate {
pub fn matches_capability_set(
&self,
caps: &crate::adapter::net::behavior::CapabilitySet,
) -> bool {
let tags: Vec<Tag> = caps.tags.iter().cloned().collect();
let ctx = EvalContext::new(&tags, &caps.metadata);
self.evaluate(&ctx)
}
}
pub trait RpcPredicateContext {
fn rpc_predicate_tags(&self) -> &[Tag];
fn rpc_predicate_metadata(&self) -> &BTreeMap<String, String>;
}
pub fn filter_by_predicate<R, I>(rows: I, pred: Option<&Predicate>) -> impl Iterator<Item = R> + '_
where
R: RpcPredicateContext,
I: IntoIterator<Item = R>,
I::IntoIter: 'static,
{
rows.into_iter().filter(move |row| match pred {
None => true,
Some(p) => {
let ctx = EvalContext::new(row.rpc_predicate_tags(), row.rpc_predicate_metadata());
p.evaluate(&ctx)
}
})
}
impl Predicate {
pub fn exists(key: TagKey) -> Self {
Self::Exists { key }
}
pub fn equals(key: TagKey, value: impl Into<String>) -> Self {
Self::Equals {
key,
value: value.into(),
}
}
pub fn numeric_at_least(key: TagKey, threshold: f64) -> Self {
Self::NumericAtLeast { key, threshold }
}
pub fn numeric_at_most(key: TagKey, threshold: f64) -> Self {
Self::NumericAtMost { key, threshold }
}
pub fn numeric_in_range(key: TagKey, min: f64, max: f64) -> Self {
Self::NumericInRange { key, min, max }
}
pub fn semver_at_least(key: TagKey, version: impl Into<String>) -> Self {
Self::SemverAtLeast {
key,
version: version.into(),
}
}
pub fn semver_at_most(key: TagKey, version: impl Into<String>) -> Self {
Self::SemverAtMost {
key,
version: version.into(),
}
}
pub fn semver_compatible(key: TagKey, version: impl Into<String>) -> Self {
Self::SemverCompatible {
key,
version: version.into(),
}
}
pub fn string_prefix(key: TagKey, prefix: impl Into<String>) -> Self {
Self::StringPrefix {
key,
prefix: prefix.into(),
}
}
pub fn string_matches(key: TagKey, pattern: impl Into<String>) -> Self {
Self::StringMatches {
key,
pattern: pattern.into(),
}
}
pub fn metadata_exists(key: impl Into<String>) -> Self {
Self::MetadataExists { key: key.into() }
}
pub fn metadata_equals(key: impl Into<String>, value: impl Into<String>) -> Self {
Self::MetadataEquals {
key: key.into(),
value: value.into(),
}
}
pub fn metadata_matches(key: impl Into<String>, pattern: impl Into<String>) -> Self {
Self::MetadataMatches {
key: key.into(),
pattern: pattern.into(),
}
}
pub fn metadata_numeric_at_least(key: impl Into<String>, threshold: f64) -> Self {
Self::MetadataNumericAtLeast {
key: key.into(),
threshold,
}
}
pub fn and(clauses: Vec<Predicate>) -> Self {
Self::And(clauses)
}
pub fn or(clauses: Vec<Predicate>) -> Self {
Self::Or(clauses)
}
#[allow(clippy::should_implement_trait)]
pub fn not(inner: Predicate) -> Self {
Self::Not(Box::new(inner))
}
}
impl Predicate {
pub fn evaluate(&self, ctx: &EvalContext<'_>) -> bool {
match self {
Self::And(children) => Self::eval_all_in_cost_order(children, ctx),
Self::Or(children) => Self::eval_any_in_cost_order(children, ctx),
Self::Not(inner) => !inner.evaluate(ctx),
other => other.evaluate_leaf(ctx),
}
}
pub fn evaluate_unplanned(&self, ctx: &EvalContext<'_>) -> bool {
match self {
Self::And(children) => children.iter().all(|c| c.evaluate_unplanned(ctx)),
Self::Or(children) => children.iter().any(|c| c.evaluate_unplanned(ctx)),
Self::Not(inner) => !inner.evaluate_unplanned(ctx),
other => other.evaluate_leaf(ctx),
}
}
fn evaluate_leaf(&self, ctx: &EvalContext<'_>) -> bool {
match self {
Self::Exists { key } => ctx.tags.iter().any(|t| t.axis_key().as_ref() == Some(key)),
Self::Equals { key, value } => match_axis_tag(ctx.tags, key, |v| v == value.as_str()),
Self::NumericAtLeast { key, threshold } => match_axis_tag(ctx.tags, key, |v| {
v.parse::<f64>().is_ok_and(|n| n >= *threshold)
}),
Self::NumericAtMost { key, threshold } => match_axis_tag(ctx.tags, key, |v| {
v.parse::<f64>().is_ok_and(|n| n <= *threshold)
}),
Self::NumericInRange { key, min, max } => match_axis_tag(ctx.tags, key, |v| {
v.parse::<f64>().is_ok_and(|n| n >= *min && n <= *max)
}),
Self::SemverAtLeast { key, version } => {
let Some(rhs) = parse_semver(version) else {
return false;
};
match_axis_tag(ctx.tags, key, |v| {
parse_semver(v).is_some_and(|lhs| lhs >= rhs)
})
}
Self::SemverAtMost { key, version } => {
let Some(rhs) = parse_semver(version) else {
return false;
};
match_axis_tag(ctx.tags, key, |v| {
parse_semver(v).is_some_and(|lhs| lhs <= rhs)
})
}
Self::SemverCompatible { key, version } => {
let Some(rhs) = parse_semver(version) else {
return false;
};
match_axis_tag(ctx.tags, key, |v| {
parse_semver(v).is_some_and(|lhs| semver_compatible(lhs, rhs))
})
}
Self::StringPrefix { key, prefix } => {
match_axis_tag(ctx.tags, key, |v| v.starts_with(prefix.as_str()))
}
Self::StringMatches { key, pattern } => {
match_axis_tag(ctx.tags, key, |v| v.contains(pattern.as_str()))
}
Self::MetadataExists { key } => ctx.metadata.contains_key(key),
Self::MetadataEquals { key, value } => {
ctx.metadata.get(key).is_some_and(|v| v == value)
}
Self::MetadataMatches { key, pattern } => ctx
.metadata
.get(key)
.is_some_and(|v| v.contains(pattern.as_str())),
Self::MetadataNumericAtLeast { key, threshold } => ctx
.metadata
.get(key)
.and_then(|v| v.parse::<f64>().ok())
.is_some_and(|n| n >= *threshold),
Self::And(_) | Self::Or(_) | Self::Not(_) => unreachable!(
"evaluate_leaf called with a composite Predicate; \
routing bug in evaluate / evaluate_unplanned"
),
}
}
fn eval_all_in_cost_order(children: &[Predicate], ctx: &EvalContext<'_>) -> bool {
let mut order: Vec<usize> = (0..children.len()).collect();
order.sort_by_key(|&i| children[i].static_cost());
order.into_iter().all(|i| children[i].evaluate(ctx))
}
fn eval_any_in_cost_order(children: &[Predicate], ctx: &EvalContext<'_>) -> bool {
let mut order: Vec<usize> = (0..children.len()).collect();
order.sort_by_key(|&i| children[i].static_cost());
order.into_iter().any(|i| children[i].evaluate(ctx))
}
fn static_cost(&self) -> u32 {
match self {
Self::MetadataExists { .. } => 10,
Self::MetadataEquals { .. } => 11,
Self::Exists { .. } => 20,
Self::Equals { .. } => 21,
Self::MetadataNumericAtLeast { .. } => 25,
Self::NumericAtLeast { .. }
| Self::NumericAtMost { .. }
| Self::NumericInRange { .. } => 30,
Self::StringPrefix { .. } => 40,
Self::MetadataMatches { .. } => 45,
Self::StringMatches { .. } => 50,
Self::SemverAtLeast { .. }
| Self::SemverAtMost { .. }
| Self::SemverCompatible { .. } => 60,
Self::And(c) | Self::Or(c) => c
.iter()
.map(|c| c.static_cost())
.fold(0u32, |a, b| a.saturating_add(b)),
Self::Not(inner) => inner.static_cost(),
}
}
fn dynamic_cost<P: crate::adapter::net::behavior::CardinalityProvider>(
&self,
index: &P,
) -> u32 {
match self {
Self::Exists { key }
| Self::Equals { key, .. }
| Self::NumericAtLeast { key, .. }
| Self::NumericAtMost { key, .. }
| Self::NumericInRange { key, .. }
| Self::SemverAtLeast { key, .. }
| Self::SemverAtMost { key, .. }
| Self::SemverCompatible { key, .. }
| Self::StringPrefix { key, .. }
| Self::StringMatches { key, .. } => {
let static_c = self.static_cost();
let cardinality = index.axis_cardinality(key);
if cardinality > 0 {
static_c.saturating_div(u32::try_from(cardinality).unwrap_or(u32::MAX).max(1))
} else {
static_c
}
}
Self::MetadataExists { key }
| Self::MetadataEquals { key, .. }
| Self::MetadataMatches { key, .. }
| Self::MetadataNumericAtLeast { key, .. } => {
let static_c = self.static_cost();
let cardinality = index.metadata_value_cardinality(key);
if cardinality > 0 {
static_c.saturating_div(u32::try_from(cardinality).unwrap_or(u32::MAX).max(1))
} else {
static_c
}
}
Self::And(c) | Self::Or(c) => c
.iter()
.map(|c| c.dynamic_cost(index))
.fold(0u32, |a, b| a.saturating_add(b)),
Self::Not(inner) => inner.dynamic_cost(index),
}
}
pub fn evaluate_with_index<P: crate::adapter::net::behavior::CardinalityProvider>(
&self,
ctx: &EvalContext<'_>,
index: &P,
) -> bool {
match self {
Self::And(children) => Self::eval_all_with_index(children, ctx, index),
Self::Or(children) => Self::eval_any_with_index(children, ctx, index),
Self::Not(inner) => !inner.evaluate_with_index(ctx, index),
other => other.evaluate_leaf(ctx),
}
}
fn eval_all_with_index<P: crate::adapter::net::behavior::CardinalityProvider>(
children: &[Predicate],
ctx: &EvalContext<'_>,
index: &P,
) -> bool {
let mut order: Vec<usize> = (0..children.len()).collect();
order.sort_by_key(|&i| children[i].dynamic_cost(index));
order
.into_iter()
.all(|i| children[i].evaluate_with_index(ctx, index))
}
fn eval_any_with_index<P: crate::adapter::net::behavior::CardinalityProvider>(
children: &[Predicate],
ctx: &EvalContext<'_>,
index: &P,
) -> bool {
let mut order: Vec<usize> = (0..children.len()).collect();
order.sort_by_key(|&i| children[i].dynamic_cost_or(index));
order
.into_iter()
.any(|i| children[i].evaluate_with_index(ctx, index))
}
fn dynamic_cost_or<P: crate::adapter::net::behavior::CardinalityProvider>(
&self,
index: &P,
) -> u32 {
match self {
Self::Exists { key }
| Self::Equals { key, .. }
| Self::NumericAtLeast { key, .. }
| Self::NumericAtMost { key, .. }
| Self::NumericInRange { key, .. }
| Self::SemverAtLeast { key, .. }
| Self::SemverAtMost { key, .. }
| Self::SemverCompatible { key, .. }
| Self::StringPrefix { key, .. }
| Self::StringMatches { key, .. } => {
let static_c = self.static_cost();
let cardinality = index.axis_cardinality(key);
if cardinality == 0 {
return static_c;
}
static_c.saturating_mul(u32::try_from(cardinality).unwrap_or(u32::MAX).max(1))
}
Self::MetadataExists { key }
| Self::MetadataEquals { key, .. }
| Self::MetadataMatches { key, .. }
| Self::MetadataNumericAtLeast { key, .. } => {
let static_c = self.static_cost();
let cardinality = index.metadata_value_cardinality(key);
if cardinality == 0 {
return static_c;
}
static_c.saturating_mul(u32::try_from(cardinality).unwrap_or(u32::MAX).max(1))
}
Self::And(c) => c
.iter()
.map(|c| c.dynamic_cost(index))
.fold(0u32, |a, b| a.saturating_add(b)),
Self::Or(c) => c
.iter()
.map(|c| c.dynamic_cost_or(index))
.fold(0u32, |a, b| a.saturating_add(b)),
Self::Not(inner) => inner.dynamic_cost_or(index),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ClauseTrace {
pub label: String,
pub result: bool,
pub children: Vec<ClauseTrace>,
}
impl Predicate {
pub fn evaluate_with_trace(&self, ctx: &EvalContext<'_>) -> (bool, ClauseTrace) {
let label = self.debug_label();
match self {
Self::And(children) => {
let mut order: Vec<usize> = (0..children.len()).collect();
order.sort_by_key(|&i| children[i].static_cost());
let mut traces = Vec::with_capacity(order.len());
let mut result = true;
for i in order {
let (r, t) = children[i].evaluate_with_trace(ctx);
traces.push(t);
if !r {
result = false;
break;
}
}
(
result,
ClauseTrace {
label,
result,
children: traces,
},
)
}
Self::Or(children) => {
let mut order: Vec<usize> = (0..children.len()).collect();
order.sort_by_key(|&i| children[i].static_cost());
let mut traces = Vec::with_capacity(order.len());
let mut result = false;
for i in order {
let (r, t) = children[i].evaluate_with_trace(ctx);
traces.push(t);
if r {
result = true;
break;
}
}
(
result,
ClauseTrace {
label,
result,
children: traces,
},
)
}
Self::Not(inner) => {
let (r, t) = inner.evaluate_with_trace(ctx);
(
!r,
ClauseTrace {
label,
result: !r,
children: vec![t],
},
)
}
leaf => {
let result = leaf.evaluate_leaf(ctx);
(
result,
ClauseTrace {
label,
result,
children: Vec::new(),
},
)
}
}
}
fn debug_label(&self) -> String {
match self {
Self::Exists { key } => format!("Exists({key})"),
Self::Equals { key, value } => format!("Equals({key}={value})"),
Self::NumericAtLeast { key, threshold } => {
format!("NumericAtLeast({key} >= {threshold})")
}
Self::NumericAtMost { key, threshold } => {
format!("NumericAtMost({key} <= {threshold})")
}
Self::NumericInRange { key, min, max } => {
format!("NumericInRange({key} in [{min}, {max}])")
}
Self::SemverAtLeast { key, version } => {
format!("SemverAtLeast({key} >= {version})")
}
Self::SemverAtMost { key, version } => {
format!("SemverAtMost({key} <= {version})")
}
Self::SemverCompatible { key, version } => {
format!("SemverCompatible({key} ~= {version})")
}
Self::StringPrefix { key, prefix } => {
format!("StringPrefix({key} starts with {prefix:?})")
}
Self::StringMatches { key, pattern } => {
format!("StringMatches({key} contains {pattern:?})")
}
Self::MetadataExists { key } => format!("MetadataExists({key})"),
Self::MetadataEquals { key, value } => {
format!("MetadataEquals({key}={value})")
}
Self::MetadataMatches { key, pattern } => {
format!("MetadataMatches({key} contains {pattern:?})")
}
Self::MetadataNumericAtLeast { key, threshold } => {
format!("MetadataNumericAtLeast({key} >= {threshold})")
}
Self::And(c) => format!("And({} clauses)", c.len()),
Self::Or(c) => format!("Or({} clauses)", c.len()),
Self::Not(_) => "Not".to_string(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ClauseStats {
pub label: String,
pub evaluated: usize,
pub matched: usize,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PredicateDebugReport {
pub total_candidates: usize,
pub matched: usize,
pub clause_stats: std::collections::BTreeMap<String, ClauseStats>,
}
impl PredicateDebugReport {
pub fn from_evaluations<'a, I>(pred: &Predicate, contexts: I) -> Self
where
I: IntoIterator<Item = EvalContext<'a>>,
{
let mut report = Self::default();
for ctx in contexts {
report.total_candidates += 1;
let (matched, trace) = pred.evaluate_with_trace(&ctx);
if matched {
report.matched += 1;
}
accumulate_trace(&trace, &mut report.clause_stats);
}
report
}
pub fn render(&self) -> String {
let mut out = String::new();
let pct = |num: usize, denom: usize| -> f64 {
if denom == 0 {
0.0
} else {
100.0 * (num as f64) / (denom as f64)
}
};
out.push_str("Predicate evaluation report\n");
out.push_str("─────────────────────────────────────────\n");
out.push_str(&format!(
"Total candidates: {}\nMatched: {} ({:.1}%)\n\n",
self.total_candidates,
self.matched,
pct(self.matched, self.total_candidates),
));
out.push_str("Per-clause stats (alphabetical):\n");
for stats in self.clause_stats.values() {
out.push_str(&format!(
" {:<60} evaluated {:>5}, matched {:>5} ({:>5.1}%)\n",
stats.label,
stats.evaluated,
stats.matched,
pct(stats.matched, stats.evaluated),
));
}
out
}
}
fn accumulate_trace(
trace: &ClauseTrace,
acc: &mut std::collections::BTreeMap<String, ClauseStats>,
) {
let entry = acc
.entry(trace.label.clone())
.or_insert_with(|| ClauseStats {
label: trace.label.clone(),
evaluated: 0,
matched: 0,
});
entry.evaluated += 1;
if trace.result {
entry.matched += 1;
}
for child in &trace.children {
accumulate_trace(child, acc);
}
}
fn match_axis_tag(tags: &[Tag], key: &TagKey, value_pred: impl Fn(&str) -> bool) -> bool {
tags.iter().any(|t| {
t.axis_key().as_ref() == Some(key)
&& match t {
Tag::AxisValue { value, .. } => value_pred(value),
_ => false,
}
})
}
type SemverTriple = (u64, u64, u64);
fn parse_semver(s: &str) -> Option<SemverTriple> {
let core = s
.split_once('-')
.map(|(c, _)| c)
.unwrap_or(s)
.split_once('+')
.map(|(c, _)| c)
.unwrap_or_else(|| s.split_once('-').map(|(c, _)| c).unwrap_or(s));
let mut parts = core.split('.').map(str::trim);
let major = parts.next()?.parse().ok()?;
let minor = parts.next().map(|p| p.parse().ok()).unwrap_or(Some(0))?;
let patch = parts.next().map(|p| p.parse().ok()).unwrap_or(Some(0))?;
if parts.next().is_some() {
return None; }
Some((major, minor, patch))
}
fn semver_compatible(lhs: SemverTriple, rhs: SemverTriple) -> bool {
if lhs < rhs {
return false;
}
if rhs.0 == 0 {
if rhs.1 == 0 {
lhs == rhs
} else {
lhs.0 == 0 && rhs.1 == lhs.1
}
} else {
rhs.0 == lhs.0
}
}
#[macro_export]
macro_rules! pred {
(exists $key:literal) => {
$crate::adapter::net::behavior::predicate::Predicate::exists(
$crate::adapter::net::behavior::predicate::__tag_key_from_str($key),
)
};
(equals $key:literal, $value:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::equals(
$crate::adapter::net::behavior::predicate::__tag_key_from_str($key),
$value,
)
};
(num_at_least $key:literal, $t:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::numeric_at_least(
$crate::adapter::net::behavior::predicate::__tag_key_from_str($key),
$t,
)
};
(num_at_most $key:literal, $t:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::numeric_at_most(
$crate::adapter::net::behavior::predicate::__tag_key_from_str($key),
$t,
)
};
(num_in_range $key:literal, $min:expr, $max:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::numeric_in_range(
$crate::adapter::net::behavior::predicate::__tag_key_from_str($key),
$min,
$max,
)
};
(semver_at_least $key:literal, $v:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::semver_at_least(
$crate::adapter::net::behavior::predicate::__tag_key_from_str($key),
$v,
)
};
(semver_at_most $key:literal, $v:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::semver_at_most(
$crate::adapter::net::behavior::predicate::__tag_key_from_str($key),
$v,
)
};
(semver_compatible $key:literal, $v:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::semver_compatible(
$crate::adapter::net::behavior::predicate::__tag_key_from_str($key),
$v,
)
};
(prefix $key:literal, $p:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::string_prefix(
$crate::adapter::net::behavior::predicate::__tag_key_from_str($key),
$p,
)
};
(matches $key:literal, $p:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::string_matches(
$crate::adapter::net::behavior::predicate::__tag_key_from_str($key),
$p,
)
};
(metadata_exists $key:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::metadata_exists($key)
};
(metadata_equals $key:expr, $v:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::metadata_equals($key, $v)
};
(metadata_matches $key:expr, $p:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::metadata_matches($key, $p)
};
(metadata_num_at_least $key:expr, $t:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::metadata_numeric_at_least(
$key, $t,
)
};
(and [ $($clause:expr),* $(,)? ]) => {
$crate::adapter::net::behavior::predicate::Predicate::and(vec![$($clause),*])
};
(or [ $($clause:expr),* $(,)? ]) => {
$crate::adapter::net::behavior::predicate::Predicate::or(vec![$($clause),*])
};
(not $clause:expr) => {
$crate::adapter::net::behavior::predicate::Predicate::not($clause)
};
}
#[doc(hidden)]
pub fn __tag_key_from_str(s: &'static str) -> TagKey {
let (axis_str, key) = s
.split_once('.')
.unwrap_or_else(|| panic!("pred! tag key {s:?} must be `<axis>.<key>`"));
let axis = crate::adapter::net::behavior::tag::TaxonomyAxis::from_prefix(axis_str)
.unwrap_or_else(|| {
panic!(
"pred! tag key {s:?} has unknown axis prefix {axis_str:?}; \
valid axes: hardware, software, devices, dataforts"
)
});
TagKey::new(axis, key.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::adapter::net::behavior::tag::{Tag, TaxonomyAxis};
use crate::adapter::net::behavior::{CapabilitySet, GpuInfo, GpuVendor, HardwareCapabilities};
fn ctx<'a>(tags: &'a [Tag], metadata: &'a BTreeMap<String, String>) -> EvalContext<'a> {
EvalContext::new(tags, metadata)
}
fn empty_meta() -> BTreeMap<String, String> {
BTreeMap::new()
}
fn axis_present(axis: TaxonomyAxis, key: &str) -> Tag {
Tag::AxisPresent {
axis,
key: key.into(),
}
}
fn axis_eq(axis: TaxonomyAxis, key: &str, value: &str) -> Tag {
Tag::AxisValue {
axis,
key: key.into(),
value: value.into(),
separator: crate::adapter::net::behavior::tag::AxisSeparator::Eq,
}
}
#[test]
fn exists_matches_axis_present_tag() {
let tags = [axis_present(TaxonomyAxis::Hardware, "gpu")];
let meta = empty_meta();
let p = pred!(exists "hardware.gpu");
assert!(p.evaluate(&ctx(&tags, &meta)));
}
#[test]
fn exists_matches_axis_value_tag() {
let tags = [axis_eq(TaxonomyAxis::Hardware, "gpu.vram_gb", "80")];
let meta = empty_meta();
let p = pred!(exists "hardware.gpu.vram_gb");
assert!(p.evaluate(&ctx(&tags, &meta)));
}
#[test]
fn exists_misses_when_axis_differs() {
let tags = [axis_present(TaxonomyAxis::Software, "gpu")];
let meta = empty_meta();
let p = pred!(exists "hardware.gpu");
assert!(!p.evaluate(&ctx(&tags, &meta)));
}
#[test]
fn equals_matches_value_exactly() {
let tags = [axis_eq(TaxonomyAxis::Software, "runtime", "cuda-12.4")];
let meta = empty_meta();
assert!(pred!(equals "software.runtime", "cuda-12.4").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(equals "software.runtime", "cuda-11").evaluate(&ctx(&tags, &meta)));
}
#[test]
fn numeric_at_least_compares_value() {
let tags = [axis_eq(TaxonomyAxis::Hardware, "gpu.vram_gb", "80")];
let meta = empty_meta();
assert!(pred!(num_at_least "hardware.gpu.vram_gb", 24.0).evaluate(&ctx(&tags, &meta)));
assert!(pred!(num_at_least "hardware.gpu.vram_gb", 80.0).evaluate(&ctx(&tags, &meta)));
assert!(!pred!(num_at_least "hardware.gpu.vram_gb", 96.0).evaluate(&ctx(&tags, &meta)));
}
#[test]
fn numeric_at_most_and_in_range() {
let tags = [axis_eq(TaxonomyAxis::Hardware, "cpu_cores", "16")];
let meta = empty_meta();
assert!(pred!(num_at_most "hardware.cpu_cores", 32.0).evaluate(&ctx(&tags, &meta)));
assert!(!pred!(num_at_most "hardware.cpu_cores", 8.0).evaluate(&ctx(&tags, &meta)));
assert!(pred!(num_in_range "hardware.cpu_cores", 8.0, 32.0).evaluate(&ctx(&tags, &meta)));
assert!(!pred!(num_in_range "hardware.cpu_cores", 32.0, 64.0).evaluate(&ctx(&tags, &meta)));
}
#[test]
fn numeric_unparseable_value_evaluates_to_false() {
let tags = [axis_eq(TaxonomyAxis::Hardware, "cpu_cores", "many")];
let meta = empty_meta();
assert!(!pred!(num_at_least "hardware.cpu_cores", 1.0).evaluate(&ctx(&tags, &meta)));
}
#[test]
fn semver_at_least_basic() {
let tags = [axis_eq(TaxonomyAxis::Software, "runtime", "12.4.1")];
let meta = empty_meta();
assert!(pred!(semver_at_least "software.runtime", "12.0.0").evaluate(&ctx(&tags, &meta)));
assert!(pred!(semver_at_least "software.runtime", "12.4.0").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(semver_at_least "software.runtime", "13.0.0").evaluate(&ctx(&tags, &meta)));
}
#[test]
fn semver_compatible_caret_rule() {
let tags = [axis_eq(TaxonomyAxis::Software, "runtime", "1.5.2")];
let meta = empty_meta();
assert!(pred!(semver_compatible "software.runtime", "1.0.0").evaluate(&ctx(&tags, &meta)));
assert!(pred!(semver_compatible "software.runtime", "1.4.0").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(semver_compatible "software.runtime", "0.9.0").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(semver_compatible "software.runtime", "2.0.0").evaluate(&ctx(&tags, &meta)));
let tags = [axis_eq(TaxonomyAxis::Software, "runtime", "0.5.7")];
assert!(pred!(semver_compatible "software.runtime", "0.5.0").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(semver_compatible "software.runtime", "0.4.0").evaluate(&ctx(&tags, &meta)));
}
#[test]
fn semver_compatible_zero_zero_patch_is_exact_only() {
let meta = empty_meta();
let tags = [axis_eq(TaxonomyAxis::Software, "runtime", "0.0.3")];
assert!(pred!(semver_compatible "software.runtime", "0.0.3").evaluate(&ctx(&tags, &meta)));
let tags = [axis_eq(TaxonomyAxis::Software, "runtime", "0.0.4")];
assert!(!pred!(semver_compatible "software.runtime", "0.0.3").evaluate(&ctx(&tags, &meta)));
let tags = [axis_eq(TaxonomyAxis::Software, "runtime", "0.0.2")];
assert!(!pred!(semver_compatible "software.runtime", "0.0.3").evaluate(&ctx(&tags, &meta)));
let tags = [axis_eq(TaxonomyAxis::Software, "runtime", "0.1.0")];
assert!(!pred!(semver_compatible "software.runtime", "0.0.3").evaluate(&ctx(&tags, &meta)));
}
#[test]
fn semver_compatible_zero_x_band_requires_lhs_major_zero() {
let meta = empty_meta();
let tags = [axis_eq(TaxonomyAxis::Software, "runtime", "0.2.5")];
assert!(pred!(semver_compatible "software.runtime", "0.2.3").evaluate(&ctx(&tags, &meta)));
let tags = [axis_eq(TaxonomyAxis::Software, "runtime", "1.2.5")];
assert!(!pred!(semver_compatible "software.runtime", "0.2.3").evaluate(&ctx(&tags, &meta)));
let tags = [axis_eq(TaxonomyAxis::Software, "runtime", "2.2.5")];
assert!(!pred!(semver_compatible "software.runtime", "0.2.3").evaluate(&ctx(&tags, &meta)));
}
#[test]
fn axis_present_does_not_satisfy_value_predicates() {
let tags = [axis_present(TaxonomyAxis::Hardware, "gpu")];
let meta = empty_meta();
assert!(!pred!(equals "hardware.gpu", "").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(equals "hardware.gpu", "nvidia").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(prefix "hardware.gpu", "").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(matches "hardware.gpu", "").evaluate(&ctx(&tags, &meta)));
assert!(pred!(exists "hardware.gpu").evaluate(&ctx(&tags, &meta)));
let tags = [axis_eq(TaxonomyAxis::Hardware, "gpu", "nvidia")];
assert!(pred!(exists "hardware.gpu").evaluate(&ctx(&tags, &meta)));
}
#[test]
fn semver_lenient_parser() {
assert_eq!(parse_semver("1"), Some((1, 0, 0)));
assert_eq!(parse_semver("1.2"), Some((1, 2, 0)));
assert_eq!(parse_semver("1.2.3"), Some((1, 2, 3)));
assert_eq!(parse_semver("1.2.3-beta"), Some((1, 2, 3)));
assert_eq!(parse_semver("1.2.3+build.42"), Some((1, 2, 3)));
assert_eq!(parse_semver("1.2.3.4"), None);
assert_eq!(parse_semver("a.b.c"), None);
assert_eq!(parse_semver(""), None);
}
#[test]
fn string_prefix_and_matches() {
let tags = [axis_eq(TaxonomyAxis::Software, "tool", "ffmpeg-7.0")];
let meta = empty_meta();
assert!(pred!(prefix "software.tool", "ffmpeg").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(prefix "software.tool", "imagemagick").evaluate(&ctx(&tags, &meta)));
assert!(pred!(matches "software.tool", "7.0").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(matches "software.tool", "8.0").evaluate(&ctx(&tags, &meta)));
}
#[test]
fn metadata_predicates() {
let tags: Vec<Tag> = vec![];
let mut meta = BTreeMap::new();
meta.insert("intent".into(), "ml-training".into());
meta.insert("priority".into(), "5".into());
assert!(pred!(metadata_exists "intent").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(metadata_exists "missing").evaluate(&ctx(&tags, &meta)));
assert!(pred!(metadata_equals "intent", "ml-training").evaluate(&ctx(&tags, &meta)));
assert!(!pred!(metadata_equals "intent", "billing").evaluate(&ctx(&tags, &meta)));
assert!(pred!(metadata_matches "intent", "training").evaluate(&ctx(&tags, &meta)));
assert!(pred!(metadata_num_at_least "priority", 3.0).evaluate(&ctx(&tags, &meta)));
assert!(!pred!(metadata_num_at_least "priority", 10.0).evaluate(&ctx(&tags, &meta)));
}
#[test]
fn and_or_not_composition() {
let tags = [
axis_present(TaxonomyAxis::Hardware, "gpu"),
axis_eq(TaxonomyAxis::Hardware, "gpu.vram_gb", "80"),
];
let meta = empty_meta();
let p = pred!(and [
pred!(exists "hardware.gpu"),
pred!(num_at_least "hardware.gpu.vram_gb", 24.0),
]);
assert!(p.evaluate(&ctx(&tags, &meta)));
let p = pred!(and [
pred!(exists "hardware.gpu"),
pred!(num_at_least "hardware.gpu.vram_gb", 96.0),
]);
assert!(!p.evaluate(&ctx(&tags, &meta)));
let p = pred!(or [
pred!(exists "hardware.tpu"),
pred!(exists "hardware.gpu"),
]);
assert!(p.evaluate(&ctx(&tags, &meta)));
let p = pred!(not pred!(exists "hardware.tpu"));
assert!(p.evaluate(&ctx(&tags, &meta)));
let p = pred!(not pred!(exists "hardware.gpu"));
assert!(!p.evaluate(&ctx(&tags, &meta)));
}
#[test]
fn empty_and_is_vacuously_true() {
let tags: Vec<Tag> = vec![];
let meta = empty_meta();
assert!(Predicate::and(vec![]).evaluate(&ctx(&tags, &meta)));
}
#[test]
fn empty_or_is_vacuously_false() {
let tags: Vec<Tag> = vec![];
let meta = empty_meta();
assert!(!Predicate::or(vec![]).evaluate(&ctx(&tags, &meta)));
}
#[test]
fn not_does_not_flip_unparseable_to_true() {
let tags = [axis_eq(TaxonomyAxis::Hardware, "cpu_cores", "many")];
let meta = empty_meta();
let p = pred!(not pred!(num_at_least "hardware.cpu_cores", 1.0));
assert!(p.evaluate(&ctx(&tags, &meta)));
}
#[test]
fn clone_and_eq_preserve_ast() {
let p = pred!(and [
pred!(exists "hardware.gpu"),
pred!(num_at_least "hardware.gpu.vram_gb", 24.0),
pred!(or [
pred!(equals "software.runtime", "cuda-12.4"),
pred!(semver_compatible "software.runtime", "13.0"),
]),
pred!(not pred!(metadata_exists "decommissioning")),
]);
let p2 = p.clone();
assert_eq!(p, p2);
}
#[test]
#[should_panic(expected = "unknown axis prefix")]
fn pred_macro_panics_on_unknown_axis() {
let _ = pred!(exists "bogus.foo");
}
#[test]
#[should_panic(expected = "must be `<axis>.<key>`")]
fn pred_macro_panics_on_missing_dot() {
let _ = pred!(exists "hardware");
}
fn meta_with(pairs: &[(&str, &str)]) -> BTreeMap<String, String> {
pairs
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect()
}
fn worst_case_and() -> Predicate {
Predicate::And(vec![
Predicate::SemverCompatible {
key: TagKey::new(TaxonomyAxis::Software, "runtime.python"),
version: "3.11".into(),
},
Predicate::StringMatches {
key: TagKey::new(TaxonomyAxis::Software, "os"),
pattern: "linux".into(),
},
Predicate::NumericAtLeast {
key: TagKey::new(TaxonomyAxis::Hardware, "memory_gb"),
threshold: 64.0,
},
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
},
Predicate::MetadataEquals {
key: "intent".into(),
value: "ml-training".into(),
},
])
}
#[test]
fn planner_reorders_and_children_cheap_first() {
let ast = worst_case_and();
if let Predicate::And(children) = &ast {
let costs: Vec<u32> = children.iter().map(|c| c.static_cost()).collect();
assert_eq!(costs, vec![60, 50, 30, 20, 11]);
} else {
panic!("worst_case_and produced non-And");
}
}
#[test]
fn planner_preserves_semantics_on_short_circuit_false() {
let tags: Vec<Tag> = vec![axis_eq(TaxonomyAxis::Hardware, "memory_gb", "32")];
let meta = empty_meta();
let cx = ctx(&tags, &meta);
let ast = worst_case_and();
assert!(!ast.evaluate(&cx));
assert!(!ast.evaluate_unplanned(&cx));
}
#[test]
fn planner_preserves_semantics_on_full_match() {
let tags: Vec<Tag> = vec![
axis_eq(TaxonomyAxis::Hardware, "memory_gb", "128"),
axis_present(TaxonomyAxis::Hardware, "gpu"),
axis_eq(TaxonomyAxis::Software, "os", "linux"),
axis_eq(TaxonomyAxis::Software, "runtime.python", "3.11.5"),
];
let meta = meta_with(&[("intent", "ml-training")]);
let cx = ctx(&tags, &meta);
let ast = worst_case_and();
assert!(ast.evaluate(&cx));
assert!(ast.evaluate_unplanned(&cx));
}
#[test]
fn planner_preserves_or_short_circuit_semantics() {
let ast = Predicate::Or(vec![
Predicate::SemverCompatible {
key: TagKey::new(TaxonomyAxis::Software, "runtime.python"),
version: "9.9".into(),
},
Predicate::MetadataEquals {
key: "intent".into(),
value: "ml-training".into(),
},
]);
let meta = meta_with(&[("intent", "ml-training")]);
let cx = ctx(&[], &meta);
assert!(ast.evaluate(&cx));
assert!(ast.evaluate_unplanned(&cx));
}
#[test]
fn planner_static_cost_compositees_sum_children() {
let cheap = Predicate::MetadataExists { key: "k".into() };
let expensive = Predicate::SemverCompatible {
key: TagKey::new(TaxonomyAxis::Software, "x"),
version: "1.0".into(),
};
let nested = Predicate::And(vec![cheap.clone(), expensive.clone()]);
let leaf_cost = cheap.static_cost() + expensive.static_cost();
assert_eq!(nested.static_cost(), leaf_cost);
let negated = Predicate::Not(Box::new(expensive.clone()));
assert_eq!(negated.static_cost(), expensive.static_cost());
}
#[test]
fn planner_handles_empty_and_or_correctly() {
let meta = BTreeMap::new();
let cx = ctx(&[], &meta);
assert!(Predicate::And(vec![]).evaluate(&cx));
assert!(!Predicate::Or(vec![]).evaluate(&cx));
assert!(Predicate::And(vec![]).evaluate_unplanned(&cx));
assert!(!Predicate::Or(vec![]).evaluate_unplanned(&cx));
}
#[test]
fn planner_evaluate_matches_unplanned_across_canonical_inputs() {
let predicates: Vec<Predicate> = vec![
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
},
Predicate::MetadataEquals {
key: "intent".into(),
value: "ml-training".into(),
},
Predicate::NumericAtLeast {
key: TagKey::new(TaxonomyAxis::Hardware, "memory_gb"),
threshold: 64.0,
},
worst_case_and(),
Predicate::Or(vec![
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
},
Predicate::MetadataEquals {
key: "intent".into(),
value: "ml-training".into(),
},
]),
Predicate::And(vec![
Predicate::Or(vec![
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
},
Predicate::And(vec![
Predicate::NumericAtLeast {
key: TagKey::new(TaxonomyAxis::Hardware, "memory_gb"),
threshold: 64.0,
},
Predicate::MetadataExists {
key: "intent".into(),
},
]),
]),
Predicate::Not(Box::new(Predicate::MetadataEquals {
key: "decommissioning".into(),
value: "true".into(),
})),
]),
];
let contexts: Vec<(Vec<Tag>, BTreeMap<String, String>)> = vec![
(vec![], BTreeMap::new()),
(
vec![
axis_present(TaxonomyAxis::Hardware, "gpu"),
axis_eq(TaxonomyAxis::Hardware, "memory_gb", "128"),
],
BTreeMap::new(),
),
(vec![], meta_with(&[("intent", "ml-training")])),
(
vec![
axis_present(TaxonomyAxis::Hardware, "gpu"),
axis_eq(TaxonomyAxis::Hardware, "memory_gb", "128"),
axis_eq(TaxonomyAxis::Software, "os", "linux"),
axis_eq(TaxonomyAxis::Software, "runtime.python", "3.11.5"),
],
meta_with(&[("intent", "ml-training")]),
),
(
vec![
axis_present(TaxonomyAxis::Hardware, "gpu"),
axis_eq(TaxonomyAxis::Hardware, "memory_gb", "128"),
],
meta_with(&[("intent", "ml-training"), ("decommissioning", "true")]),
),
];
for (i, ast) in predicates.iter().enumerate() {
for (j, (tags, meta)) in contexts.iter().enumerate() {
let cx = ctx(tags, meta);
let planned = ast.evaluate(&cx);
let unplanned = ast.evaluate_unplanned(&cx);
assert_eq!(
planned, unplanned,
"predicate[{i}] ctx[{j}]: planned={planned} != unplanned={unplanned}"
);
}
}
}
#[test]
fn evaluate_with_trace_returns_same_result_as_evaluate() {
let ast = worst_case_and();
let tags: Vec<Tag> = vec![axis_eq(TaxonomyAxis::Hardware, "memory_gb", "32")];
let meta = empty_meta();
let cx = ctx(&tags, &meta);
let plain_result = ast.evaluate(&cx);
let (traced_result, _trace) = ast.evaluate_with_trace(&cx);
assert_eq!(plain_result, traced_result);
}
#[test]
fn evaluate_with_trace_short_circuits_drop_unevaluated_siblings() {
let ast = Predicate::And(vec![
Predicate::MetadataEquals {
key: "intent".into(),
value: "ml-training".into(),
},
Predicate::SemverCompatible {
key: TagKey::new(TaxonomyAxis::Software, "runtime.python"),
version: "3.11".into(),
},
]);
let meta = empty_meta();
let cx = ctx(&[], &meta); let (result, trace) = ast.evaluate_with_trace(&cx);
assert!(!result);
assert_eq!(
trace.children.len(),
1,
"And trace should drop unevaluated siblings; got {trace:?}"
);
assert!(trace.children[0].label.starts_with("MetadataEquals"));
assert!(!trace.children[0].result);
}
#[test]
fn evaluate_with_trace_captures_full_evaluation_when_no_short_circuit() {
let ast = Predicate::And(vec![
Predicate::MetadataExists {
key: "intent".into(),
},
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
},
]);
let tags: Vec<Tag> = vec![axis_present(TaxonomyAxis::Hardware, "gpu")];
let meta = meta_with(&[("intent", "ml-training")]);
let cx = ctx(&tags, &meta);
let (result, trace) = ast.evaluate_with_trace(&cx);
assert!(result);
assert_eq!(trace.children.len(), 2);
for child in &trace.children {
assert!(child.result, "all children must have matched: {child:?}");
}
}
#[test]
fn evaluate_with_trace_records_not_inversion() {
let ast = Predicate::Not(Box::new(Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
}));
let meta = empty_meta();
let cx = ctx(&[], &meta); let (result, trace) = ast.evaluate_with_trace(&cx);
assert!(result, "Not(absent) should be true");
assert_eq!(trace.label, "Not");
assert!(trace.result);
assert_eq!(trace.children.len(), 1);
assert!(!trace.children[0].result, "inner Exists should be false");
}
#[test]
fn debug_report_aggregates_match_counts() {
let pred = Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
};
let no_gpu_tags: Vec<Tag> = vec![];
let gpu_tags: Vec<Tag> = vec![axis_present(TaxonomyAxis::Hardware, "gpu")];
let meta = empty_meta();
let contexts = vec![
ctx(&no_gpu_tags, &meta),
ctx(&gpu_tags, &meta),
ctx(&no_gpu_tags, &meta),
];
let report = PredicateDebugReport::from_evaluations(&pred, contexts);
assert_eq!(report.total_candidates, 3);
assert_eq!(report.matched, 1);
assert_eq!(report.clause_stats.len(), 1);
let stats = report.clause_stats.values().next().unwrap();
assert_eq!(stats.evaluated, 3);
assert_eq!(stats.matched, 1);
}
#[test]
fn debug_report_separates_per_clause_stats_in_composite() {
let pred = Predicate::And(vec![
Predicate::MetadataEquals {
key: "intent".into(),
value: "ml-training".into(),
}, Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
}, ]);
let no_meta = empty_meta();
let intent_meta = meta_with(&[("intent", "ml-training")]);
let no_gpu: Vec<Tag> = vec![];
let gpu: Vec<Tag> = vec![axis_present(TaxonomyAxis::Hardware, "gpu")];
let contexts = vec![
ctx(&no_gpu, &no_meta), ctx(&gpu, &no_meta), ctx(&no_gpu, &intent_meta), ctx(&gpu, &intent_meta), ];
let report = PredicateDebugReport::from_evaluations(&pred, contexts);
assert_eq!(report.total_candidates, 4);
assert_eq!(report.matched, 1);
assert_eq!(report.clause_stats.len(), 3);
let metadata_stats = report
.clause_stats
.values()
.find(|s| s.label.starts_with("MetadataEquals"))
.expect("MetadataEquals stats present");
assert_eq!(
metadata_stats.evaluated, 4,
"metadata clause runs every time"
);
assert_eq!(metadata_stats.matched, 2, "intent matches in 2 of 4");
let exists_stats = report
.clause_stats
.values()
.find(|s| s.label.starts_with("Exists"))
.expect("Exists stats present");
assert_eq!(
exists_stats.evaluated, 2,
"gpu clause only runs after metadata passes"
);
assert_eq!(exists_stats.matched, 1);
}
#[test]
fn debug_report_render_includes_summary_and_clauses() {
let pred = Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
};
let report = PredicateDebugReport::from_evaluations(&pred, vec![ctx(&[], &empty_meta())]);
let rendered = report.render();
assert!(rendered.contains("Predicate evaluation report"));
assert!(rendered.contains("Total candidates: 1"));
assert!(rendered.contains("Matched: 0"));
assert!(rendered.contains("Per-clause stats"));
assert!(rendered.contains("Exists(hardware.gpu)"));
}
#[test]
fn debug_report_handles_empty_corpus() {
let pred = Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
};
let report = PredicateDebugReport::from_evaluations(&pred, Vec::<EvalContext>::new());
assert_eq!(report.total_candidates, 0);
assert_eq!(report.matched, 0);
assert!(report.clause_stats.is_empty());
let rendered = report.render();
assert!(rendered.contains("Total candidates: 0"));
}
fn sample_complex_predicate() -> Predicate {
Predicate::And(vec![
Predicate::Or(vec![
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
},
Predicate::And(vec![
Predicate::NumericAtLeast {
key: TagKey::new(TaxonomyAxis::Hardware, "memory_gb"),
threshold: 64.0,
},
Predicate::MetadataExists {
key: "intent".into(),
},
]),
]),
Predicate::Not(Box::new(Predicate::MetadataEquals {
key: "decommissioning".into(),
value: "true".into(),
})),
Predicate::SemverCompatible {
key: TagKey::new(TaxonomyAxis::Software, "runtime.python"),
version: "3.11".into(),
},
])
}
#[test]
fn wire_round_trip_preserves_complex_predicate() {
let original = sample_complex_predicate();
let wire = original.to_wire();
let rebuilt = wire.into_predicate().expect("rebuild");
assert_eq!(original, rebuilt);
}
#[test]
fn wire_round_trip_through_serde_json() {
let original = sample_complex_predicate();
let wire = original.to_wire();
let json = serde_json::to_string(&wire).expect("serialize wire");
let parsed: PredicateWire = serde_json::from_str(&json).expect("deserialize wire");
let rebuilt = parsed.into_predicate().expect("rebuild");
assert_eq!(original, rebuilt);
}
#[test]
fn wire_root_is_at_highest_index_in_post_order_emission() {
let pred = sample_complex_predicate();
let wire = pred.to_wire();
assert_eq!(wire.root_idx as usize, wire.nodes.len() - 1);
}
#[test]
fn wire_round_trip_byte_stable_across_calls() {
let pred = sample_complex_predicate();
let wire_a = pred.to_wire();
let wire_b = pred.to_wire();
assert_eq!(wire_a, wire_b);
let json_a = serde_json::to_string(&wire_a).unwrap();
let json_b = serde_json::to_string(&wire_b).unwrap();
assert_eq!(json_a, json_b);
}
#[test]
fn wire_round_trip_preserves_evaluation_semantics() {
let original = sample_complex_predicate();
let wire = original.to_wire();
let rebuilt = wire.into_predicate().unwrap();
let no_meta = empty_meta();
let intent_meta = meta_with(&[("intent", "ml-training")]);
let decommission_meta =
meta_with(&[("intent", "ml-training"), ("decommissioning", "true")]);
let no_gpu: Vec<Tag> = vec![];
let gpu: Vec<Tag> = vec![axis_present(TaxonomyAxis::Hardware, "gpu")];
let gpu_with_runtime: Vec<Tag> = vec![
axis_present(TaxonomyAxis::Hardware, "gpu"),
axis_eq(TaxonomyAxis::Software, "runtime.python", "3.11.5"),
];
let cases: Vec<(&[Tag], &BTreeMap<String, String>)> = vec![
(&no_gpu, &no_meta),
(&gpu, &no_meta),
(&gpu, &intent_meta),
(&gpu_with_runtime, &intent_meta),
(&gpu_with_runtime, &decommission_meta),
];
for (i, (tags, meta)) in cases.iter().enumerate() {
let cx = ctx(tags, meta);
assert_eq!(
original.evaluate(&cx),
rebuilt.evaluate(&cx),
"case {i}: original vs rebuilt diverged on evaluation",
);
}
}
#[test]
fn wire_from_empty_nodes_table_errors_gracefully() {
let wire = PredicateWire {
nodes: Vec::new(),
root_idx: 0,
};
assert_eq!(wire.into_predicate(), Err(PredicateWireError::Empty));
}
#[test]
fn wire_from_out_of_bounds_root_errors_gracefully() {
let wire = PredicateWire {
nodes: vec![PredicateNodeWire::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
}],
root_idx: 5,
};
assert_eq!(
wire.into_predicate(),
Err(PredicateWireError::RootOutOfBounds {
root_idx: 5,
len: 1,
}),
);
}
#[test]
fn wire_from_cycle_in_and_children_errors_gracefully() {
let wire = PredicateWire {
nodes: vec![PredicateNodeWire::And { children: vec![1] }],
root_idx: 0,
};
let err = wire.into_predicate().unwrap_err();
assert!(
matches!(
err,
PredicateWireError::CycleDetected {
parent: 0,
child: 1
}
),
"expected CycleDetected; got {err:?}",
);
}
#[test]
fn wire_from_self_referencing_not_errors_gracefully() {
let wire = PredicateWire {
nodes: vec![PredicateNodeWire::Not { child: 0 }],
root_idx: 0,
};
let err = wire.into_predicate().unwrap_err();
assert!(
matches!(
err,
PredicateWireError::CycleDetected {
parent: 0,
child: 0
}
),
"expected CycleDetected; got {err:?}",
);
}
#[test]
fn wire_simple_leaf_round_trips() {
let pred = Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
};
let wire = pred.to_wire();
assert_eq!(wire.nodes.len(), 1);
assert_eq!(wire.root_idx, 0);
assert_eq!(wire.into_predicate().unwrap(), pred);
}
#[test]
fn wire_rebuilt_predicate_matches_planner_evaluation() {
let original = sample_complex_predicate();
let wire = original.to_wire();
let rebuilt = wire.into_predicate().unwrap();
let tags: Vec<Tag> = vec![
axis_present(TaxonomyAxis::Hardware, "gpu"),
axis_eq(TaxonomyAxis::Software, "runtime.python", "3.11.5"),
];
let meta = meta_with(&[("intent", "ml-training")]);
let cx = ctx(&tags, &meta);
let orig_planned = original.evaluate(&cx);
let orig_unplanned = original.evaluate_unplanned(&cx);
let rebuilt_planned = rebuilt.evaluate(&cx);
let rebuilt_unplanned = rebuilt.evaluate_unplanned(&cx);
assert_eq!(orig_planned, orig_unplanned);
assert_eq!(rebuilt_planned, rebuilt_unplanned);
assert_eq!(orig_planned, rebuilt_planned);
}
#[test]
fn rpc_header_round_trip_preserves_predicate() {
let original = sample_complex_predicate();
let header = predicate_to_rpc_header(&original).expect("encode");
assert_eq!(header.0, RPC_WHERE_HEADER);
let headers = vec![
("trace-id".to_string(), b"abc123".to_vec()),
header,
("idempotency-key".to_string(), b"def456".to_vec()),
];
let decoded = predicate_from_rpc_headers(&headers)
.expect("header present")
.expect("decode succeeds");
assert_eq!(decoded, original);
}
#[test]
fn rpc_header_missing_returns_none() {
let headers = vec![
("trace-id".to_string(), b"abc123".to_vec()),
("idempotency-key".to_string(), b"def456".to_vec()),
];
assert!(predicate_from_rpc_headers(&headers).is_none());
}
#[test]
fn rpc_header_empty_returns_none() {
let headers: Vec<(String, Vec<u8>)> = Vec::new();
assert!(predicate_from_rpc_headers(&headers).is_none());
}
#[test]
fn rpc_header_malformed_json_returns_decode_error() {
let headers = vec![(RPC_WHERE_HEADER.to_string(), b"not-json".to_vec())];
let result = predicate_from_rpc_headers(&headers).unwrap();
assert!(
matches!(result, Err(PredicateRpcDecodeError::Json(_))),
"expected JSON decode error; got {result:?}",
);
}
#[test]
fn rpc_header_oversize_payload_returns_decode_error() {
let oversize = vec![b' '; MAX_PREDICATE_RPC_HEADER_VALUE_LEN + 1];
let headers = vec![(RPC_WHERE_HEADER.to_string(), oversize)];
let result = predicate_from_rpc_headers(&headers).unwrap();
assert!(
matches!(
result,
Err(PredicateRpcDecodeError::Oversize { actual, limit })
if actual == MAX_PREDICATE_RPC_HEADER_VALUE_LEN + 1
&& limit == MAX_PREDICATE_RPC_HEADER_VALUE_LEN
),
"expected Oversize decode error; got {result:?}",
);
}
#[test]
fn rpc_header_cycle_in_payload_returns_decode_error() {
let bad_wire = PredicateWire {
nodes: vec![PredicateNodeWire::Not { child: 0 }],
root_idx: 0,
};
let bad_bytes = serde_json::to_vec(&bad_wire).unwrap();
let headers = vec![(RPC_WHERE_HEADER.to_string(), bad_bytes)];
let result = predicate_from_rpc_headers(&headers).unwrap();
assert!(
matches!(
result,
Err(PredicateRpcDecodeError::Wire(
PredicateWireError::CycleDetected { .. }
))
),
"expected wire cycle error; got {result:?}",
);
}
#[test]
fn rpc_header_first_match_wins_on_duplicate_headers() {
let pred_a = Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
};
let pred_b = Predicate::MetadataEquals {
key: "intent".into(),
value: "ml-training".into(),
};
let header_a = predicate_to_rpc_header(&pred_a).unwrap();
let header_b = predicate_to_rpc_header(&pred_b).unwrap();
let headers = vec![header_a, header_b];
let decoded = predicate_from_rpc_headers(&headers).unwrap().unwrap();
assert_eq!(decoded, pred_a);
}
#[test]
fn rpc_header_oversize_predicate_rejected_at_encode() {
let mut clauses = Vec::new();
for i in 0..200 {
clauses.push(Predicate::MetadataEquals {
key: format!("very-long-metadata-key-{i:04}"),
value: format!("very-long-metadata-value-{i:04}"),
});
}
let huge = Predicate::Or(clauses);
let result = predicate_to_rpc_header(&huge);
assert!(
matches!(result, Err(PredicateRpcEncodeError::TooLarge { actual, limit })
if actual > limit && limit == MAX_PREDICATE_RPC_HEADER_VALUE_LEN),
"expected TooLarge; got {result:?}",
);
}
#[test]
fn rpc_header_typical_predicate_fits_well_under_cap() {
let pred = sample_complex_predicate();
let header = predicate_to_rpc_header(&pred).expect("encode");
assert!(
header.1.len() < 1024,
"encoded predicate is {} bytes, expected < 1024",
header.1.len(),
);
}
#[test]
fn rpc_header_can_be_decoded_via_borrow_or_owned_tuple() {
let pred = Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
};
let header = predicate_to_rpc_header(&pred).unwrap();
let headers = vec![header];
let decoded_owned = predicate_from_rpc_headers(&headers).unwrap().unwrap();
assert_eq!(decoded_owned, pred);
let by_ref: Vec<&(String, Vec<u8>)> = headers.iter().collect();
let decoded_borrow = predicate_from_rpc_headers(&by_ref).unwrap().unwrap();
assert_eq!(decoded_borrow, pred);
}
#[test]
fn rpc_header_json_format_is_human_readable() {
let pred = Predicate::MetadataEquals {
key: "intent".into(),
value: "ml-training".into(),
};
let header = predicate_to_rpc_header(&pred).unwrap();
let json = std::str::from_utf8(&header.1).expect("JSON is UTF-8");
assert!(
json.contains("\"kind\":\"metadata_equals\""),
"unexpected JSON shape: {json}",
);
assert!(json.contains("\"key\":\"intent\""), "missing key: {json}");
assert!(
json.contains("\"value\":\"ml-training\""),
"missing value: {json}",
);
}
#[test]
fn dynamic_cost_saturates_huge_cardinality_to_u32_max() {
struct HugeCardinality;
impl crate::adapter::net::behavior::CardinalityProvider for HugeCardinality {
fn axis_cardinality(&self, _key: &crate::adapter::net::behavior::tag::TagKey) -> usize {
(u32::MAX as usize).wrapping_add(2)
}
fn metadata_value_cardinality(&self, _key: &str) -> usize {
(u32::MAX as usize).wrapping_add(2)
}
}
let clause = Predicate::Equals {
key: TagKey::new(TaxonomyAxis::Hardware, "memory_gb"),
value: "1".into(),
};
let dyn_cost = clause.dynamic_cost(&HugeCardinality);
let static_c = clause.static_cost();
assert!(
dyn_cost < static_c,
"dynamic_cost must reflect saturation (got {dyn_cost}, static={static_c})",
);
let or_cost = clause.dynamic_cost_or(&HugeCardinality);
assert_eq!(
or_cost,
u32::MAX,
"dynamic_cost_or must saturate to u32::MAX on huge cardinality",
);
}
#[test]
fn matches_capability_set_evaluates_against_caps_tags_and_metadata() {
let pred = Predicate::And(vec![
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
},
Predicate::MetadataEquals {
key: "intent".into(),
value: "ml-training".into(),
},
]);
let caps_match = CapabilitySet::new()
.with_hardware(HardwareCapabilities::new().with_gpu(GpuInfo::new(
GpuVendor::Nvidia,
"h100",
80,
)))
.with_metadata("intent", "ml-training");
assert!(pred.matches_capability_set(&caps_match));
let caps_miss_meta = CapabilitySet::new().with_hardware(
HardwareCapabilities::new().with_gpu(GpuInfo::new(GpuVendor::Nvidia, "h100", 80)),
);
assert!(!pred.matches_capability_set(&caps_miss_meta));
let caps_miss_tag = CapabilitySet::new().with_metadata("intent", "ml-training");
assert!(!pred.matches_capability_set(&caps_miss_tag));
assert!(!pred.matches_capability_set(&CapabilitySet::default()));
}
struct TestJob {
id: u64,
tags: Vec<Tag>,
metadata: BTreeMap<String, String>,
}
impl RpcPredicateContext for TestJob {
fn rpc_predicate_tags(&self) -> &[Tag] {
&self.tags
}
fn rpc_predicate_metadata(&self) -> &BTreeMap<String, String> {
&self.metadata
}
}
#[test]
fn filter_by_predicate_returns_all_rows_when_predicate_is_none() {
let jobs = vec![
TestJob {
id: 1,
tags: vec![],
metadata: BTreeMap::new(),
},
TestJob {
id: 2,
tags: vec![axis_present(TaxonomyAxis::Hardware, "gpu")],
metadata: BTreeMap::new(),
},
];
let filtered: Vec<u64> = filter_by_predicate(jobs, None).map(|j| j.id).collect();
assert_eq!(filtered, vec![1, 2]);
}
#[test]
fn filter_by_predicate_keeps_only_matching_rows() {
let pred = Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
};
let jobs = vec![
TestJob {
id: 1,
tags: vec![],
metadata: BTreeMap::new(),
},
TestJob {
id: 2,
tags: vec![axis_present(TaxonomyAxis::Hardware, "gpu")],
metadata: BTreeMap::new(),
},
TestJob {
id: 3,
tags: vec![axis_eq(TaxonomyAxis::Hardware, "gpu.vendor", "nvidia")],
metadata: BTreeMap::new(),
},
TestJob {
id: 4,
tags: vec![
axis_present(TaxonomyAxis::Hardware, "gpu"),
axis_eq(TaxonomyAxis::Hardware, "memory_gb", "64"),
],
metadata: BTreeMap::new(),
},
];
let filtered: Vec<u64> = filter_by_predicate(jobs, Some(&pred))
.map(|j| j.id)
.collect();
assert_eq!(filtered, vec![2, 4]);
}
#[test]
fn filter_by_predicate_combined_axis_and_metadata_clauses() {
let pred = Predicate::And(vec![
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
},
Predicate::MetadataEquals {
key: "intent".into(),
value: "ml-training".into(),
},
]);
let jobs = vec![
TestJob {
id: 1,
tags: vec![axis_present(TaxonomyAxis::Hardware, "gpu")],
metadata: meta_with(&[("intent", "embedding-cache")]),
},
TestJob {
id: 2,
tags: vec![axis_present(TaxonomyAxis::Hardware, "gpu")],
metadata: meta_with(&[("intent", "ml-training")]),
},
TestJob {
id: 3,
tags: vec![],
metadata: meta_with(&[("intent", "ml-training")]),
},
];
let filtered: Vec<u64> = filter_by_predicate(jobs, Some(&pred))
.map(|j| j.id)
.collect();
assert_eq!(filtered, vec![2]);
}
#[test]
fn filter_by_predicate_empty_input_yields_empty_iterator() {
let pred = Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
};
let jobs: Vec<TestJob> = Vec::new();
let filtered: Vec<u64> = filter_by_predicate(jobs, Some(&pred))
.map(|j| j.id)
.collect();
assert!(filtered.is_empty());
}
#[test]
fn end_to_end_predicate_pushdown_flow() {
let pred = Predicate::And(vec![
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
},
Predicate::NumericAtLeast {
key: TagKey::new(TaxonomyAxis::Hardware, "memory_gb"),
threshold: 32.0,
},
]);
let encoded = predicate_to_rpc_header(&pred).expect("encode");
let request_headers = vec![
("trace-id".to_string(), b"abc123".to_vec()),
encoded,
("idempotency-key".to_string(), b"def456".to_vec()),
];
let decoded_pred = predicate_from_rpc_headers(&request_headers)
.expect("header present")
.expect("decode");
let jobs = vec![
TestJob {
id: 1, tags: vec![axis_eq(TaxonomyAxis::Hardware, "memory_gb", "64")],
metadata: BTreeMap::new(),
},
TestJob {
id: 2, tags: vec![
axis_present(TaxonomyAxis::Hardware, "gpu"),
axis_eq(TaxonomyAxis::Hardware, "memory_gb", "32"),
],
metadata: BTreeMap::new(),
},
TestJob {
id: 3, tags: vec![
axis_present(TaxonomyAxis::Hardware, "gpu"),
axis_eq(TaxonomyAxis::Hardware, "memory_gb", "16"),
],
metadata: BTreeMap::new(),
},
TestJob {
id: 4, tags: vec![
axis_present(TaxonomyAxis::Hardware, "gpu"),
axis_eq(TaxonomyAxis::Hardware, "memory_gb", "64"),
],
metadata: BTreeMap::new(),
},
];
let matched: Vec<u64> = filter_by_predicate(jobs, Some(&decoded_pred))
.map(|j| j.id)
.collect();
assert_eq!(matched, vec![2, 4]);
}
#[test]
fn to_wire_handles_deep_nesting_without_stack_overflow() {
let leaf = Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "gpu"),
};
let mut p = leaf;
for _ in 0..10_000 {
p = Predicate::Not(Box::new(p));
}
let wire = p.to_wire();
assert_eq!(wire.nodes.len(), 10_001);
let root = &wire.nodes[wire.root_idx as usize];
assert!(matches!(root, PredicateNodeWire::Not { .. }));
}
#[test]
fn to_wire_preserves_left_to_right_child_ordering() {
let p = Predicate::And(vec![
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "a"),
},
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "b"),
},
Predicate::Exists {
key: TagKey::new(TaxonomyAxis::Hardware, "c"),
},
]);
let wire = p.to_wire();
assert_eq!(wire.nodes.len(), 4);
let PredicateNodeWire::And { children } = &wire.nodes[wire.root_idx as usize] else {
panic!("root should be And");
};
assert_eq!(children.as_slice(), &[0u32, 1, 2]);
for (i, key) in ["a", "b", "c"].iter().enumerate() {
let PredicateNodeWire::Exists { key: k } = &wire.nodes[i] else {
panic!("expected Exists at index {i}");
};
assert_eq!(k.key.as_str(), *key);
}
}
}