diff --git a/Cargo.lock b/Cargo.lock index 7b23eb0..e3cb70e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -933,6 +933,7 @@ dependencies = [ "sha2", "substrate-identity-store", "substrate-ipc", + "substrate-session", "tempfile", "thiserror 2.0.18", "tokio", @@ -1537,6 +1538,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "substrate-session" +version = "0.1.0" +dependencies = [ + "async-trait", + "chrono", + "guildhouse-did", + "serde", + "thiserror 2.0.18", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index ae8a294..8c0d4cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,5 +25,6 @@ atty = "0.2" tracing = "0.1" substrate-ipc = { path = "../substrate/crates/substrate-ipc" } substrate-identity-store = { path = "../substrate/crates/substrate-identity-store" } +substrate-session = { path = "../substrate-session" } tokio = { version = "1", features = ["full"] } libc = "0.2" diff --git a/libgsh/Cargo.toml b/libgsh/Cargo.toml index d848607..a727198 100644 --- a/libgsh/Cargo.toml +++ b/libgsh/Cargo.toml @@ -16,6 +16,7 @@ dirs = { workspace = true } tracing = { workspace = true } substrate-ipc = { workspace = true } substrate-identity-store = { workspace = true, optional = true } +substrate-session = { workspace = true } tokio = { workspace = true } libc = { workspace = true } diff --git a/libgsh/src/shell_tier.rs b/libgsh/src/shell_tier.rs index 0a0fe6b..94bdbd0 100644 --- a/libgsh/src/shell_tier.rs +++ b/libgsh/src/shell_tier.rs @@ -1,263 +1 @@ -// Copyright 2026 Guildhouse Dev -// SPDX-License-Identifier: Apache-2.0 - -//! Typed shell hierarchy (T0–T6). -//! -//! Local definition matching org-ops-core's `ShellTier`. Kept here to -//! avoid pulling the full org-ops-core dependency tree into gsh. If a -//! shared governance-types crate is created, consolidate there. -//! -//! ```text -//! T0 Genesis → T1 Infrastructure → ┬─ T2 Operator → ┬─ T3 Agent -//! │ └─ T4 Task -//! ├─ T5 Forensic -//! └─ T6 Recovery -//! ``` - -use std::fmt; -use std::str::FromStr; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum ShellTier { - T0Genesis, - T1Infrastructure, - T2Operator, - T3Agent, - T4Task, - T5Forensic, - T6Recovery, -} - -impl ShellTier { - pub fn numeric_level(&self) -> u8 { - match self { - Self::T0Genesis => 0, - Self::T1Infrastructure => 1, - Self::T2Operator => 2, - Self::T3Agent => 3, - Self::T4Task => 4, - Self::T5Forensic => 5, - Self::T6Recovery => 6, - } - } - - pub fn from_numeric(n: u8) -> Option { - match n { - 0 => Some(Self::T0Genesis), - 1 => Some(Self::T1Infrastructure), - 2 => Some(Self::T2Operator), - 3 => Some(Self::T3Agent), - 4 => Some(Self::T4Task), - 5 => Some(Self::T5Forensic), - 6 => Some(Self::T6Recovery), - _ => None, - } - } - - pub fn label(&self) -> &'static str { - match self { - Self::T0Genesis => "Genesis", - Self::T1Infrastructure => "Infrastructure", - Self::T2Operator => "Operator", - Self::T3Agent => "Agent", - Self::T4Task => "Task", - Self::T5Forensic => "Forensic", - Self::T6Recovery => "Recovery", - } - } - - pub fn parent(&self) -> Option { - match self { - Self::T0Genesis => None, - Self::T1Infrastructure => Some(Self::T0Genesis), - Self::T2Operator => Some(Self::T1Infrastructure), - Self::T3Agent => Some(Self::T2Operator), - Self::T4Task => Some(Self::T2Operator), - Self::T5Forensic => Some(Self::T1Infrastructure), - Self::T6Recovery => Some(Self::T1Infrastructure), - } - } - - pub fn satisfies(&self, required: ShellTier) -> bool { - if *self == required { - return true; - } - let mut current = required; - while let Some(p) = current.parent() { - if p == *self { - return true; - } - current = p; - } - false - } - - /// Map a legacy `GSH_SHELL_CLASS` string to a tier. - pub fn from_shell_class(class: &str) -> Self { - match class { - "System" => Self::T1Infrastructure, - "Application" => Self::T2Operator, - _ => Self::T2Operator, - } - } - - /// Return the legacy shell class string for backward compatibility. - pub fn to_shell_class(&self) -> &'static str { - match self { - Self::T0Genesis | Self::T1Infrastructure => "System", - _ => "Application", - } - } -} - -impl Default for ShellTier { - fn default() -> Self { - ShellTier::T2Operator - } -} - -impl fmt::Display for ShellTier { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "T{} ({})", self.numeric_level(), self.label()) - } -} - -impl FromStr for ShellTier { - type Err = String; - - fn from_str(s: &str) -> Result { - if let Ok(n) = s.parse::() { - return Self::from_numeric(n).ok_or_else(|| format!("unknown shell tier: {s}")); - } - match s.to_lowercase().trim() { - "t0" | "genesis" | "t0genesis" => Ok(Self::T0Genesis), - "t1" | "infrastructure" | "t1infrastructure" => Ok(Self::T1Infrastructure), - "t2" | "operator" | "t2operator" => Ok(Self::T2Operator), - "t3" | "agent" | "t3agent" => Ok(Self::T3Agent), - "t4" | "task" | "t4task" => Ok(Self::T4Task), - "t5" | "forensic" | "t5forensic" => Ok(Self::T5Forensic), - "t6" | "recovery" | "t6recovery" => Ok(Self::T6Recovery), - other => Err(format!("unknown shell tier: {other}")), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn default_is_t2() { - assert_eq!(ShellTier::default(), ShellTier::T2Operator); - } - - #[test] - fn display_format() { - assert_eq!(format!("{}", ShellTier::T0Genesis), "T0 (Genesis)"); - assert_eq!(format!("{}", ShellTier::T2Operator), "T2 (Operator)"); - assert_eq!(format!("{}", ShellTier::T5Forensic), "T5 (Forensic)"); - } - - #[test] - fn parse_canonical_short() { - assert_eq!("t0".parse::().unwrap(), ShellTier::T0Genesis); - assert_eq!("T2".parse::().unwrap(), ShellTier::T2Operator); - assert_eq!("T6".parse::().unwrap(), ShellTier::T6Recovery); - } - - #[test] - fn parse_numeric_string() { - assert_eq!("0".parse::().unwrap(), ShellTier::T0Genesis); - assert_eq!("3".parse::().unwrap(), ShellTier::T3Agent); - assert_eq!("6".parse::().unwrap(), ShellTier::T6Recovery); - assert!("7".parse::().is_err()); - } - - #[test] - fn parse_label() { - assert_eq!("genesis".parse::().unwrap(), ShellTier::T0Genesis); - assert_eq!("operator".parse::().unwrap(), ShellTier::T2Operator); - assert_eq!("forensic".parse::().unwrap(), ShellTier::T5Forensic); - } - - #[test] - fn parse_invalid() { - assert!("t7".parse::().is_err()); - assert!("bogus".parse::().is_err()); - } - - #[test] - fn parent_tree() { - assert_eq!(ShellTier::T0Genesis.parent(), None); - assert_eq!(ShellTier::T1Infrastructure.parent(), Some(ShellTier::T0Genesis)); - assert_eq!(ShellTier::T2Operator.parent(), Some(ShellTier::T1Infrastructure)); - assert_eq!(ShellTier::T3Agent.parent(), Some(ShellTier::T2Operator)); - assert_eq!(ShellTier::T4Task.parent(), Some(ShellTier::T2Operator)); - assert_eq!(ShellTier::T5Forensic.parent(), Some(ShellTier::T1Infrastructure)); - assert_eq!(ShellTier::T6Recovery.parent(), Some(ShellTier::T1Infrastructure)); - } - - #[test] - fn satisfies_self() { - for tier in [ - ShellTier::T0Genesis, ShellTier::T1Infrastructure, ShellTier::T2Operator, - ShellTier::T3Agent, ShellTier::T4Task, ShellTier::T5Forensic, ShellTier::T6Recovery, - ] { - assert!(tier.satisfies(tier)); - } - } - - #[test] - fn t0_satisfies_everything() { - assert!(ShellTier::T0Genesis.satisfies(ShellTier::T4Task)); - assert!(ShellTier::T0Genesis.satisfies(ShellTier::T6Recovery)); - } - - #[test] - fn t2_satisfies_t3_t4_not_t5_t6() { - assert!(ShellTier::T2Operator.satisfies(ShellTier::T3Agent)); - assert!(ShellTier::T2Operator.satisfies(ShellTier::T4Task)); - assert!(!ShellTier::T2Operator.satisfies(ShellTier::T5Forensic)); - assert!(!ShellTier::T2Operator.satisfies(ShellTier::T6Recovery)); - } - - #[test] - fn leaf_does_not_satisfy_sibling() { - assert!(!ShellTier::T3Agent.satisfies(ShellTier::T4Task)); - assert!(!ShellTier::T5Forensic.satisfies(ShellTier::T6Recovery)); - } - - #[test] - fn from_shell_class_mapping() { - assert_eq!(ShellTier::from_shell_class("Application"), ShellTier::T2Operator); - assert_eq!(ShellTier::from_shell_class("System"), ShellTier::T1Infrastructure); - assert_eq!(ShellTier::from_shell_class("Unknown"), ShellTier::T2Operator); - } - - #[test] - fn to_shell_class_backward_compat() { - assert_eq!(ShellTier::T0Genesis.to_shell_class(), "System"); - assert_eq!(ShellTier::T1Infrastructure.to_shell_class(), "System"); - assert_eq!(ShellTier::T2Operator.to_shell_class(), "Application"); - assert_eq!(ShellTier::T3Agent.to_shell_class(), "Application"); - assert_eq!(ShellTier::T5Forensic.to_shell_class(), "Application"); - } - - #[test] - fn numeric_levels() { - assert_eq!(ShellTier::T0Genesis.numeric_level(), 0); - assert_eq!(ShellTier::T3Agent.numeric_level(), 3); - assert_eq!(ShellTier::T6Recovery.numeric_level(), 6); - } - - #[test] - fn from_numeric_round_trip() { - for n in 0..=6u8 { - let tier = ShellTier::from_numeric(n).unwrap(); - assert_eq!(tier.numeric_level(), n); - } - assert!(ShellTier::from_numeric(7).is_none()); - } -} +pub use substrate_session::ShellTier;