Skip to main content

eredu_core/
inspection.rs

1//! Portable model-artifact inspection results.
2
3use crate::{ArtifactFormat, ModelResourceProfile};
4use serde::{Deserialize, Serialize};
5use std::path::{Path, PathBuf};
6
7/// A readiness result that preserves distinct failure modes.
8#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum InspectionReadiness {
11    /// The inspected artifacts establish this capability.
12    Ready,
13    /// The artifact omits a required component.
14    Missing,
15    /// The selected backend does not implement the combination.
16    Unsupported,
17    /// Relevant artifact data is malformed.
18    Invalid,
19    /// A concrete request is needed before deciding.
20    RequestDependent,
21    /// The check necessarily occurs during preparation or execution.
22    Unverified,
23    /// The capability does not apply.
24    NotApplicable,
25}
26
27/// Severity attached to an inspection issue.
28#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum InspectionSeverity {
31    /// Prevents the stated readiness or requested preparation route.
32    Error,
33    /// Limits a capability without preventing selected preparation.
34    Warning,
35    /// Actionable context that is neither rejection nor warning.
36    Info,
37}
38
39/// Stable machine-readable inspection issue category.
40#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42#[non_exhaustive]
43pub enum InspectionIssueCode {
44    /// Artifact path or container structure is invalid.
45    InvalidContainer,
46    /// Configuration or architecture metadata is invalid.
47    InvalidConfiguration,
48    /// Architecture dispatch has no backend implementation.
49    UnsupportedArchitecture,
50    /// A referenced checkpoint shard is absent or contradictory.
51    MissingCheckpointShard,
52    /// A tensor storage encoding cannot be consumed.
53    UnsupportedTensorEncoding,
54    /// An architecture-required tensor is absent.
55    MissingRequiredTensor,
56    /// Tensor aliases or layouts conflict after translation.
57    ConflictingTensorLayout,
58    /// A catalog tensor has the wrong rank or dimensions.
59    TensorShapeMismatch,
60    /// Packed quantization metadata and companion tensors disagree.
61    QuantizationCompanionMismatch,
62    /// Configured layer, attention, or expert geometry is invalid.
63    InvalidLayerOrExpertCount,
64    /// No usable tokenizer is available.
65    MissingTokenizer,
66    /// No checkpoint or sidecar chat template is available.
67    MissingChatTemplate,
68    /// A required multimodal projector is absent or ambiguous.
69    MissingMediaProjector,
70    /// A media processor or its build feature is unavailable.
71    MissingProcessor,
72    /// Requested on-load quantization is incompatible.
73    UnsupportedQuantizationRequest,
74    /// Requested weight-residency route is incompatible.
75    UnsupportedResidencyPolicy,
76    /// Requested parallel topology cannot use this loader.
77    UnsupportedParallelTopology,
78    /// No fail-closed semantic streaming protocol was recognized.
79    UnsupportedSemanticProtocol,
80    /// No fail-closed native-tool protocol was recognized.
81    UnsupportedToolProtocol,
82    /// EOS metadata is absent.
83    MissingEosMetadata,
84    /// Exact binding requires preparation-time module validation.
85    ValidationUnavailableUntilLoad,
86    /// Request data or runtime state still needs validation.
87    RequestSpecificValidation,
88    /// An ordinary local I/O operation failed.
89    Io,
90}
91
92/// One structured diagnostic produced by inspection.
93#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
94pub struct InspectionIssue {
95    /// Stable category for routing and UI behavior.
96    pub code: InspectionIssueCode,
97    /// Diagnostic severity.
98    pub severity: InspectionSeverity,
99    /// Human-readable actionable detail.
100    pub detail: String,
101    /// Relevant artifact or sidecar path.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub path: Option<PathBuf>,
104    /// Relevant metadata key.
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub metadata_key: Option<String>,
107    /// Relevant logical or physical tensor name.
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub tensor_name: Option<String>,
110    /// Relevant numeric tensor type code.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub tensor_type_code: Option<u32>,
113}
114
115/// One tensor storage encoding observed in artifact headers.
116#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
117pub struct ArtifactTensorEncoding {
118    /// Stable textual representation.
119    pub name: String,
120    /// GGML type code for GGUF encodings.
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub ggml_type_code: Option<u32>,
123}
124
125/// Input modality advertised by the resolved architecture.
126#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
127#[serde(rename_all = "snake_case")]
128pub enum ArtifactModality {
129    /// Text tokens.
130    Text,
131    /// Still images.
132    Image,
133    /// Video frame sequences.
134    Video,
135    /// Audio waveforms or features.
136    Audio,
137}
138
139/// A sidecar or companion requirement discovered during inspection.
140#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
141pub struct InspectionRequirement {
142    /// Machine-readable issue category.
143    pub code: InspectionIssueCode,
144    /// Current readiness of the requirement.
145    pub readiness: InspectionReadiness,
146    /// Human-readable explanation.
147    pub detail: String,
148    /// Expected or selected path.
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub path: Option<PathBuf>,
151}
152
153/// Structured pre-preparation compatibility report for a local artifact.
154#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
155pub struct ModelInspectionReport {
156    /// Submitted local artifact path.
157    pub path: PathBuf,
158    /// Detected artifact container.
159    pub artifact_format: ArtifactFormat,
160    /// Resolved high-level model family.
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub model_family: Option<String>,
163    /// Submitted model type or architecture value.
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub architecture: Option<String>,
166    /// GGUF versions observed across validated shards.
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub gguf_versions: Option<Vec<u32>>,
169    /// Number of checkpoint payload shards.
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub checkpoint_shards: Option<usize>,
172    /// Number of cataloged logical tensors.
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub tensor_count: Option<usize>,
175    /// Header-only resource accounting.
176    pub resources: ModelResourceProfile,
177    /// Distinct storage encodings observed in headers.
178    pub tensor_encodings: Vec<ArtifactTensorEncoding>,
179    /// Expected input modalities.
180    pub expected_modalities: Vec<ArtifactModality>,
181    /// Container/config/header validity.
182    pub container: InspectionReadiness,
183    /// Whether architecture dispatch selects a backend implementation.
184    pub architecture_support: InspectionReadiness,
185    /// Exact header/catalog binding to that implementation.
186    pub structural_binding: InspectionReadiness,
187    /// Model preparation readiness independent of text sidecars.
188    pub model_loadability: InspectionReadiness,
189    /// Compatibility with backend preparation options.
190    pub requested_load: InspectionReadiness,
191    /// Combined model and tokenizer readiness for raw text generation.
192    pub text_generation: InspectionReadiness,
193    /// Tokenizer reconstruction/readiness.
194    pub tokenizer: InspectionReadiness,
195    /// Chat-template availability and parseability.
196    pub chat_template: InspectionReadiness,
197    /// Behavioral structured semantic-streaming readiness.
198    pub semantic_streaming: InspectionReadiness,
199    /// Behavioral native-tool readiness.
200    pub native_tools: InspectionReadiness,
201    /// Processor/projector readiness for non-text modalities.
202    pub multimodal: InspectionReadiness,
203    /// Discovered sidecar and request requirements.
204    pub requirements: Vec<InspectionRequirement>,
205    /// Structured rejection reasons, warnings, and limitations.
206    pub issues: Vec<InspectionIssue>,
207}
208
209impl ModelInspectionReport {
210    /// Creates an initially unverified report for backend-specific enrichment.
211    pub fn unverified(path: &Path, artifact_format: ArtifactFormat) -> Self {
212        Self {
213            path: path.to_path_buf(),
214            artifact_format,
215            model_family: None,
216            architecture: None,
217            gguf_versions: None,
218            checkpoint_shards: None,
219            tensor_count: None,
220            resources: ModelResourceProfile::unmeasured(path.to_path_buf(), artifact_format),
221            tensor_encodings: Vec::new(),
222            expected_modalities: Vec::new(),
223            container: InspectionReadiness::Unverified,
224            architecture_support: InspectionReadiness::Unverified,
225            structural_binding: InspectionReadiness::Unverified,
226            model_loadability: InspectionReadiness::Unverified,
227            requested_load: InspectionReadiness::Unverified,
228            text_generation: InspectionReadiness::Unverified,
229            tokenizer: InspectionReadiness::Unverified,
230            chat_template: InspectionReadiness::Unverified,
231            semantic_streaming: InspectionReadiness::Unverified,
232            native_tools: InspectionReadiness::Unverified,
233            multimodal: InspectionReadiness::Unverified,
234            requirements: Vec::new(),
235            issues: Vec::new(),
236        }
237    }
238
239    /// Returns whether artifact and requested backend policy passed preflight.
240    pub fn is_loadable(&self) -> bool {
241        self.container == InspectionReadiness::Ready
242            && self.architecture_support == InspectionReadiness::Ready
243            && self.structural_binding == InspectionReadiness::Ready
244            && self.model_loadability == InspectionReadiness::Ready
245            && self.requested_load == InspectionReadiness::Ready
246            && !self
247                .issues
248                .iter()
249                .any(|issue| issue.code == InspectionIssueCode::ValidationUnavailableUntilLoad)
250    }
251
252    /// Adds a structured issue with an optional artifact path.
253    pub fn issue(
254        &mut self,
255        code: InspectionIssueCode,
256        severity: InspectionSeverity,
257        detail: impl Into<String>,
258        path: Option<PathBuf>,
259    ) {
260        self.issues.push(InspectionIssue {
261            code,
262            severity,
263            detail: detail.into(),
264            path,
265            metadata_key: None,
266            tensor_name: None,
267            tensor_type_code: None,
268        });
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn report_schema_round_trips_without_a_backend() {
278        let report =
279            ModelInspectionReport::unverified(Path::new("model.gguf"), ArtifactFormat::Gguf);
280        let json = serde_json::to_string(&report).unwrap();
281        let decoded: ModelInspectionReport = serde_json::from_str(&json).unwrap();
282        assert_eq!(decoded, report);
283    }
284}