# Error Model
Errors are **categories**, not strings.
## The type
```rust
pub enum KatraError {
InvalidArgument(&'static str),
NotSupported(String),
OutOfBudget { resource: &'static str, requested: u64, available: u64 },
Io(std::io::ErrorKind, String),
Protocol(String),
NotFound(String),
AlreadyExists(String),
Corrupt(String),
Timeout { what: &'static str, timeout_ns: u64 },
Sync(String),
FallbackUnavailable(&'static str),
Internal(&'static str),
}
```
## Rules
1. Policy code (fallback selection, promotion gates, budget handling)
switches on `KatraError::category()` — **never** on the message.
2. Errors carry enough structure to act on: budgets carry requested vs.
available; timeouts carry what and how long; I/O carries the
`io::ErrorKind`.
3. `From<std::io::Error>` and `From<serde_json::Error>`-style conversions
exist where the crate actually uses those sources; `katra-core` itself
stays dependency-light.
4. Cross-boundary errors (the C ABI) map categories to stable integer codes
(`KATRA_ERR_*`).
5. `Result<T>` aliases `core::result::Result<T, KatraError>`.
## Why
Games observe NTSTATUS/HRESULT/GetLastError at the Windows boundary; Katra's
internal machinery must not leak implementation-shaped errors upward. The
category is the contract; the message is the detail.