Skip to main content

tenferro_runtime/runtime/
identity.rs

1use std::any::TypeId;
2use std::fmt;
3use std::num::NonZeroU64;
4use std::sync::Arc;
5
6use super::{IdentityError, IdentityKind};
7
8/// Opaque identity allocated for one [`Runtime`](super::Runtime) instance.
9///
10/// The runtime uses this value to namespace snapshots, event domains, and
11/// prepared execution. Construction and numeric access remain crate-private so
12/// callers can compare identities without manufacturing one.
13///
14/// # Examples
15///
16/// ```
17/// use std::fmt::Debug;
18/// use tenferro_runtime::RuntimeId;
19///
20/// fn requires_debug<T: Debug>() {}
21/// requires_debug::<RuntimeId>();
22/// ```
23#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
24pub struct RuntimeId(NonZeroU64);
25
26impl RuntimeId {
27    // INVARIANT: Runtime construction is the sole production allocator; the
28    // crate-private constructor keeps the numeric representation opaque.
29    #[allow(dead_code)]
30    pub(crate) const fn from_nonzero(value: NonZeroU64) -> Self {
31        Self(value)
32    }
33
34    // INVARIANT: Runtime internals read the value only to namespace runtime
35    // snapshots, event domains, and prepared-work validation.
36    #[allow(dead_code)]
37    pub(crate) const fn get(self) -> NonZeroU64 {
38        self.0
39    }
40}
41
42/// Opaque monotonically increasing configuration epoch for one runtime.
43///
44/// A new epoch is published when a runtime reconfiguration changes its
45/// execution snapshot; prepared work from an older epoch is rejected.
46///
47/// # Examples
48///
49/// ```
50/// use std::fmt::Debug;
51/// use tenferro_runtime::RuntimeEpoch;
52///
53/// fn requires_debug<T: Debug>() {}
54/// requires_debug::<RuntimeEpoch>();
55/// ```
56#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
57pub struct RuntimeEpoch(NonZeroU64);
58
59impl RuntimeEpoch {
60    // INVARIANT: A newly built runtime starts at epoch one; publication is the
61    // only production path that advances it.
62    #[allow(dead_code)]
63    pub(crate) const fn one() -> Self {
64        Self(NonZeroU64::MIN)
65    }
66
67    // INVARIANT: Snapshot construction supplies validated nonzero epochs while
68    // keeping the representation private to the runtime crate.
69    #[allow(dead_code)]
70    pub(crate) const fn from_nonzero(value: NonZeroU64) -> Self {
71        Self(value)
72    }
73
74    // INVARIANT: Runtime snapshot and prepared-work validation are the only
75    // production consumers of the numeric epoch.
76    #[allow(dead_code)]
77    pub(crate) const fn get(self) -> NonZeroU64 {
78        self.0
79    }
80
81    // INVARIANT: Reconfiguration uses `None` to report epoch exhaustion before
82    // publishing a wrapped value.
83    #[allow(dead_code)]
84    pub(crate) fn checked_next(self) -> Option<Self> {
85        self.0
86            .get()
87            .checked_add(1)
88            .and_then(NonZeroU64::new)
89            .map(Self)
90    }
91}
92
93/// Validated namespaced identity of an execution engine.
94///
95/// # Examples
96///
97/// ```
98/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
99/// use tenferro_runtime::EngineId;
100///
101/// assert_eq!(EngineId::new("tenferro.cpu.v1")?.as_str(), "tenferro.cpu.v1");
102/// # Ok(())
103/// # }
104/// ```
105#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
106pub struct EngineId(Arc<str>);
107
108impl EngineId {
109    /// Validate a lowercase ASCII namespaced engine identifier.
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use tenferro_runtime::EngineId;
115    ///
116    /// assert!(EngineId::new("tenferro.cpu").is_ok());
117    /// ```
118    ///
119    /// # Errors
120    ///
121    /// Returns [`IdentityError`] with [`IdentityKind::Engine`] when `value`
122    /// does not match the lowercase ASCII namespaced identifier grammar.
123    pub fn new(value: impl Into<Arc<str>>) -> Result<Self, IdentityError> {
124        validate_identifier(value.into(), IdentityKind::Engine).map(Self)
125    }
126
127    /// Borrow the validated identifier text.
128    ///
129    /// # Examples
130    ///
131    /// ```
132    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
133    /// use tenferro_runtime::EngineId;
134    ///
135    /// assert_eq!(EngineId::new("tenferro.cpu")?.as_str(), "tenferro.cpu");
136    /// # Ok(())
137    /// # }
138    /// ```
139    pub fn as_str(&self) -> &str {
140        &self.0
141    }
142}
143
144/// Validated namespaced identity of a runtime provider.
145///
146/// A provider ID identifies the implementation namespace in the runtime
147/// control plane. It is deliberately distinct from tensor placement metadata.
148///
149/// # Examples
150///
151/// ```
152/// use tenferro_runtime::ProviderId;
153///
154/// assert_eq!(ProviderId::new("tenferro.cpu")?.as_str(), "tenferro.cpu");
155/// # Ok::<(), Box<dyn std::error::Error>>(())
156/// ```
157#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
158pub struct ProviderId(Arc<str>);
159
160impl ProviderId {
161    /// Validate a lowercase ASCII namespaced provider identifier.
162    ///
163    /// # Errors
164    ///
165    /// Returns [`IdentityError`] with [`IdentityKind::Provider`] when `value`
166    /// does not match the lowercase ASCII namespaced identifier grammar.
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// use tenferro_runtime::ProviderId;
172    ///
173    /// assert!(ProviderId::new("tenferro.cuda").is_ok());
174    /// ```
175    pub fn new(value: impl Into<Arc<str>>) -> Result<Self, IdentityError> {
176        validate_identifier(value.into(), IdentityKind::Provider).map(Self)
177    }
178
179    /// Borrow the validated provider identifier text.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use tenferro_runtime::ProviderId;
185    ///
186    /// assert_eq!(ProviderId::new("tenferro.cuda")?.as_str(), "tenferro.cuda");
187    /// # Ok::<(), Box<dyn std::error::Error>>(())
188    /// ```
189    pub fn as_str(&self) -> &str {
190        &self.0
191    }
192}
193
194/// Immutable runtime-control-plane binding of a provider to one physical target.
195///
196/// The target identity is opaque to the runtime and is canonicalized by the
197/// provider. It is not [`tenferro_tensor::DeviceId`] or tensor placement data.
198/// This immutable provider/target pair is the durable identity carried by
199/// runtime snapshots, execution locations, and transfer requests.
200/// Because this value appears in structured diagnostics and debug output, it
201/// accepts nonempty ASCII graphic text (no controls, whitespace, or Unicode
202/// confusables). Providers that need byte-level or Unicode identities must
203/// first canonicalize them into an escaped diagnostic-safe ASCII form.
204/// Equality, ordering, and hashing include both the provider and target.
205///
206/// # Examples
207///
208/// ```
209/// use tenferro_runtime::{ProviderDeviceIdentity, ProviderId};
210///
211/// let identity = ProviderDeviceIdentity::new(ProviderId::new("tenferro.cuda")?, "device-0")?;
212/// assert_eq!(identity.provider_id().as_str(), "tenferro.cuda");
213/// assert_eq!(identity.target_identity(), "device-0");
214/// # Ok::<(), Box<dyn std::error::Error>>(())
215/// ```
216#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
217pub struct ProviderDeviceIdentity {
218    provider_id: ProviderId,
219    target_identity: Arc<str>,
220}
221
222impl ProviderDeviceIdentity {
223    /// Construct a provider binding from a validated provider and opaque target.
224    ///
225    /// # Errors
226    ///
227    /// Returns [`IdentityError`] with [`IdentityKind::ProviderTarget`] when the
228    /// provider-canonical target identity is empty or is not ASCII graphic
229    /// text. Target text is deliberately diagnostic-safe; opaque
230    /// provider-specific byte or Unicode identities must be encoded by the
231    /// provider before construction.
232    ///
233    /// # Examples
234    ///
235    /// ```
236    /// use tenferro_runtime::{ProviderDeviceIdentity, ProviderId};
237    ///
238    /// let identity = ProviderDeviceIdentity::new(
239    ///     ProviderId::new("tenferro.cpu")?,
240    ///     "host-0",
241    /// )?;
242    /// assert_eq!(identity.target_identity(), "host-0");
243    /// # Ok::<(), Box<dyn std::error::Error>>(())
244    /// ```
245    pub fn new(
246        provider_id: ProviderId,
247        target_identity: impl Into<Arc<str>>,
248    ) -> Result<Self, IdentityError> {
249        let target_identity = target_identity.into();
250        if target_identity.is_empty()
251            || !target_identity
252                .chars()
253                .all(|character| character.is_ascii_graphic())
254        {
255            return Err(IdentityError::malformed(IdentityKind::ProviderTarget));
256        }
257        Ok(Self {
258            provider_id,
259            target_identity,
260        })
261    }
262
263    /// Return the validated provider namespace.
264    ///
265    /// # Examples
266    ///
267    /// ```
268    /// use tenferro_runtime::{ProviderDeviceIdentity, ProviderId};
269    ///
270    /// let provider = ProviderId::new("tenferro.cpu")?;
271    /// let identity = ProviderDeviceIdentity::new(provider.clone(), "host-0")?;
272    /// assert_eq!(identity.provider_id(), &provider);
273    /// # Ok::<(), Box<dyn std::error::Error>>(())
274    /// ```
275    pub fn provider_id(&self) -> &ProviderId {
276        &self.provider_id
277    }
278
279    /// Return the provider-canonical opaque target identity.
280    ///
281    /// # Examples
282    ///
283    /// ```
284    /// use tenferro_runtime::{ProviderDeviceIdentity, ProviderId};
285    ///
286    /// let identity = ProviderDeviceIdentity::new(ProviderId::new("tenferro.cpu")?, "host-0")?;
287    /// assert_eq!(identity.target_identity(), "host-0");
288    /// # Ok::<(), Box<dyn std::error::Error>>(())
289    /// ```
290    pub fn target_identity(&self) -> &str {
291        &self.target_identity
292    }
293}
294
295/// Validated namespaced identity of a hardware class.
296///
297/// # Examples
298///
299/// ```
300/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
301/// use tenferro_runtime::HardwareClassId;
302///
303/// assert_eq!(HardwareClassId::new("tenferro.cpu.host")?.as_str(), "tenferro.cpu.host");
304/// # Ok(())
305/// # }
306/// ```
307#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
308pub struct HardwareClassId(Arc<str>);
309
310impl HardwareClassId {
311    /// Validate a lowercase ASCII namespaced hardware-class identifier.
312    ///
313    /// # Examples
314    ///
315    /// ```
316    /// use tenferro_runtime::HardwareClassId;
317    ///
318    /// assert!(HardwareClassId::new("tenferro.cpu").is_ok());
319    /// ```
320    ///
321    /// # Errors
322    ///
323    /// Returns [`IdentityError`] with [`IdentityKind::HardwareClass`] when
324    /// `value` does not match the lowercase ASCII namespaced identifier grammar.
325    pub fn new(value: impl Into<Arc<str>>) -> Result<Self, IdentityError> {
326        validate_identifier(value.into(), IdentityKind::HardwareClass).map(Self)
327    }
328
329    /// Borrow the validated identifier text.
330    ///
331    /// # Examples
332    ///
333    /// ```
334    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
335    /// use tenferro_runtime::HardwareClassId;
336    ///
337    /// assert_eq!(HardwareClassId::new("tenferro.cpu")?.as_str(), "tenferro.cpu");
338    /// # Ok(())
339    /// # }
340    /// ```
341    pub fn as_str(&self) -> &str {
342        &self.0
343    }
344}
345
346/// Opaque runtime-local identity assigned to one frozen engine registration.
347///
348/// The identity is part of the durable registration contract: it is preserved
349/// when the same registration is carried into a later snapshot, changes when
350/// that registration is replaced, and participates in event-domain identity.
351/// Its equality, ordering, and hashing include the private issuer and ordinal;
352/// debug output exposes only the useful ordinal.
353///
354/// # Examples
355///
356/// ```
357/// use std::fmt::Debug;
358/// use tenferro_runtime::RegistrationIdentity;
359///
360/// fn requires_debug<T: Debug>() {}
361/// requires_debug::<RegistrationIdentity>();
362/// ```
363#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
364pub struct RegistrationIdentity {
365    issuer: NonZeroU64,
366    ordinal: NonZeroU64,
367}
368
369impl RegistrationIdentity {
370    // INVARIANT: The runtime registration allocator is the sole production
371    // issuer; callers receive and compare the opaque identity only.
372    #[allow(dead_code)]
373    pub(crate) const fn new(issuer: NonZeroU64, ordinal: NonZeroU64) -> Self {
374        Self { issuer, ordinal }
375    }
376
377    /// Return this registration's runtime-local ordinal.
378    ///
379    /// # Examples
380    ///
381    /// ```
382    /// use std::fmt::Debug;
383    /// use tenferro_runtime::RegistrationIdentity;
384    ///
385    /// fn requires_debug<T: Debug>() {}
386    /// requires_debug::<RegistrationIdentity>();
387    /// ```
388    pub fn ordinal(self) -> NonZeroU64 {
389        self.ordinal
390    }
391}
392
393impl fmt::Debug for RegistrationIdentity {
394    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
395        formatter
396            .debug_struct("RegistrationIdentity")
397            .field("ordinal", &self.ordinal)
398            .finish()
399    }
400}
401
402/// Type identity of an execution context without retaining a context value.
403///
404/// # Examples
405///
406/// ```
407/// use tenferro_runtime::ExecutionContextIdentity;
408///
409/// assert_eq!(ExecutionContextIdentity::of::<u64>().type_name(), std::any::type_name::<u64>());
410/// ```
411#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
412pub struct ExecutionContextIdentity {
413    type_id: TypeId,
414    type_name: &'static str,
415}
416
417impl ExecutionContextIdentity {
418    /// Return the identity of a `'static` context type.
419    ///
420    /// # Examples
421    ///
422    /// ```
423    /// use tenferro_runtime::ExecutionContextIdentity;
424    ///
425    /// assert_eq!(ExecutionContextIdentity::of::<u64>(), ExecutionContextIdentity::of::<u64>());
426    /// ```
427    pub fn of<T: 'static>() -> Self {
428        Self {
429            type_id: TypeId::of::<T>(),
430            type_name: std::any::type_name::<T>(),
431        }
432    }
433
434    /// Return the diagnostic name of the context type.
435    ///
436    /// # Examples
437    ///
438    /// ```
439    /// use tenferro_runtime::ExecutionContextIdentity;
440    ///
441    /// assert_eq!(ExecutionContextIdentity::of::<u64>().type_name(), std::any::type_name::<u64>());
442    /// ```
443    pub fn type_name(&self) -> &'static str {
444        self.type_name
445    }
446}
447
448pub(super) fn validate_identifier(
449    value: Arc<str>,
450    kind: IdentityKind,
451) -> Result<Arc<str>, IdentityError> {
452    let valid = value.is_ascii()
453        && value.split('.').count() >= 2
454        && value.split('.').all(valid_identifier_component);
455    valid
456        .then_some(value)
457        .ok_or_else(|| IdentityError::malformed(kind))
458}
459
460fn valid_identifier_component(component: &str) -> bool {
461    let bytes = component.as_bytes();
462    match bytes {
463        [single] => single.is_ascii_lowercase() || single.is_ascii_digit(),
464        [first, middle @ .., last] => {
465            (first.is_ascii_lowercase() || first.is_ascii_digit())
466                && (last.is_ascii_lowercase() || last.is_ascii_digit())
467                && middle.iter().all(|byte| {
468                    byte.is_ascii_lowercase()
469                        || byte.is_ascii_digit()
470                        || matches!(byte, b'-' | b'_')
471                })
472        }
473        [] => false,
474    }
475}