Skip to main content

orbok_core/
error.rs

1//! Application error types.
2//!
3//! Backend crates return typed errors; the UI maps them to i18n message
4//! keys (RFC-031 §6 rule 2). Error categories follow the failure
5//! taxonomies of RFC-004 §16 and RFC-005 §13.
6
7use thiserror::Error;
8
9/// Stable error categories recorded in the catalog
10/// (`extraction_records.error_category`, `index_jobs.error_category`).
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ErrorCategory {
13    SourceMissing,
14    PermissionDenied,
15    PathCanonicalizationFailed,
16    SymlinkPolicyBlocked,
17    FileTooLarge,
18    UnsupportedType,
19    UnsupportedFormat,
20    EncodingError,
21    ParserError,
22    EncryptedDocument,
23    FileChangedDuringRead,
24    ReadError,
25    HashError,
26    Timeout,
27    OutOfMemory,
28    Canceled,
29    ModelUnavailable,
30    ParserPanic,
31    InternalError,
32}
33
34impl ErrorCategory {
35    /// Stable catalog string (snake_case, as in RFC-004/RFC-005).
36    pub fn as_str(&self) -> &'static str {
37        match self {
38            ErrorCategory::SourceMissing => "source_missing",
39            ErrorCategory::PermissionDenied => "permission_denied",
40            ErrorCategory::PathCanonicalizationFailed => "path_canonicalization_failed",
41            ErrorCategory::SymlinkPolicyBlocked => "symlink_policy_blocked",
42            ErrorCategory::FileTooLarge => "file_too_large",
43            ErrorCategory::UnsupportedType => "unsupported_type",
44            ErrorCategory::UnsupportedFormat => "unsupported_format",
45            ErrorCategory::EncodingError => "encoding_error",
46            ErrorCategory::ParserError => "parser_error",
47            ErrorCategory::EncryptedDocument => "encrypted_document",
48            ErrorCategory::FileChangedDuringRead => "file_changed_during_read",
49            ErrorCategory::ReadError => "read_error",
50            ErrorCategory::HashError => "hash_error",
51            ErrorCategory::Timeout => "timeout",
52            ErrorCategory::OutOfMemory => "out_of_memory",
53            ErrorCategory::Canceled => "canceled",
54            ErrorCategory::ModelUnavailable => "model_unavailable",
55            ErrorCategory::ParserPanic => "parser_panic",
56            ErrorCategory::InternalError => "internal_error",
57        }
58    }
59}
60
61/// Top-level orbok error.
62///
63/// Messages intentionally avoid document contents; paths appear only
64/// where required for actionability (NFR-014 log hygiene).
65#[derive(Debug, Error)]
66pub enum OrbokError {
67    #[error("database error: {0}")]
68    Database(String),
69
70    #[error("migration failed at version {version}: {message}")]
71    MigrationFailed { version: i64, message: String },
72
73    #[error("path is outside all active sources")]
74    PathOutsideSources,
75
76    #[error("path canonicalization failed: {0}")]
77    PathCanonicalization(String),
78
79    #[error("blocked by source policy: {0}")]
80    PolicyBlocked(&'static str),
81
82    #[error("source not found")]
83    SourceNotFound,
84
85    #[error("file not found in catalog")]
86    FileNotFound,
87
88    #[error("cleanup plan would touch persistent catalog data")]
89    CleanupWouldTouchPersistentData,
90
91    #[error("cache engine error: {0}")]
92    Cache(String),
93
94    #[error("extraction failed: {category:?}")]
95    Extraction {
96        category: ErrorCategory,
97        message: String,
98    },
99
100    /// RFC-008 §15's embedding job failure categories, written directly as
101    /// the literal spec'd strings (`"model_missing"`, `"inference_error"`,
102    /// etc.) rather than through an intermediate enum like
103    /// [`ErrorCategory`] — deliberately, following Task 013/Review 161:
104    /// `ErrorCategory::ModelUnavailable` stringifies to
105    /// `"model_unavailable"`, which does not match RFC-008 §15's own
106    /// vocabulary, and reusing the extraction-shaped [`Extraction`] variant
107    /// for an embedding-job failure would be semantically wrong. `category`
108    /// is `&'static str` rather than `String`: every caller passes a
109    /// literal from RFC-008 §15's named set, and `Scheduler::fail`
110    /// (RFC-036 §20.1) matches directly on it to decide whether the
111    /// category is retryable.
112    #[error("embedding job failed: {category} — {message}")]
113    Embedding {
114        category: &'static str,
115        message: String,
116    },
117
118    /// Task 056: a chunk or embedding job found no extracted text for its
119    /// file in the extraction cache -- erased by "Clear extracted text" or
120    /// trimmed at idle. Not a failure of the job's own work: the caller
121    /// decides whether to re-queue extraction (see
122    /// `orbok_workers::extraction_cache_miss_category`).
123    #[error("extracted text for this file is not in the extraction cache")]
124    ExtractionCacheMissing,
125
126    #[error("invalid value in catalog column {column}: {value}")]
127    InvalidCatalogValue { column: &'static str, value: String },
128
129    #[error("I/O error: {0}")]
130    Io(#[from] std::io::Error),
131
132    #[error("operation canceled")]
133    Canceled,
134
135    #[error("queue {queue} is full — backpressure active")]
136    BackpressureActive { queue: String },
137
138    /// RFC-062 §6: a catalog whose `schema_migrations` table already
139    /// records a version this build's own `migrations::latest_version()`
140    /// doesn't know about — written by a newer orbok, synced from another
141    /// machine, or `ORBOK_DATA_DIR` pointed at a newer profile (RFC-049,
142    /// RFC-054). Refused rather than opened: running an older binary's
143    /// queries against an unknown, possibly-incompatible layout produces
144    /// arbitrary query failures instead of one clear one.
145    #[error(
146        "catalog schema version {stored} is newer than this build supports (up to {supported}) \
147         — this data directory was written by a newer orbok"
148    )]
149    SchemaVersionUnsupported { stored: i64, supported: i64 },
150}
151
152/// Convenience result alias.
153pub type OrbokResult<T> = Result<T, OrbokError>;