// 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;
} 
