Target

Make conversations and double-ratchets stateless by consolidating all persistence into the storage crate.

Branch for existing work: https://github.com/logos-messaging/libchat/tree/storage-rebase-3

Motivation

Today, both conversations and double-ratchets contain their own storage/ submodules that directly depend on SQLite (via the shared storage crate). This creates several problems:

  1. Tight coupling — Domain logic (RatchetState, Identity, Conversation) is entangled with persistence details (SQL schemas, migrations, serialization to rows).
  2. Hard to test — Unit-testing encryption or conversation logic requires spinning up a database, even when tests only care about pure computation.
  3. Hard to swap backends — Any future storage backend (e.g. an in-memory store, a remote DB, or a platform-specific keychain) would require changes inside the domain crates.
  4. Duplicated patterns — Both crates independently implement the same patterns: wrapping SqliteDb, running migrations, upsert logic, and transactional save/rollback.

Current Architecture

┌─────────────────────────────────┐
│          conversations          │
│  (libchat)                      │
│                                 │
│  src/storage/                   │
│    db.rs        ChatStorage     │  ← owns SqliteDb, SQL queries,
│    migrations.rs                │     migration runner
│    types.rs     IdentityRecord  │
│                 ConvoRecord     │
│                                 │
│  depends on: storage, double-   │
│              ratchets           │
├─────────────────────────────────┤
│        double-ratchets          │
│                                 │
│  src/storage/                   │
│    db.rs      RatchetStorage    │  ← owns SqliteDb, SQL queries,
│    session.rs RatchetSession    │     inline schema DDL
│    errors.rs  SessionError      │
│    types.rs   RatchetStateRecord│
│                                 │
│  depends on: storage            │
├─────────────────────────────────┤
│            storage              │
│                                 │
│  SqliteDb, StorageConfig,       │
│  StorageError                   │  ← thin wrapper around rusqlite
│  re-exports rusqlite types      │
└─────────────────────────────────┘

Key observations:

Concern Where it lives today
SQLite connection / encryption storage crate
Ratchet SQL schema + queries double-ratchets/src/storage/db.rs (inline RATCHET_SCHEMA const)
Ratchet record types (RatchetStateRecord) double-ratchets/src/storage/types.rs
RatchetSession (auto-persist wrapper) double-ratchets/src/storage/session.rs
Chat SQL schema + migrations conversations/src/storage/migrations/ (SQL files)
Chat record types (IdentityRecord, ConversationRecord) conversations/src/storage/types.rs
ChatStorage (identity, ephemeral keys, convos) conversations/src/storage/db.rs
Context — holds both ChatStorage and RatchetStorage conversations/src/context.rs

Target Architecture

┌───────────────────────────────────┐
│          conversations            │
│  — STATELESS.                     │
│                                   │
│  Pure domain logic only:          │
│    Identity, Inbox, Conversation, │
│    Crypto, Proto, FFI             │
│                                   │
│  Accepts &mut dyn ChatStore       │
│  (or concrete ChatStorage ref)    │
│  from the caller.                 │
│                                   │
│  No storage/ submodule.           │
│  No dependency on `storage` crate.│
├───────────────────────────────────┤
│         double-ratchets           │
│  — STATELESS                      │
│                                   │
│  Pure domain logic only:          │
│    RatchetState, encrypt/decrypt, │
│    KDF chains, key derivation     │
│                                   │
│  Accepts &mut dyn RatchetStore    │
│  (or concrete RatchetStorage ref) │
│  from the caller.                 │
│                                   │
│  No storage/ submodule.           │
│  No dependency on `storage` crate.│
├───────────────────────────────────┤
│             storage               │
│  — SINGLE OWNER OF ALL STATE      │
│                                   │
│  From `storage` (existing):       │
│    SqliteDb, StorageConfig,       │
│    StorageError                   │
│                                   │
│  Moved from `double-ratchets`:    │
│    RatchetStorage, RatchetSession,│
│    RatchetStateRecord,            │
│    SessionError, RATCHET_SCHEMA   │
│                                   │
│  Moved from `conversations`:      │
│    ChatStorage, migrations/,      │
│    IdentityRecord,                │
│    ConversationRecord             │
│                                   │
│  New:                             │
│    Unified migration runner       │
│    (manages all schemas in one    │
│    database)                      │
└───────────────────────────────────┘

Plan

Phase 1 — Define domain-side storage traits (non-breaking)

Introduce traits in each domain crate that express what storage operations the domain needs, without any knowledge of SQLite.