use crate::adapter::net::behavior::capability::CapabilitySet;
use crate::adapter::net::behavior::placement::{
ColocationPolicy, IntentMatchPolicy, IntentRegistry, PlacementMetadataKeys,
};
use crate::adapter::net::behavior::predicate::EvalContext;
use crate::adapter::net::behavior::tag::Tag;
use super::config::GreedyConfig;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AdmissionVerdict {
Admit,
RejectScope,
RejectIntent,
RejectColocation,
}
#[derive(Debug, Clone, Copy)]
pub struct AdmissionInputs<'a> {
pub chain_caps: &'a CapabilitySet,
pub local_caps: &'a CapabilitySet,
pub config: &'a GreedyConfig,
pub intent_registry: &'a IntentRegistry,
pub metadata_keys: &'a PlacementMetadataKeys,
pub colocation_target_held: Option<bool>,
}
pub fn should_admit(inputs: &AdmissionInputs<'_>) -> AdmissionVerdict {
if !passes_scope_gate(inputs.chain_caps, &inputs.config.scopes) {
return AdmissionVerdict::RejectScope;
}
if !passes_intent_gate(
inputs.chain_caps,
inputs.local_caps,
inputs.config.intent_match.clone(),
inputs.intent_registry,
inputs.metadata_keys,
) {
return AdmissionVerdict::RejectIntent;
}
if !passes_colocation_gate(
inputs.chain_caps,
inputs.config.colocation_policy,
inputs.metadata_keys,
inputs.colocation_target_held,
) {
return AdmissionVerdict::RejectColocation;
}
AdmissionVerdict::Admit
}
fn passes_scope_gate(chain_caps: &CapabilitySet, configured_scopes: &[super::ScopeLabel]) -> bool {
if configured_scopes.is_empty() {
return true;
}
chain_caps.tags.iter().any(|tag| match tag {
Tag::Reserved { prefix, body } if prefix == "scope:" => {
configured_scopes.iter().any(|s| s.as_str() == body)
}
_ => false,
})
}
fn passes_intent_gate(
chain_caps: &CapabilitySet,
local_caps: &CapabilitySet,
policy: IntentMatchPolicy,
registry: &IntentRegistry,
metadata_keys: &PlacementMetadataKeys,
) -> bool {
let local_tags: Vec<Tag> = local_caps.tags.iter().cloned().collect();
let local_ctx = EvalContext::new(&local_tags, &local_caps.metadata);
match policy {
IntentMatchPolicy::Disabled => true,
IntentMatchPolicy::Strict => {
let Some(intent) = chain_caps.metadata.get(&metadata_keys.intent) else {
return true;
};
let Some(reqs) = registry.lookup(intent) else {
return true; };
reqs.iter().all(|req| req.evaluate(&local_ctx))
}
IntentMatchPolicy::AnyOfLocalCapabilities => {
if registry.is_empty() {
return true;
}
registry
.iter()
.any(|(_, reqs)| reqs.iter().all(|req| req.evaluate(&local_ctx)))
}
}
}
fn passes_colocation_gate(
chain_caps: &CapabilitySet,
policy: ColocationPolicy,
metadata_keys: &PlacementMetadataKeys,
target_held: Option<bool>,
) -> bool {
if matches!(policy, ColocationPolicy::Ignore) {
return true;
}
let has_strict = chain_caps
.metadata
.contains_key(&metadata_keys.colocate_with_strict);
let has_soft = chain_caps
.metadata
.contains_key(&metadata_keys.colocate_with);
if has_strict && !target_held.unwrap_or(false) {
return false;
}
if has_soft
&& matches!(policy, ColocationPolicy::StrictRequired)
&& !target_held.unwrap_or(false)
{
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::adapter::net::behavior::capability::CapabilitySet;
use crate::adapter::net::behavior::tag::{Tag, TaxonomyAxis};
fn caps_with_scope(scope: &str) -> CapabilitySet {
let mut caps = CapabilitySet::default();
caps.tags.insert(Tag::Reserved {
prefix: "scope:".to_string(),
body: scope.to_string(),
});
caps
}
fn caps_with_intent(intent: &str) -> CapabilitySet {
let mut caps = CapabilitySet::default();
caps.metadata
.insert("intent".to_string(), intent.to_string());
caps
}
fn caps_with_gpu_24gb() -> CapabilitySet {
let mut caps = CapabilitySet::default();
caps.tags.insert(Tag::AxisPresent {
axis: TaxonomyAxis::Hardware,
key: "gpu".to_string(),
});
caps.tags.insert(Tag::AxisValue {
axis: TaxonomyAxis::Hardware,
key: "gpu.vram_gb".to_string(),
value: "24".to_string(),
separator: crate::adapter::net::behavior::tag::AxisSeparator::Eq,
});
caps
}
fn caps_with_cpu_only() -> CapabilitySet {
let mut caps = CapabilitySet::default();
caps.tags.insert(Tag::AxisValue {
axis: TaxonomyAxis::Hardware,
key: "cpu_cores".to_string(),
value: "16".to_string(),
separator: crate::adapter::net::behavior::tag::AxisSeparator::Eq,
});
caps
}
fn metadata_keys() -> PlacementMetadataKeys {
PlacementMetadataKeys::default()
}
#[test]
fn empty_scope_set_admits_regardless() {
let chain = caps_with_scope("industrial");
let local = CapabilitySet::default();
let registry = IntentRegistry::new();
let keys = metadata_keys();
let cfg = GreedyConfig::default().with_intent_match(IntentMatchPolicy::Disabled);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::Admit);
}
#[test]
fn scope_match_admits() {
let chain = caps_with_scope("industrial");
let local = CapabilitySet::default();
let registry = IntentRegistry::new();
let keys = metadata_keys();
let cfg = GreedyConfig::default()
.with_scopes(vec![super::super::ScopeLabel::new("industrial")])
.with_intent_match(IntentMatchPolicy::Disabled);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::Admit);
}
#[test]
fn scope_miss_rejects() {
let chain = caps_with_scope("webcam-streams");
let local = CapabilitySet::default();
let registry = IntentRegistry::new();
let keys = metadata_keys();
let cfg = GreedyConfig::default()
.with_scopes(vec![super::super::ScopeLabel::new("industrial")])
.with_intent_match(IntentMatchPolicy::Disabled);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::RejectScope);
}
#[test]
fn chain_with_no_scope_tag_rejects_under_non_empty_scope_set() {
let chain = CapabilitySet::default();
let local = CapabilitySet::default();
let registry = IntentRegistry::new();
let keys = metadata_keys();
let cfg = GreedyConfig::default()
.with_scopes(vec![super::super::ScopeLabel::new("industrial")])
.with_intent_match(IntentMatchPolicy::Disabled);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::RejectScope);
}
#[test]
fn intent_disabled_admits_regardless_of_local_caps() {
let chain = caps_with_intent("ml-training");
let local = caps_with_cpu_only();
let registry = IntentRegistry::defaults();
let keys = metadata_keys();
let cfg = GreedyConfig::default().with_intent_match(IntentMatchPolicy::Disabled);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::Admit);
}
#[test]
fn intent_strict_admits_when_local_satisfies() {
let chain = caps_with_intent("ml-training");
let local = caps_with_gpu_24gb();
let registry = IntentRegistry::defaults();
let keys = metadata_keys();
let cfg = GreedyConfig::default().with_intent_match(IntentMatchPolicy::Strict);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::Admit);
}
#[test]
fn intent_strict_rejects_when_local_lacks_required() {
let chain = caps_with_intent("ml-training");
let local = caps_with_cpu_only();
let registry = IntentRegistry::defaults();
let keys = metadata_keys();
let cfg = GreedyConfig::default().with_intent_match(IntentMatchPolicy::Strict);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::RejectIntent);
}
#[test]
fn intent_strict_with_no_declared_intent_admits() {
let chain = CapabilitySet::default();
let local = caps_with_cpu_only();
let registry = IntentRegistry::defaults();
let keys = metadata_keys();
let cfg = GreedyConfig::default().with_intent_match(IntentMatchPolicy::Strict);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::Admit);
}
#[test]
fn intent_strict_with_unknown_intent_admits() {
let chain = caps_with_intent("custom-not-registered");
let local = caps_with_cpu_only();
let registry = IntentRegistry::defaults();
let keys = metadata_keys();
let cfg = GreedyConfig::default().with_intent_match(IntentMatchPolicy::Strict);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::Admit);
}
#[test]
fn intent_any_of_admits_when_local_satisfies_some_registered_intent() {
let chain = caps_with_intent("ml-training");
let local = caps_with_cpu_only();
let registry = IntentRegistry::defaults();
let keys = metadata_keys();
let cfg =
GreedyConfig::default().with_intent_match(IntentMatchPolicy::AnyOfLocalCapabilities);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::Admit);
}
#[test]
fn intent_any_of_rejects_when_local_satisfies_no_registered_intent() {
let chain = caps_with_intent("ml-training");
let local = CapabilitySet::default(); let registry = IntentRegistry::defaults();
let keys = metadata_keys();
let cfg =
GreedyConfig::default().with_intent_match(IntentMatchPolicy::AnyOfLocalCapabilities);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::RejectIntent);
}
#[test]
fn intent_any_of_admits_when_registry_empty() {
let chain = caps_with_intent("ml-training");
let local = CapabilitySet::default();
let registry = IntentRegistry::new(); let keys = metadata_keys();
let cfg =
GreedyConfig::default().with_intent_match(IntentMatchPolicy::AnyOfLocalCapabilities);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::Admit);
}
fn caps_with_metadata(pairs: &[(&str, &str)]) -> CapabilitySet {
let mut caps = CapabilitySet::default();
for (k, v) in pairs {
caps.metadata.insert((*k).to_string(), (*v).to_string());
}
caps
}
fn ignored_intent_cfg() -> GreedyConfig {
GreedyConfig::default().with_intent_match(IntentMatchPolicy::Disabled)
}
#[test]
fn colocation_ignore_admits_even_with_unheld_strict_target() {
let chain = caps_with_metadata(&[("colocate-with-strict", "deadbeef")]);
let local = CapabilitySet::default();
let registry = IntentRegistry::new();
let keys = metadata_keys();
let cfg = ignored_intent_cfg().with_colocation_policy(ColocationPolicy::Ignore);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: Some(false),
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::Admit);
}
#[test]
fn colocation_strict_hint_blocks_under_soft_preference_when_target_absent() {
let chain = caps_with_metadata(&[("colocate-with-strict", "deadbeef")]);
let local = CapabilitySet::default();
let registry = IntentRegistry::new();
let keys = metadata_keys();
let cfg = ignored_intent_cfg().with_colocation_policy(ColocationPolicy::SoftPreference);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: Some(false),
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::RejectColocation);
}
#[test]
fn colocation_strict_hint_admits_when_target_held() {
let chain = caps_with_metadata(&[("colocate-with-strict", "deadbeef")]);
let local = CapabilitySet::default();
let registry = IntentRegistry::new();
let keys = metadata_keys();
let cfg = ignored_intent_cfg().with_colocation_policy(ColocationPolicy::SoftPreference);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: Some(true),
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::Admit);
}
#[test]
fn colocation_soft_hint_does_not_block_under_soft_preference() {
let chain = caps_with_metadata(&[("colocate-with", "deadbeef")]);
let local = CapabilitySet::default();
let registry = IntentRegistry::new();
let keys = metadata_keys();
let cfg = ignored_intent_cfg().with_colocation_policy(ColocationPolicy::SoftPreference);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: Some(false),
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::Admit);
}
#[test]
fn colocation_soft_hint_blocks_under_strict_required_when_target_absent() {
let chain = caps_with_metadata(&[("colocate-with", "deadbeef")]);
let local = CapabilitySet::default();
let registry = IntentRegistry::new();
let keys = metadata_keys();
let cfg = ignored_intent_cfg().with_colocation_policy(ColocationPolicy::StrictRequired);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: Some(false),
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::RejectColocation);
}
#[test]
fn colocation_none_resolution_treated_as_target_not_held() {
let chain = caps_with_metadata(&[("colocate-with-strict", "deadbeef")]);
let local = CapabilitySet::default();
let registry = IntentRegistry::new();
let keys = metadata_keys();
let cfg = ignored_intent_cfg().with_colocation_policy(ColocationPolicy::SoftPreference);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::RejectColocation);
}
#[test]
fn first_failing_axis_returns_specific_reject_variant() {
let mut chain = caps_with_scope("webcam-streams");
chain
.metadata
.insert("intent".to_string(), "ml-training".to_string());
let local = CapabilitySet::default();
let registry = IntentRegistry::defaults();
let keys = metadata_keys();
let cfg = GreedyConfig::default()
.with_scopes(vec![super::super::ScopeLabel::new("industrial")])
.with_intent_match(IntentMatchPolicy::Strict);
let inputs = AdmissionInputs {
chain_caps: &chain,
local_caps: &local,
config: &cfg,
intent_registry: ®istry,
metadata_keys: &keys,
colocation_target_held: None,
};
assert_eq!(should_admit(&inputs), AdmissionVerdict::RejectScope);
}
}