Skip to main content

gradatum_core/
error.rs

1//! Taxonomie des erreurs Gradatum.
2//!
3//! See ARCHITECTURE.md for the error model design.
4//!
5//! ## Hiérarchie
6//!
7//! - `GradatumError` — erreur top-level pour les consommateurs des crates L1+
8//! - `ValidationError` — validation de données entrantes (Tag, VaultId, etc.)
9//! - `DriftError` — écart détecté entre source de vérité Markdown et index SQLite
10//! - `ConfigError` — chargement/parsing de la config TOML (voir `config.rs`)
11//! - `schema_registry::ValidationError` — validation payload override contre schéma
12//! - `schema_registry::MigrationError` — migration payload override
13//!
14//! ## Typage fort
15//!
16//! Conformément aux règles Rust-grade : pas de `Box<dyn Error>` en lib publique.
17//! Toutes les erreurs sont typées via `thiserror`.
18//!
19//! ## Caveat C11
20//!
21//! `GradatumError::VaultOnNfs` — le vault ne peut pas être monté sur NFS.
22//! Détection via `nix::sys::statfs::statfs` + `STATFS_TYPE == NFS_SUPER_MAGIC`.
23//! Vérification déclenchée lors de `gradatum-vault::VaultConfig::validate()`.
24
25use std::path::PathBuf;
26use thiserror::Error;
27
28use crate::frontmatter::SchemaVersion;
29use crate::identity::{ContentHash, NoteId};
30use crate::scope::VaultId;
31use crate::status::NoteStatus;
32
33/// Erreur top-level de gradatum-core.
34///
35/// Produite par les couches L0 (core) et retournée aux consommateurs L1+.
36/// Chaque variante mappe une couche spécifique de l'architecture.
37#[derive(Debug, Error)]
38pub enum GradatumError {
39    /// Erreur de validation d'une valeur entrante.
40    #[error("erreur de validation : {0}")]
41    Validation(#[from] ValidationError),
42
43    /// Drift détecté entre le Markdown sur disque et l'index SQLite.
44    #[error("drift détecté : {0}")]
45    Drift(#[from] DriftError),
46
47    /// Erreur de stockage (SQLite, OpenDAL, filesystem).
48    ///
49    /// Les couches de stockage mapent leurs erreurs spécifiques via `GradatumError::Storage`.
50    #[error("erreur de stockage : {0}")]
51    Storage(String),
52
53    /// Erreur de parsing Markdown.
54    #[error("erreur parse Markdown : {0}")]
55    Markdown(String),
56
57    /// Note introuvable dans l'index.
58    #[error("note introuvable : {0:?}")]
59    NoteNotFound(NoteId),
60
61    /// Transition de statut invalide (ne respecte pas la state machine §2.6).
62    #[error("transition de statut invalide : {from:?} → {to:?}")]
63    InvalidStatusTransition {
64        /// Statut source (avant la transition).
65        from: NoteStatus,
66        /// Statut cible (refusé).
67        to: NoteStatus,
68    },
69
70    /// Mismatch de version de schéma frontmatter.
71    #[error("version de schéma incorrecte : attendu {expected}, trouvé {found}")]
72    SchemaVersionMismatch {
73        /// Version attendue par le crate courant.
74        expected: SchemaVersion,
75        /// Version trouvée dans le frontmatter.
76        found: SchemaVersion,
77    },
78
79    /// Vault introuvable dans la configuration.
80    #[error("vault introuvable : {0:?}")]
81    VaultNotFound(VaultId),
82
83    /// Vault monté sur NFS — non supporté.
84    ///
85    /// Caveat C11 — le vault doit être sur un filesystem local.
86    /// Détection via `nix::sys::statfs::statfs` + `NFS_SUPER_MAGIC` comparison.
87    #[error("vault root sur NFS (NFS_SUPER_MAGIC), non supporté : {path:?}")]
88    VaultOnNfs {
89        /// Chemin du vault root dont le statfs a retourné NFS_SUPER_MAGIC.
90        path: PathBuf,
91    },
92
93    /// Erreur de validation du payload override contre le schéma registry.
94    #[error("validation schéma override : {0}")]
95    SchemaValidation(#[from] crate::schema_registry::ValidationError),
96
97    /// Erreur de migration du payload override.
98    #[error("migration schéma override : {0}")]
99    SchemaMigration(#[from] crate::schema_registry::MigrationError),
100
101    /// Erreur I/O (lecture/écriture fichier, permissions, etc.).
102    #[error("io : {0}")]
103    Io(#[from] std::io::Error),
104
105    /// Erreur de parsing TOML.
106    #[error("toml parse : {0}")]
107    TomlParse(#[from] toml::de::Error),
108
109    /// Erreur de sérialisation TOML.
110    #[error("toml serialize : {0}")]
111    TomlSerialize(#[from] toml::ser::Error),
112
113    /// Erreur de configuration (chargement TOML, validation champs).
114    #[error("config : {0}")]
115    Config(#[from] crate::config::ConfigError),
116
117    /// Erreur d'inférence (embedding, reranker, modèle LLM).
118    ///
119    /// Variant dédié pour les erreurs d'embedder/reranker.
120    /// Permet aux handlers (ex: `vault_search`) de distinguer une panne d'inférence
121    /// d'une erreur de stockage et de dégrader gracieusement (fallback BM25 au lieu de 500).
122    ///
123    /// La conversion `From<EmbedError>` est implémentée côté
124    /// `gradatum-embed::error` pour respecter les orphan rules.
125    ///
126    /// Mapping HTTP recommandé : 503 Service Unavailable (embedder en panne)
127    /// — mais les handlers production peuvent préférer un fallback gracieux
128    /// (200 + BM25 only) via `tracing::warn!` et un vecteur sémantique vide.
129    #[error("inference : {0}")]
130    Inference(String),
131}
132
133/// Erreur de validation d'une valeur entrante.
134///
135/// Retournée par les constructeurs qui valident le format des types newtype
136/// (ex. `Tag::new()`, `VaultId::validate()`).
137#[derive(Debug, Clone, PartialEq, Eq, Error)]
138pub enum ValidationError {
139    /// Tag mal formé (format invalide ou trop long).
140    ///
141    /// Format attendu : `^[a-z0-9][a-z0-9-]{0,63}$`
142    #[error("tag invalide : {0:?} (format attendu: ^[a-z0-9][a-z0-9-]{{0,63}}$)")]
143    InvalidTag(String),
144
145    /// Vault ID mal formé.
146    #[error("vault_id invalide : {0:?}")]
147    InvalidVaultId(String),
148
149    /// Locus ID mal formé.
150    #[error("locus_id invalide : {0:?}")]
151    InvalidLocusId(String),
152
153    /// Section invalide.
154    #[error("section invalide : {0:?}")]
155    InvalidSection(String),
156
157    /// Statut invalide.
158    #[error("statut invalide : {0:?}")]
159    InvalidStatus(String),
160
161    /// Corps de note vide.
162    #[error("corps de note vide")]
163    EmptyBody,
164
165    /// Contrainte métier violée (input sémantiquement invalide).
166    ///
167    /// Utilisé pour les règles qui ne relèvent pas du format (ex. auto-référence
168    /// `replaced_by == note_id`). Distinct des variantes de format ci-dessus.
169    #[error("input invalide : {0}")]
170    InvalidInput(String),
171}
172
173/// Erreur de détection de drift entre Markdown et index SQLite.
174///
175/// Produite par `Note::verify_integrity()` et par le drift detector (`gradatum-vault`).
176/// Déclenche un re-parse + re-index + re-embed du fichier concerné.
177#[derive(Debug, Clone, PartialEq, Error)]
178pub enum DriftError {
179    /// Le ContentHash recomputed diffère du hash stocké en SQLite.
180    ///
181    /// Cause probable : édition manuelle du fichier Markdown hors de Gradatum.
182    /// Action : re-parse + re-index par le worker.
183    #[error("content hash mismatch: stored={stored}, computed={computed}")]
184    ContentHashMismatch {
185        /// Hash stocké dans SQLite à la dernière écriture connue.
186        stored: ContentHash,
187        /// Hash recomputed depuis le fichier Markdown actuel.
188        computed: ContentHash,
189    },
190
191    /// Fichier Markdown absent sur disque pour une note indexée.
192    #[error("fichier md absent sur disque : {note_id:?}")]
193    NoteMdMissing {
194        /// Identifiant de la note dont le fichier `.md` est absent.
195        note_id: NoteId,
196    },
197
198    /// Fichier Markdown orphelin (pas de note correspondante dans l'index).
199    #[error("fichier md orphelin : {0}")]
200    OrphanMd(String),
201}