#![allow(missing_docs)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum CycleKind {
Genesis,
Heartbeat,
Login,
Logout,
Command,
GateTransition,
}
impl CycleKind {
pub fn discriminator(&self) -> u8 {
match self {
CycleKind::Genesis => 0,
CycleKind::Heartbeat => 1,
CycleKind::Login => 2,
CycleKind::Logout => 3,
CycleKind::Command => 4,
CycleKind::GateTransition => 5,
}
}
}
impl std::fmt::Display for CycleKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CycleKind::Genesis => write!(f, "genesis"),
CycleKind::Heartbeat => write!(f, "heartbeat"),
CycleKind::Login => write!(f, "login"),
CycleKind::Logout => write!(f, "logout"),
CycleKind::Command => write!(f, "command"),
CycleKind::GateTransition => write!(f, "gate_transition"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CycleContext {
pub cycle_class: CycleKind,
pub command_type: Option<String>,
pub actor: Option<String>,
pub request_id: Option<String>,
pub authorization_outcome: Option<String>,
pub detail: Option<String>,
}
impl CycleContext {
pub fn genesis() -> Self {
Self { cycle_class: CycleKind::Genesis, command_type: None, actor: None, request_id: None, authorization_outcome: None, detail: None }
}
pub fn heartbeat() -> Self {
Self { cycle_class: CycleKind::Heartbeat, command_type: None, actor: None, request_id: None, authorization_outcome: None, detail: None }
}
pub fn login(actor: impl Into<String>, outcome: impl Into<String>) -> Self {
Self { cycle_class: CycleKind::Login, command_type: Some("login".into()), actor: Some(actor.into()), request_id: None, authorization_outcome: Some(outcome.into()), detail: None }
}
pub fn logout(actor: impl Into<String>) -> Self {
Self { cycle_class: CycleKind::Logout, command_type: Some("session.logout".into()), actor: Some(actor.into()), request_id: None, authorization_outcome: Some("invalidated".into()), detail: None }
}
pub fn command(command_type: impl Into<String>, actor: impl Into<String>, request_id: impl Into<String>, authorization_outcome: impl Into<String>) -> Self {
Self { cycle_class: CycleKind::Command, command_type: Some(command_type.into()), actor: Some(actor.into()), request_id: Some(request_id.into()), authorization_outcome: Some(authorization_outcome.into()), detail: None }
}
pub fn gate_transition(actor: impl Into<String>, request_id: impl Into<String>, authorization_outcome: impl Into<String>, detail: impl Into<String>) -> Self {
Self { cycle_class: CycleKind::GateTransition, command_type: Some("gate.transition".into()), actor: Some(actor.into()), request_id: Some(request_id.into()), authorization_outcome: Some(authorization_outcome.into()), detail: Some(detail.into()) }
}
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
self.detail = Some(detail.into());
self
}
pub fn semantic_hash(&self) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(b"soma.traceability.v1");
hasher.update(&[self.cycle_class.discriminator()]);
hash_optional(&mut hasher, &self.command_type);
hash_optional(&mut hasher, &self.actor);
hash_optional(&mut hasher, &self.request_id);
hash_optional(&mut hasher, &self.authorization_outcome);
hash_optional(&mut hasher, &self.detail);
*hasher.finalize().as_bytes()
}
}
fn hash_optional(hasher: &mut blake3::Hasher, field: &Option<String>) {
match field {
None => { hasher.update(&[0x00]); }
Some(s) => {
hasher.update(&[0x01]);
hasher.update(&(s.len() as u32).to_le_bytes());
hasher.update(s.as_bytes());
}
}
}
pub const LEGACY_SEMANTIC_HASH: [u8; 32] = [0u8; 32];
use crate::quad::Tree;
pub trait CycleAnnotator: Send + Sync {
fn annotate(&self, tree: &Tree, cycle: u64) -> Vec<u8>;
}
pub struct NullAnnotator;
impl CycleAnnotator for NullAnnotator {
fn annotate(&self, _tree: &Tree, _cycle: u64) -> Vec<u8> {
vec![]
}
}