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}