de_mls/events.rs
1//! I/O contract between the protocol layer and an integrator.
2//!
3//! [`ConversationEvent`] — fire-and-forget notifications about a single
4//! conversation. Each [`crate::Conversation`] holds a pending
5//! buffer; integrators drain it once per polling cycle.
6//!
7//! The library carries no transport: it buffers `Outbound` and consumes
8//! inbound payloads. The integrator owns delivery (see the `de-mls-ds`
9//! crate's `DeliveryService` for the reference transport).
10
11use crate::{
12 ConversationState,
13 protos::de_mls::messages::v1::{AppMessage, ConversationUpdateRequest, MemberWelcome},
14};
15
16/// Per-conversation notification. Sessions append these to their pending
17/// buffer; integrators drain via
18/// [`crate::Conversation::drain_events`] once per polling cycle. All
19/// variants are fire-and-forget — no failure path back to the conversation.
20#[derive(Debug, Clone)]
21pub enum ConversationEvent {
22 /// Decrypted application message (chat, vote request, proposal
23 /// notification, ban request, …).
24 AppMessage(AppMessage),
25
26 /// The local member is out of this conversation (self-leave commit
27 /// merged, or removed by a steward). The integrator should remove the
28 /// registry entry and clean up the consensus scope.
29 Leaving,
30
31 /// A background operation (e.g., vote submission) failed. UI may surface;
32 /// state has already been reconciled.
33 Error { operation: String, message: String },
34
35 /// The local user just submitted `request` as a new proposal with the
36 /// creator's vote bundled. UI should record for history; no "please vote"
37 /// affordance.
38 OwnProposalSubmitted {
39 proposal_id: u32,
40 request: ConversationUpdateRequest,
41 },
42
43 /// A peer's consensus proposal needs the local user's vote. The
44 /// integrator surfaces a vote affordance however it wishes; an auto-vote
45 /// fires after the configured delay if no manual vote arrives. Peer-side
46 /// mirror of [`Self::OwnProposalSubmitted`].
47 VoteRequested {
48 proposal_id: u32,
49 request: ConversationUpdateRequest,
50 },
51
52 /// A freeze round merged a commit; `batch` is the set of approved
53 /// proposals that landed in this commit (in insertion order).
54 CommitApplied(Vec<ConversationUpdateRequest>),
55
56 /// Conversation transitioned into `state`.
57 PhaseChange(ConversationState),
58
59 /// A merged commit added members. Carries the MLS welcome blob and the
60 /// encrypted `ConversationSync` payload bundled for atomic delivery.
61 /// Fires on every member — the committing steward mints it
62 /// (`minted_locally == true`) and broadcasts it to the group, so peers
63 /// receive the same welcome (`minted_locally == false`). The
64 /// application decides who delivers it to the joiners and how.
65 WelcomeReady {
66 welcome: MemberWelcome,
67 minted_locally: bool,
68 },
69
70 /// A consensus session on this conversation resolved. Emitted before
71 /// the protocol effects (commit candidate, freeze, score apply, …)
72 /// run, so UI fanout sees the decision in the same polling cycle as
73 /// the state change that follows. `timestamp` is the upstream
74 /// consensus library's stamp on the bus event.
75 ConsensusReached {
76 proposal_id: u32,
77 approved: bool,
78 timestamp: u64,
79 },
80
81 /// Freeze-round candidate progress changed: `received` stewards have
82 /// submitted candidates out of `expected`. Emitted by `poll()` when in
83 /// `Freezing` and the count changed since the previous emission. The
84 /// integrator can surface this as a progress indicator without polling
85 /// `freeze_candidate_count()`.
86 FreezeProgress { received: usize, expected: usize },
87}