use crate::types::{RegionId, TaskId, Time};
use std::collections::{HashMap, HashSet};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CapabilityKind {
Spawn,
Time,
Trace,
Region,
Obligation,
Full,
}
impl fmt::Display for CapabilityKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Spawn => write!(f, "spawn"),
Self::Time => write!(f, "time"),
Self::Trace => write!(f, "trace"),
Self::Region => write!(f, "region"),
Self::Obligation => write!(f, "obligation"),
Self::Full => write!(f, "full"),
}
}
}
#[derive(Debug, Clone)]
pub struct Effect {
pub task: TaskId,
pub required: CapabilityKind,
pub description: String,
pub time: Time,
}
#[derive(Debug, Clone, Default)]
pub struct CapabilitySet {
capabilities: HashSet<CapabilityKind>,
}
impl CapabilitySet {
#[must_use]
pub fn empty() -> Self {
Self::default()
}
#[must_use]
pub fn full() -> Self {
let mut caps = Self::empty();
caps.grant(CapabilityKind::Full);
caps.grant(CapabilityKind::Spawn);
caps.grant(CapabilityKind::Time);
caps.grant(CapabilityKind::Trace);
caps.grant(CapabilityKind::Region);
caps.grant(CapabilityKind::Obligation);
caps
}
pub fn grant(&mut self, cap: CapabilityKind) {
self.capabilities.insert(cap);
}
pub fn revoke(&mut self, cap: CapabilityKind) {
self.capabilities.remove(&cap);
if cap != CapabilityKind::Full {
self.capabilities.remove(&CapabilityKind::Full);
}
}
#[must_use]
pub fn has(&self, cap: CapabilityKind) -> bool {
self.capabilities.contains(&CapabilityKind::Full) || self.capabilities.contains(&cap)
}
pub fn iter(&self) -> impl Iterator<Item = &CapabilityKind> {
self.capabilities.iter()
}
}
#[derive(Debug, Clone)]
pub struct AmbientAuthorityViolation {
pub task: TaskId,
pub required_capability: CapabilityKind,
pub effect_description: String,
pub granted_capabilities: Vec<CapabilityKind>,
pub time: Time,
}
impl fmt::Display for AmbientAuthorityViolation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Task {:?} performed '{}' at {:?} without '{}' capability. \
Granted: {:?}",
self.task,
self.effect_description,
self.time,
self.required_capability,
self.granted_capabilities
)
}
}
impl std::error::Error for AmbientAuthorityViolation {}
#[derive(Debug, Default)]
pub struct AmbientAuthorityOracle {
capabilities: HashMap<TaskId, CapabilitySet>,
effects: Vec<Effect>,
parent_task: HashMap<TaskId, TaskId>,
task_region: HashMap<TaskId, RegionId>,
root_tasks: HashSet<TaskId>,
}
impl AmbientAuthorityOracle {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn on_task_created(
&mut self,
task: TaskId,
region: RegionId,
parent: Option<TaskId>,
_time: Time,
) {
self.task_region.insert(task, region);
if let Some(parent_id) = parent {
self.parent_task.insert(task, parent_id);
let parent_caps = self
.capabilities
.get(&parent_id)
.cloned()
.unwrap_or_default();
self.capabilities.insert(task, parent_caps);
} else {
self.root_tasks.insert(task);
self.capabilities.insert(task, CapabilitySet::full());
}
}
pub fn on_capability_granted(&mut self, task: TaskId, cap: CapabilityKind, _time: Time) {
self.capabilities.entry(task).or_default().grant(cap);
}
pub fn on_capability_revoked(&mut self, task: TaskId, cap: CapabilityKind, _time: Time) {
if let Some(caps) = self.capabilities.get_mut(&task) {
caps.revoke(cap);
}
}
pub fn on_spawn_effect(&mut self, task: TaskId, _child: TaskId, time: Time) {
self.effects.push(Effect {
task,
required: CapabilityKind::Spawn,
description: "spawn child task".to_string(),
time,
});
}
pub fn on_time_access(&mut self, task: TaskId, time: Time) {
self.effects.push(Effect {
task,
required: CapabilityKind::Time,
description: "access time".to_string(),
time,
});
}
pub fn on_trace(&mut self, task: TaskId, message: &str, time: Time) {
self.effects.push(Effect {
task,
required: CapabilityKind::Trace,
description: format!("trace: {message}"),
time,
});
}
pub fn on_region_create(&mut self, task: TaskId, _region: RegionId, time: Time) {
self.effects.push(Effect {
task,
required: CapabilityKind::Region,
description: "create region".to_string(),
time,
});
}
pub fn on_obligation_create(
&mut self,
task: TaskId,
_obligation: crate::types::ObligationId,
time: Time,
) {
self.effects.push(Effect {
task,
required: CapabilityKind::Obligation,
description: "create obligation".to_string(),
time,
});
}
pub fn on_effect(
&mut self,
task: TaskId,
required: CapabilityKind,
description: &str,
time: Time,
) {
self.effects.push(Effect {
task,
required,
description: description.to_string(),
time,
});
}
#[must_use]
pub fn capabilities_for(&self, task: TaskId) -> Option<&CapabilitySet> {
self.capabilities.get(&task)
}
#[must_use]
pub fn task_has_capability(&self, task: TaskId, cap: CapabilityKind) -> bool {
self.capabilities
.get(&task)
.is_some_and(|caps| caps.has(cap))
}
pub fn check(&self) -> Result<(), AmbientAuthorityViolation> {
for effect in &self.effects {
let caps = self.capabilities.get(&effect.task);
let has_cap = caps.is_some_and(|c| c.has(effect.required));
if !has_cap {
let granted: Vec<CapabilityKind> = caps
.map(|c| c.iter().copied().collect())
.unwrap_or_default();
return Err(AmbientAuthorityViolation {
task: effect.task,
required_capability: effect.required,
effect_description: effect.description.clone(),
granted_capabilities: granted,
time: effect.time,
});
}
}
Ok(())
}
pub fn reset(&mut self) {
self.capabilities.clear();
self.effects.clear();
self.parent_task.clear();
self.task_region.clear();
self.root_tasks.clear();
}
#[must_use]
pub fn effect_count(&self) -> usize {
self.effects.len()
}
#[must_use]
pub fn task_count(&self) -> usize {
self.capabilities.len()
}
#[must_use]
pub fn root_task_count(&self) -> usize {
self.root_tasks.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::ArenaIndex;
fn task(n: u32) -> TaskId {
TaskId::from_arena(ArenaIndex::new(n, 0))
}
fn region(n: u32) -> RegionId {
RegionId::from_arena(ArenaIndex::new(n, 0))
}
fn t(nanos: u64) -> Time {
Time::from_nanos(nanos)
}
fn init_test(name: &str) {
crate::test_utils::init_test_logging();
crate::test_phase!(name);
}
#[test]
fn empty_oracle_passes() {
init_test("empty_oracle_passes");
let oracle = AmbientAuthorityOracle::new();
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("empty_oracle_passes");
}
#[test]
fn root_task_has_full_capabilities() {
init_test("root_task_has_full_capabilities");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
let has_spawn = oracle.task_has_capability(task(1), CapabilityKind::Spawn);
crate::assert_with_log!(has_spawn, "has spawn", true, has_spawn);
let has_time = oracle.task_has_capability(task(1), CapabilityKind::Time);
crate::assert_with_log!(has_time, "has time", true, has_time);
let has_trace = oracle.task_has_capability(task(1), CapabilityKind::Trace);
crate::assert_with_log!(has_trace, "has trace", true, has_trace);
let has_region = oracle.task_has_capability(task(1), CapabilityKind::Region);
crate::assert_with_log!(has_region, "has region", true, has_region);
let has_obligation = oracle.task_has_capability(task(1), CapabilityKind::Obligation);
crate::assert_with_log!(has_obligation, "has obligation", true, has_obligation);
crate::test_complete!("root_task_has_full_capabilities");
}
#[test]
fn child_inherits_parent_capabilities() {
init_test("child_inherits_parent_capabilities");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
oracle.on_task_created(task(2), region(0), Some(task(1)), t(10));
let has_spawn = oracle.task_has_capability(task(2), CapabilityKind::Spawn);
crate::assert_with_log!(has_spawn, "child has spawn", true, has_spawn);
let has_time = oracle.task_has_capability(task(2), CapabilityKind::Time);
crate::assert_with_log!(has_time, "child has time", true, has_time);
crate::test_complete!("child_inherits_parent_capabilities");
}
#[test]
fn child_with_missing_parent_has_no_capabilities() {
init_test("child_with_missing_parent_has_no_capabilities");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(2), region(0), Some(task(99)), t(10));
let has_spawn = oracle.task_has_capability(task(2), CapabilityKind::Spawn);
crate::assert_with_log!(!has_spawn, "child spawn denied", false, has_spawn);
let has_time = oracle.task_has_capability(task(2), CapabilityKind::Time);
crate::assert_with_log!(!has_time, "child time denied", false, has_time);
oracle.on_spawn_effect(task(2), task(3), t(20));
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.task == task(2),
"violation task",
task(2),
violation.task
);
let empty = violation.granted_capabilities.is_empty();
crate::assert_with_log!(empty, "capabilities empty", true, empty);
crate::test_complete!("child_with_missing_parent_has_no_capabilities");
}
#[test]
fn authorized_spawn_passes() {
init_test("authorized_spawn_passes");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
oracle.on_spawn_effect(task(1), task(2), t(10));
oracle.on_task_created(task(2), region(0), Some(task(1)), t(10));
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("authorized_spawn_passes");
}
#[test]
fn unauthorized_spawn_fails() {
init_test("unauthorized_spawn_fails");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
oracle.on_capability_revoked(task(1), CapabilityKind::Spawn, t(5));
oracle.on_capability_revoked(task(1), CapabilityKind::Full, t(5));
oracle.on_spawn_effect(task(1), task(2), t(10));
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.task == task(1),
"violation task",
task(1),
violation.task
);
crate::assert_with_log!(
violation.required_capability == CapabilityKind::Spawn,
"required capability",
CapabilityKind::Spawn,
violation.required_capability
);
crate::test_complete!("unauthorized_spawn_fails");
}
#[test]
fn unauthorized_time_access_fails() {
init_test("unauthorized_time_access_fails");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
oracle.on_capability_revoked(task(1), CapabilityKind::Time, t(5));
oracle.on_capability_revoked(task(1), CapabilityKind::Full, t(5));
oracle.on_time_access(task(1), t(10));
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.required_capability == CapabilityKind::Time,
"required capability",
CapabilityKind::Time,
violation.required_capability
);
crate::test_complete!("unauthorized_time_access_fails");
}
#[test]
fn regranting_capability_passes() {
init_test("regranting_capability_passes");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
oracle.on_capability_revoked(task(1), CapabilityKind::Spawn, t(5));
oracle.on_capability_revoked(task(1), CapabilityKind::Full, t(5));
oracle.on_capability_granted(task(1), CapabilityKind::Spawn, t(8));
oracle.on_spawn_effect(task(1), task(2), t(10));
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("regranting_capability_passes");
}
#[test]
fn unknown_task_fails() {
init_test("unknown_task_fails");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_spawn_effect(task(1), task(2), t(10));
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.task == task(1),
"violation task",
task(1),
violation.task
);
let empty = violation.granted_capabilities.is_empty();
crate::assert_with_log!(empty, "capabilities empty", true, empty);
crate::test_complete!("unknown_task_fails");
}
#[test]
fn multiple_effects_all_authorized() {
init_test("multiple_effects_all_authorized");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
oracle.on_spawn_effect(task(1), task(2), t(10));
oracle.on_time_access(task(1), t(20));
oracle.on_trace(task(1), "hello", t(30));
oracle.on_region_create(task(1), region(1), t(40));
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
let count = oracle.effect_count();
crate::assert_with_log!(count == 4, "effect count", 4, count);
crate::test_complete!("multiple_effects_all_authorized");
}
#[test]
fn child_with_narrowed_capabilities() {
init_test("child_with_narrowed_capabilities");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
oracle.on_task_created(task(2), region(0), Some(task(1)), t(10));
oracle.on_capability_revoked(task(2), CapabilityKind::Spawn, t(15));
oracle.on_capability_revoked(task(2), CapabilityKind::Full, t(15));
oracle.on_spawn_effect(task(2), task(3), t(20));
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.task == task(2),
"violation task",
task(2),
violation.task
);
crate::test_complete!("child_with_narrowed_capabilities");
}
#[test]
fn reset_clears_state() {
init_test("reset_clears_state");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
oracle.on_capability_revoked(task(1), CapabilityKind::Spawn, t(5));
oracle.on_capability_revoked(task(1), CapabilityKind::Full, t(5));
oracle.on_spawn_effect(task(1), task(2), t(10));
let err = oracle.check().is_err();
crate::assert_with_log!(err, "oracle err", true, err);
oracle.reset();
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
let effect_count = oracle.effect_count();
crate::assert_with_log!(effect_count == 0, "effect count", 0, effect_count);
let task_count = oracle.task_count();
crate::assert_with_log!(task_count == 0, "task count", 0, task_count);
crate::test_complete!("reset_clears_state");
}
#[test]
fn capability_set_full_implies_all() {
init_test("capability_set_full_implies_all");
let full = CapabilitySet::full();
let has_spawn = full.has(CapabilityKind::Spawn);
crate::assert_with_log!(has_spawn, "has spawn", true, has_spawn);
let has_time = full.has(CapabilityKind::Time);
crate::assert_with_log!(has_time, "has time", true, has_time);
let has_trace = full.has(CapabilityKind::Trace);
crate::assert_with_log!(has_trace, "has trace", true, has_trace);
let has_region = full.has(CapabilityKind::Region);
crate::assert_with_log!(has_region, "has region", true, has_region);
let has_obligation = full.has(CapabilityKind::Obligation);
crate::assert_with_log!(has_obligation, "has obligation", true, has_obligation);
let has_full = full.has(CapabilityKind::Full);
crate::assert_with_log!(has_full, "has full", true, has_full);
crate::test_complete!("capability_set_full_implies_all");
}
#[test]
fn capability_set_individual_grants() {
init_test("capability_set_individual_grants");
let mut caps = CapabilitySet::empty();
let has_spawn = caps.has(CapabilityKind::Spawn);
crate::assert_with_log!(!has_spawn, "spawn missing", false, has_spawn);
caps.grant(CapabilityKind::Spawn);
let has_spawn = caps.has(CapabilityKind::Spawn);
crate::assert_with_log!(has_spawn, "spawn granted", true, has_spawn);
let has_time = caps.has(CapabilityKind::Time);
crate::assert_with_log!(!has_time, "time missing", false, has_time);
caps.grant(CapabilityKind::Time);
let has_time = caps.has(CapabilityKind::Time);
crate::assert_with_log!(has_time, "time granted", true, has_time);
caps.revoke(CapabilityKind::Spawn);
let has_spawn = caps.has(CapabilityKind::Spawn);
crate::assert_with_log!(!has_spawn, "spawn revoked", false, has_spawn);
let has_time = caps.has(CapabilityKind::Time);
crate::assert_with_log!(has_time, "time still", true, has_time);
crate::test_complete!("capability_set_individual_grants");
}
#[test]
fn revoke_clears_full_meta_capability() {
init_test("revoke_clears_full_meta_capability");
let mut caps = CapabilitySet::full();
let has_spawn = caps.has(CapabilityKind::Spawn);
crate::assert_with_log!(has_spawn, "spawn via full", true, has_spawn);
caps.revoke(CapabilityKind::Spawn);
let has_spawn = caps.has(CapabilityKind::Spawn);
crate::assert_with_log!(!has_spawn, "spawn revoked", false, has_spawn);
let has_full = caps.has(CapabilityKind::Full);
crate::assert_with_log!(!has_full, "full cleared", false, has_full);
let has_time = caps.has(CapabilityKind::Time);
crate::assert_with_log!(has_time, "time remains", true, has_time);
let has_trace = caps.has(CapabilityKind::Trace);
crate::assert_with_log!(has_trace, "trace remains", true, has_trace);
let has_region = caps.has(CapabilityKind::Region);
crate::assert_with_log!(has_region, "region remains", true, has_region);
let has_obligation = caps.has(CapabilityKind::Obligation);
crate::assert_with_log!(has_obligation, "obligation remains", true, has_obligation);
crate::test_complete!("revoke_clears_full_meta_capability");
}
#[test]
fn revoke_full_directly_leaves_individual_caps() {
init_test("revoke_full_directly_leaves_individual_caps");
let mut caps = CapabilitySet::full();
caps.revoke(CapabilityKind::Full);
let has_full = caps.has(CapabilityKind::Full);
crate::assert_with_log!(!has_full, "full revoked", false, has_full);
let has_spawn = caps.has(CapabilityKind::Spawn);
crate::assert_with_log!(has_spawn, "spawn remains", true, has_spawn);
let has_time = caps.has(CapabilityKind::Time);
crate::assert_with_log!(has_time, "time remains", true, has_time);
crate::test_complete!("revoke_full_directly_leaves_individual_caps");
}
#[test]
fn revoke_from_full_then_oracle_detects_violation() {
init_test("revoke_from_full_then_oracle_detects_violation");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
oracle.on_capability_revoked(task(1), CapabilityKind::Spawn, t(5));
oracle.on_spawn_effect(task(1), task(2), t(10));
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "violation detected", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.required_capability == CapabilityKind::Spawn,
"required spawn",
CapabilityKind::Spawn,
violation.required_capability
);
crate::test_complete!("revoke_from_full_then_oracle_detects_violation");
}
#[test]
fn violation_display() {
init_test("violation_display");
let violation = AmbientAuthorityViolation {
task: task(1),
required_capability: CapabilityKind::Spawn,
effect_description: "spawn child task".to_string(),
granted_capabilities: vec![CapabilityKind::Time, CapabilityKind::Trace],
time: t(100),
};
let s = violation.to_string();
let has_spawn = s.contains("spawn");
crate::assert_with_log!(has_spawn, "contains spawn", true, has_spawn);
let has_time = s.contains("Time");
crate::assert_with_log!(has_time, "contains Time", true, has_time);
crate::test_complete!("violation_display");
}
#[test]
fn generic_effect_tracking() {
init_test("generic_effect_tracking");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
oracle.on_effect(
task(1),
CapabilityKind::Time,
"custom time operation",
t(10),
);
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
let count = oracle.effect_count();
crate::assert_with_log!(count == 1, "effect count", 1, count);
crate::test_complete!("generic_effect_tracking");
}
#[test]
fn multiple_tasks_independent() {
init_test("multiple_tasks_independent");
let mut oracle = AmbientAuthorityOracle::new();
oracle.on_task_created(task(1), region(0), None, t(0));
oracle.on_spawn_effect(task(1), task(3), t(10));
oracle.on_task_created(task(2), region(0), None, t(5));
oracle.on_capability_revoked(task(2), CapabilityKind::Spawn, t(6));
oracle.on_capability_revoked(task(2), CapabilityKind::Full, t(6));
oracle.on_time_access(task(2), t(15));
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("multiple_tasks_independent");
}
}