// Proposed contract, version 1. Status: proposal for review, not confirmed design.
//
// Synchronous calls about what an Organization has and how much it used. Entitlements are a domain of farm (decided 2026-10-30, D2).
// Callers: ai-gateway (Reserve before a model call, Settle after it, in one statement each; req 69),
// every system (Check when its cached copy is missing or stale, StreamSnapshot at start-up; req 68),
// releases (Check of the features a channel requires; req 111.8), moochat (Check of a tool's features; req 122.5).
// The callers listed are authentication only (decided 2026-11-11): each RPC below names who may call it and what
// it sees, and refuses anyone else with PERMISSION_DENIED.
// Deadline: Check 500 ms, Reserve and Settle 500 ms, StreamSnapshot 60 s.
// x-moonitoring-callers: ai-gateway, moochat, grant, releases, ai-model-platform, news
// x-moonitoring-requirements: 68, 69, 111.8, 122.5
syntax = "proto3";

package moonitoring.farm.entitlements.v1;

import "google/protobuf/timestamp.proto";

service EntitlementsService {
  // Whether an Organization holds a feature now, with the limits that apply.
  // Callers: every system of the service's list. Sees: the answer for the Organization and feature it asks about.
  rpc Check(CheckRequest) returns (CheckResponse);
  // Streams the active entitlements, as of one write of entitlements, to bootstrap a system's cache.
  // Callers: every system of the service's list. Sees: the active entitlements, without usage.
  rpc StreamSnapshot(StreamSnapshotRequest) returns (stream StreamSnapshotResponse);
}

service UsageService {
  // Reserves usage of a metered feature before the work: checks the limit and adds the reservation atomically.
  // A retry with the same request_id returns the same reservation and is not counted twice.
  // x-moonitoring-callers: ai-gateway
  // Callers: ai-gateway only, which meters every model call. Sees: its own reservations.
  rpc Reserve(ReserveRequest) returns (ReserveResponse);
  // Settles a reservation after the work with the actual amounts; the difference is released.
  // x-moonitoring-callers: ai-gateway
  // Callers: ai-gateway only. Sees: only the reservations it made.
  rpc Settle(SettleRequest) returns (SettleResponse);
}

message CheckRequest {
  // Exactly one of organization_id and moonitoring_user_id is set.
  string organization_id = 1;
  string moonitoring_user_id = 2;
  // e.g. farm.models.mastitis.
  string feature = 3;
}

message CheckResponse {
  bool entitled = 1;
  string entitlement_id = 2;
  // The entitlement's limits, as a JSON object; empty means no limit.
  string limits_json = 3;
  // When the entitlement ends; unset when open.
  google.protobuf.Timestamp valid_until = 4;
}

message StreamSnapshotRequest {
  string organization_id = 1;
}

message StreamSnapshotResponse {
  string as_of_event_id = 1;
  Entitlement entitlement = 2;
  // The moment the snapshot was read, as in the snapshot of access (decided 2026-11-05).
  google.protobuf.Timestamp as_of = 3;
}

message Entitlement {
  string id = 1;
  string organization_id = 2;
  string feature = 3;
  google.protobuf.Timestamp valid_from = 4;
  google.protobuf.Timestamp valid_until = 5;
  string limits_json = 6;
}

message ReserveRequest {
  // The caller's request id; the same id reserves once.
  string request_id = 1;
  string organization_id = 2;
  string moonitoring_user_id = 3;
  string feature = 4;
  // Period in the Organization's timezone, e.g. 2026-10; empty: the current one.
  string period = 5;
  Amounts amounts = 6;
}

message Amounts {
  int64 requests = 1;
  int64 input_tokens = 2;
  int64 output_tokens = 3;
  int64 cost_micros = 4;
}

message ReserveResponse {
  string reservation_id = 1;
  ReserveOutcome outcome = 2;
  // What remains in the period after this reservation.
  Amounts remaining = 3;
  // What the entitlement says happens at 100 %: block, degrade or bill.
  string at_limit = 4;
}

// Past the limit, an entitlement whose at_limit is degrade or bill still answers RESERVED, with remaining at or
// below zero, so the usage is counted; the caller degrades (ai-gateway moves to the active lite profile) or goes on.
// LIMIT_REACHED means the work must not run (at_limit block) and reserves nothing.
enum ReserveOutcome {
  RESERVE_OUTCOME_UNSPECIFIED = 0;
  RESERVE_OUTCOME_RESERVED = 1;
  RESERVE_OUTCOME_LIMIT_REACHED = 2;
  RESERVE_OUTCOME_NOT_ENTITLED = 3;
}

message SettleRequest {
  string reservation_id = 1;
  Amounts actual = 2;
}

message SettleResponse {
  Amounts period_total = 1;
}
