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}