syntax = "proto3";
package orlando.cluster;

// Payload encoding for grain messages.
enum Encoding {
  BINCODE = 0;   // Rust-native, used for silo-to-silo traffic (default)
  PROTOBUF = 1;  // Cross-language, used by external clients
}

// Forwards a serialized grain message to the owning silo.
service GrainTransport {
  rpc Invoke(InvokeRequest) returns (InvokeResponse);
}

message InvokeRequest {
  string grain_type = 1;
  string grain_key = 2;
  string message_type = 3;
  bytes payload = 4;
  Encoding encoding = 5;
  map<string, string> request_context = 6;
  uint32 message_version = 7;
}

message InvokeResponse {
  bytes payload = 1;
  string error = 2;
  Encoding encoding = 3;
}

// Membership protocol with SWIM-style failure detection and gossip.
service Membership {
  rpc Join(JoinRequest) returns (JoinResponse);
  rpc NotifyJoin(NotifyJoinRequest) returns (NotifyJoinResponse);
  rpc NotifyLeave(NotifyLeaveRequest) returns (NotifyLeaveResponse);
  rpc Ping(PingRequest) returns (PingResponse);
  rpc PingReq(PingReqRequest) returns (PingReqResponse);
  rpc GetMembers(GetMembersRequest) returns (GetMembersResponse);
  rpc LookupGrain(LookupGrainRequest) returns (LookupGrainResponse);
}

message SiloAddress {
  string host = 1;
  uint32 port = 2;
  string silo_id = 3;
}

message JoinRequest {
  SiloAddress joiner = 1;
}

message JoinResponse {
  repeated SiloAddress members = 1;
}

// Peer-to-peer notification: a new silo has joined the cluster.
message NotifyJoinRequest {
  SiloAddress silo = 1;
}

message NotifyJoinResponse {}

// Peer-to-peer notification: a silo has been declared dead.
message NotifyLeaveRequest {
  SiloAddress silo = 1;
}

message NotifyLeaveResponse {}

message PingRequest {
  string silo_id = 1;
  repeated GossipEntry gossip = 2;
}

message PingResponse {
  string silo_id = 1;
  repeated GossipEntry gossip = 2;
}

// Indirect ping: ask a peer to ping a target on your behalf (SWIM protocol).
message PingReqRequest {
  SiloAddress target = 1;
  string requester_silo_id = 2;
  repeated GossipEntry gossip = 3;
}

message PingReqResponse {
  bool target_alive = 1;
  repeated GossipEntry gossip = 2;
}

// Gossip update piggybacked on protocol messages.
message GossipEntry {
  enum UpdateType {
    ALIVE = 0;
    SUSPECT = 1;
    DEAD = 2;
    JOIN = 3;
  }
  UpdateType update_type = 1;
  SiloAddress silo = 2;
  uint64 incarnation = 3;
  string reported_by = 4;
}

message GetMembersRequest {}

message GetMembersResponse {
  repeated SiloAddress members = 1;
}

message LookupGrainRequest {
  string grain_type = 1;
  string grain_key = 2;
}

message LookupGrainResponse {
  bool active = 1;
  string silo_id = 2;
  string endpoint = 3;
}

// Cross-cluster grain forwarding (GSI) and lifecycle management.
service ClusterGateway {
    rpc ForwardInvoke(ForwardInvokeRequest) returns (ForwardInvokeResponse);
    rpc ClusterPing(ClusterPingRequest) returns (ClusterPingResponse);
    rpc NotifyDrain(DrainNotification) returns (DrainAck);
}

// Replication stream from primary to secondary clusters.
service ReplicationService {
    rpc Subscribe(ReplicationSubscribeRequest) returns (stream ReplicationEntryProto);
}

message ForwardInvokeRequest {
    string grain_type = 1;
    string grain_key = 2;
    string message_type = 3;
    bytes payload = 4;
    Encoding encoding = 5;
    map<string, string> request_context = 6;
    string source_cluster_id = 7;
    uint32 message_version = 8;
}

message ForwardInvokeResponse {
    bytes payload = 1;
    string error = 2;
    Encoding encoding = 3;
}

message ClusterPingRequest {
    string cluster_id = 1;
}

message ClusterPingResponse {
    string cluster_id = 1;
    uint32 silo_count = 2;
    uint32 active_grains = 3;
    ClusterState state = 4;
}

enum ClusterState {
    HEALTHY = 0;
    DRAINING = 1;
    JOINING = 2;
}

// Graceful shutdown notification sent to peer clusters so they can
// begin promotion without waiting for the health-check grace period.
message DrainNotification {
    string cluster_id = 1;
    repeated GrainOwnershipEntry grains = 2;
}

message DrainAck {}

message GrainOwnershipEntry {
    string grain_type = 1;
    string grain_key = 2;
    uint64 epoch = 3;
    uint64 latest_sequence = 4;
}

// Replication protocol messages.
message ReplicationSubscribeRequest {
    string grain_type = 1;
    string grain_key = 2;
    uint64 after_sequence = 3;
    string subscriber_cluster = 4;
}

message ReplicationEntryProto {
    string grain_type = 1;
    string grain_key = 2;
    uint64 sequence = 3;
    int64 timestamp_millis = 4;
    string source_cluster = 5;
    int32 entry_type = 6;
    bytes payload = 7;
}
