// src/protos/messages/v1/application.proto
syntax = "proto3";
package de_mls.messages.v1;
import "hashgraph-like-consensus/v1/consensus.proto";
message AppMessage {
oneof payload {
ConversationMessage conversation_message = 1;
BanRequest ban_request = 2;
consensus.v1.Proposal proposal = 3;
consensus.v1.Vote vote = 4;
VotePayload vote_payload = 5;
UserVote user_vote = 6;
ProposalAdded proposal_added = 7;
CommitCandidate commit_candidate = 8;
ConversationSync conversation_sync = 9;
}
}
message BanRequest {
bytes user_to_ban = 1;
string conversation_name = 2;
}
message ConversationMessage {
bytes message = 1;
string sender = 2;
string conversation_name = 3;
}
message CommitCandidate {
bytes conversation_name = 1;
repeated bytes mls_proposals = 2;
bytes commit_message = 3;
bytes steward_identity = 4;
}
// Yes/No vote for a given proposal. Based on the result, the `Vote` will be created.
message UserVote {
uint32 proposal_id = 1;
bool vote = 2;
string conversation_name = 3;
}
// Proposal added message is sent to the UI when a new proposal is added to the conversation.
message ProposalAdded {
string conversation_id = 1;
ConversationUpdateRequest request = 2;
}
message ConversationUpdateRequest {
oneof payload {
InviteMember invite_member = 1;
RemoveMember remove_member = 2;
EmergencyCriteriaProposal emergency_criteria = 3;
StewardElectionProposal steward_election = 4;
}
}
message StewardElectionProposal {
repeated bytes proposed_stewards = 1;
uint64 election_epoch = 2;
// Retry round within the same MLS epoch. Rotates the deterministic list
// generation so a rejected election can be re-attempted with a different
// composition without advancing the MLS epoch.
uint32 retry_round = 3;
}
enum ViolationType {
VIOLATION_UNSPECIFIED = 0;
BROKEN_COMMIT = 1; // Commit doesn't match voted proposals
BROKEN_MLS_PROPOSAL = 2; // MLS proposal doesn't match voting proposal
CENSORSHIP_INACTIVITY = 3; // Steward didn't commit within threshold_duration
SCORE_BELOW_THRESHOLD = 4; // Member's peer score dropped to/below removal threshold
DEADLOCK = 5; // Layer 3: re-election retries exhausted; any member may commit
}
message ViolationEvidence {
ViolationType violation_type = 1;
bytes target_member_id = 2; // Malicious steward's identity
bytes evidence_payload = 3; // Proof (mismatched hashes, timestamps, etc.)
uint64 epoch = 4; // Epoch where violation occurred
bytes creator_member_id = 5; // Identity of the proposal creator (for scoring)
}
message EmergencyCriteriaProposal {
ViolationEvidence evidence = 1;
}
message InviteMember {
bytes key_package_bytes = 1;
bytes identity = 2;
}
message RemoveMember {
bytes identity = 1;
}
message ConversationSync {
repeated bytes steward_members = 1;
uint64 election_epoch = 2;
uint32 sn_min = 3;
uint32 sn_max = 4;
bool allow_subset_candidates = 5;
repeated PeerScore peer_scores = 6;
TimingConfig timing = 7;
// Seed for the deterministic steward-list sort; joiners reproduce the
// ordering when validating.
uint32 retry_round = 8;
// Ceiling on steward-election retries before the stuck-election error
// surfaces.
uint32 max_reelection_attempts = 9;
// Auto-vote default and consensus tie-break rule
// (RFC §Creating Voting Proposal).
bool liveness_criteria_yes = 10;
// At or below this score, a member is eligible for SCORE_BELOW_THRESHOLD
// ECP removal (RFC §Peer Scoring).
int64 threshold_peer_score = 11;
// Max age (in epochs) of a buffered membership update before the epoch
// steward drops it.
uint32 pending_update_max_epochs = 12;
}
message PeerScore {
bytes member_id = 1;
int64 score = 2;
}
message TimingConfig {
uint64 commit_inactivity_duration_ms = 1; // RFC §Inactivity Timer #1
uint64 freeze_duration_ms = 2;
uint64 proposal_expiration_ms = 3;
uint64 consensus_timeout_ms = 4;
uint64 recovery_inactivity_duration_ms = 5; // RFC §Inactivity Timer #2
}
enum Outcome {
OUTCOME_UNSPECIFIED = 0;
OUTCOME_ACCEPTED = 1;
OUTCOME_REJECTED = 2;
}
message VotePayload {
string conversation_id = 41;
uint32 proposal_id = 42;
bytes payload = 43;
uint64 timestamp = 44;
}