bamboo_domain/session/
authority.rs1use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6pub const DEFAULT_SUPERVISOR_SESSION_ID: &str = "bamboo-default-supervisor";
8
9#[derive(Debug, thiserror::Error)]
13#[error("Session authority conflict: {0}")]
14pub struct SessionAuthorityConflict(pub String);
15
16#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(tag = "kind", rename_all = "snake_case")]
20pub enum SessionAuthorityIdentity {
21 #[default]
22 Ordinary,
23 Supervisor {
24 incarnation_id: Uuid,
25 },
26}
27
28impl SessionAuthorityIdentity {
29 pub fn is_ordinary(&self) -> bool {
30 matches!(self, Self::Ordinary)
31 }
32}
33
34impl super::Session {
35 pub fn supervisor_incarnation_id(&self) -> Option<Uuid> {
38 let SessionAuthorityIdentity::Supervisor { incarnation_id } = self.authority_identity
39 else {
40 return None;
41 };
42 (self.id == DEFAULT_SUPERVISOR_SESSION_ID
43 && self.kind == super::SessionKind::Root
44 && self.root_session_id == self.id
45 && self.parent_session_id.is_none()
46 && self.spawn_depth == 0
47 && !incarnation_id.is_nil())
48 .then_some(incarnation_id)
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct SupervisorBootstrapReceipt {
56 pub session_id: String,
57 pub incarnation_id: Uuid,
58 pub created: bool,
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use crate::Session;
65
66 #[test]
67 fn legacy_metadata_and_child_constructors_do_not_grant_authority() {
68 let mut root = Session::new("root", "model");
69 root.metadata
70 .insert("authority_identity".into(), "supervisor".into());
71 root.metadata.insert("role".into(), "supervisor".into());
72 let raw = serde_json::to_value(&root).unwrap();
73 assert!(raw.get("authority_identity").is_none());
74 let loaded: Session = serde_json::from_value(raw).unwrap();
75 assert!(loaded.authority_identity.is_ordinary());
76
77 root.authority_identity = SessionAuthorityIdentity::Supervisor {
78 incarnation_id: Uuid::new_v4(),
79 };
80 let child = Session::new_child_of("child", &root, "model", "child");
81 let nested = Session::new_child_of("nested", &child, "model", "nested");
82 assert!(child.authority_identity.is_ordinary());
83 assert!(nested.authority_identity.is_ordinary());
84 assert_eq!(nested.root_session_id, root.id);
85 assert!(Session::new_child("flat", root.id, "model", "flat")
86 .authority_identity
87 .is_ordinary());
88 }
89
90 #[test]
91 fn supervisor_identity_round_trips_without_a_separate_role_field() {
92 let mut session = Session::new(DEFAULT_SUPERVISOR_SESSION_ID, "model");
93 session.authority_identity = SessionAuthorityIdentity::Supervisor {
94 incarnation_id: Uuid::new_v4(),
95 };
96 let raw = serde_json::to_value(&session).unwrap();
97 assert_eq!(raw["authority_identity"]["kind"], "supervisor");
98 let restored: Session = serde_json::from_value(raw).unwrap();
99 assert_eq!(restored.authority_identity, session.authority_identity);
100 for raw in [
101 serde_json::json!({"kind":"unknown"}),
102 serde_json::json!({"kind":"supervisor"}),
103 serde_json::json!({"kind":"supervisor","incarnation_id":"invalid"}),
104 ] {
105 assert!(serde_json::from_value::<SessionAuthorityIdentity>(raw).is_err());
106 }
107 }
108}