Skip to main content

tenferro_runtime/runtime/
error.rs

1use std::error::Error;
2use std::fmt;
3use std::sync::Arc;
4
5use super::capability::{
6    CoreCapabilityKind, PreparationKeySummary, PreparedOperationBinding, UnsupportedReason,
7};
8use super::identity::{EngineId, ProviderDeviceIdentity, RuntimeEpoch, RuntimeId};
9use super::policy::{ProgramPlacementConstraint, StorageClass, TransferEndpoint};
10use super::specialization::SpecializationProjection;
11use super::{CacheOwnerId, ExtensionModuleId};
12use tenferro_ops::ShapeGuardError;
13use tenferro_tensor::{AllocationDomainId, Error as TensorError, Placement};
14
15/// Classifies a malformed runtime identifier without retaining its input.
16///
17/// # Examples
18///
19/// ```
20/// use tenferro_runtime::EngineId;
21///
22/// let error = EngineId::new("not namespaced").unwrap_err();
23/// assert_eq!(error.kind(), tenferro_runtime::IdentityKind::Engine);
24/// ```
25#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
26#[error("malformed {kind:?} identifier")]
27pub struct IdentityError {
28    kind: IdentityKind,
29}
30
31impl IdentityError {
32    /// Return the kind of identifier that failed validation.
33    ///
34    /// # Examples
35    ///
36    /// ```
37    /// use tenferro_runtime::{EngineId, IdentityKind};
38    ///
39    /// assert_eq!(EngineId::new("engine").unwrap_err().kind(), IdentityKind::Engine);
40    /// ```
41    pub fn kind(&self) -> IdentityKind {
42        self.kind
43    }
44
45    pub(super) const fn malformed(kind: IdentityKind) -> Self {
46        Self { kind }
47    }
48}
49
50/// Identifies the runtime namespace validated by an opaque identifier.
51///
52/// # Examples
53///
54/// ```
55/// use tenferro_runtime::IdentityKind;
56///
57/// assert_eq!(IdentityKind::CacheOwner, IdentityKind::CacheOwner);
58/// ```
59#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
60#[non_exhaustive]
61pub enum IdentityKind {
62    /// An execution engine identifier.
63    Engine,
64    /// A provider identifier.
65    Provider,
66    /// A provider-canonical physical target identity.
67    ProviderTarget,
68    /// A hardware-class identifier.
69    HardwareClass,
70    /// A storage-class identifier.
71    StorageClass,
72    /// A layout-class identifier.
73    LayoutClass,
74    /// A runtime cache-owner identifier.
75    CacheOwner,
76    /// An extension module identifier.
77    ExtensionModule,
78}
79
80/// Reports invalid placement constraints.
81///
82/// # Examples
83///
84/// ```
85/// use tenferro_runtime::{EngineId, PlacementConstraintError, ProgramPlacementConstraint};
86///
87/// let engine = EngineId::new("tenferro.cpu").unwrap();
88/// let error = ProgramPlacementConstraint::new(vec![engine.clone(), engine], None).unwrap_err();
89/// assert!(matches!(error, PlacementConstraintError::DuplicateEngine { .. }));
90/// ```
91#[derive(Debug, Eq, PartialEq, thiserror::Error)]
92#[non_exhaustive]
93pub enum PlacementConstraintError {
94    /// The same engine appears more than once in a preference list.
95    #[error(
96        "engine {engine_id:?} is duplicated at positions \
97         {first_index} and {duplicate_index}"
98    )]
99    DuplicateEngine {
100        /// The duplicated engine identifier.
101        engine_id: EngineId,
102        /// The first position where the engine appeared.
103        first_index: usize,
104        /// The duplicate position that failed validation.
105        duplicate_index: usize,
106    },
107}
108
109/// Identifies a runtime registration slot involved in a configuration conflict.
110///
111/// # Examples
112///
113/// ```
114/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
115/// use tenferro_runtime::{EngineId, RegistrationKey};
116///
117/// let key = RegistrationKey::Engine(EngineId::new("tenferro.cpu")?);
118/// assert!(format!("{key:?}").contains("tenferro.cpu"));
119/// # Ok(())
120/// # }
121/// ```
122#[derive(Clone, Debug, Eq, PartialEq)]
123#[non_exhaustive]
124pub enum RegistrationKey {
125    /// A direct engine registration.
126    Engine(EngineId),
127    /// An extension engine registration.
128    ExtensionEngine {
129        /// Extension family.
130        family: &'static str,
131        /// Runtime engine.
132        engine: EngineId,
133    },
134    /// An extension planning config registration.
135    ExtensionPlanning {
136        /// Extension module.
137        module: ExtensionModuleId,
138        /// Runtime engine.
139        engine: EngineId,
140    },
141    /// An extension cache-owner registration.
142    ExtensionCacheOwner {
143        /// Extension module.
144        module: ExtensionModuleId,
145        /// Local cache owner.
146        owner: CacheOwnerId,
147    },
148    /// A transfer provider keyed by source and destination endpoints.
149    TransferProvider {
150        /// Source endpoint.
151        source: TransferEndpoint,
152        /// Destination endpoint.
153        destination: TransferEndpoint,
154    },
155}
156
157/// Reports invalid execution-policy configuration.
158///
159/// Phase 4 currently has no invalid public execution policy state, but the
160/// error type is part of the accepted runtime configuration contract.
161///
162/// # Examples
163///
164/// ```
165/// use std::fmt::Debug;
166/// use tenferro_runtime::ExecutionPolicyError;
167///
168/// fn requires_debug<T: Debug>() {}
169/// requires_debug::<ExecutionPolicyError>();
170/// ```
171#[derive(Clone, Copy, Debug, Eq, PartialEq)]
172#[non_exhaustive]
173pub enum ExecutionPolicyError {}
174
175impl fmt::Display for ExecutionPolicyError {
176    fn fmt(&self, _formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
177        match *self {}
178    }
179}
180
181impl std::error::Error for ExecutionPolicyError {}
182
183/// Reports invalid runtime configuration.
184///
185/// # Examples
186///
187/// ```
188/// use tenferro_runtime::{EngineId, RuntimeConfigError};
189///
190/// let error = RuntimeConfigError::DuplicateEngine {
191///     engine_id: EngineId::new("tenferro.cpu").unwrap(),
192/// };
193/// assert!(error.to_string().contains("already registered"));
194/// ```
195#[derive(Debug, thiserror::Error)]
196#[non_exhaustive]
197pub enum RuntimeConfigError {
198    /// Runtime identity or registration identity allocation exhausted.
199    #[error("runtime identity space exhausted")]
200    IdentityExhausted,
201    /// A namespaced runtime identifier was malformed.
202    #[error("malformed {kind:?} identifier")]
203    MalformedIdentity {
204        /// Identifier class that failed validation.
205        kind: IdentityKind,
206    },
207    /// An engine registration tried to insert a different value for an
208    /// already-registered engine ID.
209    #[error("engine {engine_id:?} is already registered")]
210    DuplicateEngine {
211        /// Duplicated engine identifier.
212        engine_id: EngineId,
213    },
214    /// Two direct engines selected the same provider/device target.
215    #[error(
216        "provider/device target {provider_device_identity:?} is already bound to engine \
217         {first_engine_id:?}; cannot bind engine {duplicate_engine_id:?}"
218    )]
219    DuplicateProviderDeviceTarget {
220        /// Physical provider/device binding that was selected twice.
221        provider_device_identity: ProviderDeviceIdentity,
222        /// First logical engine using the binding.
223        first_engine_id: EngineId,
224        /// Later logical engine that attempted to reuse the binding.
225        duplicate_engine_id: EngineId,
226    },
227    /// A replacement attempted to change the physical target of an engine ID.
228    #[error(
229        "engine {engine_id:?} cannot rebind from provider/device target {current:?} \
230         to {replacement:?}; remove affected transfer routes, remove the old engine, \
231         register the replacement under the engine ID, and re-register the routes"
232    )]
233    EngineTargetRebind {
234        /// Logical engine whose physical binding was changed.
235        engine_id: EngineId,
236        /// Binding currently attached to the engine.
237        current: ProviderDeviceIdentity,
238        /// Binding requested by the replacement.
239        replacement: ProviderDeviceIdentity,
240    },
241    /// A direct capability slot was registered twice where replacement is not
242    /// accepted.
243    #[error("duplicate {capability:?} capability")]
244    DuplicateCapability {
245        /// Duplicated capability kind.
246        capability: CoreCapabilityKind,
247    },
248    /// A registration key conflicted across transactional registration input.
249    #[error("conflicting registration {key:?}")]
250    ConflictingRegistration {
251        /// Conflicting registration key.
252        key: RegistrationKey,
253    },
254    /// A requested engine replacement or removal had no existing record.
255    #[error("engine {engine_id:?} is not registered")]
256    MissingEngine {
257        /// Missing engine identifier.
258        engine_id: EngineId,
259    },
260    /// An extension module does not register a requested family for an engine.
261    #[error(
262        "extension module {module_id:?} family {family_id:?} has no engine registration for runtime engine {engine_id:?}"
263    )]
264    MissingExtensionEngine {
265        /// Extension module whose engine registration is missing.
266        module_id: ExtensionModuleId,
267        /// Extension family whose engine registration is missing.
268        family_id: &'static str,
269        /// Runtime engine selected by the owner.
270        engine_id: EngineId,
271    },
272    /// Engine registration provided no supported storage classes.
273    #[error("engine {engine_id:?} has no storage classes")]
274    EmptyStorageClasses {
275        /// Engine being registered.
276        engine_id: EngineId,
277    },
278    /// Engine registration repeated a storage class.
279    #[error(
280        "engine {engine_id:?} storage class {storage_class:?} is duplicated at \
281         positions {first_index} and {duplicate_index}"
282    )]
283    DuplicateStorageClass {
284        /// Engine being registered.
285        engine_id: EngineId,
286        /// Duplicated storage class.
287        storage_class: StorageClass,
288        /// First position where the storage class appeared.
289        first_index: usize,
290        /// Duplicate position that failed validation.
291        duplicate_index: usize,
292    },
293    /// Engine registration selected a default storage class that is not in its
294    /// supported list.
295    #[error("engine {engine_id:?} default storage class {default_storage_class:?} is not listed")]
296    DefaultStorageClassNotListed {
297        /// Engine being registered.
298        engine_id: EngineId,
299        /// Missing default storage class.
300        default_storage_class: StorageClass,
301    },
302    /// A transfer endpoint references an engine that is absent from the
303    /// candidate configuration.
304    #[error("transfer endpoint {endpoint:?} references an unregistered engine")]
305    UnknownTransferEndpointEngine {
306        /// Endpoint that references the missing engine.
307        endpoint: TransferEndpoint,
308    },
309    /// A transfer endpoint names a storage class unsupported by its engine.
310    #[error("transfer endpoint {endpoint:?} uses storage unsupported by its engine")]
311    UnsupportedTransferEndpointStorage {
312        /// Endpoint whose storage class is unsupported by its engine.
313        endpoint: TransferEndpoint,
314    },
315    /// A transfer route removal named an endpoint pair that is not registered.
316    #[error(
317        "no transfer provider is registered from source endpoint {source_endpoint:?} to destination \
318         endpoint {destination:?}"
319    )]
320    MissingTransferProvider {
321        /// Source endpoint of the absent route.
322        source_endpoint: TransferEndpoint,
323        /// Destination endpoint of the absent route.
324        destination: TransferEndpoint,
325    },
326    /// A route retains a physical binding that no longer matches its engine.
327    #[error(
328        "stale transfer route from source endpoint {source_endpoint:?} to destination endpoint \
329         {destination:?}: endpoint {endpoint:?} was registered for {registered:?} but \
330         now resolves to {current:?}"
331    )]
332    StaleTransferRoute {
333        /// Source endpoint of the stale route.
334        source_endpoint: TransferEndpoint,
335        /// Destination endpoint of the stale route.
336        destination: TransferEndpoint,
337        /// Route endpoint whose physical binding changed.
338        endpoint: TransferEndpoint,
339        /// Binding captured when this route was registered/frozen.
340        registered: Box<ProviderDeviceIdentity>,
341        /// Binding currently attached to the endpoint's engine.
342        current: Box<ProviderDeviceIdentity>,
343    },
344    /// A route reached freezing without a physical binding after validation.
345    #[error(
346        "transfer route from source endpoint {source_endpoint:?} to destination endpoint {destination:?} \
347         has no resolved provider/device binding"
348    )]
349    UnboundTransferRoute {
350        /// Source endpoint of the unbound route.
351        source_endpoint: TransferEndpoint,
352        /// Destination endpoint of the unbound route.
353        destination: TransferEndpoint,
354    },
355    /// A bound candidate referenced an engine that disappeared before freeze;
356    /// this indicates runtime corruption rather than candidate validation.
357    #[error("bound candidate invariant failed for transfer endpoint {endpoint:?}")]
358    BoundCandidateInvariant {
359        /// Endpoint whose already-bound engine lookup failed.
360        endpoint: TransferEndpoint,
361    },
362    /// An execution bridge omitted the validators required for explicit input
363    /// ingress and destination-buffer residency.
364    #[error("engine {engine_id:?} execution bridge has no complete input ingress validator")]
365    MissingInputIngressValidator {
366        /// Engine with the incomplete execution contract.
367        engine_id: EngineId,
368    },
369    /// A replacement attempted to reuse an engine ID with a different execution
370    /// context identity in a context where that is invalid.
371    #[error("engine {engine_id:?} context identity mismatch")]
372    ContextIdentityMismatch {
373        /// Engine with the mismatched context identity.
374        engine_id: EngineId,
375    },
376    /// Execution policy failed validation.
377    #[error("invalid execution policy: {reason}")]
378    InvalidExecutionPolicy {
379        /// Typed policy validation reason.
380        reason: ExecutionPolicyError,
381    },
382    /// Extension module transaction failed.
383    #[error("extension module registration failed")]
384    ExtensionModule {
385        /// Typed extension module source.
386        #[source]
387        source: ExtensionModuleError,
388    },
389}
390
391/// Reports a runtime execution bridge that violated its output contract.
392///
393/// # Examples
394///
395/// ```
396/// use tenferro_runtime::{EngineExecutionContractError, EngineId, StorageClass};
397///
398/// let error = EngineExecutionContractError::OutputResidencyMismatch {
399///     instruction_index: 3,
400///     output_slot: 7,
401///     engine_id: EngineId::new("tenferro.cpu")?,
402///     storage_class: StorageClass::new("tenferro.storage.host")?,
403///     backend_family: Some("foreign-backend"),
404///     allocation_domain: None,
405/// };
406/// assert!(error.to_string().contains("output slot 7"));
407/// # Ok::<(), Box<dyn std::error::Error>>(())
408/// ```
409#[derive(Debug, thiserror::Error)]
410#[non_exhaustive]
411pub enum EngineExecutionContractError {
412    /// An executor returned a tensor not owned by its scheduled engine/storage.
413    #[error(
414        "instruction {instruction_index} output slot {output_slot} is not resident in \
415         engine {engine_id:?} storage {storage_class:?} \
416         (backend family {backend_family:?}, allocation domain {allocation_domain:?})"
417    )]
418    OutputResidencyMismatch {
419        /// Execution-program instruction that produced the invalid tensor.
420        instruction_index: usize,
421        /// Execution slot containing the invalid tensor.
422        output_slot: usize,
423        /// Engine selected by the prepared schedule.
424        engine_id: EngineId,
425        /// Storage class selected by the prepared schedule.
426        storage_class: StorageClass,
427        /// Physical backend family reported by the tensor.
428        backend_family: Option<&'static str>,
429        /// Physical allocation domain reported by the tensor.
430        allocation_domain: Option<AllocationDomainId>,
431    },
432}
433
434/// Reports a runtime input that violates a prepared ingress contract.
435///
436/// # Examples
437///
438/// ```
439/// use tenferro_runtime::{EngineId, InputIngressContractError, StorageClass};
440/// use tenferro_tensor::Placement;
441///
442/// let error = InputIngressContractError::ResidencyMismatch {
443///     input_slot: 0,
444///     ingress_engine_id: EngineId::new("tenferro.cpu")?,
445///     ingress_storage_class: StorageClass::new("tenferro.storage.host")?,
446///     placement: Placement::default(),
447///     backend_family: Some("foreign-backend"),
448///     allocation_domain: None,
449/// };
450/// assert!(error.to_string().contains("input slot 0"));
451/// # Ok::<(), Box<dyn std::error::Error>>(())
452/// ```
453#[derive(Debug, thiserror::Error)]
454#[non_exhaustive]
455pub enum InputIngressContractError {
456    /// A runtime input's physical residency is incompatible with its prepared ingress.
457    #[error(
458        "input slot {input_slot} at placement {placement:?} with backend family \
459         {backend_family:?} and allocation domain {allocation_domain:?} is not accepted by \
460         ingress engine {ingress_engine_id:?} storage {ingress_storage_class:?}"
461    )]
462    ResidencyMismatch {
463        /// Execution slot containing the rejected runtime input.
464        input_slot: usize,
465        /// Engine selected as the prepared input ingress.
466        ingress_engine_id: EngineId,
467        /// Storage class selected as the prepared input ingress.
468        ingress_storage_class: StorageClass,
469        /// Logical placement reported by the input.
470        placement: Placement,
471        /// Physical backend family reported by the input.
472        backend_family: Option<&'static str>,
473        /// Physical allocation domain reported by the input.
474        allocation_domain: Option<AllocationDomainId>,
475    },
476}
477
478impl From<IdentityError> for RuntimeConfigError {
479    fn from(source: IdentityError) -> Self {
480        Self::MalformedIdentity {
481            kind: source.kind(),
482        }
483    }
484}
485
486/// Reports failure to publish a runtime reconfiguration.
487///
488/// # Examples
489///
490/// ```
491/// use tenferro_runtime::{RuntimeConfigError, RuntimeReconfigureError};
492///
493/// let error = RuntimeReconfigureError::Edit {
494///     source: RuntimeConfigError::IdentityExhausted,
495/// };
496/// assert!(std::error::Error::source(&error).is_some());
497/// ```
498#[derive(Debug, thiserror::Error)]
499#[non_exhaustive]
500pub enum RuntimeReconfigureError {
501    /// Runtime state could not be read or published.
502    #[error("runtime state failed")]
503    State {
504        /// Typed state source.
505        #[source]
506        source: super::RuntimeStateError,
507    },
508    /// The edit closure or publication validation rejected the candidate.
509    #[error("runtime reconfiguration edit failed")]
510    Edit {
511        /// Typed configuration source.
512        #[source]
513        source: RuntimeConfigError,
514    },
515    /// Another writer published over the snapshot this edit was based on.
516    #[error("runtime was concurrently reconfigured from {base:?} to {current:?}")]
517    ConcurrentReconfiguration {
518        /// Epoch captured before the edit closure ran.
519        base: RuntimeEpoch,
520        /// Epoch observed at publication time.
521        current: RuntimeEpoch,
522    },
523    /// Runtime epoch cannot advance without wrapping.
524    #[error("runtime epoch exhausted at {current:?}")]
525    EpochExhausted {
526        /// Current maximum epoch.
527        current: RuntimeEpoch,
528    },
529}
530
531/// Reports invalid extension module registration transactions.
532///
533/// # Examples
534///
535/// ```
536/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
537/// use tenferro_runtime::{EngineId, ExtensionModuleError, ExtensionModuleId};
538///
539/// let error = ExtensionModuleError::MissingPlanningConfig {
540///     module_id: ExtensionModuleId::new("tenferro.module.test")?,
541///     engine_id: EngineId::new("tenferro.engine.test")?,
542/// };
543/// assert!(error.to_string().contains("planning config"));
544/// # Ok(())
545/// # }
546/// ```
547#[derive(Debug, thiserror::Error)]
548#[non_exhaustive]
549pub enum ExtensionModuleError {
550    /// A distinct module is already installed at the same module ID.
551    #[error("extension module {module_id:?} is already installed")]
552    ConflictingModule {
553        /// Conflicting module ID.
554        module_id: ExtensionModuleId,
555    },
556    /// A distinct extension engine is already registered for the same
557    /// `(family, engine)` key.
558    #[error("extension module {module_id:?} has conflicting engine {family_id:?}/{engine_id:?}")]
559    ConflictingEngine {
560        /// Extension module ID.
561        module_id: ExtensionModuleId,
562        /// Extension family ID.
563        family_id: &'static str,
564        /// Runtime engine ID.
565        engine_id: EngineId,
566    },
567    /// A planning config was registered without exactly one matching engine.
568    #[error(
569        "extension module {module_id:?} registered planning config without engine {engine_id:?}"
570    )]
571    PlanningConfigWithoutEngine {
572        /// Extension module ID.
573        module_id: ExtensionModuleId,
574        /// Runtime engine ID.
575        engine_id: EngineId,
576    },
577    /// Planning config family did not match the registered engine family.
578    #[error(
579        "extension module {module_id:?} config for engine {engine_id:?} has family \
580         {actual:?}, expected {expected:?}"
581    )]
582    PlanningConfigFamilyMismatch {
583        /// Extension module ID.
584        module_id: ExtensionModuleId,
585        /// Runtime engine ID.
586        engine_id: EngineId,
587        /// Expected extension family ID.
588        expected: &'static str,
589        /// Actual extension family ID.
590        actual: &'static str,
591    },
592    /// An engine was registered without its required planning config.
593    #[error("extension module {module_id:?} engine {engine_id:?} has no planning config")]
594    MissingPlanningConfig {
595        /// Extension module ID.
596        module_id: ExtensionModuleId,
597        /// Runtime engine ID.
598        engine_id: EngineId,
599    },
600    /// A distinct planning config is already registered for the same engine.
601    #[error("extension module {module_id:?} has conflicting planning config for {engine_id:?}")]
602    ConflictingPlanningConfig {
603        /// Extension module ID.
604        module_id: ExtensionModuleId,
605        /// Runtime engine ID.
606        engine_id: EngineId,
607    },
608    /// A distinct cache owner is already registered for the same local owner ID.
609    #[error("extension module {module_id:?} has conflicting cache owner {owner:?}")]
610    ConflictingCacheOwner {
611        /// Extension module ID.
612        module_id: ExtensionModuleId,
613        /// Local cache owner ID.
614        owner: CacheOwnerId,
615    },
616}
617
618/// Reports failures while building value-free input signatures.
619///
620/// # Examples
621///
622/// ```
623/// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
624/// use tenferro_tensor::Placement;
625///
626/// let error = InputSignatureEntry::new(
627///     DType::F64,
628///     [2_usize].into_iter().collect(),
629///     Placement::default(),
630///     LayoutClass::new("tenferro.layout.strided").unwrap(),
631///     [1_isize, 2].into_iter().collect(),
632///     None,
633/// )
634/// .unwrap_err();
635/// assert!(matches!(error, tenferro_runtime::InputSignatureError::ShapeStrideRankMismatch { .. }));
636/// ```
637#[derive(Debug, thiserror::Error)]
638pub enum InputSignatureError {
639    /// Shape rank and stride rank disagree.
640    #[error("shape rank {rank} does not match stride count {stride_count}")]
641    ShapeStrideRankMismatch {
642        /// Number of shape axes.
643        rank: usize,
644        /// Number of stride axes.
645        stride_count: usize,
646    },
647    /// The alignment class is outside the finite `usize` alignment lattice.
648    #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
649    InvalidAlignmentClass {
650        /// Base-2 logarithm of the requested alignment.
651        alignment_log2: u8,
652    },
653    /// Tensor metadata could not be read for an input.
654    #[error("input {input} metadata is unavailable")]
655    TensorMetadata {
656        /// Input position in the prepare request.
657        input: usize,
658        /// Original typed tensor error.
659        source: TensorError,
660    },
661}
662
663/// Explains why rank specialization is required by a requested field.
664///
665/// # Examples
666///
667/// ```
668/// use tenferro_runtime::RankRequirement;
669///
670/// assert_eq!(RankRequirement::ExactStrides.to_string(), "exact strides");
671/// ```
672#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
673pub enum RankRequirement {
674    /// A concrete axis dimension was requested.
675    ConcreteAxis {
676        /// Axis that requires rank specialization.
677        axis: u32,
678    },
679    /// Exact strides were requested.
680    ExactStrides,
681}
682
683impl std::fmt::Display for RankRequirement {
684    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
685        match self {
686            Self::ConcreteAxis { axis } => write!(formatter, "concrete axis {axis}"),
687            Self::ExactStrides => formatter.write_str("exact strides"),
688        }
689    }
690}
691
692/// Reports invalid input-specialization requirements.
693///
694/// # Examples
695///
696/// ```
697/// use tenferro_runtime::{
698///     InputSpecializationRequirements, InputSpecializationRequirementsError, RankRequirement,
699/// };
700///
701/// let mut builder = InputSpecializationRequirements::builder();
702/// builder.rank(false).concrete_dimensions(vec![2]);
703/// assert_eq!(
704///     builder.build().unwrap_err(),
705///     InputSpecializationRequirementsError::RankRequired {
706///         reason: RankRequirement::ConcreteAxis { axis: 2 },
707///     }
708/// );
709/// ```
710#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
711pub enum InputSpecializationRequirementsError {
712    /// The same concrete axis appears more than once.
713    #[error("axis {axis} is duplicated at positions {first_index} and {duplicate_index}")]
714    DuplicateAxis {
715        /// Duplicated concrete axis.
716        axis: u32,
717        /// First position carrying the axis.
718        first_index: usize,
719        /// Duplicate position that failed validation.
720        duplicate_index: usize,
721    },
722    /// A requested specialization field requires rank specialization.
723    #[error("{reason} requires rank specialization")]
724    RankRequired {
725        /// Requirement that needs rank specialization.
726        reason: RankRequirement,
727    },
728    /// The alignment class is outside the finite `usize` alignment lattice.
729    #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
730    InvalidAlignmentClass {
731        /// Base-2 logarithm of the requested alignment.
732        alignment_log2: u8,
733    },
734}
735
736/// Reports failures while projecting signatures through specialization requirements.
737///
738/// # Examples
739///
740/// ```
741/// use tenferro_runtime::{InputSignature, SpecializationRequirements};
742///
743/// let error = SpecializationRequirements::polymorphic(1)
744///     .project(&InputSignature::new(Vec::new()))
745///     .unwrap_err();
746/// assert!(matches!(error, tenferro_runtime::PrepareError::Specialization { .. }));
747/// ```
748#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
749pub enum SpecializationError {
750    /// The signature does not have the required input arity.
751    #[error("expected {expected} inputs but got {actual}")]
752    WrongInputCount {
753        /// Required input count.
754        expected: usize,
755        /// Actual input count.
756        actual: usize,
757    },
758    /// A concrete-axis request names an axis outside the actual rank.
759    #[error("input {input} axis {axis} is outside rank {rank}")]
760    AxisOutOfRange {
761        /// Input position in the signature.
762        input: usize,
763        /// Requested axis.
764        axis: u32,
765        /// Actual rank.
766        rank: usize,
767    },
768    /// A required host-pointer alignment class is unavailable.
769    #[error("input {input} has unknown alignment; required class {required_alignment_log2}")]
770    AlignmentUnavailable {
771        /// Input position in the signature.
772        input: usize,
773        /// Required base-2 logarithm alignment class.
774        required_alignment_log2: u8,
775    },
776    /// A specialization projection could not encode every requested axis in
777    /// the finite public axis type.
778    #[error("input {input} rank {rank} is too large to project into u32 axes")]
779    ProjectionOverflow {
780        /// Input position in the signature.
781        input: usize,
782        /// Rank that could not be encoded.
783        rank: usize,
784    },
785}
786
787/// A typed execution-context mismatch from erased runtime dispatch.
788///
789/// # Examples
790///
791/// ```
792/// use tenferro_runtime::{ExecutionContextIdentity, ExecutionContextMismatch};
793///
794/// let error = ExecutionContextMismatch {
795///     expected: ExecutionContextIdentity::of::<u64>(),
796///     actual: ExecutionContextIdentity::of::<u32>(),
797/// };
798/// assert_eq!(error.expected, ExecutionContextIdentity::of::<u64>());
799/// ```
800#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
801#[error("execution context mismatch: expected {expected:?}, actual {actual:?}")]
802pub struct ExecutionContextMismatch {
803    /// Expected execution-context type identity.
804    pub expected: super::ExecutionContextIdentity,
805    /// Actual stored execution-context type identity.
806    pub actual: super::ExecutionContextIdentity,
807}
808
809/// Reports provider violations of runtime preparation contracts.
810///
811/// # Examples
812///
813/// ```
814/// use tenferro_runtime::{CoreCapabilityKind, ProviderContractError};
815///
816/// let error = ProviderContractError::WrongOperationFamily {
817///     expected: CoreCapabilityKind::Elementwise,
818///     operation: "dot_general",
819/// };
820/// assert!(error.to_string().contains("dot_general"));
821/// ```
822#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
823pub enum ProviderContractError {
824    /// A provider accepted a request from the wrong core operation family.
825    #[error("operation {operation:?} is not in {expected:?}")]
826    WrongOperationFamily {
827        /// Expected core capability family.
828        expected: CoreCapabilityKind,
829        /// Operation diagnostic name.
830        operation: &'static str,
831    },
832    /// A prepared operation returned a different binding than requested.
833    #[error("prepared binding mismatch")]
834    BindingMismatch {
835        /// Runtime-created binding requested from the provider.
836        expected: Box<PreparedOperationBinding>,
837        /// Binding returned by the provider.
838        actual: Box<PreparedOperationBinding>,
839    },
840    /// A prepared operation returned a different specialization projection.
841    #[error("prepared specialization projection mismatch")]
842    ProjectionMismatch {
843        /// Runtime-created projection requested from the provider.
844        expected: Box<SpecializationProjection>,
845        /// Projection returned by the provider.
846        actual: Box<SpecializationProjection>,
847    },
848    /// A provider returned an invalid specialization.
849    #[error("invalid provider specialization: {source}")]
850    InvalidSpecialization {
851        /// Original specialization failure.
852        #[source]
853        source: SpecializationError,
854    },
855    /// A provider requested specialization that did not strictly widen the
856    /// current requirements.
857    #[error("specialization did not strictly widen the current requirements")]
858    NonWideningSpecialization {
859        /// Previous specialization requirements.
860        previous: Box<super::SpecializationRequirements>,
861        /// Provider-requested specialization requirements.
862        next: Box<super::SpecializationRequirements>,
863    },
864    /// A provider exceeded the finite specialization retry bound.
865    #[error("specialization retry limit exceeded after {attempts} attempts; limit was {limit}")]
866    SpecializationRetryLimitExceeded {
867        /// Number of redirect attempts observed.
868        attempts: usize,
869        /// Computed finite retry bound.
870        limit: usize,
871    },
872}
873
874/// Reports failures while preparing runtime execution artifacts.
875///
876/// # Examples
877///
878/// ```
879/// use tenferro_runtime::{InputSignature, PrepareError, SpecializationRequirements};
880///
881/// let error = SpecializationRequirements::polymorphic(1)
882///     .project(&InputSignature::new(Vec::new()))
883///     .unwrap_err();
884/// assert!(matches!(error, PrepareError::Specialization { .. }));
885/// ```
886#[derive(Debug, thiserror::Error)]
887pub enum PrepareError {
888    /// Prepared artifact belongs to a different runtime.
889    #[error("runtime mismatch: expected {expected:?}, actual {actual:?}")]
890    RuntimeMismatch {
891        /// Runtime expected by the prepared artifact.
892        expected: RuntimeId,
893        /// Runtime currently executing.
894        actual: RuntimeId,
895    },
896    /// Prepared artifact was created for a stale runtime epoch.
897    #[error("stale runtime epoch: prepared {prepared:?}, current {current:?}")]
898    StaleEpoch {
899        /// Epoch captured by the prepared artifact.
900        prepared: RuntimeEpoch,
901        /// Runtime's current epoch.
902        current: RuntimeEpoch,
903    },
904    /// Input signature construction failed.
905    #[error("input signature failed")]
906    InputSignature {
907        /// Typed source error.
908        source: InputSignatureError,
909    },
910    /// Semantic shape guards failed.
911    #[error("shape guard failed")]
912    ShapeGuard {
913        /// Typed source error.
914        #[source]
915        source: ShapeGuardError,
916    },
917    /// Signature projection through specialization requirements failed.
918    #[error("specialization failed")]
919    Specialization {
920        /// Typed source error.
921        source: SpecializationError,
922    },
923    /// No eligible engine matched the placement and capability constraints.
924    #[error("no eligible engine for {constraint:?}")]
925    NoEligibleEngine {
926        /// Placement constraint that could not be satisfied.
927        constraint: ProgramPlacementConstraint,
928    },
929    /// A preparation-only engine advertised the required capability but cannot
930    /// produce an executable prepared schedule.
931    #[error(
932        "engine {engine_id:?} has a matching preparation capability but no executable binding"
933    )]
934    NoExecutableEngine {
935        /// Engine whose preparation capability cannot be used for execution.
936        engine_id: EngineId,
937    },
938    /// The compiled graph uses an extension family absent from the runtime.
939    #[error("missing extension family {family_id:?}")]
940    MissingExtension {
941        /// Stable extension family identifier.
942        family_id: &'static str,
943    },
944    /// No eligible engine declared an ingress compatible with an input placement.
945    #[error("no eligible input ingress for input {input_index} at placement {placement:?}")]
946    NoInputIngress {
947        /// Input position in the compiled-program signature.
948        input_index: usize,
949        /// Placement rejected by every eligible engine ingress.
950        placement: tenferro_tensor::Placement,
951    },
952    /// Placement resolution exceeded its bounded combination-search budget.
953    #[error(
954        "placement resolution exhausted its search budget after {attempts} attempts \
955         (limit {limit})"
956    )]
957    DispatchSearchBudgetExceeded {
958        /// Number of dispatch combinations attempted.
959        attempts: usize,
960        /// Configured hard limit for this search.
961        limit: usize,
962    },
963    /// No available copy of a value has a registered direct transfer provider
964    /// to the storage required by its consumer.
965    #[error(
966        "instruction {instruction_index} has no direct transfer provider for value slot \
967         {value_slot} from {available_source_endpoints:?} to {destination_endpoint:?}"
968    )]
969    MissingTransferProvider {
970        /// Staged instruction whose input cannot be reached.
971        instruction_index: usize,
972        /// Value slot required by the instruction.
973        value_slot: usize,
974        /// Destination endpoint required by the instruction.
975        destination_endpoint: TransferEndpoint,
976        /// Endpoints retaining an available copy.
977        available_source_endpoints: Vec<TransferEndpoint>,
978    },
979    /// A resolved placement references an engine absent from its preparation snapshot.
980    #[error("resolved engine {engine_id:?} is unavailable in the preparation snapshot")]
981    ResolvedEngineUnavailable {
982        /// Engine referenced by the resolved placement.
983        engine_id: EngineId,
984    },
985    /// No provider supports this operation under the requested constraints.
986    #[error("operation is unsupported: {reason}")]
987    Unsupported {
988        /// Deterministic unsupported reason.
989        reason: UnsupportedReason,
990    },
991    /// Deterministic execution was requested from an engine that cannot provide it.
992    #[error("determinism unsupported by engine {engine_id:?}")]
993    DeterminismUnsupported {
994        /// Engine that cannot satisfy deterministic preparation.
995        engine_id: EngineId,
996    },
997    /// Provider returned a value that violates the runtime preparation contract.
998    #[error("provider contract violation: {source}")]
999    ProviderContract {
1000        /// Typed provider-contract source.
1001        #[source]
1002        source: ProviderContractError,
1003    },
1004    /// Preparation recursion reached an unsupported cycle.
1005    #[error("preparation cycle at {key:?}")]
1006    PreparationCycle {
1007        /// Bounded summary of the recursive key.
1008        key: PreparationKeySummary,
1009    },
1010    /// Preparation recursively requested a different key.
1011    #[error("nested preparation unsupported: parent {parent:?}, requested {requested:?}")]
1012    NestedPreparationUnsupported {
1013        /// Parent key currently preparing on this thread.
1014        parent: PreparationKeySummary,
1015        /// Nested key requested by the producer.
1016        requested: PreparationKeySummary,
1017    },
1018    /// The preparation cache is full of active/queued distinct keys.
1019    #[error(
1020        "preparation cache is at capacity: {in_flight} in flight and \
1021         {queued_distinct_keys} queued distinct keys"
1022    )]
1023    CacheInFlightCapacityExceeded {
1024        /// Active distinct preparations.
1025        in_flight: usize,
1026        /// Queued distinct keys.
1027        queued_distinct_keys: usize,
1028    },
1029    /// Runtime cache state could not be accessed.
1030    #[error("preparation cache state unavailable: {source}")]
1031    CacheState {
1032        /// Runtime state source.
1033        #[source]
1034        source: super::RuntimeStateError,
1035    },
1036    /// Engine or internal preparation source failed.
1037    #[error("engine preparation failed: {source}")]
1038    Engine {
1039        /// Shared typed source.
1040        #[source]
1041        source: Arc<dyn Error + Send + Sync>,
1042    },
1043}