syntax = "proto3";

package broadannounce.v1;

option go_package = "git3.techno-world.net/lrosales/broad-announce/gen/go/broadannounce/v1;broadannouncev1";

// Ingest is the gRPC service for first-party, high-volume alert sources.
// It runs on a dedicated port (default :9090) inside the tenant's VPC;
// external webhook sources continue to use HTTP POST on :8800.
service Ingest {
  // StreamAlerts is a bidirectional stream: the client sends zero or more
  // Alerts, the server sends exactly one Ack for each Alert received.
  //
  // Flow:
  //   1. Client opens stream (auth metadata required on the call)
  //   2. Client streams Alerts; server processes each concurrently
  //   3. Server streams back Acks as they are ready (order not guaranteed)
  //   4. Client reads Acks and adjusts send rate accordingly
  //
  // Backpressure: if the server's in-flight buffer (256 msgs) fills up,
  // it sends Error.RATE_LIMITED with retry_after_ms > 0. The client MUST
  // honour that delay before resuming.
  rpc StreamAlerts(stream Alert) returns (stream Ack);
}

// Alert is what a first-party service sends to BroadAnnounce.
message Alert {
  string company_id   = 1;  // required; maps to sources.company_id
  string source_id    = 2;  // required; maps to sources.source_id
  string severity     = 3;  // "info" | "warning" | "critical" | "inminent_colapse"
  string category     = 4;  // arbitrary category tag
  string title        = 5;  // pre-localized alert title
  string body         = 6;  // pre-localized alert body
  map<string, string> data = 7;  // arbitrary key-value metadata
  string dedupe_key   = 8;  // opaque string; if set, dedupe applies within the window
  int64 client_ts_ms = 9;  // Unix-ms timestamp set by the client; used for skew detection
}

// Ack is what the server sends back after processing an Alert.
message Ack {
  // alert_id is an opaque server-assigned identifier for the alert.
  // It may be empty if the alert was rejected before NATS publish.
  string alert_id = 1;

  // dedupe_key echoes back the client's dedupe_key.
  string dedupe_key = 2;

  // dedupe_count is 1 on first arrival; >1 when the server collapsed a
  // burst of identical dedupe_keys within the dedupe window.
  uint32 dedupe_count = 3;

  // accepted_at_ms is the server-side Unix-ms timestamp at which the alert
  // was accepted (i.e. after all protection layers passed).
  int64 accepted_at_ms = 4;

  // result is the processing outcome.
  oneof result {
    Ok    ok    = 10;  // alert accepted
    Error error = 11;  // alert rejected or rate-limited
  }
}

// Ok is returned when an Alert was accepted.
message Ok {}

// Error is returned when an Alert was rejected.
message Error {
  // Code classifies the error so the client can act programmatically.
  enum Code {
    UNKNOWN          = 0;  // catch-all; include message
    UNAUTHENTICATED  = 1;  // API key missing or invalid
    RATE_LIMITED     = 2;  // per-source or per-company rate limit hit
    INVALID          = 3;  // schema validation failed;修复 check message
    INTERNAL         = 4;  // server-side error; do not retry immediately
  }
  Code   code           = 1;
  string message        = 2;  // human-readable; never parse this
  // retry_after_ms is non-zero when Code == RATE_LIMITED.
  // The client MUST wait at least this long before sending the next message.
  // A value of 0 means "do not retry" (permanent failure).
  int32 retry_after_ms = 3;
}
