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
+20 -12
View File
@@ -8,9 +8,10 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use clap::{Parser, ValueEnum};
use crossbeam_channel::Receiver;
use logos_account::TestLogosAccount;
use logos_chat::{
ChatClient, ChatClientBuilder, ChatStore, DelegateSigner, Event, HttpRegistry,
IdentityProvider, LogosChatClient, NETWORK_PRESET, REGISTRY_ENDPOINT, RegistrationService,
AccountDirectory, ChatClient, ChatClientBuilder, ChatStore, DelegateSigner, Event,
HttpRegistry, LogosChatClient, NETWORK_PRESET, REGISTRY_ENDPOINT, RegistrationService,
StorageConfig, Transport,
};
@@ -106,10 +107,19 @@ fn main() -> Result<()> {
.context("failed to create file transport")?;
let endpoint = cli.registry_url.as_deref().unwrap_or(REGISTRY_ENDPOINT);
let (client, events) = ChatClientBuilder::new()
.ident(DelegateSigner::random())
// A fresh dev account endorsing a fresh delegate each launch,
// mirroring `LogosChatClient::open`.
let account = TestLogosAccount::new();
let delegate = DelegateSigner::random();
let mut registry = HttpRegistry::new(endpoint);
account
.add_delegate_signer(&mut registry, delegate.public_key())
.map_err(|e| anyhow::anyhow!("{e:?}"))
.context("failed to publish the device bundle")?;
let (client, events) = ChatClientBuilder::new(account.address())
.ident(delegate)
.transport(transport)
.registration(HttpRegistry::new(endpoint))
.registration(registry)
.storage_config(StorageConfig::Encrypted {
path: db_str,
key: "chat-cli".to_string(),
@@ -135,15 +145,14 @@ fn db_path(cli: &Cli) -> Result<String> {
.to_string())
}
fn launch_tui<I, T, R, S>(
client: ChatClient<I, T, R, S>,
fn launch_tui<T, R, S>(
client: ChatClient<T, R, S>,
events: Receiver<Event>,
cli: &Cli,
) -> Result<()>
where
I: IdentityProvider + Send,
T: Transport,
R: RegistrationService + Send + 'static,
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
S: ChatStore + Send,
{
let mut app = ChatApp::new(client, events, &cli.name, &cli.data)?;
@@ -158,11 +167,10 @@ where
result
}
fn run_app<I, T, R, S>(terminal: &mut ui::Tui, app: &mut ChatApp<I, T, R, S>) -> Result<()>
fn run_app<T, R, S>(terminal: &mut ui::Tui, app: &mut ChatApp<T, R, S>) -> Result<()>
where
I: IdentityProvider + Send,
T: Transport,
R: RegistrationService + Send + 'static,
R: RegistrationService + AccountDirectory + Clone + Send + 'static,
S: ChatStore + Send,
{
loop {