libchat/core/conversations/src/service_context.rs
osmaczko c09459c0a0
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).
2026-07-03 23:18:10 +02:00

117 lines
3.4 KiB
Rust

//! Bundles the services a conversation operation needs into one [`ServiceContext`].
use crypto::Identity;
use storage::ChatStore;
use crate::IdentityProvider;
use crate::causal_history::CausalHistoryStore;
use crate::inbox_v2::{MlsEphemeralPqProvider, MlsIdentityProvider};
use crate::service_traits::WakeupService;
use crate::{DeliveryService, RegistrationService};
/// Bundles the external service types (`DS`, `RS`, `CS`) behind one `S`. The
/// `(DS, RS, CS)` tuple impl lets them still be supplied separately.
pub trait ExternalServices {
type IP: IdentityProvider;
type DS: DeliveryService;
type RS: RegistrationService;
type WS: WakeupService;
type CS: ChatStore;
}
impl<IP, DS, RS, WS, CS> ExternalServices for (IP, DS, RS, WS, CS)
where
IP: IdentityProvider,
DS: DeliveryService,
RS: RegistrationService,
WS: WakeupService,
CS: ChatStore,
{
type IP = IP;
type DS = DS;
type RS = RS;
type WS = WS;
type CS = CS;
}
/// Bundles every service a conversation operation may need.
pub(crate) struct ServiceContext<S: ExternalServices> {
pub(crate) ds: S::DS,
pub(crate) registry: S::RS,
pub(crate) store: S::CS,
pub(crate) mls_identity: MlsIdentityProvider<S::IP>,
pub(crate) mls_provider: MlsEphemeralPqProvider,
pub(crate) causal: CausalHistoryStore,
pub(crate) identity: Identity,
pub(crate) wakeup_service: S::WS,
}
#[cfg(test)]
mod test_support {
use super::*;
use crate::types::AddressedEnvelope;
use crate::{ChatError, IdentityProvider};
/// Delivery double that drops every payload.
#[derive(Debug)]
pub(crate) struct NoopDelivery;
impl DeliveryService for NoopDelivery {
type Error = std::convert::Infallible;
fn publish(&mut self, _envelope: AddressedEnvelope) -> Result<(), Self::Error> {
Ok(())
}
fn subscribe(&mut self, _delivery_address: &str) -> Result<(), Self::Error> {
Ok(())
}
}
/// Registration double that holds no key packages.
#[derive(Debug)]
pub(crate) struct NoopRegistration;
impl RegistrationService for NoopRegistration {
type Error = std::convert::Infallible;
fn register(
&mut self,
_identity: &dyn IdentityProvider,
_key_bundle: Vec<u8>,
) -> Result<(), Self::Error> {
Ok(())
}
fn retrieve(&self, _device_id: &str) -> Result<Option<Vec<u8>>, Self::Error> {
Ok(None)
}
}
#[derive(Debug)]
pub(crate) struct NoopWakeups;
impl WakeupService for NoopWakeups {
fn wakeup_in(&mut self, _: std::time::Duration, _: crate::ConversationId) {}
}
impl<IP: IdentityProvider, CS: ChatStore>
ServiceContext<(IP, NoopDelivery, NoopRegistration, NoopWakeups, CS)>
{
/// Builds a context around a real store, stubbing other services.
pub(crate) fn for_test(ident: IP, store: CS) -> Result<Self, ChatError> {
let name = ident.id().as_str().to_string();
Ok(Self {
ds: NoopDelivery,
registry: NoopRegistration,
store,
mls_identity: MlsIdentityProvider::new(ident),
mls_provider: MlsEphemeralPqProvider::new().map_err(ChatError::generic)?,
causal: CausalHistoryStore::new(),
identity: Identity::new(name),
wakeup_service: NoopWakeups {},
})
}
}
}