Skip to main content

lean_ctx/core/intelligence_runtime/
lease.rs

1// SPDX-License-Identifier: Apache-2.0
2//! The seam through which the optional runtime is licensed.
3//!
4//! A lease is opaque bytes signed by LeanCTX | Hive: this engine never parses a
5//! plan, a seat count or an expiry from it, it only hands the bytes to the
6//! runtime, which verifies them against its own Hive key. The Hive
7//! connector registers the one process-wide [`LeaseSource`] at start-up,
8//! before the first tool call; without a registered source there is no
9//! lease.
10
11use std::sync::{Arc, RwLock};
12
13/// One Hive-signed runtime lease, kept as the exact bytes Hive issued.
14#[derive(Clone, PartialEq, Eq)]
15pub struct RuntimeLeaseV1(Vec<u8>);
16
17impl RuntimeLeaseV1 {
18    pub fn from_bytes(bytes: Vec<u8>) -> Self {
19        Self(bytes)
20    }
21
22    pub(crate) fn as_bytes(&self) -> &[u8] {
23        &self.0
24    }
25}
26
27// The bytes are a credential: debug output names only their length.
28impl std::fmt::Debug for RuntimeLeaseV1 {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        write!(f, "RuntimeLeaseV1({} bytes)", self.0.len())
31    }
32}
33
34/// Where the current lease comes from.
35pub trait LeaseSource: Send + Sync {
36    /// The lease to present now; `None` means not licensed.
37    fn current(&self) -> Option<RuntimeLeaseV1>;
38}
39
40static SOURCE: RwLock<Option<Arc<dyn LeaseSource>>> = RwLock::new(None);
41
42/// Registers the process-wide lease source. A later call replaces it.
43pub fn set_lease_source(source: Box<dyn LeaseSource>) {
44    let source: Arc<dyn LeaseSource> = Arc::from(source);
45    *SOURCE
46        .write()
47        .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(source);
48}
49
50/// The lease the registered source offers now; `None` without a source.
51pub(crate) fn current_lease() -> Option<RuntimeLeaseV1> {
52    // Clone the handle so the source is never asked while the lock is held.
53    let source = SOURCE
54        .read()
55        .unwrap_or_else(std::sync::PoisonError::into_inner)
56        .clone()?;
57    source.current()
58}
59
60/// The current lease as base64, the form the runtime exchange carries.
61pub(crate) fn current_lease_b64() -> Option<String> {
62    use base64::Engine as _;
63    let lease = current_lease()?;
64    Some(base64::engine::general_purpose::STANDARD.encode(lease.as_bytes()))
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    struct Fixed(Option<Vec<u8>>);
72
73    impl LeaseSource for Fixed {
74        fn current(&self) -> Option<RuntimeLeaseV1> {
75            self.0.clone().map(RuntimeLeaseV1::from_bytes)
76        }
77    }
78
79    /// A source that asks for the lease again, which would deadlock if the
80    /// registry lock were held while a source runs.
81    struct Reentrant;
82
83    impl LeaseSource for Reentrant {
84        fn current(&self) -> Option<RuntimeLeaseV1> {
85            set_lease_source(Box::new(Fixed(Some(b"after".to_vec()))));
86            Some(RuntimeLeaseV1::from_bytes(b"during".to_vec()))
87        }
88    }
89
90    // One test: the registry is process-wide state.
91    #[test]
92    fn the_registered_source_decides_and_a_later_one_replaces_it() {
93        set_lease_source(Box::new(Fixed(None)));
94        assert_eq!(current_lease(), None);
95
96        set_lease_source(Box::new(Fixed(Some(b"lease-1".to_vec()))));
97        let lease = current_lease().expect("registered lease");
98        assert_eq!(lease.as_bytes(), b"lease-1");
99        assert_eq!(format!("{lease:?}"), "RuntimeLeaseV1(7 bytes)");
100
101        set_lease_source(Box::new(Reentrant));
102        assert_eq!(current_lease().expect("lease").as_bytes(), b"during");
103        assert_eq!(current_lease().expect("lease").as_bytes(), b"after");
104    }
105}