syntax = "proto3";

package confidence.flags.resolver.v1;

import "google/api/resource.proto";
import "google/protobuf/struct.proto";
import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "google/api/visibility.proto";
import "confidence/flags/admin/v1/types.proto";

import "confidence/flags/resolver/v1/types.proto";
import "confidence/flags/resolver/v1/events/events.proto";
import "confidence/auth/v1/auth.proto";
import "confidence/api/annotations.proto";
import "confidence/flags/admin/v1/events/events.proto";
option java_package = "com.spotify.confidence.flags.resolver.v1";
option java_multiple_files = true;
option java_outer_classname = "InternalApiProto";

service InternalFlagLoggerService {
  option (google.api.api_visibility).restriction = "INTERNAL";

  option (confidence.api.service_name) = "InternalFlagLogger";
  option (confidence.api.hosts) = "resolver.eu.confidence.dev";
  option (confidence.api.hosts) = "resolver.us.confidence.dev";

  // Writes flag assignment events. Mostly called from the sidecar resolver.
  // (-- api-linter: core::0136::http-uri-suffix=disabled
  //     aip.dev/not-precedent: Disabled because the additional binding. --)
  rpc WriteFlagAssigned(WriteFlagAssignedRequest) returns (WriteFlagAssignedResponse){
    option (google.api.http) = {
      post: "/v1/flagAssigned:write"
      body: "*"
      additional_bindings {
        post: "/v1/flagAssigned:writeArray"
        body: "flag_assigned"
      }
    };
    option (confidence.auth.v1.auth) = {
      required_permission: CREATE_FLAG_ASSIGNED
      allow_basic_auth: true
      required_relation: "can_write_flag_assigned"
      object_field: "account"
    };
  }

  // Client writes flag assignment events and resolve logs using client secret authentication.
  // This method uses Authorization: ClientSecret {client_secret} header instead of JWT.
  // (-- api-linter: core::0136::http-uri-suffix=disabled
  //     aip.dev/not-precedent: Disabled because the additional binding. --)
  rpc ClientWriteFlagLogs(WriteFlagLogsRequest) returns (WriteFlagLogsResponse){
    option (google.api.http) = {
      post: "/v1/clientFlagLogs:write"
      body: "*"
    };
  }

}

// The service that allows to report flag assigned and other client-side flag
// operations, useful when the resolve engine runs on the customer's premises
// (e.g. side-car)
message WriteFlagLogsRequest {
  repeated confidence.flags.resolver.v1.events.FlagAssigned flag_assigned = 1 [
    (google.api.field_behavior) = OPTIONAL
  ];

  TelemetryData telemetry_data = 2 [
    (google.api.field_behavior) = OPTIONAL
  ];

  repeated confidence.flags.admin.v1.ClientResolveInfo client_resolve_info = 3 [
    (google.api.field_behavior) = OPTIONAL
  ];
  repeated confidence.flags.admin.v1.FlagResolveInfo flag_resolve_info = 4 [
    (google.api.field_behavior) = OPTIONAL
  ];
}

message WriteFlagLogsResponse {}

// A request to write flag assignments
message WriteFlagAssignedRequest {
  // List of flag assigned events to write
  repeated confidence.flags.resolver.v1.events.FlagAssigned flag_assigned = 1 [
    (google.api.field_behavior) = REQUIRED
  ];

  TelemetryData telemetry_data = 2 [
    (google.api.field_behavior) = OPTIONAL
  ];
}

// Response to writing flag assignments
message WriteFlagAssignedResponse {
  // The total number of assigned flags, should equal the sum of the flag counts in each flag
  // assigned event.
  int64 assigned_flags = 1;
}

// Collection of telemetry metrics, usually included in request messages to
// monitor sender-side issues or performance.
//
// Counter and histogram fields contain DELTAS relative to the previous flush.
// The sender computes current - last_flushed using wrapping subtraction.
// Gauge fields (memory_bytes, state_age) are latest point-in-time values.
//
// Histograms use exponential bucketing with boundaries at exp(exponent * ln_ratio).
// BucketSpans encode dense regions of consecutive exponents,
// each with their observation count. ln_ratio is always included, giving
// consumers everything needed to reconstruct bucket boundaries and resample
// between different histogram configurations if needed.
message TelemetryData {
  // Information about the SDK/provider
  Sdk sdk = 2 [
    (google.api.field_behavior) = OPTIONAL
  ];

  ResolveLatency resolve_latency = 4;
  repeated ResolveRate resolve_rate = 5;
  StateAge state_age = 6;

  // Current linear memory size in bytes
  uint64 memory_bytes = 7;

  // Version of the embedded resolver (e.g. "0.14.0").
  // Set from the confidence-resolver crate version at build time.
  string resolver_version = 8;

  message ResolveLatency {
    // Delta sum of observed values since the last flush.
    uint32 sum = 1;
    uint32 count = 2;
    repeated BucketSpan buckets = 3;
    // Natural log of the bucket ratio. Bucket boundary = exp(exponent * ln_ratio).
    double ln_ratio = 4;
  }

  // resolve rate tagged by reason/error
  message ResolveRate {
    uint32 count = 1;
    ResolveReason reason = 2;
  }

  message StateAge {
    // millisecond epoch
    uint64 last_state_update = 1;
  }

  // a dense region of buckets
  message BucketSpan {
    int32 offset = 1;
    repeated uint32 counts = 2;
  }
}

message ResolveToken {
  oneof resolve_token {
    ResolveTokenV1 token_v1 = 1;
  }
}

message ResolveTokenV1 {
  google.protobuf.Struct evaluation_context = 1;

  map<string, AssignedFlag> assignments = 2;

  string resolve_id = 3;

  // The account that the resolve was made for
  string account = 4;

  message AssignedFlag {
    string flag = 1 [
      (google.api.resource_reference).type = "flags.confidence.dev/Flag"
    ];

    string targeting_key = 2;
    string targeting_key_selector = 10;

    string segment = 3 [
      (google.api.resource_reference).type = "flags.confidence.dev/Segment"
    ];

    string variant = 4 [
      (google.api.resource_reference).type = "flags.confidence.dev/Variant"
    ];

    string rule = 5 [
      (google.api.resource_reference).type = "flags.confidence.dev/Rule"
    ];

    ResolveReason reason = 6;

    repeated events.FallthroughAssignment fallthrough_assignments = 9;

    string assignment_id = 8;
  }
}
