Skip to main content

tenferro_runtime/runtime/
engine_registration.rs

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