// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 HYPERI PTY LIMITED
//
// scalo native transport protocol.
// Lightweight bulk bytes transfer with format hints.

syntax = "proto3";

package scalo.transport.v1;

// scalo inter-service transport.
service Transport {
  // Push a batch of data to the receiver.
  rpc Push(PushRequest) returns (PushResponse);

  // Route a whole WorkBatch (serde-less scalo<->scalo transfer).
  //
  // Carries a block of records, payloads as OPAQUE bytes -- the codec is
  // NOT invoked in transit. Whole batch per call (batch-at-a-time, NOT
  // record-by-record streaming).
  rpc RouteBatch(Batch) returns (BatchAck);

  // Health check.
  rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
}

// Data format hint for the receiver.
enum Format {
  FORMAT_AUTO = 0;
  FORMAT_JSON = 1;
  FORMAT_MSGPACK = 2;
  FORMAT_ARROW_IPC = 3;
}

message PushRequest {
  // Raw payload bytes (JSON lines, MsgPack, or Arrow IPC batch).
  bytes payload = 1;

  // Format hint so the receiver can skip detection.
  Format format = 2;

  // Routing metadata (e.g. topic, org_id, source).
  map<string, string> metadata = 3;
}

message PushResponse {
  // Number of events accepted by the receiver.
  uint64 accepted = 1;
}

// --- Native batch transport (serde-less WorkBatch transfer) ---
//
// Mirrors the Rust `WorkBatch<T>` records (Record + RecordMeta). Payloads
// travel as OPAQUE bytes -- prost decodes the `payload` field zero-copy into
// `bytes::Bytes` (configured via `.bytes(".")` in build.rs), so no JSON /
// MsgPack codec is ever invoked on a payload in transit. The on-the-wire codec
// is protobuf by default but SWAPPABLE: the WorkBatch<->wire mapping lives in a
// single isolated mapper module so a future hand-rolled frame can replace it.

// One application/transport header (key + opaque value bytes).
message Header {
  string key = 1;
  bytes value = 2;
}

// One work record. Mirrors the Rust `Record` + `RecordMeta`. The `payload`
// bytes field is OPAQUE and decodes zero-copy into `bytes::Bytes`.
message Record {
  // Raw payload bytes -- opaque, never parsed in transit (zero-copy Bytes).
  bytes payload = 1;

  // Routing key (Kafka topic, gRPC metadata key, Redis stream, ...).
  // Empty string means "no key" (Rust `Option<Arc<str>>` -> None).
  string key = 2;

  // Transport / application headers.
  repeated Header headers = 3;

  // Source timestamp (milliseconds since epoch), if the source provided one.
  // Carried alongside `has_timestamp_ms` so a genuine 0 is distinguishable
  // from "absent" (proto3 scalars have no presence by default).
  int64 timestamp_ms = 4;

  // Whether `timestamp_ms` is present (Rust `Option<i64>` -> Some/None).
  bool has_timestamp_ms = 5;

  // Payload format hint (mirrors the Rust `PayloadFormat`).
  Format format = 6;
}

// A whole WorkBatch's records. Commit tokens and inline-DLQ entries are NOT
// sent on the wire: tokens are the SENDER's source acks (fired locally after
// the block is sent), and DLQ entries are a local no-silent-drop concern.
message Batch {
  repeated Record records = 1;
}

// Acknowledgement for a RouteBatch call.
message BatchAck {
  // Number of records accepted by the receiver.
  uint64 accepted = 1;
}

// Serving status.
enum ServingStatus {
  SERVING = 0;
  NOT_SERVING = 1;
}

message HealthCheckRequest {}

message HealthCheckResponse {
  ServingStatus status = 1;
}
