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
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:
RatchetState, Identity, Conversation) is entangled with persistence details (SQL schemas, migrations, serialization to rows).SqliteDb, running migrations, upsert logic, and transactional save/rollback.┌─────────────────────────────────┐
│ 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 |
┌───────────────────────────────────┐
│ 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) │
└───────────────────────────────────┘
Introduce traits in each domain crate that express what storage operations the domain needs, without any knowledge of SQLite.