From f4f266a334915c84fe392f98d602c6f7b26a0abb Mon Sep 17 00:00:00 2001 From: Volo Kluev Date: Mon, 24 Aug 2026 17:13:17 -0700 Subject: [PATCH 1/6] feat(billing): Add seat activity and seats service protos - Add seat_activity service endpoints: close_contract, get_original_seat_dates, get_seat_activity_history, get_seat_contract_state, get_seat_state, perform_seat_actions - Add seats service endpoints: assign_seat, check_assign_seats, disable_seat, get_daily_seat_usage, get_daily_seat_usage_by_project, get_seat_status, remove_seat, rollover_seats, update_seat_identifier - Define shared types: SeatStatus, SeatObject, SeatState, SeatActionRecord, ExternalProductSeatActivity, SeatActionItem, OriginalSeatDate, ExternalProductSeatStatus, SeatAssignmentOutcome, ProjectSeatUsage --- .../v1/endpoint_close_contract.proto | 9 +++ .../v1/endpoint_get_original_seat_dates.proto | 16 ++++++ .../endpoint_get_seat_activity_history.proto | 15 +++++ .../v1/endpoint_get_seat_contract_state.proto | 12 ++++ .../v1/endpoint_get_seat_state.proto | 14 +++++ .../v1/endpoint_perform_seat_actions.proto | 15 +++++ .../seat_activity/v1/seat_activity.proto | 56 +++++++++++++++++++ .../seats/v1/endpoint_assign_seat.proto | 14 +++++ .../v1/endpoint_check_assign_seats.proto | 16 ++++++ .../seats/v1/endpoint_disable_seat.proto | 12 ++++ .../v1/endpoint_get_daily_seat_usage.proto | 9 +++ ...oint_get_daily_seat_usage_by_project.proto | 9 +++ .../seats/v1/endpoint_get_seat_status.proto | 14 +++++ .../seats/v1/endpoint_remove_seat.proto | 12 ++++ .../seats/v1/endpoint_rollover_seats.proto | 10 ++++ .../v1/endpoint_update_seat_identifier.proto | 13 +++++ .../billing/v1/services/seats/v1/seats.proto | 24 ++++++++ 17 files changed, 270 insertions(+) create mode 100644 proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_close_contract.proto create mode 100644 proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_original_seat_dates.proto create mode 100644 proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_activity_history.proto create mode 100644 proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_contract_state.proto create mode 100644 proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_state.proto create mode 100644 proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_perform_seat_actions.proto create mode 100644 proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto create mode 100644 proto/sentry_protos/billing/v1/services/seats/v1/endpoint_assign_seat.proto create mode 100644 proto/sentry_protos/billing/v1/services/seats/v1/endpoint_check_assign_seats.proto create mode 100644 proto/sentry_protos/billing/v1/services/seats/v1/endpoint_disable_seat.proto create mode 100644 proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage.proto create mode 100644 proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage_by_project.proto create mode 100644 proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto create mode 100644 proto/sentry_protos/billing/v1/services/seats/v1/endpoint_remove_seat.proto create mode 100644 proto/sentry_protos/billing/v1/services/seats/v1/endpoint_rollover_seats.proto create mode 100644 proto/sentry_protos/billing/v1/services/seats/v1/endpoint_update_seat_identifier.proto create mode 100644 proto/sentry_protos/billing/v1/services/seats/v1/seats.proto diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_close_contract.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_close_contract.proto new file mode 100644 index 00000000..df46b99d --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_close_contract.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seat_activity.v1; + +message CloseContractRequest { + uint64 contract_id = 1; +} + +message CloseContractResponse {} diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_original_seat_dates.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_original_seat_dates.proto new file mode 100644 index 00000000..fea61dbd --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_original_seat_dates.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seat_activity.v1; + +import "sentry_protos/billing/v1/seat_category.proto"; +import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; + +message GetOriginalSeatDatesRequest { + uint64 organization_id = 1; + sentry_protos.billing.v1.SeatCategory seat_category = 2; + repeated string external_product_identifiers = 3; +} + +message GetOriginalSeatDatesResponse { + repeated OriginalSeatDate original_seat_dates = 1; +} diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_activity_history.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_activity_history.proto new file mode 100644 index 00000000..4366554b --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_activity_history.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seat_activity.v1; + +import "google/protobuf/timestamp.proto"; +import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; + +message GetSeatActivityHistoryRequest { + uint64 contract_id = 1; +} + +message GetSeatActivityHistoryResponse { + repeated ExternalProductSeatActivity seat_activities = 1; + google.protobuf.Timestamp read_ts = 2; +} diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_contract_state.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_contract_state.proto new file mode 100644 index 00000000..d6292f33 --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_contract_state.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seat_activity.v1; + +message GetSeatContractStateRequest { + uint64 contract_id = 1; +} + +message GetSeatContractStateResponse { + bool exists = 1; + bool is_active = 2; +} diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_state.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_state.proto new file mode 100644 index 00000000..e6d930ce --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_state.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seat_activity.v1; + +import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; + +message GetSeatStateRequest { + uint64 contract_id = 1; + SeatObject seat_object = 2; +} + +message GetSeatStateResponse { + SeatState seat_state = 1; +} diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_perform_seat_actions.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_perform_seat_actions.proto new file mode 100644 index 00000000..2bf580f4 --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_perform_seat_actions.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seat_activity.v1; + +import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; + +message PerformSeatActionsRequest { + uint64 contract_id = 1; + repeated SeatActionItem seat_actions = 2; + bool create_new_contract_state = 3; +} + +message PerformSeatActionsResponse { + repeated SeatState current_seat_states = 1; +} diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto new file mode 100644 index 00000000..4fb0eca7 --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto @@ -0,0 +1,56 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seat_activity.v1; + +import "google/protobuf/timestamp.proto"; +import "sentry_protos/billing/v1/seat_category.proto"; + +enum SeatStatus { + SEAT_STATUS_UNSPECIFIED = 0; + SEAT_STATUS_ACTIVE = 1; + SEAT_STATUS_DISABLED = 2; + SEAT_STATUS_OVER_QUOTA = 3; + SEAT_STATUS_DNE = 4; +} + +message SeatObject { + uint64 organization_id = 1; + uint64 project_id = 2; + sentry_protos.billing.v1.SeatCategory seat_category = 3; + string external_product_display_name = 4; + string external_product_identifier = 5; +} + +message SeatState { + string external_product_identifier = 1; + SeatStatus current_status = 2; + optional google.protobuf.Timestamp created_ts = 3; + optional google.protobuf.Timestamp last_action_ts = 4; + google.protobuf.Timestamp read_ts = 5; +} + +message SeatActionRecord { + uint64 contract_id = 1; + uint64 organization_id = 2; + uint64 project_id = 3; + google.protobuf.Timestamp timestamp = 4; + SeatStatus status = 5; + uint32 seat_category = 6; + string external_product_identifier = 7; + string external_product_display_name = 8; +} + +message ExternalProductSeatActivity { + string external_product_identifier = 1; + repeated SeatActionRecord seat_actions = 2; +} + +message SeatActionItem { + SeatObject seat_object = 1; + SeatState seat_state = 2; +} + +message OriginalSeatDate { + string external_product_identifier = 1; + google.protobuf.Timestamp created_ts = 2; +} diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_assign_seat.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_assign_seat.proto new file mode 100644 index 00000000..9342e33d --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_assign_seat.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seats.v1; + +import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; +import "sentry_protos/billing/v1/services/seats/v1/seats.proto"; + +message AssignSeatRequest { + sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_object = 1; +} + +message AssignSeatResponse { + SeatAssignmentOutcome outcome = 1; +} diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_check_assign_seats.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_check_assign_seats.proto new file mode 100644 index 00000000..65778ffa --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_check_assign_seats.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seats.v1; + +import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; +import "sentry_protos/billing/v1/services/seats/v1/seats.proto"; + +message CheckAssignSeatsRequest { + repeated sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_objects = 1; + optional uint64 contract_id = 2; +} + +message CheckAssignSeatsResponse { + SeatAssignmentOutcome outcome = 1; + string reason = 2; +} diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_disable_seat.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_disable_seat.proto new file mode 100644 index 00000000..7954ec15 --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_disable_seat.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seats.v1; + +import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; + +message DisableSeatRequest { + sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_object = 1; + optional uint64 contract_id = 2; +} + +message DisableSeatResponse {} diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage.proto new file mode 100644 index 00000000..05967220 --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seats.v1; + +import "sentry_protos/billing/v1/services/usage/v1/endpoint_usage.proto"; + +message GetDailySeatUsageResponse { + repeated sentry_protos.billing.v1.services.usage.v1.DailySeatUsage seat_days = 1; +} diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage_by_project.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage_by_project.proto new file mode 100644 index 00000000..3723caba --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage_by_project.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seats.v1; + +import "sentry_protos/billing/v1/services/seats/v1/seats.proto"; + +message GetDailySeatUsageByProjectResponse { + repeated ProjectSeatUsage projects = 1; +} diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto new file mode 100644 index 00000000..2d881bf1 --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seats.v1; + +import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; +import "sentry_protos/billing/v1/services/seats/v1/seats.proto"; + +message GetSeatStatusForExternalProductRequest { + sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_object = 1; +} + +message GetSeatStatusForExternalProductResponse { + ExternalProductSeatStatus status = 1; +} diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_remove_seat.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_remove_seat.proto new file mode 100644 index 00000000..2820d2c7 --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_remove_seat.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seats.v1; + +import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; + +message RemoveSeatRequest { + sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_object = 1; + optional uint64 contract_id = 2; +} + +message RemoveSeatResponse {} diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_rollover_seats.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_rollover_seats.proto new file mode 100644 index 00000000..f141cc0a --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_rollover_seats.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seats.v1; + +message RolloverSeatsToNewContractRequest { + uint64 prev_contract_id = 1; + uint64 new_contract_id = 2; +} + +message RolloverSeatsToNewContractResponse {} diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_update_seat_identifier.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_update_seat_identifier.proto new file mode 100644 index 00000000..31a51d16 --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_update_seat_identifier.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seats.v1; + +import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; + +message UpdateSeatIdentifierRequest { + sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_object = 1; + string new_identifier = 2; + optional uint64 contract_id = 3; +} + +message UpdateSeatIdentifierResponse {} diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/seats.proto b/proto/sentry_protos/billing/v1/services/seats/v1/seats.proto new file mode 100644 index 00000000..ba457693 --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/seats/v1/seats.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.seats.v1; + +import "sentry_protos/billing/v1/services/usage/v1/endpoint_usage.proto"; + +enum ExternalProductSeatStatus { + EXTERNAL_PRODUCT_SEAT_STATUS_UNSPECIFIED = 0; + EXTERNAL_PRODUCT_SEAT_STATUS_DNE = 1; + EXTERNAL_PRODUCT_SEAT_STATUS_ACTIVE = 2; + EXTERNAL_PRODUCT_SEAT_STATUS_DISABLED = 3; +} + +enum SeatAssignmentOutcome { + SEAT_ASSIGNMENT_OUTCOME_UNSPECIFIED = 0; + SEAT_ASSIGNMENT_OUTCOME_ACCEPTED = 1; + SEAT_ASSIGNMENT_OUTCOME_RATE_LIMITED = 2; + SEAT_ASSIGNMENT_OUTCOME_INVALID = 3; +} + +message ProjectSeatUsage { + uint64 project_id = 1; + repeated sentry_protos.billing.v1.services.usage.v1.DailySeatUsage seat_days = 2; +} From 250b06c66fbce3aa4f1801aa48410645f295efde Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:14:25 +0000 Subject: [PATCH 2/6] chore: Regenerate Rust bindings --- Cargo.lock | 2 +- rust/src/_include.rs | 10 + ...os.billing.v1.services.seat_activity.v1.rs | 175 ++++++++++++++++++ ...try_protos.billing.v1.services.seats.v1.rs | 156 ++++++++++++++++ ...try_protos.billing.v1.services.usage.v1.rs | 76 ++++---- 5 files changed, 380 insertions(+), 39 deletions(-) create mode 100644 rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs create mode 100644 rust/src/sentry_protos.billing.v1.services.seats.v1.rs diff --git a/Cargo.lock b/Cargo.lock index c2387c59..4267c9b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -717,7 +717,7 @@ checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "sentry_protos" -version = "0.62.3" +version = "0.64.0" dependencies = [ "prost", "prost-types", diff --git a/rust/src/_include.rs b/rust/src/_include.rs index e7ec5d80..35ceb083 100644 --- a/rust/src/_include.rs +++ b/rust/src/_include.rs @@ -74,6 +74,16 @@ pub mod sentry_protos { include!("sentry_protos.billing.v1.services.rate_card.v1.rs"); } } + pub mod seat_activity { + pub mod v1 { + include!("sentry_protos.billing.v1.services.seat_activity.v1.rs"); + } + } + pub mod seats { + pub mod v1 { + include!("sentry_protos.billing.v1.services.seats.v1.rs"); + } + } pub mod trial { pub mod v1 { include!("sentry_protos.billing.v1.services.trial.v1.rs"); diff --git a/rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs b/rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs new file mode 100644 index 00000000..7c50ce1a --- /dev/null +++ b/rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs @@ -0,0 +1,175 @@ +// This file is @generated by prost-build. +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct CloseContractRequest { + #[prost(uint64, tag = "1")] + pub contract_id: u64, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct CloseContractResponse {} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SeatObject { + #[prost(uint64, tag = "1")] + pub organization_id: u64, + #[prost(uint64, tag = "2")] + pub project_id: u64, + #[prost(enumeration = "super::super::super::SeatCategory", tag = "3")] + pub seat_category: i32, + #[prost(string, tag = "4")] + pub external_product_display_name: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub external_product_identifier: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SeatState { + #[prost(string, tag = "1")] + pub external_product_identifier: ::prost::alloc::string::String, + #[prost(enumeration = "SeatStatus", tag = "2")] + pub current_status: i32, + #[prost(message, optional, tag = "3")] + pub created_ts: ::core::option::Option<::prost_types::Timestamp>, + #[prost(message, optional, tag = "4")] + pub last_action_ts: ::core::option::Option<::prost_types::Timestamp>, + #[prost(message, optional, tag = "5")] + pub read_ts: ::core::option::Option<::prost_types::Timestamp>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SeatActionRecord { + #[prost(uint64, tag = "1")] + pub contract_id: u64, + #[prost(uint64, tag = "2")] + pub organization_id: u64, + #[prost(uint64, tag = "3")] + pub project_id: u64, + #[prost(message, optional, tag = "4")] + pub timestamp: ::core::option::Option<::prost_types::Timestamp>, + #[prost(enumeration = "SeatStatus", tag = "5")] + pub status: i32, + #[prost(uint32, tag = "6")] + pub seat_category: u32, + #[prost(string, tag = "7")] + pub external_product_identifier: ::prost::alloc::string::String, + #[prost(string, tag = "8")] + pub external_product_display_name: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ExternalProductSeatActivity { + #[prost(string, tag = "1")] + pub external_product_identifier: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "2")] + pub seat_actions: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SeatActionItem { + #[prost(message, optional, tag = "1")] + pub seat_object: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub seat_state: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct OriginalSeatDate { + #[prost(string, tag = "1")] + pub external_product_identifier: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub created_ts: ::core::option::Option<::prost_types::Timestamp>, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SeatStatus { + Unspecified = 0, + Active = 1, + Disabled = 2, + OverQuota = 3, + Dne = 4, +} +impl SeatStatus { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "SEAT_STATUS_UNSPECIFIED", + Self::Active => "SEAT_STATUS_ACTIVE", + Self::Disabled => "SEAT_STATUS_DISABLED", + Self::OverQuota => "SEAT_STATUS_OVER_QUOTA", + Self::Dne => "SEAT_STATUS_DNE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SEAT_STATUS_UNSPECIFIED" => Some(Self::Unspecified), + "SEAT_STATUS_ACTIVE" => Some(Self::Active), + "SEAT_STATUS_DISABLED" => Some(Self::Disabled), + "SEAT_STATUS_OVER_QUOTA" => Some(Self::OverQuota), + "SEAT_STATUS_DNE" => Some(Self::Dne), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetOriginalSeatDatesRequest { + #[prost(uint64, tag = "1")] + pub organization_id: u64, + #[prost(enumeration = "super::super::super::SeatCategory", tag = "2")] + pub seat_category: i32, + #[prost(string, repeated, tag = "3")] + pub external_product_identifiers: ::prost::alloc::vec::Vec< + ::prost::alloc::string::String, + >, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOriginalSeatDatesResponse { + #[prost(message, repeated, tag = "1")] + pub original_seat_dates: ::prost::alloc::vec::Vec, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetSeatActivityHistoryRequest { + #[prost(uint64, tag = "1")] + pub contract_id: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetSeatActivityHistoryResponse { + #[prost(message, repeated, tag = "1")] + pub seat_activities: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub read_ts: ::core::option::Option<::prost_types::Timestamp>, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetSeatContractStateRequest { + #[prost(uint64, tag = "1")] + pub contract_id: u64, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetSeatContractStateResponse { + #[prost(bool, tag = "1")] + pub exists: bool, + #[prost(bool, tag = "2")] + pub is_active: bool, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetSeatStateRequest { + #[prost(uint64, tag = "1")] + pub contract_id: u64, + #[prost(message, optional, tag = "2")] + pub seat_object: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetSeatStateResponse { + #[prost(message, optional, tag = "1")] + pub seat_state: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PerformSeatActionsRequest { + #[prost(uint64, tag = "1")] + pub contract_id: u64, + #[prost(message, repeated, tag = "2")] + pub seat_actions: ::prost::alloc::vec::Vec, + #[prost(bool, tag = "3")] + pub create_new_contract_state: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PerformSeatActionsResponse { + #[prost(message, repeated, tag = "1")] + pub current_seat_states: ::prost::alloc::vec::Vec, +} diff --git a/rust/src/sentry_protos.billing.v1.services.seats.v1.rs b/rust/src/sentry_protos.billing.v1.services.seats.v1.rs new file mode 100644 index 00000000..2507fc4c --- /dev/null +++ b/rust/src/sentry_protos.billing.v1.services.seats.v1.rs @@ -0,0 +1,156 @@ +// This file is @generated by prost-build. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProjectSeatUsage { + #[prost(uint64, tag = "1")] + pub project_id: u64, + #[prost(message, repeated, tag = "2")] + pub seat_days: ::prost::alloc::vec::Vec, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum ExternalProductSeatStatus { + Unspecified = 0, + Dne = 1, + Active = 2, + Disabled = 3, +} +impl ExternalProductSeatStatus { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "EXTERNAL_PRODUCT_SEAT_STATUS_UNSPECIFIED", + Self::Dne => "EXTERNAL_PRODUCT_SEAT_STATUS_DNE", + Self::Active => "EXTERNAL_PRODUCT_SEAT_STATUS_ACTIVE", + Self::Disabled => "EXTERNAL_PRODUCT_SEAT_STATUS_DISABLED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "EXTERNAL_PRODUCT_SEAT_STATUS_UNSPECIFIED" => Some(Self::Unspecified), + "EXTERNAL_PRODUCT_SEAT_STATUS_DNE" => Some(Self::Dne), + "EXTERNAL_PRODUCT_SEAT_STATUS_ACTIVE" => Some(Self::Active), + "EXTERNAL_PRODUCT_SEAT_STATUS_DISABLED" => Some(Self::Disabled), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SeatAssignmentOutcome { + Unspecified = 0, + Accepted = 1, + RateLimited = 2, + Invalid = 3, +} +impl SeatAssignmentOutcome { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "SEAT_ASSIGNMENT_OUTCOME_UNSPECIFIED", + Self::Accepted => "SEAT_ASSIGNMENT_OUTCOME_ACCEPTED", + Self::RateLimited => "SEAT_ASSIGNMENT_OUTCOME_RATE_LIMITED", + Self::Invalid => "SEAT_ASSIGNMENT_OUTCOME_INVALID", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SEAT_ASSIGNMENT_OUTCOME_UNSPECIFIED" => Some(Self::Unspecified), + "SEAT_ASSIGNMENT_OUTCOME_ACCEPTED" => Some(Self::Accepted), + "SEAT_ASSIGNMENT_OUTCOME_RATE_LIMITED" => Some(Self::RateLimited), + "SEAT_ASSIGNMENT_OUTCOME_INVALID" => Some(Self::Invalid), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct AssignSeatRequest { + #[prost(message, optional, tag = "1")] + pub seat_object: ::core::option::Option, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct AssignSeatResponse { + #[prost(enumeration = "SeatAssignmentOutcome", tag = "1")] + pub outcome: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CheckAssignSeatsRequest { + #[prost(message, repeated, tag = "1")] + pub seat_objects: ::prost::alloc::vec::Vec< + super::super::seat_activity::v1::SeatObject, + >, + #[prost(uint64, optional, tag = "2")] + pub contract_id: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct CheckAssignSeatsResponse { + #[prost(enumeration = "SeatAssignmentOutcome", tag = "1")] + pub outcome: i32, + #[prost(string, tag = "2")] + pub reason: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct DisableSeatRequest { + #[prost(message, optional, tag = "1")] + pub seat_object: ::core::option::Option, + #[prost(uint64, optional, tag = "2")] + pub contract_id: ::core::option::Option, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct DisableSeatResponse {} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetDailySeatUsageResponse { + #[prost(message, repeated, tag = "1")] + pub seat_days: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetDailySeatUsageByProjectResponse { + #[prost(message, repeated, tag = "1")] + pub projects: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetSeatStatusForExternalProductRequest { + #[prost(message, optional, tag = "1")] + pub seat_object: ::core::option::Option, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetSeatStatusForExternalProductResponse { + #[prost(enumeration = "ExternalProductSeatStatus", tag = "1")] + pub status: i32, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RemoveSeatRequest { + #[prost(message, optional, tag = "1")] + pub seat_object: ::core::option::Option, + #[prost(uint64, optional, tag = "2")] + pub contract_id: ::core::option::Option, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RemoveSeatResponse {} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RolloverSeatsToNewContractRequest { + #[prost(uint64, tag = "1")] + pub prev_contract_id: u64, + #[prost(uint64, tag = "2")] + pub new_contract_id: u64, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RolloverSeatsToNewContractResponse {} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct UpdateSeatIdentifierRequest { + #[prost(message, optional, tag = "1")] + pub seat_object: ::core::option::Option, + #[prost(string, tag = "2")] + pub new_identifier: ::prost::alloc::string::String, + #[prost(uint64, optional, tag = "3")] + pub contract_id: ::core::option::Option, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct UpdateSeatIdentifierResponse {} diff --git a/rust/src/sentry_protos.billing.v1.services.usage.v1.rs b/rust/src/sentry_protos.billing.v1.services.usage.v1.rs index c0055c36..ad20be77 100644 --- a/rust/src/sentry_protos.billing.v1.services.usage.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.usage.v1.rs @@ -1,43 +1,5 @@ // This file is @generated by prost-build. #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] -pub struct PageToken { - #[prost(oneof = "page_token::Value", tags = "1")] - pub value: ::core::option::Option, -} -/// Nested message and enum types in `PageToken`. -pub mod page_token { - #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Oneof)] - pub enum Value { - #[prost(uint64, tag = "1")] - Offset(u64), - } -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct GetOrgsWithUsageRequest { - #[prost(message, optional, tag = "1")] - pub start: ::core::option::Option<::prost_types::Timestamp>, - #[prost(message, optional, tag = "2")] - pub end: ::core::option::Option<::prost_types::Timestamp>, - /// Optional filter for specific data categories. - /// When empty, usage for all categories is queried. - #[prost(enumeration = "super::super::super::DataCategory", repeated, tag = "3")] - pub categories: ::prost::alloc::vec::Vec, - /// Optional max number of items to return. - #[prost(uint32, tag = "4")] - pub limit: u32, - /// Optional page token for pagination. If provided, returns the next page of results. - #[prost(message, optional, tag = "5")] - pub page_token: ::core::option::Option, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct GetOrgsWithUsageResponse { - #[prost(uint64, repeated, tag = "1")] - pub organization_ids: ::prost::alloc::vec::Vec, - /// Page token for the next page of results - #[prost(message, optional, tag = "2")] - pub page_token: ::core::option::Option, -} -#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct CategoryUsage { #[prost(enumeration = "super::super::super::DataCategory", tag = "1")] pub category: i32, @@ -98,6 +60,44 @@ pub struct GetUsageRequest { #[prost(enumeration = "super::super::super::DataCategory", repeated, tag = "4")] pub categories: ::prost::alloc::vec::Vec, } +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct PageToken { + #[prost(oneof = "page_token::Value", tags = "1")] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `PageToken`. +pub mod page_token { + #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Oneof)] + pub enum Value { + #[prost(uint64, tag = "1")] + Offset(u64), + } +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetOrgsWithUsageRequest { + #[prost(message, optional, tag = "1")] + pub start: ::core::option::Option<::prost_types::Timestamp>, + #[prost(message, optional, tag = "2")] + pub end: ::core::option::Option<::prost_types::Timestamp>, + /// Optional filter for specific data categories. + /// When empty, usage for all categories is queried. + #[prost(enumeration = "super::super::super::DataCategory", repeated, tag = "3")] + pub categories: ::prost::alloc::vec::Vec, + /// Optional max number of items to return. + #[prost(uint32, tag = "4")] + pub limit: u32, + /// Optional page token for pagination. If provided, returns the next page of results. + #[prost(message, optional, tag = "5")] + pub page_token: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetOrgsWithUsageResponse { + #[prost(uint64, repeated, tag = "1")] + pub organization_ids: ::prost::alloc::vec::Vec, + /// Page token for the next page of results + #[prost(message, optional, tag = "2")] + pub page_token: ::core::option::Option, +} #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetUsageByProjectRequest { #[prost(uint64, tag = "1")] From 742e00f2c8f8bbe7da56d819b07ffe7751b4de48 Mon Sep 17 00:00:00 2001 From: Volo Kluev Date: Wed, 26 Aug 2026 23:06:31 +0000 Subject: [PATCH 3/6] feat(billing): Consolidate SeatStatus enums into common proto Move SeatStatus into billing/v1/common/v1/seat_status.proto so both seat_activity and seats services share one enum. Remove the duplicate ExternalProductSeatStatus from seats/v1/seats.proto. --- .../billing/v1/common/v1/seat_status.proto | 11 +++++++++++ .../services/seat_activity/v1/seat_activity.proto | 13 +++---------- .../seats/v1/endpoint_get_seat_status.proto | 4 ++-- .../billing/v1/services/seats/v1/seats.proto | 7 ------- 4 files changed, 16 insertions(+), 19 deletions(-) create mode 100644 proto/sentry_protos/billing/v1/common/v1/seat_status.proto diff --git a/proto/sentry_protos/billing/v1/common/v1/seat_status.proto b/proto/sentry_protos/billing/v1/common/v1/seat_status.proto new file mode 100644 index 00000000..abe5e394 --- /dev/null +++ b/proto/sentry_protos/billing/v1/common/v1/seat_status.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.common.v1; + +enum SeatStatus { + SEAT_STATUS_UNSPECIFIED = 0; + SEAT_STATUS_ACTIVE = 1; + SEAT_STATUS_DISABLED = 2; + SEAT_STATUS_OVER_QUOTA = 3; + SEAT_STATUS_DNE = 4; +} diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto index 4fb0eca7..fe5c1300 100644 --- a/proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto @@ -3,16 +3,9 @@ syntax = "proto3"; package sentry_protos.billing.v1.services.seat_activity.v1; import "google/protobuf/timestamp.proto"; +import "sentry_protos/billing/v1/common/v1/seat_status.proto"; import "sentry_protos/billing/v1/seat_category.proto"; -enum SeatStatus { - SEAT_STATUS_UNSPECIFIED = 0; - SEAT_STATUS_ACTIVE = 1; - SEAT_STATUS_DISABLED = 2; - SEAT_STATUS_OVER_QUOTA = 3; - SEAT_STATUS_DNE = 4; -} - message SeatObject { uint64 organization_id = 1; uint64 project_id = 2; @@ -23,7 +16,7 @@ message SeatObject { message SeatState { string external_product_identifier = 1; - SeatStatus current_status = 2; + sentry_protos.billing.v1.common.v1.SeatStatus current_status = 2; optional google.protobuf.Timestamp created_ts = 3; optional google.protobuf.Timestamp last_action_ts = 4; google.protobuf.Timestamp read_ts = 5; @@ -34,7 +27,7 @@ message SeatActionRecord { uint64 organization_id = 2; uint64 project_id = 3; google.protobuf.Timestamp timestamp = 4; - SeatStatus status = 5; + sentry_protos.billing.v1.common.v1.SeatStatus status = 5; uint32 seat_category = 6; string external_product_identifier = 7; string external_product_display_name = 8; diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto index 2d881bf1..3d202107 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto @@ -2,13 +2,13 @@ syntax = "proto3"; package sentry_protos.billing.v1.services.seats.v1; +import "sentry_protos/billing/v1/common/v1/seat_status.proto"; import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; -import "sentry_protos/billing/v1/services/seats/v1/seats.proto"; message GetSeatStatusForExternalProductRequest { sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_object = 1; } message GetSeatStatusForExternalProductResponse { - ExternalProductSeatStatus status = 1; + sentry_protos.billing.v1.common.v1.SeatStatus status = 1; } diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/seats.proto b/proto/sentry_protos/billing/v1/services/seats/v1/seats.proto index ba457693..f9ef1e85 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/seats.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/seats.proto @@ -4,13 +4,6 @@ package sentry_protos.billing.v1.services.seats.v1; import "sentry_protos/billing/v1/services/usage/v1/endpoint_usage.proto"; -enum ExternalProductSeatStatus { - EXTERNAL_PRODUCT_SEAT_STATUS_UNSPECIFIED = 0; - EXTERNAL_PRODUCT_SEAT_STATUS_DNE = 1; - EXTERNAL_PRODUCT_SEAT_STATUS_ACTIVE = 2; - EXTERNAL_PRODUCT_SEAT_STATUS_DISABLED = 3; -} - enum SeatAssignmentOutcome { SEAT_ASSIGNMENT_OUTCOME_UNSPECIFIED = 0; SEAT_ASSIGNMENT_OUTCOME_ACCEPTED = 1; From 86407f784c394b42afe63c191d5e9c2533858aac Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 23:10:35 +0000 Subject: [PATCH 4/6] chore: Regenerate Rust bindings --- Cargo.lock | 2 +- .../src/sentry_protos.billing.v1.common.v1.rs | 35 +++++++++++++++++ ...os.billing.v1.services.seat_activity.v1.rs | 39 +------------------ ...try_protos.billing.v1.services.seats.v1.rs | 34 +--------------- 4 files changed, 39 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4267c9b3..11be5763 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -717,7 +717,7 @@ checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "sentry_protos" -version = "0.64.0" +version = "0.64.1" dependencies = [ "prost", "prost-types", diff --git a/rust/src/sentry_protos.billing.v1.common.v1.rs b/rust/src/sentry_protos.billing.v1.common.v1.rs index ebaa7be8..fa888362 100644 --- a/rust/src/sentry_protos.billing.v1.common.v1.rs +++ b/rust/src/sentry_protos.billing.v1.common.v1.rs @@ -530,6 +530,41 @@ pub struct DataCategoryRetention { #[prost(message, optional, tag = "2")] pub settings: ::core::option::Option, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum SeatStatus { + Unspecified = 0, + Active = 1, + Disabled = 2, + OverQuota = 3, + Dne = 4, +} +impl SeatStatus { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "SEAT_STATUS_UNSPECIFIED", + Self::Active => "SEAT_STATUS_ACTIVE", + Self::Disabled => "SEAT_STATUS_DISABLED", + Self::OverQuota => "SEAT_STATUS_OVER_QUOTA", + Self::Dne => "SEAT_STATUS_DNE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SEAT_STATUS_UNSPECIFIED" => Some(Self::Unspecified), + "SEAT_STATUS_ACTIVE" => Some(Self::Active), + "SEAT_STATUS_DISABLED" => Some(Self::Disabled), + "SEAT_STATUS_OVER_QUOTA" => Some(Self::OverQuota), + "SEAT_STATUS_DNE" => Some(Self::Dne), + _ => None, + } + } +} /// Card payment method details. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct Card { diff --git a/rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs b/rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs index 7c50ce1a..0e44f7ec 100644 --- a/rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs @@ -23,7 +23,7 @@ pub struct SeatObject { pub struct SeatState { #[prost(string, tag = "1")] pub external_product_identifier: ::prost::alloc::string::String, - #[prost(enumeration = "SeatStatus", tag = "2")] + #[prost(enumeration = "super::super::super::common::v1::SeatStatus", tag = "2")] pub current_status: i32, #[prost(message, optional, tag = "3")] pub created_ts: ::core::option::Option<::prost_types::Timestamp>, @@ -42,7 +42,7 @@ pub struct SeatActionRecord { pub project_id: u64, #[prost(message, optional, tag = "4")] pub timestamp: ::core::option::Option<::prost_types::Timestamp>, - #[prost(enumeration = "SeatStatus", tag = "5")] + #[prost(enumeration = "super::super::super::common::v1::SeatStatus", tag = "5")] pub status: i32, #[prost(uint32, tag = "6")] pub seat_category: u32, @@ -72,41 +72,6 @@ pub struct OriginalSeatDate { #[prost(message, optional, tag = "2")] pub created_ts: ::core::option::Option<::prost_types::Timestamp>, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum SeatStatus { - Unspecified = 0, - Active = 1, - Disabled = 2, - OverQuota = 3, - Dne = 4, -} -impl SeatStatus { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - Self::Unspecified => "SEAT_STATUS_UNSPECIFIED", - Self::Active => "SEAT_STATUS_ACTIVE", - Self::Disabled => "SEAT_STATUS_DISABLED", - Self::OverQuota => "SEAT_STATUS_OVER_QUOTA", - Self::Dne => "SEAT_STATUS_DNE", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "SEAT_STATUS_UNSPECIFIED" => Some(Self::Unspecified), - "SEAT_STATUS_ACTIVE" => Some(Self::Active), - "SEAT_STATUS_DISABLED" => Some(Self::Disabled), - "SEAT_STATUS_OVER_QUOTA" => Some(Self::OverQuota), - "SEAT_STATUS_DNE" => Some(Self::Dne), - _ => None, - } - } -} #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetOriginalSeatDatesRequest { #[prost(uint64, tag = "1")] diff --git a/rust/src/sentry_protos.billing.v1.services.seats.v1.rs b/rust/src/sentry_protos.billing.v1.services.seats.v1.rs index 2507fc4c..ff7e2a89 100644 --- a/rust/src/sentry_protos.billing.v1.services.seats.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.seats.v1.rs @@ -8,38 +8,6 @@ pub struct ProjectSeatUsage { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] -pub enum ExternalProductSeatStatus { - Unspecified = 0, - Dne = 1, - Active = 2, - Disabled = 3, -} -impl ExternalProductSeatStatus { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - Self::Unspecified => "EXTERNAL_PRODUCT_SEAT_STATUS_UNSPECIFIED", - Self::Dne => "EXTERNAL_PRODUCT_SEAT_STATUS_DNE", - Self::Active => "EXTERNAL_PRODUCT_SEAT_STATUS_ACTIVE", - Self::Disabled => "EXTERNAL_PRODUCT_SEAT_STATUS_DISABLED", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "EXTERNAL_PRODUCT_SEAT_STATUS_UNSPECIFIED" => Some(Self::Unspecified), - "EXTERNAL_PRODUCT_SEAT_STATUS_DNE" => Some(Self::Dne), - "EXTERNAL_PRODUCT_SEAT_STATUS_ACTIVE" => Some(Self::Active), - "EXTERNAL_PRODUCT_SEAT_STATUS_DISABLED" => Some(Self::Disabled), - _ => None, - } - } -} -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] pub enum SeatAssignmentOutcome { Unspecified = 0, Accepted = 1, @@ -122,7 +90,7 @@ pub struct GetSeatStatusForExternalProductRequest { } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetSeatStatusForExternalProductResponse { - #[prost(enumeration = "ExternalProductSeatStatus", tag = "1")] + #[prost(enumeration = "super::super::super::common::v1::SeatStatus", tag = "1")] pub status: i32, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] From 092a50158b2555b6b76a8f80cf6ad93931bd81f4 Mon Sep 17 00:00:00 2001 From: Volo Kluev Date: Thu, 27 Aug 2026 22:49:37 +0000 Subject: [PATCH 5/6] docs: Add comments to seat service proto definitions Add documentation comments to all seat_activity and seats service proto messages explaining what each endpoint does, its constraints, and any notable behavior (e.g. OVER_QUOTA -> DISABLED mapping, idempotent rollover, high-throughput check-in path). --- .../billing/v1/common/v1/seat_status.proto | 8 ++++++++ .../v1/endpoint_close_contract.proto | 3 +++ .../v1/endpoint_get_original_seat_dates.proto | 3 +++ .../v1/endpoint_get_seat_activity_history.proto | 3 +++ .../v1/endpoint_get_seat_contract_state.proto | 3 +++ .../v1/endpoint_get_seat_state.proto | 2 ++ .../v1/endpoint_perform_seat_actions.proto | 8 ++++++++ .../seat_activity/v1/seat_activity.proto | 17 +++++++++++++++++ .../seats/v1/endpoint_assign_seat.proto | 4 ++++ .../seats/v1/endpoint_check_assign_seats.proto | 5 +++++ .../seats/v1/endpoint_disable_seat.proto | 4 ++++ .../v1/endpoint_get_daily_seat_usage.proto | 2 ++ ...dpoint_get_daily_seat_usage_by_project.proto | 2 ++ .../seats/v1/endpoint_get_seat_status.proto | 4 ++++ .../seats/v1/endpoint_remove_seat.proto | 3 +++ .../seats/v1/endpoint_rollover_seats.proto | 5 +++++ .../v1/endpoint_update_seat_identifier.proto | 5 +++++ .../billing/v1/services/seats/v1/seats.proto | 5 +++++ 18 files changed, 86 insertions(+) diff --git a/proto/sentry_protos/billing/v1/common/v1/seat_status.proto b/proto/sentry_protos/billing/v1/common/v1/seat_status.proto index abe5e394..0b41d3c4 100644 --- a/proto/sentry_protos/billing/v1/common/v1/seat_status.proto +++ b/proto/sentry_protos/billing/v1/common/v1/seat_status.proto @@ -2,10 +2,18 @@ syntax = "proto3"; package sentry_protos.billing.v1.common.v1; +// Lifecycle status of a billing seat. Shared by both the seat_activity and +// seats services. enum SeatStatus { SEAT_STATUS_UNSPECIFIED = 0; + // Seat is active and counting toward the billing period. SEAT_STATUS_ACTIVE = 1; + // Seat is disabled; it still counts toward the period total but new + // activity (e.g. check-ins) is dropped. SEAT_STATUS_DISABLED = 2; + // Seat was created but exceeds reserved+PAYG capacity. Treated as + // DISABLED by external consumers (the distinction is internal only). SEAT_STATUS_OVER_QUOTA = 3; + // Seat does not exist (removed or never created). SEAT_STATUS_DNE = 4; } diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_close_contract.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_close_contract.proto index df46b99d..1819f967 100644 --- a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_close_contract.proto +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_close_contract.proto @@ -2,6 +2,9 @@ syntax = "proto3"; package sentry_protos.billing.v1.services.seat_activity.v1; +// Marks a contract's seat tracker as CLOSED, preventing further seat actions. +// Called during rollover before writing seats to the new contract. +// Silently succeeds if no tracker exists for the contract. message CloseContractRequest { uint64 contract_id = 1; } diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_original_seat_dates.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_original_seat_dates.proto index fea61dbd..5a3cb824 100644 --- a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_original_seat_dates.proto +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_original_seat_dates.proto @@ -5,6 +5,9 @@ package sentry_protos.billing.v1.services.seat_activity.v1; import "sentry_protos/billing/v1/seat_category.proto"; import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; +// Returns each seat's earliest-ever recorded action timestamp across the +// entire organization (not scoped to a single contract). Used during contract +// rollover to sort seats by seniority when capacity is tight. message GetOriginalSeatDatesRequest { uint64 organization_id = 1; sentry_protos.billing.v1.SeatCategory seat_category = 2; diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_activity_history.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_activity_history.proto index 4366554b..db09b670 100644 --- a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_activity_history.proto +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_activity_history.proto @@ -5,6 +5,9 @@ package sentry_protos.billing.v1.services.seat_activity.v1; import "google/protobuf/timestamp.proto"; import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; +// Returns every seat action recorded for a contract, grouped by product +// identifier and ordered chronologically. This is the raw audit log of all +// state transitions during a billing period. message GetSeatActivityHistoryRequest { uint64 contract_id = 1; } diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_contract_state.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_contract_state.proto index d6292f33..4181d4bb 100644 --- a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_contract_state.proto +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_contract_state.proto @@ -2,6 +2,9 @@ syntax = "proto3"; package sentry_protos.billing.v1.services.seat_activity.v1; +// Checks whether a contract has a seat tracking record and whether it is +// ACTIVE. Used by the seats service to decide whether to bootstrap a new +// tracker or trigger a rollover from a previous contract. message GetSeatContractStateRequest { uint64 contract_id = 1; } diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_state.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_state.proto index e6d930ce..d6ac589e 100644 --- a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_state.proto +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_get_seat_state.proto @@ -4,6 +4,8 @@ package sentry_protos.billing.v1.services.seat_activity.v1; import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; +// Returns the current status of a single seat by replaying its action history +// on the given contract (last action wins). Returns DNE if no actions exist. message GetSeatStateRequest { uint64 contract_id = 1; SeatObject seat_object = 2; diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_perform_seat_actions.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_perform_seat_actions.proto index 2bf580f4..fe6f672d 100644 --- a/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_perform_seat_actions.proto +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/endpoint_perform_seat_actions.proto @@ -4,9 +4,17 @@ package sentry_protos.billing.v1.services.seat_activity.v1; import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; +// The single write path for all seat mutations. Appends action records in bulk +// under a transaction. Does NOT check budgets or eligibility — that is the +// caller's responsibility. +// +// Rejects duplicate identifiers within a single request. Requires an ACTIVE +// contract tracker unless create_new_contract_state is set. message PerformSeatActionsRequest { uint64 contract_id = 1; repeated SeatActionItem seat_actions = 2; + // When true, creates a new ACTIVE contract tracker for this contract. + // Fails if one already exists (used for first-time setup and rollover). bool create_new_contract_state = 3; } diff --git a/proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto b/proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto index fe5c1300..c54f4165 100644 --- a/proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto +++ b/proto/sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto @@ -6,22 +6,34 @@ import "google/protobuf/timestamp.proto"; import "sentry_protos/billing/v1/common/v1/seat_status.proto"; import "sentry_protos/billing/v1/seat_category.proto"; +// Identifies a billable seat (e.g. a monitor, uptime detector, or seer +// contributor) within an organization and project. message SeatObject { uint64 organization_id = 1; uint64 project_id = 2; sentry_protos.billing.v1.SeatCategory seat_category = 3; + // Human-readable label for the seat (e.g. monitor name). string external_product_display_name = 4; + // Unique identifier for the seat within its category and project + // (e.g. monitor slug). Used as the key for all state lookups. string external_product_identifier = 5; } +// Reconstructed point-in-time state for a single seat, derived by replaying +// its action history (last action wins). message SeatState { string external_product_identifier = 1; sentry_protos.billing.v1.common.v1.SeatStatus current_status = 2; + // Timestamp of the first-ever action for this seat on the contract. optional google.protobuf.Timestamp created_ts = 3; + // Timestamp of the most recent action for this seat on the contract. optional google.protobuf.Timestamp last_action_ts = 4; + // When the state was read (server clock). Useful for cache staleness checks. google.protobuf.Timestamp read_ts = 5; } +// A single immutable record of a seat state transition, stored as an +// append-only audit log. message SeatActionRecord { uint64 contract_id = 1; uint64 organization_id = 2; @@ -33,16 +45,21 @@ message SeatActionRecord { string external_product_display_name = 8; } +// All action records for a single external product identifier on a contract, +// ordered chronologically. message ExternalProductSeatActivity { string external_product_identifier = 1; repeated SeatActionRecord seat_actions = 2; } +// Input item for PerformSeatActions: pairs a seat with its desired new state. message SeatActionItem { SeatObject seat_object = 1; SeatState seat_state = 2; } +// The earliest-ever recorded action timestamp for a seat across all contracts +// in an organization. Used to sort seats by seniority during rollover. message OriginalSeatDate { string external_product_identifier = 1; google.protobuf.Timestamp created_ts = 2; diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_assign_seat.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_assign_seat.proto index 9342e33d..0ef35cbb 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_assign_seat.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_assign_seat.proto @@ -5,6 +5,10 @@ package sentry_protos.billing.v1.services.seats.v1; import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; import "sentry_protos/billing/v1/services/seats/v1/seats.proto"; +// Assigns a single billing seat for the current contract period. Internally +// runs check_assign_seats to determine capacity: ACCEPTED seats become ACTIVE, +// RATE_LIMITED seats become OVER_QUOTA, INVALID seats are rejected. +// Lazily initializes the contract's seat tracker on first write. message AssignSeatRequest { sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_object = 1; } diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_check_assign_seats.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_check_assign_seats.proto index 65778ffa..d5d9b0f3 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_check_assign_seats.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_check_assign_seats.proto @@ -5,8 +5,13 @@ package sentry_protos.billing.v1.services.seats.v1; import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; import "sentry_protos/billing/v1/services/seats/v1/seats.proto"; +// Dry-run check: can these seats be assigned? Validates the product is enabled +// on the plan, filters out already-existing seats, then simulates usage pricing +// to determine if reserved or PAYG budgets have capacity. +// All seat_objects must share the same seat category and organization. message CheckAssignSeatsRequest { repeated sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_objects = 1; + // When omitted, resolves to the org's current contract. optional uint64 contract_id = 2; } diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_disable_seat.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_disable_seat.proto index 7954ec15..ffb16093 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_disable_seat.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_disable_seat.proto @@ -4,8 +4,12 @@ package sentry_protos.billing.v1.services.seats.v1; import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; +// Sets a seat to DISABLED status. The seat still counts toward the period +// total but stops accepting new activity (e.g. check-ins are dropped). +// Only allowed for seat categories whose policy supports disabling. message DisableSeatRequest { sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_object = 1; + // When omitted, resolves to the org's current contract. optional uint64 contract_id = 2; } diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage.proto index 05967220..dba0905f 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage.proto @@ -4,6 +4,8 @@ package sentry_protos.billing.v1.services.seats.v1; import "sentry_protos/billing/v1/services/usage/v1/endpoint_usage.proto"; +// Response-only proto; the request reuses GetUsageRequest from the usage service. +// Returns per-day billable and active seat counts for the requested period. message GetDailySeatUsageResponse { repeated sentry_protos.billing.v1.services.usage.v1.DailySeatUsage seat_days = 1; } diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage_by_project.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage_by_project.proto index 3723caba..0c58f0e8 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage_by_project.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_daily_seat_usage_by_project.proto @@ -4,6 +4,8 @@ package sentry_protos.billing.v1.services.seats.v1; import "sentry_protos/billing/v1/services/seats/v1/seats.proto"; +// Response-only proto; the request reuses GetUsageByProjectRequest from the +// usage service. Same as GetDailySeatUsageResponse but broken out per project. message GetDailySeatUsageByProjectResponse { repeated ProjectSeatUsage projects = 1; } diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto index 3d202107..3b240ada 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_get_seat_status.proto @@ -5,6 +5,10 @@ package sentry_protos.billing.v1.services.seats.v1; import "sentry_protos/billing/v1/common/v1/seat_status.proto"; import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; +// High-throughput read path called by the monitor consumer on every check-in. +// Looks up the seat's current state and maps it to an external-facing status. +// OVER_QUOTA is mapped to DISABLED — external consumers do not distinguish +// between the two. message GetSeatStatusForExternalProductRequest { sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_object = 1; } diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_remove_seat.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_remove_seat.proto index 2820d2c7..4b3c71d8 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_remove_seat.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_remove_seat.proto @@ -4,8 +4,11 @@ package sentry_protos.billing.v1.services.seats.v1; import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; +// Permanently removes a seat (status -> DNE), freeing the slot for +// reallocation. Only allowed for seat categories whose policy supports removal. message RemoveSeatRequest { sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_object = 1; + // When omitted, resolves to the org's current contract. optional uint64 contract_id = 2; } diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_rollover_seats.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_rollover_seats.proto index f141cc0a..ce9900b7 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_rollover_seats.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_rollover_seats.proto @@ -2,6 +2,11 @@ syntax = "proto3"; package sentry_protos.billing.v1.services.seats.v1; +// Recreates carry-over seats when a contract rolls over. Closes the old +// contract's tracker, collects seats that ended ACTIVE or OVER_QUOTA, orders +// them by seniority, and splits against the new contract's capacity (fits -> +// ACTIVE, overflow -> OVER_QUOTA). No-ops if the new contract already has +// seat state (idempotent on retry). message RolloverSeatsToNewContractRequest { uint64 prev_contract_id = 1; uint64 new_contract_id = 2; diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_update_seat_identifier.proto b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_update_seat_identifier.proto index 31a51d16..e0b1d9d8 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_update_seat_identifier.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/endpoint_update_seat_identifier.proto @@ -4,9 +4,14 @@ package sentry_protos.billing.v1.services.seats.v1; import "sentry_protos/billing/v1/services/seat_activity/v1/seat_activity.proto"; +// Atomically renames a seat's identifier (e.g. when a monitor slug changes). +// Implemented as two actions in one batch: old identifier -> DNE, new +// identifier -> previous status. Only allowed for seat categories whose +// policy supports renaming. message UpdateSeatIdentifierRequest { sentry_protos.billing.v1.services.seat_activity.v1.SeatObject seat_object = 1; string new_identifier = 2; + // When omitted, resolves to the org's current contract. optional uint64 contract_id = 3; } diff --git a/proto/sentry_protos/billing/v1/services/seats/v1/seats.proto b/proto/sentry_protos/billing/v1/services/seats/v1/seats.proto index f9ef1e85..5cfd3ac3 100644 --- a/proto/sentry_protos/billing/v1/services/seats/v1/seats.proto +++ b/proto/sentry_protos/billing/v1/services/seats/v1/seats.proto @@ -4,13 +4,18 @@ package sentry_protos.billing.v1.services.seats.v1; import "sentry_protos/billing/v1/services/usage/v1/endpoint_usage.proto"; +// Result of a seat assignment or check_assign_seats call. enum SeatAssignmentOutcome { SEAT_ASSIGNMENT_OUTCOME_UNSPECIFIED = 0; + // Seat fits within reserved or PAYG capacity. SEAT_ASSIGNMENT_OUTCOME_ACCEPTED = 1; + // No budget remaining; seat is created as OVER_QUOTA on assign. SEAT_ASSIGNMENT_OUTCOME_RATE_LIMITED = 2; + // Product not enabled on plan or request is structurally invalid. SEAT_ASSIGNMENT_OUTCOME_INVALID = 3; } +// Per-project seat usage over a date range, used by get_daily_seat_usage_by_project. message ProjectSeatUsage { uint64 project_id = 1; repeated sentry_protos.billing.v1.services.usage.v1.DailySeatUsage seat_days = 2; From 6af66b0197d11d99e5500c58ef089c8a2cbfcce1 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:39:10 +0000 Subject: [PATCH 6/6] chore: Regenerate Rust bindings --- .../src/sentry_protos.billing.v1.common.v1.rs | 8 ++++ ...os.billing.v1.services.seat_activity.v1.rs | 39 +++++++++++++++++++ ...try_protos.billing.v1.services.seats.v1.rs | 39 +++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/rust/src/sentry_protos.billing.v1.common.v1.rs b/rust/src/sentry_protos.billing.v1.common.v1.rs index fa888362..bb89a8d3 100644 --- a/rust/src/sentry_protos.billing.v1.common.v1.rs +++ b/rust/src/sentry_protos.billing.v1.common.v1.rs @@ -530,13 +530,21 @@ pub struct DataCategoryRetention { #[prost(message, optional, tag = "2")] pub settings: ::core::option::Option, } +/// Lifecycle status of a billing seat. Shared by both the seat_activity and +/// seats services. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum SeatStatus { Unspecified = 0, + /// Seat is active and counting toward the billing period. Active = 1, + /// Seat is disabled; it still counts toward the period total but new + /// activity (e.g. check-ins) is dropped. Disabled = 2, + /// Seat was created but exceeds reserved+PAYG capacity. Treated as + /// DISABLED by external consumers (the distinction is internal only). OverQuota = 3, + /// Seat does not exist (removed or never created). Dne = 4, } impl SeatStatus { diff --git a/rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs b/rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs index 0e44f7ec..82019421 100644 --- a/rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.seat_activity.v1.rs @@ -1,4 +1,7 @@ // This file is @generated by prost-build. +/// Marks a contract's seat tracker as CLOSED, preventing further seat actions. +/// Called during rollover before writing seats to the new contract. +/// Silently succeeds if no tracker exists for the contract. #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct CloseContractRequest { #[prost(uint64, tag = "1")] @@ -6,6 +9,8 @@ pub struct CloseContractRequest { } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct CloseContractResponse {} +/// Identifies a billable seat (e.g. a monitor, uptime detector, or seer +/// contributor) within an organization and project. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct SeatObject { #[prost(uint64, tag = "1")] @@ -14,24 +19,34 @@ pub struct SeatObject { pub project_id: u64, #[prost(enumeration = "super::super::super::SeatCategory", tag = "3")] pub seat_category: i32, + /// Human-readable label for the seat (e.g. monitor name). #[prost(string, tag = "4")] pub external_product_display_name: ::prost::alloc::string::String, + /// Unique identifier for the seat within its category and project + /// (e.g. monitor slug). Used as the key for all state lookups. #[prost(string, tag = "5")] pub external_product_identifier: ::prost::alloc::string::String, } +/// Reconstructed point-in-time state for a single seat, derived by replaying +/// its action history (last action wins). #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct SeatState { #[prost(string, tag = "1")] pub external_product_identifier: ::prost::alloc::string::String, #[prost(enumeration = "super::super::super::common::v1::SeatStatus", tag = "2")] pub current_status: i32, + /// Timestamp of the first-ever action for this seat on the contract. #[prost(message, optional, tag = "3")] pub created_ts: ::core::option::Option<::prost_types::Timestamp>, + /// Timestamp of the most recent action for this seat on the contract. #[prost(message, optional, tag = "4")] pub last_action_ts: ::core::option::Option<::prost_types::Timestamp>, + /// When the state was read (server clock). Useful for cache staleness checks. #[prost(message, optional, tag = "5")] pub read_ts: ::core::option::Option<::prost_types::Timestamp>, } +/// A single immutable record of a seat state transition, stored as an +/// append-only audit log. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct SeatActionRecord { #[prost(uint64, tag = "1")] @@ -51,6 +66,8 @@ pub struct SeatActionRecord { #[prost(string, tag = "8")] pub external_product_display_name: ::prost::alloc::string::String, } +/// All action records for a single external product identifier on a contract, +/// ordered chronologically. #[derive(Clone, PartialEq, ::prost::Message)] pub struct ExternalProductSeatActivity { #[prost(string, tag = "1")] @@ -58,6 +75,7 @@ pub struct ExternalProductSeatActivity { #[prost(message, repeated, tag = "2")] pub seat_actions: ::prost::alloc::vec::Vec, } +/// Input item for PerformSeatActions: pairs a seat with its desired new state. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct SeatActionItem { #[prost(message, optional, tag = "1")] @@ -65,6 +83,8 @@ pub struct SeatActionItem { #[prost(message, optional, tag = "2")] pub seat_state: ::core::option::Option, } +/// The earliest-ever recorded action timestamp for a seat across all contracts +/// in an organization. Used to sort seats by seniority during rollover. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct OriginalSeatDate { #[prost(string, tag = "1")] @@ -72,6 +92,9 @@ pub struct OriginalSeatDate { #[prost(message, optional, tag = "2")] pub created_ts: ::core::option::Option<::prost_types::Timestamp>, } +/// Returns each seat's earliest-ever recorded action timestamp across the +/// entire organization (not scoped to a single contract). Used during contract +/// rollover to sort seats by seniority when capacity is tight. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetOriginalSeatDatesRequest { #[prost(uint64, tag = "1")] @@ -88,6 +111,9 @@ pub struct GetOriginalSeatDatesResponse { #[prost(message, repeated, tag = "1")] pub original_seat_dates: ::prost::alloc::vec::Vec, } +/// Returns every seat action recorded for a contract, grouped by product +/// identifier and ordered chronologically. This is the raw audit log of all +/// state transitions during a billing period. #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetSeatActivityHistoryRequest { #[prost(uint64, tag = "1")] @@ -100,6 +126,9 @@ pub struct GetSeatActivityHistoryResponse { #[prost(message, optional, tag = "2")] pub read_ts: ::core::option::Option<::prost_types::Timestamp>, } +/// Checks whether a contract has a seat tracking record and whether it is +/// ACTIVE. Used by the seats service to decide whether to bootstrap a new +/// tracker or trigger a rollover from a previous contract. #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetSeatContractStateRequest { #[prost(uint64, tag = "1")] @@ -112,6 +141,8 @@ pub struct GetSeatContractStateResponse { #[prost(bool, tag = "2")] pub is_active: bool, } +/// Returns the current status of a single seat by replaying its action history +/// on the given contract (last action wins). Returns DNE if no actions exist. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetSeatStateRequest { #[prost(uint64, tag = "1")] @@ -124,12 +155,20 @@ pub struct GetSeatStateResponse { #[prost(message, optional, tag = "1")] pub seat_state: ::core::option::Option, } +/// The single write path for all seat mutations. Appends action records in bulk +/// under a transaction. Does NOT check budgets or eligibility — that is the +/// caller's responsibility. +/// +/// Rejects duplicate identifiers within a single request. Requires an ACTIVE +/// contract tracker unless create_new_contract_state is set. #[derive(Clone, PartialEq, ::prost::Message)] pub struct PerformSeatActionsRequest { #[prost(uint64, tag = "1")] pub contract_id: u64, #[prost(message, repeated, tag = "2")] pub seat_actions: ::prost::alloc::vec::Vec, + /// When true, creates a new ACTIVE contract tracker for this contract. + /// Fails if one already exists (used for first-time setup and rollover). #[prost(bool, tag = "3")] pub create_new_contract_state: bool, } diff --git a/rust/src/sentry_protos.billing.v1.services.seats.v1.rs b/rust/src/sentry_protos.billing.v1.services.seats.v1.rs index ff7e2a89..1f753d4f 100644 --- a/rust/src/sentry_protos.billing.v1.services.seats.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.seats.v1.rs @@ -1,4 +1,5 @@ // This file is @generated by prost-build. +/// Per-project seat usage over a date range, used by get_daily_seat_usage_by_project. #[derive(Clone, PartialEq, ::prost::Message)] pub struct ProjectSeatUsage { #[prost(uint64, tag = "1")] @@ -6,12 +7,16 @@ pub struct ProjectSeatUsage { #[prost(message, repeated, tag = "2")] pub seat_days: ::prost::alloc::vec::Vec, } +/// Result of a seat assignment or check_assign_seats call. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum SeatAssignmentOutcome { Unspecified = 0, + /// Seat fits within reserved or PAYG capacity. Accepted = 1, + /// No budget remaining; seat is created as OVER_QUOTA on assign. RateLimited = 2, + /// Product not enabled on plan or request is structurally invalid. Invalid = 3, } impl SeatAssignmentOutcome { @@ -38,6 +43,10 @@ impl SeatAssignmentOutcome { } } } +/// Assigns a single billing seat for the current contract period. Internally +/// runs check_assign_seats to determine capacity: ACCEPTED seats become ACTIVE, +/// RATE_LIMITED seats become OVER_QUOTA, INVALID seats are rejected. +/// Lazily initializes the contract's seat tracker on first write. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct AssignSeatRequest { #[prost(message, optional, tag = "1")] @@ -48,12 +57,17 @@ pub struct AssignSeatResponse { #[prost(enumeration = "SeatAssignmentOutcome", tag = "1")] pub outcome: i32, } +/// Dry-run check: can these seats be assigned? Validates the product is enabled +/// on the plan, filters out already-existing seats, then simulates usage pricing +/// to determine if reserved or PAYG budgets have capacity. +/// All seat_objects must share the same seat category and organization. #[derive(Clone, PartialEq, ::prost::Message)] pub struct CheckAssignSeatsRequest { #[prost(message, repeated, tag = "1")] pub seat_objects: ::prost::alloc::vec::Vec< super::super::seat_activity::v1::SeatObject, >, + /// When omitted, resolves to the org's current contract. #[prost(uint64, optional, tag = "2")] pub contract_id: ::core::option::Option, } @@ -64,25 +78,37 @@ pub struct CheckAssignSeatsResponse { #[prost(string, tag = "2")] pub reason: ::prost::alloc::string::String, } +/// Sets a seat to DISABLED status. The seat still counts toward the period +/// total but stops accepting new activity (e.g. check-ins are dropped). +/// Only allowed for seat categories whose policy supports disabling. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct DisableSeatRequest { #[prost(message, optional, tag = "1")] pub seat_object: ::core::option::Option, + /// When omitted, resolves to the org's current contract. #[prost(uint64, optional, tag = "2")] pub contract_id: ::core::option::Option, } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct DisableSeatResponse {} +/// Response-only proto; the request reuses GetUsageRequest from the usage service. +/// Returns per-day billable and active seat counts for the requested period. #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetDailySeatUsageResponse { #[prost(message, repeated, tag = "1")] pub seat_days: ::prost::alloc::vec::Vec, } +/// Response-only proto; the request reuses GetUsageByProjectRequest from the +/// usage service. Same as GetDailySeatUsageResponse but broken out per project. #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetDailySeatUsageByProjectResponse { #[prost(message, repeated, tag = "1")] pub projects: ::prost::alloc::vec::Vec, } +/// High-throughput read path called by the monitor consumer on every check-in. +/// Looks up the seat's current state and maps it to an external-facing status. +/// OVER_QUOTA is mapped to DISABLED — external consumers do not distinguish +/// between the two. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetSeatStatusForExternalProductRequest { #[prost(message, optional, tag = "1")] @@ -93,15 +119,23 @@ pub struct GetSeatStatusForExternalProductResponse { #[prost(enumeration = "super::super::super::common::v1::SeatStatus", tag = "1")] pub status: i32, } +/// Permanently removes a seat (status -> DNE), freeing the slot for +/// reallocation. Only allowed for seat categories whose policy supports removal. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct RemoveSeatRequest { #[prost(message, optional, tag = "1")] pub seat_object: ::core::option::Option, + /// When omitted, resolves to the org's current contract. #[prost(uint64, optional, tag = "2")] pub contract_id: ::core::option::Option, } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct RemoveSeatResponse {} +/// Recreates carry-over seats when a contract rolls over. Closes the old +/// contract's tracker, collects seats that ended ACTIVE or OVER_QUOTA, orders +/// them by seniority, and splits against the new contract's capacity (fits -> +/// ACTIVE, overflow -> OVER_QUOTA). No-ops if the new contract already has +/// seat state (idempotent on retry). #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct RolloverSeatsToNewContractRequest { #[prost(uint64, tag = "1")] @@ -111,12 +145,17 @@ pub struct RolloverSeatsToNewContractRequest { } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct RolloverSeatsToNewContractResponse {} +/// Atomically renames a seat's identifier (e.g. when a monitor slug changes). +/// Implemented as two actions in one batch: old identifier -> DNE, new +/// identifier -> previous status. Only allowed for seat categories whose +/// policy supports renaming. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct UpdateSeatIdentifierRequest { #[prost(message, optional, tag = "1")] pub seat_object: ::core::option::Option, #[prost(string, tag = "2")] pub new_identifier: ::prost::alloc::string::String, + /// When omitted, resolves to the org's current contract. #[prost(uint64, optional, tag = "3")] pub contract_id: ::core::option::Option, }