Skip to main content

tenferro_runtime/runtime/
engine_registration.rs

1use std::fmt;
2use std::sync::Arc;
3
4use super::{
5    execution, CoreCapabilityBundle, EngineId, EventDomainDriver, ExecutionContextIdentity,
6    HardwareClassId, InputSignatureEntry, ProviderDeviceIdentity, RuntimeCacheOwner,
7    RuntimeConfigError, StorageClass,
8};
9use tenferro_tensor::{AllocationDomainId, Placement, TensorBackend, TensorRead};
10
11#[derive(Debug)]
12pub(super) struct CandidateRegistrationToken;
13
14type InputPlacementPredicate = dyn Fn(&Placement, &StorageClass) -> bool + Send + Sync + 'static;
15type InputSignaturePredicate = dyn Fn(&Placement, Option<&'static str>, Option<AllocationDomainId>, &StorageClass) -> bool
16    + Send
17    + Sync
18    + 'static;
19type InputTensorPredicate =
20    dyn for<'a> Fn(&TensorRead<'a>, &StorageClass) -> bool + Send + Sync + 'static;
21
22/// Named placement admission contract for one provider ingress.
23#[derive(Clone)]
24pub struct InputPlacementContract(Arc<InputPlacementPredicate>);
25
26impl InputPlacementContract {
27    /// Construct a named placement admission contract.
28    ///
29    /// # Examples
30    ///
31    /// ```
32    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
33    /// use tenferro_runtime::{InputPlacementContract, StorageClass};
34    /// use tenferro_tensor::Placement;
35    ///
36    /// let storage = StorageClass::new("example.storage.host")?;
37    /// let contract = InputPlacementContract::new(move |placement: &Placement, candidate| {
38    ///     placement == &Placement::default() && candidate == &storage
39    /// });
40    /// let _ = contract;
41    /// # Ok(())
42    /// # }
43    /// ```
44    pub fn new(
45        predicate: impl Fn(&Placement, &StorageClass) -> bool + Send + Sync + 'static,
46    ) -> Self {
47        Self(Arc::new(predicate))
48    }
49
50    fn accepts(&self, placement: &Placement, storage_class: &StorageClass) -> bool {
51        (self.0)(placement, storage_class)
52    }
53}
54
55impl fmt::Debug for InputPlacementContract {
56    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
57        formatter.write_str("InputPlacementContract(..)")
58    }
59}
60
61/// Named value-free physical input signature contract.
62#[derive(Clone)]
63pub struct InputSignatureContract(Arc<InputSignaturePredicate>);
64
65impl InputSignatureContract {
66    /// Construct a named value-free physical input signature contract.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
72    /// use tenferro_runtime::{InputSignatureContract, StorageClass};
73    /// use tenferro_tensor::Placement;
74    ///
75    /// let storage = StorageClass::new("example.storage.host")?;
76    /// let contract = InputSignatureContract::new(move |
77    ///     placement: &Placement, family, domain, candidate| {
78    ///         placement == &Placement::default()
79    ///             && family.is_none()
80    ///             && domain.is_none()
81    ///             && candidate == &storage
82    ///     });
83    /// let _ = contract;
84    /// # Ok(())
85    /// # }
86    /// ```
87    pub fn new(
88        predicate: impl Fn(&Placement, Option<&'static str>, Option<AllocationDomainId>, &StorageClass) -> bool
89            + Send
90            + Sync
91            + 'static,
92    ) -> Self {
93        Self(Arc::new(predicate))
94    }
95
96    fn accepts(&self, input: &InputSignatureEntry, storage_class: &StorageClass) -> bool {
97        (self.0)(
98            input.placement(),
99            input.backend_family(),
100            input.allocation_domain(),
101            storage_class,
102        )
103    }
104}
105
106impl fmt::Debug for InputSignatureContract {
107    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108        formatter.write_str("InputSignatureContract(..)")
109    }
110}
111
112/// Named runtime-input residency contract.
113#[derive(Clone)]
114pub struct RuntimeInputContract(Arc<InputTensorPredicate>);
115
116impl RuntimeInputContract {
117    /// Construct a named runtime-input residency contract.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// use tenferro_runtime::RuntimeInputContract;
123    ///
124    /// let contract = RuntimeInputContract::new(|_, _| true);
125    /// let _ = contract;
126    /// ```
127    pub fn new(
128        predicate: impl for<'a> Fn(&TensorRead<'a>, &StorageClass) -> bool + Send + Sync + 'static,
129    ) -> Self {
130        Self(Arc::new(predicate))
131    }
132
133    fn accepts(&self, input: &TensorRead<'_>, storage_class: &StorageClass) -> bool {
134        (self.0)(input, storage_class)
135    }
136}
137
138impl fmt::Debug for RuntimeInputContract {
139    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
140        formatter.write_str("RuntimeInputContract(..)")
141    }
142}
143
144/// Named resident-output ownership contract.
145#[derive(Clone)]
146pub struct ResidentOutputContract(Arc<InputTensorPredicate>);
147
148impl ResidentOutputContract {
149    /// Construct a named resident-output ownership contract.
150    ///
151    /// # Examples
152    ///
153    /// ```
154    /// use tenferro_runtime::ResidentOutputContract;
155    ///
156    /// let contract = ResidentOutputContract::new(|_, _| true);
157    /// let _ = contract;
158    /// ```
159    pub fn new(
160        predicate: impl for<'a> Fn(&TensorRead<'a>, &StorageClass) -> bool + Send + Sync + 'static,
161    ) -> Self {
162        Self(Arc::new(predicate))
163    }
164
165    fn accepts(&self, input: &TensorRead<'_>, storage_class: &StorageClass) -> bool {
166        (self.0)(input, storage_class)
167    }
168}
169
170impl fmt::Debug for ResidentOutputContract {
171    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
172        formatter.write_str("ResidentOutputContract(..)")
173    }
174}
175
176/// Complete named ingress contract for an executable provider binding.
177#[derive(Clone, Debug)]
178pub struct InputIngressContract {
179    placement: InputPlacementContract,
180    signature: InputSignatureContract,
181    runtime_input: RuntimeInputContract,
182    resident_output: ResidentOutputContract,
183}
184
185impl InputIngressContract {
186    /// Assemble the four distinct ingress contracts atomically.
187    ///
188    /// # Examples
189    ///
190    /// ```
191    /// use tenferro_runtime::{
192    ///     InputIngressContract, InputPlacementContract, InputSignatureContract,
193    ///     ResidentOutputContract, RuntimeInputContract,
194    /// };
195    ///
196    /// let ingress = InputIngressContract::new(
197    ///     InputPlacementContract::new(|_, _| true),
198    ///     InputSignatureContract::new(|_, _, _, _| true),
199    ///     RuntimeInputContract::new(|_, _| true),
200    ///     ResidentOutputContract::new(|_, _| true),
201    /// );
202    /// let _ = ingress;
203    /// ```
204    pub fn new(
205        placement: InputPlacementContract,
206        signature: InputSignatureContract,
207        runtime_input: RuntimeInputContract,
208        resident_output: ResidentOutputContract,
209    ) -> Self {
210        Self {
211            placement,
212            signature,
213            runtime_input,
214            resident_output,
215        }
216    }
217
218    fn accepts_placement(&self, placement: &Placement, storage_class: &StorageClass) -> bool {
219        self.placement.accepts(placement, storage_class)
220    }
221
222    fn accepts_signature(&self, input: &InputSignatureEntry, storage_class: &StorageClass) -> bool {
223        self.signature.accepts(input, storage_class)
224    }
225
226    fn accepts_runtime_input(&self, input: &TensorRead<'_>, storage_class: &StorageClass) -> bool {
227        self.runtime_input.accepts(input, storage_class)
228    }
229
230    fn owns_resident_output(&self, input: &TensorRead<'_>, storage_class: &StorageClass) -> bool {
231        self.resident_output.accepts(input, storage_class)
232    }
233}
234
235/// Shared provider metadata for one runtime engine registration.
236///
237/// The caller supplies the engine and provider/device identities explicitly.
238/// Storage metadata is validated when the metadata is assembled into either an
239/// executable or preparation-only registration.
240///
241/// # Examples
242///
243/// ```
244/// use std::sync::Arc;
245///
246/// use tenferro_runtime::{
247///     CoreCapabilityBundle, EngineId, EngineRegistrationMetadata, HardwareClassId,
248///     ProviderDeviceIdentity, ProviderId, StorageClass,
249/// };
250///
251/// let engine_id = EngineId::new("example.engine.v1")?;
252/// let provider = ProviderDeviceIdentity::new(
253///     ProviderId::new("example.provider")?,
254///     "device:0",
255/// )?;
256/// let hardware = HardwareClassId::new("example.hardware.v1")?;
257/// let storage = StorageClass::new("example.storage.v1")?;
258/// let metadata = EngineRegistrationMetadata::new(
259///     engine_id,
260///     provider,
261///     hardware,
262///     Arc::from([storage.clone()]),
263///     storage,
264///     CoreCapabilityBundle::default(),
265/// );
266/// let _ = metadata;
267/// # Ok::<(), Box<dyn std::error::Error>>(())
268/// ```
269#[derive(Clone, Debug)]
270pub struct EngineRegistrationMetadata {
271    engine_id: EngineId,
272    provider_device_identity: ProviderDeviceIdentity,
273    hardware_class: HardwareClassId,
274    storage_classes: Arc<[StorageClass]>,
275    default_storage_class: StorageClass,
276    capabilities: CoreCapabilityBundle,
277}
278
279impl EngineRegistrationMetadata {
280    /// Construct the metadata shared by executable and preparation-only
281    /// registration descriptors.
282    pub fn new(
283        engine_id: EngineId,
284        provider_device_identity: ProviderDeviceIdentity,
285        hardware_class: HardwareClassId,
286        storage_classes: Arc<[StorageClass]>,
287        default_storage_class: StorageClass,
288        capabilities: CoreCapabilityBundle,
289    ) -> Self {
290        Self {
291            engine_id,
292            provider_device_identity,
293            hardware_class,
294            storage_classes,
295            default_storage_class,
296            capabilities,
297        }
298    }
299}
300
301/// Typed configuration for assembling an executable engine registration.
302///
303/// The backend, event domain, ingress contract, and optional cache owner are
304/// kept together with the shared provider metadata until the runtime creates
305/// the complete execution witness.
306///
307/// # Examples
308///
309/// ```
310/// use std::sync::Arc;
311///
312/// use tenferro_runtime::{
313///     CoreCapabilityBundle, EngineId, EngineRegistrationMetadata,
314///     ExecutableEngineRegistrationConfig, EventDomainDriver, HardwareClassId,
315///     ImmediateEventDomainDriver, InputIngressContract, InputPlacementContract,
316///     InputSignatureContract, ProviderDeviceIdentity, ProviderId, ResidentOutputContract,
317///     RuntimeInputContract, StorageClass,
318/// };
319///
320/// fn make_config<B>(backend: B) -> ExecutableEngineRegistrationConfig<B> {
321///     let engine_id = EngineId::new("example.engine.v1").unwrap();
322///     let provider = ProviderDeviceIdentity::new(
323///         ProviderId::new("example.provider").unwrap(),
324///         "device:0",
325///     )
326///     .unwrap();
327///     let hardware = HardwareClassId::new("example.hardware.v1").unwrap();
328///     let storage = StorageClass::new("example.storage.v1").unwrap();
329///     let metadata = EngineRegistrationMetadata::new(
330///         engine_id,
331///         provider,
332///         hardware,
333///         Arc::from([storage.clone()]),
334///         storage,
335///         CoreCapabilityBundle::default(),
336///     );
337///     let ingress = InputIngressContract::new(
338///         InputPlacementContract::new(|_, _| true),
339///         InputSignatureContract::new(|_, _, _, _| true),
340///         RuntimeInputContract::new(|_, _| true),
341///         ResidentOutputContract::new(|_, _| true),
342///     );
343///     let event_domain_driver: Arc<dyn EventDomainDriver> =
344///         Arc::new(ImmediateEventDomainDriver::new());
345///     ExecutableEngineRegistrationConfig::new(
346///         metadata,
347///         backend,
348///         event_domain_driver,
349///         ingress,
350///         None,
351///     )
352/// }
353///
354/// let _ = make_config(());
355/// ```
356pub struct ExecutableEngineRegistrationConfig<B> {
357    metadata: EngineRegistrationMetadata,
358    backend: B,
359    event_domain_driver: Arc<dyn EventDomainDriver>,
360    ingress: InputIngressContract,
361    cache_owner: Option<Arc<dyn RuntimeCacheOwner>>,
362}
363
364impl<B> fmt::Debug for ExecutableEngineRegistrationConfig<B> {
365    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
366        formatter
367            .debug_struct("ExecutableEngineRegistrationConfig")
368            .field("metadata", &self.metadata)
369            .field("backend_type", &std::any::type_name::<B>())
370            .field("event_domain_driver_present", &true)
371            .field("ingress_present", &true)
372            .field("cache_owner_present", &self.cache_owner.is_some())
373            .finish_non_exhaustive()
374    }
375}
376
377impl<B> ExecutableEngineRegistrationConfig<B> {
378    /// Construct an executable registration descriptor.
379    pub fn new(
380        metadata: EngineRegistrationMetadata,
381        backend: B,
382        event_domain_driver: Arc<dyn EventDomainDriver>,
383        ingress: InputIngressContract,
384        cache_owner: Option<Arc<dyn RuntimeCacheOwner>>,
385    ) -> Self {
386        Self {
387            metadata,
388            backend,
389            event_domain_driver,
390            ingress,
391            cache_owner,
392        }
393    }
394}
395
396/// Typed configuration for assembling a preparation-only engine registration.
397///
398/// Preparation-only registrations retain the provider's execution-context
399/// identity but intentionally contain no executable backend or event driver.
400///
401/// # Examples
402///
403/// ```
404/// use std::sync::Arc;
405///
406/// use tenferro_runtime::{
407///     CoreCapabilityBundle, EngineId, EngineRegistrationMetadata,
408///     ExecutionContextIdentity, HardwareClassId, PreparationOnlyEngineRegistrationConfig,
409///     ProviderDeviceIdentity, ProviderId, StorageClass,
410/// };
411///
412/// let engine_id = EngineId::new("example.engine.v1")?;
413/// let provider = ProviderDeviceIdentity::new(
414///     ProviderId::new("example.provider")?,
415///     "device:0",
416/// )?;
417/// let hardware = HardwareClassId::new("example.hardware.v1")?;
418/// let storage = StorageClass::new("example.storage.v1")?;
419/// let metadata = EngineRegistrationMetadata::new(
420///     engine_id,
421///     provider,
422///     hardware,
423///     Arc::from([storage.clone()]),
424///     storage,
425///     CoreCapabilityBundle::default(),
426/// );
427/// let config = PreparationOnlyEngineRegistrationConfig::new(
428///     metadata,
429///     ExecutionContextIdentity::of::<()>(),
430/// );
431/// let _ = config;
432/// # Ok::<(), Box<dyn std::error::Error>>(())
433/// ```
434#[derive(Debug)]
435pub struct PreparationOnlyEngineRegistrationConfig {
436    metadata: EngineRegistrationMetadata,
437    context_identity: ExecutionContextIdentity,
438}
439
440impl PreparationOnlyEngineRegistrationConfig {
441    /// Construct a preparation-only registration descriptor.
442    pub fn new(
443        metadata: EngineRegistrationMetadata,
444        context_identity: ExecutionContextIdentity,
445    ) -> Self {
446        Self {
447            metadata,
448            context_identity,
449        }
450    }
451}
452
453/// The complete execution witness stored by an executable provider binding.
454#[derive(Clone)]
455pub(crate) struct ExecutableEngineContract {
456    provider_device_identity: ProviderDeviceIdentity,
457    context_identity: ExecutionContextIdentity,
458    capabilities: CoreCapabilityBundle,
459    executor: Arc<dyn execution::ErasedTensorBackendExecutor>,
460    event_domain_driver: Arc<dyn EventDomainDriver>,
461    ingress: InputIngressContract,
462    cache_owner: Option<Arc<dyn RuntimeCacheOwner>>,
463}
464
465impl ExecutableEngineContract {
466    // This constructor is intentionally limited to the runtime assembly
467    // boundary. Providers use assemble_executable_engine_registration instead
468    // of manufacturing a partial contract and binding independently.
469    pub(super) fn new<B>(
470        provider_device_identity: ProviderDeviceIdentity,
471        capabilities: CoreCapabilityBundle,
472        backend: B,
473        event_domain_driver: Arc<dyn EventDomainDriver>,
474        ingress: InputIngressContract,
475        cache_owner: Option<Arc<dyn RuntimeCacheOwner>>,
476    ) -> Self
477    where
478        B: TensorBackend + Send + Sync + 'static,
479    {
480        Self {
481            provider_device_identity,
482            context_identity: ExecutionContextIdentity::of::<B>(),
483            capabilities,
484            executor: execution::erased_tensor_backend_executor(backend),
485            event_domain_driver,
486            ingress,
487            cache_owner,
488        }
489    }
490
491    #[cfg(test)]
492    pub(super) fn from_erased_for_test(
493        provider_device_identity: ProviderDeviceIdentity,
494        context_identity: ExecutionContextIdentity,
495        capabilities: CoreCapabilityBundle,
496        executor: Arc<dyn execution::ErasedTensorBackendExecutor>,
497        event_domain_driver: Arc<dyn EventDomainDriver>,
498        ingress: InputIngressContract,
499        cache_owner: Option<Arc<dyn RuntimeCacheOwner>>,
500    ) -> Self {
501        Self {
502            provider_device_identity,
503            context_identity,
504            capabilities,
505            executor,
506            event_domain_driver,
507            ingress,
508            cache_owner,
509        }
510    }
511
512    pub(super) fn capabilities(&self) -> &CoreCapabilityBundle {
513        &self.capabilities
514    }
515
516    pub(super) fn provider_device_identity(&self) -> &ProviderDeviceIdentity {
517        &self.provider_device_identity
518    }
519
520    pub(super) fn context_identity(&self) -> ExecutionContextIdentity {
521        self.context_identity
522    }
523
524    pub(super) fn executor(&self) -> &Arc<dyn execution::ErasedTensorBackendExecutor> {
525        &self.executor
526    }
527
528    pub(super) fn event_domain_driver(&self) -> &Arc<dyn EventDomainDriver> {
529        &self.event_domain_driver
530    }
531
532    pub(super) fn cache_owner(&self) -> Option<&Arc<dyn RuntimeCacheOwner>> {
533        self.cache_owner.as_ref()
534    }
535
536    pub(super) fn accepts_input_placement(
537        &self,
538        placement: &Placement,
539        storage_class: &StorageClass,
540    ) -> bool {
541        self.ingress.accepts_placement(placement, storage_class)
542    }
543
544    pub(super) fn accepts_input_signature(
545        &self,
546        input: &InputSignatureEntry,
547        storage_class: &StorageClass,
548    ) -> bool {
549        self.ingress.accepts_signature(input, storage_class)
550    }
551
552    pub(super) fn accepts_runtime_input(
553        &self,
554        input: &TensorRead<'_>,
555        storage_class: &StorageClass,
556    ) -> bool {
557        self.ingress.accepts_runtime_input(input, storage_class)
558    }
559
560    pub(super) fn owns_resident_tensor(
561        &self,
562        input: &TensorRead<'_>,
563        storage_class: &StorageClass,
564    ) -> bool {
565        self.ingress.owns_resident_output(input, storage_class)
566    }
567}
568
569impl fmt::Debug for ExecutableEngineContract {
570    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
571        formatter
572            .debug_struct("ExecutableEngineContract")
573            .field("provider_device_identity", &self.provider_device_identity)
574            .field("context_identity", &self.context_identity)
575            .field("capabilities", &self.capabilities)
576            .field("executor", &self.executor.backend_type_name())
577            .field("event_domain_driver", &self.event_domain_driver)
578            .field("ingress", &self.ingress)
579            .field("cache_owner", &self.cache_owner.is_some())
580            .finish_non_exhaustive()
581    }
582}
583
584/// Provider-owned metadata plus one complete executable witness.
585pub(crate) struct ProviderExecutableBinding {
586    engine_id: EngineId,
587    hardware_class: HardwareClassId,
588    storage_classes: Arc<[StorageClass]>,
589    default_storage_class: StorageClass,
590    contract: ExecutableEngineContract,
591}
592
593impl ProviderExecutableBinding {
594    // Storage metadata is validated as part of the runtime-owned executable
595    // assembly; this partial constructor is not a public provider API.
596    pub(super) fn new(
597        engine_id: EngineId,
598        hardware_class: HardwareClassId,
599        storage_classes: Arc<[StorageClass]>,
600        default_storage_class: StorageClass,
601        contract: ExecutableEngineContract,
602    ) -> Result<Self, RuntimeConfigError> {
603        validate_storage_classes(&engine_id, &storage_classes, &default_storage_class)?;
604        Ok(Self {
605            engine_id,
606            hardware_class,
607            storage_classes,
608            default_storage_class,
609            contract,
610        })
611    }
612
613    pub(super) fn engine_id(&self) -> &EngineId {
614        &self.engine_id
615    }
616
617    pub(super) fn hardware_class(&self) -> &HardwareClassId {
618        &self.hardware_class
619    }
620
621    pub(super) fn storage_classes(&self) -> &[StorageClass] {
622        &self.storage_classes
623    }
624
625    pub(super) fn default_storage_class(&self) -> &StorageClass {
626        &self.default_storage_class
627    }
628
629    pub(super) fn contract(&self) -> &ExecutableEngineContract {
630        &self.contract
631    }
632}
633
634impl fmt::Debug for ProviderExecutableBinding {
635    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
636        formatter
637            .debug_struct("ProviderExecutableBinding")
638            .field("engine_id", &self.engine_id)
639            .field("hardware_class", &self.hardware_class)
640            .field("storage_class_count", &self.storage_classes.len())
641            .field("contract", &self.contract)
642            .finish_non_exhaustive()
643    }
644}
645
646impl Clone for ProviderExecutableBinding {
647    fn clone(&self) -> Self {
648        Self {
649            engine_id: self.engine_id.clone(),
650            hardware_class: self.hardware_class.clone(),
651            storage_classes: Arc::clone(&self.storage_classes),
652            default_storage_class: self.default_storage_class.clone(),
653            contract: self.contract.clone(),
654        }
655    }
656}
657
658/// Provider-owned metadata plus preparation capabilities without execution.
659pub(crate) struct ProviderPreparationBinding {
660    engine_id: EngineId,
661    provider_device_identity: ProviderDeviceIdentity,
662    context_identity: ExecutionContextIdentity,
663    hardware_class: HardwareClassId,
664    storage_classes: Arc<[StorageClass]>,
665    default_storage_class: StorageClass,
666    capabilities: CoreCapabilityBundle,
667}
668
669impl ProviderPreparationBinding {
670    // Preparation-only metadata follows the same runtime-owned assembly
671    // boundary and cannot be promoted to an executable binding.
672    pub(super) fn new(
673        engine_id: EngineId,
674        provider_device_identity: ProviderDeviceIdentity,
675        context_identity: ExecutionContextIdentity,
676        hardware_class: HardwareClassId,
677        storage_classes: Arc<[StorageClass]>,
678        default_storage_class: StorageClass,
679        capabilities: CoreCapabilityBundle,
680    ) -> Result<Self, RuntimeConfigError> {
681        validate_storage_classes(&engine_id, &storage_classes, &default_storage_class)?;
682        Ok(Self {
683            engine_id,
684            provider_device_identity,
685            context_identity,
686            hardware_class,
687            storage_classes,
688            default_storage_class,
689            capabilities,
690        })
691    }
692
693    pub(super) fn engine_id(&self) -> &EngineId {
694        &self.engine_id
695    }
696
697    pub(super) fn provider_device_identity(&self) -> &ProviderDeviceIdentity {
698        &self.provider_device_identity
699    }
700
701    pub(super) fn context_identity(&self) -> ExecutionContextIdentity {
702        self.context_identity
703    }
704
705    pub(super) fn hardware_class(&self) -> &HardwareClassId {
706        &self.hardware_class
707    }
708
709    pub(super) fn storage_classes(&self) -> &[StorageClass] {
710        &self.storage_classes
711    }
712
713    pub(super) fn default_storage_class(&self) -> &StorageClass {
714        &self.default_storage_class
715    }
716
717    pub(super) fn capabilities(&self) -> &CoreCapabilityBundle {
718        &self.capabilities
719    }
720}
721
722impl Clone for ProviderPreparationBinding {
723    fn clone(&self) -> Self {
724        Self {
725            engine_id: self.engine_id.clone(),
726            provider_device_identity: self.provider_device_identity.clone(),
727            context_identity: self.context_identity,
728            hardware_class: self.hardware_class.clone(),
729            storage_classes: Arc::clone(&self.storage_classes),
730            default_storage_class: self.default_storage_class.clone(),
731            capabilities: self.capabilities.clone(),
732        }
733    }
734}
735
736impl fmt::Debug for ProviderPreparationBinding {
737    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
738        formatter
739            .debug_struct("ProviderPreparationBinding")
740            .field("engine_id", &self.engine_id)
741            .field("provider_device_identity", &self.provider_device_identity)
742            .field("context_identity", &self.context_identity)
743            .field("hardware_class", &self.hardware_class)
744            .field("storage_class_count", &self.storage_classes.len())
745            .field("capabilities", &self.capabilities)
746            .finish_non_exhaustive()
747    }
748}
749
750/// Mutually exclusive preparation-only or executable registration state.
751#[derive(Clone, Debug)]
752pub(crate) enum EngineRegistrationState {
753    /// Capabilities are available to planning, but this registration cannot
754    /// admit runtime inputs or execute a schedule.
755    PreparationOnly { binding: ProviderPreparationBinding },
756    /// A complete provider execution witness.
757    Executable(ProviderExecutableBinding),
758}
759
760impl EngineRegistrationState {
761    pub(super) fn capabilities(&self) -> &CoreCapabilityBundle {
762        match self {
763            Self::PreparationOnly { binding } => binding.capabilities(),
764            Self::Executable(binding) => binding.contract().capabilities(),
765        }
766    }
767
768    pub(super) fn engine_id(&self) -> &EngineId {
769        match self {
770            Self::PreparationOnly { binding } => binding.engine_id(),
771            Self::Executable(binding) => binding.engine_id(),
772        }
773    }
774
775    pub(super) fn provider_device_identity(&self) -> &ProviderDeviceIdentity {
776        match self {
777            Self::PreparationOnly { binding } => binding.provider_device_identity(),
778            Self::Executable(binding) => binding.contract().provider_device_identity(),
779        }
780    }
781
782    pub(super) fn context_identity(&self) -> ExecutionContextIdentity {
783        match self {
784            Self::PreparationOnly { binding } => binding.context_identity(),
785            Self::Executable(binding) => binding.contract().context_identity(),
786        }
787    }
788
789    pub(super) fn hardware_class(&self) -> &HardwareClassId {
790        match self {
791            Self::PreparationOnly { binding } => binding.hardware_class(),
792            Self::Executable(binding) => binding.hardware_class(),
793        }
794    }
795
796    pub(super) fn storage_classes(&self) -> &[StorageClass] {
797        match self {
798            Self::PreparationOnly { binding } => binding.storage_classes(),
799            Self::Executable(binding) => binding.storage_classes(),
800        }
801    }
802
803    pub(super) fn default_storage_class(&self) -> &StorageClass {
804        match self {
805            Self::PreparationOnly { binding } => binding.default_storage_class(),
806            Self::Executable(binding) => binding.default_storage_class(),
807        }
808    }
809}
810
811/// Immutable direct engine registration candidate.
812#[derive(Clone)]
813pub struct EngineRegistration {
814    state: EngineRegistrationState,
815    candidate_token: Arc<CandidateRegistrationToken>,
816}
817
818impl EngineRegistration {
819    /// Consume one provider-owned preparation binding.
820    pub(super) fn preparation_only(binding: ProviderPreparationBinding) -> Self {
821        Self {
822            state: EngineRegistrationState::PreparationOnly { binding },
823            candidate_token: Arc::new(CandidateRegistrationToken),
824        }
825    }
826
827    /// Consume one provider-owned complete executable binding.
828    pub(super) fn executable(binding: ProviderExecutableBinding) -> Self {
829        Self {
830            state: EngineRegistrationState::Executable(binding),
831            candidate_token: Arc::new(CandidateRegistrationToken),
832        }
833    }
834
835    pub(super) fn from_state(state: EngineRegistrationState) -> Self {
836        Self {
837            state,
838            candidate_token: Arc::new(CandidateRegistrationToken),
839        }
840    }
841
842    /// Return the immutable registration state witness.
843    #[cfg(test)]
844    pub(crate) fn execution_state(&self) -> &EngineRegistrationState {
845        &self.state
846    }
847
848    /// Return the engine identifier.
849    pub fn engine_id(&self) -> &EngineId {
850        self.state.engine_id()
851    }
852
853    /// Return the immutable provider/device binding for this engine.
854    pub fn provider_device_identity(&self) -> &ProviderDeviceIdentity {
855        self.state.provider_device_identity()
856    }
857
858    /// Return the execution-context type identity accepted by the engine.
859    pub fn context_identity(&self) -> ExecutionContextIdentity {
860        self.state.context_identity()
861    }
862
863    /// Return the hardware class exposed by this engine.
864    pub fn hardware_class(&self) -> &HardwareClassId {
865        self.state.hardware_class()
866    }
867
868    /// Return the supported storage classes in registration order.
869    pub fn storage_classes(&self) -> &[StorageClass] {
870        self.state.storage_classes()
871    }
872
873    pub(super) fn with_candidate_token(
874        mut self,
875        candidate_token: Arc<CandidateRegistrationToken>,
876    ) -> Self {
877        self.candidate_token = candidate_token;
878        self
879    }
880
881    pub(super) fn into_state_and_token(
882        self,
883    ) -> (EngineRegistrationState, Arc<CandidateRegistrationToken>) {
884        (self.state, self.candidate_token)
885    }
886
887    /// Return the default storage class.
888    pub fn default_storage_class(&self) -> &StorageClass {
889        self.state.default_storage_class()
890    }
891
892    /// Return direct core capability slots.
893    pub fn capabilities(&self) -> &CoreCapabilityBundle {
894        self.state.capabilities()
895    }
896
897    pub(super) fn candidate_identical(&self, other: &Self) -> bool {
898        self.engine_id() == other.engine_id()
899            && Arc::ptr_eq(&self.candidate_token, &other.candidate_token)
900    }
901}
902
903/// Assemble one complete executable provider registration.
904///
905/// Provider adapters supply only provider-owned identity, capability, ingress,
906/// event-driver, and cache-owner values.  The runtime owns the assembly of the
907/// executable witness and its storage metadata so every executable engine
908/// enters the runtime through the same invariant-preserving path.
909///
910/// # Errors
911///
912/// Returns [`RuntimeConfigError`] if the descriptor contains empty or duplicate
913/// storage classes, or if its default storage class is not listed in the
914/// supported storage classes.
915pub fn assemble_executable_engine_registration<B>(
916    config: ExecutableEngineRegistrationConfig<B>,
917) -> Result<EngineRegistration, RuntimeConfigError>
918where
919    B: TensorBackend + Send + Sync + 'static,
920{
921    let ExecutableEngineRegistrationConfig {
922        metadata:
923            EngineRegistrationMetadata {
924                engine_id,
925                provider_device_identity,
926                hardware_class,
927                storage_classes,
928                default_storage_class,
929                capabilities,
930            },
931        backend,
932        event_domain_driver,
933        ingress,
934        cache_owner,
935    } = config;
936    let contract = ExecutableEngineContract::new(
937        provider_device_identity,
938        capabilities,
939        backend,
940        event_domain_driver,
941        ingress,
942        cache_owner,
943    );
944    let binding = ProviderExecutableBinding::new(
945        engine_id,
946        hardware_class,
947        storage_classes,
948        default_storage_class,
949        contract,
950    )?;
951    Ok(EngineRegistration::executable(binding))
952}
953
954/// Assemble one preparation-only provider registration.
955///
956/// This is the preparation counterpart of
957/// [`assemble_executable_engine_registration`].  It deliberately cannot
958/// manufacture an execution bridge or a scheduled witness.
959///
960/// # Errors
961///
962/// Returns [`RuntimeConfigError`] if the descriptor contains empty or duplicate
963/// storage classes, or if its default storage class is not listed in the
964/// supported storage classes.
965pub fn assemble_preparation_only_engine_registration(
966    config: PreparationOnlyEngineRegistrationConfig,
967) -> Result<EngineRegistration, RuntimeConfigError> {
968    let PreparationOnlyEngineRegistrationConfig {
969        metadata:
970            EngineRegistrationMetadata {
971                engine_id,
972                provider_device_identity,
973                hardware_class,
974                storage_classes,
975                default_storage_class,
976                capabilities,
977            },
978        context_identity,
979    } = config;
980    let binding = ProviderPreparationBinding::new(
981        engine_id,
982        provider_device_identity,
983        context_identity,
984        hardware_class,
985        storage_classes,
986        default_storage_class,
987        capabilities,
988    )?;
989    Ok(EngineRegistration::preparation_only(binding))
990}
991
992impl fmt::Debug for EngineRegistration {
993    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
994        formatter
995            .debug_struct("EngineRegistration")
996            .field("engine_id", self.engine_id())
997            .field("provider_device_identity", self.provider_device_identity())
998            .field("context_identity", &self.context_identity())
999            .field("hardware_class", self.hardware_class())
1000            .field("storage_class_count", &self.storage_classes().len())
1001            .field("default_storage_class", self.default_storage_class())
1002            .field("state", &self.state)
1003            .finish_non_exhaustive()
1004    }
1005}
1006
1007fn validate_storage_classes(
1008    engine_id: &EngineId,
1009    storage_classes: &[StorageClass],
1010    default_storage_class: &StorageClass,
1011) -> Result<(), RuntimeConfigError> {
1012    if storage_classes.is_empty() {
1013        return Err(RuntimeConfigError::EmptyStorageClasses {
1014            engine_id: engine_id.clone(),
1015        });
1016    }
1017    for duplicate_index in 0..storage_classes.len() {
1018        if let Some(first_index) = (0..duplicate_index)
1019            .find(|&first| storage_classes[first] == storage_classes[duplicate_index])
1020        {
1021            return Err(RuntimeConfigError::DuplicateStorageClass {
1022                engine_id: engine_id.clone(),
1023                storage_class: storage_classes[duplicate_index].clone(),
1024                first_index,
1025                duplicate_index,
1026            });
1027        }
1028    }
1029    if !storage_classes
1030        .iter()
1031        .any(|storage_class| storage_class == default_storage_class)
1032    {
1033        return Err(RuntimeConfigError::DefaultStorageClassNotListed {
1034            engine_id: engine_id.clone(),
1035            default_storage_class: default_storage_class.clone(),
1036        });
1037    }
1038    Ok(())
1039}