DirectV1 over a shared key-package registry (HttpRegistry) could not deliver
the MLS Welcome to an account-associated invitee. Key-package resolution
requires the shared address to be the account key (the account directory
resolves it to the device key HttpRegistry stores the package under), but the
Welcome was routed to, and the invitee's InboxV2 subscribed and gated on, the
credential id (hex of the DelegateCredential TLV). The two strings never
matched, so the Welcome fell through the dispatch gate to PayloadOutcome::Empty
and the invitee never joined. EphemeralRegistry hid this by keying key-packages
on the credential id, collapsing both halves onto one string.
Decouple the 1:1 routing identity from the credential identity:
- Add a defaulted IdentityProvider::routing_id() -> IdentId (defaults to id()).
- DelegateSigner derives routing_id() and account_addr() from its own
credential: once associated, the account address is read from the
DelegateCredential TLV; otherwise routing_id() falls back to the credential
id. The association is stored only in the credential, never in a separate field.
- Core::assemble feeds InboxV2 routing_id() instead of id(); the MLS credential,
member id, sender id, and decode_sender keep reading id(), so MLS membership
and sender attribution are unchanged.
Add a regression test (direct_v1_associated_invitee_receives_welcome) over a
DeviceKeyedRegistry that keys key-packages by the device verifying key, as the
deployed HttpRegistry does; it fails without routing_id and passes with it.
The client, not the app, now drives the transport; events are delivered
asynchronously, per ADR 0001.
- ChatClient owns Arc<Mutex<Core>> + a worker thread.
- The worker select!s over the inbound and shutdown channels; Drop joins it.
Outbound runs on the caller's thread.
- A single Transport (DeliveryService + inbound()) owns both directions of the
boundary, so the client takes one transport rather than a (delivery, inbound)
pair. InProcessDelivery::new, CDelivery, and chat-cli's transports implement it.
- FFI replaces client_receive with client_push_inbound + client_poll_events.
- chat-cli drains Receiver<Event>; inbound and event channels are both crossbeam.
- Corrects ADR 0001's inbound sequence to push — the worker parks on select!,
it never polls.
* chore(flake): accept extra system attr; add perl for openssl-sys build
forAllSystems calls the lambda with {system, pkgs}; strict
destructuring requires `..` to ignore the system attribute.
`pkgs.perl` is needed because openssl-sys is pulled vendored via
libsqlite3-sys / rusqlite / chat-sqlite, and its `perl Configure`
step needs FindBin.pm, which Fedora's system perl doesn't ship.
* feat: introduce client event system
- Core processing yields a `PayloadOutcome` enum — `Empty`, `Convo`, or
`Inbox`. `ConvoOutcome` carries a conversation id and an optional
decrypted `Content`; `InboxOutcome` adds a `NewConversation`
(id + `ConversationClass`) for a peer-initiated conversation.
- Client translates `PayloadOutcome` into app-facing `Vec<Event>`
(`ConversationStarted`, `MessageReceived`) at the boundary, so the
application loop sees discrete events rather than core types.
- MLS group welcomes produce a `ConversationStarted` event with no
initial content, fixing the silent-group-join case where the inbox
layer dropped the observation.
- C FFI exposes an `EventList` opaque type with indexed accessors and
an `Invalid` sentinel for out-of-bounds / non-applicable reads.
- Symmetric `Inbox` / `InboxV2` handlers: both return
`Result<InboxOutcome, _>` and own the persistence + ephemeral-key
cleanup for the conversations they create.
- Updated and simplified `docs/adr/0001-client-event-system.md`.
* chore(flake): bump nixpkgs to nixos-unstable-small
Temporary. The two crates.io UA fixes (NixOS/nixpkgs#512735 for
fetchCargoVendor's python-requests UA, NixOS/nixpkgs#524985 for
importCargoLock's curl UA) haven't propagated to nixos-unstable yet.
Switch to nixos-unstable-small and force logos-delivery to follow so
the smoketest gets the same fix. Revert once nixos-unstable catches up.
Refs:
- https://github.com/rust-lang/crates.io/issues/13482
- https://github.com/rust-lang/crates.io/issues/13783
- https://crates.io/data-access
Implement a `client` crate that wraps the `libchat` context behind a
simple `ChatClient<D>` API. The delivery strategy is pluggable via a
`DeliveryService` trait, with two implementations provided:
- `InProcessDelivery` — shared `MessageBus` for single-process tests
- `CDelivery` — C function-pointer callback for the FFI layer
Add a `client-ffi` crate that exposes the client as a C API via
`safer-ffi`. A `generate-headers` binary produces the companion C
header.
Include two runnable examples:
- `examples/in-process` — Alice/Bob exchange using in-process delivery
- `examples/c-ffi` — same exchange written entirely in C; smoketested
under valgrind (to catch memory leaks) in CI
iterates: #71