Skip to main content

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}