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    /// Engine registration provided no supported storage classes.
261    #[error("engine {engine_id:?} has no storage classes")]
262    EmptyStorageClasses {
263        /// Engine being registered.
264        engine_id: EngineId,
265    },
266    /// Engine registration repeated a storage class.
267    #[error(
268        "engine {engine_id:?} storage class {storage_class:?} is duplicated at \
269         positions {first_index} and {duplicate_index}"
270    )]
271    DuplicateStorageClass {
272        /// Engine being registered.
273        engine_id: EngineId,
274        /// Duplicated storage class.
275        storage_class: StorageClass,
276        /// First position where the storage class appeared.
277        first_index: usize,
278        /// Duplicate position that failed validation.
279        duplicate_index: usize,
280    },
281    /// Engine registration selected a default storage class that is not in its
282    /// supported list.
283    #[error("engine {engine_id:?} default storage class {default_storage_class:?} is not listed")]
284    DefaultStorageClassNotListed {
285        /// Engine being registered.
286        engine_id: EngineId,
287        /// Missing default storage class.
288        default_storage_class: StorageClass,
289    },
290    /// A transfer endpoint references an engine that is absent from the
291    /// candidate configuration.
292    #[error("transfer endpoint {endpoint:?} references an unregistered engine")]
293    UnknownTransferEndpointEngine {
294        /// Endpoint that references the missing engine.
295        endpoint: TransferEndpoint,
296    },
297    /// A transfer endpoint names a storage class unsupported by its engine.
298    #[error("transfer endpoint {endpoint:?} uses storage unsupported by its engine")]
299    UnsupportedTransferEndpointStorage {
300        /// Endpoint whose storage class is unsupported by its engine.
301        endpoint: TransferEndpoint,
302    },
303    /// A transfer route removal named an endpoint pair that is not registered.
304    #[error(
305        "no transfer provider is registered from source endpoint {source_endpoint:?} to destination \
306         endpoint {destination:?}"
307    )]
308    MissingTransferProvider {
309        /// Source endpoint of the absent route.
310        source_endpoint: TransferEndpoint,
311        /// Destination endpoint of the absent route.
312        destination: TransferEndpoint,
313    },
314    /// A route retains a physical binding that no longer matches its engine.
315    #[error(
316        "stale transfer route from source endpoint {source_endpoint:?} to destination endpoint \
317         {destination:?}: endpoint {endpoint:?} was registered for {registered:?} but \
318         now resolves to {current:?}"
319    )]
320    StaleTransferRoute {
321        /// Source endpoint of the stale route.
322        source_endpoint: TransferEndpoint,
323        /// Destination endpoint of the stale route.
324        destination: TransferEndpoint,
325        /// Route endpoint whose physical binding changed.
326        endpoint: TransferEndpoint,
327        /// Binding captured when this route was registered/frozen.
328        registered: Box<ProviderDeviceIdentity>,
329        /// Binding currently attached to the endpoint's engine.
330        current: Box<ProviderDeviceIdentity>,
331    },
332    /// A route reached freezing without a physical binding after validation.
333    #[error(
334        "transfer route from source endpoint {source_endpoint:?} to destination endpoint {destination:?} \
335         has no resolved provider/device binding"
336    )]
337    UnboundTransferRoute {
338        /// Source endpoint of the unbound route.
339        source_endpoint: TransferEndpoint,
340        /// Destination endpoint of the unbound route.
341        destination: TransferEndpoint,
342    },
343    /// A bound candidate referenced an engine that disappeared before freeze;
344    /// this indicates runtime corruption rather than candidate validation.
345    #[error("bound candidate invariant failed for transfer endpoint {endpoint:?}")]
346    BoundCandidateInvariant {
347        /// Endpoint whose already-bound engine lookup failed.
348        endpoint: TransferEndpoint,
349    },
350    /// An execution bridge omitted the validators required for explicit input
351    /// ingress and destination-buffer residency.
352    #[error("engine {engine_id:?} execution bridge has no complete input ingress validator")]
353    MissingInputIngressValidator {
354        /// Engine with the incomplete execution contract.
355        engine_id: EngineId,
356    },
357    /// A replacement attempted to reuse an engine ID with a different execution
358    /// context identity in a context where that is invalid.
359    #[error("engine {engine_id:?} context identity mismatch")]
360    ContextIdentityMismatch {
361        /// Engine with the mismatched context identity.
362        engine_id: EngineId,
363    },
364    /// Execution policy failed validation.
365    #[error("invalid execution policy: {reason}")]
366    InvalidExecutionPolicy {
367        /// Typed policy validation reason.
368        reason: ExecutionPolicyError,
369    },
370    /// Extension module transaction failed.
371    #[error("extension module registration failed")]
372    ExtensionModule {
373        /// Typed extension module source.
374        #[source]
375        source: ExtensionModuleError,
376    },
377}
378
379/// Reports a runtime execution bridge that violated its output contract.
380///
381/// # Examples
382///
383/// ```
384/// use tenferro_runtime::{EngineExecutionContractError, EngineId, StorageClass};
385///
386/// let error = EngineExecutionContractError::OutputResidencyMismatch {
387///     instruction_index: 3,
388///     output_slot: 7,
389///     engine_id: EngineId::new("tenferro.cpu")?,
390///     storage_class: StorageClass::new("tenferro.storage.host")?,
391///     backend_family: Some("foreign-backend"),
392///     allocation_domain: None,
393/// };
394/// assert!(error.to_string().contains("output slot 7"));
395/// # Ok::<(), Box<dyn std::error::Error>>(())
396/// ```
397#[derive(Debug, thiserror::Error)]
398#[non_exhaustive]
399pub enum EngineExecutionContractError {
400    /// An executor returned a tensor not owned by its scheduled engine/storage.
401    #[error(
402        "instruction {instruction_index} output slot {output_slot} is not resident in \
403         engine {engine_id:?} storage {storage_class:?} \
404         (backend family {backend_family:?}, allocation domain {allocation_domain:?})"
405    )]
406    OutputResidencyMismatch {
407        /// Execution-program instruction that produced the invalid tensor.
408        instruction_index: usize,
409        /// Execution slot containing the invalid tensor.
410        output_slot: usize,
411        /// Engine selected by the prepared schedule.
412        engine_id: EngineId,
413        /// Storage class selected by the prepared schedule.
414        storage_class: StorageClass,
415        /// Physical backend family reported by the tensor.
416        backend_family: Option<&'static str>,
417        /// Physical allocation domain reported by the tensor.
418        allocation_domain: Option<AllocationDomainId>,
419    },
420}
421
422/// Reports a runtime input that violates a prepared ingress contract.
423///
424/// # Examples
425///
426/// ```
427/// use tenferro_runtime::{EngineId, InputIngressContractError, StorageClass};
428/// use tenferro_tensor::Placement;
429///
430/// let error = InputIngressContractError::ResidencyMismatch {
431///     input_slot: 0,
432///     ingress_engine_id: EngineId::new("tenferro.cpu")?,
433///     ingress_storage_class: StorageClass::new("tenferro.storage.host")?,
434///     placement: Placement::default(),
435///     backend_family: Some("foreign-backend"),
436///     allocation_domain: None,
437/// };
438/// assert!(error.to_string().contains("input slot 0"));
439/// # Ok::<(), Box<dyn std::error::Error>>(())
440/// ```
441#[derive(Debug, thiserror::Error)]
442#[non_exhaustive]
443pub enum InputIngressContractError {
444    /// A runtime input's physical residency is incompatible with its prepared ingress.
445    #[error(
446        "input slot {input_slot} at placement {placement:?} with backend family \
447         {backend_family:?} and allocation domain {allocation_domain:?} is not accepted by \
448         ingress engine {ingress_engine_id:?} storage {ingress_storage_class:?}"
449    )]
450    ResidencyMismatch {
451        /// Execution slot containing the rejected runtime input.
452        input_slot: usize,
453        /// Engine selected as the prepared input ingress.
454        ingress_engine_id: EngineId,
455        /// Storage class selected as the prepared input ingress.
456        ingress_storage_class: StorageClass,
457        /// Logical placement reported by the input.
458        placement: Placement,
459        /// Physical backend family reported by the input.
460        backend_family: Option<&'static str>,
461        /// Physical allocation domain reported by the input.
462        allocation_domain: Option<AllocationDomainId>,
463    },
464}
465
466/// Reports failure to admit an asynchronous runtime submission.
467///
468/// # Examples
469///
470/// ```
471/// use std::io;
472/// use tenferro_runtime::SubmissionError;
473///
474/// let error = SubmissionError::WorkerSpawn {
475///     source: io::Error::other("worker unavailable"),
476/// };
477/// assert!(error.to_string().contains("spawn"));
478/// ```
479#[derive(Debug, thiserror::Error)]
480#[non_exhaustive]
481pub enum SubmissionError {
482    /// The runtime could not create the worker after synchronous preparation.
483    #[error("failed to spawn runtime submission worker")]
484    WorkerSpawn {
485        /// Operating-system thread creation failure.
486        #[source]
487        source: std::io::Error,
488    },
489}
490
491impl From<IdentityError> for RuntimeConfigError {
492    fn from(source: IdentityError) -> Self {
493        Self::MalformedIdentity {
494            kind: source.kind(),
495        }
496    }
497}
498
499/// Reports failure to publish a runtime reconfiguration.
500///
501/// # Examples
502///
503/// ```
504/// use tenferro_runtime::{RuntimeConfigError, RuntimeReconfigureError};
505///
506/// let error = RuntimeReconfigureError::Edit {
507///     source: RuntimeConfigError::IdentityExhausted,
508/// };
509/// assert!(std::error::Error::source(&error).is_some());
510/// ```
511#[derive(Debug, thiserror::Error)]
512#[non_exhaustive]
513pub enum RuntimeReconfigureError {
514    /// Runtime state could not be read or published.
515    #[error("runtime state failed")]
516    State {
517        /// Typed state source.
518        #[source]
519        source: super::RuntimeStateError,
520    },
521    /// The edit closure or publication validation rejected the candidate.
522    #[error("runtime reconfiguration edit failed")]
523    Edit {
524        /// Typed configuration source.
525        #[source]
526        source: RuntimeConfigError,
527    },
528    /// Another writer published over the snapshot this edit was based on.
529    #[error("runtime was concurrently reconfigured from {base:?} to {current:?}")]
530    ConcurrentReconfiguration {
531        /// Epoch captured before the edit closure ran.
532        base: RuntimeEpoch,
533        /// Epoch observed at publication time.
534        current: RuntimeEpoch,
535    },
536    /// Runtime epoch cannot advance without wrapping.
537    #[error("runtime epoch exhausted at {current:?}")]
538    EpochExhausted {
539        /// Current maximum epoch.
540        current: RuntimeEpoch,
541    },
542}
543
544/// Reports invalid extension module registration transactions.
545///
546/// # Examples
547///
548/// ```
549/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
550/// use tenferro_runtime::{EngineId, ExtensionModuleError, ExtensionModuleId};
551///
552/// let error = ExtensionModuleError::MissingPlanningConfig {
553///     module_id: ExtensionModuleId::new("tenferro.module.test")?,
554///     engine_id: EngineId::new("tenferro.engine.test")?,
555/// };
556/// assert!(error.to_string().contains("planning config"));
557/// # Ok(())
558/// # }
559/// ```
560#[derive(Debug, thiserror::Error)]
561#[non_exhaustive]
562pub enum ExtensionModuleError {
563    /// A distinct module is already installed at the same module ID.
564    #[error("extension module {module_id:?} is already installed")]
565    ConflictingModule {
566        /// Conflicting module ID.
567        module_id: ExtensionModuleId,
568    },
569    /// A distinct extension engine is already registered for the same
570    /// `(family, engine)` key.
571    #[error("extension module {module_id:?} has conflicting engine {family_id:?}/{engine_id:?}")]
572    ConflictingEngine {
573        /// Extension module ID.
574        module_id: ExtensionModuleId,
575        /// Extension family ID.
576        family_id: &'static str,
577        /// Runtime engine ID.
578        engine_id: EngineId,
579    },
580    /// A planning config was registered without exactly one matching engine.
581    #[error(
582        "extension module {module_id:?} registered planning config without engine {engine_id:?}"
583    )]
584    PlanningConfigWithoutEngine {
585        /// Extension module ID.
586        module_id: ExtensionModuleId,
587        /// Runtime engine ID.
588        engine_id: EngineId,
589    },
590    /// Planning config family did not match the registered engine family.
591    #[error(
592        "extension module {module_id:?} config for engine {engine_id:?} has family \
593         {actual:?}, expected {expected:?}"
594    )]
595    PlanningConfigFamilyMismatch {
596        /// Extension module ID.
597        module_id: ExtensionModuleId,
598        /// Runtime engine ID.
599        engine_id: EngineId,
600        /// Expected extension family ID.
601        expected: &'static str,
602        /// Actual extension family ID.
603        actual: &'static str,
604    },
605    /// An engine was registered without its required planning config.
606    #[error("extension module {module_id:?} engine {engine_id:?} has no planning config")]
607    MissingPlanningConfig {
608        /// Extension module ID.
609        module_id: ExtensionModuleId,
610        /// Runtime engine ID.
611        engine_id: EngineId,
612    },
613    /// A distinct planning config is already registered for the same engine.
614    #[error("extension module {module_id:?} has conflicting planning config for {engine_id:?}")]
615    ConflictingPlanningConfig {
616        /// Extension module ID.
617        module_id: ExtensionModuleId,
618        /// Runtime engine ID.
619        engine_id: EngineId,
620    },
621    /// A distinct cache owner is already registered for the same local owner ID.
622    #[error("extension module {module_id:?} has conflicting cache owner {owner:?}")]
623    ConflictingCacheOwner {
624        /// Extension module ID.
625        module_id: ExtensionModuleId,
626        /// Local cache owner ID.
627        owner: CacheOwnerId,
628    },
629}
630
631/// Reports failures while building value-free input signatures.
632///
633/// # Examples
634///
635/// ```
636/// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
637/// use tenferro_tensor::Placement;
638///
639/// let error = InputSignatureEntry::new(
640///     DType::F64,
641///     [2_usize].into_iter().collect(),
642///     Placement::default(),
643///     LayoutClass::new("tenferro.layout.strided").unwrap(),
644///     [1_isize, 2].into_iter().collect(),
645///     None,
646/// )
647/// .unwrap_err();
648/// assert!(matches!(error, tenferro_runtime::InputSignatureError::ShapeStrideRankMismatch { .. }));
649/// ```
650#[derive(Debug, thiserror::Error)]
651pub enum InputSignatureError {
652    /// Shape rank and stride rank disagree.
653    #[error("shape rank {rank} does not match stride count {stride_count}")]
654    ShapeStrideRankMismatch {
655        /// Number of shape axes.
656        rank: usize,
657        /// Number of stride axes.
658        stride_count: usize,
659    },
660    /// The alignment class is outside the finite `usize` alignment lattice.
661    #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
662    InvalidAlignmentClass {
663        /// Base-2 logarithm of the requested alignment.
664        alignment_log2: u8,
665    },
666    /// Tensor metadata could not be read for an input.
667    #[error("input {input} metadata is unavailable")]
668    TensorMetadata {
669        /// Input position in the prepare request.
670        input: usize,
671        /// Original typed tensor error.
672        source: TensorError,
673    },
674}
675
676/// Explains why rank specialization is required by a requested field.
677///
678/// # Examples
679///
680/// ```
681/// use tenferro_runtime::RankRequirement;
682///
683/// assert_eq!(RankRequirement::ExactStrides.to_string(), "exact strides");
684/// ```
685#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
686pub enum RankRequirement {
687    /// A concrete axis dimension was requested.
688    ConcreteAxis {
689        /// Axis that requires rank specialization.
690        axis: u32,
691    },
692    /// Exact strides were requested.
693    ExactStrides,
694}
695
696impl std::fmt::Display for RankRequirement {
697    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
698        match self {
699            Self::ConcreteAxis { axis } => write!(formatter, "concrete axis {axis}"),
700            Self::ExactStrides => formatter.write_str("exact strides"),
701        }
702    }
703}
704
705/// Reports invalid input-specialization requirements.
706///
707/// # Examples
708///
709/// ```
710/// use tenferro_runtime::{
711///     InputSpecializationRequirements, InputSpecializationRequirementsError, RankRequirement,
712/// };
713///
714/// let mut builder = InputSpecializationRequirements::builder();
715/// builder.rank(false).concrete_dimensions(vec![2]);
716/// assert_eq!(
717///     builder.build().unwrap_err(),
718///     InputSpecializationRequirementsError::RankRequired {
719///         reason: RankRequirement::ConcreteAxis { axis: 2 },
720///     }
721/// );
722/// ```
723#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
724pub enum InputSpecializationRequirementsError {
725    /// The same concrete axis appears more than once.
726    #[error("axis {axis} is duplicated at positions {first_index} and {duplicate_index}")]
727    DuplicateAxis {
728        /// Duplicated concrete axis.
729        axis: u32,
730        /// First position carrying the axis.
731        first_index: usize,
732        /// Duplicate position that failed validation.
733        duplicate_index: usize,
734    },
735    /// A requested specialization field requires rank specialization.
736    #[error("{reason} requires rank specialization")]
737    RankRequired {
738        /// Requirement that needs rank specialization.
739        reason: RankRequirement,
740    },
741    /// The alignment class is outside the finite `usize` alignment lattice.
742    #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
743    InvalidAlignmentClass {
744        /// Base-2 logarithm of the requested alignment.
745        alignment_log2: u8,
746    },
747}
748
749/// Reports failures while projecting signatures through specialization requirements.
750///
751/// # Examples
752///
753/// ```
754/// use tenferro_runtime::{InputSignature, SpecializationRequirements};
755///
756/// let error = SpecializationRequirements::polymorphic(1)
757///     .project(&InputSignature::new(Vec::new()))
758///     .unwrap_err();
759/// assert!(matches!(error, tenferro_runtime::PrepareError::Specialization { .. }));
760/// ```
761#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
762pub enum SpecializationError {
763    /// The signature does not have the required input arity.
764    #[error("expected {expected} inputs but got {actual}")]
765    WrongInputCount {
766        /// Required input count.
767        expected: usize,
768        /// Actual input count.
769        actual: usize,
770    },
771    /// A concrete-axis request names an axis outside the actual rank.
772    #[error("input {input} axis {axis} is outside rank {rank}")]
773    AxisOutOfRange {
774        /// Input position in the signature.
775        input: usize,
776        /// Requested axis.
777        axis: u32,
778        /// Actual rank.
779        rank: usize,
780    },
781    /// A required host-pointer alignment class is unavailable.
782    #[error("input {input} has unknown alignment; required class {required_alignment_log2}")]
783    AlignmentUnavailable {
784        /// Input position in the signature.
785        input: usize,
786        /// Required base-2 logarithm alignment class.
787        required_alignment_log2: u8,
788    },
789    /// A specialization projection could not encode every requested axis in
790    /// the finite public axis type.
791    #[error("input {input} rank {rank} is too large to project into u32 axes")]
792    ProjectionOverflow {
793        /// Input position in the signature.
794        input: usize,
795        /// Rank that could not be encoded.
796        rank: usize,
797    },
798}
799
800/// A typed execution-context mismatch from erased runtime dispatch.
801///
802/// # Examples
803///
804/// ```
805/// use tenferro_runtime::{ExecutionContextIdentity, ExecutionContextMismatch};
806///
807/// let error = ExecutionContextMismatch {
808///     expected: ExecutionContextIdentity::of::<u64>(),
809///     actual: ExecutionContextIdentity::of::<u32>(),
810/// };
811/// assert_eq!(error.expected, ExecutionContextIdentity::of::<u64>());
812/// ```
813#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
814#[error("execution context mismatch: expected {expected:?}, actual {actual:?}")]
815pub struct ExecutionContextMismatch {
816    /// Expected execution-context type identity.
817    pub expected: super::ExecutionContextIdentity,
818    /// Actual stored execution-context type identity.
819    pub actual: super::ExecutionContextIdentity,
820}
821
822/// Reports provider violations of runtime preparation contracts.
823///
824/// # Examples
825///
826/// ```
827/// use tenferro_runtime::{CoreCapabilityKind, ProviderContractError};
828///
829/// let error = ProviderContractError::WrongOperationFamily {
830///     expected: CoreCapabilityKind::Elementwise,
831///     operation: "dot_general",
832/// };
833/// assert!(error.to_string().contains("dot_general"));
834/// ```
835#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
836pub enum ProviderContractError {
837    /// A provider accepted a request from the wrong core operation family.
838    #[error("operation {operation:?} is not in {expected:?}")]
839    WrongOperationFamily {
840        /// Expected core capability family.
841        expected: CoreCapabilityKind,
842        /// Operation diagnostic name.
843        operation: &'static str,
844    },
845    /// A prepared operation returned a different binding than requested.
846    #[error("prepared binding mismatch")]
847    BindingMismatch {
848        /// Runtime-created binding requested from the provider.
849        expected: Box<PreparedOperationBinding>,
850        /// Binding returned by the provider.
851        actual: Box<PreparedOperationBinding>,
852    },
853    /// A prepared operation returned a different specialization projection.
854    #[error("prepared specialization projection mismatch")]
855    ProjectionMismatch {
856        /// Runtime-created projection requested from the provider.
857        expected: Box<SpecializationProjection>,
858        /// Projection returned by the provider.
859        actual: Box<SpecializationProjection>,
860    },
861    /// A provider returned an invalid specialization.
862    #[error("invalid provider specialization: {source}")]
863    InvalidSpecialization {
864        /// Original specialization failure.
865        #[source]
866        source: SpecializationError,
867    },
868    /// A provider requested specialization that did not strictly widen the
869    /// current requirements.
870    #[error("specialization did not strictly widen the current requirements")]
871    NonWideningSpecialization {
872        /// Previous specialization requirements.
873        previous: Box<super::SpecializationRequirements>,
874        /// Provider-requested specialization requirements.
875        next: Box<super::SpecializationRequirements>,
876    },
877    /// A provider exceeded the finite specialization retry bound.
878    #[error("specialization retry limit exceeded after {attempts} attempts; limit was {limit}")]
879    SpecializationRetryLimitExceeded {
880        /// Number of redirect attempts observed.
881        attempts: usize,
882        /// Computed finite retry bound.
883        limit: usize,
884    },
885}
886
887/// Reports failures while preparing runtime execution artifacts.
888///
889/// # Examples
890///
891/// ```
892/// use tenferro_runtime::{InputSignature, PrepareError, SpecializationRequirements};
893///
894/// let error = SpecializationRequirements::polymorphic(1)
895///     .project(&InputSignature::new(Vec::new()))
896///     .unwrap_err();
897/// assert!(matches!(error, PrepareError::Specialization { .. }));
898/// ```
899#[derive(Debug, thiserror::Error)]
900pub enum PrepareError {
901    /// Prepared artifact belongs to a different runtime.
902    #[error("runtime mismatch: expected {expected:?}, actual {actual:?}")]
903    RuntimeMismatch {
904        /// Runtime expected by the prepared artifact.
905        expected: RuntimeId,
906        /// Runtime currently executing.
907        actual: RuntimeId,
908    },
909    /// Prepared artifact was created for a stale runtime epoch.
910    #[error("stale runtime epoch: prepared {prepared:?}, current {current:?}")]
911    StaleEpoch {
912        /// Epoch captured by the prepared artifact.
913        prepared: RuntimeEpoch,
914        /// Runtime's current epoch.
915        current: RuntimeEpoch,
916    },
917    /// Input signature construction failed.
918    #[error("input signature failed")]
919    InputSignature {
920        /// Typed source error.
921        source: InputSignatureError,
922    },
923    /// Semantic shape guards failed.
924    #[error("shape guard failed")]
925    ShapeGuard {
926        /// Typed source error.
927        #[source]
928        source: ShapeGuardError,
929    },
930    /// Signature projection through specialization requirements failed.
931    #[error("specialization failed")]
932    Specialization {
933        /// Typed source error.
934        source: SpecializationError,
935    },
936    /// No eligible engine matched the placement and capability constraints.
937    #[error("no eligible engine for {constraint:?}")]
938    NoEligibleEngine {
939        /// Placement constraint that could not be satisfied.
940        constraint: ProgramPlacementConstraint,
941    },
942    /// A preparation-only engine advertised the required capability but cannot
943    /// produce an executable prepared schedule.
944    #[error(
945        "engine {engine_id:?} has a matching preparation capability but no executable binding"
946    )]
947    NoExecutableEngine {
948        /// Engine whose preparation capability cannot be used for execution.
949        engine_id: EngineId,
950    },
951    /// No eligible engine declared an ingress compatible with an input placement.
952    #[error("no eligible input ingress for input {input_index} at placement {placement:?}")]
953    NoInputIngress {
954        /// Input position in the compiled-program signature.
955        input_index: usize,
956        /// Placement rejected by every eligible engine ingress.
957        placement: tenferro_tensor::Placement,
958    },
959    /// Placement resolution exceeded its bounded combination-search budget.
960    #[error(
961        "placement resolution exhausted its search budget after {attempts} attempts \
962         (limit {limit})"
963    )]
964    DispatchSearchBudgetExceeded {
965        /// Number of dispatch combinations attempted.
966        attempts: usize,
967        /// Configured hard limit for this search.
968        limit: usize,
969    },
970    /// No available copy of a value has a registered direct transfer provider
971    /// to the storage required by its consumer.
972    #[error(
973        "instruction {instruction_index} has no direct transfer provider for value slot \
974         {value_slot} from {available_source_endpoints:?} to {destination_endpoint:?}"
975    )]
976    MissingTransferProvider {
977        /// Staged instruction whose input cannot be reached.
978        instruction_index: usize,
979        /// Value slot required by the instruction.
980        value_slot: usize,
981        /// Destination endpoint required by the instruction.
982        destination_endpoint: TransferEndpoint,
983        /// Endpoints retaining an available copy.
984        available_source_endpoints: Vec<TransferEndpoint>,
985    },
986    /// A resolved placement references an engine absent from its preparation snapshot.
987    #[error("resolved engine {engine_id:?} is unavailable in the preparation snapshot")]
988    ResolvedEngineUnavailable {
989        /// Engine referenced by the resolved placement.
990        engine_id: EngineId,
991    },
992    /// No provider supports this operation under the requested constraints.
993    #[error("operation is unsupported: {reason}")]
994    Unsupported {
995        /// Deterministic unsupported reason.
996        reason: UnsupportedReason,
997    },
998    /// Deterministic execution was requested from an engine that cannot provide it.
999    #[error("determinism unsupported by engine {engine_id:?}")]
1000    DeterminismUnsupported {
1001        /// Engine that cannot satisfy deterministic preparation.
1002        engine_id: EngineId,
1003    },
1004    /// Provider returned a value that violates the runtime preparation contract.
1005    #[error("provider contract violation: {source}")]
1006    ProviderContract {
1007        /// Typed provider-contract source.
1008        #[source]
1009        source: ProviderContractError,
1010    },
1011    /// Preparation recursion reached an unsupported cycle.
1012    #[error("preparation cycle at {key:?}")]
1013    PreparationCycle {
1014        /// Bounded summary of the recursive key.
1015        key: PreparationKeySummary,
1016    },
1017    /// Preparation recursively requested a different key.
1018    #[error("nested preparation unsupported: parent {parent:?}, requested {requested:?}")]
1019    NestedPreparationUnsupported {
1020        /// Parent key currently preparing on this thread.
1021        parent: PreparationKeySummary,
1022        /// Nested key requested by the producer.
1023        requested: PreparationKeySummary,
1024    },
1025    /// The preparation cache is full of active/queued distinct keys.
1026    #[error(
1027        "preparation cache is at capacity: {in_flight} in flight and \
1028         {queued_distinct_keys} queued distinct keys"
1029    )]
1030    CacheInFlightCapacityExceeded {
1031        /// Active distinct preparations.
1032        in_flight: usize,
1033        /// Queued distinct keys.
1034        queued_distinct_keys: usize,
1035    },
1036    /// Runtime cache state could not be accessed.
1037    #[error("preparation cache state unavailable: {source}")]
1038    CacheState {
1039        /// Runtime state source.
1040        #[source]
1041        source: super::RuntimeStateError,
1042    },
1043    /// Engine or internal preparation source failed.
1044    #[error("engine preparation failed: {source}")]
1045    Engine {
1046        /// Shared typed source.
1047        #[source]
1048        source: Arc<dyn Error + Send + Sync>,
1049    },
1050}