Skip to content

Persist identity#67

Open
kaichaosun wants to merge 12 commits intomainfrom
storage-rebase
Open

Persist identity#67
kaichaosun wants to merge 12 commits intomainfrom
storage-rebase

Conversation

@kaichaosun
Copy link
Contributor

@kaichaosun kaichaosun commented Feb 27, 2026

What changes:

  • integrate the storage crate for sqlite access
  • migrations and run helpers
  • save and load identity
  • expose ffi with app provided storage path

@kaichaosun kaichaosun marked this pull request as ready for review February 27, 2026 07:08
"INSERT OR REPLACE INTO identity (id, name, secret_key) VALUES (1, ?1, ?2)",
params![
identity.get_name(),
identity.secret().DANGER_to_bytes().as_slice()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess since the whole db will be encrypted this is ok

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it should be fine for now until there is shared logos module to help manage such credentials.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nb: While there may be shared functionality these installation keys will need to be compartmentalized to a given instance.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also a deprecation notice on DANGER_to_bytes which we may need to re-discuss.

The intention is that the conversations crate will eventually not have any access to key material, and all operations on secrets bytes would be localized to a single location such as crate crypto. This makes external reviews and safety checks straight forward by establishing an invariant that crates cannot have unsafe access to key material.

Storage poses a problem with this approach and will need to be need to be considered more fully.

Copy link
Contributor Author

@kaichaosun kaichaosun Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The improvement of key storage in a better place and DANGER_to_bytes is out of scope of this PR.
The target here is to implement MVP that can persist the necessary information.

A few more questions worth to be discussed before putting more efforts,

  • how are such credentials should be handled in logos
  • will the credentials be portable

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds SQLite-backed persistence for conversation state, focusing initially on saving/loading the local identity, and exposes a new FFI entrypoint to open a context with an app-provided storage path.

Changes:

  • Introduces a ChatStorage SQLite layer with embedded migrations and identity save/load.
  • Extends Context to open from storage (load-or-create identity) and adds persistence tests.
  • Adds an FFI helper (create_context_with_storage) plus basic shared FFI utilities (CResult, destroy_string).

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
storage/src/lib.rs Re-exports rusqlite::Connection for domain crates to run migrations without depending on rusqlite directly.
storage/src/errors.rs Adds StorageError::InvalidData for domain-level validation failures.
conversations/src/storage/types.rs Adds IdentityRecord with zeroize support and a unit test.
conversations/src/storage/mod.rs Introduces the conversations storage module wiring.
conversations/src/storage/migrations/001_initial_schema.sql Creates initial schema with an identity table.
conversations/src/storage/migrations.rs Adds embedded, ordered migration application with _migrations tracking.
conversations/src/storage/db.rs Implements ChatStorage with identity save/load and a roundtrip test.
conversations/src/lib.rs Wires new ffi and storage modules into the crate.
conversations/src/identity.rs Adds Identity::from_secret constructor for restoring persisted identities.
conversations/src/ffi/utils.rs Adds CResult and destroy_string utility for FFI.
conversations/src/ffi/mod.rs Exposes the ffi::utils module.
conversations/src/errors.rs Adds ChatError::Storage to propagate storage failures.
conversations/src/context.rs Adds Context::open using storage; keeps new_with_name as in-memory; adds persistence test.
conversations/src/api.rs Adds create_context_with_storage FFI entrypoint using a DB path.
conversations/Cargo.toml Adds storage, zeroize, and tempfile dependencies.
Cargo.lock Locks new dependencies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 72 to 76
#[ffi_export]
pub fn create_context_with_storage(
name: repr_c::String,
db_path: repr_c::String,
) -> CResult<repr_c::Box<ContextHandle>, repr_c::String> {
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file’s ABI notes indicate using out-parameters for returning non-trivial structs (e.g., installation_name, create_intro_bundle). create_context_with_storage returns a CResult<...> struct by value, which is inconsistent with that convention and may be ABI-problematic for Nim callers. Consider switching to an out-parameter result struct to match the established pattern.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@osmaczko is this going to contribute to the issues with sret?

Comment on lines +19 to +22
fn from(record: IdentityRecord) -> Self {
let name = record.name.clone();
let secret = PrivateKey::from(record.secret_key);
Identity::from_secret(name, secret)
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

impl From<IdentityRecord> for Identity clones record.name even though IdentityRecord is moved into the conversion. This adds an unnecessary allocation/copy; you can move the String out of the record directly instead of cloning it.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move name did not pass the compiler,

cannot move out of type `IdentityRecord`, which implements the `Drop` trait

// ==================== Identity Operations ====================

/// Saves the identity (secret key).
pub fn save_identity(&mut self, identity: &Identity) -> Result<(), StorageError> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Sand] Clearing up the language used in this library: Installation represent keys for this given instance, where Identity represents a group of installations tied to a set of Installations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clear the Installation or identity terms used is out of scope for this PR.
We need to write the spec first, also there may need org level consensus on how such concept being shared or reused.

"INSERT OR REPLACE INTO identity (id, name, secret_key) VALUES (1, ?1, ?2)",
params![
identity.get_name(),
identity.secret().DANGER_to_bytes().as_slice()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nb: While there may be shared functionality these installation keys will need to be compartmentalized to a given instance.

"INSERT OR REPLACE INTO identity (id, name, secret_key) VALUES (1, ?1, ?2)",
params![
identity.get_name(),
identity.secret().DANGER_to_bytes().as_slice()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also a deprecation notice on DANGER_to_bytes which we may need to re-discuss.

The intention is that the conversations crate will eventually not have any access to key material, and all operations on secrets bytes would be localized to a single location such as crate crypto. This makes external reviews and safety checks straight forward by establishing an invariant that crates cannot have unsafe access to key material.

Storage poses a problem with this approach and will need to be need to be considered more fully.

Comment on lines +1 to +7
//! Storage module for persisting chat state.

mod db;
mod migrations;
pub(crate) mod types;

pub(crate) use db::ChatStorage;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This project (with the exception of double-ratchets) follows the Rust 2018 guidance - https://doc.rust-lang.org/edition-guide/rust-2018/path-changes.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why we should follow this "guidance".
I have randomly searched a few top repos in github, all of my searched repos includes mod.rs.
https://github.com/EvanLi/Github-Ranking/blob/master/Top100/Rust.md

Comment on lines 72 to 76
#[ffi_export]
pub fn create_context_with_storage(
name: repr_c::String,
db_path: repr_c::String,
) -> CResult<repr_c::Box<ContextHandle>, repr_c::String> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@osmaczko is this going to contribute to the issues with sret?

Comment on lines +5 to +8
pub struct CResult<T: ReprC, Err: ReprC> {
pub ok: Option<T>,
pub err: Option<Err>,
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a consistency POV, api.rs uses the safer_ffi to create unique types per each api_call. I'm not saying that is the best approach, however this does represent a different approach of using generic containers as return values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly, that's why I put in ffi/utils, I hope it can be reused in other apis, so that we can have uniform apis and reused code.

Comment on lines +19 to +22
fn from(record: IdentityRecord) -> Self {
let name = record.name.clone();
let secret = PrivateKey::from(record.secret_key);
Identity::from_secret(name, secret)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants