lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit f9a96db8b74ca4be5d7b5c429991b21823944793
parent 20c86010b7a6179f0c93401acf2335b38abffe8b
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 00:54:01 -0700

authority: define actor role capabilities

- represent actor roles as a deterministic set
- add actor source, selector, and resolution request types
- enforce strict role satisfaction semantics for Any and explicit roles
- validate with cargo fmt, check, and tests for radroots_authority

Diffstat:
Mcrates/authority/src/actor.rs | 128++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mcrates/authority/src/lib.rs | 5++++-
Mcrates/events/src/contract.rs | 2+-
3 files changed, 132 insertions(+), 3 deletions(-)

diff --git a/crates/authority/src/actor.rs b/crates/authority/src/actor.rs @@ -1,21 +1,147 @@ #![forbid(unsafe_code)] use crate::RadrootsAuthorityError; +use radroots_events::contract::RadrootsActorRole; use radroots_events::ids::RadrootsPublicKey; +#[cfg(not(feature = "std"))] +use alloc::collections::BTreeSet; +#[cfg(feature = "std")] +use std::collections::BTreeSet; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RadrootsActorSource { + Direct, + Account, + GroupMembership, + RelayAuth, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RadrootsActorSelector { + Pubkey(RadrootsPublicKey), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsActorResolutionRequest { + pub selector: RadrootsActorSelector, + pub required_role: RadrootsActorRole, +} + +impl RadrootsActorResolutionRequest { + pub fn new(selector: RadrootsActorSelector, required_role: RadrootsActorRole) -> Self { + Self { + selector, + required_role, + } + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsActorContext { pub pubkey: RadrootsPublicKey, + pub roles: BTreeSet<RadrootsActorRole>, + pub source: RadrootsActorSource, } impl RadrootsActorContext { pub fn new(pubkey: impl AsRef<str>) -> Result<Self, RadrootsAuthorityError> { + Self::with_roles(pubkey, []) + } + + pub fn with_roles<I>(pubkey: impl AsRef<str>, roles: I) -> Result<Self, RadrootsAuthorityError> + where + I: IntoIterator<Item = RadrootsActorRole>, + { + Self::with_source_and_roles(pubkey, RadrootsActorSource::Direct, roles) + } + + pub fn with_source_and_roles<I>( + pubkey: impl AsRef<str>, + source: RadrootsActorSource, + roles: I, + ) -> Result<Self, RadrootsAuthorityError> + where + I: IntoIterator<Item = RadrootsActorRole>, + { let pubkey = RadrootsPublicKey::parse(pubkey.as_ref()) .map_err(|_| RadrootsAuthorityError::InvalidActorPubkey)?; - Ok(Self { pubkey }) + Ok(Self { + pubkey, + roles: roles.into_iter().collect(), + source, + }) } pub fn pubkey(&self) -> &RadrootsPublicKey { &self.pubkey } + + pub fn roles(&self) -> &BTreeSet<RadrootsActorRole> { + &self.roles + } + + pub fn source(&self) -> RadrootsActorSource { + self.source + } + + pub fn satisfies(&self, required_role: RadrootsActorRole) -> bool { + role_satisfies(&self.roles, required_role) + } +} + +pub fn role_satisfies( + actor_roles: &BTreeSet<RadrootsActorRole>, + required_role: RadrootsActorRole, +) -> bool { + match required_role { + RadrootsActorRole::Any => true, + role => actor_roles.contains(&role), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn hex_64(character: char) -> String { + std::iter::repeat_n(character, 64).collect() + } + + #[test] + fn any_is_satisfied_by_any_actor_context() { + let actor = RadrootsActorContext::new(hex_64('a')).expect("actor"); + + assert!(actor.satisfies(RadrootsActorRole::Any)); + } + + #[test] + fn specific_roles_require_explicit_membership() { + let actor = RadrootsActorContext::with_roles(hex_64('a'), [RadrootsActorRole::Farmer]) + .expect("actor"); + + assert!(actor.satisfies(RadrootsActorRole::Farmer)); + assert!(!actor.satisfies(RadrootsActorRole::Seller)); + } + + #[test] + fn farmer_does_not_globally_satisfy_seller() { + let actor = RadrootsActorContext::with_roles(hex_64('a'), [RadrootsActorRole::Farmer]) + .expect("actor"); + + assert!(!actor.satisfies(RadrootsActorRole::Seller)); + } + + #[test] + fn multi_role_actors_satisfy_each_assigned_role() { + let actor = RadrootsActorContext::with_roles( + hex_64('a'), + [RadrootsActorRole::Farmer, RadrootsActorRole::Seller], + ) + .expect("actor"); + + assert!(actor.satisfies(RadrootsActorRole::Farmer)); + assert!(actor.satisfies(RadrootsActorRole::Seller)); + assert!(!actor.satisfies(RadrootsActorRole::Buyer)); + } } diff --git a/crates/authority/src/lib.rs b/crates/authority/src/lib.rs @@ -9,6 +9,9 @@ pub mod actor; pub mod error; pub mod signer; -pub use actor::RadrootsActorContext; +pub use actor::{ + RadrootsActorContext, RadrootsActorResolutionRequest, RadrootsActorSelector, + RadrootsActorSource, role_satisfies, +}; pub use error::RadrootsAuthorityError; pub use signer::RadrootsSignerIdentity; diff --git a/crates/events/src/contract.rs b/crates/events/src/contract.rs @@ -53,7 +53,7 @@ pub enum RadrootsEventStability { Experimental, } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum RadrootsActorRole { Any, Application,