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
478/// Reports failure to admit an asynchronous runtime submission.
479///
480/// # Examples
481///
482/// ```
483/// use std::io;
484/// use tenferro_runtime::SubmissionError;
485///
486/// let error = SubmissionError::WorkerSpawn {
487///     source: io::Error::other("worker unavailable"),
488/// };
489/// assert!(error.to_string().contains("spawn"));
490/// ```
491#[derive(Debug, thiserror::Error)]
492#[non_exhaustive]
493pub enum SubmissionError {
494    /// The runtime could not create the worker after synchronous preparation.
495    #[error("failed to spawn runtime submission worker")]
496    WorkerSpawn {
497        /// Operating-system thread creation failure.
498        #[source]
499        source: std::io::Error,
500    },
501}
502
503impl From<IdentityError> for RuntimeConfigError {
504    fn from(source: IdentityError) -> Self {
505        Self::MalformedIdentity {
506            kind: source.kind(),
507        }
508    }
509}
510
511/// Reports failure to publish a runtime reconfiguration.
512///
513/// # Examples
514///
515/// ```
516/// use tenferro_runtime::{RuntimeConfigError, RuntimeReconfigureError};
517///
518/// let error = RuntimeReconfigureError::Edit {
519///     source: RuntimeConfigError::IdentityExhausted,
520/// };
521/// assert!(std::error::Error::source(&error).is_some());
522/// ```
523#[derive(Debug, thiserror::Error)]
524#[non_exhaustive]
525pub enum RuntimeReconfigureError {
526    /// Runtime state could not be read or published.
527    #[error("runtime state failed")]
528    State {
529        /// Typed state source.
530        #[source]
531        source: super::RuntimeStateError,
532    },
533    /// The edit closure or publication validation rejected the candidate.
534    #[error("runtime reconfiguration edit failed")]
535    Edit {
536        /// Typed configuration source.
537        #[source]
538        source: RuntimeConfigError,
539    },
540    /// Another writer published over the snapshot this edit was based on.
541    #[error("runtime was concurrently reconfigured from {base:?} to {current:?}")]
542    ConcurrentReconfiguration {
543        /// Epoch captured before the edit closure ran.
544        base: RuntimeEpoch,
545        /// Epoch observed at publication time.
546        current: RuntimeEpoch,
547    },
548    /// Runtime epoch cannot advance without wrapping.
549    #[error("runtime epoch exhausted at {current:?}")]
550    EpochExhausted {
551        /// Current maximum epoch.
552        current: RuntimeEpoch,
553    },
554}
555
556/// Reports invalid extension module registration transactions.
557///
558/// # Examples
559///
560/// ```
561/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
562/// use tenferro_runtime::{EngineId, ExtensionModuleError, ExtensionModuleId};
563///
564/// let error = ExtensionModuleError::MissingPlanningConfig {
565///     module_id: ExtensionModuleId::new("tenferro.module.test")?,
566///     engine_id: EngineId::new("tenferro.engine.test")?,
567/// };
568/// assert!(error.to_string().contains("planning config"));
569/// # Ok(())
570/// # }
571/// ```
572#[derive(Debug, thiserror::Error)]
573#[non_exhaustive]
574pub enum ExtensionModuleError {
575    /// A distinct module is already installed at the same module ID.
576    #[error("extension module {module_id:?} is already installed")]
577    ConflictingModule {
578        /// Conflicting module ID.
579        module_id: ExtensionModuleId,
580    },
581    /// A distinct extension engine is already registered for the same
582    /// `(family, engine)` key.
583    #[error("extension module {module_id:?} has conflicting engine {family_id:?}/{engine_id:?}")]
584    ConflictingEngine {
585        /// Extension module ID.
586        module_id: ExtensionModuleId,
587        /// Extension family ID.
588        family_id: &'static str,
589        /// Runtime engine ID.
590        engine_id: EngineId,
591    },
592    /// A planning config was registered without exactly one matching engine.
593    #[error(
594        "extension module {module_id:?} registered planning config without engine {engine_id:?}"
595    )]
596    PlanningConfigWithoutEngine {
597        /// Extension module ID.
598        module_id: ExtensionModuleId,
599        /// Runtime engine ID.
600        engine_id: EngineId,
601    },
602    /// Planning config family did not match the registered engine family.
603    #[error(
604        "extension module {module_id:?} config for engine {engine_id:?} has family \
605         {actual:?}, expected {expected:?}"
606    )]
607    PlanningConfigFamilyMismatch {
608        /// Extension module ID.
609        module_id: ExtensionModuleId,
610        /// Runtime engine ID.
611        engine_id: EngineId,
612        /// Expected extension family ID.
613        expected: &'static str,
614        /// Actual extension family ID.
615        actual: &'static str,
616    },
617    /// An engine was registered without its required planning config.
618    #[error("extension module {module_id:?} engine {engine_id:?} has no planning config")]
619    MissingPlanningConfig {
620        /// Extension module ID.
621        module_id: ExtensionModuleId,
622        /// Runtime engine ID.
623        engine_id: EngineId,
624    },
625    /// A distinct planning config is already registered for the same engine.
626    #[error("extension module {module_id:?} has conflicting planning config for {engine_id:?}")]
627    ConflictingPlanningConfig {
628        /// Extension module ID.
629        module_id: ExtensionModuleId,
630        /// Runtime engine ID.
631        engine_id: EngineId,
632    },
633    /// A distinct cache owner is already registered for the same local owner ID.
634    #[error("extension module {module_id:?} has conflicting cache owner {owner:?}")]
635    ConflictingCacheOwner {
636        /// Extension module ID.
637        module_id: ExtensionModuleId,
638        /// Local cache owner ID.
639        owner: CacheOwnerId,
640    },
641}
642
643/// Reports failures while building value-free input signatures.
644///
645/// # Examples
646///
647/// ```
648/// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
649/// use tenferro_tensor::Placement;
650///
651/// let error = InputSignatureEntry::new(
652///     DType::F64,
653///     [2_usize].into_iter().collect(),
654///     Placement::default(),
655///     LayoutClass::new("tenferro.layout.strided").unwrap(),
656///     [1_isize, 2].into_iter().collect(),
657///     None,
658/// )
659/// .unwrap_err();
660/// assert!(matches!(error, tenferro_runtime::InputSignatureError::ShapeStrideRankMismatch { .. }));
661/// ```
662#[derive(Debug, thiserror::Error)]
663pub enum InputSignatureError {
664    /// Shape rank and stride rank disagree.
665    #[error("shape rank {rank} does not match stride count {stride_count}")]
666    ShapeStrideRankMismatch {
667        /// Number of shape axes.
668        rank: usize,
669        /// Number of stride axes.
670        stride_count: usize,
671    },
672    /// The alignment class is outside the finite `usize` alignment lattice.
673    #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
674    InvalidAlignmentClass {
675        /// Base-2 logarithm of the requested alignment.
676        alignment_log2: u8,
677    },
678    /// Tensor metadata could not be read for an input.
679    #[error("input {input} metadata is unavailable")]
680    TensorMetadata {
681        /// Input position in the prepare request.
682        input: usize,
683        /// Original typed tensor error.
684        source: TensorError,
685    },
686}
687
688/// Explains why rank specialization is required by a requested field.
689///
690/// # Examples
691///
692/// ```
693/// use tenferro_runtime::RankRequirement;
694///
695/// assert_eq!(RankRequirement::ExactStrides.to_string(), "exact strides");
696/// ```
697#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
698pub enum RankRequirement {
699    /// A concrete axis dimension was requested.
700    ConcreteAxis {
701        /// Axis that requires rank specialization.
702        axis: u32,
703    },
704    /// Exact strides were requested.
705    ExactStrides,
706}
707
708impl std::fmt::Display for RankRequirement {
709    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
710        match self {
711            Self::ConcreteAxis { axis } => write!(formatter, "concrete axis {axis}"),
712            Self::ExactStrides => formatter.write_str("exact strides"),
713        }
714    }
715}
716
717/// Reports invalid input-specialization requirements.
718///
719/// # Examples
720///
721/// ```
722/// use tenferro_runtime::{
723///     InputSpecializationRequirements, InputSpecializationRequirementsError, RankRequirement,
724/// };
725///
726/// let mut builder = InputSpecializationRequirements::builder();
727/// builder.rank(false).concrete_dimensions(vec![2]);
728/// assert_eq!(
729///     builder.build().unwrap_err(),
730///     InputSpecializationRequirementsError::RankRequired {
731///         reason: RankRequirement::ConcreteAxis { axis: 2 },
732///     }
733/// );
734/// ```
735#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
736pub enum InputSpecializationRequirementsError {
737    /// The same concrete axis appears more than once.
738    #[error("axis {axis} is duplicated at positions {first_index} and {duplicate_index}")]
739    DuplicateAxis {
740        /// Duplicated concrete axis.
741        axis: u32,
742        /// First position carrying the axis.
743        first_index: usize,
744        /// Duplicate position that failed validation.
745        duplicate_index: usize,
746    },
747    /// A requested specialization field requires rank specialization.
748    #[error("{reason} requires rank specialization")]
749    RankRequired {
750        /// Requirement that needs rank specialization.
751        reason: RankRequirement,
752    },
753    /// The alignment class is outside the finite `usize` alignment lattice.
754    #[error("alignment class {alignment_log2} is outside the usize alignment lattice")]
755    InvalidAlignmentClass {
756        /// Base-2 logarithm of the requested alignment.
757        alignment_log2: u8,
758    },
759}
760
761/// Reports failures while projecting signatures through specialization requirements.
762///
763/// # Examples
764///
765/// ```
766/// use tenferro_runtime::{InputSignature, SpecializationRequirements};
767///
768/// let error = SpecializationRequirements::polymorphic(1)
769///     .project(&InputSignature::new(Vec::new()))
770///     .unwrap_err();
771/// assert!(matches!(error, tenferro_runtime::PrepareError::Specialization { .. }));
772/// ```
773#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
774pub enum SpecializationError {
775    /// The signature does not have the required input arity.
776    #[error("expected {expected} inputs but got {actual}")]
777    WrongInputCount {
778        /// Required input count.
779        expected: usize,
780        /// Actual input count.
781        actual: usize,
782    },
783    /// A concrete-axis request names an axis outside the actual rank.
784    #[error("input {input} axis {axis} is outside rank {rank}")]
785    AxisOutOfRange {
786        /// Input position in the signature.
787        input: usize,
788        /// Requested axis.
789        axis: u32,
790        /// Actual rank.
791        rank: usize,
792    },
793    /// A required host-pointer alignment class is unavailable.
794    #[error("input {input} has unknown alignment; required class {required_alignment_log2}")]
795    AlignmentUnavailable {
796        /// Input position in the signature.
797        input: usize,
798        /// Required base-2 logarithm alignment class.
799        required_alignment_log2: u8,
800    },
801    /// A specialization projection could not encode every requested axis in
802    /// the finite public axis type.
803    #[error("input {input} rank {rank} is too large to project into u32 axes")]
804    ProjectionOverflow {
805        /// Input position in the signature.
806        input: usize,
807        /// Rank that could not be encoded.
808        rank: usize,
809    },
810}
811
812/// A typed execution-context mismatch from erased runtime dispatch.
813///
814/// # Examples
815///
816/// ```
817/// use tenferro_runtime::{ExecutionContextIdentity, ExecutionContextMismatch};
818///
819/// let error = ExecutionContextMismatch {
820///     expected: ExecutionContextIdentity::of::<u64>(),
821///     actual: ExecutionContextIdentity::of::<u32>(),
822/// };
823/// assert_eq!(error.expected, ExecutionContextIdentity::of::<u64>());
824/// ```
825#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
826#[error("execution context mismatch: expected {expected:?}, actual {actual:?}")]
827pub struct ExecutionContextMismatch {
828    /// Expected execution-context type identity.
829    pub expected: super::ExecutionContextIdentity,
830    /// Actual stored execution-context type identity.
831    pub actual: super::ExecutionContextIdentity,
832}
833
834/// Reports provider violations of runtime preparation contracts.
835///
836/// # Examples
837///
838/// ```
839/// use tenferro_runtime::{CoreCapabilityKind, ProviderContractError};
840///
841/// let error = ProviderContractError::WrongOperationFamily {
842///     expected: CoreCapabilityKind::Elementwise,
843///     operation: "dot_general",
844/// };
845/// assert!(error.to_string().contains("dot_general"));
846/// ```
847#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
848pub enum ProviderContractError {
849    /// A provider accepted a request from the wrong core operation family.
850    #[error("operation {operation:?} is not in {expected:?}")]
851    WrongOperationFamily {
852        /// Expected core capability family.
853        expected: CoreCapabilityKind,
854        /// Operation diagnostic name.
855        operation: &'static str,
856    },
857    /// A prepared operation returned a different binding than requested.
858    #[error("prepared binding mismatch")]
859    BindingMismatch {
860        /// Runtime-created binding requested from the provider.
861        expected: Box<PreparedOperationBinding>,
862        /// Binding returned by the provider.
863        actual: Box<PreparedOperationBinding>,
864    },
865    /// A prepared operation returned a different specialization projection.
866    #[error("prepared specialization projection mismatch")]
867    ProjectionMismatch {
868        /// Runtime-created projection requested from the provider.
869        expected: Box<SpecializationProjection>,
870        /// Projection returned by the provider.
871        actual: Box<SpecializationProjection>,
872    },
873    /// A provider returned an invalid specialization.
874    #[error("invalid provider specialization: {source}")]
875    InvalidSpecialization {
876        /// Original specialization failure.
877        #[source]
878        source: SpecializationError,
879    },
880    /// A provider requested specialization that did not strictly widen the
881    /// current requirements.
882    #[error("specialization did not strictly widen the current requirements")]
883    NonWideningSpecialization {
884        /// Previous specialization requirements.
885        previous: Box<super::SpecializationRequirements>,
886        /// Provider-requested specialization requirements.
887        next: Box<super::SpecializationRequirements>,
888    },
889    /// A provider exceeded the finite specialization retry bound.
890    #[error("specialization retry limit exceeded after {attempts} attempts; limit was {limit}")]
891    SpecializationRetryLimitExceeded {
892        /// Number of redirect attempts observed.
893        attempts: usize,
894        /// Computed finite retry bound.
895        limit: usize,
896    },
897}
898
899/// Reports failures while preparing runtime execution artifacts.
900///
901/// # Examples
902///
903/// ```
904/// use tenferro_runtime::{InputSignature, PrepareError, SpecializationRequirements};
905///
906/// let error = SpecializationRequirements::polymorphic(1)
907///     .project(&InputSignature::new(Vec::new()))
908///     .unwrap_err();
909/// assert!(matches!(error, PrepareError::Specialization { .. }));
910/// ```
911#[derive(Debug, thiserror::Error)]
912pub enum PrepareError {
913    /// Prepared artifact belongs to a different runtime.
914    #[error("runtime mismatch: expected {expected:?}, actual {actual:?}")]
915    RuntimeMismatch {
916        /// Runtime expected by the prepared artifact.
917        expected: RuntimeId,
918        /// Runtime currently executing.
919        actual: RuntimeId,
920    },
921    /// Prepared artifact was created for a stale runtime epoch.
922    #[error("stale runtime epoch: prepared {prepared:?}, current {current:?}")]
923    StaleEpoch {
924        /// Epoch captured by the prepared artifact.
925        prepared: RuntimeEpoch,
926        /// Runtime's current epoch.
927        current: RuntimeEpoch,
928    },
929    /// Input signature construction failed.
930    #[error("input signature failed")]
931    InputSignature {
932        /// Typed source error.
933        source: InputSignatureError,
934    },
935    /// Semantic shape guards failed.
936    #[error("shape guard failed")]
937    ShapeGuard {
938        /// Typed source error.
939        #[source]
940        source: ShapeGuardError,
941    },
942    /// Signature projection through specialization requirements failed.
943    #[error("specialization failed")]
944    Specialization {
945        /// Typed source error.
946        source: SpecializationError,
947    },
948    /// No eligible engine matched the placement and capability constraints.
949    #[error("no eligible engine for {constraint:?}")]
950    NoEligibleEngine {
951        /// Placement constraint that could not be satisfied.
952        constraint: ProgramPlacementConstraint,
953    },
954    /// A preparation-only engine advertised the required capability but cannot
955    /// produce an executable prepared schedule.
956    #[error(
957        "engine {engine_id:?} has a matching preparation capability but no executable binding"
958    )]
959    NoExecutableEngine {
960        /// Engine whose preparation capability cannot be used for execution.
961        engine_id: EngineId,
962    },
963    /// The compiled graph uses an extension family absent from the runtime.
964    #[error("missing extension family {family_id:?}")]
965    MissingExtension {
966        /// Stable extension family identifier.
967        family_id: &'static str,
968    },
969    /// No eligible engine declared an ingress compatible with an input placement.
970    #[error("no eligible input ingress for input {input_index} at placement {placement:?}")]
971    NoInputIngress {
972        /// Input position in the compiled-program signature.
973        input_index: usize,
974        /// Placement rejected by every eligible engine ingress.
975        placement: tenferro_tensor::Placement,
976    },
977    /// Placement resolution exceeded its bounded combination-search budget.
978    #[error(
979        "placement resolution exhausted its search budget after {attempts} attempts \
980         (limit {limit})"
981    )]
982    DispatchSearchBudgetExceeded {
983        /// Number of dispatch combinations attempted.
984        attempts: usize,
985        /// Configured hard limit for this search.
986        limit: usize,
987    },
988    /// No available copy of a value has a registered direct transfer provider
989    /// to the storage required by its consumer.
990    #[error(
991        "instruction {instruction_index} has no direct transfer provider for value slot \
992         {value_slot} from {available_source_endpoints:?} to {destination_endpoint:?}"
993    )]
994    MissingTransferProvider {
995        /// Staged instruction whose input cannot be reached.
996        instruction_index: usize,
997        /// Value slot required by the instruction.
998        value_slot: usize,
999        /// Destination endpoint required by the instruction.
1000        destination_endpoint: TransferEndpoint,
1001        /// Endpoints retaining an available copy.
1002        available_source_endpoints: Vec<TransferEndpoint>,
1003    },
1004    /// A resolved placement references an engine absent from its preparation snapshot.
1005    #[error("resolved engine {engine_id:?} is unavailable in the preparation snapshot")]
1006    ResolvedEngineUnavailable {
1007        /// Engine referenced by the resolved placement.
1008        engine_id: EngineId,
1009    },
1010    /// No provider supports this operation under the requested constraints.
1011    #[error("operation is unsupported: {reason}")]
1012    Unsupported {
1013        /// Deterministic unsupported reason.
1014        reason: UnsupportedReason,
1015    },
1016    /// Deterministic execution was requested from an engine that cannot provide it.
1017    #[error("determinism unsupported by engine {engine_id:?}")]
1018    DeterminismUnsupported {
1019        /// Engine that cannot satisfy deterministic preparation.
1020        engine_id: EngineId,
1021    },
1022    /// Provider returned a value that violates the runtime preparation contract.
1023    #[error("provider contract violation: {source}")]
1024    ProviderContract {
1025        /// Typed provider-contract source.
1026        #[source]
1027        source: ProviderContractError,
1028    },
1029    /// Preparation recursion reached an unsupported cycle.
1030    #[error("preparation cycle at {key:?}")]
1031    PreparationCycle {
1032        /// Bounded summary of the recursive key.
1033        key: PreparationKeySummary,
1034    },
1035    /// Preparation recursively requested a different key.
1036    #[error("nested preparation unsupported: parent {parent:?}, requested {requested:?}")]
1037    NestedPreparationUnsupported {
1038        /// Parent key currently preparing on this thread.
1039        parent: PreparationKeySummary,
1040        /// Nested key requested by the producer.
1041        requested: PreparationKeySummary,
1042    },
1043    /// The preparation cache is full of active/queued distinct keys.
1044    #[error(
1045        "preparation cache is at capacity: {in_flight} in flight and \
1046         {queued_distinct_keys} queued distinct keys"
1047    )]
1048    CacheInFlightCapacityExceeded {
1049        /// Active distinct preparations.
1050        in_flight: usize,
1051        /// Queued distinct keys.
1052        queued_distinct_keys: usize,
1053    },
1054    /// Runtime cache state could not be accessed.
1055    #[error("preparation cache state unavailable: {source}")]
1056    CacheState {
1057        /// Runtime state source.
1058        #[source]
1059        source: super::RuntimeStateError,
1060    },
1061    /// Engine or internal preparation source failed.
1062    #[error("engine preparation failed: {source}")]
1063    Engine {
1064        /// Shared typed source.
1065        #[source]
1066        source: Arc<dyn Error + Send + Sync>,
1067    },
1068}