Skip to main content

tenferro_runtime/runtime/
snapshot.rs

1use std::collections::BTreeMap;
2use std::fmt;
3use std::num::NonZeroU64;
4use std::sync::atomic::{AtomicU64, Ordering};
5use std::sync::{Arc, RwLock};
6
7use tenferro_tensor::{Tensor, TensorValue};
8
9use crate::graph::CompiledGraph;
10use crate::program::FrozenProgram;
11
12use super::cache::{PreparedPlanCacheLimits, RuntimeCacheSet};
13use super::cache_owner::{FrozenCacheOwner, FrozenCacheOwnerKind};
14use super::engine_registration::{CandidateRegistrationToken, EngineRegistrationState};
15use super::execution;
16#[cfg(test)]
17use super::extension::ExtensionSlotFullForTest;
18use super::extension::{
19    bind_candidate_module, configure_module, freeze_extension_slots, BoundCandidateModuleRecord,
20    CandidateModuleRecord, CandidateRegistrationIdentity, ExtensionEngineSnapshotView,
21    ExtensionFamilyId, FrozenExtensionSlots,
22};
23use super::preparation::{PreparedEntryKey, PreparedProgram, PreparedProgramResult};
24use super::schedule::EventDomainId;
25use super::{
26    CacheOwnerId, CoreCapabilityBundle, EngineId, EngineRegistration, ExecutionContextIdentity,
27    ExecutionPolicy, ExtensionModule, ExtensionModuleError, ExtensionModuleId,
28    FrozenTransferRegistry, HardwareClassId, InputSignature, PrepareOptions,
29    ProviderDeviceIdentity, RegistrationIdentity, RegistrationKey, ResolvedTransferEndpoint,
30    ResolvedTransferRoute, RuntimeCacheError, RuntimeCacheStats, RuntimeConfigError, RuntimeEpoch,
31    RuntimeId, RuntimeReconfigureError, RuntimeStateError, StorageClass, TransferEndpoint,
32    TransferProvider, TransferRoute,
33};
34
35static NEXT_RUNTIME_ID: AtomicU64 = AtomicU64::new(1);
36static NEXT_REGISTRATION_ISSUER: AtomicU64 = AtomicU64::new(1);
37const INITIAL_REGISTRATION_ORDINAL: NonZeroU64 = NonZeroU64::MIN;
38
39#[derive(Clone, Debug)]
40struct CandidateEngineRecord {
41    registration: EngineRegistration,
42    identity: CandidateRegistrationIdentity,
43}
44
45#[derive(Clone, Debug)]
46struct BoundCandidateEngineRecord {
47    registration: EngineRegistration,
48    identity: RegistrationIdentity,
49}
50
51#[derive(Clone, Debug)]
52enum CandidateTransferBinding {
53    /// A route registered before its complete candidate can be validated.
54    New,
55    /// A route carried forward from a frozen snapshot.
56    Preserved {
57        source: ProviderDeviceIdentity,
58        destination: ProviderDeviceIdentity,
59    },
60}
61
62#[derive(Clone, Debug)]
63struct CandidateTransferRecord {
64    provider: Arc<dyn TransferProvider>,
65    binding: CandidateTransferBinding,
66}
67
68struct BoundCandidateTransferRecord {
69    provider: Arc<dyn TransferProvider>,
70    source: ProviderDeviceIdentity,
71    destination: ProviderDeviceIdentity,
72}
73
74#[derive(Clone, Debug)]
75struct CandidateConfig {
76    policy: ExecutionPolicy,
77    engines: BTreeMap<EngineId, CandidateEngineRecord>,
78    modules: BTreeMap<ExtensionModuleId, CandidateModuleRecord>,
79    transfers: BTreeMap<TransferRoute, CandidateTransferRecord>,
80}
81
82struct BoundCandidateConfig {
83    policy: ExecutionPolicy,
84    engines: BTreeMap<EngineId, BoundCandidateEngineRecord>,
85    modules: BTreeMap<ExtensionModuleId, BoundCandidateModuleRecord>,
86    transfers: BTreeMap<TransferRoute, BoundCandidateTransferRecord>,
87}
88
89impl CandidateConfig {
90    fn empty() -> Self {
91        Self {
92            policy: default_execution_policy(),
93            engines: BTreeMap::new(),
94            modules: BTreeMap::new(),
95            transfers: BTreeMap::new(),
96        }
97    }
98
99    fn from_snapshot(snapshot: &RuntimeConfigSnapshot) -> Result<Self, RuntimeConfigError> {
100        let engines = snapshot
101            .engines
102            .iter()
103            .map(|slot| {
104                let registration = slot.to_registration()?;
105                Ok((
106                    registration.engine_id().clone(),
107                    CandidateEngineRecord {
108                        registration,
109                        identity: CandidateRegistrationIdentity::Preserved(
110                            slot.metadata().identity,
111                        ),
112                    },
113                ))
114            })
115            .collect::<Result<BTreeMap<_, _>, RuntimeConfigError>>()?;
116        Ok(Self {
117            policy: snapshot.policy.clone(),
118            engines,
119            modules: snapshot.extensions.to_candidate_modules(),
120            transfers: snapshot
121                .transfers
122                .iter()
123                .map(|(resolved_route, provider)| {
124                    (
125                        TransferRoute::new(
126                            resolved_route.source().logical().clone(),
127                            resolved_route.destination().logical().clone(),
128                        ),
129                        CandidateTransferRecord {
130                            provider: Arc::clone(provider),
131                            binding: CandidateTransferBinding::Preserved {
132                                source: resolved_route.source().provider_device_identity().clone(),
133                                destination: resolved_route
134                                    .destination()
135                                    .provider_device_identity()
136                                    .clone(),
137                            },
138                        },
139                    )
140                })
141                .collect(),
142        })
143    }
144}
145
146#[derive(Clone, Debug)]
147struct FrozenEngineMetadata {
148    candidate_token: Arc<CandidateRegistrationToken>,
149    identity: RegistrationIdentity,
150    event_domain_id: EventDomainId,
151}
152
153#[derive(Clone)]
154struct PreparationOnlyEngineSnapshot {
155    metadata: FrozenEngineMetadata,
156    binding: super::ProviderPreparationBinding,
157}
158
159#[derive(Clone, Debug)]
160pub(super) struct ExecutableEngineSnapshot {
161    metadata: FrozenEngineMetadata,
162    binding: super::ProviderExecutableBinding,
163}
164
165#[derive(Clone)]
166enum FrozenEngineSlot {
167    PreparationOnly(Arc<PreparationOnlyEngineSnapshot>),
168    Executable(Arc<ExecutableEngineSnapshot>),
169}
170
171impl FrozenEngineSlot {
172    fn metadata(&self) -> &FrozenEngineMetadata {
173        match self {
174            Self::PreparationOnly(snapshot) => &snapshot.metadata,
175            Self::Executable(snapshot) => &snapshot.metadata,
176        }
177    }
178
179    fn provider_device_identity(&self) -> &ProviderDeviceIdentity {
180        match self {
181            Self::PreparationOnly(snapshot) => snapshot.binding.provider_device_identity(),
182            Self::Executable(snapshot) => snapshot.binding.contract().provider_device_identity(),
183        }
184    }
185
186    fn engine_id(&self) -> &EngineId {
187        match self {
188            Self::PreparationOnly(snapshot) => snapshot.binding.engine_id(),
189            Self::Executable(snapshot) => snapshot.binding.engine_id(),
190        }
191    }
192
193    fn hardware_class(&self) -> &HardwareClassId {
194        match self {
195            Self::PreparationOnly(snapshot) => snapshot.binding.hardware_class(),
196            Self::Executable(snapshot) => snapshot.binding.hardware_class(),
197        }
198    }
199
200    fn storage_classes(&self) -> &[StorageClass] {
201        match self {
202            Self::PreparationOnly(snapshot) => snapshot.binding.storage_classes(),
203            Self::Executable(snapshot) => snapshot.binding.storage_classes(),
204        }
205    }
206
207    fn default_storage_class(&self) -> &StorageClass {
208        match self {
209            Self::PreparationOnly(snapshot) => snapshot.binding.default_storage_class(),
210            Self::Executable(snapshot) => snapshot.binding.default_storage_class(),
211        }
212    }
213
214    fn context_identity(&self) -> ExecutionContextIdentity {
215        match self {
216            Self::PreparationOnly(snapshot) => snapshot.binding.context_identity(),
217            Self::Executable(snapshot) => snapshot.binding.contract().context_identity(),
218        }
219    }
220
221    fn capabilities(&self) -> &CoreCapabilityBundle {
222        match self {
223            Self::PreparationOnly(snapshot) => snapshot.binding.capabilities(),
224            Self::Executable(snapshot) => snapshot.binding.contract().capabilities(),
225        }
226    }
227
228    fn executable(&self) -> Option<&Arc<ExecutableEngineSnapshot>> {
229        match self {
230            Self::PreparationOnly(_) => None,
231            Self::Executable(snapshot) => Some(snapshot),
232        }
233    }
234}
235
236impl ExecutableEngineSnapshot {
237    pub(super) fn engine_id(&self) -> &EngineId {
238        self.binding.engine_id()
239    }
240
241    pub(super) fn event_domain_id(&self) -> EventDomainId {
242        self.metadata.event_domain_id
243    }
244
245    pub(super) fn provider_device_identity(&self) -> &ProviderDeviceIdentity {
246        self.binding.contract().provider_device_identity()
247    }
248
249    #[cfg(test)]
250    pub(super) fn context_identity(&self) -> ExecutionContextIdentity {
251        self.binding.contract().context_identity()
252    }
253
254    pub(super) fn executor(&self) -> &Arc<dyn super::execution::ErasedTensorBackendExecutor> {
255        self.binding.contract().executor()
256    }
257
258    pub(super) fn event_domain_driver(&self) -> &Arc<dyn super::EventDomainDriver> {
259        self.binding.contract().event_domain_driver()
260    }
261
262    #[cfg(test)]
263    pub(super) fn has_executor(&self) -> bool {
264        true
265    }
266
267    #[cfg(test)]
268    pub(super) fn has_event_domain_driver(&self) -> bool {
269        true
270    }
271
272    pub(super) fn accepts_input_placement(
273        &self,
274        placement: &tenferro_tensor::Placement,
275        storage_class: &StorageClass,
276    ) -> bool {
277        self.binding.storage_classes().contains(storage_class)
278            && self
279                .binding
280                .contract()
281                .accepts_input_placement(placement, storage_class)
282    }
283
284    pub(super) fn accepts_input_signature(
285        &self,
286        input: &super::InputSignatureEntry,
287        storage_class: &StorageClass,
288    ) -> bool {
289        self.binding.storage_classes().contains(storage_class)
290            && self
291                .binding
292                .contract()
293                .accepts_input_signature(input, storage_class)
294    }
295
296    pub(super) fn accepts_runtime_input(
297        &self,
298        input: &tenferro_tensor::TensorRead<'_>,
299        storage_class: &StorageClass,
300    ) -> bool {
301        self.binding.storage_classes().contains(storage_class)
302            && self
303                .binding
304                .contract()
305                .accepts_runtime_input(input, storage_class)
306    }
307
308    pub(super) fn owns_resident_tensor(
309        &self,
310        input: &tenferro_tensor::TensorRead<'_>,
311        storage_class: &StorageClass,
312    ) -> bool {
313        self.binding.storage_classes().contains(storage_class)
314            && self
315                .binding
316                .contract()
317                .owns_resident_tensor(input, storage_class)
318    }
319
320    #[cfg(test)]
321    pub(super) fn for_test(
322        engine_id: EngineId,
323        provider_device_identity: ProviderDeviceIdentity,
324        event_domain_id: EventDomainId,
325        storage_class: StorageClass,
326    ) -> Arc<Self> {
327        Self::for_test_with_driver(
328            engine_id,
329            provider_device_identity,
330            event_domain_id,
331            storage_class,
332            Arc::new(super::ImmediateEventDomainDriver::new()),
333        )
334    }
335
336    #[cfg(test)]
337    pub(super) fn for_test_with_driver(
338        engine_id: EngineId,
339        provider_device_identity: ProviderDeviceIdentity,
340        event_domain_id: EventDomainId,
341        storage_class: StorageClass,
342        event_domain_driver: Arc<dyn super::EventDomainDriver>,
343    ) -> Arc<Self> {
344        let ingress = super::InputIngressContract::new(
345            super::InputPlacementContract::new(|_, _| true),
346            super::InputSignatureContract::new(|_, _, _, _| true),
347            super::RuntimeInputContract::new(|_, _| true),
348            super::ResidentOutputContract::new(|_, _| true),
349        );
350        let contract = super::ExecutableEngineContract::new(
351            provider_device_identity,
352            CoreCapabilityBundle::default(),
353            tenferro_cpu::CpuBackend::new(),
354            event_domain_driver,
355            ingress,
356            None,
357        );
358        let binding = super::ProviderExecutableBinding::new(
359            engine_id,
360            HardwareClassId::new("tenferro.test.schedule.hardware").expect("test hardware class"),
361            Arc::from(vec![storage_class.clone()]),
362            storage_class,
363            contract,
364        )
365        .expect("test executable binding");
366        Arc::new(Self {
367            metadata: FrozenEngineMetadata {
368                candidate_token: Arc::new(CandidateRegistrationToken),
369                identity: event_domain_id.registration_identity(),
370                event_domain_id,
371            },
372            binding,
373        })
374    }
375}
376
377impl FrozenEngineSlot {
378    fn to_registration(&self) -> Result<EngineRegistration, RuntimeConfigError> {
379        let metadata = self.metadata();
380        let registration = match self {
381            Self::PreparationOnly(snapshot) => {
382                EngineRegistration::from_state(EngineRegistrationState::PreparationOnly {
383                    binding: snapshot.binding.clone(),
384                })
385            }
386            Self::Executable(snapshot) => EngineRegistration::from_state(
387                EngineRegistrationState::Executable(snapshot.binding.clone()),
388            ),
389        };
390        Ok(registration.with_candidate_token(Arc::clone(&metadata.candidate_token)))
391    }
392}
393
394impl fmt::Debug for FrozenEngineSlot {
395    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
396        let metadata = self.metadata();
397        formatter
398            .debug_struct("FrozenEngineSlot")
399            .field("engine_id", self.engine_id())
400            .field("registration_identity", &metadata.identity)
401            .field("event_domain_id", &metadata.event_domain_id)
402            .field("context_identity", &self.context_identity())
403            .field("hardware_class", self.hardware_class())
404            .field(
405                "state",
406                &match self {
407                    Self::PreparationOnly(_) => "preparation-only",
408                    Self::Executable(_) => "executable",
409                },
410            )
411            .finish_non_exhaustive()
412    }
413}
414
415/// Immutable runtime configuration snapshot.
416///
417/// A snapshot is readable after later reconfiguration because the runtime
418/// publishes a new `Arc` instead of mutating existing slots.
419///
420/// # Examples
421///
422/// ```
423/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
424/// use tenferro_runtime::RuntimeConfigBuilder;
425///
426/// let runtime = RuntimeConfigBuilder::new().build()?;
427/// let snapshot = runtime.snapshot()?;
428/// assert_eq!(snapshot.engine_count(), 0);
429/// # Ok(())
430/// # }
431/// ```
432#[derive(Clone)]
433pub struct RuntimeConfigSnapshot {
434    runtime_id: RuntimeId,
435    epoch: RuntimeEpoch,
436    policy: ExecutionPolicy,
437    engines: Arc<[FrozenEngineSlot]>,
438    engine_indices: BTreeMap<EngineId, usize>,
439    extensions: FrozenExtensionSlots,
440    transfers: FrozenTransferRegistry,
441    cache_owners: Arc<[FrozenCacheOwner]>,
442}
443
444impl RuntimeConfigSnapshot {
445    /// Return the runtime identity that published this snapshot.
446    pub fn runtime_id(&self) -> RuntimeId {
447        self.runtime_id
448    }
449
450    /// Return the epoch for this immutable snapshot.
451    pub fn epoch(&self) -> RuntimeEpoch {
452        self.epoch
453    }
454
455    /// Return the execution policy captured by this snapshot.
456    pub fn execution_policy(&self) -> &ExecutionPolicy {
457        &self.policy
458    }
459
460    /// Return the number of direct engine slots.
461    pub fn engine_count(&self) -> usize {
462        self.engines.len()
463    }
464
465    /// Return the number of installed extension modules.
466    pub fn extension_module_count(&self) -> usize {
467        self.extensions.module_count()
468    }
469
470    /// Return the number of registered transfer providers.
471    pub fn transfer_provider_count(&self) -> usize {
472        self.transfers.len()
473    }
474
475    /// Return whether this snapshot contains an extension engine for a family.
476    #[doc(hidden)]
477    pub fn has_extension_family(&self, family_id: &'static str) -> bool {
478        self.extensions.has_family(family_id)
479    }
480
481    /// Return whether this snapshot contains an extension engine for one exact
482    /// family/engine pair.
483    ///
484    /// This narrow, doc-hidden query is the cross-crate eager-extension
485    /// contract used by `tenferro-ad` to validate an owner-selected module
486    /// without exposing module internals or widening [`ExtensionModule`].
487    ///
488    /// # Examples
489    ///
490    /// ```rust
491    /// use tenferro_runtime::{EngineId, Runtime};
492    ///
493    /// let runtime = Runtime::builder().build()?;
494    /// let snapshot = runtime.snapshot()?;
495    /// let engine = EngineId::new("example.engine")?;
496    /// assert!(!snapshot.has_extension_engine("example.family.v1", &engine));
497    /// # Ok::<(), Box<dyn std::error::Error>>(())
498    /// ```
499    #[doc(hidden)]
500    pub fn has_extension_engine(&self, family_id: &'static str, engine_id: &EngineId) -> bool {
501        self.extensions.has_engine(family_id, engine_id)
502    }
503
504    /// Return an immutable view of a registered engine slot.
505    pub fn engine(&self, id: &EngineId) -> Option<EngineSnapshotView<'_>> {
506        self.engine_indices
507            .get(id)
508            .map(|&index| EngineSnapshotView {
509                slot: &self.engines[index],
510            })
511    }
512
513    #[cfg(test)]
514    pub(crate) fn engine_ids_for_test(&self) -> impl Iterator<Item = &EngineId> {
515        self.engines.iter().map(FrozenEngineSlot::engine_id)
516    }
517
518    #[cfg(test)]
519    pub(crate) fn transfer_routes_for_test(&self) -> impl Iterator<Item = &ResolvedTransferRoute> {
520        self.transfers.iter().map(|(route, _)| route)
521    }
522
523    pub(super) fn engine_views_for_preparation(
524        &self,
525    ) -> impl Iterator<Item = EngineSnapshotView<'_>> + '_ {
526        self.engines.iter().map(|slot| EngineSnapshotView { slot })
527    }
528
529    pub(super) fn extension_slot_for_preparation(
530        &self,
531        family_id: ExtensionFamilyId,
532        engine_id: &EngineId,
533    ) -> Option<ExtensionEngineSnapshotView<'_>> {
534        self.extensions.slot_for_preparation(family_id, engine_id)
535    }
536
537    pub(super) fn transfer_registry_for_preparation(&self) -> FrozenTransferRegistry {
538        self.transfers.clone()
539    }
540
541    #[cfg(test)]
542    pub(crate) fn extension_slots_for_test(
543        &self,
544    ) -> impl Iterator<
545        Item = (
546            &ExtensionModuleId,
547            ExtensionFamilyId,
548            &EngineId,
549            RegistrationIdentity,
550        ),
551    > {
552        self.extensions.slots_for_test()
553    }
554
555    #[cfg(test)]
556    pub(crate) fn extension_slot_identity_for_test(
557        &self,
558        family_id: ExtensionFamilyId,
559        engine_id: &EngineId,
560    ) -> Option<RegistrationIdentity> {
561        self.extensions.slot_identity_for_test(family_id, engine_id)
562    }
563
564    #[cfg(test)]
565    pub(crate) fn extension_slot_full_for_test(
566        &self,
567        family_id: ExtensionFamilyId,
568        engine_id: &EngineId,
569    ) -> Option<ExtensionSlotFullForTest<'_>> {
570        self.extensions.slot_full_for_test(family_id, engine_id)
571    }
572
573    #[cfg(test)]
574    pub(super) fn cache_owners_for_test(&self) -> &[FrozenCacheOwner] {
575        &self.cache_owners
576    }
577
578    pub(super) fn cache_owners_for_runtime(&self) -> &[FrozenCacheOwner] {
579        &self.cache_owners
580    }
581}
582
583impl fmt::Debug for RuntimeConfigSnapshot {
584    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
585        formatter
586            .debug_struct("RuntimeConfigSnapshot")
587            .field("runtime_id", &self.runtime_id)
588            .field("epoch", &self.epoch)
589            .field("execution_policy", &self.policy)
590            .field("engine_count", &self.engines.len())
591            .field("extension_module_count", &self.extensions.module_count())
592            .field("extension_engine_count", &self.extensions.engine_count())
593            .field("transfer_provider_count", &self.transfers.len())
594            .field("cache_owner_count", &self.cache_owners.len())
595            .finish_non_exhaustive()
596    }
597}
598
599struct RuntimeState {
600    runtime_id: RuntimeId,
601    issuer: NonZeroU64,
602    next_registration_ordinal: AtomicU64,
603    active: RwLock<Arc<RuntimeConfigSnapshot>>,
604    published_epoch: AtomicU64,
605    caches: RuntimeCacheSet<PreparedEntryKey, PreparedProgram>,
606}
607
608/// Runtime owner for immutable configuration snapshots.
609///
610/// # Examples
611///
612/// ```
613/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
614/// use tenferro_runtime::Runtime;
615///
616/// let runtime = Runtime::builder().build()?;
617/// assert_eq!(runtime.snapshot()?.engine_count(), 0);
618/// # Ok(())
619/// # }
620/// ```
621#[derive(Clone)]
622pub struct Runtime(Arc<RuntimeState>);
623
624impl Runtime {
625    /// Return a consuming runtime configuration builder.
626    pub fn builder() -> RuntimeConfigBuilder {
627        RuntimeConfigBuilder::new()
628    }
629
630    /// Return this runtime's opaque identity.
631    pub fn id(&self) -> RuntimeId {
632        self.0.runtime_id
633    }
634
635    /// Clone the current immutable runtime snapshot.
636    ///
637    /// # Errors
638    ///
639    /// Returns [`RuntimeStateError::Poisoned`] when the active snapshot lock was
640    /// poisoned by another thread.
641    pub fn snapshot(&self) -> Result<Arc<RuntimeConfigSnapshot>, RuntimeStateError> {
642        self.0
643            .active
644            .read()
645            .map(|snapshot| Arc::clone(&snapshot))
646            .map_err(|_| RuntimeStateError::Poisoned {
647                lock: "runtime.active",
648            })
649    }
650
651    /// Return the currently published runtime epoch without locking the active
652    /// snapshot.
653    ///
654    /// # Errors
655    ///
656    /// Returns [`RuntimeStateError`] only if runtime state invariants have been
657    /// violated internally.
658    pub fn epoch(&self) -> Result<RuntimeEpoch, RuntimeStateError> {
659        match NonZeroU64::new(self.0.published_epoch.load(Ordering::Acquire)) {
660            Some(value) => Ok(RuntimeEpoch::from_nonzero(value)),
661            None => Err(RuntimeStateError::Poisoned {
662                lock: "runtime.published_epoch",
663            }),
664        }
665    }
666
667    /// Return current prepared-plan cache limits.
668    ///
669    /// # Examples
670    ///
671    /// ```
672    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
673    /// use tenferro_runtime::Runtime;
674    ///
675    /// let runtime = Runtime::builder().build()?;
676    /// assert_eq!(runtime.prepared_cache_limits()?, Default::default());
677    /// # Ok(())
678    /// # }
679    /// ```
680    ///
681    /// # Errors
682    ///
683    /// Returns [`RuntimeStateError`] when the runtime-owned prepared cache state
684    /// cannot be accessed.
685    pub fn prepared_cache_limits(&self) -> Result<PreparedPlanCacheLimits, RuntimeStateError> {
686        self.0.caches.prepared().limits()
687    }
688
689    /// Replace current prepared-plan cache limits and evict retained entries
690    /// until the new limits are satisfied.
691    ///
692    /// # Examples
693    ///
694    /// ```
695    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
696    /// use std::num::NonZeroUsize;
697    /// use tenferro_runtime::{PreparedPlanCacheLimits, Runtime};
698    ///
699    /// let runtime = Runtime::builder().build()?;
700    /// runtime.set_prepared_cache_limits(PreparedPlanCacheLimits {
701    ///     max_entries: NonZeroUsize::new(1).unwrap(),
702    ///     max_retained_bytes: NonZeroUsize::new(1024).unwrap(),
703    ///     max_in_flight_entries: NonZeroUsize::new(1).unwrap(),
704    ///     max_queued_distinct_keys: NonZeroUsize::new(1).unwrap(),
705    /// })?;
706    /// assert_eq!(runtime.prepared_cache_limits()?.max_entries.get(), 1);
707    /// # Ok(())
708    /// # }
709    /// ```
710    ///
711    /// # Errors
712    ///
713    /// Returns [`RuntimeStateError`] when the runtime-owned prepared cache state
714    /// cannot be accessed.
715    pub fn set_prepared_cache_limits(
716        &self,
717        limits: PreparedPlanCacheLimits,
718    ) -> Result<(), RuntimeStateError> {
719        self.0.caches.prepared().set_limits(limits)
720    }
721
722    /// Clear the runtime-owned prepared-plan cache.
723    ///
724    /// # Examples
725    ///
726    /// ```
727    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
728    /// use tenferro_runtime::Runtime;
729    ///
730    /// let runtime = Runtime::builder().build()?;
731    /// runtime.clear_prepared_cache()?;
732    /// # Ok(())
733    /// # }
734    /// ```
735    ///
736    /// # Errors
737    ///
738    /// Returns [`RuntimeStateError`] when the runtime-owned prepared cache state
739    /// cannot be accessed.
740    pub fn clear_prepared_cache(&self) -> Result<(), RuntimeStateError> {
741        self.0.caches.prepared().clear()
742    }
743
744    /// Return aggregate cache statistics for the runtime and registered cache
745    /// owners.
746    ///
747    /// # Examples
748    ///
749    /// ```
750    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
751    /// use tenferro_runtime::Runtime;
752    ///
753    /// let runtime = Runtime::builder().build()?;
754    /// assert_eq!(runtime.cache_stats()?.prepared_plans.entries, 0);
755    /// # Ok(())
756    /// # }
757    /// ```
758    ///
759    /// # Errors
760    ///
761    /// Returns [`RuntimeCacheError`] when the runtime cache or a registered
762    /// cache owner cannot report statistics.
763    pub fn cache_stats(&self) -> Result<RuntimeCacheStats, RuntimeCacheError> {
764        super::preparation::cache_stats(self, &self.0.caches)
765    }
766
767    /// Clear runtime-owned prepared plans and all registered engine/extension
768    /// cache owners.
769    ///
770    /// # Examples
771    ///
772    /// ```
773    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
774    /// use tenferro_runtime::Runtime;
775    ///
776    /// let runtime = Runtime::builder().build()?;
777    /// runtime.clear_caches()?;
778    /// # Ok(())
779    /// # }
780    /// ```
781    ///
782    /// # Errors
783    ///
784    /// Returns [`RuntimeCacheError`] when the runtime cache or a registered
785    /// cache owner cannot be cleared.
786    pub fn clear_caches(&self) -> Result<(), RuntimeCacheError> {
787        super::preparation::clear_caches(self, &self.0.caches)
788    }
789
790    #[allow(
791        dead_code,
792        reason = "Phase 5 graph execution consumes crate-private prepared programs"
793    )]
794    pub(crate) fn prepare_for(
795        &self,
796        frozen: &FrozenProgram,
797        signature: &InputSignature,
798        options: &PrepareOptions,
799    ) -> PreparedProgramResult<Arc<PreparedProgram>> {
800        super::preparation::prepare_for(self, &self.0.caches, frozen, signature, options)
801    }
802
803    pub(crate) fn prepare_compiled_for(
804        &self,
805        program: &CompiledGraph,
806        signature: &InputSignature,
807        options: &PrepareOptions,
808    ) -> PreparedProgramResult<Arc<PreparedProgram>> {
809        super::preparation::prepare_compiled_for(self, &self.0.caches, program, signature, options)
810    }
811
812    /// Run a compiled graph synchronously with borrowed tensor inputs.
813    ///
814    /// The borrows remain valid until this call returns; this surface never
815    /// detaches work. Asynchronous [`Self::submit`] accepts only the owning
816    /// [`super::execution::ExecutionInputs`] package.
817    ///
818    /// # Examples
819    ///
820    /// ```
821    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
822    /// use tenferro_runtime::{Runtime, TracedTensor, GraphCompiler};
823    ///
824    /// let runtime = Runtime::builder().build()?;
825    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
826    /// let program = GraphCompiler::new().compile(&x)?;
827    /// let error = runtime.run_compiled(&program, &[]).unwrap_err();
828    /// assert!(error.to_string().contains("no eligible engine"));
829    /// # Ok(())
830    /// # }
831    /// ```
832    ///
833    /// # Errors
834    ///
835    /// Returns [`crate::Error::UnboundPlaceholder`] when no explicit inputs are
836    /// supplied and a semantic input has no bound default tensor.
837    /// Returns [`crate::Error::GraphInputCountMismatch`],
838    /// [`crate::Error::PlaceholderDtypeMismatch`],
839    /// [`crate::Error::PlaceholderRankMismatch`],
840    /// [`crate::Error::PlaceholderShapeMismatch`], or
841    /// [`crate::Error::PlaceholderShapeBoundExceeded`] when ordered runtime
842    /// inputs do not match the compiled graph metadata.
843    /// Returns [`crate::Error::RuntimeState`] when runtime preparation, schedule
844    /// validation, snapshot access, stale epoch checks, or execution-bridge
845    /// resolution fails, including [`crate::PrepareError::NoInputIngress`]
846    /// when no engine accepts an input's physical backend/allocation domain,
847    /// [`crate::PrepareError::MissingTransferProvider`] when ingress cannot
848    /// reach its first scheduled consumer, a runtime with no eligible engine,
849    /// or no execution bridge for the prepared engine. Backend execution may
850    /// also return concrete backend variants such as
851    /// [`crate::Error::Unsupported`], [`crate::Error::Validation`], or
852    /// [`crate::Error::Extension`].
853    pub fn run_compiled(
854        &self,
855        program: &CompiledGraph,
856        inputs: &[&Tensor],
857    ) -> crate::Result<Vec<Tensor>> {
858        super::execution::run_compiled(self, program, inputs)
859    }
860
861    /// Execute borrowed read-only inputs synchronously through retirement.
862    ///
863    /// Host/CPU providers may complete this call. Asynchronous device
864    /// providers reject before admission and return the unchanged borrowed
865    /// package through [`crate::ScopedSubmitRejected`].
866    ///
867    /// # Errors
868    ///
869    /// Returns [`crate::Error::Unsupported`] when the selected asynchronous
870    /// provider cannot execute borrowed inputs synchronously, or
871    /// [`crate::ScopedSubmitRejected`] when pre-admission validation fails.
872    /// Provider execution failures are reported as
873    /// [`crate::runtime::execution::ScopedExecutionOutcome::RetiredFailed`].
874    pub fn execute_scoped_read_only<'env>(
875        &self,
876        program: &CompiledGraph,
877        inputs: super::execution::ScopedReadInputs<'env>,
878    ) -> std::result::Result<
879        super::execution::ScopedExecutionOutcome<'env>,
880        super::execution::ScopedSubmitRejected<'env>,
881    > {
882        super::execution::execute_scoped_read_only(self, program, inputs)
883    }
884
885    /// Prepare a compiled graph for repeated execution with the same runtime.
886    ///
887    /// Preparation validates the supplied input metadata, selects a runtime
888    /// engine, and caches the staged execution plan. Use [`Self::run_prepared`]
889    /// for steady-state execution when the same compiled graph is run many
890    /// times.
891    ///
892    /// # Errors
893    ///
894    /// Returns [`crate::Error::UnboundPlaceholder`] when no explicit inputs are
895    /// supplied and a semantic input has no bound default tensor.
896    /// Returns [`crate::Error::GraphInputCountMismatch`],
897    /// [`crate::Error::PlaceholderDtypeMismatch`],
898    /// [`crate::Error::PlaceholderRankMismatch`],
899    /// [`crate::Error::PlaceholderShapeMismatch`], or
900    /// [`crate::Error::PlaceholderShapeBoundExceeded`] when ordered runtime
901    /// inputs do not match the compiled graph metadata.
902    /// Returns [`crate::Error::RuntimeState`] when runtime preparation, schedule
903    /// validation, snapshot access, stale epoch checks, or execution-bridge
904    /// resolution fails, including [`crate::PrepareError::NoInputIngress`]
905    /// when no engine accepts an input's physical backend/allocation domain,
906    /// [`crate::PrepareError::MissingTransferProvider`] when ingress cannot
907    /// reach its first scheduled consumer, a runtime with no eligible engine,
908    /// or no execution bridge for the prepared engine.
909    pub fn prepare_compiled(
910        &self,
911        program: &CompiledGraph,
912        inputs: &[&Tensor],
913    ) -> crate::Result<super::execution::PreparedCompiledGraph> {
914        super::execution::prepare_compiled(self, program, inputs)
915    }
916
917    /// Run a graph previously prepared by [`Self::prepare_compiled`].
918    ///
919    /// # Errors
920    ///
921    /// Returns metadata validation errors for incompatible inputs, a runtime
922    /// state error with [`crate::InputIngressContractError`] as its typed source
923    /// when an input's physical residency does not match the prepared ingress,
924    /// or a runtime state error if the prepared handle belongs to a different
925    /// runtime or a stale runtime epoch.
926    pub fn run_prepared(
927        &self,
928        prepared: &super::execution::PreparedCompiledGraph,
929        inputs: &[&Tensor],
930    ) -> crate::Result<Vec<Tensor>> {
931        super::execution::run_prepared(self, prepared, inputs)
932    }
933
934    /// Submit a compiled graph for asynchronous runtime-owned execution.
935    ///
936    /// Dropping the returned handle detaches the observer without blocking.
937    /// Use [`super::execution::ExecutionHandle::wait`] to observe completion.
938    ///
939    /// # Errors
940    ///
941    /// Returns the same [`crate::PrepareError::InputSignature`],
942    /// [`crate::PrepareError::Specialization`],
943    /// [`crate::PrepareError::NoEligibleEngine`],
944    /// [`crate::PrepareError::NoInputIngress`], and
945    /// [`crate::PrepareError::MissingTransferProvider`] failures as
946    /// [`Self::run_compiled`] before the worker is submitted. Returns a runtime
947    /// state error with [`crate::SubmissionError`] as its typed source if the
948    /// operating system rejects worker creation after admission.
949    pub fn submit(
950        &self,
951        program: &CompiledGraph,
952        inputs: super::execution::ExecutionInputs,
953    ) -> std::result::Result<super::execution::ExecutionHandle, super::execution::SubmitError> {
954        super::execution::submit(self, program, inputs)
955    }
956
957    /// Run a compiled graph and preserve lazy owned output views.
958    ///
959    /// # Examples
960    ///
961    /// ```
962    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
963    /// use tenferro_runtime::{Runtime, TracedTensor, GraphCompiler};
964    ///
965    /// let runtime = Runtime::builder().build()?;
966    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
967    /// let program = GraphCompiler::new().compile(&x)?;
968    /// let error = runtime.run_compiled_values(&program, &[]).unwrap_err();
969    /// assert!(error.to_string().contains("no eligible engine"));
970    /// # Ok(())
971    /// # }
972    /// ```
973    ///
974    /// # Errors
975    ///
976    /// Returns [`crate::Error::UnboundPlaceholder`] when no explicit inputs are
977    /// supplied and a semantic input has no bound default tensor.
978    /// Returns [`crate::Error::GraphInputCountMismatch`],
979    /// [`crate::Error::PlaceholderDtypeMismatch`],
980    /// [`crate::Error::PlaceholderRankMismatch`],
981    /// [`crate::Error::PlaceholderShapeMismatch`], or
982    /// [`crate::Error::PlaceholderShapeBoundExceeded`] when ordered runtime
983    /// inputs do not match the compiled graph metadata.
984    /// Returns [`crate::Error::RuntimeState`] when runtime preparation, schedule
985    /// validation, snapshot access, stale epoch checks, or execution-bridge
986    /// resolution fails, including [`crate::PrepareError::NoInputIngress`]
987    /// when no engine accepts an input's physical backend/allocation domain,
988    /// [`crate::PrepareError::MissingTransferProvider`] when ingress cannot
989    /// reach its first scheduled consumer, a runtime with no eligible engine,
990    /// or no execution bridge for the prepared engine. Backend execution may
991    /// also return concrete backend variants such as
992    /// [`crate::Error::Unsupported`], [`crate::Error::Validation`], or
993    /// [`crate::Error::Extension`].
994    pub fn run_compiled_values(
995        &self,
996        program: &CompiledGraph,
997        inputs: &[&Tensor],
998    ) -> crate::Result<Vec<TensorValue>> {
999        super::execution::run_compiled_values(self, program, inputs)
1000    }
1001
1002    /// Transactionally edit and publish runtime configuration.
1003    ///
1004    /// No user callback runs while the publication lock is held. If another
1005    /// writer publishes over the same base snapshot, this call returns
1006    /// [`RuntimeReconfigureError::ConcurrentReconfiguration`] and publishes
1007    /// nothing.
1008    ///
1009    /// # Errors
1010    ///
1011    /// Returns [`RuntimeReconfigureError`] when state access, edit validation,
1012    /// identity allocation, epoch advancement, or compare-and-publish fails.
1013    /// Invalid transfer endpoints are reported as the typed
1014    /// [`RuntimeConfigError::UnknownTransferEndpointEngine`] or
1015    /// [`RuntimeConfigError::UnsupportedTransferEndpointStorage`] source of
1016    /// [`RuntimeReconfigureError::Edit`].
1017    pub fn reconfigure(
1018        &self,
1019        edit: impl FnOnce(&mut RuntimeReconfiguration<'_>) -> Result<(), RuntimeConfigError>,
1020    ) -> Result<RuntimeEpoch, RuntimeReconfigureError> {
1021        let base = self
1022            .snapshot()
1023            .map_err(|source| RuntimeReconfigureError::State { source })?;
1024        let mut candidate = CandidateConfig::from_snapshot(&base)
1025            .map_err(|source| RuntimeReconfigureError::Edit { source })?;
1026        let mut changed = false;
1027        {
1028            let mut reconfiguration = RuntimeReconfiguration {
1029                candidate: &mut candidate,
1030                changed: &mut changed,
1031            };
1032            edit(&mut reconfiguration)
1033                .map_err(|source| RuntimeReconfigureError::Edit { source })?;
1034        }
1035
1036        if !changed {
1037            return Ok(base.epoch());
1038        }
1039        let next_identity_ordinal = NonZeroU64::new(
1040            self.0.next_registration_ordinal.load(Ordering::SeqCst),
1041        )
1042        .ok_or(RuntimeReconfigureError::Edit {
1043            source: RuntimeConfigError::IdentityExhausted,
1044        })?;
1045        let (bound_candidate, post_ordinal) =
1046            validate_candidate(candidate, self.0.issuer, next_identity_ordinal)
1047                .map_err(|source| RuntimeReconfigureError::Edit { source })?;
1048
1049        let next_epoch =
1050            base.epoch()
1051                .checked_next()
1052                .ok_or(RuntimeReconfigureError::EpochExhausted {
1053                    current: base.epoch(),
1054                })?;
1055
1056        let mut guard = self
1057            .0
1058            .active
1059            .write()
1060            .map_err(|_| RuntimeReconfigureError::State {
1061                source: RuntimeStateError::Poisoned {
1062                    lock: "runtime.active",
1063                },
1064            })?;
1065        if !Arc::ptr_eq(&*guard, &base) {
1066            return Err(RuntimeReconfigureError::ConcurrentReconfiguration {
1067                base: base.epoch(),
1068                current: guard.epoch(),
1069            });
1070        }
1071
1072        let next_snapshot = Arc::new(
1073            freeze_candidate(self.0.runtime_id, next_epoch, bound_candidate)
1074                .map_err(|source| RuntimeReconfigureError::Edit { source })?,
1075        );
1076
1077        self.0
1078            .next_registration_ordinal
1079            .store(post_ordinal.get(), Ordering::SeqCst);
1080        *guard = next_snapshot;
1081        self.0
1082            .published_epoch
1083            .store(next_epoch.get().get(), Ordering::Release);
1084        Ok(next_epoch)
1085    }
1086
1087    #[cfg(test)]
1088    pub(crate) fn force_epoch_for_test(&self, epoch: RuntimeEpoch) {
1089        let mut guard = self.0.active.write().expect("test runtime lock");
1090        let mut replacement = (**guard).clone();
1091        replacement.epoch = epoch;
1092        *guard = Arc::new(replacement);
1093        self.0
1094            .published_epoch
1095            .store(epoch.get().get(), Ordering::Release);
1096    }
1097
1098    #[cfg(test)]
1099    pub(crate) fn force_next_registration_ordinal_for_test(&self, next: NonZeroU64) {
1100        self.0
1101            .next_registration_ordinal
1102            .store(next.get(), Ordering::SeqCst);
1103    }
1104
1105    #[cfg(test)]
1106    pub(crate) fn poison_active_lock_for_test(&self) {
1107        let state = Arc::clone(&self.0);
1108        let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
1109            let _guard = state.active.write().expect("test runtime lock");
1110            panic!("poison runtime.active for test");
1111        }));
1112    }
1113}
1114
1115impl fmt::Debug for Runtime {
1116    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1117        formatter
1118            .debug_struct("Runtime")
1119            .field("runtime_id", &self.0.runtime_id)
1120            .field("published_epoch", &self.epoch().ok())
1121            .finish_non_exhaustive()
1122    }
1123}
1124
1125/// Consuming builder for an immutable runtime configuration.
1126///
1127/// # Examples
1128///
1129/// ```
1130/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1131/// use tenferro_runtime::RuntimeConfigBuilder;
1132///
1133/// let runtime = RuntimeConfigBuilder::new().build()?;
1134/// assert_eq!(runtime.snapshot()?.engine_count(), 0);
1135/// # Ok(())
1136/// # }
1137/// ```
1138pub struct RuntimeConfigBuilder {
1139    candidate: CandidateConfig,
1140}
1141
1142impl RuntimeConfigBuilder {
1143    /// Create an empty runtime builder.
1144    pub fn new() -> Self {
1145        Self {
1146            candidate: CandidateConfig::empty(),
1147        }
1148    }
1149
1150    /// Replace the execution policy in the candidate configuration.
1151    pub fn execution_policy(&mut self, value: ExecutionPolicy) -> &mut Self {
1152        self.candidate.policy = value;
1153        self
1154    }
1155
1156    /// Register a new engine candidate.
1157    ///
1158    /// # Errors
1159    ///
1160    /// Returns [`RuntimeConfigError::DuplicateEngine`] if a different candidate
1161    /// with the same engine ID is already present.
1162    pub fn register_engine(
1163        &mut self,
1164        value: EngineRegistration,
1165    ) -> Result<&mut Self, RuntimeConfigError> {
1166        let mut changed = false;
1167        register_engine_candidate(&mut self.candidate, value, &mut changed)?;
1168        Ok(self)
1169    }
1170
1171    /// Explicitly replace an existing engine candidate.
1172    ///
1173    /// # Errors
1174    ///
1175    /// Returns [`RuntimeConfigError::MissingEngine`] if the engine ID is absent.
1176    pub fn replace_engine(
1177        &mut self,
1178        value: EngineRegistration,
1179    ) -> Result<&mut Self, RuntimeConfigError> {
1180        let mut changed = false;
1181        replace_engine_candidate(&mut self.candidate, value, &mut changed)?;
1182        Ok(self)
1183    }
1184
1185    /// Remove an existing engine candidate.
1186    ///
1187    /// # Errors
1188    ///
1189    /// Returns [`RuntimeConfigError::MissingEngine`] if the engine ID is absent.
1190    pub fn remove_engine(&mut self, id: &EngineId) -> Result<&mut Self, RuntimeConfigError> {
1191        let mut changed = false;
1192        remove_engine_candidate(&mut self.candidate, id, &mut changed)?;
1193        Ok(self)
1194    }
1195
1196    /// Install an extension module transaction.
1197    ///
1198    /// # Errors
1199    ///
1200    /// Returns [`RuntimeConfigError::ExtensionModule`] when module configuration
1201    /// fails or a distinct module already uses the same module ID.
1202    pub fn install_extension_module(
1203        &mut self,
1204        value: Arc<dyn ExtensionModule>,
1205    ) -> Result<&mut Self, RuntimeConfigError> {
1206        let mut changed = false;
1207        install_extension_module_candidate(&mut self.candidate, value, &mut changed)?;
1208        Ok(self)
1209    }
1210
1211    /// Register a transfer provider keyed by source and destination endpoints.
1212    ///
1213    /// # Examples
1214    ///
1215    /// ```
1216    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1217    /// use std::sync::Arc;
1218    /// use tenferro_runtime::{
1219    ///     assemble_preparation_only_engine_registration, CoreCapabilityBundle, EngineId,
1220    ///     EngineRegistration, EngineRegistrationMetadata, Error, ExecutionContextIdentity,
1221    ///     HardwareClassId, PreparationOnlyEngineRegistrationConfig, ProviderDeviceIdentity,
1222    ///     ProviderId, Runtime, StorageClass, TransferEndpoint, TransferProvider, TransferRequest,
1223    /// };
1224    ///
1225    /// #[derive(Debug)]
1226    /// struct ExampleProvider;
1227    ///
1228    /// impl TransferProvider for ExampleProvider {
1229    ///     fn transfer_blocking(
1230    ///         &self,
1231    ///         _request: TransferRequest<'_>,
1232    ///     ) -> tenferro_runtime::Result<tenferro_tensor::Tensor> {
1233    ///         Err(Error::Internal("the example does not execute a transfer".into()))
1234    ///     }
1235    /// }
1236    ///
1237    /// fn registration(
1238    ///     id: EngineId,
1239    ///     target: &str,
1240    ///     storage: &StorageClass,
1241    /// ) -> Result<EngineRegistration, tenferro_runtime::RuntimeConfigError> {
1242    ///     let metadata = EngineRegistrationMetadata::new(
1243    ///         id,
1244    ///         ProviderDeviceIdentity::new(ProviderId::new("example.provider")?, target)?,
1245    ///         HardwareClassId::new("example.hardware")?,
1246    ///         Arc::from([storage.clone()]),
1247    ///         storage.clone(),
1248    ///         CoreCapabilityBundle::default(),
1249    ///     );
1250    ///     assemble_preparation_only_engine_registration(
1251    ///         PreparationOnlyEngineRegistrationConfig::new(
1252    ///             metadata,
1253    ///             ExecutionContextIdentity::of::<()>(),
1254    ///         ),
1255    ///     )
1256    /// }
1257    ///
1258    /// let storage = StorageClass::new("example.storage.host")?;
1259    /// let source_id = EngineId::new("example.engine.source")?;
1260    /// let destination_id = EngineId::new("example.engine.destination")?;
1261    /// let source = registration(
1262    ///     source_id.clone(),
1263    ///     "source-0",
1264    ///     &storage,
1265    /// )?;
1266    /// let destination = registration(
1267    ///     destination_id.clone(),
1268    ///     "destination-0",
1269    ///     &storage,
1270    /// )?;
1271    /// let mut builder = Runtime::builder();
1272    /// builder.register_engine(source)?;
1273    /// builder.register_engine(destination)?;
1274    /// builder.register_transfer_provider(
1275    ///     TransferEndpoint::new(source_id, storage.clone()),
1276    ///     TransferEndpoint::new(destination_id, storage),
1277    ///     Arc::new(ExampleProvider),
1278    /// )?;
1279    /// let runtime = builder.build()?;
1280    /// assert_eq!(runtime.snapshot()?.transfer_provider_count(), 1);
1281    /// # Ok(())
1282    /// # }
1283    /// ```
1284    ///
1285    /// # Errors
1286    ///
1287    /// Returns [`RuntimeConfigError::ConflictingRegistration`] if a different
1288    /// provider is already registered for the same endpoint pair. The complete
1289    /// endpoint pair is validated when [`Self::build`] freezes the candidate.
1290    pub fn register_transfer_provider(
1291        &mut self,
1292        source: TransferEndpoint,
1293        destination: TransferEndpoint,
1294        provider: Arc<dyn TransferProvider>,
1295    ) -> Result<&mut Self, RuntimeConfigError> {
1296        let mut changed = false;
1297        register_transfer_provider_candidate(
1298            &mut self.candidate,
1299            source,
1300            destination,
1301            provider,
1302            &mut changed,
1303        )?;
1304        Ok(self)
1305    }
1306
1307    /// Remove the transfer provider for an endpoint pair.
1308    ///
1309    /// This explicit removal is required before changing an engine's physical
1310    /// binding. The route can then be registered again against the replacement
1311    /// binding in the same candidate transaction.
1312    ///
1313    /// # Errors
1314    ///
1315    /// Returns [`RuntimeConfigError::MissingTransferProvider`] when the exact
1316    /// endpoint pair is not registered.
1317    pub fn remove_transfer_provider(
1318        &mut self,
1319        source: TransferEndpoint,
1320        destination: TransferEndpoint,
1321    ) -> Result<&mut Self, RuntimeConfigError> {
1322        let mut changed = false;
1323        remove_transfer_provider_candidate(&mut self.candidate, source, destination, &mut changed)?;
1324        Ok(self)
1325    }
1326
1327    /// Replace an extension module transaction, installing it when absent.
1328    ///
1329    /// # Errors
1330    ///
1331    /// Returns [`RuntimeConfigError::ExtensionModule`] when module configuration
1332    /// fails.
1333    pub fn replace_extension_module(
1334        &mut self,
1335        value: Arc<dyn ExtensionModule>,
1336    ) -> Result<&mut Self, RuntimeConfigError> {
1337        let mut changed = false;
1338        replace_extension_module_candidate(&mut self.candidate, value, &mut changed)?;
1339        Ok(self)
1340    }
1341
1342    /// Remove an extension module candidate if present.
1343    ///
1344    /// # Errors
1345    ///
1346    /// This method currently has no failing absent-module path; it returns
1347    /// [`RuntimeConfigError`] only for future validated module removal failures.
1348    pub fn remove_extension_module(
1349        &mut self,
1350        id: &ExtensionModuleId,
1351    ) -> Result<&mut Self, RuntimeConfigError> {
1352        let mut changed = false;
1353        remove_extension_module_candidate(&mut self.candidate, id, &mut changed)?;
1354        Ok(self)
1355    }
1356
1357    /// Build and publish the initial runtime snapshot.
1358    ///
1359    /// # Errors
1360    ///
1361    /// Returns [`RuntimeConfigError::IdentityExhausted`] if runtime or
1362    /// registration identity allocation would wrap, or
1363    /// [`RuntimeConfigError::UnknownTransferEndpointEngine`] or
1364    /// [`RuntimeConfigError::UnsupportedTransferEndpointStorage`] if a
1365    /// registered transfer endpoint is invalid for the complete candidate.
1366    pub fn build(self) -> Result<Runtime, RuntimeConfigError> {
1367        let runtime_id = RuntimeId::from_nonzero(allocate_nonzero(&NEXT_RUNTIME_ID)?);
1368        let issuer = allocate_nonzero(&NEXT_REGISTRATION_ISSUER)?;
1369        let (bound_candidate, post_ordinal) =
1370            validate_candidate(self.candidate, issuer, INITIAL_REGISTRATION_ORDINAL)?;
1371        let epoch = RuntimeEpoch::one();
1372        let snapshot = Arc::new(freeze_candidate(runtime_id, epoch, bound_candidate)?);
1373        let state = RuntimeState {
1374            runtime_id,
1375            issuer,
1376            next_registration_ordinal: AtomicU64::new(post_ordinal.get()),
1377            active: RwLock::new(snapshot),
1378            published_epoch: AtomicU64::new(epoch.get().get()),
1379            caches: RuntimeCacheSet::new(PreparedPlanCacheLimits::default()),
1380        };
1381        Ok(Runtime(Arc::new(state)))
1382    }
1383}
1384
1385impl Default for RuntimeConfigBuilder {
1386    fn default() -> Self {
1387        Self::new()
1388    }
1389}
1390
1391impl fmt::Debug for RuntimeConfigBuilder {
1392    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1393        formatter
1394            .debug_struct("RuntimeConfigBuilder")
1395            .field("execution_policy", &self.candidate.policy)
1396            .field("engine_count", &self.candidate.engines.len())
1397            .field("extension_module_count", &self.candidate.modules.len())
1398            .field("transfer_provider_count", &self.candidate.transfers.len())
1399            .finish_non_exhaustive()
1400    }
1401}
1402
1403/// Non-escapable reconfiguration edit view.
1404pub struct RuntimeReconfiguration<'a> {
1405    candidate: &'a mut CandidateConfig,
1406    changed: &'a mut bool,
1407}
1408
1409impl RuntimeReconfiguration<'_> {
1410    /// Replace the candidate execution policy.
1411    pub fn execution_policy(&mut self, policy: ExecutionPolicy) -> &mut Self {
1412        if self.candidate.policy != policy {
1413            self.candidate.policy = policy;
1414            *self.changed = true;
1415        }
1416        self
1417    }
1418
1419    /// Register a new engine in this reconfiguration.
1420    ///
1421    /// # Errors
1422    ///
1423    /// Returns [`RuntimeConfigError::DuplicateEngine`] if a different candidate
1424    /// with the same engine ID is already present.
1425    pub fn register_engine(
1426        &mut self,
1427        value: EngineRegistration,
1428    ) -> Result<&mut Self, RuntimeConfigError> {
1429        register_engine_candidate(self.candidate, value, self.changed)?;
1430        Ok(self)
1431    }
1432
1433    /// Replace an existing engine in this reconfiguration.
1434    ///
1435    /// # Errors
1436    ///
1437    /// Returns [`RuntimeConfigError::MissingEngine`] if the engine ID is absent.
1438    pub fn replace_engine(
1439        &mut self,
1440        value: EngineRegistration,
1441    ) -> Result<&mut Self, RuntimeConfigError> {
1442        replace_engine_candidate(self.candidate, value, self.changed)?;
1443        Ok(self)
1444    }
1445
1446    /// Remove an existing engine in this reconfiguration.
1447    ///
1448    /// # Errors
1449    ///
1450    /// Returns [`RuntimeConfigError::MissingEngine`] if the engine ID is absent.
1451    pub fn remove_engine(&mut self, id: &EngineId) -> Result<&mut Self, RuntimeConfigError> {
1452        remove_engine_candidate(self.candidate, id, self.changed)?;
1453        Ok(self)
1454    }
1455
1456    /// Install an extension module in this reconfiguration.
1457    ///
1458    /// # Errors
1459    ///
1460    /// Returns [`RuntimeConfigError::ExtensionModule`] when module configuration
1461    /// fails or a distinct module already uses the same module ID.
1462    pub fn install_extension_module(
1463        &mut self,
1464        value: Arc<dyn ExtensionModule>,
1465    ) -> Result<&mut Self, RuntimeConfigError> {
1466        install_extension_module_candidate(self.candidate, value, self.changed)?;
1467        Ok(self)
1468    }
1469
1470    /// Register a transfer provider in this reconfiguration by endpoint pair.
1471    ///
1472    /// # Examples
1473    ///
1474    /// ```
1475    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1476    /// use std::sync::Arc;
1477    /// use tenferro_runtime::{
1478    ///     assemble_preparation_only_engine_registration, CoreCapabilityBundle, EngineId,
1479    ///     EngineRegistration, EngineRegistrationMetadata, Error, ExecutionContextIdentity,
1480    ///     HardwareClassId, PreparationOnlyEngineRegistrationConfig, ProviderDeviceIdentity,
1481    ///     ProviderId, Runtime, StorageClass, TransferEndpoint, TransferProvider, TransferRequest,
1482    /// };
1483    ///
1484    /// #[derive(Debug)]
1485    /// struct ExampleProvider;
1486    ///
1487    /// impl TransferProvider for ExampleProvider {
1488    ///     fn transfer_blocking(
1489    ///         &self,
1490    ///         _request: TransferRequest<'_>,
1491    ///     ) -> tenferro_runtime::Result<tenferro_tensor::Tensor> {
1492    ///         Err(Error::Internal("the example does not execute a transfer".into()))
1493    ///     }
1494    /// }
1495    ///
1496    /// fn registration(
1497    ///     id: &str,
1498    ///     storage: &StorageClass,
1499    /// ) -> Result<EngineRegistration, tenferro_runtime::RuntimeConfigError> {
1500    ///     let metadata = EngineRegistrationMetadata::new(
1501    ///         EngineId::new(id)?,
1502    ///         ProviderDeviceIdentity::new(
1503    ///             ProviderId::new("example.provider")?,
1504    ///             format!("engine:{id}"),
1505    ///         )?,
1506    ///         HardwareClassId::new("example.hardware")?,
1507    ///         Arc::from([storage.clone()]),
1508    ///         storage.clone(),
1509    ///         CoreCapabilityBundle::default(),
1510    ///     );
1511    ///     Ok(assemble_preparation_only_engine_registration(
1512    ///         PreparationOnlyEngineRegistrationConfig::new(
1513    ///             metadata,
1514    ///             ExecutionContextIdentity::of::<()>(),
1515    ///         ),
1516    ///     )?)
1517    /// }
1518    ///
1519    /// let storage = StorageClass::new("example.storage.host")?;
1520    /// let source_id = EngineId::new("example.engine.source")?;
1521    /// let destination_id = EngineId::new("example.engine.destination")?;
1522    /// let mut builder = Runtime::builder();
1523    /// builder.register_engine(registration(source_id.as_str(), &storage)?)?;
1524    /// builder.register_engine(registration(destination_id.as_str(), &storage)?)?;
1525    /// let runtime = builder.build()?;
1526    /// runtime.reconfigure(|edit| {
1527    ///     edit.register_transfer_provider(
1528    ///         TransferEndpoint::new(source_id, storage.clone()),
1529    ///         TransferEndpoint::new(destination_id, storage),
1530    ///         Arc::new(ExampleProvider),
1531    ///     )?;
1532    ///     Ok(())
1533    /// })?;
1534    /// assert_eq!(runtime.snapshot()?.transfer_provider_count(), 1);
1535    /// # Ok(())
1536    /// # }
1537    /// ```
1538    ///
1539    /// # Errors
1540    ///
1541    /// Returns [`RuntimeConfigError::ConflictingRegistration`] if a different
1542    /// provider is already registered for the same endpoint pair. The complete
1543    /// endpoint pair is validated before this reconfiguration is published.
1544    pub fn register_transfer_provider(
1545        &mut self,
1546        source: TransferEndpoint,
1547        destination: TransferEndpoint,
1548        provider: Arc<dyn TransferProvider>,
1549    ) -> Result<&mut Self, RuntimeConfigError> {
1550        register_transfer_provider_candidate(
1551            self.candidate,
1552            source,
1553            destination,
1554            provider,
1555            self.changed,
1556        )?;
1557        Ok(self)
1558    }
1559
1560    /// Remove the transfer provider for an endpoint pair in this
1561    /// reconfiguration.
1562    ///
1563    /// # Errors
1564    ///
1565    /// Returns [`RuntimeConfigError::MissingTransferProvider`] when the exact
1566    /// endpoint pair is not registered.
1567    pub fn remove_transfer_provider(
1568        &mut self,
1569        source: TransferEndpoint,
1570        destination: TransferEndpoint,
1571    ) -> Result<&mut Self, RuntimeConfigError> {
1572        remove_transfer_provider_candidate(self.candidate, source, destination, self.changed)?;
1573        Ok(self)
1574    }
1575
1576    /// Replace an extension module in this reconfiguration, installing when
1577    /// absent.
1578    ///
1579    /// # Errors
1580    ///
1581    /// Returns [`RuntimeConfigError::ExtensionModule`] when module configuration
1582    /// fails.
1583    pub fn replace_extension_module(
1584        &mut self,
1585        value: Arc<dyn ExtensionModule>,
1586    ) -> Result<&mut Self, RuntimeConfigError> {
1587        replace_extension_module_candidate(self.candidate, value, self.changed)?;
1588        Ok(self)
1589    }
1590
1591    /// Ensure an extension module owns the selected family and engine
1592    /// registration, installing it when absent.
1593    ///
1594    /// If the candidate already contains the incoming module ID with the exact
1595    /// `(family_id, engine_id)` registration, this owner-scoped operation is a
1596    /// no-op even when the incoming module is a fresh `Arc`. Otherwise it
1597    /// validates the incoming module before [`Runtime::reconfigure`] can
1598    /// publish it. A failed validation therefore leaves the previously
1599    /// published module and snapshot untouched.
1600    ///
1601    /// # Examples
1602    ///
1603    /// ```rust
1604    /// use std::sync::Arc;
1605    ///
1606    /// use tenferro_runtime::{
1607    ///     EngineId, ExtensionModule, ExtensionModuleError, ExtensionModuleId,
1608    ///     ExtensionModuleRegistrar, Runtime,
1609    /// };
1610    ///
1611    /// #[derive(Debug)]
1612    /// struct ExampleModule {
1613    ///     id: ExtensionModuleId,
1614    /// }
1615    ///
1616    /// impl ExtensionModule for ExampleModule {
1617    ///     fn module_id(&self) -> &ExtensionModuleId {
1618    ///         &self.id
1619    ///     }
1620    ///
1621    ///     fn configure(
1622    ///         &self,
1623    ///         _registrar: &mut ExtensionModuleRegistrar<'_>,
1624    ///     ) -> Result<(), ExtensionModuleError> {
1625    ///         Ok(())
1626    ///     }
1627    /// }
1628    ///
1629    /// let runtime = Runtime::builder().build()?;
1630    /// let engine = EngineId::new("example.engine.v1")?;
1631    /// let error = runtime
1632    ///     .reconfigure(|edit| {
1633    ///         edit.ensure_extension_module_for_engine(
1634    ///             Arc::new(ExampleModule {
1635    ///                 id: ExtensionModuleId::new("example.module.v1")?,
1636    ///             }),
1637    ///             "example.family.v1",
1638    ///             &engine,
1639    ///         )?;
1640    ///         Ok(())
1641    ///     })
1642    ///     .unwrap_err();
1643    /// let source = std::error::Error::source(&error).unwrap();
1644    /// assert!(source.to_string().contains("example.module.v1"));
1645    /// # Ok::<(), Box<dyn std::error::Error>>(())
1646    /// ```
1647    ///
1648    /// # Errors
1649    ///
1650    /// Returns [`RuntimeConfigError::MissingExtensionEngine`] when the
1651    /// configured module does not register `family_id` for `engine_id`, or
1652    /// [`RuntimeConfigError::ExtensionModule`] when module configuration fails.
1653    #[doc(hidden)]
1654    pub fn ensure_extension_module_for_engine(
1655        &mut self,
1656        value: Arc<dyn ExtensionModule>,
1657        family_id: &'static str,
1658        engine_id: &EngineId,
1659    ) -> Result<&mut Self, RuntimeConfigError> {
1660        let module_id = value.module_id().clone();
1661        if let Some(existing) = self.candidate.modules.get(&module_id) {
1662            if existing
1663                .engines
1664                .contains_key(&(family_id, engine_id.clone()))
1665            {
1666                return Ok(self);
1667            }
1668        }
1669
1670        let record = configure_module(Arc::clone(&value))
1671            .map_err(|source| RuntimeConfigError::ExtensionModule { source })?;
1672        if !record.engines.contains_key(&(family_id, engine_id.clone())) {
1673            return Err(RuntimeConfigError::MissingExtensionEngine {
1674                module_id,
1675                family_id,
1676                engine_id: engine_id.clone(),
1677            });
1678        }
1679        self.candidate.modules.insert(module_id, record);
1680        *self.changed = true;
1681        Ok(self)
1682    }
1683
1684    /// Replace one extension module only when it owns the selected family and
1685    /// engine registration.
1686    ///
1687    /// This owner-scoped operation validates the configured candidate before
1688    /// [`Runtime::reconfigure`] can publish it. A failed validation therefore
1689    /// leaves the previously published module and snapshot untouched.
1690    ///
1691    /// # Examples
1692    ///
1693    /// ```rust
1694    /// use std::sync::Arc;
1695    ///
1696    /// use tenferro_runtime::{
1697    ///     EngineId, ExtensionModule, ExtensionModuleError, ExtensionModuleId,
1698    ///     ExtensionModuleRegistrar, Runtime,
1699    /// };
1700    ///
1701    /// #[derive(Debug)]
1702    /// struct ExampleModule {
1703    ///     id: ExtensionModuleId,
1704    /// }
1705    ///
1706    /// impl ExtensionModule for ExampleModule {
1707    ///     fn module_id(&self) -> &ExtensionModuleId {
1708    ///         &self.id
1709    ///     }
1710    ///
1711    ///     fn configure(
1712    ///         &self,
1713    ///         _registrar: &mut ExtensionModuleRegistrar<'_>,
1714    ///     ) -> Result<(), ExtensionModuleError> {
1715    ///         Ok(())
1716    ///     }
1717    /// }
1718    ///
1719    /// let runtime = Runtime::builder().build()?;
1720    /// let engine = EngineId::new("example.engine.v1")?;
1721    /// let error = runtime
1722    ///     .reconfigure(|edit| {
1723    ///         edit.replace_extension_module_for_engine(
1724    ///             Arc::new(ExampleModule {
1725    ///                 id: ExtensionModuleId::new("example.module.v1")?,
1726    ///             }),
1727    ///             "example.family.v1",
1728    ///             &engine,
1729    ///         )?;
1730    ///         Ok(())
1731    ///     })
1732    ///     .unwrap_err();
1733    /// let source = std::error::Error::source(&error).unwrap();
1734    /// assert!(source.to_string().contains("example.module.v1"));
1735    /// # Ok::<(), Box<dyn std::error::Error>>(())
1736    /// ```
1737    ///
1738    /// # Errors
1739    ///
1740    /// Returns [`RuntimeConfigError::MissingExtensionEngine`] when the
1741    /// configured module does not register `family_id` for `engine_id`, or
1742    /// [`RuntimeConfigError::ExtensionModule`] when module configuration fails.
1743    #[doc(hidden)]
1744    pub fn replace_extension_module_for_engine(
1745        &mut self,
1746        value: Arc<dyn ExtensionModule>,
1747        family_id: &'static str,
1748        engine_id: &EngineId,
1749    ) -> Result<&mut Self, RuntimeConfigError> {
1750        let module_id = value.module_id().clone();
1751        if let Some(existing) = self.candidate.modules.get(&module_id) {
1752            if existing.module_identical(&value) {
1753                if existing
1754                    .engines
1755                    .contains_key(&(family_id, engine_id.clone()))
1756                {
1757                    return Ok(self);
1758                }
1759                return Err(RuntimeConfigError::MissingExtensionEngine {
1760                    module_id,
1761                    family_id,
1762                    engine_id: engine_id.clone(),
1763                });
1764            }
1765        }
1766
1767        let record = configure_module(Arc::clone(&value))
1768            .map_err(|source| RuntimeConfigError::ExtensionModule { source })?;
1769        if !record.engines.contains_key(&(family_id, engine_id.clone())) {
1770            return Err(RuntimeConfigError::MissingExtensionEngine {
1771                module_id,
1772                family_id,
1773                engine_id: engine_id.clone(),
1774            });
1775        }
1776        self.candidate.modules.insert(module_id, record);
1777        *self.changed = true;
1778        Ok(self)
1779    }
1780
1781    /// Remove an extension module if present.
1782    ///
1783    /// # Errors
1784    ///
1785    /// This method currently has no failing absent-module path; it returns
1786    /// [`RuntimeConfigError`] only for future validated module removal failures.
1787    pub fn remove_extension_module(
1788        &mut self,
1789        id: &ExtensionModuleId,
1790    ) -> Result<&mut Self, RuntimeConfigError> {
1791        remove_extension_module_candidate(self.candidate, id, self.changed)?;
1792        Ok(self)
1793    }
1794}
1795
1796impl fmt::Debug for RuntimeReconfiguration<'_> {
1797    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1798        formatter
1799            .debug_struct("RuntimeReconfiguration")
1800            .field("engine_count", &self.candidate.engines.len())
1801            .field("extension_module_count", &self.candidate.modules.len())
1802            .field("transfer_provider_count", &self.candidate.transfers.len())
1803            .field("changed", &*self.changed)
1804            .finish_non_exhaustive()
1805    }
1806}
1807
1808/// Borrowed immutable view of one engine slot in a runtime snapshot.
1809///
1810/// # Examples
1811///
1812/// ```
1813/// use std::fmt::Debug;
1814/// use tenferro_runtime::EngineSnapshotView;
1815///
1816/// fn requires_debug<T: Debug>() {}
1817/// requires_debug::<EngineSnapshotView<'_>>();
1818/// ```
1819#[derive(Clone, Copy)]
1820pub struct EngineSnapshotView<'a> {
1821    slot: &'a FrozenEngineSlot,
1822}
1823
1824impl<'a> EngineSnapshotView<'a> {
1825    /// Return the engine ID for this slot.
1826    pub fn engine_id(&self) -> &'a EngineId {
1827        self.slot.engine_id()
1828    }
1829
1830    /// Return the immutable provider/device binding for this engine slot.
1831    ///
1832    /// # Examples
1833    ///
1834    /// ```
1835    /// # fn inspect(view: tenferro_runtime::EngineSnapshotView<'_>) {
1836    /// let _ = view.provider_device_identity();
1837    /// # }
1838    /// ```
1839    pub fn provider_device_identity(&self) -> &'a super::ProviderDeviceIdentity {
1840        self.slot.provider_device_identity()
1841    }
1842
1843    /// Return the runtime-local registration identity for this slot.
1844    pub fn registration_identity(&self) -> RegistrationIdentity {
1845        self.slot.metadata().identity
1846    }
1847
1848    /// Return the execution-context identity required by this slot.
1849    pub fn context_identity(&self) -> ExecutionContextIdentity {
1850        self.slot.context_identity()
1851    }
1852
1853    /// Return this engine's runtime event domain.
1854    pub fn event_domain_id(&self) -> EventDomainId {
1855        self.slot.metadata().event_domain_id
1856    }
1857
1858    pub(super) fn executable_witness(&self) -> Option<&'a Arc<ExecutableEngineSnapshot>> {
1859        self.slot.executable()
1860    }
1861
1862    /// Return the hardware class for this slot.
1863    pub fn hardware_class(&self) -> &'a HardwareClassId {
1864        self.slot.hardware_class()
1865    }
1866
1867    /// Return direct core capability slots for this engine.
1868    pub fn capabilities(&self) -> &'a CoreCapabilityBundle {
1869        self.slot.capabilities()
1870    }
1871
1872    pub(super) fn storage_classes(&self) -> &'a [StorageClass] {
1873        self.slot.storage_classes()
1874    }
1875
1876    pub(super) fn default_storage_class(&self) -> &'a StorageClass {
1877        self.slot.default_storage_class()
1878    }
1879
1880    /// Return whether this engine's registered ingress accepts one physical
1881    /// input signature across its advertised storage classes.
1882    ///
1883    /// This narrow, doc-hidden query is used by eager extension owners before
1884    /// invoking a module factory. It includes placement, backend family, and
1885    /// allocation-domain admission rather than checking only tensor shape or
1886    /// dtype.
1887    ///
1888    /// # Examples
1889    ///
1890    /// ```rust
1891    /// use tenferro_runtime::{InputSignature, Runtime, Tensor, TensorRead};
1892    ///
1893    /// let backend = tenferro_cpu::CpuBackend::new();
1894    /// let mut builder = Runtime::builder();
1895    /// builder.register_engine(tenferro_cpu::runtime_engine_registration(&backend)?)?;
1896    /// let runtime = builder.build()?;
1897    /// let engine_id = tenferro_cpu::runtime_engine_id()?;
1898    /// let snapshot = runtime.snapshot()?;
1899    /// let engine = snapshot.engine(&engine_id).unwrap();
1900    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
1901    /// let reads = [TensorRead::from_tensor(&tensor)];
1902    /// let signature = InputSignature::from_reads(&reads)?;
1903    /// assert!(engine.accepts_input_signature(&signature.entries()[0]));
1904    /// # Ok::<(), Box<dyn std::error::Error>>(())
1905    /// ```
1906    #[doc(hidden)]
1907    pub fn accepts_input_signature(&self, input: &super::InputSignatureEntry) -> bool {
1908        self.slot
1909            .storage_classes()
1910            .iter()
1911            .any(|storage_class| self.accepts_input_signature_for_storage(input, storage_class))
1912    }
1913
1914    pub(super) fn accepts_input_signature_for_storage(
1915        &self,
1916        input: &super::InputSignatureEntry,
1917        storage_class: &StorageClass,
1918    ) -> bool {
1919        self.slot
1920            .executable()
1921            .is_some_and(|snapshot| snapshot.accepts_input_signature(input, storage_class))
1922    }
1923
1924    #[cfg(test)]
1925    pub(crate) fn has_execution_engine_for_test(&self) -> bool {
1926        self.slot.executable().is_some()
1927    }
1928}
1929
1930impl fmt::Debug for EngineSnapshotView<'_> {
1931    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1932        formatter
1933            .debug_struct("EngineSnapshotView")
1934            .field("engine_id", self.engine_id())
1935            .field("registration_identity", &self.registration_identity())
1936            .field("context_identity", &self.context_identity())
1937            .field("hardware_class", self.hardware_class())
1938            .field("capabilities", self.capabilities())
1939            .finish()
1940    }
1941}
1942
1943fn default_execution_policy() -> ExecutionPolicy {
1944    ExecutionPolicy::new(super::Determinism::Fast, None, 0)
1945}
1946
1947fn register_engine_candidate(
1948    candidate: &mut CandidateConfig,
1949    registration: EngineRegistration,
1950    changed: &mut bool,
1951) -> Result<(), RuntimeConfigError> {
1952    let engine_id = registration.engine_id().clone();
1953    match candidate.engines.get(&engine_id) {
1954        Some(existing) if existing.registration.candidate_identical(&registration) => Ok(()),
1955        Some(_) => Err(RuntimeConfigError::DuplicateEngine { engine_id }),
1956        None => {
1957            ensure_unique_provider_device_target(candidate, &registration)?;
1958            candidate.engines.insert(
1959                engine_id,
1960                CandidateEngineRecord {
1961                    registration,
1962                    identity: CandidateRegistrationIdentity::New,
1963                },
1964            );
1965            *changed = true;
1966            Ok(())
1967        }
1968    }
1969}
1970
1971fn replace_engine_candidate(
1972    candidate: &mut CandidateConfig,
1973    registration: EngineRegistration,
1974    changed: &mut bool,
1975) -> Result<(), RuntimeConfigError> {
1976    let engine_id = registration.engine_id().clone();
1977    let Some(existing) = candidate.engines.get(&engine_id) else {
1978        return Err(RuntimeConfigError::MissingEngine { engine_id });
1979    };
1980    if existing.registration.candidate_identical(&registration) {
1981        return Ok(());
1982    }
1983    if existing.registration.provider_device_identity() != registration.provider_device_identity() {
1984        return Err(RuntimeConfigError::EngineTargetRebind {
1985            engine_id,
1986            current: existing.registration.provider_device_identity().clone(),
1987            replacement: registration.provider_device_identity().clone(),
1988        });
1989    }
1990    ensure_unique_provider_device_target_except(candidate, &registration, &engine_id)?;
1991    candidate.engines.insert(
1992        engine_id,
1993        CandidateEngineRecord {
1994            registration,
1995            identity: CandidateRegistrationIdentity::New,
1996        },
1997    );
1998    *changed = true;
1999    Ok(())
2000}
2001
2002fn remove_engine_candidate(
2003    candidate: &mut CandidateConfig,
2004    id: &EngineId,
2005    changed: &mut bool,
2006) -> Result<(), RuntimeConfigError> {
2007    match candidate.engines.remove(id) {
2008        Some(_) => {
2009            *changed = true;
2010            Ok(())
2011        }
2012        None => Err(RuntimeConfigError::MissingEngine {
2013            engine_id: id.clone(),
2014        }),
2015    }
2016}
2017
2018fn install_extension_module_candidate(
2019    candidate: &mut CandidateConfig,
2020    module: Arc<dyn ExtensionModule>,
2021    changed: &mut bool,
2022) -> Result<(), RuntimeConfigError> {
2023    let module_id = module.module_id().clone();
2024    match candidate.modules.get(&module_id) {
2025        Some(existing) if existing.module_identical(&module) => Ok(()),
2026        Some(_) => Err(RuntimeConfigError::ExtensionModule {
2027            source: ExtensionModuleError::ConflictingModule { module_id },
2028        }),
2029        None => {
2030            let record = configure_module(module)
2031                .map_err(|source| RuntimeConfigError::ExtensionModule { source })?;
2032            candidate.modules.insert(module_id, record);
2033            *changed = true;
2034            Ok(())
2035        }
2036    }
2037}
2038
2039fn replace_extension_module_candidate(
2040    candidate: &mut CandidateConfig,
2041    module: Arc<dyn ExtensionModule>,
2042    changed: &mut bool,
2043) -> Result<(), RuntimeConfigError> {
2044    let module_id = module.module_id().clone();
2045    match candidate.modules.get(&module_id) {
2046        Some(existing) if existing.module_identical(&module) => Ok(()),
2047        _ => {
2048            let record = configure_module(module)
2049                .map_err(|source| RuntimeConfigError::ExtensionModule { source })?;
2050            candidate.modules.insert(module_id, record);
2051            *changed = true;
2052            Ok(())
2053        }
2054    }
2055}
2056
2057fn remove_extension_module_candidate(
2058    candidate: &mut CandidateConfig,
2059    id: &ExtensionModuleId,
2060    changed: &mut bool,
2061) -> Result<(), RuntimeConfigError> {
2062    if candidate.modules.remove(id).is_some() {
2063        *changed = true;
2064    }
2065    Ok(())
2066}
2067
2068fn register_transfer_provider_candidate(
2069    candidate: &mut CandidateConfig,
2070    source: TransferEndpoint,
2071    destination: TransferEndpoint,
2072    provider: Arc<dyn TransferProvider>,
2073    changed: &mut bool,
2074) -> Result<(), RuntimeConfigError> {
2075    let key = TransferRoute::new(source, destination);
2076    match candidate.transfers.get(&key) {
2077        Some(existing) if Arc::ptr_eq(&existing.provider, &provider) => Ok(()),
2078        Some(_) => Err(RuntimeConfigError::ConflictingRegistration {
2079            key: RegistrationKey::TransferProvider {
2080                source: key.source().clone(),
2081                destination: key.destination().clone(),
2082            },
2083        }),
2084        None => {
2085            candidate.transfers.insert(
2086                key,
2087                CandidateTransferRecord {
2088                    provider,
2089                    binding: CandidateTransferBinding::New,
2090                },
2091            );
2092            *changed = true;
2093            Ok(())
2094        }
2095    }
2096}
2097
2098fn remove_transfer_provider_candidate(
2099    candidate: &mut CandidateConfig,
2100    source: TransferEndpoint,
2101    destination: TransferEndpoint,
2102    changed: &mut bool,
2103) -> Result<(), RuntimeConfigError> {
2104    let key = TransferRoute::new(source, destination);
2105    if candidate.transfers.remove(&key).is_none() {
2106        return Err(RuntimeConfigError::MissingTransferProvider {
2107            source_endpoint: key.source().clone(),
2108            destination: key.destination().clone(),
2109        });
2110    }
2111    *changed = true;
2112    Ok(())
2113}
2114
2115fn ensure_unique_provider_device_target(
2116    candidate: &CandidateConfig,
2117    registration: &EngineRegistration,
2118) -> Result<(), RuntimeConfigError> {
2119    ensure_unique_provider_device_target_except(candidate, registration, registration.engine_id())
2120}
2121
2122fn ensure_unique_provider_device_target_except(
2123    candidate: &CandidateConfig,
2124    registration: &EngineRegistration,
2125    ignored_engine_id: &EngineId,
2126) -> Result<(), RuntimeConfigError> {
2127    if let Some((first_engine_id, _)) = candidate.engines.iter().find(|(engine_id, record)| {
2128        *engine_id != ignored_engine_id
2129            && record.registration.provider_device_identity()
2130                == registration.provider_device_identity()
2131    }) {
2132        return Err(RuntimeConfigError::DuplicateProviderDeviceTarget {
2133            provider_device_identity: registration.provider_device_identity().clone(),
2134            first_engine_id: first_engine_id.clone(),
2135            duplicate_engine_id: registration.engine_id().clone(),
2136        });
2137    }
2138    Ok(())
2139}
2140
2141fn validate_candidate(
2142    candidate: CandidateConfig,
2143    issuer: NonZeroU64,
2144    next_ordinal: NonZeroU64,
2145) -> Result<(BoundCandidateConfig, NonZeroU64), RuntimeConfigError> {
2146    let mut seen_targets = BTreeMap::<ProviderDeviceIdentity, EngineId>::new();
2147    for (engine_id, record) in &candidate.engines {
2148        if let Some(first_engine_id) = seen_targets.insert(
2149            record.registration.provider_device_identity().clone(),
2150            engine_id.clone(),
2151        ) {
2152            return Err(RuntimeConfigError::DuplicateProviderDeviceTarget {
2153                provider_device_identity: record.registration.provider_device_identity().clone(),
2154                first_engine_id,
2155                duplicate_engine_id: engine_id.clone(),
2156            });
2157        }
2158    }
2159
2160    let mut bound_transfers = BTreeMap::new();
2161    for (route, record) in &candidate.transfers {
2162        let source_binding = validate_transfer_endpoint(&candidate, route.source())?;
2163        let destination_binding = validate_transfer_endpoint(&candidate, route.destination())?;
2164        let preserved = match &record.binding {
2165            CandidateTransferBinding::New => None,
2166            CandidateTransferBinding::Preserved {
2167                source,
2168                destination,
2169            } => Some((source, destination)),
2170        };
2171        if let Some((registered_source, registered_destination)) = preserved {
2172            if registered_source != &source_binding {
2173                return Err(RuntimeConfigError::StaleTransferRoute {
2174                    source_endpoint: route.source().clone(),
2175                    destination: route.destination().clone(),
2176                    endpoint: route.source().clone(),
2177                    registered: Box::new(registered_source.clone()),
2178                    current: Box::new(source_binding.clone()),
2179                });
2180            }
2181            if registered_destination != &destination_binding {
2182                return Err(RuntimeConfigError::StaleTransferRoute {
2183                    source_endpoint: route.source().clone(),
2184                    destination: route.destination().clone(),
2185                    endpoint: route.destination().clone(),
2186                    registered: Box::new(registered_destination.clone()),
2187                    current: Box::new(destination_binding.clone()),
2188                });
2189            }
2190        }
2191        bound_transfers.insert(
2192            route.clone(),
2193            BoundCandidateTransferRecord {
2194                provider: Arc::clone(&record.provider),
2195                source: source_binding,
2196                destination: destination_binding,
2197            },
2198        );
2199    }
2200    let mut seen = BTreeMap::<(ExtensionFamilyId, EngineId), ExtensionModuleId>::new();
2201    for (module_id, module) in &candidate.modules {
2202        for family_engine in module.engines.keys() {
2203            if seen
2204                .insert(
2205                    (family_engine.0, family_engine.1.clone()),
2206                    module_id.clone(),
2207                )
2208                .is_some()
2209            {
2210                return Err(RuntimeConfigError::ConflictingRegistration {
2211                    key: RegistrationKey::ExtensionEngine {
2212                        family: family_engine.0,
2213                        engine: family_engine.1.clone(),
2214                    },
2215                });
2216            }
2217        }
2218    }
2219
2220    let CandidateConfig {
2221        policy,
2222        engines,
2223        modules,
2224        transfers: _,
2225    } = candidate;
2226    let mut allocator = RegistrationIdentityAllocator::new(issuer, next_ordinal);
2227    let engines = engines
2228        .into_iter()
2229        .map(|(engine_id, record)| {
2230            let identity = match record.identity {
2231                CandidateRegistrationIdentity::New => allocator.allocate()?,
2232                CandidateRegistrationIdentity::Preserved(identity) => identity,
2233            };
2234            Ok((
2235                engine_id,
2236                BoundCandidateEngineRecord {
2237                    registration: record.registration,
2238                    identity,
2239                },
2240            ))
2241        })
2242        .collect::<Result<BTreeMap<_, _>, RuntimeConfigError>>()?;
2243    let modules = modules
2244        .into_iter()
2245        .map(|(module_id, module)| {
2246            let mut allocate = || allocator.allocate();
2247            Ok((module_id, bind_candidate_module(module, &mut allocate)?))
2248        })
2249        .collect::<Result<BTreeMap<_, _>, RuntimeConfigError>>()?;
2250    Ok((
2251        BoundCandidateConfig {
2252            policy,
2253            engines,
2254            modules,
2255            transfers: bound_transfers,
2256        },
2257        allocator.next_ordinal(),
2258    ))
2259}
2260
2261fn validate_transfer_endpoint(
2262    candidate: &CandidateConfig,
2263    endpoint: &TransferEndpoint,
2264) -> Result<ProviderDeviceIdentity, RuntimeConfigError> {
2265    let Some(engine) = candidate.engines.get(endpoint.engine_id()) else {
2266        return Err(RuntimeConfigError::UnknownTransferEndpointEngine {
2267            endpoint: endpoint.clone(),
2268        });
2269    };
2270    if !engine
2271        .registration
2272        .storage_classes()
2273        .contains(endpoint.storage_class())
2274    {
2275        return Err(RuntimeConfigError::UnsupportedTransferEndpointStorage {
2276            endpoint: endpoint.clone(),
2277        });
2278    }
2279    Ok(engine.registration.provider_device_identity().clone())
2280}
2281
2282struct RegistrationIdentityAllocator {
2283    issuer: NonZeroU64,
2284    next: NonZeroU64,
2285}
2286
2287impl RegistrationIdentityAllocator {
2288    fn new(issuer: NonZeroU64, next: NonZeroU64) -> Self {
2289        Self { issuer, next }
2290    }
2291
2292    fn allocate(&mut self) -> Result<RegistrationIdentity, RuntimeConfigError> {
2293        let identity = RegistrationIdentity::new(self.issuer, self.next);
2294        let next = self
2295            .next
2296            .get()
2297            .checked_add(1)
2298            .and_then(NonZeroU64::new)
2299            .ok_or(RuntimeConfigError::IdentityExhausted)?;
2300        self.next = next;
2301        Ok(identity)
2302    }
2303
2304    fn next_ordinal(&self) -> NonZeroU64 {
2305        self.next
2306    }
2307}
2308
2309fn freeze_candidate(
2310    runtime_id: RuntimeId,
2311    epoch: RuntimeEpoch,
2312    candidate: BoundCandidateConfig,
2313) -> Result<RuntimeConfigSnapshot, RuntimeConfigError> {
2314    let mut engines = Vec::with_capacity(candidate.engines.len());
2315    let mut engine_indices = BTreeMap::new();
2316    let mut engine_locations = BTreeMap::new();
2317    let mut cache_owners = Vec::new();
2318    for (index, (engine_id, record)) in candidate.engines.into_iter().enumerate() {
2319        let BoundCandidateEngineRecord {
2320            registration,
2321            identity,
2322        } = record;
2323        let event_domain_id = EventDomainId::new(runtime_id, epoch, identity);
2324        let (state, candidate_token) = registration.into_state_and_token();
2325        let provider_device_identity = state.provider_device_identity().clone();
2326        let metadata = FrozenEngineMetadata {
2327            candidate_token,
2328            identity,
2329            event_domain_id,
2330        };
2331        let frozen = match state {
2332            EngineRegistrationState::PreparationOnly { binding } => {
2333                FrozenEngineSlot::PreparationOnly(Arc::new(PreparationOnlyEngineSnapshot {
2334                    metadata,
2335                    binding,
2336                }))
2337            }
2338            EngineRegistrationState::Executable(binding) => {
2339                if let Some(owner) = binding.contract().cache_owner().cloned() {
2340                    cache_owners.push(FrozenCacheOwner {
2341                        id: engine_cache_owner_id(&engine_id),
2342                        kind: FrozenCacheOwnerKind::Engine,
2343                        owner,
2344                    });
2345                }
2346                cache_owners.push(FrozenCacheOwner {
2347                    id: engine_extension_cache_owner_id(&engine_id),
2348                    kind: FrozenCacheOwnerKind::Extension,
2349                    owner: execution::extension_cache_owner(binding.contract().executor().clone()),
2350                });
2351                FrozenEngineSlot::Executable(Arc::new(ExecutableEngineSnapshot {
2352                    metadata,
2353                    binding,
2354                }))
2355            }
2356        };
2357        engine_locations.insert(
2358            engine_id.clone(),
2359            (provider_device_identity, event_domain_id),
2360        );
2361        engine_indices.insert(engine_id, index);
2362        engines.push(frozen);
2363    }
2364    let extensions = freeze_extension_slots(candidate.modules)?;
2365    for (id, owner) in extensions.cache_owner_records() {
2366        cache_owners.push(FrozenCacheOwner {
2367            id,
2368            kind: FrozenCacheOwnerKind::Extension,
2369            owner,
2370        });
2371    }
2372    let mut transfers = BTreeMap::new();
2373    for (route, record) in candidate.transfers {
2374        let BoundCandidateTransferRecord {
2375            provider,
2376            source: source_binding,
2377            destination: destination_binding,
2378        } = record;
2379        let (_, source_event_domain_id) = bound_engine_location(&engine_locations, route.source())?;
2380        let (_, destination_event_domain_id) =
2381            bound_engine_location(&engine_locations, route.destination())?;
2382        let resolved_route = ResolvedTransferRoute::new(
2383            ResolvedTransferEndpoint::new(
2384                route.source().clone(),
2385                source_binding,
2386                *source_event_domain_id,
2387            ),
2388            ResolvedTransferEndpoint::new(
2389                route.destination().clone(),
2390                destination_binding,
2391                *destination_event_domain_id,
2392            ),
2393        );
2394        transfers.insert(resolved_route, provider);
2395    }
2396    Ok(RuntimeConfigSnapshot {
2397        runtime_id,
2398        epoch,
2399        policy: candidate.policy,
2400        engines: engines.into(),
2401        engine_indices,
2402        extensions,
2403        transfers: FrozenTransferRegistry::new(transfers),
2404        cache_owners: cache_owners.into(),
2405    })
2406}
2407
2408fn bound_engine_location<'a>(
2409    locations: &'a BTreeMap<EngineId, (ProviderDeviceIdentity, EventDomainId)>,
2410    endpoint: &TransferEndpoint,
2411) -> Result<&'a (ProviderDeviceIdentity, EventDomainId), RuntimeConfigError> {
2412    locations
2413        .get(endpoint.engine_id())
2414        .ok_or_else(|| RuntimeConfigError::BoundCandidateInvariant {
2415            endpoint: endpoint.clone(),
2416        })
2417}
2418
2419fn engine_cache_owner_id(engine_id: &EngineId) -> CacheOwnerId {
2420    let id = engine_id.as_str();
2421    CacheOwnerId::from_canonical_owner_id(Arc::<str>::from(format!("engine[{}]:{id}", id.len())))
2422}
2423
2424fn engine_extension_cache_owner_id(engine_id: &EngineId) -> CacheOwnerId {
2425    let id = engine_id.as_str();
2426    CacheOwnerId::from_canonical_owner_id(Arc::<str>::from(format!(
2427        "extension-executor[{}]:{id}",
2428        id.len()
2429    )))
2430}
2431
2432fn allocate_nonzero(counter: &AtomicU64) -> Result<NonZeroU64, RuntimeConfigError> {
2433    let value = counter
2434        .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |next| {
2435            next.checked_add(1)
2436        })
2437        .map_err(|_| RuntimeConfigError::IdentityExhausted)?;
2438    NonZeroU64::new(value).ok_or(RuntimeConfigError::IdentityExhausted)
2439}
2440
2441#[cfg(test)]
2442mod freeze_tests {
2443    use crate::{ProviderId, TransferRequest};
2444
2445    use super::*;
2446
2447    #[derive(Debug)]
2448    struct FreezeTestContext;
2449
2450    #[derive(Debug)]
2451    struct FreezeTestProvider;
2452
2453    impl TransferProvider for FreezeTestProvider {
2454        fn transfer_blocking(
2455            &self,
2456            _request: TransferRequest<'_>,
2457        ) -> crate::Result<tenferro_tensor::Tensor> {
2458            Err(crate::Error::Internal("freeze test provider".into()))
2459        }
2460    }
2461
2462    fn registration(
2463        engine_id: &str,
2464        target: &str,
2465    ) -> Result<EngineRegistration, RuntimeConfigError> {
2466        let engine_id = EngineId::new(engine_id).map_err(RuntimeConfigError::from)?;
2467        let storage =
2468            StorageClass::new("tenferro.test.freeze.storage").map_err(RuntimeConfigError::from)?;
2469        Ok(EngineRegistration::preparation_only(
2470            super::super::ProviderPreparationBinding::new(
2471                engine_id,
2472                ProviderDeviceIdentity::new(
2473                    ProviderId::new("tenferro.test.freeze.provider")
2474                        .map_err(RuntimeConfigError::from)?,
2475                    target,
2476                )
2477                .map_err(RuntimeConfigError::from)?,
2478                ExecutionContextIdentity::of::<FreezeTestContext>(),
2479                HardwareClassId::new("tenferro.test.freeze.hardware")
2480                    .map_err(RuntimeConfigError::from)?,
2481                Arc::from(vec![storage.clone()]),
2482                storage,
2483                CoreCapabilityBundle::default(),
2484            )?,
2485        ))
2486    }
2487
2488    fn candidate(binding: CandidateTransferBinding) -> Result<CandidateConfig, RuntimeConfigError> {
2489        let source_id =
2490            EngineId::new("tenferro.test.freeze.source").map_err(RuntimeConfigError::from)?;
2491        let destination_id =
2492            EngineId::new("tenferro.test.freeze.destination").map_err(RuntimeConfigError::from)?;
2493        let storage =
2494            StorageClass::new("tenferro.test.freeze.storage").map_err(RuntimeConfigError::from)?;
2495        let source_endpoint = TransferEndpoint::new(source_id.clone(), storage.clone());
2496        let destination_endpoint = TransferEndpoint::new(destination_id.clone(), storage);
2497        let mut candidate = CandidateConfig::empty();
2498        let mut changed = false;
2499        register_engine_candidate(
2500            &mut candidate,
2501            registration(source_id.as_str(), "freeze-source")?,
2502            &mut changed,
2503        )?;
2504        register_engine_candidate(
2505            &mut candidate,
2506            registration(destination_id.as_str(), "freeze-destination")?,
2507            &mut changed,
2508        )?;
2509        register_transfer_provider_candidate(
2510            &mut candidate,
2511            source_endpoint.clone(),
2512            destination_endpoint.clone(),
2513            Arc::new(FreezeTestProvider),
2514            &mut changed,
2515        )?;
2516        candidate
2517            .transfers
2518            .get_mut(&TransferRoute::new(source_endpoint, destination_endpoint))
2519            .expect("registered route")
2520            .binding = binding;
2521        Ok(candidate)
2522    }
2523
2524    #[test]
2525    fn validation_owns_stale_route_rejection_and_bound_freeze_is_total() {
2526        let wrong_source = ProviderDeviceIdentity::new(
2527            ProviderId::new("tenferro.test.freeze.provider").unwrap(),
2528            "different-source",
2529        )
2530        .unwrap();
2531        let preserved = CandidateTransferBinding::Preserved {
2532            source: wrong_source,
2533            destination: ProviderDeviceIdentity::new(
2534                ProviderId::new("tenferro.test.freeze.provider").unwrap(),
2535                "freeze-destination",
2536            )
2537            .unwrap(),
2538        };
2539        let result = validate_candidate(
2540            candidate(preserved).unwrap(),
2541            NonZeroU64::new(1).unwrap(),
2542            NonZeroU64::new(1).unwrap(),
2543        );
2544        let error = match result {
2545            Ok(_) => panic!("candidate validation must reject stale preserved bindings"),
2546            Err(error) => error,
2547        };
2548        assert!(matches!(
2549            error,
2550            RuntimeConfigError::StaleTransferRoute { .. }
2551        ));
2552
2553        let (bound, _) = validate_candidate(
2554            candidate(CandidateTransferBinding::New).unwrap(),
2555            NonZeroU64::new(1).unwrap(),
2556            NonZeroU64::new(1).unwrap(),
2557        )
2558        .expect("validation must produce a complete bound candidate");
2559        freeze_candidate(
2560            RuntimeId::from_nonzero(NonZeroU64::new(1).unwrap()),
2561            RuntimeEpoch::one(),
2562            bound,
2563        )
2564        .expect("a bound candidate must freeze without semantic route revalidation");
2565    }
2566
2567    #[test]
2568    fn frozen_engine_slots_are_arc_sized() {
2569        let slot_size = std::mem::size_of::<FrozenEngineSlot>();
2570        let arc_size = std::mem::size_of::<Arc<()>>();
2571
2572        assert!(
2573            slot_size <= 2 * arc_size,
2574            "frozen engine slots should keep immutable snapshot payloads behind Arc: slot_size={slot_size}, arc_size={arc_size}",
2575        );
2576    }
2577}