Skip to main content

bamboo_domain/session/
authority.rs

1//! Trusted Session identity. Cosmetic metadata never grants this identity.
2
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6/// Reserved location for the default Supervisor. The ID alone grants no authority.
7pub const DEFAULT_SUPERVISOR_SESSION_ID: &str = "bamboo-default-supervisor";
8
9/// A snapshot cannot be committed against the durable Session authority.
10/// Persistence publishers must not cache a rejected identity, including when
11/// they normally publish runtime state after an unrelated I/O failure.
12#[derive(Debug, thiserror::Error)]
13#[error("Session authority conflict: {0}")]
14pub struct SessionAuthorityConflict(pub String);
15
16/// Persisted authority assigned only through the trusted bootstrap storage port.
17/// Ordinary constructors, copies and children must not inherit Supervisor identity.
18#[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    /// Identity shape observed from an executing snapshot, never a live grant.
36    /// Every management operation must still revalidate canonical storage.
37    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/// Small bootstrap result, deliberately excluding Session history/control-plane
53/// snapshots so callers cannot install a history-free snapshot in a session cache.
54#[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}