fix: signer-scoped DirectV1 routing (#162)

Core is no longer account-aware: the client resolves an account address
to signer ids via the account directory, and the signer's verifying-key
hex serves as registry key, inbox subscription, and Welcome routing
target end to end. The MLS credential stays the full id().

- GroupV2 reads the de-mls member id from the fetched key package and
  maps it to the signer id the welcome is delivered to.
- All account machinery (directory trait, bundle codec, resolution)
  moves out of core into logos-account; the RegistrationService
  supertrait and Core::account_directory() are gone, and the client
  holds its own directory handle.
- The account exposes functionality, never a signer: add_delegate_signer
  does the lamport upsert and signs internally.
- Every client acts for an account (ChatClientBuilder::new(account)).
  DelegateSigner is a pure keypair; the client composes the wire
  credential from the signer and the account, so the association is
  client state. addr() is the account address.
- resolve_device_ids fails fast (NotAnAccountKey / NoDeviceBundle /
  Directory) instead of falling back to treating an unresolved address
  as a signer id. LogosChatClient::open and chat-cli mint and publish a
  dev account each launch.
- EphemeralRegistry keys key packages by hex pubkey like HttpRegistry.

Supersedes #155 (routing_id).
This commit is contained in:
osmaczko
2026-07-03 23:18:10 +02:00
committed by GitHub
parent d131a69583
commit c09459c0a0
34 changed files with 692 additions and 500 deletions
+13 -19
View File
@@ -16,7 +16,7 @@ use ratatui::{
widgets::{Block, Borders, List, ListItem, Paragraph, Wrap},
};
use logos_chat::{ChatStore, IdentityProvider, RegistrationService, Transport};
use logos_chat::{AccountDirectory, ChatStore, RegistrationService, Transport};
use crate::app::ChatApp;
@@ -38,11 +38,10 @@ pub fn restore() -> io::Result<()> {
}
/// Draw the UI.
pub fn draw<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>)
pub fn draw<D, R, S>(frame: &mut Frame, app: &ChatApp<D, R, S>)
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
S: ChatStore + Send + 'static,
{
let chunks = Layout::default()
@@ -61,11 +60,10 @@ where
draw_status(frame, app, chunks[3]);
}
fn draw_header<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
fn draw_header<D, R, S>(frame: &mut Frame, app: &ChatApp<D, R, S>, area: Rect)
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
S: ChatStore + Send + 'static,
{
let title = match app.current_session() {
@@ -90,11 +88,10 @@ where
frame.render_widget(header, area);
}
fn draw_messages<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
fn draw_messages<D, R, S>(frame: &mut Frame, app: &ChatApp<D, R, S>, area: Rect)
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
S: ChatStore + Send + 'static,
{
let remote_name = app
@@ -182,11 +179,10 @@ where
frame.render_stateful_widget(messages_widget, area, &mut list_state);
}
fn draw_input<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
fn draw_input<D, R, S>(frame: &mut Frame, app: &ChatApp<D, R, S>, area: Rect)
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
S: ChatStore + Send + 'static,
{
// Inner width: area minus borders (2).
@@ -215,11 +211,10 @@ where
frame.set_cursor_position((cursor_x, area.y + 1));
}
fn draw_status<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
fn draw_status<D, R, S>(frame: &mut Frame, app: &ChatApp<D, R, S>, area: Rect)
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
S: ChatStore + Send + 'static,
{
let status = Paragraph::new(app.status.as_str())
@@ -231,11 +226,10 @@ where
}
/// Handle keyboard events.
pub fn handle_events<I, D, R, S>(app: &mut ChatApp<I, D, R, S>) -> io::Result<bool>
pub fn handle_events<D, R, S>(app: &mut ChatApp<D, R, S>) -> io::Result<bool>
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
S: ChatStore + Send + 'static,
{
// Poll for events with a short timeout to allow checking incoming messages