refactor(libgsh): consolidate ShellTier to substrate-session canonical def
Replace local 263-line ShellTier enum + 16 tests with a single re-export from substrate-session. All generic ShellTier behavior (satisfies, parent, from_shell_class, from_numeric) lives in the canonical crate. 51 gsh tests passing. Signed-off-by: Tyler J King <tking@guildhouse.dev> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Tyler J King <tking@guildhouse.dev>
This commit is contained in:
parent
a97e9569d6
commit
c98211ab3d
4 changed files with 15 additions and 263 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -933,6 +933,7 @@ dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
"substrate-identity-store",
|
"substrate-identity-store",
|
||||||
"substrate-ipc",
|
"substrate-ipc",
|
||||||
|
"substrate-session",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -1537,6 +1538,17 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "substrate-session"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"chrono",
|
||||||
|
"guildhouse-did",
|
||||||
|
"serde",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
|
|
|
||||||
|
|
@ -25,5 +25,6 @@ atty = "0.2"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
substrate-ipc = { path = "../substrate/crates/substrate-ipc" }
|
substrate-ipc = { path = "../substrate/crates/substrate-ipc" }
|
||||||
substrate-identity-store = { path = "../substrate/crates/substrate-identity-store" }
|
substrate-identity-store = { path = "../substrate/crates/substrate-identity-store" }
|
||||||
|
substrate-session = { path = "../substrate-session" }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ dirs = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
substrate-ipc = { workspace = true }
|
substrate-ipc = { workspace = true }
|
||||||
substrate-identity-store = { workspace = true, optional = true }
|
substrate-identity-store = { workspace = true, optional = true }
|
||||||
|
substrate-session = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,263 +1 @@
|
||||||
// Copyright 2026 Guildhouse Dev
|
pub use substrate_session::ShellTier;
|
||||||
// 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<Self> {
|
|
||||||
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<ShellTier> {
|
|
||||||
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<Self, Self::Err> {
|
|
||||||
if let Ok(n) = s.parse::<u8>() {
|
|
||||||
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::<ShellTier>().unwrap(), ShellTier::T0Genesis);
|
|
||||||
assert_eq!("T2".parse::<ShellTier>().unwrap(), ShellTier::T2Operator);
|
|
||||||
assert_eq!("T6".parse::<ShellTier>().unwrap(), ShellTier::T6Recovery);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_numeric_string() {
|
|
||||||
assert_eq!("0".parse::<ShellTier>().unwrap(), ShellTier::T0Genesis);
|
|
||||||
assert_eq!("3".parse::<ShellTier>().unwrap(), ShellTier::T3Agent);
|
|
||||||
assert_eq!("6".parse::<ShellTier>().unwrap(), ShellTier::T6Recovery);
|
|
||||||
assert!("7".parse::<ShellTier>().is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_label() {
|
|
||||||
assert_eq!("genesis".parse::<ShellTier>().unwrap(), ShellTier::T0Genesis);
|
|
||||||
assert_eq!("operator".parse::<ShellTier>().unwrap(), ShellTier::T2Operator);
|
|
||||||
assert_eq!("forensic".parse::<ShellTier>().unwrap(), ShellTier::T5Forensic);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_invalid() {
|
|
||||||
assert!("t7".parse::<ShellTier>().is_err());
|
|
||||||
assert!("bogus".parse::<ShellTier>().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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue