Base de connaissance persistante par projet, indépendante de tout modèle/CLU
et de git. Cadrage archi en §14.5 (ARCHITECTURE.md), cycle Archi→Dev→Test.
LOT A — étage 1 (.md, source de vérité)
- domaine: entité Memory (+ MemorySlug, MemoryType, MemoryFrontmatter,
MemoryLink, MemoryIndexEntry), liens [[slug]], index MEMORY.md dérivé
- port MemoryStore + MemoryError, adapter FsMemoryStore (.ideai/memory/)
- application: 7 use cases (Create/Update/List/Get/Delete/ReadIndex/
ResolveLinks), From<MemoryError> for AppError
- app-tauri: commandes + DTO, events MemorySaved/MemoryDeleted
- suppression de la variante morte DomainError::MalformedFrontmatter
LOT B — rappel adaptatif (étage 1)
- port MemoryRecall + MemoryQuery, adapter NaiveMemoryRecall (troncature
au budget de tokens, court-circuit budget-0), use case RecallMemory
LOT C — étage 2 vectoriel (structure complète, zéro dépendance lourde)
- port Embedder + EmbedderError, profils déclaratifs EmbedderProfile/
EmbedderStrategy (embedder.json)
- VectorMemoryRecall (cosinus, cache .ideai/memory/.index/ gitignoré)
- AdaptiveMemoryRecall (bascule pure should_use_vector), défaut none
- HashEmbedder (déterministe, tests), StubEmbedder (onnx/server/api)
Tests: 57 binaires verts, build + clippy --workspace sans warning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
137 lines
3.9 KiB
Rust
137 lines
3.9 KiB
Rust
//! [`AppError`] — the single error type returned by every use case.
|
|
//!
|
|
//! Per-port errors from the domain ([`domain::ports::FsError`],
|
|
//! [`domain::ports::StoreError`], …) are mapped into this application-level
|
|
//! error so that the presentation layer (Tauri commands) only ever has to deal
|
|
//! with one error shape when building its `ErrorDTO`.
|
|
|
|
use domain::ports::{
|
|
EmbedderError, FsError, GitError, MemoryError, ProcessError, PtyError, RemoteError,
|
|
RuntimeError, StoreError,
|
|
};
|
|
|
|
/// Errors surfaced by application use cases.
|
|
///
|
|
/// Each variant carries a stable, machine-readable `code` (see [`AppError::code`])
|
|
/// so the presentation layer can map it to an `ErrorDTO` without string matching.
|
|
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
|
pub enum AppError {
|
|
/// A requested resource was not found.
|
|
#[error("not found: {0}")]
|
|
NotFound(String),
|
|
|
|
/// The input failed a domain/application invariant.
|
|
#[error("invalid input: {0}")]
|
|
Invalid(String),
|
|
|
|
/// A filesystem operation failed.
|
|
#[error("filesystem error: {0}")]
|
|
FileSystem(String),
|
|
|
|
/// A persistence (store) operation failed.
|
|
#[error("store error: {0}")]
|
|
Store(String),
|
|
|
|
/// A process/PTY/runtime operation failed.
|
|
#[error("process error: {0}")]
|
|
Process(String),
|
|
|
|
/// A git operation failed.
|
|
#[error("git error: {0}")]
|
|
Git(String),
|
|
|
|
/// A remote (SSH/WSL) operation failed.
|
|
#[error("remote error: {0}")]
|
|
Remote(String),
|
|
|
|
/// An unexpected internal error.
|
|
#[error("internal error: {0}")]
|
|
Internal(String),
|
|
}
|
|
|
|
impl AppError {
|
|
/// A stable, machine-readable code for this error, intended for the
|
|
/// `ErrorDTO` so the frontend can branch without parsing messages.
|
|
#[must_use]
|
|
pub fn code(&self) -> &'static str {
|
|
match self {
|
|
Self::NotFound(_) => "NOT_FOUND",
|
|
Self::Invalid(_) => "INVALID",
|
|
Self::FileSystem(_) => "FILESYSTEM",
|
|
Self::Store(_) => "STORE",
|
|
Self::Process(_) => "PROCESS",
|
|
Self::Git(_) => "GIT",
|
|
Self::Remote(_) => "REMOTE",
|
|
Self::Internal(_) => "INTERNAL",
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<FsError> for AppError {
|
|
fn from(e: FsError) -> Self {
|
|
match e {
|
|
FsError::NotFound(p) => Self::NotFound(p),
|
|
other => Self::FileSystem(other.to_string()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<StoreError> for AppError {
|
|
fn from(e: StoreError) -> Self {
|
|
match e {
|
|
StoreError::NotFound => Self::NotFound("store item".to_owned()),
|
|
other => Self::Store(other.to_string()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<MemoryError> for AppError {
|
|
fn from(e: MemoryError) -> Self {
|
|
match e {
|
|
MemoryError::NotFound => Self::NotFound("memory note".to_owned()),
|
|
MemoryError::Frontmatter(m) => Self::Invalid(m),
|
|
other => Self::Store(other.to_string()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<EmbedderError> for AppError {
|
|
/// Maps to [`AppError::Store`] — an embedder is a *derived* recall detail; its
|
|
/// failure must degrade the recall (fallback to naïve), never fail hard. This
|
|
/// mapping exists for completeness; recall adapters degrade *before* an
|
|
/// embedder error ever reaches a use case.
|
|
fn from(e: EmbedderError) -> Self {
|
|
Self::Store(e.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<PtyError> for AppError {
|
|
fn from(e: PtyError) -> Self {
|
|
Self::Process(e.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<ProcessError> for AppError {
|
|
fn from(e: ProcessError) -> Self {
|
|
Self::Process(e.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<RuntimeError> for AppError {
|
|
fn from(e: RuntimeError) -> Self {
|
|
Self::Process(e.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<GitError> for AppError {
|
|
fn from(e: GitError) -> Self {
|
|
Self::Git(e.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<RemoteError> for AppError {
|
|
fn from(e: RemoteError) -> Self {
|
|
Self::Remote(e.to_string())
|
|
}
|
|
}
|