syntax = "proto3";

package chroma;
option go_package = "github.com/chroma-core/chroma/go/pkg/proto/logservicepb";

import "chromadb/proto/chroma.proto";

// Customer-managed encryption key
message Cmek {
  oneof provider {
    string gcp = 1;  // GCP KMS resource name
  }
}

message PushLogsRequest {
  string collection_id = 1;
  string database_name = 5;
  repeated OperationRecord records = 2;
  optional Cmek cmek = 3;  // Encryption key for log fragments
}

message PushLogsResponse {
  int32 record_count = 1;
  bool log_is_sealed = 2;
}

message ScoutLogsRequest {
  string collection_id = 1;
  string database_name = 5;
}

message ScoutLogsResponse {
  // This field was once used for an ambiguous last_record_offset alternative.
  reserved 1;
  // The next record to insert will have this offset.
  int64 first_uninserted_record_offset = 2;
  // The oldest record on the log will have this offset.
  int64 first_uncompacted_record_offset = 3;
  // Whether the log is sealed.
  bool is_sealed = 4;
}

message PullLogsRequest {
  string collection_id = 1;
  string database_name = 5;
  // The offset of the first record to be returned.  This should match
  // PullLogsResponse.records[0].log_offset.
  int64 start_from_offset = 2;
  int32 batch_size = 3;
  int64 end_timestamp = 4;
}

// Represents an operation from the log
message LogRecord {
  int64 log_offset = 1;
  OperationRecord record = 2;
}

message PullLogsResponse {
  repeated LogRecord records = 1;
}

message ForkLogsRequest {
  string source_collection_id = 1;
  string target_collection_id = 2;
  string database_name = 5;
  optional Cmek cmek = 6;  // Encryption key for log fragments
}

message ForkLogsResponse {
  // The offset of the last record that was compacted.
  uint64 compaction_offset = 1;
  // The offset of the last record that was inserted.
  uint64 enumeration_offset = 2;
}

message CollectionInfo {
  string collection_id = 1;
  // The log offset of the first log entry of the collection that needs to be compacted
  int64 first_log_offset = 2;
  // The timestamp of the first log entry of the collection that needs to be compacted
  int64 first_log_ts = 3;
  // The topology the collection ID is associated with.
  optional string topology_name = 4;
}

message GetAllCollectionInfoToCompactRequest {
  // The minimum number of log entries that a collection should have before it should
  // be returned for compaction
  uint64 min_compaction_size = 1;
}

message GetAllCollectionInfoToCompactResponse {
  repeated CollectionInfo all_collection_info = 1;
}

message UpdateCollectionLogOffsetRequest {
  string collection_id = 1;
  string database_name = 5;
  // The offset of the last record that was compacted.
  int64 log_offset = 2;
}

message UpdateCollectionLogOffsetResponse {
  // Empty
}

message PurgeDirtyForCollectionRequest {
  repeated string collection_ids = 1;
  optional string topology_name = 5;
}

message PurgeDirtyForCollectionResponse {
  // Empty
}

message InspectDirtyLogRequest {
  // Empty
}

message InspectDirtyLogResponse {
  repeated string markers = 1;
}

message SealLogRequest {
  string collection_id = 1;
}

message SealLogResponse {
  // Empty
}

message MigrateLogRequest {
  string collection_id = 1;
}

message MigrateLogResponse {
  // Empty
}

message InspectLogStateRequest {
  string collection_id = 1;
  string database_name = 5;
}

message InspectLogStateResponse {
  string debug = 1;
  uint64 start = 2;
  uint64 limit = 3;
  string json = 4;
}

message ScrubLogRequest {
  oneof log_to_scrub {
    string collection_id = 1;
    string dirty_log = 2;
  };
  string database_name = 5;
  uint64 max_bytes_to_read = 3;
  uint32 max_files_to_read = 4;
}

message ScrubLogResponse {
  string calculated_setsum = 1;
  uint64 bytes_read = 2;
  repeated string errors = 3;
  bool short_read = 4; // true if read was truncated due to limits
}

message GarbageCollectPhase2Request {
  oneof log_to_collect {
    string collection_id = 1;
    string dirty_log = 2;
  };
  string database_name = 5;
}

message GarbageCollectPhase2Response {
}

message PurgeFromCacheRequest {
  oneof entry_to_evict {
    string cursor_for_collection_id = 1;
    string manifest_for_collection_id = 2;
    FragmentToEvict fragment = 3;
  };
  string database_name = 5;
}

message FragmentToEvict {
  string collection_id = 1;
  string fragment_path = 2;
};

message PurgeFromCacheResponse {
}

// A pointer to a fragment stored in object storage.
message LogFragmentPointer {
  // The path of the fragment in object storage, relative to the log prefix.
  string path = 1;
  // The first log offset contained in this fragment.
  uint64 start_offset = 2;
  // The first log offset >= start_offset NOT contained in this fragment (exclusive upper bound).
  uint64 limit_offset = 3;
  // The size of the fragment file in bytes.
  uint64 num_bytes = 4;
  // The storage prefix to prepend to fragment paths when reading from object storage.
  string storage_prefix = 5;
  // When true the parquet file uses absolute offsets (column "offset").
  // When false (or absent) the parquet file uses relative offsets
  // (column "relative_offset") and start_offset must be supplied to
  // reconstruct absolute positions.
  bool absolute_offsets = 6;
}

message ScoutLogFragmentsRequest {
  string collection_id = 1;
  string database_name = 5;
  // The offset to start scouting from.
  uint64 start_from_offset = 2;
}

message ScoutLogFragmentsResponse {
  // The next record to insert will have this offset.
  uint64 first_uninserted_record_offset = 1;
  // Fragment pointers covering the requested log window.
  repeated LogFragmentPointer fragments = 2;
}

service LogService {
  rpc PushLogs(PushLogsRequest) returns (PushLogsResponse) {}
  rpc ScoutLogs(ScoutLogsRequest) returns (ScoutLogsResponse) {}
  rpc PullLogs(PullLogsRequest) returns (PullLogsResponse) {}
  rpc ForkLogs(ForkLogsRequest) returns (ForkLogsResponse) {}
  rpc GetAllCollectionInfoToCompact(GetAllCollectionInfoToCompactRequest) returns (GetAllCollectionInfoToCompactResponse) {}
  rpc UpdateCollectionLogOffset(UpdateCollectionLogOffsetRequest) returns (UpdateCollectionLogOffsetResponse) {}
  rpc PurgeDirtyForCollection(PurgeDirtyForCollectionRequest) returns (PurgeDirtyForCollectionResponse) {}
  // This endpoint must route to the rust log service.
  rpc InspectDirtyLog(InspectDirtyLogRequest) returns (InspectDirtyLogResponse) {}
  // This endpoint must route to the go log service.
  rpc SealLog(SealLogRequest) returns (SealLogResponse) {}
  // This endpoint must route to the rust log service.
  rpc MigrateLog(MigrateLogRequest) returns (MigrateLogResponse) {}
  // RPC endpoints to expose for operator debuggability.
  // This endpoint can be supported by any log service.
  rpc InspectLogState(InspectLogStateRequest) returns (InspectLogStateResponse) {}
  // This endpoint should route to the rust log service.
  rpc ScrubLog(ScrubLogRequest) returns (ScrubLogResponse) {}
  // This endpoint should route to the rust log service.
  rpc GarbageCollectPhase2(GarbageCollectPhase2Request) returns (GarbageCollectPhase2Response) {}
  // This endpoint will purge from cache the specified items.
  rpc PurgeFromCache(PurgeFromCacheRequest) returns (PurgeFromCacheResponse) {}
  // Similar to UpdateCollectionLogOffset, but allows the offset to go back in time.
  // Uses the exact same request/response types as UpdateCollectionLogOffset by design.
  rpc RollbackCollectionLogOffset(UpdateCollectionLogOffsetRequest) returns (UpdateCollectionLogOffsetResponse) {}
  // Returns fragment pointers for the requested log window so that query/compactor
  // nodes can read fragment data directly from object storage.
  rpc ScoutLogFragments(ScoutLogFragmentsRequest) returns (ScoutLogFragmentsResponse) {}
}
