heptagent-memory-tool-backend 0.1.0

Rust backend for the Anthropic memory_20250818 tool-call protocol — 6-command dispatcher with redb persistence + per-quest rate limiter. Not affiliated with Anthropic, PBC.
Documentation
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 heptagent core team

//! [`MemoryError`] — NP30 thiserror 2.0 derive pattern per Phase 12 sediment.
//!
//! All variants are deterministic failures; no retryable variants except
//! `RateLimit` which the caller should handle by completing the quest.

/// Errors from the `heptagent-memory-tool-backend` dispatcher.
///
/// Per NP30 (Phase 12 thiserror 2.0 migration): `#[derive(thiserror::Error)]`.
/// No i18n in spike (t_with adds heptagent_manifest dep — deferred to K-08).
#[derive(Debug, thiserror::Error)]
pub enum MemoryError {
    /// Rate limit exceeded for this quest (D-C-03: max 20 calls/quest).
    #[error("memory_20250818 rate limit exceeded: {limit} calls/quest")]
    RateLimit { limit: u32 },

    /// Path failed security validation (T-14-00-05).
    #[error("invalid memory path '{0}': path traversal or invalid prefix")]
    PathInvalid(String),

    /// Path not found in redb backend.
    #[error("memory path not found: '{0}'")]
    PathNotFound(String),

    /// Path already exists — create() cannot overwrite an existing file.
    ///
    /// 14-FIX-PACK P0-2: memory_20250818 spec requires create() to fail if path exists.
    /// Callers must use str_replace or delete+create to update existing files.
    #[error("memory path already exists: '{path}'")]
    PathExists { path: String },

    /// redb backend error.
    #[error("redb backend error: {0}")]
    Redb(String),

    /// I/O error.
    #[error("memory I/O error: {reason}")]
    Io { reason: String },

    /// StrReplace old_str not found in file content.
    #[error("str_replace: old_str not found in '{path}'")]
    OldStrNotFound { path: String },

    /// Insert line number out of range.
    #[error("insert: line {line} out of range in '{path}'")]
    InsertOutOfRange { path: String, line: u32 },

    /// View range is invalid: start > end, or end exceeds content line count.
    ///
    /// 14-FIX-PACK P1-8: day-1 consumer crash fix — `lines[start..end]` panics when
    /// start > end or start >= lines.len(). Explicit error per spec instead of silent
    /// clamp; callers should handle this as a 422 Unprocessable Entity.
    #[error(
        "invalid view_range [{start}, {end}]: start > end or end exceeds content length ({len} lines)"
    )]
    RangeInvalid {
        start: usize,
        end: usize,
        len: usize,
    },
}

impl From<redb::Error> for MemoryError {
    fn from(e: redb::Error) -> Self {
        Self::Redb(e.to_string())
    }
}

impl From<redb::DatabaseError> for MemoryError {
    fn from(e: redb::DatabaseError) -> Self {
        Self::Redb(e.to_string())
    }
}

impl From<redb::TransactionError> for MemoryError {
    fn from(e: redb::TransactionError) -> Self {
        Self::Redb(e.to_string())
    }
}

impl From<redb::StorageError> for MemoryError {
    fn from(e: redb::StorageError) -> Self {
        Self::Redb(e.to_string())
    }
}

impl From<redb::TableError> for MemoryError {
    fn from(e: redb::TableError) -> Self {
        Self::Redb(e.to_string())
    }
}

impl From<redb::CommitError> for MemoryError {
    fn from(e: redb::CommitError) -> Self {
        Self::Redb(e.to_string())
    }
}