use crate::ownership::OwnershipLevel;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum ActorType {
#[serde(rename = "human_actor")]
HumanActor,
#[serde(rename = "ai_actor")]
AIActor,
#[serde(rename = "os_node_actor", alias = "node_actor")]
OSNodeActor,
#[serde(rename = "core_node_actor")]
CoreNodeActor,
#[serde(rename = "group_actor")]
GroupActor,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ActorDomain {
Life,
System,
Organization,
}
impl ActorType {
pub fn domain(&self) -> ActorDomain {
match self {
ActorType::HumanActor | ActorType::AIActor => ActorDomain::Life,
ActorType::OSNodeActor | ActorType::CoreNodeActor => ActorDomain::System,
ActorType::GroupActor => ActorDomain::Organization,
}
}
pub fn is_life_subject(&self) -> bool {
matches!(self.domain(), ActorDomain::Life)
}
pub fn is_system_node(&self) -> bool {
matches!(self.domain(), ActorDomain::System)
}
pub fn is_organization(&self) -> bool {
matches!(self.domain(), ActorDomain::Organization)
}
pub fn default_ownership_level(&self) -> OwnershipLevel {
match self {
ActorType::HumanActor | ActorType::AIActor => OwnershipLevel::L1ActorLife,
ActorType::OSNodeActor | ActorType::CoreNodeActor => OwnershipLevel::L3SystemNode,
ActorType::GroupActor => OwnershipLevel::L1ActorLife, }
}
}
impl fmt::Display for ActorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ActorType::HumanActor => write!(f, "human_actor"),
ActorType::AIActor => write!(f, "ai_actor"),
ActorType::OSNodeActor => write!(f, "osnode_actor"),
ActorType::CoreNodeActor => write!(f, "corenode_actor"),
ActorType::GroupActor => write!(f, "group_actor"),
}
}
}
const VALID_TYPE_PREFIXES: &[&str] = &["human", "ai", "osnode", "corenode", "group"];
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct ActorId(pub String);
impl ActorId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn new_validated(id: impl Into<String>) -> Option<Self> {
let actor_id = Self(id.into());
if actor_id.is_canonical() {
Some(actor_id)
} else {
None
}
}
pub fn generate(actor_type: ActorType) -> Self {
let uuid = uuid::Uuid::new_v4();
let prefix = match actor_type {
ActorType::HumanActor => "human",
ActorType::AIActor => "ai",
ActorType::OSNodeActor => "osnode",
ActorType::CoreNodeActor => "corenode",
ActorType::GroupActor => "group",
};
Self(format!("{}_{}", prefix, uuid.as_hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn is_canonical(&self) -> bool {
if self.0.is_empty() || self.0.len() > 128 {
return false;
}
let parts: Vec<&str> = self.0.splitn(2, '_').collect();
if parts.len() != 2 {
return false;
}
let prefix = parts[0];
let identifier = parts[1];
if !VALID_TYPE_PREFIXES.contains(&prefix) || identifier.is_empty() {
return false;
}
identifier
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-')
}
pub fn type_prefix(&self) -> Option<&str> {
self.0.split('_').next()
}
pub fn inferred_type(&self) -> Option<ActorType> {
match self.type_prefix()? {
"human" => Some(ActorType::HumanActor),
"ai" => Some(ActorType::AIActor),
"osnode" => Some(ActorType::OSNodeActor),
"corenode" => Some(ActorType::CoreNodeActor),
"group" => Some(ActorType::GroupActor),
_ => None,
}
}
pub fn matches_type(&self, expected: ActorType) -> bool {
self.inferred_type() == Some(expected)
}
}
impl fmt::Display for ActorId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for ActorId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for ActorId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ActorStatus {
Active,
Suspended,
InRepair,
Terminated,
}
impl Default for ActorStatus {
fn default() -> Self {
Self::Active
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_actor_type_serde_all_variants() {
assert_eq!(
serde_json::to_string(&ActorType::HumanActor).unwrap(),
"\"human_actor\""
);
assert_eq!(
serde_json::to_string(&ActorType::AIActor).unwrap(),
"\"ai_actor\""
);
assert_eq!(
serde_json::to_string(&ActorType::OSNodeActor).unwrap(),
"\"os_node_actor\""
);
assert_eq!(
serde_json::to_string(&ActorType::CoreNodeActor).unwrap(),
"\"core_node_actor\""
);
assert_eq!(
serde_json::to_string(&ActorType::GroupActor).unwrap(),
"\"group_actor\""
);
for variant in [
ActorType::HumanActor,
ActorType::AIActor,
ActorType::OSNodeActor,
ActorType::CoreNodeActor,
ActorType::GroupActor,
] {
let json = serde_json::to_string(&variant).unwrap();
let parsed: ActorType = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, variant);
}
}
#[test]
fn test_actor_type_serde_aliases() {
let parsed: ActorType = serde_json::from_str("\"node_actor\"").unwrap();
assert_eq!(parsed, ActorType::OSNodeActor);
let parsed: ActorType = serde_json::from_str("\"ai_actor\"").unwrap();
assert_eq!(parsed, ActorType::AIActor);
}
#[test]
fn test_actor_type_display() {
assert_eq!(ActorType::AIActor.to_string(), "ai_actor");
assert_eq!(ActorType::OSNodeActor.to_string(), "osnode_actor");
assert_eq!(ActorType::CoreNodeActor.to_string(), "corenode_actor");
}
#[test]
fn test_actor_type_domain() {
assert_eq!(ActorType::HumanActor.domain(), ActorDomain::Life);
assert_eq!(ActorType::AIActor.domain(), ActorDomain::Life);
assert_eq!(ActorType::OSNodeActor.domain(), ActorDomain::System);
assert_eq!(ActorType::CoreNodeActor.domain(), ActorDomain::System);
assert_eq!(ActorType::GroupActor.domain(), ActorDomain::Organization);
}
#[test]
fn test_actor_type_is_system_node() {
assert!(!ActorType::HumanActor.is_system_node());
assert!(!ActorType::AIActor.is_system_node());
assert!(ActorType::OSNodeActor.is_system_node());
assert!(ActorType::CoreNodeActor.is_system_node());
assert!(!ActorType::GroupActor.is_system_node());
}
#[test]
fn test_actor_type_is_life_subject() {
assert!(ActorType::HumanActor.is_life_subject());
assert!(ActorType::AIActor.is_life_subject());
assert!(!ActorType::OSNodeActor.is_life_subject());
assert!(!ActorType::CoreNodeActor.is_life_subject());
assert!(!ActorType::GroupActor.is_life_subject());
}
#[test]
fn test_actor_id_generation() {
let id = ActorId::generate(ActorType::AIActor);
assert!(id.as_str().starts_with("ai_"));
assert!(id.is_canonical());
}
#[test]
fn test_actor_id_serialization() {
let id = ActorId::new("ai_test-123");
let json = serde_json::to_string(&id).unwrap();
assert_eq!(json, "\"ai_test-123\"");
let parsed: ActorId = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, id);
}
#[test]
fn test_actor_id_canonical_valid() {
assert!(ActorId::new("ai_abc123").is_canonical());
assert!(ActorId::new("human_user-001").is_canonical());
assert!(ActorId::new("osnode_node-a1b2c3d4").is_canonical());
assert!(ActorId::new("corenode_validator-1").is_canonical());
assert!(ActorId::new("group_dao-xyz").is_canonical());
for actor_type in [
ActorType::HumanActor,
ActorType::AIActor,
ActorType::OSNodeActor,
ActorType::CoreNodeActor,
ActorType::GroupActor,
] {
let id = ActorId::generate(actor_type);
assert!(
id.is_canonical(),
"Generated {} ID should be canonical",
actor_type
);
}
}
#[test]
fn test_actor_id_canonical_invalid() {
assert!(!ActorId::new("").is_canonical());
assert!(!ActorId::new("abc123").is_canonical());
assert!(!ActorId::new("invalid_abc123").is_canonical());
assert!(!ActorId::new("ai_").is_canonical());
assert!(!ActorId::new("ai_test_with_underscore").is_canonical());
assert!(!ActorId::new("ai_test@123").is_canonical());
let long_id = format!("ai_{}", "a".repeat(130));
assert!(!ActorId::new(long_id).is_canonical());
}
#[test]
fn test_actor_id_inferred_type() {
assert_eq!(
ActorId::new("ai_test").inferred_type(),
Some(ActorType::AIActor)
);
assert_eq!(
ActorId::new("human_user").inferred_type(),
Some(ActorType::HumanActor)
);
assert_eq!(
ActorId::new("osnode_node1").inferred_type(),
Some(ActorType::OSNodeActor)
);
assert_eq!(
ActorId::new("corenode_val1").inferred_type(),
Some(ActorType::CoreNodeActor)
);
assert_eq!(
ActorId::new("group_dao1").inferred_type(),
Some(ActorType::GroupActor)
);
assert_eq!(ActorId::new("invalid_test").inferred_type(), None);
}
#[test]
fn test_actor_id_matches_type() {
let id = ActorId::new("ai_assistant");
assert!(id.matches_type(ActorType::AIActor));
assert!(!id.matches_type(ActorType::HumanActor));
}
#[test]
fn test_actor_status_default() {
assert_eq!(ActorStatus::default(), ActorStatus::Active);
}
#[test]
fn test_actor_status_serde() {
for status in [
ActorStatus::Active,
ActorStatus::Suspended,
ActorStatus::InRepair,
ActorStatus::Terminated,
] {
let json = serde_json::to_string(&status).unwrap();
let parsed: ActorStatus = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, status);
}
}
}