From 44975dab199937f2009338684f193e217d894501 Mon Sep 17 00:00:00 2001 From: flesher Date: Wed, 10 Jun 2026 15:55:21 -0500 Subject: [PATCH 1/9] =?UTF-8?q?refactor(sites):=20rename=20ReassignDevices?= =?UTF-8?q?ToSite=20=E2=86=92=20AssignDevicesToSite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standardizes the parent-assignment RPC verb across the fleet hierarchy: all hierarchical assignment RPCs now use Assign* (no Reassign/Set/Move variants). No semantic change — the existing optional target_site_id already encodes both initial assignment and re-parenting via the same null-equals-unassigned convention. Sets up the naming convention used by the new RPCs landing in subsequent commits (AssignDevicesToRack, AssignDevicesToBuilding, AssignRacksToSite). Refs #420. --- .../api/generated/sites/v1/sites_pb.ts | 36 +- client/src/protoFleet/api/sites.ts | 10 +- .../FleetLayout/FleetLayout.test.tsx | 2 +- .../sites/hooks/useSiteModals.test.ts | 2 +- .../features/sites/hooks/useSiteModals.ts | 2 +- proto/sites/v1/sites.proto | 16 +- server/generated/grpc/sites/v1/sites.pb.go | 385 +++++++++--------- .../sites/v1/sitesv1connect/sites.connect.go | 52 +-- server/generated/sqlc/site.sql.go | 56 +-- .../domain/fleetmanagement/collection_test.go | 2 +- server/internal/domain/sites/models/models.go | 4 +- server/internal/domain/sites/service.go | 8 +- server/internal/domain/sites/service_test.go | 26 +- .../interfaces/mocks/mock_site_store.go | 30 +- .../internal/domain/stores/interfaces/site.go | 8 +- ...lection_site_placement_integration_test.go | 10 +- .../internal/domain/stores/sqlstores/site.go | 4 +- .../site_invariant_integration_test.go | 2 +- .../handlers/middleware/rpc_permissions.go | 12 +- server/internal/handlers/sites/handler.go | 6 +- .../internal/handlers/sites/handler_test.go | 12 +- server/internal/handlers/sites/translate.go | 4 +- server/sqlc/queries/site.sql | 4 +- 23 files changed, 346 insertions(+), 347 deletions(-) diff --git a/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts b/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts index 3f1bd2c01..9079632ce 100644 --- a/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts +++ b/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts @@ -15,7 +15,7 @@ import type { Message } from "@bufbuild/protobuf"; export const file_sites_v1_sites: GenFile = /*@__PURE__*/ fileDesc( - "ChRzaXRlcy92MS9zaXRlcy5wcm90bxIIc2l0ZXMudjEi4gIKBFNpdGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIVCg1sb2NhdGlvbl9jaXR5GAQgASgJEhYKDmxvY2F0aW9uX3N0YXRlGAUgASgJEhAKCHRpbWV6b25lGAYgASgJEhkKEXBvd2VyX2NhcGFjaXR5X213GAcgASgBEhYKDm5ldHdvcmtfY29uZmlnGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB2FkZHJlc3MYCyABKAkSEwoLcG9zdGFsX2NvZGUYDSABKAkSDwoHY291bnRyeRgOIAEoCRINCgVub3RlcxgPIAEoCUoECAMQBEoECAwQDVILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIicAoOU2l0ZVdpdGhDb3VudHMSHAoEc2l0ZRgBIAEoCzIOLnNpdGVzLnYxLlNpdGUSFAoMZGV2aWNlX2NvdW50GAIgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAMgASgDEhIKCnJhY2tfY291bnQYBCABKAMiEgoQTGlzdFNpdGVzUmVxdWVzdCI8ChFMaXN0U2l0ZXNSZXNwb25zZRInCgVzaXRlcxgBIAMoCzIYLnNpdGVzLnYxLlNpdGVXaXRoQ291bnRzIu0CChFDcmVhdGVTaXRlUmVxdWVzdBIYCgRuYW1lGAEgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYAyABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAQgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgFIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgGIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYByABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAggASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgKIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAsgASgJQge6SARyAhgCEhcKBW5vdGVzGAwgASgJQgi6SAVyAxiAIEoECAIQA0oECAkQClILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSQ3JlYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIoIDChFVcGRhdGVTaXRlUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYBCABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAUgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgGIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgHIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYCCABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAkgASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgLIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAwgASgJQge6SARyAhgCEhcKBW5vdGVzGA0gASgJQgi6SAVyAxiAIEoECAMQBEoECAoQC1ILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSVXBkYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIigKEURlbGV0ZVNpdGVSZXF1ZXN0EhMKAmlkGAEgASgDQge6SAQiAiAAInQKEkRlbGV0ZVNpdGVSZXNwb25zZRIfChd1bmFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZkZWxldGVkX2J1aWxkaW5nX2NvdW50GAIgASgDEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgDIAEoAyKJAQocUmVhc3NpZ25EZXZpY2VzVG9TaXRlUmVxdWVzdBIkCg50YXJnZXRfc2l0ZV9pZBgBIAEoA0IHukgEIgIgAEgAiAEBEjAKEmRldmljZV9pZGVudGlmaWVycxgCIAMoCUIUukgRkgEOCAEQkE4iB3IFEAEYgAJCEQoPX3RhcmdldF9zaXRlX2lkIn4KEVBlckRldmljZUNvbmZsaWN0EhkKEWRldmljZV9pZGVudGlmaWVyGAEgASgJEjEKBnJlYXNvbhgCIAEoDjIhLnNpdGVzLnYxLlBlckRldmljZUNvbmZsaWN0UmVhc29uEhsKE2NvbmZsaWN0aW5nX3NpdGVfaWQYAyABKAMiaQodUmVhc3NpZ25EZXZpY2VzVG9TaXRlUmVzcG9uc2USGAoQcmVhc3NpZ25lZF9jb3VudBgBIAEoAxIuCgljb25mbGljdHMYAiADKAsyGy5zaXRlcy52MS5QZXJEZXZpY2VDb25mbGljdCJ0ChtBc3NpZ25CdWlsZGluZ1RvU2l0ZVJlcXVlc3QSHAoLYnVpbGRpbmdfaWQYASABKANCB7pIBCICIAASJAoOdGFyZ2V0X3NpdGVfaWQYAiABKANCB7pIBCICIABIAIgBAUIRCg9fdGFyZ2V0X3NpdGVfaWQiXgocQXNzaWduQnVpbGRpbmdUb1NpdGVSZXNwb25zZRIdChVyZWFzc2lnbmVkX3JhY2tfY291bnQYASABKAMSHwoXcmVhc3NpZ25lZF9kZXZpY2VfY291bnQYAiABKAMiLwoTR2V0U2l0ZVN0YXRzUmVxdWVzdBIYCgdzaXRlX2lkGAEgASgDQge6SAQiAiAAIv8CChRHZXRTaXRlU3RhdHNSZXNwb25zZRIPCgdzaXRlX2lkGAEgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAIgASgFEhQKDGRldmljZV9jb3VudBgDIAEoBRIXCg9yZXBvcnRpbmdfY291bnQYBCABKAUSGgoSdG90YWxfaGFzaHJhdGVfdGhzGAUgASgBEhoKEmF2Z19lZmZpY2llbmN5X2p0aBgGIAEoARIWCg50b3RhbF9wb3dlcl9rdxgHIAEoARIVCg1oYXNoaW5nX2NvdW50GAggASgFEhQKDGJyb2tlbl9jb3VudBgJIAEoBRIVCg1vZmZsaW5lX2NvdW50GAogASgFEhYKDnNsZWVwaW5nX2NvdW50GAsgASgFEiAKGGhhc2hyYXRlX3JlcG9ydGluZ19jb3VudBgMIAEoBRIiChplZmZpY2llbmN5X3JlcG9ydGluZ19jb3VudBgNIAEoBRIdChVwb3dlcl9yZXBvcnRpbmdfY291bnQYDiABKAUqswEKF1BlckRldmljZUNvbmZsaWN0UmVhc29uEioKJlBFUl9ERVZJQ0VfQ09ORkxJQ1RfUkVBU09OX1VOU1BFQ0lGSUVEEAASLworUEVSX0RFVklDRV9DT05GTElDVF9SRUFTT05fREVWSUNFX05PVF9GT1VORBABEjsKN1BFUl9ERVZJQ0VfQ09ORkxJQ1RfUkVBU09OX0RFVklDRV9JTl9SQUNLX0FUX09USEVSX1NJVEUQAjLOBAoLU2l0ZVNlcnZpY2USRAoJTGlzdFNpdGVzEhouc2l0ZXMudjEuTGlzdFNpdGVzUmVxdWVzdBobLnNpdGVzLnYxLkxpc3RTaXRlc1Jlc3BvbnNlEkcKCkNyZWF0ZVNpdGUSGy5zaXRlcy52MS5DcmVhdGVTaXRlUmVxdWVzdBocLnNpdGVzLnYxLkNyZWF0ZVNpdGVSZXNwb25zZRJHCgpVcGRhdGVTaXRlEhsuc2l0ZXMudjEuVXBkYXRlU2l0ZVJlcXVlc3QaHC5zaXRlcy52MS5VcGRhdGVTaXRlUmVzcG9uc2USRwoKRGVsZXRlU2l0ZRIbLnNpdGVzLnYxLkRlbGV0ZVNpdGVSZXF1ZXN0Ghwuc2l0ZXMudjEuRGVsZXRlU2l0ZVJlc3BvbnNlEmgKFVJlYXNzaWduRGV2aWNlc1RvU2l0ZRImLnNpdGVzLnYxLlJlYXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QaJy5zaXRlcy52MS5SZWFzc2lnbkRldmljZXNUb1NpdGVSZXNwb25zZRJlChRBc3NpZ25CdWlsZGluZ1RvU2l0ZRIlLnNpdGVzLnYxLkFzc2lnbkJ1aWxkaW5nVG9TaXRlUmVxdWVzdBomLnNpdGVzLnYxLkFzc2lnbkJ1aWxkaW5nVG9TaXRlUmVzcG9uc2USTQoMR2V0U2l0ZVN0YXRzEh0uc2l0ZXMudjEuR2V0U2l0ZVN0YXRzUmVxdWVzdBoeLnNpdGVzLnYxLkdldFNpdGVTdGF0c1Jlc3BvbnNlQqABCgxjb20uc2l0ZXMudjFCClNpdGVzUHJvdG9QAVpDZ2l0aHViLmNvbS9ibG9jay9wcm90by1mbGVldC9zZXJ2ZXIvZ2VuZXJhdGVkL2dycGMvc2l0ZXMvdjE7c2l0ZXN2MaICA1NYWKoCCFNpdGVzLlYxygIIU2l0ZXNcVjHiAhRTaXRlc1xWMVxHUEJNZXRhZGF0YeoCCVNpdGVzOjpWMWIGcHJvdG8z", + "ChRzaXRlcy92MS9zaXRlcy5wcm90bxIIc2l0ZXMudjEi4gIKBFNpdGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIVCg1sb2NhdGlvbl9jaXR5GAQgASgJEhYKDmxvY2F0aW9uX3N0YXRlGAUgASgJEhAKCHRpbWV6b25lGAYgASgJEhkKEXBvd2VyX2NhcGFjaXR5X213GAcgASgBEhYKDm5ldHdvcmtfY29uZmlnGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB2FkZHJlc3MYCyABKAkSEwoLcG9zdGFsX2NvZGUYDSABKAkSDwoHY291bnRyeRgOIAEoCRINCgVub3RlcxgPIAEoCUoECAMQBEoECAwQDVILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIicAoOU2l0ZVdpdGhDb3VudHMSHAoEc2l0ZRgBIAEoCzIOLnNpdGVzLnYxLlNpdGUSFAoMZGV2aWNlX2NvdW50GAIgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAMgASgDEhIKCnJhY2tfY291bnQYBCABKAMiEgoQTGlzdFNpdGVzUmVxdWVzdCI8ChFMaXN0U2l0ZXNSZXNwb25zZRInCgVzaXRlcxgBIAMoCzIYLnNpdGVzLnYxLlNpdGVXaXRoQ291bnRzIu0CChFDcmVhdGVTaXRlUmVxdWVzdBIYCgRuYW1lGAEgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYAyABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAQgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgFIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgGIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYByABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAggASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgKIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAsgASgJQge6SARyAhgCEhcKBW5vdGVzGAwgASgJQgi6SAVyAxiAIEoECAIQA0oECAkQClILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSQ3JlYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIoIDChFVcGRhdGVTaXRlUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYBCABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAUgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgGIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgHIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYCCABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAkgASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgLIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAwgASgJQge6SARyAhgCEhcKBW5vdGVzGA0gASgJQgi6SAVyAxiAIEoECAMQBEoECAoQC1ILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSVXBkYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIigKEURlbGV0ZVNpdGVSZXF1ZXN0EhMKAmlkGAEgASgDQge6SAQiAiAAInQKEkRlbGV0ZVNpdGVSZXNwb25zZRIfChd1bmFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZkZWxldGVkX2J1aWxkaW5nX2NvdW50GAIgASgDEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgDIAEoAyKHAQoaQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QSJAoOdGFyZ2V0X3NpdGVfaWQYASABKANCB7pIBCICIABIAIgBARIwChJkZXZpY2VfaWRlbnRpZmllcnMYAiADKAlCFLpIEZIBDggBEJBOIgdyBRABGIACQhEKD190YXJnZXRfc2l0ZV9pZCJ+ChFQZXJEZXZpY2VDb25mbGljdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCgZyZWFzb24YAiABKA4yIS5zaXRlcy52MS5QZXJEZXZpY2VDb25mbGljdFJlYXNvbhIbChNjb25mbGljdGluZ19zaXRlX2lkGAMgASgDImcKG0Fzc2lnbkRldmljZXNUb1NpdGVSZXNwb25zZRIYChByZWFzc2lnbmVkX2NvdW50GAEgASgDEi4KCWNvbmZsaWN0cxgCIAMoCzIbLnNpdGVzLnYxLlBlckRldmljZUNvbmZsaWN0InQKG0Fzc2lnbkJ1aWxkaW5nVG9TaXRlUmVxdWVzdBIcCgtidWlsZGluZ19pZBgBIAEoA0IHukgEIgIgABIkCg50YXJnZXRfc2l0ZV9pZBgCIAEoA0IHukgEIgIgAEgAiAEBQhEKD190YXJnZXRfc2l0ZV9pZCJeChxBc3NpZ25CdWlsZGluZ1RvU2l0ZVJlc3BvbnNlEh0KFXJlYXNzaWduZWRfcmFja19jb3VudBgBIAEoAxIfChdyZWFzc2lnbmVkX2RldmljZV9jb3VudBgCIAEoAyIvChNHZXRTaXRlU3RhdHNSZXF1ZXN0EhgKB3NpdGVfaWQYASABKANCB7pIBCICIAAi/wIKFEdldFNpdGVTdGF0c1Jlc3BvbnNlEg8KB3NpdGVfaWQYASABKAMSFgoOYnVpbGRpbmdfY291bnQYAiABKAUSFAoMZGV2aWNlX2NvdW50GAMgASgFEhcKD3JlcG9ydGluZ19jb3VudBgEIAEoBRIaChJ0b3RhbF9oYXNocmF0ZV90aHMYBSABKAESGgoSYXZnX2VmZmljaWVuY3lfanRoGAYgASgBEhYKDnRvdGFsX3Bvd2VyX2t3GAcgASgBEhUKDWhhc2hpbmdfY291bnQYCCABKAUSFAoMYnJva2VuX2NvdW50GAkgASgFEhUKDW9mZmxpbmVfY291bnQYCiABKAUSFgoOc2xlZXBpbmdfY291bnQYCyABKAUSIAoYaGFzaHJhdGVfcmVwb3J0aW5nX2NvdW50GAwgASgFEiIKGmVmZmljaWVuY3lfcmVwb3J0aW5nX2NvdW50GA0gASgFEh0KFXBvd2VyX3JlcG9ydGluZ19jb3VudBgOIAEoBSqzAQoXUGVyRGV2aWNlQ29uZmxpY3RSZWFzb24SKgomUEVSX0RFVklDRV9DT05GTElDVF9SRUFTT05fVU5TUEVDSUZJRUQQABIvCitQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9ERVZJQ0VfTk9UX0ZPVU5EEAESOwo3UEVSX0RFVklDRV9DT05GTElDVF9SRUFTT05fREVWSUNFX0lOX1JBQ0tfQVRfT1RIRVJfU0lURRACMsgECgtTaXRlU2VydmljZRJECglMaXN0U2l0ZXMSGi5zaXRlcy52MS5MaXN0U2l0ZXNSZXF1ZXN0Ghsuc2l0ZXMudjEuTGlzdFNpdGVzUmVzcG9uc2USRwoKQ3JlYXRlU2l0ZRIbLnNpdGVzLnYxLkNyZWF0ZVNpdGVSZXF1ZXN0Ghwuc2l0ZXMudjEuQ3JlYXRlU2l0ZVJlc3BvbnNlEkcKClVwZGF0ZVNpdGUSGy5zaXRlcy52MS5VcGRhdGVTaXRlUmVxdWVzdBocLnNpdGVzLnYxLlVwZGF0ZVNpdGVSZXNwb25zZRJHCgpEZWxldGVTaXRlEhsuc2l0ZXMudjEuRGVsZXRlU2l0ZVJlcXVlc3QaHC5zaXRlcy52MS5EZWxldGVTaXRlUmVzcG9uc2USYgoTQXNzaWduRGV2aWNlc1RvU2l0ZRIkLnNpdGVzLnYxLkFzc2lnbkRldmljZXNUb1NpdGVSZXF1ZXN0GiUuc2l0ZXMudjEuQXNzaWduRGV2aWNlc1RvU2l0ZVJlc3BvbnNlEmUKFEFzc2lnbkJ1aWxkaW5nVG9TaXRlEiUuc2l0ZXMudjEuQXNzaWduQnVpbGRpbmdUb1NpdGVSZXF1ZXN0GiYuc2l0ZXMudjEuQXNzaWduQnVpbGRpbmdUb1NpdGVSZXNwb25zZRJNCgxHZXRTaXRlU3RhdHMSHS5zaXRlcy52MS5HZXRTaXRlU3RhdHNSZXF1ZXN0Gh4uc2l0ZXMudjEuR2V0U2l0ZVN0YXRzUmVzcG9uc2VCoAEKDGNvbS5zaXRlcy52MUIKU2l0ZXNQcm90b1ABWkNnaXRodWIuY29tL2Jsb2NrL3Byb3RvLWZsZWV0L3NlcnZlci9nZW5lcmF0ZWQvZ3JwYy9zaXRlcy92MTtzaXRlc3YxogIDU1hYqgIIU2l0ZXMuVjHKAghTaXRlc1xWMeICFFNpdGVzXFYxXEdQQk1ldGFkYXRh6gIJU2l0ZXM6OlYxYgZwcm90bzM", [file_buf_validate_validate, file_google_protobuf_timestamp], ); @@ -414,9 +414,9 @@ export const DeleteSiteResponseSchema: GenMessage = messageDesc(file_sites_v1_sites, 9); /** - * @generated from message sites.v1.ReassignDevicesToSiteRequest + * @generated from message sites.v1.AssignDevicesToSiteRequest */ -export type ReassignDevicesToSiteRequest = Message<"sites.v1.ReassignDevicesToSiteRequest"> & { +export type AssignDevicesToSiteRequest = Message<"sites.v1.AssignDevicesToSiteRequest"> & { /** * Unset = move to the "Unassigned" bucket. When present, must be > 0. * @@ -431,16 +431,16 @@ export type ReassignDevicesToSiteRequest = Message<"sites.v1.ReassignDevicesToSi }; /** - * Describes the message sites.v1.ReassignDevicesToSiteRequest. - * Use `create(ReassignDevicesToSiteRequestSchema)` to create a new message. + * Describes the message sites.v1.AssignDevicesToSiteRequest. + * Use `create(AssignDevicesToSiteRequestSchema)` to create a new message. */ -export const ReassignDevicesToSiteRequestSchema: GenMessage = +export const AssignDevicesToSiteRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 10); /** * PerDeviceConflict carries the per-device error details surfaced - * when ReassignDevicesToSite rejects. The whole batch is rejected; + * when AssignDevicesToSite rejects. The whole batch is rejected; * no rows are mutated. * * @generated from message sites.v1.PerDeviceConflict @@ -474,9 +474,9 @@ export const PerDeviceConflictSchema: GenMessage = messageDesc(file_sites_v1_sites, 11); /** - * @generated from message sites.v1.ReassignDevicesToSiteResponse + * @generated from message sites.v1.AssignDevicesToSiteResponse */ -export type ReassignDevicesToSiteResponse = Message<"sites.v1.ReassignDevicesToSiteResponse"> & { +export type AssignDevicesToSiteResponse = Message<"sites.v1.AssignDevicesToSiteResponse"> & { /** * @generated from field: int64 reassigned_count = 1; */ @@ -491,10 +491,10 @@ export type ReassignDevicesToSiteResponse = Message<"sites.v1.ReassignDevicesToS }; /** - * Describes the message sites.v1.ReassignDevicesToSiteResponse. - * Use `create(ReassignDevicesToSiteResponseSchema)` to create a new message. + * Describes the message sites.v1.AssignDevicesToSiteResponse. + * Use `create(AssignDevicesToSiteResponseSchema)` to create a new message. */ -export const ReassignDevicesToSiteResponseSchema: GenMessage = +export const AssignDevicesToSiteResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 12); @@ -665,7 +665,7 @@ export const GetSiteStatsResponseSchema: GenMessage = /** * PerDeviceConflictReason enumerates the reasons a device can be - * rejected by ReassignDevicesToSite. + * rejected by AssignDevicesToSite. * * @generated from enum sites.v1.PerDeviceConflictReason */ @@ -751,18 +751,18 @@ export const SiteService: GenService<{ output: typeof DeleteSiteResponseSchema; }; /** - * ReassignDevicesToSite is an all-or-nothing bulk update of + * AssignDevicesToSite is an all-or-nothing bulk update of * device.site_id. If any device is in a rack whose site_id differs * from the target, the entire batch rejects with per-device error * details and no row is touched. Omit target_site_id (or leave it * unset) to move devices to the "Unassigned" bucket. * - * @generated from rpc sites.v1.SiteService.ReassignDevicesToSite + * @generated from rpc sites.v1.SiteService.AssignDevicesToSite */ - reassignDevicesToSite: { + assignDevicesToSite: { methodKind: "unary"; - input: typeof ReassignDevicesToSiteRequestSchema; - output: typeof ReassignDevicesToSiteResponseSchema; + input: typeof AssignDevicesToSiteRequestSchema; + output: typeof AssignDevicesToSiteResponseSchema; }; /** * AssignBuildingToSite moves a building to a different site diff --git a/client/src/protoFleet/api/sites.ts b/client/src/protoFleet/api/sites.ts index 4966645f6..ede533036 100644 --- a/client/src/protoFleet/api/sites.ts +++ b/client/src/protoFleet/api/sites.ts @@ -114,7 +114,7 @@ interface DeleteSiteProps { onFinally?: () => void; } -interface ReassignDevicesToSiteProps { +interface AssignDevicesToSiteProps { // Unset routes the devices to the "Unassigned" bucket; the create flow // always supplies a target so this is typically set in practice. targetSiteId?: bigint; @@ -267,10 +267,10 @@ const useSites = () => { [handleAuthErrors], ); - const reassignDevicesToSite = useCallback( - async ({ targetSiteId, deviceIdentifiers, signal, onSuccess, onError, onFinally }: ReassignDevicesToSiteProps) => { + const assignDevicesToSite = useCallback( + async ({ targetSiteId, deviceIdentifiers, signal, onSuccess, onError, onFinally }: AssignDevicesToSiteProps) => { try { - const response = await sitesClient.reassignDevicesToSite( + const response = await sitesClient.assignDevicesToSite( { targetSiteId, deviceIdentifiers, @@ -325,7 +325,7 @@ const useSites = () => { [handleAuthErrors], ); - return { listSites, createSite, updateSite, deleteSite, reassignDevicesToSite, assignBuildingToSite }; + return { listSites, createSite, updateSite, deleteSite, assignDevicesToSite, assignBuildingToSite }; }; export { useSites }; diff --git a/client/src/protoFleet/features/fleetManagement/components/FleetLayout/FleetLayout.test.tsx b/client/src/protoFleet/features/fleetManagement/components/FleetLayout/FleetLayout.test.tsx index 85f0f45e4..37d045553 100644 --- a/client/src/protoFleet/features/fleetManagement/components/FleetLayout/FleetLayout.test.tsx +++ b/client/src/protoFleet/features/fleetManagement/components/FleetLayout/FleetLayout.test.tsx @@ -30,7 +30,7 @@ vi.mock("@/protoFleet/api/sites", async (importOriginal) => { createSite: vi.fn(), updateSite: vi.fn(), deleteSite: vi.fn(), - reassignDevicesToSite: vi.fn(), + assignDevicesToSite: vi.fn(), }), }; }); diff --git a/client/src/protoFleet/features/sites/hooks/useSiteModals.test.ts b/client/src/protoFleet/features/sites/hooks/useSiteModals.test.ts index 3b13e2fed..b14bcd25d 100644 --- a/client/src/protoFleet/features/sites/hooks/useSiteModals.test.ts +++ b/client/src/protoFleet/features/sites/hooks/useSiteModals.test.ts @@ -23,7 +23,7 @@ vi.mock("@/protoFleet/api/clients", () => ({ createSite: vi.fn(), updateSite: vi.fn(), deleteSite: vi.fn(), - reassignDevicesToSite: vi.fn(), + assignDevicesToSite: vi.fn(), }, })); diff --git a/client/src/protoFleet/features/sites/hooks/useSiteModals.ts b/client/src/protoFleet/features/sites/hooks/useSiteModals.ts index 307d3ed8d..842ca93ac 100644 --- a/client/src/protoFleet/features/sites/hooks/useSiteModals.ts +++ b/client/src/protoFleet/features/sites/hooks/useSiteModals.ts @@ -252,7 +252,7 @@ const useSiteModals = ({ refetchSites }: UseSiteModalsOptions): SiteModalsApi => }, }); // TODO(phase-1b #199): once the miner picker ships, follow this - // CreateSite call with `reassignDevicesToSite({ targetSiteId: site.id, + // CreateSite call with `assignDevicesToSite({ targetSiteId: site.id, // deviceIdentifiers: pendingDeviceIds })` inside the onSuccess branch // (and gate setSaving(false) on the inner onFinally). Partial-failure // toast wording is already drafted in PR #292 review. diff --git a/proto/sites/v1/sites.proto b/proto/sites/v1/sites.proto index 3e803a5e3..1f23c8887 100644 --- a/proto/sites/v1/sites.proto +++ b/proto/sites/v1/sites.proto @@ -30,13 +30,13 @@ service SiteService { // result in a toast/activity row. rpc DeleteSite(DeleteSiteRequest) returns (DeleteSiteResponse); - // ReassignDevicesToSite is an all-or-nothing bulk update of + // AssignDevicesToSite is an all-or-nothing bulk update of // device.site_id. If any device is in a rack whose site_id differs // from the target, the entire batch rejects with per-device error // details and no row is touched. Omit target_site_id (or leave it // unset) to move devices to the "Unassigned" bucket. - rpc ReassignDevicesToSite(ReassignDevicesToSiteRequest) - returns (ReassignDevicesToSiteResponse); + rpc AssignDevicesToSite(AssignDevicesToSiteRequest) + returns (AssignDevicesToSiteResponse); // AssignBuildingToSite moves a building to a different site // (or to "Unassigned" when target_site_id is unset). The same @@ -181,9 +181,9 @@ message DeleteSiteResponse { int64 unassigned_rack_count = 3; } -// ReassignDevicesToSite +// AssignDevicesToSite -message ReassignDevicesToSiteRequest { +message AssignDevicesToSiteRequest { // Unset = move to the "Unassigned" bucket. When present, must be > 0. optional int64 target_site_id = 1 [(buf.validate.field).int64.gt = 0]; repeated string device_identifiers = 2 [(buf.validate.field).repeated = { @@ -194,7 +194,7 @@ message ReassignDevicesToSiteRequest { } // PerDeviceConflictReason enumerates the reasons a device can be -// rejected by ReassignDevicesToSite. +// rejected by AssignDevicesToSite. enum PerDeviceConflictReason { PER_DEVICE_CONFLICT_REASON_UNSPECIFIED = 0; PER_DEVICE_CONFLICT_REASON_DEVICE_NOT_FOUND = 1; @@ -202,7 +202,7 @@ enum PerDeviceConflictReason { } // PerDeviceConflict carries the per-device error details surfaced -// when ReassignDevicesToSite rejects. The whole batch is rejected; +// when AssignDevicesToSite rejects. The whole batch is rejected; // no rows are mutated. message PerDeviceConflict { string device_identifier = 1; @@ -212,7 +212,7 @@ message PerDeviceConflict { int64 conflicting_site_id = 3; } -message ReassignDevicesToSiteResponse { +message AssignDevicesToSiteResponse { int64 reassigned_count = 1; // Populated only on rejection. Empty on success. repeated PerDeviceConflict conflicts = 2; diff --git a/server/generated/grpc/sites/v1/sites.pb.go b/server/generated/grpc/sites/v1/sites.pb.go index cd630b1df..60b36bcf3 100644 --- a/server/generated/grpc/sites/v1/sites.pb.go +++ b/server/generated/grpc/sites/v1/sites.pb.go @@ -25,7 +25,7 @@ const ( ) // PerDeviceConflictReason enumerates the reasons a device can be -// rejected by ReassignDevicesToSite. +// rejected by AssignDevicesToSite. type PerDeviceConflictReason int32 const ( @@ -840,7 +840,7 @@ func (x *DeleteSiteResponse) GetUnassignedRackCount() int64 { return 0 } -type ReassignDevicesToSiteRequest struct { +type AssignDevicesToSiteRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Unset = move to the "Unassigned" bucket. When present, must be > 0. TargetSiteId *int64 `protobuf:"varint,1,opt,name=target_site_id,json=targetSiteId,proto3,oneof" json:"target_site_id,omitempty"` @@ -849,20 +849,20 @@ type ReassignDevicesToSiteRequest struct { sizeCache protoimpl.SizeCache } -func (x *ReassignDevicesToSiteRequest) Reset() { - *x = ReassignDevicesToSiteRequest{} +func (x *AssignDevicesToSiteRequest) Reset() { + *x = AssignDevicesToSiteRequest{} mi := &file_sites_v1_sites_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ReassignDevicesToSiteRequest) String() string { +func (x *AssignDevicesToSiteRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ReassignDevicesToSiteRequest) ProtoMessage() {} +func (*AssignDevicesToSiteRequest) ProtoMessage() {} -func (x *ReassignDevicesToSiteRequest) ProtoReflect() protoreflect.Message { +func (x *AssignDevicesToSiteRequest) ProtoReflect() protoreflect.Message { mi := &file_sites_v1_sites_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -874,19 +874,19 @@ func (x *ReassignDevicesToSiteRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ReassignDevicesToSiteRequest.ProtoReflect.Descriptor instead. -func (*ReassignDevicesToSiteRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use AssignDevicesToSiteRequest.ProtoReflect.Descriptor instead. +func (*AssignDevicesToSiteRequest) Descriptor() ([]byte, []int) { return file_sites_v1_sites_proto_rawDescGZIP(), []int{10} } -func (x *ReassignDevicesToSiteRequest) GetTargetSiteId() int64 { +func (x *AssignDevicesToSiteRequest) GetTargetSiteId() int64 { if x != nil && x.TargetSiteId != nil { return *x.TargetSiteId } return 0 } -func (x *ReassignDevicesToSiteRequest) GetDeviceIdentifiers() []string { +func (x *AssignDevicesToSiteRequest) GetDeviceIdentifiers() []string { if x != nil { return x.DeviceIdentifiers } @@ -894,7 +894,7 @@ func (x *ReassignDevicesToSiteRequest) GetDeviceIdentifiers() []string { } // PerDeviceConflict carries the per-device error details surfaced -// when ReassignDevicesToSite rejects. The whole batch is rejected; +// when AssignDevicesToSite rejects. The whole batch is rejected; // no rows are mutated. type PerDeviceConflict struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -958,7 +958,7 @@ func (x *PerDeviceConflict) GetConflictingSiteId() int64 { return 0 } -type ReassignDevicesToSiteResponse struct { +type AssignDevicesToSiteResponse struct { state protoimpl.MessageState `protogen:"open.v1"` ReassignedCount int64 `protobuf:"varint,1,opt,name=reassigned_count,json=reassignedCount,proto3" json:"reassigned_count,omitempty"` // Populated only on rejection. Empty on success. @@ -967,20 +967,20 @@ type ReassignDevicesToSiteResponse struct { sizeCache protoimpl.SizeCache } -func (x *ReassignDevicesToSiteResponse) Reset() { - *x = ReassignDevicesToSiteResponse{} +func (x *AssignDevicesToSiteResponse) Reset() { + *x = AssignDevicesToSiteResponse{} mi := &file_sites_v1_sites_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ReassignDevicesToSiteResponse) String() string { +func (x *AssignDevicesToSiteResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ReassignDevicesToSiteResponse) ProtoMessage() {} +func (*AssignDevicesToSiteResponse) ProtoMessage() {} -func (x *ReassignDevicesToSiteResponse) ProtoReflect() protoreflect.Message { +func (x *AssignDevicesToSiteResponse) ProtoReflect() protoreflect.Message { mi := &file_sites_v1_sites_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -992,19 +992,19 @@ func (x *ReassignDevicesToSiteResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ReassignDevicesToSiteResponse.ProtoReflect.Descriptor instead. -func (*ReassignDevicesToSiteResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use AssignDevicesToSiteResponse.ProtoReflect.Descriptor instead. +func (*AssignDevicesToSiteResponse) Descriptor() ([]byte, []int) { return file_sites_v1_sites_proto_rawDescGZIP(), []int{12} } -func (x *ReassignDevicesToSiteResponse) GetReassignedCount() int64 { +func (x *AssignDevicesToSiteResponse) GetReassignedCount() int64 { if x != nil { return x.ReassignedCount } return 0 } -func (x *ReassignDevicesToSiteResponse) GetConflicts() []*PerDeviceConflict { +func (x *AssignDevicesToSiteResponse) GetConflicts() []*PerDeviceConflict { if x != nil { return x.Conflicts } @@ -1466,156 +1466,155 @@ var file_sites_v1_sites_proto_rawDesc = string([]byte{ 0x75, 0x6e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x75, 0x6e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x22, 0xaa, 0x01, 0x0a, 0x1c, 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, - 0x20, 0x00, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, - 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x43, 0x0a, 0x12, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x09, 0x42, 0x14, 0xba, 0x48, 0x11, 0x92, 0x01, 0x0e, 0x08, 0x01, 0x10, 0x90, 0x4e, 0x22, 0x07, - 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x02, 0x52, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x22, 0xab, 0x01, - 0x0a, 0x11, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, - 0x69, 0x63, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x12, 0x39, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x21, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x63, - 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, - 0x63, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x22, 0x85, 0x01, 0x0a, 0x1d, - 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, - 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, - 0x10, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, - 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x66, - 0x6c, 0x69, 0x63, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x69, - 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, - 0x63, 0x74, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x1b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, - 0x00, 0x52, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x32, 0x0a, - 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, - 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x88, 0x01, - 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, - 0x65, 0x5f, 0x69, 0x64, 0x22, 0x8a, 0x01, 0x0a, 0x1c, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, - 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, - 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, - 0x52, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x61, - 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, 0x61, 0x73, - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x22, 0x37, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, - 0x20, 0x00, 0x52, 0x06, 0x73, 0x69, 0x74, 0x65, 0x49, 0x64, 0x22, 0xe4, 0x04, 0x0a, 0x14, 0x47, - 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x69, 0x74, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, - 0x65, 0x5f, 0x74, 0x68, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, 0x2c, 0x0a, - 0x12, 0x61, 0x76, 0x67, 0x5f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, - 0x6a, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, 0x45, 0x66, - 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4b, - 0x77, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, - 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, - 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, - 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, - 0x0a, 0x0e, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, - 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x3c, 0x0a, 0x1a, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x18, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, - 0x15, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, - 0x77, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x2a, 0xb3, 0x01, 0x0a, 0x17, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, - 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, - 0x26, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, - 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, - 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x45, 0x52, - 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, - 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4e, - 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x01, 0x12, 0x3b, 0x0a, 0x37, 0x50, 0x45, - 0x52, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, - 0x54, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, - 0x49, 0x4e, 0x5f, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x41, 0x54, 0x5f, 0x4f, 0x54, 0x48, 0x45, 0x52, - 0x5f, 0x53, 0x49, 0x54, 0x45, 0x10, 0x02, 0x32, 0xce, 0x04, 0x0a, 0x0b, 0x53, 0x69, 0x74, 0x65, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x69, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, - 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, - 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x47, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, - 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, - 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x15, 0x52, 0x65, 0x61, 0x73, + 0x22, 0xa8, 0x01, 0x0a, 0x1a, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, + 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, + 0x88, 0x01, 0x01, 0x12, 0x43, 0x0a, 0x12, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, + 0x14, 0xba, 0x48, 0x11, 0x92, 0x01, 0x0e, 0x08, 0x01, 0x10, 0x90, 0x4e, 0x22, 0x07, 0x72, 0x05, + 0x10, 0x01, 0x18, 0x80, 0x02, 0x52, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x22, 0xab, 0x01, 0x0a, 0x11, + 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, + 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x39, + 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, + 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x6f, 0x6e, + 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, + 0x69, 0x6e, 0x67, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x22, 0x83, 0x01, 0x0a, 0x1b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, - 0x65, 0x12, 0x26, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x61, - 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x73, 0x69, 0x74, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x65, 0x0a, 0x14, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x25, 0x2e, 0x73, 0x69, 0x74, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x26, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, - 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0c, 0x47, 0x65, 0x74, - 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x74, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xa0, 0x01, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, - 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x53, 0x69, 0x74, 0x65, 0x73, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, - 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x69, 0x74, 0x65, - 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x73, 0x69, 0x74, 0x65, 0x73, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x53, - 0x58, 0x58, 0xaa, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, - 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x14, 0x53, 0x69, 0x74, 0x65, 0x73, - 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, - 0x02, 0x09, 0x53, 0x69, 0x74, 0x65, 0x73, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x61, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, + 0x6c, 0x69, 0x63, 0x74, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x22, + 0x8e, 0x01, 0x0a, 0x1b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, + 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x28, 0x0a, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0a, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, + 0x0f, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, + 0x22, 0x8a, 0x01, 0x0a, 0x1c, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, + 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x13, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x61, 0x63, 0x6b, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x37, 0x0a, + 0x13, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x06, + 0x73, 0x69, 0x74, 0x65, 0x49, 0x64, 0x22, 0xe4, 0x04, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x69, + 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x73, 0x69, 0x74, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x75, 0x69, 0x6c, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, + 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x61, + 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x67, + 0x5f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6a, 0x74, 0x68, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, 0x45, 0x66, 0x66, 0x69, 0x63, 0x69, + 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x23, 0x0a, + 0x0d, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, + 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, + 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x65, + 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x18, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6f, 0x77, + 0x65, 0x72, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0xb3, 0x01, + 0x0a, 0x17, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, + 0x69, 0x63, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x26, 0x50, 0x45, 0x52, + 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, + 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, + 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, + 0x53, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, + 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x01, 0x12, 0x3b, 0x0a, 0x37, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, + 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, + 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x52, + 0x41, 0x43, 0x4b, 0x5f, 0x41, 0x54, 0x5f, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x5f, 0x53, 0x49, 0x54, + 0x45, 0x10, 0x02, 0x32, 0xc8, 0x04, 0x0a, 0x0b, 0x53, 0x69, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, + 0x12, 0x1a, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x73, + 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, + 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, + 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, + 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x73, 0x69, + 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x25, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, + 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x14, 0x41, 0x73, 0x73, 0x69, + 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, + 0x12, 0x25, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, + 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, + 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x4d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, + 0x1d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, + 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xa0, + 0x01, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x42, + 0x0a, 0x53, 0x69, 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x43, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, + 0x63, 0x2f, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x73, 0x69, 0x74, 0x65, 0x73, + 0x76, 0x31, 0xa2, 0x02, 0x03, 0x53, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, + 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, + 0x14, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x53, 0x69, 0x74, 0x65, 0x73, 0x3a, 0x3a, 0x56, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -1633,25 +1632,25 @@ func file_sites_v1_sites_proto_rawDescGZIP() []byte { var file_sites_v1_sites_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_sites_v1_sites_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_sites_v1_sites_proto_goTypes = []any{ - (PerDeviceConflictReason)(0), // 0: sites.v1.PerDeviceConflictReason - (*Site)(nil), // 1: sites.v1.Site - (*SiteWithCounts)(nil), // 2: sites.v1.SiteWithCounts - (*ListSitesRequest)(nil), // 3: sites.v1.ListSitesRequest - (*ListSitesResponse)(nil), // 4: sites.v1.ListSitesResponse - (*CreateSiteRequest)(nil), // 5: sites.v1.CreateSiteRequest - (*CreateSiteResponse)(nil), // 6: sites.v1.CreateSiteResponse - (*UpdateSiteRequest)(nil), // 7: sites.v1.UpdateSiteRequest - (*UpdateSiteResponse)(nil), // 8: sites.v1.UpdateSiteResponse - (*DeleteSiteRequest)(nil), // 9: sites.v1.DeleteSiteRequest - (*DeleteSiteResponse)(nil), // 10: sites.v1.DeleteSiteResponse - (*ReassignDevicesToSiteRequest)(nil), // 11: sites.v1.ReassignDevicesToSiteRequest - (*PerDeviceConflict)(nil), // 12: sites.v1.PerDeviceConflict - (*ReassignDevicesToSiteResponse)(nil), // 13: sites.v1.ReassignDevicesToSiteResponse - (*AssignBuildingToSiteRequest)(nil), // 14: sites.v1.AssignBuildingToSiteRequest - (*AssignBuildingToSiteResponse)(nil), // 15: sites.v1.AssignBuildingToSiteResponse - (*GetSiteStatsRequest)(nil), // 16: sites.v1.GetSiteStatsRequest - (*GetSiteStatsResponse)(nil), // 17: sites.v1.GetSiteStatsResponse - (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp + (PerDeviceConflictReason)(0), // 0: sites.v1.PerDeviceConflictReason + (*Site)(nil), // 1: sites.v1.Site + (*SiteWithCounts)(nil), // 2: sites.v1.SiteWithCounts + (*ListSitesRequest)(nil), // 3: sites.v1.ListSitesRequest + (*ListSitesResponse)(nil), // 4: sites.v1.ListSitesResponse + (*CreateSiteRequest)(nil), // 5: sites.v1.CreateSiteRequest + (*CreateSiteResponse)(nil), // 6: sites.v1.CreateSiteResponse + (*UpdateSiteRequest)(nil), // 7: sites.v1.UpdateSiteRequest + (*UpdateSiteResponse)(nil), // 8: sites.v1.UpdateSiteResponse + (*DeleteSiteRequest)(nil), // 9: sites.v1.DeleteSiteRequest + (*DeleteSiteResponse)(nil), // 10: sites.v1.DeleteSiteResponse + (*AssignDevicesToSiteRequest)(nil), // 11: sites.v1.AssignDevicesToSiteRequest + (*PerDeviceConflict)(nil), // 12: sites.v1.PerDeviceConflict + (*AssignDevicesToSiteResponse)(nil), // 13: sites.v1.AssignDevicesToSiteResponse + (*AssignBuildingToSiteRequest)(nil), // 14: sites.v1.AssignBuildingToSiteRequest + (*AssignBuildingToSiteResponse)(nil), // 15: sites.v1.AssignBuildingToSiteResponse + (*GetSiteStatsRequest)(nil), // 16: sites.v1.GetSiteStatsRequest + (*GetSiteStatsResponse)(nil), // 17: sites.v1.GetSiteStatsResponse + (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp } var file_sites_v1_sites_proto_depIdxs = []int32{ 18, // 0: sites.v1.Site.created_at:type_name -> google.protobuf.Timestamp @@ -1661,19 +1660,19 @@ var file_sites_v1_sites_proto_depIdxs = []int32{ 1, // 4: sites.v1.CreateSiteResponse.site:type_name -> sites.v1.Site 1, // 5: sites.v1.UpdateSiteResponse.site:type_name -> sites.v1.Site 0, // 6: sites.v1.PerDeviceConflict.reason:type_name -> sites.v1.PerDeviceConflictReason - 12, // 7: sites.v1.ReassignDevicesToSiteResponse.conflicts:type_name -> sites.v1.PerDeviceConflict + 12, // 7: sites.v1.AssignDevicesToSiteResponse.conflicts:type_name -> sites.v1.PerDeviceConflict 3, // 8: sites.v1.SiteService.ListSites:input_type -> sites.v1.ListSitesRequest 5, // 9: sites.v1.SiteService.CreateSite:input_type -> sites.v1.CreateSiteRequest 7, // 10: sites.v1.SiteService.UpdateSite:input_type -> sites.v1.UpdateSiteRequest 9, // 11: sites.v1.SiteService.DeleteSite:input_type -> sites.v1.DeleteSiteRequest - 11, // 12: sites.v1.SiteService.ReassignDevicesToSite:input_type -> sites.v1.ReassignDevicesToSiteRequest + 11, // 12: sites.v1.SiteService.AssignDevicesToSite:input_type -> sites.v1.AssignDevicesToSiteRequest 14, // 13: sites.v1.SiteService.AssignBuildingToSite:input_type -> sites.v1.AssignBuildingToSiteRequest 16, // 14: sites.v1.SiteService.GetSiteStats:input_type -> sites.v1.GetSiteStatsRequest 4, // 15: sites.v1.SiteService.ListSites:output_type -> sites.v1.ListSitesResponse 6, // 16: sites.v1.SiteService.CreateSite:output_type -> sites.v1.CreateSiteResponse 8, // 17: sites.v1.SiteService.UpdateSite:output_type -> sites.v1.UpdateSiteResponse 10, // 18: sites.v1.SiteService.DeleteSite:output_type -> sites.v1.DeleteSiteResponse - 13, // 19: sites.v1.SiteService.ReassignDevicesToSite:output_type -> sites.v1.ReassignDevicesToSiteResponse + 13, // 19: sites.v1.SiteService.AssignDevicesToSite:output_type -> sites.v1.AssignDevicesToSiteResponse 15, // 20: sites.v1.SiteService.AssignBuildingToSite:output_type -> sites.v1.AssignBuildingToSiteResponse 17, // 21: sites.v1.SiteService.GetSiteStats:output_type -> sites.v1.GetSiteStatsResponse 15, // [15:22] is the sub-list for method output_type diff --git a/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go b/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go index 223d0db21..9c033eb4f 100644 --- a/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go +++ b/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go @@ -42,9 +42,9 @@ const ( SiteServiceUpdateSiteProcedure = "/sites.v1.SiteService/UpdateSite" // SiteServiceDeleteSiteProcedure is the fully-qualified name of the SiteService's DeleteSite RPC. SiteServiceDeleteSiteProcedure = "/sites.v1.SiteService/DeleteSite" - // SiteServiceReassignDevicesToSiteProcedure is the fully-qualified name of the SiteService's - // ReassignDevicesToSite RPC. - SiteServiceReassignDevicesToSiteProcedure = "/sites.v1.SiteService/ReassignDevicesToSite" + // SiteServiceAssignDevicesToSiteProcedure is the fully-qualified name of the SiteService's + // AssignDevicesToSite RPC. + SiteServiceAssignDevicesToSiteProcedure = "/sites.v1.SiteService/AssignDevicesToSite" // SiteServiceAssignBuildingToSiteProcedure is the fully-qualified name of the SiteService's // AssignBuildingToSite RPC. SiteServiceAssignBuildingToSiteProcedure = "/sites.v1.SiteService/AssignBuildingToSite" @@ -72,12 +72,12 @@ type SiteServiceClient interface { // Cascade impact counts are returned so the UI can surface the // result in a toast/activity row. DeleteSite(context.Context, *connect.Request[v1.DeleteSiteRequest]) (*connect.Response[v1.DeleteSiteResponse], error) - // ReassignDevicesToSite is an all-or-nothing bulk update of + // AssignDevicesToSite is an all-or-nothing bulk update of // device.site_id. If any device is in a rack whose site_id differs // from the target, the entire batch rejects with per-device error // details and no row is touched. Omit target_site_id (or leave it // unset) to move devices to the "Unassigned" bucket. - ReassignDevicesToSite(context.Context, *connect.Request[v1.ReassignDevicesToSiteRequest]) (*connect.Response[v1.ReassignDevicesToSiteResponse], error) + AssignDevicesToSite(context.Context, *connect.Request[v1.AssignDevicesToSiteRequest]) (*connect.Response[v1.AssignDevicesToSiteResponse], error) // AssignBuildingToSite moves a building to a different site // (or to "Unassigned" when target_site_id is unset). The same // transaction cascades site_id down to the building's racks and @@ -120,9 +120,9 @@ func NewSiteServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. baseURL+SiteServiceDeleteSiteProcedure, opts..., ), - reassignDevicesToSite: connect.NewClient[v1.ReassignDevicesToSiteRequest, v1.ReassignDevicesToSiteResponse]( + assignDevicesToSite: connect.NewClient[v1.AssignDevicesToSiteRequest, v1.AssignDevicesToSiteResponse]( httpClient, - baseURL+SiteServiceReassignDevicesToSiteProcedure, + baseURL+SiteServiceAssignDevicesToSiteProcedure, opts..., ), assignBuildingToSite: connect.NewClient[v1.AssignBuildingToSiteRequest, v1.AssignBuildingToSiteResponse]( @@ -140,13 +140,13 @@ func NewSiteServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. // siteServiceClient implements SiteServiceClient. type siteServiceClient struct { - listSites *connect.Client[v1.ListSitesRequest, v1.ListSitesResponse] - createSite *connect.Client[v1.CreateSiteRequest, v1.CreateSiteResponse] - updateSite *connect.Client[v1.UpdateSiteRequest, v1.UpdateSiteResponse] - deleteSite *connect.Client[v1.DeleteSiteRequest, v1.DeleteSiteResponse] - reassignDevicesToSite *connect.Client[v1.ReassignDevicesToSiteRequest, v1.ReassignDevicesToSiteResponse] - assignBuildingToSite *connect.Client[v1.AssignBuildingToSiteRequest, v1.AssignBuildingToSiteResponse] - getSiteStats *connect.Client[v1.GetSiteStatsRequest, v1.GetSiteStatsResponse] + listSites *connect.Client[v1.ListSitesRequest, v1.ListSitesResponse] + createSite *connect.Client[v1.CreateSiteRequest, v1.CreateSiteResponse] + updateSite *connect.Client[v1.UpdateSiteRequest, v1.UpdateSiteResponse] + deleteSite *connect.Client[v1.DeleteSiteRequest, v1.DeleteSiteResponse] + assignDevicesToSite *connect.Client[v1.AssignDevicesToSiteRequest, v1.AssignDevicesToSiteResponse] + assignBuildingToSite *connect.Client[v1.AssignBuildingToSiteRequest, v1.AssignBuildingToSiteResponse] + getSiteStats *connect.Client[v1.GetSiteStatsRequest, v1.GetSiteStatsResponse] } // ListSites calls sites.v1.SiteService.ListSites. @@ -169,9 +169,9 @@ func (c *siteServiceClient) DeleteSite(ctx context.Context, req *connect.Request return c.deleteSite.CallUnary(ctx, req) } -// ReassignDevicesToSite calls sites.v1.SiteService.ReassignDevicesToSite. -func (c *siteServiceClient) ReassignDevicesToSite(ctx context.Context, req *connect.Request[v1.ReassignDevicesToSiteRequest]) (*connect.Response[v1.ReassignDevicesToSiteResponse], error) { - return c.reassignDevicesToSite.CallUnary(ctx, req) +// AssignDevicesToSite calls sites.v1.SiteService.AssignDevicesToSite. +func (c *siteServiceClient) AssignDevicesToSite(ctx context.Context, req *connect.Request[v1.AssignDevicesToSiteRequest]) (*connect.Response[v1.AssignDevicesToSiteResponse], error) { + return c.assignDevicesToSite.CallUnary(ctx, req) } // AssignBuildingToSite calls sites.v1.SiteService.AssignBuildingToSite. @@ -203,12 +203,12 @@ type SiteServiceHandler interface { // Cascade impact counts are returned so the UI can surface the // result in a toast/activity row. DeleteSite(context.Context, *connect.Request[v1.DeleteSiteRequest]) (*connect.Response[v1.DeleteSiteResponse], error) - // ReassignDevicesToSite is an all-or-nothing bulk update of + // AssignDevicesToSite is an all-or-nothing bulk update of // device.site_id. If any device is in a rack whose site_id differs // from the target, the entire batch rejects with per-device error // details and no row is touched. Omit target_site_id (or leave it // unset) to move devices to the "Unassigned" bucket. - ReassignDevicesToSite(context.Context, *connect.Request[v1.ReassignDevicesToSiteRequest]) (*connect.Response[v1.ReassignDevicesToSiteResponse], error) + AssignDevicesToSite(context.Context, *connect.Request[v1.AssignDevicesToSiteRequest]) (*connect.Response[v1.AssignDevicesToSiteResponse], error) // AssignBuildingToSite moves a building to a different site // (or to "Unassigned" when target_site_id is unset). The same // transaction cascades site_id down to the building's racks and @@ -247,9 +247,9 @@ func NewSiteServiceHandler(svc SiteServiceHandler, opts ...connect.HandlerOption svc.DeleteSite, opts..., ) - siteServiceReassignDevicesToSiteHandler := connect.NewUnaryHandler( - SiteServiceReassignDevicesToSiteProcedure, - svc.ReassignDevicesToSite, + siteServiceAssignDevicesToSiteHandler := connect.NewUnaryHandler( + SiteServiceAssignDevicesToSiteProcedure, + svc.AssignDevicesToSite, opts..., ) siteServiceAssignBuildingToSiteHandler := connect.NewUnaryHandler( @@ -272,8 +272,8 @@ func NewSiteServiceHandler(svc SiteServiceHandler, opts ...connect.HandlerOption siteServiceUpdateSiteHandler.ServeHTTP(w, r) case SiteServiceDeleteSiteProcedure: siteServiceDeleteSiteHandler.ServeHTTP(w, r) - case SiteServiceReassignDevicesToSiteProcedure: - siteServiceReassignDevicesToSiteHandler.ServeHTTP(w, r) + case SiteServiceAssignDevicesToSiteProcedure: + siteServiceAssignDevicesToSiteHandler.ServeHTTP(w, r) case SiteServiceAssignBuildingToSiteProcedure: siteServiceAssignBuildingToSiteHandler.ServeHTTP(w, r) case SiteServiceGetSiteStatsProcedure: @@ -303,8 +303,8 @@ func (UnimplementedSiteServiceHandler) DeleteSite(context.Context, *connect.Requ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sites.v1.SiteService.DeleteSite is not implemented")) } -func (UnimplementedSiteServiceHandler) ReassignDevicesToSite(context.Context, *connect.Request[v1.ReassignDevicesToSiteRequest]) (*connect.Response[v1.ReassignDevicesToSiteResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sites.v1.SiteService.ReassignDevicesToSite is not implemented")) +func (UnimplementedSiteServiceHandler) AssignDevicesToSite(context.Context, *connect.Request[v1.AssignDevicesToSiteRequest]) (*connect.Response[v1.AssignDevicesToSiteResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sites.v1.SiteService.AssignDevicesToSite is not implemented")) } func (UnimplementedSiteServiceHandler) AssignBuildingToSite(context.Context, *connect.Request[v1.AssignBuildingToSiteRequest]) (*connect.Response[v1.AssignBuildingToSiteResponse], error) { diff --git a/server/generated/sqlc/site.sql.go b/server/generated/sqlc/site.sql.go index e2bf52d21..73920b5b7 100644 --- a/server/generated/sqlc/site.sql.go +++ b/server/generated/sqlc/site.sql.go @@ -13,6 +13,33 @@ import ( "github.com/lib/pq" ) +const assignDevicesToSite = `-- name: AssignDevicesToSite :execrows +UPDATE device +SET site_id = $1, + updated_at = CURRENT_TIMESTAMP +WHERE org_id = $2 + AND device_identifier = ANY($3::text[]) + AND deleted_at IS NULL +` + +type AssignDevicesToSiteParams struct { + TargetSiteID sql.NullInt64 + OrgID int64 + DeviceIdentifiers []string +} + +// Bulk update of device.site_id for the given identifiers within the +// org. Caller is expected to have already validated that no device is +// in a rack at a different site (see FindDeviceSiteConflicts). +// target_site_id NULL = move to Unassigned. +func (q *Queries) AssignDevicesToSite(ctx context.Context, arg AssignDevicesToSiteParams) (int64, error) { + result, err := q.exec(ctx, q.assignDevicesToSiteStmt, assignDevicesToSite, arg.TargetSiteID, arg.OrgID, pq.Array(arg.DeviceIdentifiers)) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + const createSite = `-- name: CreateSite :one INSERT INTO site ( org_id, @@ -226,7 +253,7 @@ type ListExistingDeviceIdentifiersParams struct { // Filters the requested identifier list down to those that actually // exist as live devices in the org. Used to surface "device_not_found" -// conflicts in ReassignDevicesToSite without an N+1 lookup. +// conflicts in AssignDevicesToSite without an N+1 lookup. func (q *Queries) ListExistingDeviceIdentifiers(ctx context.Context, arg ListExistingDeviceIdentifiersParams) ([]string, error) { rows, err := q.query(ctx, q.listExistingDeviceIdentifiersStmt, listExistingDeviceIdentifiers, arg.OrgID, pq.Array(arg.DeviceIdentifiers)) if err != nil { @@ -525,33 +552,6 @@ func (q *Queries) LockSiteForWrite(ctx context.Context, arg LockSiteForWritePara return id, err } -const reassignDevicesToSite = `-- name: ReassignDevicesToSite :execrows -UPDATE device -SET site_id = $1, - updated_at = CURRENT_TIMESTAMP -WHERE org_id = $2 - AND device_identifier = ANY($3::text[]) - AND deleted_at IS NULL -` - -type ReassignDevicesToSiteParams struct { - TargetSiteID sql.NullInt64 - OrgID int64 - DeviceIdentifiers []string -} - -// Bulk update of device.site_id for the given identifiers within the -// org. Caller is expected to have already validated that no device is -// in a rack at a different site (see FindDeviceSiteConflicts). -// target_site_id NULL = move to Unassigned. -func (q *Queries) ReassignDevicesToSite(ctx context.Context, arg ReassignDevicesToSiteParams) (int64, error) { - result, err := q.exec(ctx, q.reassignDevicesToSiteStmt, reassignDevicesToSite, arg.TargetSiteID, arg.OrgID, pq.Array(arg.DeviceIdentifiers)) - if err != nil { - return 0, err - } - return result.RowsAffected() -} - const reassignDevicesUnderBuilding = `-- name: ReassignDevicesUnderBuilding :execrows UPDATE device d SET site_id = $1, diff --git a/server/internal/domain/fleetmanagement/collection_test.go b/server/internal/domain/fleetmanagement/collection_test.go index 4ccfdc0e0..921d214fc 100644 --- a/server/internal/domain/fleetmanagement/collection_test.go +++ b/server/internal/domain/fleetmanagement/collection_test.go @@ -254,7 +254,7 @@ func TestService_ListMinerStateSnapshots_ShouldPopulateSiteIDAndLabel(t *testing require.NoError(t, err) // Reassign only device 0 to the site; device 1 stays unassigned. - _, err = siteStore.ReassignDevicesToSite(t.Context(), orgID, &site.ID, deviceIDs[:1]) + _, err = siteStore.AssignDevicesToSite(t.Context(), orgID, &site.ID, deviceIDs[:1]) require.NoError(t, err) ctx := testutil.MockAuthContextForTesting(t.Context(), testUser.DatabaseID, orgID) diff --git a/server/internal/domain/sites/models/models.go b/server/internal/domain/sites/models/models.go index 5fa0ce142..8863a8144 100644 --- a/server/internal/domain/sites/models/models.go +++ b/server/internal/domain/sites/models/models.go @@ -95,9 +95,9 @@ type PerDeviceConflict struct { ConflictingSiteID int64 } -// ReassignDevicesToSiteParams is the input shape for the bulk reassign +// AssignDevicesToSiteParams is the input shape for the bulk assign // flow. TargetSiteID == nil means "Unassigned". -type ReassignDevicesToSiteParams struct { +type AssignDevicesToSiteParams struct { OrgID int64 TargetSiteID *int64 DeviceIdentifiers []string diff --git a/server/internal/domain/sites/service.go b/server/internal/domain/sites/service.go index 7bcf46151..3e92e6b30 100644 --- a/server/internal/domain/sites/service.go +++ b/server/internal/domain/sites/service.go @@ -273,13 +273,13 @@ func (s *Service) DeleteSite(ctx context.Context, orgID, id int64) (*models.Dele return &out, nil } -// ReassignDevicesToSite enforces the cross-collection invariant and, +// AssignDevicesToSite enforces the cross-collection invariant and, // on success, bulk-updates device.site_id for every identifier in one // transaction. Per the plan, the entire batch rejects if *any* device // fails the check; no partial writes. The conflict check and the // UPDATE run inside the same row-locked transaction so a concurrent -// reassign can't slip between them. -func (s *Service) ReassignDevicesToSite(ctx context.Context, params models.ReassignDevicesToSiteParams) (int64, []models.PerDeviceConflict, error) { +// assign can't slip between them. +func (s *Service) AssignDevicesToSite(ctx context.Context, params models.AssignDevicesToSiteParams) (int64, []models.PerDeviceConflict, error) { identifiers := dedupeStrings(params.DeviceIdentifiers) if len(identifiers) == 0 { return 0, nil, fleeterror.NewInvalidArgumentError("device_identifiers must not be empty") @@ -318,7 +318,7 @@ func (s *Service) ReassignDevicesToSite(ctx context.Context, params models.Reass txConflicts = conflicts return nil } - n, txErr := s.store.ReassignDevicesToSite(txCtx, params.OrgID, targetSiteID, identifiers) + n, txErr := s.store.AssignDevicesToSite(txCtx, params.OrgID, targetSiteID, identifiers) if txErr != nil { return txErr } diff --git a/server/internal/domain/sites/service_test.go b/server/internal/domain/sites/service_test.go index d963da205..7cfd9e3b8 100644 --- a/server/internal/domain/sites/service_test.go +++ b/server/internal/domain/sites/service_test.go @@ -163,7 +163,7 @@ func TestDeleteSite_notFoundWhenSoftDeleteAffectsZeroRows(t *testing.T) { } } -func TestReassignDevicesToSite_rejectsCrossCollectionConflict(t *testing.T) { +func TestAssignDevicesToSite_rejectsCrossCollectionConflict(t *testing.T) { // Table-driven across both transactors: the production-shaped // wrapping transactor pins Fix #1 (a sentinel error would get // wrapped as Internal in prod, breaking the conflict path). @@ -190,7 +190,7 @@ func TestReassignDevicesToSite_rejectsCrossCollectionConflict(t *testing.T) { }, nil) // No update call — entire batch rejected. - count, conflicts, err := svc.ReassignDevicesToSite(context.Background(), models.ReassignDevicesToSiteParams{ + count, conflicts, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ OrgID: testOrgID, TargetSiteID: &target, DeviceIdentifiers: identifiers, @@ -217,7 +217,7 @@ func TestReassignDevicesToSite_rejectsCrossCollectionConflict(t *testing.T) { } } -func TestReassignDevicesToSite_reportsMissingDevices(t *testing.T) { +func TestAssignDevicesToSite_reportsMissingDevices(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} @@ -232,7 +232,7 @@ func TestReassignDevicesToSite_reportsMissingDevices(t *testing.T) { store.EXPECT().ListExistingDeviceIdentifiers(inTxCtx, testOrgID, identifiers).Return([]string{"d1"}, nil) store.EXPECT().FindDeviceSiteConflicts(inTxCtx, testOrgID, identifiers).Return(map[string]int64{}, nil) - _, conflicts, err := svc.ReassignDevicesToSite(context.Background(), models.ReassignDevicesToSiteParams{ + _, conflicts, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ OrgID: testOrgID, TargetSiteID: &target, DeviceIdentifiers: identifiers, @@ -248,7 +248,7 @@ func TestReassignDevicesToSite_reportsMissingDevices(t *testing.T) { } } -func TestReassignDevicesToSite_writesOnSuccess(t *testing.T) { +func TestAssignDevicesToSite_writesOnSuccess(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} @@ -262,9 +262,9 @@ func TestReassignDevicesToSite_writesOnSuccess(t *testing.T) { store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, target).Return(nil) store.EXPECT().ListExistingDeviceIdentifiers(inTxCtx, testOrgID, identifiers).Return(identifiers, nil) store.EXPECT().FindDeviceSiteConflicts(inTxCtx, testOrgID, identifiers).Return(map[string]int64{}, nil) - store.EXPECT().ReassignDevicesToSite(inTxCtx, testOrgID, gomock.AssignableToTypeOf(ptrInt64(0)), identifiers).Return(int64(2), nil) + store.EXPECT().AssignDevicesToSite(inTxCtx, testOrgID, gomock.AssignableToTypeOf(ptrInt64(0)), identifiers).Return(int64(2), nil) - count, conflicts, err := svc.ReassignDevicesToSite(context.Background(), models.ReassignDevicesToSiteParams{ + count, conflicts, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ OrgID: testOrgID, TargetSiteID: &target, DeviceIdentifiers: identifiers, @@ -283,7 +283,7 @@ func TestReassignDevicesToSite_writesOnSuccess(t *testing.T) { } } -func TestReassignDevicesToSite_unassignedTargetSkipsBelongsCheck(t *testing.T) { +func TestAssignDevicesToSite_unassignedTargetSkipsBelongsCheck(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} @@ -296,9 +296,9 @@ func TestReassignDevicesToSite_unassignedTargetSkipsBelongsCheck(t *testing.T) { store.EXPECT().LockDevicesForReassign(inTxCtx, testOrgID, identifiers).Return(nil) store.EXPECT().ListExistingDeviceIdentifiers(inTxCtx, testOrgID, identifiers).Return(identifiers, nil) store.EXPECT().FindDeviceSiteConflicts(inTxCtx, testOrgID, identifiers).Return(map[string]int64{}, nil) - store.EXPECT().ReassignDevicesToSite(inTxCtx, testOrgID, gomock.Nil(), identifiers).Return(int64(1), nil) + store.EXPECT().AssignDevicesToSite(inTxCtx, testOrgID, gomock.Nil(), identifiers).Return(int64(1), nil) - _, _, err := svc.ReassignDevicesToSite(context.Background(), models.ReassignDevicesToSiteParams{ + _, _, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ OrgID: testOrgID, TargetSiteID: nil, DeviceIdentifiers: identifiers, @@ -308,7 +308,7 @@ func TestReassignDevicesToSite_unassignedTargetSkipsBelongsCheck(t *testing.T) { } } -func TestReassignDevicesToSite_targetMatchesCurrentRackSiteIsNotAConflict(t *testing.T) { +func TestAssignDevicesToSite_targetMatchesCurrentRackSiteIsNotAConflict(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} @@ -324,9 +324,9 @@ func TestReassignDevicesToSite_targetMatchesCurrentRackSiteIsNotAConflict(t *tes store.EXPECT().FindDeviceSiteConflicts(inTxCtx, testOrgID, identifiers).Return(map[string]int64{ "d1": target, }, nil) - store.EXPECT().ReassignDevicesToSite(inTxCtx, testOrgID, gomock.AssignableToTypeOf(ptrInt64(0)), identifiers).Return(int64(1), nil) + store.EXPECT().AssignDevicesToSite(inTxCtx, testOrgID, gomock.AssignableToTypeOf(ptrInt64(0)), identifiers).Return(int64(1), nil) - _, conflicts, err := svc.ReassignDevicesToSite(context.Background(), models.ReassignDevicesToSiteParams{ + _, conflicts, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ OrgID: testOrgID, TargetSiteID: &target, DeviceIdentifiers: identifiers, diff --git a/server/internal/domain/stores/interfaces/mocks/mock_site_store.go b/server/internal/domain/stores/interfaces/mocks/mock_site_store.go index ae44eb0a3..026322048 100644 --- a/server/internal/domain/stores/interfaces/mocks/mock_site_store.go +++ b/server/internal/domain/stores/interfaces/mocks/mock_site_store.go @@ -56,6 +56,21 @@ func (mr *MockSiteStoreMockRecorder) AssignBuildingToSite(ctx, orgID, buildingID return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssignBuildingToSite", reflect.TypeOf((*MockSiteStore)(nil).AssignBuildingToSite), ctx, orgID, buildingID, targetSiteID) } +// AssignDevicesToSite mocks base method. +func (m *MockSiteStore) AssignDevicesToSite(ctx context.Context, orgID int64, targetSiteID *int64, deviceIdentifiers []string) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssignDevicesToSite", ctx, orgID, targetSiteID, deviceIdentifiers) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AssignDevicesToSite indicates an expected call of AssignDevicesToSite. +func (mr *MockSiteStoreMockRecorder) AssignDevicesToSite(ctx, orgID, targetSiteID, deviceIdentifiers any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssignDevicesToSite", reflect.TypeOf((*MockSiteStore)(nil).AssignDevicesToSite), ctx, orgID, targetSiteID, deviceIdentifiers) +} + // CreateSite mocks base method. func (m *MockSiteStore) CreateSite(ctx context.Context, params models.CreateSiteParams) (*models.Site, error) { m.ctrl.T.Helper() @@ -217,21 +232,6 @@ func (mr *MockSiteStoreMockRecorder) LockSiteForWrite(ctx, orgID, siteID any) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockSiteForWrite", reflect.TypeOf((*MockSiteStore)(nil).LockSiteForWrite), ctx, orgID, siteID) } -// ReassignDevicesToSite mocks base method. -func (m *MockSiteStore) ReassignDevicesToSite(ctx context.Context, orgID int64, targetSiteID *int64, deviceIdentifiers []string) (int64, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReassignDevicesToSite", ctx, orgID, targetSiteID, deviceIdentifiers) - ret0, _ := ret[0].(int64) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ReassignDevicesToSite indicates an expected call of ReassignDevicesToSite. -func (mr *MockSiteStoreMockRecorder) ReassignDevicesToSite(ctx, orgID, targetSiteID, deviceIdentifiers any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReassignDevicesToSite", reflect.TypeOf((*MockSiteStore)(nil).ReassignDevicesToSite), ctx, orgID, targetSiteID, deviceIdentifiers) -} - // ReassignDevicesUnderBuilding mocks base method. func (m *MockSiteStore) ReassignDevicesUnderBuilding(ctx context.Context, orgID, buildingID int64, targetSiteID *int64) (int64, error) { m.ctrl.T.Helper() diff --git a/server/internal/domain/stores/interfaces/site.go b/server/internal/domain/stores/interfaces/site.go index 327b26885..33eb9744f 100644 --- a/server/internal/domain/stores/interfaces/site.go +++ b/server/internal/domain/stores/interfaces/site.go @@ -81,16 +81,16 @@ type SiteStore interface { // LockDevicesForReassign takes a row-lock on every matching live // device for the duration of the surrounding transaction so the - // conflict check + UPDATE in ReassignDevicesToSite are atomic - // against a concurrent reassign. + // conflict check + UPDATE in AssignDevicesToSite are atomic + // against a concurrent assign. LockDevicesForReassign(ctx context.Context, orgID int64, deviceIdentifiers []string) error - // ReassignDevicesToSite bulk-updates device.site_id for the + // AssignDevicesToSite bulk-updates device.site_id for the // given identifiers. The caller must have validated cross- // collection conflicts (see FindDeviceSiteConflicts) and that // every identifier exists (see ListExistingDeviceIdentifiers). // targetSiteID == nil means "Unassigned". - ReassignDevicesToSite(ctx context.Context, orgID int64, targetSiteID *int64, deviceIdentifiers []string) (int64, error) + AssignDevicesToSite(ctx context.Context, orgID int64, targetSiteID *int64, deviceIdentifiers []string) (int64, error) // FindDeviceSiteConflicts returns, for each requested device that // is in a rack with a site_id, the device identifier and that diff --git a/server/internal/domain/stores/sqlstores/collection_site_placement_integration_test.go b/server/internal/domain/stores/sqlstores/collection_site_placement_integration_test.go index d6b87eba6..c71695f85 100644 --- a/server/internal/domain/stores/sqlstores/collection_site_placement_integration_test.go +++ b/server/internal/domain/stores/sqlstores/collection_site_placement_integration_test.go @@ -111,7 +111,7 @@ func TestCascadeRackDeviceSites_RewritesMemberDevices(t *testing.T) { require.NoError(t, err) // Pre-assign all devices to site A. - _, err = siteStore.ReassignDevicesToSite(ctx, orgID, &siteA.ID, deviceIDs) + _, err = siteStore.AssignDevicesToSite(ctx, orgID, &siteA.ID, deviceIDs) require.NoError(t, err) rack, err := collectionStore.CreateCollection(ctx, orgID, pb.CollectionType_COLLECTION_TYPE_RACK, "Rack", "") @@ -163,9 +163,9 @@ func TestGetAddedDeviceSiteConflicts_ReturnsOnlyConflictingDevices(t *testing.T) require.NoError(t, err) // Device 0 already at site A (matches rack); 1 at site B (conflict); 2 unassigned (conflict). - _, err = siteStore.ReassignDevicesToSite(ctx, orgID, &siteA.ID, deviceIDs[:1]) + _, err = siteStore.AssignDevicesToSite(ctx, orgID, &siteA.ID, deviceIDs[:1]) require.NoError(t, err) - _, err = siteStore.ReassignDevicesToSite(ctx, orgID, &siteB.ID, deviceIDs[1:2]) + _, err = siteStore.AssignDevicesToSite(ctx, orgID, &siteB.ID, deviceIDs[1:2]) require.NoError(t, err) rack, err := collectionStore.CreateCollection(ctx, orgID, pb.CollectionType_COLLECTION_TYPE_RACK, "Rack", "") @@ -201,7 +201,7 @@ func TestCascadeAddedDeviceSites_RewritesOnlyDiffering(t *testing.T) { siteB, err := siteStore.CreateSite(ctx, sitesmodels.CreateSiteParams{OrgID: orgID, Name: "Site B"}) require.NoError(t, err) - _, err = siteStore.ReassignDevicesToSite(ctx, orgID, &siteB.ID, deviceIDs[1:2]) + _, err = siteStore.AssignDevicesToSite(ctx, orgID, &siteB.ID, deviceIDs[1:2]) require.NoError(t, err) rack, err := collectionStore.CreateCollection(ctx, orgID, pb.CollectionType_COLLECTION_TYPE_RACK, "Rack", "") @@ -314,7 +314,7 @@ func TestUnassignDeviceSitesByRack_ClearsRackMembersOnly(t *testing.T) { site, err := siteStore.CreateSite(ctx, sitesmodels.CreateSiteParams{OrgID: orgID, Name: "Site"}) require.NoError(t, err) - _, err = siteStore.ReassignDevicesToSite(ctx, orgID, &site.ID, deviceIDs) + _, err = siteStore.AssignDevicesToSite(ctx, orgID, &site.ID, deviceIDs) require.NoError(t, err) rack, err := collectionStore.CreateCollection(ctx, orgID, pb.CollectionType_COLLECTION_TYPE_RACK, "Rack", "") diff --git a/server/internal/domain/stores/sqlstores/site.go b/server/internal/domain/stores/sqlstores/site.go index 48d21ca99..4ef807e53 100644 --- a/server/internal/domain/stores/sqlstores/site.go +++ b/server/internal/domain/stores/sqlstores/site.go @@ -228,8 +228,8 @@ func (s *SQLSiteStore) LockDevicesForReassign(ctx context.Context, orgID int64, return nil } -func (s *SQLSiteStore) ReassignDevicesToSite(ctx context.Context, orgID int64, targetSiteID *int64, deviceIdentifiers []string) (int64, error) { - rowsAffected, err := s.GetQueries(ctx).ReassignDevicesToSite(ctx, sqlc.ReassignDevicesToSiteParams{ +func (s *SQLSiteStore) AssignDevicesToSite(ctx context.Context, orgID int64, targetSiteID *int64, deviceIdentifiers []string) (int64, error) { + rowsAffected, err := s.GetQueries(ctx).AssignDevicesToSite(ctx, sqlc.AssignDevicesToSiteParams{ OrgID: orgID, TargetSiteID: ptrToNullInt64(targetSiteID), DeviceIdentifiers: deviceIdentifiers, diff --git a/server/internal/domain/stores/sqlstores/site_invariant_integration_test.go b/server/internal/domain/stores/sqlstores/site_invariant_integration_test.go index b711f156b..947790425 100644 --- a/server/internal/domain/stores/sqlstores/site_invariant_integration_test.go +++ b/server/internal/domain/stores/sqlstores/site_invariant_integration_test.go @@ -40,7 +40,7 @@ func TestDeleteSite_ClearsAllDeviceSitePointers(t *testing.T) { }) require.NoError(t, err) - _, err = siteStore.ReassignDevicesToSite(t.Context(), orgID, &site.ID, deviceIDs[:2]) + _, err = siteStore.AssignDevicesToSite(t.Context(), orgID, &site.ID, deviceIDs[:2]) require.NoError(t, err) // Sanity: the two reassigned devices now point at the site. diff --git a/server/internal/handlers/middleware/rpc_permissions.go b/server/internal/handlers/middleware/rpc_permissions.go index fdcd8ac56..a70c98b6d 100644 --- a/server/internal/handlers/middleware/rpc_permissions.go +++ b/server/internal/handlers/middleware/rpc_permissions.go @@ -283,12 +283,12 @@ var ProcedurePermissions = map[string]string{ serverlogv1connect.ServerLogServiceListServerLogsProcedure: authz.PermServerlogRead, // Sites CRUD — site:read for List, site:manage for everything else. - sitesv1connect.SiteServiceListSitesProcedure: authz.PermSiteRead, - sitesv1connect.SiteServiceCreateSiteProcedure: authz.PermSiteManage, - sitesv1connect.SiteServiceUpdateSiteProcedure: authz.PermSiteManage, - sitesv1connect.SiteServiceDeleteSiteProcedure: authz.PermSiteManage, - sitesv1connect.SiteServiceReassignDevicesToSiteProcedure: authz.PermSiteManage, - sitesv1connect.SiteServiceAssignBuildingToSiteProcedure: authz.PermSiteManage, + sitesv1connect.SiteServiceListSitesProcedure: authz.PermSiteRead, + sitesv1connect.SiteServiceCreateSiteProcedure: authz.PermSiteManage, + sitesv1connect.SiteServiceUpdateSiteProcedure: authz.PermSiteManage, + sitesv1connect.SiteServiceDeleteSiteProcedure: authz.PermSiteManage, + sitesv1connect.SiteServiceAssignDevicesToSiteProcedure: authz.PermSiteManage, + sitesv1connect.SiteServiceAssignBuildingToSiteProcedure: authz.PermSiteManage, // GetSiteStats also calls RequirePermission(PermFleetRead) inline to // cover the aggregate telemetry surface (matching the gate on // telemetry.GetCombinedMetrics). The map entry is the primary gate. diff --git a/server/internal/handlers/sites/handler.go b/server/internal/handlers/sites/handler.go index ea37ed5ec..d44da1b13 100644 --- a/server/internal/handlers/sites/handler.go +++ b/server/internal/handlers/sites/handler.go @@ -86,16 +86,16 @@ func (h *Handler) DeleteSite(ctx context.Context, req *connect.Request[pb.Delete }), nil } -func (h *Handler) ReassignDevicesToSite(ctx context.Context, req *connect.Request[pb.ReassignDevicesToSiteRequest]) (*connect.Response[pb.ReassignDevicesToSiteResponse], error) { +func (h *Handler) AssignDevicesToSite(ctx context.Context, req *connect.Request[pb.AssignDevicesToSiteRequest]) (*connect.Response[pb.AssignDevicesToSiteResponse], error) { info, err := middleware.RequirePermission(ctx, authz.PermSiteManage, authz.ResourceContext{}) if err != nil { return nil, err } - count, conflicts, err := h.service.ReassignDevicesToSite(ctx, toReassignParams(req.Msg, info.OrganizationID)) + count, conflicts, err := h.service.AssignDevicesToSite(ctx, toAssignDevicesParams(req.Msg, info.OrganizationID)) if err != nil { return nil, err } - return connect.NewResponse(&pb.ReassignDevicesToSiteResponse{ + return connect.NewResponse(&pb.AssignDevicesToSiteResponse{ ReassignedCount: count, Conflicts: toProtoConflicts(conflicts), }), nil diff --git a/server/internal/handlers/sites/handler_test.go b/server/internal/handlers/sites/handler_test.go index 8c48cf4be..eeccea261 100644 --- a/server/internal/handlers/sites/handler_test.go +++ b/server/internal/handlers/sites/handler_test.go @@ -223,7 +223,7 @@ func TestHandler_DeleteSite_surfacesCascadeCounts(t *testing.T) { assert.Equal(t, int64(4), resp.Msg.GetUnassignedRackCount()) } -func TestHandler_ReassignDevicesToSite_success(t *testing.T) { +func TestHandler_AssignDevicesToSite_success(t *testing.T) { t.Parallel() h := newTestHandler(t) @@ -234,9 +234,9 @@ func TestHandler_ReassignDevicesToSite_success(t *testing.T) { h.siteStore.EXPECT().LockSiteForWrite(gomock.Any(), int64(7), target).Return(nil) h.siteStore.EXPECT().ListExistingDeviceIdentifiers(gomock.Any(), int64(7), idents).Return(idents, nil) h.siteStore.EXPECT().FindDeviceSiteConflicts(gomock.Any(), int64(7), idents).Return(map[string]int64{}, nil) - h.siteStore.EXPECT().ReassignDevicesToSite(gomock.Any(), int64(7), gomock.AssignableToTypeOf(ptrInt64(0)), idents).Return(int64(2), nil) + h.siteStore.EXPECT().AssignDevicesToSite(gomock.Any(), int64(7), gomock.AssignableToTypeOf(ptrInt64(0)), idents).Return(int64(2), nil) - resp, err := h.handler.ReassignDevicesToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.ReassignDevicesToSiteRequest{ + resp, err := h.handler.AssignDevicesToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignDevicesToSiteRequest{ TargetSiteId: &target, DeviceIdentifiers: idents, })) @@ -245,7 +245,7 @@ func TestHandler_ReassignDevicesToSite_success(t *testing.T) { assert.Empty(t, resp.Msg.GetConflicts()) } -func TestHandler_ReassignDevicesToSite_conflictsReturnTypedReason(t *testing.T) { +func TestHandler_AssignDevicesToSite_conflictsReturnTypedReason(t *testing.T) { t.Parallel() h := newTestHandler(t) @@ -259,9 +259,9 @@ func TestHandler_ReassignDevicesToSite_conflictsReturnTypedReason(t *testing.T) h.siteStore.EXPECT().FindDeviceSiteConflicts(gomock.Any(), int64(7), idents).Return(map[string]int64{ "d1": conflictingSite, }, nil) - // No ReassignDevicesToSite store call — conflict path rejects entire batch. + // No AssignDevicesToSite store call — conflict path rejects entire batch. - resp, err := h.handler.ReassignDevicesToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.ReassignDevicesToSiteRequest{ + resp, err := h.handler.AssignDevicesToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignDevicesToSiteRequest{ TargetSiteId: &target, DeviceIdentifiers: idents, })) diff --git a/server/internal/handlers/sites/translate.go b/server/internal/handlers/sites/translate.go index 964ea7401..fa998cab4 100644 --- a/server/internal/handlers/sites/translate.go +++ b/server/internal/handlers/sites/translate.go @@ -41,13 +41,13 @@ func toUpdateSiteParams(req *pb.UpdateSiteRequest, orgID int64) models.UpdateSit } } -func toReassignParams(req *pb.ReassignDevicesToSiteRequest, orgID int64) models.ReassignDevicesToSiteParams { +func toAssignDevicesParams(req *pb.AssignDevicesToSiteRequest, orgID int64) models.AssignDevicesToSiteParams { var targetSiteID *int64 if req.TargetSiteId != nil { v := req.GetTargetSiteId() targetSiteID = &v } - return models.ReassignDevicesToSiteParams{ + return models.AssignDevicesToSiteParams{ OrgID: orgID, TargetSiteID: targetSiteID, DeviceIdentifiers: req.GetDeviceIdentifiers(), diff --git a/server/sqlc/queries/site.sql b/server/sqlc/queries/site.sql index c7742d08e..794edb6ca 100644 --- a/server/sqlc/queries/site.sql +++ b/server/sqlc/queries/site.sql @@ -209,7 +209,7 @@ WHERE org_id = sqlc.arg('org_id') AND deleted_at IS NULL FOR UPDATE; --- name: ReassignDevicesToSite :execrows +-- name: AssignDevicesToSite :execrows -- Bulk update of device.site_id for the given identifiers within the -- org. Caller is expected to have already validated that no device is -- in a rack at a different site (see FindDeviceSiteConflicts). @@ -261,7 +261,7 @@ WHERE d.org_id = sqlc.arg('org_id') -- name: ListExistingDeviceIdentifiers :many -- Filters the requested identifier list down to those that actually -- exist as live devices in the org. Used to surface "device_not_found" --- conflicts in ReassignDevicesToSite without an N+1 lookup. +-- conflicts in AssignDevicesToSite without an N+1 lookup. SELECT device_identifier FROM device WHERE org_id = sqlc.arg('org_id') From 8dc17f76130989f697b4833456325289279a66de Mon Sep 17 00:00:00 2001 From: flesher Date: Wed, 10 Jun 2026 16:26:11 -0500 Subject: [PATCH 2/9] =?UTF-8?q?refactor(sites):=20pluralize=20AssignBuildi?= =?UTF-8?q?ngToSite=20=E2=86=92=20AssignBuildingsToSite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promotes the building→site assignment RPC to a bulk shape so operators can move multiple buildings between sites in one atomic transaction. A single-building caller passes a one-element `building_ids` array; the response aggregates cascade counts across every building. The store-level operation stays singular (one UPDATE per building); the service layer sorts by id for deterministic lock ordering then loops inside a single tx — if any building fails, the whole batch rolls back. Updates the in-flight FleetBuildingsPage row-action call site (#412) to pass the new array shape. Refs #420. --- .../api/generated/sites/v1/sites_pb.ts | 49 +-- client/src/protoFleet/api/sites.ts | 17 +- .../MinerActionsMenu/MinerReparentPicker.tsx | 4 +- .../pages/FleetBuildingsPage.tsx | 6 +- proto/buildings/v1/buildings.proto | 2 +- proto/sites/v1/sites.proto | 31 +- server/generated/grpc/sites/v1/sites.pb.go | 330 +++++++++--------- .../sites/v1/sitesv1connect/sites.connect.go | 68 ++-- .../domain/buildings/models/models.go | 2 +- server/internal/domain/buildings/service.go | 10 +- .../domain/buildings/service_stats_test.go | 4 +- .../domain/collection/service_test.go | 2 +- server/internal/domain/sites/models/models.go | 15 +- server/internal/domain/sites/service.go | 85 +++-- server/internal/domain/sites/service_test.go | 39 ++- .../domain/stores/interfaces/building.go | 2 +- server/internal/handlers/buildings/handler.go | 2 +- .../handlers/middleware/rpc_permissions.go | 12 +- server/internal/handlers/sites/handler.go | 6 +- .../internal/handlers/sites/handler_test.go | 39 ++- server/internal/handlers/sites/translate.go | 6 +- server/sqlc/queries/site.sql | 4 +- 22 files changed, 416 insertions(+), 319 deletions(-) diff --git a/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts b/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts index 9079632ce..62eb67f37 100644 --- a/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts +++ b/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts @@ -15,7 +15,7 @@ import type { Message } from "@bufbuild/protobuf"; export const file_sites_v1_sites: GenFile = /*@__PURE__*/ fileDesc( - "ChRzaXRlcy92MS9zaXRlcy5wcm90bxIIc2l0ZXMudjEi4gIKBFNpdGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIVCg1sb2NhdGlvbl9jaXR5GAQgASgJEhYKDmxvY2F0aW9uX3N0YXRlGAUgASgJEhAKCHRpbWV6b25lGAYgASgJEhkKEXBvd2VyX2NhcGFjaXR5X213GAcgASgBEhYKDm5ldHdvcmtfY29uZmlnGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB2FkZHJlc3MYCyABKAkSEwoLcG9zdGFsX2NvZGUYDSABKAkSDwoHY291bnRyeRgOIAEoCRINCgVub3RlcxgPIAEoCUoECAMQBEoECAwQDVILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIicAoOU2l0ZVdpdGhDb3VudHMSHAoEc2l0ZRgBIAEoCzIOLnNpdGVzLnYxLlNpdGUSFAoMZGV2aWNlX2NvdW50GAIgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAMgASgDEhIKCnJhY2tfY291bnQYBCABKAMiEgoQTGlzdFNpdGVzUmVxdWVzdCI8ChFMaXN0U2l0ZXNSZXNwb25zZRInCgVzaXRlcxgBIAMoCzIYLnNpdGVzLnYxLlNpdGVXaXRoQ291bnRzIu0CChFDcmVhdGVTaXRlUmVxdWVzdBIYCgRuYW1lGAEgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYAyABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAQgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgFIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgGIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYByABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAggASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgKIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAsgASgJQge6SARyAhgCEhcKBW5vdGVzGAwgASgJQgi6SAVyAxiAIEoECAIQA0oECAkQClILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSQ3JlYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIoIDChFVcGRhdGVTaXRlUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYBCABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAUgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgGIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgHIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYCCABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAkgASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgLIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAwgASgJQge6SARyAhgCEhcKBW5vdGVzGA0gASgJQgi6SAVyAxiAIEoECAMQBEoECAoQC1ILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSVXBkYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIigKEURlbGV0ZVNpdGVSZXF1ZXN0EhMKAmlkGAEgASgDQge6SAQiAiAAInQKEkRlbGV0ZVNpdGVSZXNwb25zZRIfChd1bmFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZkZWxldGVkX2J1aWxkaW5nX2NvdW50GAIgASgDEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgDIAEoAyKHAQoaQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QSJAoOdGFyZ2V0X3NpdGVfaWQYASABKANCB7pIBCICIABIAIgBARIwChJkZXZpY2VfaWRlbnRpZmllcnMYAiADKAlCFLpIEZIBDggBEJBOIgdyBRABGIACQhEKD190YXJnZXRfc2l0ZV9pZCJ+ChFQZXJEZXZpY2VDb25mbGljdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCgZyZWFzb24YAiABKA4yIS5zaXRlcy52MS5QZXJEZXZpY2VDb25mbGljdFJlYXNvbhIbChNjb25mbGljdGluZ19zaXRlX2lkGAMgASgDImcKG0Fzc2lnbkRldmljZXNUb1NpdGVSZXNwb25zZRIYChByZWFzc2lnbmVkX2NvdW50GAEgASgDEi4KCWNvbmZsaWN0cxgCIAMoCzIbLnNpdGVzLnYxLlBlckRldmljZUNvbmZsaWN0InQKG0Fzc2lnbkJ1aWxkaW5nVG9TaXRlUmVxdWVzdBIcCgtidWlsZGluZ19pZBgBIAEoA0IHukgEIgIgABIkCg50YXJnZXRfc2l0ZV9pZBgCIAEoA0IHukgEIgIgAEgAiAEBQhEKD190YXJnZXRfc2l0ZV9pZCJeChxBc3NpZ25CdWlsZGluZ1RvU2l0ZVJlc3BvbnNlEh0KFXJlYXNzaWduZWRfcmFja19jb3VudBgBIAEoAxIfChdyZWFzc2lnbmVkX2RldmljZV9jb3VudBgCIAEoAyIvChNHZXRTaXRlU3RhdHNSZXF1ZXN0EhgKB3NpdGVfaWQYASABKANCB7pIBCICIAAi/wIKFEdldFNpdGVTdGF0c1Jlc3BvbnNlEg8KB3NpdGVfaWQYASABKAMSFgoOYnVpbGRpbmdfY291bnQYAiABKAUSFAoMZGV2aWNlX2NvdW50GAMgASgFEhcKD3JlcG9ydGluZ19jb3VudBgEIAEoBRIaChJ0b3RhbF9oYXNocmF0ZV90aHMYBSABKAESGgoSYXZnX2VmZmljaWVuY3lfanRoGAYgASgBEhYKDnRvdGFsX3Bvd2VyX2t3GAcgASgBEhUKDWhhc2hpbmdfY291bnQYCCABKAUSFAoMYnJva2VuX2NvdW50GAkgASgFEhUKDW9mZmxpbmVfY291bnQYCiABKAUSFgoOc2xlZXBpbmdfY291bnQYCyABKAUSIAoYaGFzaHJhdGVfcmVwb3J0aW5nX2NvdW50GAwgASgFEiIKGmVmZmljaWVuY3lfcmVwb3J0aW5nX2NvdW50GA0gASgFEh0KFXBvd2VyX3JlcG9ydGluZ19jb3VudBgOIAEoBSqzAQoXUGVyRGV2aWNlQ29uZmxpY3RSZWFzb24SKgomUEVSX0RFVklDRV9DT05GTElDVF9SRUFTT05fVU5TUEVDSUZJRUQQABIvCitQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9ERVZJQ0VfTk9UX0ZPVU5EEAESOwo3UEVSX0RFVklDRV9DT05GTElDVF9SRUFTT05fREVWSUNFX0lOX1JBQ0tfQVRfT1RIRVJfU0lURRACMsgECgtTaXRlU2VydmljZRJECglMaXN0U2l0ZXMSGi5zaXRlcy52MS5MaXN0U2l0ZXNSZXF1ZXN0Ghsuc2l0ZXMudjEuTGlzdFNpdGVzUmVzcG9uc2USRwoKQ3JlYXRlU2l0ZRIbLnNpdGVzLnYxLkNyZWF0ZVNpdGVSZXF1ZXN0Ghwuc2l0ZXMudjEuQ3JlYXRlU2l0ZVJlc3BvbnNlEkcKClVwZGF0ZVNpdGUSGy5zaXRlcy52MS5VcGRhdGVTaXRlUmVxdWVzdBocLnNpdGVzLnYxLlVwZGF0ZVNpdGVSZXNwb25zZRJHCgpEZWxldGVTaXRlEhsuc2l0ZXMudjEuRGVsZXRlU2l0ZVJlcXVlc3QaHC5zaXRlcy52MS5EZWxldGVTaXRlUmVzcG9uc2USYgoTQXNzaWduRGV2aWNlc1RvU2l0ZRIkLnNpdGVzLnYxLkFzc2lnbkRldmljZXNUb1NpdGVSZXF1ZXN0GiUuc2l0ZXMudjEuQXNzaWduRGV2aWNlc1RvU2l0ZVJlc3BvbnNlEmUKFEFzc2lnbkJ1aWxkaW5nVG9TaXRlEiUuc2l0ZXMudjEuQXNzaWduQnVpbGRpbmdUb1NpdGVSZXF1ZXN0GiYuc2l0ZXMudjEuQXNzaWduQnVpbGRpbmdUb1NpdGVSZXNwb25zZRJNCgxHZXRTaXRlU3RhdHMSHS5zaXRlcy52MS5HZXRTaXRlU3RhdHNSZXF1ZXN0Gh4uc2l0ZXMudjEuR2V0U2l0ZVN0YXRzUmVzcG9uc2VCoAEKDGNvbS5zaXRlcy52MUIKU2l0ZXNQcm90b1ABWkNnaXRodWIuY29tL2Jsb2NrL3Byb3RvLWZsZWV0L3NlcnZlci9nZW5lcmF0ZWQvZ3JwYy9zaXRlcy92MTtzaXRlc3YxogIDU1hYqgIIU2l0ZXMuVjHKAghTaXRlc1xWMeICFFNpdGVzXFYxXEdQQk1ldGFkYXRh6gIJU2l0ZXM6OlYxYgZwcm90bzM", + "ChRzaXRlcy92MS9zaXRlcy5wcm90bxIIc2l0ZXMudjEi4gIKBFNpdGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIVCg1sb2NhdGlvbl9jaXR5GAQgASgJEhYKDmxvY2F0aW9uX3N0YXRlGAUgASgJEhAKCHRpbWV6b25lGAYgASgJEhkKEXBvd2VyX2NhcGFjaXR5X213GAcgASgBEhYKDm5ldHdvcmtfY29uZmlnGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB2FkZHJlc3MYCyABKAkSEwoLcG9zdGFsX2NvZGUYDSABKAkSDwoHY291bnRyeRgOIAEoCRINCgVub3RlcxgPIAEoCUoECAMQBEoECAwQDVILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIicAoOU2l0ZVdpdGhDb3VudHMSHAoEc2l0ZRgBIAEoCzIOLnNpdGVzLnYxLlNpdGUSFAoMZGV2aWNlX2NvdW50GAIgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAMgASgDEhIKCnJhY2tfY291bnQYBCABKAMiEgoQTGlzdFNpdGVzUmVxdWVzdCI8ChFMaXN0U2l0ZXNSZXNwb25zZRInCgVzaXRlcxgBIAMoCzIYLnNpdGVzLnYxLlNpdGVXaXRoQ291bnRzIu0CChFDcmVhdGVTaXRlUmVxdWVzdBIYCgRuYW1lGAEgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYAyABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAQgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgFIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgGIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYByABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAggASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgKIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAsgASgJQge6SARyAhgCEhcKBW5vdGVzGAwgASgJQgi6SAVyAxiAIEoECAIQA0oECAkQClILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSQ3JlYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIoIDChFVcGRhdGVTaXRlUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYBCABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAUgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgGIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgHIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYCCABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAkgASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgLIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAwgASgJQge6SARyAhgCEhcKBW5vdGVzGA0gASgJQgi6SAVyAxiAIEoECAMQBEoECAoQC1ILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSVXBkYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIigKEURlbGV0ZVNpdGVSZXF1ZXN0EhMKAmlkGAEgASgDQge6SAQiAiAAInQKEkRlbGV0ZVNpdGVSZXNwb25zZRIfChd1bmFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZkZWxldGVkX2J1aWxkaW5nX2NvdW50GAIgASgDEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgDIAEoAyKHAQoaQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QSJAoOdGFyZ2V0X3NpdGVfaWQYASABKANCB7pIBCICIABIAIgBARIwChJkZXZpY2VfaWRlbnRpZmllcnMYAiADKAlCFLpIEZIBDggBEJBOIgdyBRABGIACQhEKD190YXJnZXRfc2l0ZV9pZCJ+ChFQZXJEZXZpY2VDb25mbGljdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCgZyZWFzb24YAiABKA4yIS5zaXRlcy52MS5QZXJEZXZpY2VDb25mbGljdFJlYXNvbhIbChNjb25mbGljdGluZ19zaXRlX2lkGAMgASgDImcKG0Fzc2lnbkRldmljZXNUb1NpdGVSZXNwb25zZRIYChByZWFzc2lnbmVkX2NvdW50GAEgASgDEi4KCWNvbmZsaWN0cxgCIAMoCzIbLnNpdGVzLnYxLlBlckRldmljZUNvbmZsaWN0IoABChxBc3NpZ25CdWlsZGluZ3NUb1NpdGVSZXF1ZXN0EicKDGJ1aWxkaW5nX2lkcxgBIAMoA0IRukgOkgELCAEQ6AciBCICIAASJAoOdGFyZ2V0X3NpdGVfaWQYAiABKANCB7pIBCICIABIAIgBAUIRCg9fdGFyZ2V0X3NpdGVfaWQiXwodQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVzcG9uc2USHQoVcmVhc3NpZ25lZF9yYWNrX2NvdW50GAEgASgDEh8KF3JlYXNzaWduZWRfZGV2aWNlX2NvdW50GAIgASgDIi8KE0dldFNpdGVTdGF0c1JlcXVlc3QSGAoHc2l0ZV9pZBgBIAEoA0IHukgEIgIgACL/AgoUR2V0U2l0ZVN0YXRzUmVzcG9uc2USDwoHc2l0ZV9pZBgBIAEoAxIWCg5idWlsZGluZ19jb3VudBgCIAEoBRIUCgxkZXZpY2VfY291bnQYAyABKAUSFwoPcmVwb3J0aW5nX2NvdW50GAQgASgFEhoKEnRvdGFsX2hhc2hyYXRlX3RocxgFIAEoARIaChJhdmdfZWZmaWNpZW5jeV9qdGgYBiABKAESFgoOdG90YWxfcG93ZXJfa3cYByABKAESFQoNaGFzaGluZ19jb3VudBgIIAEoBRIUCgxicm9rZW5fY291bnQYCSABKAUSFQoNb2ZmbGluZV9jb3VudBgKIAEoBRIWCg5zbGVlcGluZ19jb3VudBgLIAEoBRIgChhoYXNocmF0ZV9yZXBvcnRpbmdfY291bnQYDCABKAUSIgoaZWZmaWNpZW5jeV9yZXBvcnRpbmdfY291bnQYDSABKAUSHQoVcG93ZXJfcmVwb3J0aW5nX2NvdW50GA4gASgFKrMBChdQZXJEZXZpY2VDb25mbGljdFJlYXNvbhIqCiZQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9VTlNQRUNJRklFRBAAEi8KK1BFUl9ERVZJQ0VfQ09ORkxJQ1RfUkVBU09OX0RFVklDRV9OT1RfRk9VTkQQARI7CjdQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9ERVZJQ0VfSU5fUkFDS19BVF9PVEhFUl9TSVRFEAIyywQKC1NpdGVTZXJ2aWNlEkQKCUxpc3RTaXRlcxIaLnNpdGVzLnYxLkxpc3RTaXRlc1JlcXVlc3QaGy5zaXRlcy52MS5MaXN0U2l0ZXNSZXNwb25zZRJHCgpDcmVhdGVTaXRlEhsuc2l0ZXMudjEuQ3JlYXRlU2l0ZVJlcXVlc3QaHC5zaXRlcy52MS5DcmVhdGVTaXRlUmVzcG9uc2USRwoKVXBkYXRlU2l0ZRIbLnNpdGVzLnYxLlVwZGF0ZVNpdGVSZXF1ZXN0Ghwuc2l0ZXMudjEuVXBkYXRlU2l0ZVJlc3BvbnNlEkcKCkRlbGV0ZVNpdGUSGy5zaXRlcy52MS5EZWxldGVTaXRlUmVxdWVzdBocLnNpdGVzLnYxLkRlbGV0ZVNpdGVSZXNwb25zZRJiChNBc3NpZ25EZXZpY2VzVG9TaXRlEiQuc2l0ZXMudjEuQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QaJS5zaXRlcy52MS5Bc3NpZ25EZXZpY2VzVG9TaXRlUmVzcG9uc2USaAoVQXNzaWduQnVpbGRpbmdzVG9TaXRlEiYuc2l0ZXMudjEuQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVxdWVzdBonLnNpdGVzLnYxLkFzc2lnbkJ1aWxkaW5nc1RvU2l0ZVJlc3BvbnNlEk0KDEdldFNpdGVTdGF0cxIdLnNpdGVzLnYxLkdldFNpdGVTdGF0c1JlcXVlc3QaHi5zaXRlcy52MS5HZXRTaXRlU3RhdHNSZXNwb25zZUKgAQoMY29tLnNpdGVzLnYxQgpTaXRlc1Byb3RvUAFaQ2dpdGh1Yi5jb20vYmxvY2svcHJvdG8tZmxlZXQvc2VydmVyL2dlbmVyYXRlZC9ncnBjL3NpdGVzL3YxO3NpdGVzdjGiAgNTWFiqAghTaXRlcy5WMcoCCFNpdGVzXFYx4gIUU2l0ZXNcVjFcR1BCTWV0YWRhdGHqAglTaXRlczo6VjFiBnByb3RvMw", [file_buf_validate_validate, file_google_protobuf_timestamp], ); @@ -499,16 +499,16 @@ export const AssignDevicesToSiteResponseSchema: GenMessage & { +export type AssignBuildingsToSiteRequest = Message<"sites.v1.AssignBuildingsToSiteRequest"> & { /** - * @generated from field: int64 building_id = 1; + * @generated from field: repeated int64 building_ids = 1; */ - buildingId: bigint; + buildingIds: bigint[]; /** - * Unset = move building to "Unassigned". When present, must be > 0. + * Unset = move buildings to "Unassigned". When present, must be > 0. * * @generated from field: optional int64 target_site_id = 2; */ @@ -516,20 +516,21 @@ export type AssignBuildingToSiteRequest = Message<"sites.v1.AssignBuildingToSite }; /** - * Describes the message sites.v1.AssignBuildingToSiteRequest. - * Use `create(AssignBuildingToSiteRequestSchema)` to create a new message. + * Describes the message sites.v1.AssignBuildingsToSiteRequest. + * Use `create(AssignBuildingsToSiteRequestSchema)` to create a new message. */ -export const AssignBuildingToSiteRequestSchema: GenMessage = +export const AssignBuildingsToSiteRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 13); /** - * @generated from message sites.v1.AssignBuildingToSiteResponse + * @generated from message sites.v1.AssignBuildingsToSiteResponse */ -export type AssignBuildingToSiteResponse = Message<"sites.v1.AssignBuildingToSiteResponse"> & { +export type AssignBuildingsToSiteResponse = Message<"sites.v1.AssignBuildingsToSiteResponse"> & { /** * Cascade impact: how many racks and devices were re-stamped with - * the new site_id in the same transaction. + * the new site_id in the same transaction, aggregated across every + * building in the batch. * * @generated from field: int64 reassigned_rack_count = 1; */ @@ -542,10 +543,10 @@ export type AssignBuildingToSiteResponse = Message<"sites.v1.AssignBuildingToSit }; /** - * Describes the message sites.v1.AssignBuildingToSiteResponse. - * Use `create(AssignBuildingToSiteResponseSchema)` to create a new message. + * Describes the message sites.v1.AssignBuildingsToSiteResponse. + * Use `create(AssignBuildingsToSiteResponseSchema)` to create a new message. */ -export const AssignBuildingToSiteResponseSchema: GenMessage = +export const AssignBuildingsToSiteResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 14); @@ -765,17 +766,19 @@ export const SiteService: GenService<{ output: typeof AssignDevicesToSiteResponseSchema; }; /** - * AssignBuildingToSite moves a building to a different site - * (or to "Unassigned" when target_site_id is unset). The same - * transaction cascades site_id down to the building's racks and - * their devices. Returns the cascade counts. + * AssignBuildingsToSite moves one or more buildings to a target site + * (or to "Unassigned" when target_site_id is unset). All updates run + * in a single transaction; if any building fails, the batch rolls + * back. The same transaction cascades site_id down to each + * building's racks and their devices. Returns the aggregate cascade + * counts across every building in the batch. * - * @generated from rpc sites.v1.SiteService.AssignBuildingToSite + * @generated from rpc sites.v1.SiteService.AssignBuildingsToSite */ - assignBuildingToSite: { + assignBuildingsToSite: { methodKind: "unary"; - input: typeof AssignBuildingToSiteRequestSchema; - output: typeof AssignBuildingToSiteResponseSchema; + input: typeof AssignBuildingsToSiteRequestSchema; + output: typeof AssignBuildingsToSiteResponseSchema; }; /** * GetSiteStats returns server-rolled telemetry + miner-state counts diff --git a/client/src/protoFleet/api/sites.ts b/client/src/protoFleet/api/sites.ts index ede533036..5abbae47d 100644 --- a/client/src/protoFleet/api/sites.ts +++ b/client/src/protoFleet/api/sites.ts @@ -128,9 +128,10 @@ interface AssignDevicesToSiteProps { onFinally?: () => void; } -interface AssignBuildingToSiteProps { - buildingId: bigint; - // Unset moves the building to "Unassigned". +interface AssignBuildingsToSiteProps { + // Bulk-friendly. Pass a single-element array for the singular case. + buildingIds: bigint[]; + // Unset moves the buildings to "Unassigned". targetSiteId?: bigint; signal?: AbortSignal; onSuccess?: (reassignedRackCount: bigint, reassignedDeviceCount: bigint) => void; @@ -298,12 +299,12 @@ const useSites = () => { [handleAuthErrors], ); - const assignBuildingToSite = useCallback( - async ({ buildingId, targetSiteId, signal, onSuccess, onError, onFinally }: AssignBuildingToSiteProps) => { + const assignBuildingsToSite = useCallback( + async ({ buildingIds, targetSiteId, signal, onSuccess, onError, onFinally }: AssignBuildingsToSiteProps) => { try { - const response = await sitesClient.assignBuildingToSite( + const response = await sitesClient.assignBuildingsToSite( { - buildingId, + buildingIds, targetSiteId, }, { signal }, @@ -325,7 +326,7 @@ const useSites = () => { [handleAuthErrors], ); - return { listSites, createSite, updateSite, deleteSite, assignDevicesToSite, assignBuildingToSite }; + return { listSites, createSite, updateSite, deleteSite, assignDevicesToSite, assignBuildingsToSite }; }; export { useSites }; diff --git a/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx b/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx index d8a3aa632..fc4e5683d 100644 --- a/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx +++ b/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx @@ -188,7 +188,7 @@ const MinerReparentPicker = ({ onClose, onRefetchMiners, }: MinerReparentPickerProps) => { - const { reassignDevicesToSite } = useSites(); + const { assignDevicesToSite } = useSites(); const { addDevicesToDeviceSet, getDeviceSet, listRacks, removeDevicesFromDeviceSet } = useDeviceSets(); const [siteMoveConfirmation, setSiteMoveConfirmation] = useState(null); const [siteMoveInFlight, setSiteMoveInFlight] = useState(false); @@ -227,7 +227,7 @@ const MinerReparentPicker = ({ }); const dispatchSiteReassign = (targetSiteId: bigint, ids: string[]) => { - void reassignDevicesToSite({ + void assignDevicesToSite({ targetSiteId, deviceIdentifiers: ids, onSuccess: (count) => { diff --git a/client/src/protoFleet/features/fleetManagement/pages/FleetBuildingsPage.tsx b/client/src/protoFleet/features/fleetManagement/pages/FleetBuildingsPage.tsx index 9f1a53688..ed8dd3292 100644 --- a/client/src/protoFleet/features/fleetManagement/pages/FleetBuildingsPage.tsx +++ b/client/src/protoFleet/features/fleetManagement/pages/FleetBuildingsPage.tsx @@ -117,7 +117,7 @@ const FleetBuildingsPage = () => { [buildingModals, siteNameById], ); - const { assignBuildingToSite } = useSites(); + const { assignBuildingsToSite } = useSites(); const [reparentTarget, setReparentTarget] = useState(null); const handleAddBuildingToSite = useCallback((row: BuildingWithCounts) => setReparentTarget(row), []); @@ -251,8 +251,8 @@ const FleetBuildingsPage = () => { } const name = reparentTarget.building.name || "building"; const buildingId = reparentTarget.building.id; - void assignBuildingToSite({ - buildingId, + void assignBuildingsToSite({ + buildingIds: [buildingId], targetSiteId, onSuccess: () => { pushToast({ message: `Moved "${name}" to selected site.`, status: STATUSES.success }); diff --git a/proto/buildings/v1/buildings.proto b/proto/buildings/v1/buildings.proto index 664627320..7116bf70c 100644 --- a/proto/buildings/v1/buildings.proto +++ b/proto/buildings/v1/buildings.proto @@ -29,7 +29,7 @@ service BuildingService { // UpdateBuilding mutates name, description, capacity, layout // defaults. Site assignment is *not* changed by UpdateBuilding — - // use AssignBuildingToSite (SiteService) for that, since it has + // use AssignBuildingsToSite (SiteService) for that, since it has // cross-collection enforcement. rpc UpdateBuilding(UpdateBuildingRequest) returns (UpdateBuildingResponse); diff --git a/proto/sites/v1/sites.proto b/proto/sites/v1/sites.proto index 1f23c8887..7cb692fab 100644 --- a/proto/sites/v1/sites.proto +++ b/proto/sites/v1/sites.proto @@ -38,12 +38,14 @@ service SiteService { rpc AssignDevicesToSite(AssignDevicesToSiteRequest) returns (AssignDevicesToSiteResponse); - // AssignBuildingToSite moves a building to a different site - // (or to "Unassigned" when target_site_id is unset). The same - // transaction cascades site_id down to the building's racks and - // their devices. Returns the cascade counts. - rpc AssignBuildingToSite(AssignBuildingToSiteRequest) - returns (AssignBuildingToSiteResponse); + // AssignBuildingsToSite moves one or more buildings to a target site + // (or to "Unassigned" when target_site_id is unset). All updates run + // in a single transaction; if any building fails, the batch rolls + // back. The same transaction cascades site_id down to each + // building's racks and their devices. Returns the aggregate cascade + // counts across every building in the batch. + rpc AssignBuildingsToSite(AssignBuildingsToSiteRequest) + returns (AssignBuildingsToSiteResponse); // GetSiteStats returns server-rolled telemetry + miner-state counts // for every device assigned to the site, including devices whose @@ -218,17 +220,22 @@ message AssignDevicesToSiteResponse { repeated PerDeviceConflict conflicts = 2; } -// AssignBuildingToSite +// AssignBuildingsToSite -message AssignBuildingToSiteRequest { - int64 building_id = 1 [(buf.validate.field).int64.gt = 0]; - // Unset = move building to "Unassigned". When present, must be > 0. +message AssignBuildingsToSiteRequest { + repeated int64 building_ids = 1 [(buf.validate.field).repeated = { + min_items: 1 + max_items: 1000 + items: {int64: {gt: 0}} + }]; + // Unset = move buildings to "Unassigned". When present, must be > 0. optional int64 target_site_id = 2 [(buf.validate.field).int64.gt = 0]; } -message AssignBuildingToSiteResponse { +message AssignBuildingsToSiteResponse { // Cascade impact: how many racks and devices were re-stamped with - // the new site_id in the same transaction. + // the new site_id in the same transaction, aggregated across every + // building in the batch. int64 reassigned_rack_count = 1; int64 reassigned_device_count = 2; } diff --git a/server/generated/grpc/sites/v1/sites.pb.go b/server/generated/grpc/sites/v1/sites.pb.go index 60b36bcf3..65d669114 100644 --- a/server/generated/grpc/sites/v1/sites.pb.go +++ b/server/generated/grpc/sites/v1/sites.pb.go @@ -1011,29 +1011,29 @@ func (x *AssignDevicesToSiteResponse) GetConflicts() []*PerDeviceConflict { return nil } -type AssignBuildingToSiteRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - BuildingId int64 `protobuf:"varint,1,opt,name=building_id,json=buildingId,proto3" json:"building_id,omitempty"` - // Unset = move building to "Unassigned". When present, must be > 0. +type AssignBuildingsToSiteRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + BuildingIds []int64 `protobuf:"varint,1,rep,packed,name=building_ids,json=buildingIds,proto3" json:"building_ids,omitempty"` + // Unset = move buildings to "Unassigned". When present, must be > 0. TargetSiteId *int64 `protobuf:"varint,2,opt,name=target_site_id,json=targetSiteId,proto3,oneof" json:"target_site_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AssignBuildingToSiteRequest) Reset() { - *x = AssignBuildingToSiteRequest{} +func (x *AssignBuildingsToSiteRequest) Reset() { + *x = AssignBuildingsToSiteRequest{} mi := &file_sites_v1_sites_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AssignBuildingToSiteRequest) String() string { +func (x *AssignBuildingsToSiteRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AssignBuildingToSiteRequest) ProtoMessage() {} +func (*AssignBuildingsToSiteRequest) ProtoMessage() {} -func (x *AssignBuildingToSiteRequest) ProtoReflect() protoreflect.Message { +func (x *AssignBuildingsToSiteRequest) ProtoReflect() protoreflect.Message { mi := &file_sites_v1_sites_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1045,49 +1045,50 @@ func (x *AssignBuildingToSiteRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AssignBuildingToSiteRequest.ProtoReflect.Descriptor instead. -func (*AssignBuildingToSiteRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use AssignBuildingsToSiteRequest.ProtoReflect.Descriptor instead. +func (*AssignBuildingsToSiteRequest) Descriptor() ([]byte, []int) { return file_sites_v1_sites_proto_rawDescGZIP(), []int{13} } -func (x *AssignBuildingToSiteRequest) GetBuildingId() int64 { +func (x *AssignBuildingsToSiteRequest) GetBuildingIds() []int64 { if x != nil { - return x.BuildingId + return x.BuildingIds } - return 0 + return nil } -func (x *AssignBuildingToSiteRequest) GetTargetSiteId() int64 { +func (x *AssignBuildingsToSiteRequest) GetTargetSiteId() int64 { if x != nil && x.TargetSiteId != nil { return *x.TargetSiteId } return 0 } -type AssignBuildingToSiteResponse struct { +type AssignBuildingsToSiteResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // Cascade impact: how many racks and devices were re-stamped with - // the new site_id in the same transaction. + // the new site_id in the same transaction, aggregated across every + // building in the batch. ReassignedRackCount int64 `protobuf:"varint,1,opt,name=reassigned_rack_count,json=reassignedRackCount,proto3" json:"reassigned_rack_count,omitempty"` ReassignedDeviceCount int64 `protobuf:"varint,2,opt,name=reassigned_device_count,json=reassignedDeviceCount,proto3" json:"reassigned_device_count,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AssignBuildingToSiteResponse) Reset() { - *x = AssignBuildingToSiteResponse{} +func (x *AssignBuildingsToSiteResponse) Reset() { + *x = AssignBuildingsToSiteResponse{} mi := &file_sites_v1_sites_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AssignBuildingToSiteResponse) String() string { +func (x *AssignBuildingsToSiteResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AssignBuildingToSiteResponse) ProtoMessage() {} +func (*AssignBuildingsToSiteResponse) ProtoMessage() {} -func (x *AssignBuildingToSiteResponse) ProtoReflect() protoreflect.Message { +func (x *AssignBuildingsToSiteResponse) ProtoReflect() protoreflect.Message { mi := &file_sites_v1_sites_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1099,19 +1100,19 @@ func (x *AssignBuildingToSiteResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AssignBuildingToSiteResponse.ProtoReflect.Descriptor instead. -func (*AssignBuildingToSiteResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use AssignBuildingsToSiteResponse.ProtoReflect.Descriptor instead. +func (*AssignBuildingsToSiteResponse) Descriptor() ([]byte, []int) { return file_sites_v1_sites_proto_rawDescGZIP(), []int{14} } -func (x *AssignBuildingToSiteResponse) GetReassignedRackCount() int64 { +func (x *AssignBuildingsToSiteResponse) GetReassignedRackCount() int64 { if x != nil { return x.ReassignedRackCount } return 0 } -func (x *AssignBuildingToSiteResponse) GetReassignedDeviceCount() int64 { +func (x *AssignBuildingsToSiteResponse) GetReassignedDeviceCount() int64 { if x != nil { return x.ReassignedDeviceCount } @@ -1496,125 +1497,126 @@ var file_sites_v1_sites_proto_rawDesc = string([]byte{ 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x22, - 0x8e, 0x01, 0x0a, 0x1b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, - 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x28, 0x0a, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0a, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, - 0x0f, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, - 0x22, 0x8a, 0x01, 0x0a, 0x1c, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, - 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, - 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x13, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x61, 0x63, 0x6b, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, - 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, - 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x37, 0x0a, - 0x13, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x06, - 0x73, 0x69, 0x74, 0x65, 0x49, 0x64, 0x22, 0xe4, 0x04, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x69, - 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x17, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x73, 0x69, 0x74, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, - 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x61, - 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x67, - 0x5f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6a, 0x74, 0x68, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, 0x45, 0x66, 0x66, 0x69, 0x63, 0x69, - 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x23, 0x0a, - 0x0d, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, - 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, - 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x65, - 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x18, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6f, 0x77, - 0x65, 0x72, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0xb3, 0x01, - 0x0a, 0x17, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, - 0x69, 0x63, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x26, 0x50, 0x45, 0x52, - 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, - 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, - 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, - 0x53, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, - 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x01, 0x12, 0x3b, 0x0a, 0x37, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, - 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, - 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x52, - 0x41, 0x43, 0x4b, 0x5f, 0x41, 0x54, 0x5f, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x5f, 0x53, 0x49, 0x54, - 0x45, 0x10, 0x02, 0x32, 0xc8, 0x04, 0x0a, 0x0b, 0x53, 0x69, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, - 0x12, 0x1a, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x73, - 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x9b, 0x01, 0x0a, 0x1c, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, + 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x34, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x42, 0x11, 0xba, 0x48, 0x0e, 0x92, 0x01, 0x0b, 0x08, 0x01, + 0x10, 0xe8, 0x07, 0x22, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x69, 0x6e, 0x67, 0x49, 0x64, 0x73, 0x12, 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, + 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x22, 0x8b, 0x01, + 0x0a, 0x1d, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, + 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x32, 0x0a, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x61, + 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, + 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x61, 0x63, 0x6b, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x37, 0x0a, 0x13, 0x47, + 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x06, 0x73, 0x69, + 0x74, 0x65, 0x49, 0x64, 0x22, 0xe4, 0x04, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, + 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, + 0x73, 0x69, 0x74, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, + 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x61, 0x73, 0x68, + 0x72, 0x61, 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x67, 0x5f, 0x65, + 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6a, 0x74, 0x68, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, 0x45, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, + 0x63, 0x79, 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, + 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x23, 0x0a, 0x0d, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, + 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, 0x65, 0x65, + 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x66, 0x66, + 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, + 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x65, + 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6f, 0x77, 0x65, 0x72, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0xb3, 0x01, 0x0a, 0x17, + 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, + 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x26, 0x50, 0x45, 0x52, 0x5f, 0x44, + 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, + 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, + 0x4e, 0x44, 0x10, 0x01, 0x12, 0x3b, 0x0a, 0x37, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, 0x49, + 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x52, 0x41, 0x43, + 0x4b, 0x5f, 0x41, 0x54, 0x5f, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x5f, 0x53, 0x49, 0x54, 0x45, 0x10, + 0x02, 0x32, 0xcb, 0x04, 0x0a, 0x0b, 0x53, 0x69, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x44, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x12, 0x1a, + 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, + 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x73, 0x69, 0x74, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x47, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, + 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, + 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, - 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, - 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, - 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x73, 0x69, - 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x25, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, - 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x14, 0x41, 0x73, 0x73, 0x69, - 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, - 0x12, 0x25, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, - 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x62, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x73, 0x69, 0x74, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x25, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x15, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, + 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, + 0x26, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, - 0x67, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, - 0x1d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, - 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, - 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xa0, - 0x01, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x42, - 0x0a, 0x53, 0x69, 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x43, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, - 0x63, 0x2f, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x73, 0x69, 0x74, 0x65, 0x73, - 0x76, 0x31, 0xa2, 0x02, 0x03, 0x53, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, - 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, - 0x14, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x53, 0x69, 0x74, 0x65, 0x73, 0x3a, 0x3a, 0x56, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, + 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1e, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, + 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0xa0, 0x01, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x42, 0x0a, 0x53, 0x69, 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x43, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, + 0x70, 0x63, 0x2f, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x73, 0x69, 0x74, 0x65, + 0x73, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x53, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, + 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0xe2, + 0x02, 0x14, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x53, 0x69, 0x74, 0x65, 0x73, 0x3a, 0x3a, + 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -1632,25 +1634,25 @@ func file_sites_v1_sites_proto_rawDescGZIP() []byte { var file_sites_v1_sites_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_sites_v1_sites_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_sites_v1_sites_proto_goTypes = []any{ - (PerDeviceConflictReason)(0), // 0: sites.v1.PerDeviceConflictReason - (*Site)(nil), // 1: sites.v1.Site - (*SiteWithCounts)(nil), // 2: sites.v1.SiteWithCounts - (*ListSitesRequest)(nil), // 3: sites.v1.ListSitesRequest - (*ListSitesResponse)(nil), // 4: sites.v1.ListSitesResponse - (*CreateSiteRequest)(nil), // 5: sites.v1.CreateSiteRequest - (*CreateSiteResponse)(nil), // 6: sites.v1.CreateSiteResponse - (*UpdateSiteRequest)(nil), // 7: sites.v1.UpdateSiteRequest - (*UpdateSiteResponse)(nil), // 8: sites.v1.UpdateSiteResponse - (*DeleteSiteRequest)(nil), // 9: sites.v1.DeleteSiteRequest - (*DeleteSiteResponse)(nil), // 10: sites.v1.DeleteSiteResponse - (*AssignDevicesToSiteRequest)(nil), // 11: sites.v1.AssignDevicesToSiteRequest - (*PerDeviceConflict)(nil), // 12: sites.v1.PerDeviceConflict - (*AssignDevicesToSiteResponse)(nil), // 13: sites.v1.AssignDevicesToSiteResponse - (*AssignBuildingToSiteRequest)(nil), // 14: sites.v1.AssignBuildingToSiteRequest - (*AssignBuildingToSiteResponse)(nil), // 15: sites.v1.AssignBuildingToSiteResponse - (*GetSiteStatsRequest)(nil), // 16: sites.v1.GetSiteStatsRequest - (*GetSiteStatsResponse)(nil), // 17: sites.v1.GetSiteStatsResponse - (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp + (PerDeviceConflictReason)(0), // 0: sites.v1.PerDeviceConflictReason + (*Site)(nil), // 1: sites.v1.Site + (*SiteWithCounts)(nil), // 2: sites.v1.SiteWithCounts + (*ListSitesRequest)(nil), // 3: sites.v1.ListSitesRequest + (*ListSitesResponse)(nil), // 4: sites.v1.ListSitesResponse + (*CreateSiteRequest)(nil), // 5: sites.v1.CreateSiteRequest + (*CreateSiteResponse)(nil), // 6: sites.v1.CreateSiteResponse + (*UpdateSiteRequest)(nil), // 7: sites.v1.UpdateSiteRequest + (*UpdateSiteResponse)(nil), // 8: sites.v1.UpdateSiteResponse + (*DeleteSiteRequest)(nil), // 9: sites.v1.DeleteSiteRequest + (*DeleteSiteResponse)(nil), // 10: sites.v1.DeleteSiteResponse + (*AssignDevicesToSiteRequest)(nil), // 11: sites.v1.AssignDevicesToSiteRequest + (*PerDeviceConflict)(nil), // 12: sites.v1.PerDeviceConflict + (*AssignDevicesToSiteResponse)(nil), // 13: sites.v1.AssignDevicesToSiteResponse + (*AssignBuildingsToSiteRequest)(nil), // 14: sites.v1.AssignBuildingsToSiteRequest + (*AssignBuildingsToSiteResponse)(nil), // 15: sites.v1.AssignBuildingsToSiteResponse + (*GetSiteStatsRequest)(nil), // 16: sites.v1.GetSiteStatsRequest + (*GetSiteStatsResponse)(nil), // 17: sites.v1.GetSiteStatsResponse + (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp } var file_sites_v1_sites_proto_depIdxs = []int32{ 18, // 0: sites.v1.Site.created_at:type_name -> google.protobuf.Timestamp @@ -1666,14 +1668,14 @@ var file_sites_v1_sites_proto_depIdxs = []int32{ 7, // 10: sites.v1.SiteService.UpdateSite:input_type -> sites.v1.UpdateSiteRequest 9, // 11: sites.v1.SiteService.DeleteSite:input_type -> sites.v1.DeleteSiteRequest 11, // 12: sites.v1.SiteService.AssignDevicesToSite:input_type -> sites.v1.AssignDevicesToSiteRequest - 14, // 13: sites.v1.SiteService.AssignBuildingToSite:input_type -> sites.v1.AssignBuildingToSiteRequest + 14, // 13: sites.v1.SiteService.AssignBuildingsToSite:input_type -> sites.v1.AssignBuildingsToSiteRequest 16, // 14: sites.v1.SiteService.GetSiteStats:input_type -> sites.v1.GetSiteStatsRequest 4, // 15: sites.v1.SiteService.ListSites:output_type -> sites.v1.ListSitesResponse 6, // 16: sites.v1.SiteService.CreateSite:output_type -> sites.v1.CreateSiteResponse 8, // 17: sites.v1.SiteService.UpdateSite:output_type -> sites.v1.UpdateSiteResponse 10, // 18: sites.v1.SiteService.DeleteSite:output_type -> sites.v1.DeleteSiteResponse 13, // 19: sites.v1.SiteService.AssignDevicesToSite:output_type -> sites.v1.AssignDevicesToSiteResponse - 15, // 20: sites.v1.SiteService.AssignBuildingToSite:output_type -> sites.v1.AssignBuildingToSiteResponse + 15, // 20: sites.v1.SiteService.AssignBuildingsToSite:output_type -> sites.v1.AssignBuildingsToSiteResponse 17, // 21: sites.v1.SiteService.GetSiteStats:output_type -> sites.v1.GetSiteStatsResponse 15, // [15:22] is the sub-list for method output_type 8, // [8:15] is the sub-list for method input_type diff --git a/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go b/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go index 9c033eb4f..1c67f49ec 100644 --- a/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go +++ b/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go @@ -45,9 +45,9 @@ const ( // SiteServiceAssignDevicesToSiteProcedure is the fully-qualified name of the SiteService's // AssignDevicesToSite RPC. SiteServiceAssignDevicesToSiteProcedure = "/sites.v1.SiteService/AssignDevicesToSite" - // SiteServiceAssignBuildingToSiteProcedure is the fully-qualified name of the SiteService's - // AssignBuildingToSite RPC. - SiteServiceAssignBuildingToSiteProcedure = "/sites.v1.SiteService/AssignBuildingToSite" + // SiteServiceAssignBuildingsToSiteProcedure is the fully-qualified name of the SiteService's + // AssignBuildingsToSite RPC. + SiteServiceAssignBuildingsToSiteProcedure = "/sites.v1.SiteService/AssignBuildingsToSite" // SiteServiceGetSiteStatsProcedure is the fully-qualified name of the SiteService's GetSiteStats // RPC. SiteServiceGetSiteStatsProcedure = "/sites.v1.SiteService/GetSiteStats" @@ -78,11 +78,13 @@ type SiteServiceClient interface { // details and no row is touched. Omit target_site_id (or leave it // unset) to move devices to the "Unassigned" bucket. AssignDevicesToSite(context.Context, *connect.Request[v1.AssignDevicesToSiteRequest]) (*connect.Response[v1.AssignDevicesToSiteResponse], error) - // AssignBuildingToSite moves a building to a different site - // (or to "Unassigned" when target_site_id is unset). The same - // transaction cascades site_id down to the building's racks and - // their devices. Returns the cascade counts. - AssignBuildingToSite(context.Context, *connect.Request[v1.AssignBuildingToSiteRequest]) (*connect.Response[v1.AssignBuildingToSiteResponse], error) + // AssignBuildingsToSite moves one or more buildings to a target site + // (or to "Unassigned" when target_site_id is unset). All updates run + // in a single transaction; if any building fails, the batch rolls + // back. The same transaction cascades site_id down to each + // building's racks and their devices. Returns the aggregate cascade + // counts across every building in the batch. + AssignBuildingsToSite(context.Context, *connect.Request[v1.AssignBuildingsToSiteRequest]) (*connect.Response[v1.AssignBuildingsToSiteResponse], error) // GetSiteStats returns server-rolled telemetry + miner-state counts // for every device assigned to the site, including devices whose // rack has no building set and devices that have no rack at all. @@ -125,9 +127,9 @@ func NewSiteServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. baseURL+SiteServiceAssignDevicesToSiteProcedure, opts..., ), - assignBuildingToSite: connect.NewClient[v1.AssignBuildingToSiteRequest, v1.AssignBuildingToSiteResponse]( + assignBuildingsToSite: connect.NewClient[v1.AssignBuildingsToSiteRequest, v1.AssignBuildingsToSiteResponse]( httpClient, - baseURL+SiteServiceAssignBuildingToSiteProcedure, + baseURL+SiteServiceAssignBuildingsToSiteProcedure, opts..., ), getSiteStats: connect.NewClient[v1.GetSiteStatsRequest, v1.GetSiteStatsResponse]( @@ -140,13 +142,13 @@ func NewSiteServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. // siteServiceClient implements SiteServiceClient. type siteServiceClient struct { - listSites *connect.Client[v1.ListSitesRequest, v1.ListSitesResponse] - createSite *connect.Client[v1.CreateSiteRequest, v1.CreateSiteResponse] - updateSite *connect.Client[v1.UpdateSiteRequest, v1.UpdateSiteResponse] - deleteSite *connect.Client[v1.DeleteSiteRequest, v1.DeleteSiteResponse] - assignDevicesToSite *connect.Client[v1.AssignDevicesToSiteRequest, v1.AssignDevicesToSiteResponse] - assignBuildingToSite *connect.Client[v1.AssignBuildingToSiteRequest, v1.AssignBuildingToSiteResponse] - getSiteStats *connect.Client[v1.GetSiteStatsRequest, v1.GetSiteStatsResponse] + listSites *connect.Client[v1.ListSitesRequest, v1.ListSitesResponse] + createSite *connect.Client[v1.CreateSiteRequest, v1.CreateSiteResponse] + updateSite *connect.Client[v1.UpdateSiteRequest, v1.UpdateSiteResponse] + deleteSite *connect.Client[v1.DeleteSiteRequest, v1.DeleteSiteResponse] + assignDevicesToSite *connect.Client[v1.AssignDevicesToSiteRequest, v1.AssignDevicesToSiteResponse] + assignBuildingsToSite *connect.Client[v1.AssignBuildingsToSiteRequest, v1.AssignBuildingsToSiteResponse] + getSiteStats *connect.Client[v1.GetSiteStatsRequest, v1.GetSiteStatsResponse] } // ListSites calls sites.v1.SiteService.ListSites. @@ -174,9 +176,9 @@ func (c *siteServiceClient) AssignDevicesToSite(ctx context.Context, req *connec return c.assignDevicesToSite.CallUnary(ctx, req) } -// AssignBuildingToSite calls sites.v1.SiteService.AssignBuildingToSite. -func (c *siteServiceClient) AssignBuildingToSite(ctx context.Context, req *connect.Request[v1.AssignBuildingToSiteRequest]) (*connect.Response[v1.AssignBuildingToSiteResponse], error) { - return c.assignBuildingToSite.CallUnary(ctx, req) +// AssignBuildingsToSite calls sites.v1.SiteService.AssignBuildingsToSite. +func (c *siteServiceClient) AssignBuildingsToSite(ctx context.Context, req *connect.Request[v1.AssignBuildingsToSiteRequest]) (*connect.Response[v1.AssignBuildingsToSiteResponse], error) { + return c.assignBuildingsToSite.CallUnary(ctx, req) } // GetSiteStats calls sites.v1.SiteService.GetSiteStats. @@ -209,11 +211,13 @@ type SiteServiceHandler interface { // details and no row is touched. Omit target_site_id (or leave it // unset) to move devices to the "Unassigned" bucket. AssignDevicesToSite(context.Context, *connect.Request[v1.AssignDevicesToSiteRequest]) (*connect.Response[v1.AssignDevicesToSiteResponse], error) - // AssignBuildingToSite moves a building to a different site - // (or to "Unassigned" when target_site_id is unset). The same - // transaction cascades site_id down to the building's racks and - // their devices. Returns the cascade counts. - AssignBuildingToSite(context.Context, *connect.Request[v1.AssignBuildingToSiteRequest]) (*connect.Response[v1.AssignBuildingToSiteResponse], error) + // AssignBuildingsToSite moves one or more buildings to a target site + // (or to "Unassigned" when target_site_id is unset). All updates run + // in a single transaction; if any building fails, the batch rolls + // back. The same transaction cascades site_id down to each + // building's racks and their devices. Returns the aggregate cascade + // counts across every building in the batch. + AssignBuildingsToSite(context.Context, *connect.Request[v1.AssignBuildingsToSiteRequest]) (*connect.Response[v1.AssignBuildingsToSiteResponse], error) // GetSiteStats returns server-rolled telemetry + miner-state counts // for every device assigned to the site, including devices whose // rack has no building set and devices that have no rack at all. @@ -252,9 +256,9 @@ func NewSiteServiceHandler(svc SiteServiceHandler, opts ...connect.HandlerOption svc.AssignDevicesToSite, opts..., ) - siteServiceAssignBuildingToSiteHandler := connect.NewUnaryHandler( - SiteServiceAssignBuildingToSiteProcedure, - svc.AssignBuildingToSite, + siteServiceAssignBuildingsToSiteHandler := connect.NewUnaryHandler( + SiteServiceAssignBuildingsToSiteProcedure, + svc.AssignBuildingsToSite, opts..., ) siteServiceGetSiteStatsHandler := connect.NewUnaryHandler( @@ -274,8 +278,8 @@ func NewSiteServiceHandler(svc SiteServiceHandler, opts ...connect.HandlerOption siteServiceDeleteSiteHandler.ServeHTTP(w, r) case SiteServiceAssignDevicesToSiteProcedure: siteServiceAssignDevicesToSiteHandler.ServeHTTP(w, r) - case SiteServiceAssignBuildingToSiteProcedure: - siteServiceAssignBuildingToSiteHandler.ServeHTTP(w, r) + case SiteServiceAssignBuildingsToSiteProcedure: + siteServiceAssignBuildingsToSiteHandler.ServeHTTP(w, r) case SiteServiceGetSiteStatsProcedure: siteServiceGetSiteStatsHandler.ServeHTTP(w, r) default: @@ -307,8 +311,8 @@ func (UnimplementedSiteServiceHandler) AssignDevicesToSite(context.Context, *con return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sites.v1.SiteService.AssignDevicesToSite is not implemented")) } -func (UnimplementedSiteServiceHandler) AssignBuildingToSite(context.Context, *connect.Request[v1.AssignBuildingToSiteRequest]) (*connect.Response[v1.AssignBuildingToSiteResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sites.v1.SiteService.AssignBuildingToSite is not implemented")) +func (UnimplementedSiteServiceHandler) AssignBuildingsToSite(context.Context, *connect.Request[v1.AssignBuildingsToSiteRequest]) (*connect.Response[v1.AssignBuildingsToSiteResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sites.v1.SiteService.AssignBuildingsToSite is not implemented")) } func (UnimplementedSiteServiceHandler) GetSiteStats(context.Context, *connect.Request[v1.GetSiteStatsRequest]) (*connect.Response[v1.GetSiteStatsResponse], error) { diff --git a/server/internal/domain/buildings/models/models.go b/server/internal/domain/buildings/models/models.go index a3d0ffd53..c17be9181 100644 --- a/server/internal/domain/buildings/models/models.go +++ b/server/internal/domain/buildings/models/models.go @@ -67,7 +67,7 @@ type CreateParams struct { // UpdateParams is the input shape for building updates. SiteID is // intentionally NOT updated here; that flow lives on -// SiteService.AssignBuildingToSite, which carries the cross-collection +// SiteService.AssignBuildingsToSite, which carries the cross-collection // invariant check. type UpdateParams struct { OrgID int64 diff --git a/server/internal/domain/buildings/service.go b/server/internal/domain/buildings/service.go index b2af855cc..c4a486ee5 100644 --- a/server/internal/domain/buildings/service.go +++ b/server/internal/domain/buildings/service.go @@ -1,6 +1,6 @@ // Package buildings is the domain layer for the BuildingService RPC // surface. CRUD + cascade-unassign-on-delete; site assignment lives on -// SiteService.AssignBuildingToSite where the cross-collection +// SiteService.AssignBuildingsToSite where the cross-collection // invariant is enforced. package buildings @@ -531,7 +531,7 @@ func (s *Service) DeleteBuilding(ctx context.Context, orgID, id int64) (*models. // the org. // // `expectedSiteID` carries the site the handler resolved at authz time: -// if a concurrent AssignBuildingToSite moves the building between the +// if a concurrent AssignBuildingsToSite moves the building between the // handler's pre-authz lookup and this read, the building's current // site will diverge from what the caller was authorized for. We // surface that as NotFound rather than leaking telemetry into the @@ -587,7 +587,7 @@ func (s *Service) GetBuildingStats(ctx context.Context, orgID, buildingID int64, if err != nil { return nil, err } - // Guard against the AssignBuildingToSite race: if the building has + // Guard against the AssignBuildingsToSite race: if the building has // moved to a different site since the handler's pre-authz lookup, // the permission grant we ran against doesn't match the current // scope. NotFound is the safe surface here — the caller was never @@ -663,7 +663,7 @@ func (s *Service) GetBuildingStats(ctx context.Context, orgID, buildingID int64, // Pass PAIRED + AUTHENTICATION_NEEDED explicitly so the stats roll-up // counts AUTH_NEEDED devices the same way the miner list does. // - // Also constrain by expectedSiteID so a concurrent AssignBuildingToSite + // Also constrain by expectedSiteID so a concurrent AssignBuildingsToSite // that commits between the building re-read and the device fetch can't // leak the new site's device set: the cascade stamps device.site_id // onto every device under the moved building, so requiring @@ -729,7 +729,7 @@ func (s *Service) GetBuildingStats(ctx context.Context, orgID, buildingID int64, // Belt-and-braces: re-read the building after all the rollup queries. // The device fetch is already scoped to expectedSiteID, but the rack // and per-rack state queries join on building_id alone — if - // AssignBuildingToSite committed between the initial GetBuilding check + // AssignBuildingsToSite committed between the initial GetBuilding check // and these reads, the rack/state data would still be that of the // moved building (which now belongs to a site the caller wasn't // authorized for). Catch that here and surface NotFound rather than diff --git a/server/internal/domain/buildings/service_stats_test.go b/server/internal/domain/buildings/service_stats_test.go index 6d520f2ab..52269a8f5 100644 --- a/server/internal/domain/buildings/service_stats_test.go +++ b/server/internal/domain/buildings/service_stats_test.go @@ -72,7 +72,7 @@ func TestGetBuildingStats_notFound(t *testing.T) { func TestGetBuildingStats_notFoundWhenSiteMovedDuringAuthz(t *testing.T) { // Race: handler resolved the building at site A, but a concurrent - // AssignBuildingToSite moved it to site B before the service read. + // AssignBuildingsToSite moved it to site B before the service read. // Expectation: surface NotFound rather than return stats the caller // wasn't authorized for in the new site-scope. ctrl := gomock.NewController(t) @@ -93,7 +93,7 @@ func TestGetBuildingStats_notFoundWhenSiteMovedDuringAuthz(t *testing.T) { func TestGetBuildingStats_notFoundWhenSiteMovedAfterReads(t *testing.T) { // Sharper race: handler + initial site read both saw site A, so the - // rollup proceeded. AssignBuildingToSite then commits to site B + // rollup proceeded. AssignBuildingsToSite then commits to site B // before the service hits its post-read re-check. Expectation: the // post-read guard catches the move and returns NotFound rather than // handing the caller a snapshot built under stale authz. diff --git a/server/internal/domain/collection/service_test.go b/server/internal/domain/collection/service_test.go index ed161681f..1d505166a 100644 --- a/server/internal/domain/collection/service_test.go +++ b/server/internal/domain/collection/service_test.go @@ -1536,7 +1536,7 @@ func TestService_SaveRack_MoveBetweenBuildingsCascadesSite(t *testing.T) { mockStore.EXPECT().GetCollectionType(gomock.Any(), testOrgID, collectionID).Return(pb.CollectionType_COLLECTION_TYPE_RACK, nil) // resolveAndLockRackPlacement peeks building→site, locks site, locks // building, then re-reads building→site under the lock to detect - // concurrent AssignBuildingToSite. Both reads return the same value + // concurrent AssignBuildingsToSite. Both reads return the same value // here, so the tx proceeds without abort/retry. mockStore.EXPECT().GetBuildingSite(gomock.Any(), testOrgID, newBuilding).Return(&newSiteID, nil).Times(2) mockSiteStore.EXPECT().LockSiteForWrite(gomock.Any(), testOrgID, newSiteID).Return(nil) diff --git a/server/internal/domain/sites/models/models.go b/server/internal/domain/sites/models/models.go index 8863a8144..c23776bf0 100644 --- a/server/internal/domain/sites/models/models.go +++ b/server/internal/domain/sites/models/models.go @@ -103,17 +103,18 @@ type AssignDevicesToSiteParams struct { DeviceIdentifiers []string } -// AssignBuildingToSiteParams is the input shape for the building site -// reassignment flow. TargetSiteID == nil means "Unassigned". -type AssignBuildingToSiteParams struct { +// AssignBuildingsToSiteParams is the input shape for the bulk +// building→site assignment flow. TargetSiteID == nil means "Unassigned"; +// the entire batch is applied in one transaction. +type AssignBuildingsToSiteParams struct { OrgID int64 - BuildingID int64 + BuildingIDs []int64 TargetSiteID *int64 } -// AssignBuildingToSiteResult is the cascade-impact tally for the -// building → site move. -type AssignBuildingToSiteResult struct { +// AssignBuildingsToSiteResult is the aggregate cascade-impact tally +// across every building in the batch. +type AssignBuildingsToSiteResult struct { ReassignedRackCount int64 ReassignedDeviceCount int64 } diff --git a/server/internal/domain/sites/service.go b/server/internal/domain/sites/service.go index 3e92e6b30..0a493bc53 100644 --- a/server/internal/domain/sites/service.go +++ b/server/internal/domain/sites/service.go @@ -361,11 +361,20 @@ func (s *Service) AssignDevicesToSite(ctx context.Context, params models.AssignD return rowsAffected, nil, nil } -// AssignBuildingToSite moves a building to a different site (or to -// "Unassigned" when TargetSiteID is nil) and cascades site_id down to -// the building's racks and their devices in one transaction. Returns -// the cascade counts. -func (s *Service) AssignBuildingToSite(ctx context.Context, params models.AssignBuildingToSiteParams) (*models.AssignBuildingToSiteResult, error) { +// AssignBuildingsToSite moves one or more buildings to a target site +// (or to "Unassigned" when TargetSiteID is nil) and cascades site_id +// down to each building's racks and their devices. Everything runs in +// one transaction; if any building fails, the batch rolls back. +// Returns the aggregate cascade counts across every building. +func (s *Service) AssignBuildingsToSite(ctx context.Context, params models.AssignBuildingsToSiteParams) (*models.AssignBuildingsToSiteResult, error) { + buildingIDs := dedupeInt64s(params.BuildingIDs) + if len(buildingIDs) == 0 { + return nil, fleeterror.NewInvalidArgumentError("building_ids must not be empty") + } + // Sort for stable lock order: deadlock-safe against concurrent + // AssignBuildingsToSite touching an overlapping building set. + sort.Slice(buildingIDs, func(i, j int) bool { return buildingIDs[i] < buildingIDs[j] }) + var ( rackCount int64 deviceCount int64 @@ -381,26 +390,30 @@ func (s *Service) AssignBuildingToSite(ctx context.Context, params models.Assign return err } } - // Lock the building so a concurrent DeleteSite that owns the - // source site can't clear this building's racks while we - // reassign them. Same site→building lock order DeleteSite uses. - if err := s.store.LockBuildingForWrite(txCtx, params.OrgID, params.BuildingID); err != nil { - return err - } - rowsAffected, err := s.store.AssignBuildingToSite(txCtx, params.OrgID, params.BuildingID, params.TargetSiteID) - if err != nil { - return err - } - if rowsAffected == 0 { - return fleeterror.NewNotFoundErrorf("building %d not found", params.BuildingID) - } - rackCount, err = s.store.ReassignRacksUnderBuilding(txCtx, params.OrgID, params.BuildingID, params.TargetSiteID) - if err != nil { - return err - } - deviceCount, err = s.store.ReassignDevicesUnderBuilding(txCtx, params.OrgID, params.BuildingID, params.TargetSiteID) - if err != nil { - return err + for _, buildingID := range buildingIDs { + // Lock the building so a concurrent DeleteSite that owns the + // source site can't clear this building's racks while we + // reassign them. Same site→building lock order DeleteSite uses. + if err := s.store.LockBuildingForWrite(txCtx, params.OrgID, buildingID); err != nil { + return err + } + rowsAffected, err := s.store.AssignBuildingToSite(txCtx, params.OrgID, buildingID, params.TargetSiteID) + if err != nil { + return err + } + if rowsAffected == 0 { + return fleeterror.NewNotFoundErrorf("building %d not found", buildingID) + } + racks, err := s.store.ReassignRacksUnderBuilding(txCtx, params.OrgID, buildingID, params.TargetSiteID) + if err != nil { + return err + } + rackCount += racks + devices, err := s.store.ReassignDevicesUnderBuilding(txCtx, params.OrgID, buildingID, params.TargetSiteID) + if err != nil { + return err + } + deviceCount += devices } return nil }) @@ -409,18 +422,17 @@ func (s *Service) AssignBuildingToSite(ctx context.Context, params models.Assign } orgIDVal := params.OrgID - buildingIDVal := params.BuildingID event := activitymodels.Event{ Category: activitymodels.CategoryFleetManagement, Type: eventBuildingAssignedToSite, OrganizationID: &orgIDVal, SiteID: params.TargetSiteID, Description: fmt.Sprintf( - "Assigned building %d to site %s (%d racks, %d devices cascaded)", - buildingIDVal, formatSiteIDForDescription(params.TargetSiteID), rackCount, deviceCount, + "Assigned %d building(s) to site %s (%d racks, %d devices cascaded)", + len(buildingIDs), formatSiteIDForDescription(params.TargetSiteID), rackCount, deviceCount, ), Metadata: map[string]any{ - "building_id": buildingIDVal, + "building_ids": buildingIDs, "target_site_id": params.TargetSiteID, "reassigned_rack_count": rackCount, "reassigned_device_count": deviceCount, @@ -429,7 +441,7 @@ func (s *Service) AssignBuildingToSite(ctx context.Context, params models.Assign activity.StampActor(ctx, &event) s.activitySvc.Log(ctx, event) - return &models.AssignBuildingToSiteResult{ + return &models.AssignBuildingsToSiteResult{ ReassignedRackCount: rackCount, ReassignedDeviceCount: deviceCount, }, nil @@ -529,6 +541,19 @@ func dedupeStrings(in []string) []string { return out } +func dedupeInt64s(in []int64) []int64 { + seen := make(map[int64]struct{}, len(in)) + out := make([]int64, 0, len(in)) + for _, v := range in { + if _, ok := seen[v]; ok { + continue + } + seen[v] = struct{}{} + out = append(out, v) + } + return out +} + func formatSiteIDForDescription(target *int64) string { if target == nil { return "Unassigned" diff --git a/server/internal/domain/sites/service_test.go b/server/internal/domain/sites/service_test.go index 7cfd9e3b8..a16c78bf3 100644 --- a/server/internal/domain/sites/service_test.go +++ b/server/internal/domain/sites/service_test.go @@ -339,7 +339,7 @@ func TestAssignDevicesToSite_targetMatchesCurrentRackSiteIsNotAConflict(t *testi } } -func TestAssignBuildingToSite_cascadeOnSuccess(t *testing.T) { +func TestAssignBuildingsToSite_cascadeOnSuccess(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} @@ -357,9 +357,9 @@ func TestAssignBuildingToSite_cascadeOnSuccess(t *testing.T) { store.EXPECT().ReassignRacksUnderBuilding(inTxCtx, testOrgID, int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(3), nil) store.EXPECT().ReassignDevicesUnderBuilding(inTxCtx, testOrgID, int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(15), nil) - out, err := svc.AssignBuildingToSite(context.Background(), models.AssignBuildingToSiteParams{ + out, err := svc.AssignBuildingsToSite(context.Background(), models.AssignBuildingsToSiteParams{ OrgID: testOrgID, - BuildingID: 50, + BuildingIDs: []int64{50}, TargetSiteID: &target, }) if err != nil { @@ -370,7 +370,7 @@ func TestAssignBuildingToSite_cascadeOnSuccess(t *testing.T) { } } -func TestAssignBuildingToSite_notFoundWhenBuildingMissing(t *testing.T) { +func TestAssignBuildingsToSite_notFoundWhenBuildingMissing(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} @@ -385,9 +385,9 @@ func TestAssignBuildingToSite_notFoundWhenBuildingMissing(t *testing.T) { store.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, int64(50)). Return(fleeterror.NewNotFoundErrorf("building %d not found", 50)) - _, err := svc.AssignBuildingToSite(context.Background(), models.AssignBuildingToSiteParams{ + _, err := svc.AssignBuildingsToSite(context.Background(), models.AssignBuildingsToSiteParams{ OrgID: testOrgID, - BuildingID: 50, + BuildingIDs: []int64{50}, TargetSiteID: &target, }) if !fleeterror.IsNotFoundError(err) { @@ -395,6 +395,33 @@ func TestAssignBuildingToSite_notFoundWhenBuildingMissing(t *testing.T) { } } +func TestAssignBuildingsToSite_bulkRollsBackOnLaterFailure(t *testing.T) { + // First building succeeds, second fails — tx must roll back, no + // AssignBuildingToSite call on the second building's cascade phase. + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, nil, nil, tx, nil) + + target := int64(20) + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, target).Return(nil) + store.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, int64(50)).Return(nil) + store.EXPECT().AssignBuildingToSite(inTxCtx, testOrgID, int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(1), nil) + store.EXPECT().ReassignRacksUnderBuilding(inTxCtx, testOrgID, int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(2), nil) + store.EXPECT().ReassignDevicesUnderBuilding(inTxCtx, testOrgID, int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(10), nil) + store.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, int64(51)). + Return(fleeterror.NewNotFoundErrorf("building %d not found", 51)) + + _, err := svc.AssignBuildingsToSite(context.Background(), models.AssignBuildingsToSiteParams{ + OrgID: testOrgID, + BuildingIDs: []int64{50, 51}, + TargetSiteID: &target, + }) + if !fleeterror.IsNotFoundError(err) { + t.Fatalf("expected NotFound for second building, got %v", err) + } +} + func TestCreateSite_invalidNetworkConfigBlocksWrite(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) diff --git a/server/internal/domain/stores/interfaces/building.go b/server/internal/domain/stores/interfaces/building.go index 13f655efe..178d7bc5f 100644 --- a/server/internal/domain/stores/interfaces/building.go +++ b/server/internal/domain/stores/interfaces/building.go @@ -28,7 +28,7 @@ type BuildingStore interface { ListBuildings(ctx context.Context, filter models.ListFilter) ([]models.BuildingWithCounts, error) // UpdateBuilding mutates the row's mutable fields (excluding - // site_id — that lives on SiteService.AssignBuildingToSite for + // site_id — that lives on SiteService.AssignBuildingsToSite for // cross-collection enforcement). Returns NotFound when row gone. UpdateBuilding(ctx context.Context, params models.UpdateParams) (*models.Building, error) diff --git a/server/internal/handlers/buildings/handler.go b/server/internal/handlers/buildings/handler.go index b8723230f..46fe2e3bd 100644 --- a/server/internal/handlers/buildings/handler.go +++ b/server/internal/handlers/buildings/handler.go @@ -172,7 +172,7 @@ func (h *Handler) GetBuildingStats(ctx context.Context, req *connect.Request[pb. } // Pass the building's site as we saw it at authz time. The service // re-reads the building and rejects with NotFound if a concurrent - // AssignBuildingToSite moved it — otherwise a site-scoped caller + // AssignBuildingsToSite moved it — otherwise a site-scoped caller // could end up with telemetry for a site they're not authorized for. out, err := h.service.GetBuildingStats(ctx, info.OrganizationID, req.Msg.GetBuildingId(), building.SiteID) if err != nil { diff --git a/server/internal/handlers/middleware/rpc_permissions.go b/server/internal/handlers/middleware/rpc_permissions.go index a70c98b6d..fd28ac7e1 100644 --- a/server/internal/handlers/middleware/rpc_permissions.go +++ b/server/internal/handlers/middleware/rpc_permissions.go @@ -283,12 +283,12 @@ var ProcedurePermissions = map[string]string{ serverlogv1connect.ServerLogServiceListServerLogsProcedure: authz.PermServerlogRead, // Sites CRUD — site:read for List, site:manage for everything else. - sitesv1connect.SiteServiceListSitesProcedure: authz.PermSiteRead, - sitesv1connect.SiteServiceCreateSiteProcedure: authz.PermSiteManage, - sitesv1connect.SiteServiceUpdateSiteProcedure: authz.PermSiteManage, - sitesv1connect.SiteServiceDeleteSiteProcedure: authz.PermSiteManage, - sitesv1connect.SiteServiceAssignDevicesToSiteProcedure: authz.PermSiteManage, - sitesv1connect.SiteServiceAssignBuildingToSiteProcedure: authz.PermSiteManage, + sitesv1connect.SiteServiceListSitesProcedure: authz.PermSiteRead, + sitesv1connect.SiteServiceCreateSiteProcedure: authz.PermSiteManage, + sitesv1connect.SiteServiceUpdateSiteProcedure: authz.PermSiteManage, + sitesv1connect.SiteServiceDeleteSiteProcedure: authz.PermSiteManage, + sitesv1connect.SiteServiceAssignDevicesToSiteProcedure: authz.PermSiteManage, + sitesv1connect.SiteServiceAssignBuildingsToSiteProcedure: authz.PermSiteManage, // GetSiteStats also calls RequirePermission(PermFleetRead) inline to // cover the aggregate telemetry surface (matching the gate on // telemetry.GetCombinedMetrics). The map entry is the primary gate. diff --git a/server/internal/handlers/sites/handler.go b/server/internal/handlers/sites/handler.go index d44da1b13..4a8dfed08 100644 --- a/server/internal/handlers/sites/handler.go +++ b/server/internal/handlers/sites/handler.go @@ -101,16 +101,16 @@ func (h *Handler) AssignDevicesToSite(ctx context.Context, req *connect.Request[ }), nil } -func (h *Handler) AssignBuildingToSite(ctx context.Context, req *connect.Request[pb.AssignBuildingToSiteRequest]) (*connect.Response[pb.AssignBuildingToSiteResponse], error) { +func (h *Handler) AssignBuildingsToSite(ctx context.Context, req *connect.Request[pb.AssignBuildingsToSiteRequest]) (*connect.Response[pb.AssignBuildingsToSiteResponse], error) { info, err := middleware.RequirePermission(ctx, authz.PermSiteManage, authz.ResourceContext{}) if err != nil { return nil, err } - out, err := h.service.AssignBuildingToSite(ctx, toAssignBuildingParams(req.Msg, info.OrganizationID)) + out, err := h.service.AssignBuildingsToSite(ctx, toAssignBuildingsParams(req.Msg, info.OrganizationID)) if err != nil { return nil, err } - return connect.NewResponse(&pb.AssignBuildingToSiteResponse{ + return connect.NewResponse(&pb.AssignBuildingsToSiteResponse{ ReassignedRackCount: out.ReassignedRackCount, ReassignedDeviceCount: out.ReassignedDeviceCount, }), nil diff --git a/server/internal/handlers/sites/handler_test.go b/server/internal/handlers/sites/handler_test.go index eeccea261..2bc7c7e31 100644 --- a/server/internal/handlers/sites/handler_test.go +++ b/server/internal/handlers/sites/handler_test.go @@ -274,7 +274,7 @@ func TestHandler_AssignDevicesToSite_conflictsReturnTypedReason(t *testing.T) { assert.Equal(t, conflictingSite, c.GetConflictingSiteId()) } -func TestHandler_AssignBuildingToSite_surfacesCascadeCounts(t *testing.T) { +func TestHandler_AssignBuildingsToSite_surfacesCascadeCounts(t *testing.T) { t.Parallel() h := newTestHandler(t) @@ -286,8 +286,8 @@ func TestHandler_AssignBuildingToSite_surfacesCascadeCounts(t *testing.T) { h.siteStore.EXPECT().ReassignRacksUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(3), nil) h.siteStore.EXPECT().ReassignDevicesUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(15), nil) - resp, err := h.handler.AssignBuildingToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignBuildingToSiteRequest{ - BuildingId: 50, + resp, err := h.handler.AssignBuildingsToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignBuildingsToSiteRequest{ + BuildingIds: []int64{50}, TargetSiteId: &target, })) require.NoError(t, err) @@ -295,7 +295,7 @@ func TestHandler_AssignBuildingToSite_surfacesCascadeCounts(t *testing.T) { assert.Equal(t, int64(15), resp.Msg.GetReassignedDeviceCount()) } -func TestHandler_AssignBuildingToSite_targetUnsetCascadesToUnassigned(t *testing.T) { +func TestHandler_AssignBuildingsToSite_targetUnsetCascadesToUnassigned(t *testing.T) { t.Parallel() h := newTestHandler(t) @@ -307,10 +307,37 @@ func TestHandler_AssignBuildingToSite_targetUnsetCascadesToUnassigned(t *testing h.siteStore.EXPECT().ReassignRacksUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.Nil()).Return(int64(0), nil) h.siteStore.EXPECT().ReassignDevicesUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.Nil()).Return(int64(0), nil) - resp, err := h.handler.AssignBuildingToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignBuildingToSiteRequest{ - BuildingId: 50, + resp, err := h.handler.AssignBuildingsToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignBuildingsToSiteRequest{ + BuildingIds: []int64{50}, })) require.NoError(t, err) assert.Equal(t, int64(0), resp.Msg.GetReassignedRackCount()) assert.Equal(t, int64(0), resp.Msg.GetReassignedDeviceCount()) } + +func TestHandler_AssignBuildingsToSite_bulkAggregatesCascadeCounts(t *testing.T) { + t.Parallel() + h := newTestHandler(t) + + target := int64(20) + + // Two buildings, processed in sorted ID order. + h.siteStore.EXPECT().LockSiteForWrite(gomock.Any(), int64(7), target).Return(nil) + h.siteStore.EXPECT().LockBuildingForWrite(gomock.Any(), int64(7), int64(50)).Return(nil) + h.siteStore.EXPECT().AssignBuildingToSite(gomock.Any(), int64(7), int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(1), nil) + h.siteStore.EXPECT().ReassignRacksUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(2), nil) + h.siteStore.EXPECT().ReassignDevicesUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(10), nil) + h.siteStore.EXPECT().LockBuildingForWrite(gomock.Any(), int64(7), int64(51)).Return(nil) + h.siteStore.EXPECT().AssignBuildingToSite(gomock.Any(), int64(7), int64(51), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(1), nil) + h.siteStore.EXPECT().ReassignRacksUnderBuilding(gomock.Any(), int64(7), int64(51), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(4), nil) + h.siteStore.EXPECT().ReassignDevicesUnderBuilding(gomock.Any(), int64(7), int64(51), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(20), nil) + + // Pass IDs out of order to verify deterministic locking via sort. + resp, err := h.handler.AssignBuildingsToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignBuildingsToSiteRequest{ + BuildingIds: []int64{51, 50}, + TargetSiteId: &target, + })) + require.NoError(t, err) + assert.Equal(t, int64(6), resp.Msg.GetReassignedRackCount()) + assert.Equal(t, int64(30), resp.Msg.GetReassignedDeviceCount()) +} diff --git a/server/internal/handlers/sites/translate.go b/server/internal/handlers/sites/translate.go index fa998cab4..eca57fc39 100644 --- a/server/internal/handlers/sites/translate.go +++ b/server/internal/handlers/sites/translate.go @@ -54,15 +54,15 @@ func toAssignDevicesParams(req *pb.AssignDevicesToSiteRequest, orgID int64) mode } } -func toAssignBuildingParams(req *pb.AssignBuildingToSiteRequest, orgID int64) models.AssignBuildingToSiteParams { +func toAssignBuildingsParams(req *pb.AssignBuildingsToSiteRequest, orgID int64) models.AssignBuildingsToSiteParams { var targetSiteID *int64 if req.TargetSiteId != nil { v := req.GetTargetSiteId() targetSiteID = &v } - return models.AssignBuildingToSiteParams{ + return models.AssignBuildingsToSiteParams{ OrgID: orgID, - BuildingID: req.GetBuildingId(), + BuildingIDs: req.GetBuildingIds(), TargetSiteID: targetSiteID, } } diff --git a/server/sqlc/queries/site.sql b/server/sqlc/queries/site.sql index 794edb6ca..8376d1781 100644 --- a/server/sqlc/queries/site.sql +++ b/server/sqlc/queries/site.sql @@ -189,7 +189,7 @@ FOR UPDATE; -- name: LockBuildingForWrite :one -- Row-locks a specific building so concurrent mutations (DeleteSite, --- AssignBuildingToSite, DeleteBuilding) serialize. Returns the building +-- AssignBuildingsToSite, DeleteBuilding) serialize. Returns the building -- id when alive; sql.ErrNoRows when soft-deleted or missing. SELECT id FROM building WHERE id = sqlc.arg('id') @@ -200,7 +200,7 @@ FOR UPDATE; -- name: LockBuildingsBySiteForWrite :many -- Row-locks every live building under the given site so DeleteSite's -- cascade can rewrite their racks without a concurrent --- AssignBuildingToSite slipping a building out from under it. Returns +-- AssignBuildingsToSite slipping a building out from under it. Returns -- the locked ids (result is informational; the FOR UPDATE side-effect -- is what matters). SELECT id FROM building From d20f464147b4c365a0d2f9fe01aa8e2b16d614bc Mon Sep 17 00:00:00 2001 From: flesher Date: Wed, 10 Jun 2026 16:34:52 -0500 Subject: [PATCH 3/9] =?UTF-8?q?refactor(buildings):=20pluralize=20AssignRa?= =?UTF-8?q?ckToBuilding=20=E2=86=92=20AssignRacksToBuilding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promotes the rack→building assignment RPC to a bulk shape so the ManageBuildingModal save flow (which previously fired N concurrent singular calls) can land an entire grid layout in two atomic batches — vacate first, then place — instead of N independent transactions. Single-rack callers (RacksPage row action) pass a one-element array. Request shape: repeated RackPlacement { rack_id, optional aisle_index, optional position_in_aisle } + optional target_building_id. Each entry carries its own placement so a single call can mix placed and cleared positions. Service-layer behavior preserved per rack — building lock once, canonical lock order (building → rack id ascending), per-rack zone clear / cascade / position write. If any rack fails, the whole tx rolls back. Activity-log payload now carries rack_ids[] + aggregate cascade counts; consumers reading the singular rack_id metadata key will need to migrate (no existing consumers in tree). Refs #420. --- client/src/protoFleet/api/buildings.ts | 47 +- .../generated/buildings/v1/buildings_pb.ts | 102 +-- .../BuildingModals/BuildingModals.test.tsx | 2 +- .../ManageBuildingModal.tsx | 96 ++- .../components/ManageBuildingModal/types.ts | 2 +- .../fleetManagement/pages/RacksPage.tsx | 14 +- proto/buildings/v1/buildings.proto | 48 +- .../grpc/buildings/v1/buildings.pb.go | 595 ++++++++++-------- .../buildingsv1connect/buildings.connect.go | 86 +-- .../domain/buildings/models/models.go | 27 +- server/internal/domain/buildings/service.go | 288 +++++---- .../internal/domain/buildings/service_test.go | 104 +-- server/internal/handlers/buildings/handler.go | 6 +- .../handlers/buildings/handler_test.go | 26 +- .../internal/handlers/buildings/translate.go | 32 +- .../handlers/middleware/rpc_permissions.go | 16 +- 16 files changed, 807 insertions(+), 684 deletions(-) diff --git a/client/src/protoFleet/api/buildings.ts b/client/src/protoFleet/api/buildings.ts index 233214e51..dae42b783 100644 --- a/client/src/protoFleet/api/buildings.ts +++ b/client/src/protoFleet/api/buildings.ts @@ -68,13 +68,20 @@ interface DeleteBuildingProps { onFinally?: () => void; } -interface AssignRackToBuildingProps { +// RackPlacementInput is one rack's slot in an AssignRacksToBuilding +// batch. aisleIndex + positionInAisle must be paired (both set or both +// unset); the server rejects half-set inputs. +export interface RackPlacementInput { rackId: bigint; - // Unset = unassign from any building. - buildingId?: bigint; - // Optional grid cell. Must be paired. aisleIndex?: number; positionInAisle?: number; +} + +interface AssignRacksToBuildingProps { + // Bulk-friendly. Pass a single-element array for the singular case. + racks: RackPlacementInput[]; + // Unset = unassign every rack in the batch from any building. + targetBuildingId?: bigint; signal?: AbortSignal; onSuccess?: (siteReassignedDeviceCount: bigint) => void; onError?: (message: string) => void; @@ -353,29 +360,17 @@ const useBuildings = () => { [handleAuthErrors], ); - // assignRackToBuilding wraps the dedicated rack-positioning RPC. - // Unset `buildingId` unassigns the rack; passing both `aisleIndex` - // and `positionInAisle` positions the rack at that grid cell. - // Passing one without the other is rejected by the server; the - // wrapper preserves the failure surface so callers can react. - const assignRackToBuilding = useCallback( - async ({ - rackId, - buildingId, - aisleIndex, - positionInAisle, - signal, - onSuccess, - onError, - onFinally, - }: AssignRackToBuildingProps) => { + // assignRacksToBuilding wraps the bulk rack-positioning RPC. + // Unset `targetBuildingId` unassigns every rack in the batch; + // each rack's `aisleIndex` + `positionInAisle` must be paired. + // The server rejects half-set inputs and out-of-bounds positions. + const assignRacksToBuilding = useCallback( + async ({ racks, targetBuildingId, signal, onSuccess, onError, onFinally }: AssignRacksToBuildingProps) => { try { - const response = await buildingsClient.assignRackToBuilding( + const response = await buildingsClient.assignRacksToBuilding( { - rackId, - buildingId, - aisleIndex, - positionInAisle, + racks, + targetBuildingId, }, { signal }, ); @@ -404,7 +399,7 @@ const useBuildings = () => { createBuilding, updateBuilding, deleteBuilding, - assignRackToBuilding, + assignRacksToBuilding, }; }; diff --git a/client/src/protoFleet/api/generated/buildings/v1/buildings_pb.ts b/client/src/protoFleet/api/generated/buildings/v1/buildings_pb.ts index 9656e675f..92277fcf9 100644 --- a/client/src/protoFleet/api/generated/buildings/v1/buildings_pb.ts +++ b/client/src/protoFleet/api/generated/buildings/v1/buildings_pb.ts @@ -15,7 +15,7 @@ import type { Message } from "@bufbuild/protobuf"; export const file_buildings_v1_buildings: GenFile = /*@__PURE__*/ fileDesc( - "ChxidWlsZGluZ3MvdjEvYnVpbGRpbmdzLnByb3RvEgxidWlsZGluZ3MudjEioQMKCEJ1aWxkaW5nEgoKAmlkGAEgASgDEhQKB3NpdGVfaWQYAiABKANIAIgBARIMCgRuYW1lGAMgASgJEhMKC2Rlc2NyaXB0aW9uGAQgASgJEhAKCHBvd2VyX2t3GAUgASgBEhMKC292ZXJoZWFkX2t3GAYgASgBEg4KBmFpc2xlcxgHIAEoBRIbChNwaHlzaWNhbF9yYWNrX2NvdW50GAggASgFEhcKD3JhY2tzX3Blcl9haXNsZRgJIAEoBRIZChFkZWZhdWx0X3JhY2tfcm93cxgKIAEoBRIcChRkZWZhdWx0X3JhY2tfY29sdW1ucxgLIAEoBRI+ChhkZWZhdWx0X3JhY2tfb3JkZXJfaW5kZXgYDCABKA4yHC5idWlsZGluZ3MudjEuUmFja09yZGVySW5kZXgSLgoKY3JlYXRlZF9hdBgNIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgOIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCCgoIX3NpdGVfaWQiUgoSQnVpbGRpbmdXaXRoQ291bnRzEigKCGJ1aWxkaW5nGAEgASgLMhYuYnVpbGRpbmdzLnYxLkJ1aWxkaW5nEhIKCnJhY2tfY291bnQYAiABKAMiXAoUTGlzdEJ1aWxkaW5nc1JlcXVlc3QSGgoHc2l0ZV9pZBgBIAEoA0IHukgEIgIgAEgAEhkKD3VuYXNzaWduZWRfb25seRgCIAEoCEgAQg0KC3NpdGVfZmlsdGVyIkwKFUxpc3RCdWlsZGluZ3NSZXNwb25zZRIzCglidWlsZGluZ3MYASADKAsyIC5idWlsZGluZ3MudjEuQnVpbGRpbmdXaXRoQ291bnRzIikKEkdldEJ1aWxkaW5nUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgACI/ChNHZXRCdWlsZGluZ1Jlc3BvbnNlEigKCGJ1aWxkaW5nGAEgASgLMhYuYnVpbGRpbmdzLnYxLkJ1aWxkaW5nIuoEChVDcmVhdGVCdWlsZGluZ1JlcXVlc3QSHQoHc2l0ZV9pZBgBIAEoA0IHukgEIgIgAEgAiAEBEhgKBG5hbWUYAiABKAlCCrpIB3IFEAEY/wESHQoLZGVzY3JpcHRpb24YAyABKAlCCLpIBXIDGIAgEiAKCHBvd2VyX2t3GAQgASgBQg66SAsSCSkAAAAAAAAAABIjCgtvdmVyaGVhZF9rdxgFIAEoAUIOukgLEgkpAAAAAAAAAAASGQoGYWlzbGVzGAYgASgFQgm6SAYaBBhkKAASJAoTcGh5c2ljYWxfcmFja19jb3VudBgHIAEoBUIHukgEGgIoABIiCg9yYWNrc19wZXJfYWlzbGUYCCABKAVCCbpIBhoEGGQoABIiChFkZWZhdWx0X3JhY2tfcm93cxgJIAEoBUIHukgEGgIoABIlChRkZWZhdWx0X3JhY2tfY29sdW1ucxgKIAEoBUIHukgEGgIoABJIChhkZWZhdWx0X3JhY2tfb3JkZXJfaW5kZXgYCyABKA4yHC5idWlsZGluZ3MudjEuUmFja09yZGVySW5kZXhCCLpIBYIBAhABOqsBukinARqkAQoYZGVmYXVsdF9yYWNrX2RpbXNfcGFpcmVkEkVkZWZhdWx0X3JhY2tfcm93cyBhbmQgZGVmYXVsdF9yYWNrX2NvbHVtbnMgbXVzdCBib3RoIGJlIDAgb3IgYm90aCA+IDAaQSh0aGlzLmRlZmF1bHRfcmFja19yb3dzID09IDApID09ICh0aGlzLmRlZmF1bHRfcmFja19jb2x1bW5zID09IDApQgoKCF9zaXRlX2lkIkIKFkNyZWF0ZUJ1aWxkaW5nUmVzcG9uc2USKAoIYnVpbGRpbmcYASABKAsyFi5idWlsZGluZ3MudjEuQnVpbGRpbmci1AQKFVVwZGF0ZUJ1aWxkaW5nUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh0KC2Rlc2NyaXB0aW9uGAMgASgJQgi6SAVyAxiAIBIgCghwb3dlcl9rdxgEIAEoAUIOukgLEgkpAAAAAAAAAAASIwoLb3ZlcmhlYWRfa3cYBSABKAFCDrpICxIJKQAAAAAAAAAAEhkKBmFpc2xlcxgGIAEoBUIJukgGGgQYZCgAEiQKE3BoeXNpY2FsX3JhY2tfY291bnQYByABKAVCB7pIBBoCKAASIgoPcmFja3NfcGVyX2Fpc2xlGAggASgFQgm6SAYaBBhkKAASIgoRZGVmYXVsdF9yYWNrX3Jvd3MYCSABKAVCB7pIBBoCKAASJQoUZGVmYXVsdF9yYWNrX2NvbHVtbnMYCiABKAVCB7pIBBoCKAASSAoYZGVmYXVsdF9yYWNrX29yZGVyX2luZGV4GAsgASgOMhwuYnVpbGRpbmdzLnYxLlJhY2tPcmRlckluZGV4Qgi6SAWCAQIQATqrAbpIpwEapAEKGGRlZmF1bHRfcmFja19kaW1zX3BhaXJlZBJFZGVmYXVsdF9yYWNrX3Jvd3MgYW5kIGRlZmF1bHRfcmFja19jb2x1bW5zIG11c3QgYm90aCBiZSAwIG9yIGJvdGggPiAwGkEodGhpcy5kZWZhdWx0X3JhY2tfcm93cyA9PSAwKSA9PSAodGhpcy5kZWZhdWx0X3JhY2tfY29sdW1ucyA9PSAwKSJCChZVcGRhdGVCdWlsZGluZ1Jlc3BvbnNlEigKCGJ1aWxkaW5nGAEgASgLMhYuYnVpbGRpbmdzLnYxLkJ1aWxkaW5nIiwKFURlbGV0ZUJ1aWxkaW5nUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgACI3ChZEZWxldGVCdWlsZGluZ1Jlc3BvbnNlEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgBIAEoAyJ1ChhMaXN0QnVpbGRpbmdSYWNrc1JlcXVlc3QSHAoLYnVpbGRpbmdfaWQYASABKANCB7pIBCICIAASHQoJcGFnZV9zaXplGAIgASgFQgq6SAcaBRjoBygAEhwKCnBhZ2VfdG9rZW4YAyABKAlCCLpIBXIDGIAQIpMBCgxCdWlsZGluZ1JhY2sSDwoHcmFja19pZBgBIAEoAxISCgpyYWNrX2xhYmVsGAIgASgJEhgKC2Fpc2xlX2luZGV4GAMgASgFSACIAQESHgoRcG9zaXRpb25faW5fYWlzbGUYBCABKAVIAYgBAUIOCgxfYWlzbGVfaW5kZXhCFAoSX3Bvc2l0aW9uX2luX2Fpc2xlIl8KGUxpc3RCdWlsZGluZ1JhY2tzUmVzcG9uc2USKQoFcmFja3MYASADKAsyGi5idWlsZGluZ3MudjEuQnVpbGRpbmdSYWNrEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSL2AgobQXNzaWduUmFja1RvQnVpbGRpbmdSZXF1ZXN0EhgKB3JhY2tfaWQYASABKANCB7pIBCICIAASIQoLYnVpbGRpbmdfaWQYAiABKANCB7pIBCICIABIAIgBARIhCgthaXNsZV9pbmRleBgDIAEoBUIHukgEGgIoAEgBiAEBEicKEXBvc2l0aW9uX2luX2Fpc2xlGAQgASgFQge6SAQaAigASAKIAQE6lwG6SJMBGpABChZwb3NpdGlvbl9wYWlyX3JlcXVpcmVkEkBhaXNsZV9pbmRleCBhbmQgcG9zaXRpb25faW5fYWlzbGUgbXVzdCBib3RoIGJlIHNldCBvciBib3RoIHVuc2V0GjRoYXModGhpcy5haXNsZV9pbmRleCkgPT0gaGFzKHRoaXMucG9zaXRpb25faW5fYWlzbGUpQg4KDF9idWlsZGluZ19pZEIOCgxfYWlzbGVfaW5kZXhCFAoSX3Bvc2l0aW9uX2luX2Fpc2xlIkQKHEFzc2lnblJhY2tUb0J1aWxkaW5nUmVzcG9uc2USJAocc2l0ZV9yZWFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAyI3ChdHZXRCdWlsZGluZ1N0YXRzUmVxdWVzdBIcCgtidWlsZGluZ19pZBgBIAEoA0IHukgEIgIgACLWAwoYR2V0QnVpbGRpbmdTdGF0c1Jlc3BvbnNlEhMKC2J1aWxkaW5nX2lkGAEgASgDEhIKCnJhY2tfY291bnQYAiABKAUSFAoMZGV2aWNlX2NvdW50GAMgASgFEhcKD3JlcG9ydGluZ19jb3VudBgEIAEoBRIaChJ0b3RhbF9oYXNocmF0ZV90aHMYBSABKAESGgoSYXZnX2VmZmljaWVuY3lfanRoGAYgASgBEhYKDnRvdGFsX3Bvd2VyX2t3GAcgASgBEhUKDWhhc2hpbmdfY291bnQYCCABKAUSFAoMYnJva2VuX2NvdW50GAkgASgFEhUKDW9mZmxpbmVfY291bnQYCiABKAUSFgoOc2xlZXBpbmdfY291bnQYCyABKAUSNQoLcmFja19oZWFsdGgYDCADKAsyIC5idWlsZGluZ3MudjEuQnVpbGRpbmdSYWNrSGVhbHRoEhoKEmRldmljZV9pZGVudGlmaWVycxgNIAMoCRIgChhoYXNocmF0ZV9yZXBvcnRpbmdfY291bnQYDiABKAUSIgoaZWZmaWNpZW5jeV9yZXBvcnRpbmdfY291bnQYDyABKAUSHQoVcG93ZXJfcmVwb3J0aW5nX2NvdW50GBAgASgFIvUBChJCdWlsZGluZ1JhY2tIZWFsdGgSDwoHcmFja19pZBgBIAEoAxISCgpyYWNrX2xhYmVsGAIgASgJEhgKC2Fpc2xlX2luZGV4GAMgASgFSACIAQESHgoRcG9zaXRpb25faW5fYWlzbGUYBCABKAVIAYgBARIVCg1oYXNoaW5nX2NvdW50GAUgASgFEhQKDGJyb2tlbl9jb3VudBgGIAEoBRIVCg1vZmZsaW5lX2NvdW50GAcgASgFEhYKDnNsZWVwaW5nX2NvdW50GAggASgFQg4KDF9haXNsZV9pbmRleEIUChJfcG9zaXRpb25faW5fYWlzbGUqtgEKDlJhY2tPcmRlckluZGV4EiAKHFJBQ0tfT1JERVJfSU5ERVhfVU5TUEVDSUZJRUQQABIgChxSQUNLX09SREVSX0lOREVYX0JPVFRPTV9MRUZUEAESHQoZUkFDS19PUkRFUl9JTkRFWF9UT1BfTEVGVBACEiEKHVJBQ0tfT1JERVJfSU5ERVhfQk9UVE9NX1JJR0hUEAMSHgoaUkFDS19PUkRFUl9JTkRFWF9UT1BfUklHSFQQBDKOBgoPQnVpbGRpbmdTZXJ2aWNlElgKDUxpc3RCdWlsZGluZ3MSIi5idWlsZGluZ3MudjEuTGlzdEJ1aWxkaW5nc1JlcXVlc3QaIy5idWlsZGluZ3MudjEuTGlzdEJ1aWxkaW5nc1Jlc3BvbnNlElIKC0dldEJ1aWxkaW5nEiAuYnVpbGRpbmdzLnYxLkdldEJ1aWxkaW5nUmVxdWVzdBohLmJ1aWxkaW5ncy52MS5HZXRCdWlsZGluZ1Jlc3BvbnNlElsKDkNyZWF0ZUJ1aWxkaW5nEiMuYnVpbGRpbmdzLnYxLkNyZWF0ZUJ1aWxkaW5nUmVxdWVzdBokLmJ1aWxkaW5ncy52MS5DcmVhdGVCdWlsZGluZ1Jlc3BvbnNlElsKDlVwZGF0ZUJ1aWxkaW5nEiMuYnVpbGRpbmdzLnYxLlVwZGF0ZUJ1aWxkaW5nUmVxdWVzdBokLmJ1aWxkaW5ncy52MS5VcGRhdGVCdWlsZGluZ1Jlc3BvbnNlElsKDkRlbGV0ZUJ1aWxkaW5nEiMuYnVpbGRpbmdzLnYxLkRlbGV0ZUJ1aWxkaW5nUmVxdWVzdBokLmJ1aWxkaW5ncy52MS5EZWxldGVCdWlsZGluZ1Jlc3BvbnNlEmQKEUxpc3RCdWlsZGluZ1JhY2tzEiYuYnVpbGRpbmdzLnYxLkxpc3RCdWlsZGluZ1JhY2tzUmVxdWVzdBonLmJ1aWxkaW5ncy52MS5MaXN0QnVpbGRpbmdSYWNrc1Jlc3BvbnNlEm0KFEFzc2lnblJhY2tUb0J1aWxkaW5nEikuYnVpbGRpbmdzLnYxLkFzc2lnblJhY2tUb0J1aWxkaW5nUmVxdWVzdBoqLmJ1aWxkaW5ncy52MS5Bc3NpZ25SYWNrVG9CdWlsZGluZ1Jlc3BvbnNlEmEKEEdldEJ1aWxkaW5nU3RhdHMSJS5idWlsZGluZ3MudjEuR2V0QnVpbGRpbmdTdGF0c1JlcXVlc3QaJi5idWlsZGluZ3MudjEuR2V0QnVpbGRpbmdTdGF0c1Jlc3BvbnNlQsABChBjb20uYnVpbGRpbmdzLnYxQg5CdWlsZGluZ3NQcm90b1ABWktnaXRodWIuY29tL2Jsb2NrL3Byb3RvLWZsZWV0L3NlcnZlci9nZW5lcmF0ZWQvZ3JwYy9idWlsZGluZ3MvdjE7YnVpbGRpbmdzdjGiAgNCWFiqAgxCdWlsZGluZ3MuVjHKAgxCdWlsZGluZ3NcVjHiAhhCdWlsZGluZ3NcVjFcR1BCTWV0YWRhdGHqAg1CdWlsZGluZ3M6OlYxYgZwcm90bzM", + "ChxidWlsZGluZ3MvdjEvYnVpbGRpbmdzLnByb3RvEgxidWlsZGluZ3MudjEioQMKCEJ1aWxkaW5nEgoKAmlkGAEgASgDEhQKB3NpdGVfaWQYAiABKANIAIgBARIMCgRuYW1lGAMgASgJEhMKC2Rlc2NyaXB0aW9uGAQgASgJEhAKCHBvd2VyX2t3GAUgASgBEhMKC292ZXJoZWFkX2t3GAYgASgBEg4KBmFpc2xlcxgHIAEoBRIbChNwaHlzaWNhbF9yYWNrX2NvdW50GAggASgFEhcKD3JhY2tzX3Blcl9haXNsZRgJIAEoBRIZChFkZWZhdWx0X3JhY2tfcm93cxgKIAEoBRIcChRkZWZhdWx0X3JhY2tfY29sdW1ucxgLIAEoBRI+ChhkZWZhdWx0X3JhY2tfb3JkZXJfaW5kZXgYDCABKA4yHC5idWlsZGluZ3MudjEuUmFja09yZGVySW5kZXgSLgoKY3JlYXRlZF9hdBgNIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgOIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCCgoIX3NpdGVfaWQiUgoSQnVpbGRpbmdXaXRoQ291bnRzEigKCGJ1aWxkaW5nGAEgASgLMhYuYnVpbGRpbmdzLnYxLkJ1aWxkaW5nEhIKCnJhY2tfY291bnQYAiABKAMiXAoUTGlzdEJ1aWxkaW5nc1JlcXVlc3QSGgoHc2l0ZV9pZBgBIAEoA0IHukgEIgIgAEgAEhkKD3VuYXNzaWduZWRfb25seRgCIAEoCEgAQg0KC3NpdGVfZmlsdGVyIkwKFUxpc3RCdWlsZGluZ3NSZXNwb25zZRIzCglidWlsZGluZ3MYASADKAsyIC5idWlsZGluZ3MudjEuQnVpbGRpbmdXaXRoQ291bnRzIikKEkdldEJ1aWxkaW5nUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgACI/ChNHZXRCdWlsZGluZ1Jlc3BvbnNlEigKCGJ1aWxkaW5nGAEgASgLMhYuYnVpbGRpbmdzLnYxLkJ1aWxkaW5nIuoEChVDcmVhdGVCdWlsZGluZ1JlcXVlc3QSHQoHc2l0ZV9pZBgBIAEoA0IHukgEIgIgAEgAiAEBEhgKBG5hbWUYAiABKAlCCrpIB3IFEAEY/wESHQoLZGVzY3JpcHRpb24YAyABKAlCCLpIBXIDGIAgEiAKCHBvd2VyX2t3GAQgASgBQg66SAsSCSkAAAAAAAAAABIjCgtvdmVyaGVhZF9rdxgFIAEoAUIOukgLEgkpAAAAAAAAAAASGQoGYWlzbGVzGAYgASgFQgm6SAYaBBhkKAASJAoTcGh5c2ljYWxfcmFja19jb3VudBgHIAEoBUIHukgEGgIoABIiCg9yYWNrc19wZXJfYWlzbGUYCCABKAVCCbpIBhoEGGQoABIiChFkZWZhdWx0X3JhY2tfcm93cxgJIAEoBUIHukgEGgIoABIlChRkZWZhdWx0X3JhY2tfY29sdW1ucxgKIAEoBUIHukgEGgIoABJIChhkZWZhdWx0X3JhY2tfb3JkZXJfaW5kZXgYCyABKA4yHC5idWlsZGluZ3MudjEuUmFja09yZGVySW5kZXhCCLpIBYIBAhABOqsBukinARqkAQoYZGVmYXVsdF9yYWNrX2RpbXNfcGFpcmVkEkVkZWZhdWx0X3JhY2tfcm93cyBhbmQgZGVmYXVsdF9yYWNrX2NvbHVtbnMgbXVzdCBib3RoIGJlIDAgb3IgYm90aCA+IDAaQSh0aGlzLmRlZmF1bHRfcmFja19yb3dzID09IDApID09ICh0aGlzLmRlZmF1bHRfcmFja19jb2x1bW5zID09IDApQgoKCF9zaXRlX2lkIkIKFkNyZWF0ZUJ1aWxkaW5nUmVzcG9uc2USKAoIYnVpbGRpbmcYASABKAsyFi5idWlsZGluZ3MudjEuQnVpbGRpbmci1AQKFVVwZGF0ZUJ1aWxkaW5nUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh0KC2Rlc2NyaXB0aW9uGAMgASgJQgi6SAVyAxiAIBIgCghwb3dlcl9rdxgEIAEoAUIOukgLEgkpAAAAAAAAAAASIwoLb3ZlcmhlYWRfa3cYBSABKAFCDrpICxIJKQAAAAAAAAAAEhkKBmFpc2xlcxgGIAEoBUIJukgGGgQYZCgAEiQKE3BoeXNpY2FsX3JhY2tfY291bnQYByABKAVCB7pIBBoCKAASIgoPcmFja3NfcGVyX2Fpc2xlGAggASgFQgm6SAYaBBhkKAASIgoRZGVmYXVsdF9yYWNrX3Jvd3MYCSABKAVCB7pIBBoCKAASJQoUZGVmYXVsdF9yYWNrX2NvbHVtbnMYCiABKAVCB7pIBBoCKAASSAoYZGVmYXVsdF9yYWNrX29yZGVyX2luZGV4GAsgASgOMhwuYnVpbGRpbmdzLnYxLlJhY2tPcmRlckluZGV4Qgi6SAWCAQIQATqrAbpIpwEapAEKGGRlZmF1bHRfcmFja19kaW1zX3BhaXJlZBJFZGVmYXVsdF9yYWNrX3Jvd3MgYW5kIGRlZmF1bHRfcmFja19jb2x1bW5zIG11c3QgYm90aCBiZSAwIG9yIGJvdGggPiAwGkEodGhpcy5kZWZhdWx0X3JhY2tfcm93cyA9PSAwKSA9PSAodGhpcy5kZWZhdWx0X3JhY2tfY29sdW1ucyA9PSAwKSJCChZVcGRhdGVCdWlsZGluZ1Jlc3BvbnNlEigKCGJ1aWxkaW5nGAEgASgLMhYuYnVpbGRpbmdzLnYxLkJ1aWxkaW5nIiwKFURlbGV0ZUJ1aWxkaW5nUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgACI3ChZEZWxldGVCdWlsZGluZ1Jlc3BvbnNlEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgBIAEoAyJ1ChhMaXN0QnVpbGRpbmdSYWNrc1JlcXVlc3QSHAoLYnVpbGRpbmdfaWQYASABKANCB7pIBCICIAASHQoJcGFnZV9zaXplGAIgASgFQgq6SAcaBRjoBygAEhwKCnBhZ2VfdG9rZW4YAyABKAlCCLpIBXIDGIAQIpMBCgxCdWlsZGluZ1JhY2sSDwoHcmFja19pZBgBIAEoAxISCgpyYWNrX2xhYmVsGAIgASgJEhgKC2Fpc2xlX2luZGV4GAMgASgFSACIAQESHgoRcG9zaXRpb25faW5fYWlzbGUYBCABKAVIAYgBAUIOCgxfYWlzbGVfaW5kZXhCFAoSX3Bvc2l0aW9uX2luX2Fpc2xlIl8KGUxpc3RCdWlsZGluZ1JhY2tzUmVzcG9uc2USKQoFcmFja3MYASADKAsyGi5idWlsZGluZ3MudjEuQnVpbGRpbmdSYWNrEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSK1AgoNUmFja1BsYWNlbWVudBIYCgdyYWNrX2lkGAEgASgDQge6SAQiAiAAEiEKC2Fpc2xlX2luZGV4GAIgASgFQge6SAQaAigASACIAQESJwoRcG9zaXRpb25faW5fYWlzbGUYAyABKAVCB7pIBBoCKABIAYgBATqXAbpIkwEakAEKFnBvc2l0aW9uX3BhaXJfcmVxdWlyZWQSQGFpc2xlX2luZGV4IGFuZCBwb3NpdGlvbl9pbl9haXNsZSBtdXN0IGJvdGggYmUgc2V0IG9yIGJvdGggdW5zZXQaNGhhcyh0aGlzLmFpc2xlX2luZGV4KSA9PSBoYXModGhpcy5wb3NpdGlvbl9pbl9haXNsZSlCDgoMX2Fpc2xlX2luZGV4QhQKEl9wb3NpdGlvbl9pbl9haXNsZSKYAQocQXNzaWduUmFja3NUb0J1aWxkaW5nUmVxdWVzdBI3CgVyYWNrcxgBIAMoCzIbLmJ1aWxkaW5ncy52MS5SYWNrUGxhY2VtZW50Qgu6SAiSAQUIARDoBxIoChJ0YXJnZXRfYnVpbGRpbmdfaWQYAiABKANCB7pIBCICIABIAIgBAUIVChNfdGFyZ2V0X2J1aWxkaW5nX2lkIkUKHUFzc2lnblJhY2tzVG9CdWlsZGluZ1Jlc3BvbnNlEiQKHHNpdGVfcmVhc3NpZ25lZF9kZXZpY2VfY291bnQYASABKAMiNwoXR2V0QnVpbGRpbmdTdGF0c1JlcXVlc3QSHAoLYnVpbGRpbmdfaWQYASABKANCB7pIBCICIAAi1gMKGEdldEJ1aWxkaW5nU3RhdHNSZXNwb25zZRITCgtidWlsZGluZ19pZBgBIAEoAxISCgpyYWNrX2NvdW50GAIgASgFEhQKDGRldmljZV9jb3VudBgDIAEoBRIXCg9yZXBvcnRpbmdfY291bnQYBCABKAUSGgoSdG90YWxfaGFzaHJhdGVfdGhzGAUgASgBEhoKEmF2Z19lZmZpY2llbmN5X2p0aBgGIAEoARIWCg50b3RhbF9wb3dlcl9rdxgHIAEoARIVCg1oYXNoaW5nX2NvdW50GAggASgFEhQKDGJyb2tlbl9jb3VudBgJIAEoBRIVCg1vZmZsaW5lX2NvdW50GAogASgFEhYKDnNsZWVwaW5nX2NvdW50GAsgASgFEjUKC3JhY2tfaGVhbHRoGAwgAygLMiAuYnVpbGRpbmdzLnYxLkJ1aWxkaW5nUmFja0hlYWx0aBIaChJkZXZpY2VfaWRlbnRpZmllcnMYDSADKAkSIAoYaGFzaHJhdGVfcmVwb3J0aW5nX2NvdW50GA4gASgFEiIKGmVmZmljaWVuY3lfcmVwb3J0aW5nX2NvdW50GA8gASgFEh0KFXBvd2VyX3JlcG9ydGluZ19jb3VudBgQIAEoBSL1AQoSQnVpbGRpbmdSYWNrSGVhbHRoEg8KB3JhY2tfaWQYASABKAMSEgoKcmFja19sYWJlbBgCIAEoCRIYCgthaXNsZV9pbmRleBgDIAEoBUgAiAEBEh4KEXBvc2l0aW9uX2luX2Fpc2xlGAQgASgFSAGIAQESFQoNaGFzaGluZ19jb3VudBgFIAEoBRIUCgxicm9rZW5fY291bnQYBiABKAUSFQoNb2ZmbGluZV9jb3VudBgHIAEoBRIWCg5zbGVlcGluZ19jb3VudBgIIAEoBUIOCgxfYWlzbGVfaW5kZXhCFAoSX3Bvc2l0aW9uX2luX2Fpc2xlKrYBCg5SYWNrT3JkZXJJbmRleBIgChxSQUNLX09SREVSX0lOREVYX1VOU1BFQ0lGSUVEEAASIAocUkFDS19PUkRFUl9JTkRFWF9CT1RUT01fTEVGVBABEh0KGVJBQ0tfT1JERVJfSU5ERVhfVE9QX0xFRlQQAhIhCh1SQUNLX09SREVSX0lOREVYX0JPVFRPTV9SSUdIVBADEh4KGlJBQ0tfT1JERVJfSU5ERVhfVE9QX1JJR0hUEAQykQYKD0J1aWxkaW5nU2VydmljZRJYCg1MaXN0QnVpbGRpbmdzEiIuYnVpbGRpbmdzLnYxLkxpc3RCdWlsZGluZ3NSZXF1ZXN0GiMuYnVpbGRpbmdzLnYxLkxpc3RCdWlsZGluZ3NSZXNwb25zZRJSCgtHZXRCdWlsZGluZxIgLmJ1aWxkaW5ncy52MS5HZXRCdWlsZGluZ1JlcXVlc3QaIS5idWlsZGluZ3MudjEuR2V0QnVpbGRpbmdSZXNwb25zZRJbCg5DcmVhdGVCdWlsZGluZxIjLmJ1aWxkaW5ncy52MS5DcmVhdGVCdWlsZGluZ1JlcXVlc3QaJC5idWlsZGluZ3MudjEuQ3JlYXRlQnVpbGRpbmdSZXNwb25zZRJbCg5VcGRhdGVCdWlsZGluZxIjLmJ1aWxkaW5ncy52MS5VcGRhdGVCdWlsZGluZ1JlcXVlc3QaJC5idWlsZGluZ3MudjEuVXBkYXRlQnVpbGRpbmdSZXNwb25zZRJbCg5EZWxldGVCdWlsZGluZxIjLmJ1aWxkaW5ncy52MS5EZWxldGVCdWlsZGluZ1JlcXVlc3QaJC5idWlsZGluZ3MudjEuRGVsZXRlQnVpbGRpbmdSZXNwb25zZRJkChFMaXN0QnVpbGRpbmdSYWNrcxImLmJ1aWxkaW5ncy52MS5MaXN0QnVpbGRpbmdSYWNrc1JlcXVlc3QaJy5idWlsZGluZ3MudjEuTGlzdEJ1aWxkaW5nUmFja3NSZXNwb25zZRJwChVBc3NpZ25SYWNrc1RvQnVpbGRpbmcSKi5idWlsZGluZ3MudjEuQXNzaWduUmFja3NUb0J1aWxkaW5nUmVxdWVzdBorLmJ1aWxkaW5ncy52MS5Bc3NpZ25SYWNrc1RvQnVpbGRpbmdSZXNwb25zZRJhChBHZXRCdWlsZGluZ1N0YXRzEiUuYnVpbGRpbmdzLnYxLkdldEJ1aWxkaW5nU3RhdHNSZXF1ZXN0GiYuYnVpbGRpbmdzLnYxLkdldEJ1aWxkaW5nU3RhdHNSZXNwb25zZULAAQoQY29tLmJ1aWxkaW5ncy52MUIOQnVpbGRpbmdzUHJvdG9QAVpLZ2l0aHViLmNvbS9ibG9jay9wcm90by1mbGVldC9zZXJ2ZXIvZ2VuZXJhdGVkL2dycGMvYnVpbGRpbmdzL3YxO2J1aWxkaW5nc3YxogIDQlhYqgIMQnVpbGRpbmdzLlYxygIMQnVpbGRpbmdzXFYx4gIYQnVpbGRpbmdzXFYxXEdQQk1ldGFkYXRh6gINQnVpbGRpbmdzOjpWMWIGcHJvdG8z", [file_buf_validate_validate, file_google_protobuf_timestamp], ); @@ -547,53 +547,75 @@ export const ListBuildingRacksResponseSchema: GenMessage & { +export type RackPlacement = Message<"buildings.v1.RackPlacement"> & { /** * @generated from field: int64 rack_id = 1; */ rackId: bigint; - /** - * Unset = unassign rack from any building. When present, must be > 0. - * - * @generated from field: optional int64 building_id = 2; - */ - buildingId?: bigint | undefined; - /** * Optional grid placement inside the target building. Must be set * together or both unset (a half-set position would be ambiguous). * Upper bounds are validated server-side against the target * building's aisles / racks_per_aisle. * - * @generated from field: optional int32 aisle_index = 3; + * @generated from field: optional int32 aisle_index = 2; */ aisleIndex?: number | undefined; /** - * @generated from field: optional int32 position_in_aisle = 4; + * @generated from field: optional int32 position_in_aisle = 3; */ positionInAisle?: number | undefined; }; /** - * Describes the message buildings.v1.AssignRackToBuildingRequest. - * Use `create(AssignRackToBuildingRequestSchema)` to create a new message. + * Describes the message buildings.v1.RackPlacement. + * Use `create(RackPlacementSchema)` to create a new message. */ -export const AssignRackToBuildingRequestSchema: GenMessage = +export const RackPlacementSchema: GenMessage = /*@__PURE__*/ messageDesc(file_buildings_v1_buildings, 15); /** - * @generated from message buildings.v1.AssignRackToBuildingResponse + * @generated from message buildings.v1.AssignRacksToBuildingRequest */ -export type AssignRackToBuildingResponse = Message<"buildings.v1.AssignRackToBuildingResponse"> & { +export type AssignRacksToBuildingRequest = Message<"buildings.v1.AssignRacksToBuildingRequest"> & { /** - * Cascade impact: how many descendant devices had their site_id - * re-stamped to the target building's site_id (or NULL when the - * rack moved to unassigned). Zero when no cascade was needed. + * @generated from field: repeated buildings.v1.RackPlacement racks = 1; + */ + racks: RackPlacement[]; + + /** + * Unset = unassign racks from any building. When present, must be > 0. + * + * @generated from field: optional int64 target_building_id = 2; + */ + targetBuildingId?: bigint | undefined; +}; + +/** + * Describes the message buildings.v1.AssignRacksToBuildingRequest. + * Use `create(AssignRacksToBuildingRequestSchema)` to create a new message. + */ +export const AssignRacksToBuildingRequestSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_buildings_v1_buildings, 16); + +/** + * @generated from message buildings.v1.AssignRacksToBuildingResponse + */ +export type AssignRacksToBuildingResponse = Message<"buildings.v1.AssignRacksToBuildingResponse"> & { + /** + * Cascade impact: aggregate descendant devices that had their + * site_id re-stamped across every rack in the batch. Zero when no + * cascade was needed. * * @generated from field: int64 site_reassigned_device_count = 1; */ @@ -601,12 +623,12 @@ export type AssignRackToBuildingResponse = Message<"buildings.v1.AssignRackToBui }; /** - * Describes the message buildings.v1.AssignRackToBuildingResponse. - * Use `create(AssignRackToBuildingResponseSchema)` to create a new message. + * Describes the message buildings.v1.AssignRacksToBuildingResponse. + * Use `create(AssignRacksToBuildingResponseSchema)` to create a new message. */ -export const AssignRackToBuildingResponseSchema: GenMessage = +export const AssignRacksToBuildingResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_buildings_v1_buildings, 16); + messageDesc(file_buildings_v1_buildings, 17); /** * @generated from message buildings.v1.GetBuildingStatsRequest @@ -624,7 +646,7 @@ export type GetBuildingStatsRequest = Message<"buildings.v1.GetBuildingStatsRequ */ export const GetBuildingStatsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_buildings_v1_buildings, 17); + messageDesc(file_buildings_v1_buildings, 18); /** * GetBuildingStatsResponse mirrors DeviceSetStats for the rollup fields @@ -746,7 +768,7 @@ export type GetBuildingStatsResponse = Message<"buildings.v1.GetBuildingStatsRes */ export const GetBuildingStatsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_buildings_v1_buildings, 18); + messageDesc(file_buildings_v1_buildings, 19); /** * BuildingRackHealth is the per-rack rollup returned by GetBuildingStats. @@ -804,7 +826,7 @@ export type BuildingRackHealth = Message<"buildings.v1.BuildingRackHealth"> & { */ export const BuildingRackHealthSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_buildings_v1_buildings, 19); + messageDesc(file_buildings_v1_buildings, 20); /** * RackOrderIndex mirrors device_set/v1's enum. We re-declare it here @@ -895,7 +917,7 @@ export const BuildingService: GenService<{ /** * UpdateBuilding mutates name, description, capacity, layout * defaults. Site assignment is *not* changed by UpdateBuilding — - * use AssignBuildingToSite (SiteService) for that, since it has + * use AssignBuildingsToSite (SiteService) for that, since it has * cross-collection enforcement. * * @generated from rpc buildings.v1.BuildingService.UpdateBuilding @@ -932,21 +954,23 @@ export const BuildingService: GenService<{ output: typeof ListBuildingRacksResponseSchema; }; /** - * AssignRackToBuilding sets the rack's building_id and optionally + * AssignRacksToBuilding sets each rack's building_id and optionally * positions it at (aisle_index, position_in_aisle) inside the - * building's grid. Leaving building_id unset unassigns the rack - * from any building. Position fields must be set together or - * both unset; the server clears them automatically on any - * building_id transition. Runs the same site-cascade for - * descendant device.site_id that SaveRack runs when the rack's - * site changes. + * target building's grid. Leaving target_building_id unset + * unassigns every rack in the batch from any building. Each entry + * carries its own optional placement; entries with unset placement + * have their grid cell cleared (single-call mix of placed + cleared + * racks is supported). The entire batch runs in one transaction; if + * any rack fails validation or its update, no row is touched. Runs + * the same site-cascade for descendant device.site_id that SaveRack + * runs when a rack's site changes. * - * @generated from rpc buildings.v1.BuildingService.AssignRackToBuilding + * @generated from rpc buildings.v1.BuildingService.AssignRacksToBuilding */ - assignRackToBuilding: { + assignRacksToBuilding: { methodKind: "unary"; - input: typeof AssignRackToBuildingRequestSchema; - output: typeof AssignRackToBuildingResponseSchema; + input: typeof AssignRacksToBuildingRequestSchema; + output: typeof AssignRacksToBuildingResponseSchema; }; /** * GetBuildingStats returns server-rolled telemetry + miner-state diff --git a/client/src/protoFleet/features/buildings/components/BuildingModals/BuildingModals.test.tsx b/client/src/protoFleet/features/buildings/components/BuildingModals/BuildingModals.test.tsx index b24648043..188f07046 100644 --- a/client/src/protoFleet/features/buildings/components/BuildingModals/BuildingModals.test.tsx +++ b/client/src/protoFleet/features/buildings/components/BuildingModals/BuildingModals.test.tsx @@ -21,7 +21,7 @@ const mockApi = { createBuilding: vi.fn(), updateBuilding: vi.fn(), deleteBuilding: vi.fn(), - assignRackToBuilding: vi.fn(), + assignRacksToBuilding: vi.fn(), }; vi.mock("@/protoFleet/api/buildings", async () => { const actual = await vi.importActual("@/protoFleet/api/buildings"); diff --git a/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx b/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx index b339ef350..babbcbe06 100644 --- a/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx +++ b/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx @@ -6,7 +6,7 @@ import { type AssignmentEntry, buildByNameAssignments, buildManualAssignments } import BuildingGridPane from "./BuildingGridPane"; import BuildingRacksPane, { type AssignedRackRow } from "./BuildingRacksPane"; import { type BuildingAssignmentMode, type GridCellKey, parseCellKey } from "./types"; -import { useBuildings } from "@/protoFleet/api/buildings"; +import { type RackPlacementInput, useBuildings } from "@/protoFleet/api/buildings"; import { type Building, type BuildingRack } from "@/protoFleet/api/generated/buildings/v1/buildings_pb"; import FullScreenTwoPaneModal from "@/protoFleet/components/FullScreenTwoPaneModal"; import { DismissCircle } from "@/shared/assets/icons"; @@ -42,7 +42,7 @@ const ManageBuildingModal = ({ onDeleteRequested, onSaved, }: ManageBuildingModalProps) => { - const { listBuildingRacks, assignRackToBuilding } = useBuildings(); + const { listBuildingRacks, assignRacksToBuilding } = useBuildings(); // Aisles / racks_per_aisle are read straight from the building prop — // BuildingSettingsModal owns those fields now and threads any edits back @@ -76,7 +76,7 @@ const ManageBuildingModal = ({ const [errorMsg, setErrorMsg] = useState(""); // Snapshot of the server's positions at load time so Save only fires - // assignRackToBuilding for racks whose position actually changed. Keyed + // assignRacksToBuilding for racks whose position actually changed. Keyed // by rackId → "aisle:position" (or "unplaced") so we can string-compare. const initialPlacementRef = useRef>(new Map()); @@ -331,14 +331,14 @@ const ManageBuildingModal = ({ ); // Save: walk activeAssignments, diff against the load-time snapshot, and - // fire AssignRackToBuilding in two phases: + // fire AssignRacksToBuilding in two phases: // // Phase 1 (vacate): every rack whose placement is changing AND that was // previously placed sends a "clear position" write first (same building, - // no aisle/position). Removed-from-building racks also unassign here so - // their cells free up before phase 2 lands. This frees every (building, - // aisle, position) tuple that will be re-used in phase 2 BEFORE any - // placement write runs. + // no aisle/position). Removed-from-building racks unassign in a + // separate call (different target_building_id). This frees every + // (building, aisle, position) tuple that will be re-used in phase 2 + // BEFORE any placement write runs. // // Phase 2 (place): every rack with a new placement target writes its // final (aisle, position). Because phase 1 already cleared every cell @@ -346,18 +346,20 @@ const ManageBuildingModal = ({ // no longer collide on the uk_device_set_rack_building_position partial // unique index. // - // Per-rack writes within a phase still run concurrently via Promise.all — - // serialization is *between* phases, not within. Layout writes live in + // Each phase issues at most two bulk calls (one per target building_id + // bucket: this building, or unassigned). Layout writes live in // BuildingSettingsModal. const handleSave = useCallback(async () => { setErrorMsg(""); setIsSaving(true); try { const initial = initialPlacementRef.current; - const vacates: Promise[] = []; - const places: Promise[] = []; const currentIds = new Set(entries.map((e) => e.rackId.toString())); + const vacateInBuilding: RackPlacementInput[] = []; + const placeInBuilding: RackPlacementInput[] = []; + const unassign: RackPlacementInput[] = []; + for (const entry of entries) { const idStr = entry.rackId.toString(); const placedKey = rackToCell.get(idStr); @@ -374,16 +376,7 @@ const ManageBuildingModal = ({ // Sending the same building with no aisle/position clears position // without touching membership. if (prior !== "unplaced" && prior !== "missing") { - vacates.push( - new Promise((resolve, reject) => { - void assignRackToBuilding({ - rackId: entry.rackId, - buildingId: building.id, - onSuccess: () => resolve(), - onError: (msg) => reject(new Error(msg)), - }); - }), - ); + vacateInBuilding.push({ rackId: entry.rackId }); } // Phase 2: write the new state. @@ -398,29 +391,13 @@ const ManageBuildingModal = ({ // by the phase-1 vacate above, which clears the cell. if (placedKey) { const { aisle, position } = parseCellKey(placedKey); - places.push( - new Promise((resolve, reject) => { - void assignRackToBuilding({ - rackId: entry.rackId, - buildingId: building.id, - aisleIndex: aisle, - positionInAisle: position, - onSuccess: () => resolve(), - onError: (msg) => reject(new Error(msg)), - }); - }), - ); + placeInBuilding.push({ + rackId: entry.rackId, + aisleIndex: aisle, + positionInAisle: position, + }); } else if (prior === "missing") { - places.push( - new Promise((resolve, reject) => { - void assignRackToBuilding({ - rackId: entry.rackId, - buildingId: building.id, - onSuccess: () => resolve(), - onError: (msg) => reject(new Error(msg)), - }); - }), - ); + placeInBuilding.push({ rackId: entry.rackId }); } } @@ -429,20 +406,25 @@ const ManageBuildingModal = ({ // before any phase-2 placement targets them. for (const idStr of initial.keys()) { if (currentIds.has(idStr)) continue; - vacates.push( - new Promise((resolve, reject) => { - void assignRackToBuilding({ - rackId: BigInt(idStr), - buildingId: undefined, - onSuccess: () => resolve(), - onError: (msg) => reject(new Error(msg)), - }); - }), - ); + unassign.push({ rackId: BigInt(idStr) }); } + const dispatch = (racks: RackPlacementInput[], targetBuildingId?: bigint) => + new Promise((resolve, reject) => { + if (racks.length === 0) { + resolve(); + return; + } + void assignRacksToBuilding({ + racks, + targetBuildingId, + onSuccess: () => resolve(), + onError: (msg) => reject(new Error(msg)), + }); + }); + try { - await Promise.all(vacates); + await Promise.all([dispatch(vacateInBuilding, building.id), dispatch(unassign, undefined)]); } catch (err) { setErrorMsg( err instanceof Error @@ -453,7 +435,7 @@ const ManageBuildingModal = ({ } try { - await Promise.all(places); + await dispatch(placeInBuilding, building.id); } catch (err) { setErrorMsg( err instanceof Error @@ -469,7 +451,7 @@ const ManageBuildingModal = ({ } finally { setIsSaving(false); } - }, [building, rackToCell, entries, assignRackToBuilding, onSaved, onDismiss]); + }, [building, rackToCell, entries, assignRacksToBuilding, onSaved, onDismiss]); if (!open) return null; diff --git a/client/src/protoFleet/features/buildings/components/ManageBuildingModal/types.ts b/client/src/protoFleet/features/buildings/components/ManageBuildingModal/types.ts index 7ce918194..d992504a9 100644 --- a/client/src/protoFleet/features/buildings/components/ManageBuildingModal/types.ts +++ b/client/src/protoFleet/features/buildings/components/ManageBuildingModal/types.ts @@ -16,7 +16,7 @@ export const parseCellKey = (key: GridCellKey): { aisle: number; position: numbe const [aisle, position] = key.split("-").map(Number); // All callers in this feature produce keys via cellKey() so this // guard is belt-and-suspenders — a NaN coordinate would silently - // flow into AssignRackToBuilding's aisle_index / position_in_aisle + // flow into AssignRacksToBuilding's aisle_index / position_in_aisle // params and surface as a server-side InvalidArgument far from the // cause. if (!Number.isFinite(aisle) || !Number.isFinite(position)) { diff --git a/client/src/protoFleet/features/fleetManagement/pages/RacksPage.tsx b/client/src/protoFleet/features/fleetManagement/pages/RacksPage.tsx index 05fd53606..ee65fd319 100644 --- a/client/src/protoFleet/features/fleetManagement/pages/RacksPage.tsx +++ b/client/src/protoFleet/features/fleetManagement/pages/RacksPage.tsx @@ -69,9 +69,9 @@ const RACK_COLUMNS_STANDALONE: DeviceSetColumn[] = [ const RacksPage = () => { const navigate = useNavigate(); const { listRacks, listRackZones, deleteGroup } = useDeviceSets(); - const { listAllBuildings, assignRackToBuilding } = useBuildings(); + const { listAllBuildings, assignRacksToBuilding } = useBuildings(); const canEditRack = useHasPermission("rack:manage"); - const canAssignRackToBuilding = useHasPermission("site:manage"); + const canAssignRacksToBuilding = useHasPermission("site:manage"); const [reparentTarget, setReparentTarget] = useState(null); const { listSites } = useSites(); const [searchParams, setSearchParams] = useSearchParams(); @@ -408,10 +408,10 @@ const RacksPage = () => { label: "Add to building", icon: , onClick: () => setReparentTarget(rack), - hidden: !canAssignRackToBuilding, + hidden: !canAssignRacksToBuilding, }, ], - [navigate, handleEditRack, canEditRack, canAssignRackToBuilding], + [navigate, handleEditRack, canEditRack, canAssignRacksToBuilding], ); const renderName = useCallback( @@ -761,9 +761,9 @@ const RacksPage = () => { return; } const rackName = reparentTarget.label || "rack"; - void assignRackToBuilding({ - rackId: reparentTarget.id, - buildingId, + void assignRacksToBuilding({ + racks: [{ rackId: reparentTarget.id }], + targetBuildingId: buildingId, onSuccess: () => { pushToast({ message: `Moved "${rackName}" to selected building.`, status: STATUSES.success }); resetAndFetch(); diff --git a/proto/buildings/v1/buildings.proto b/proto/buildings/v1/buildings.proto index 7116bf70c..01faaee6b 100644 --- a/proto/buildings/v1/buildings.proto +++ b/proto/buildings/v1/buildings.proto @@ -45,15 +45,17 @@ service BuildingService { // grid placement. rpc ListBuildingRacks(ListBuildingRacksRequest) returns (ListBuildingRacksResponse); - // AssignRackToBuilding sets the rack's building_id and optionally + // AssignRacksToBuilding sets each rack's building_id and optionally // positions it at (aisle_index, position_in_aisle) inside the - // building's grid. Leaving building_id unset unassigns the rack - // from any building. Position fields must be set together or - // both unset; the server clears them automatically on any - // building_id transition. Runs the same site-cascade for - // descendant device.site_id that SaveRack runs when the rack's - // site changes. - rpc AssignRackToBuilding(AssignRackToBuildingRequest) returns (AssignRackToBuildingResponse); + // target building's grid. Leaving target_building_id unset + // unassigns every rack in the batch from any building. Each entry + // carries its own optional placement; entries with unset placement + // have their grid cell cleared (single-call mix of placed + cleared + // racks is supported). The entire batch runs in one transaction; if + // any rack fails validation or its update, no row is touched. Runs + // the same site-cascade for descendant device.site_id that SaveRack + // runs when a rack's site changes. + rpc AssignRacksToBuilding(AssignRacksToBuildingRequest) returns (AssignRacksToBuildingResponse); // GetBuildingStats returns server-rolled telemetry + miner-state // counts for every device whose rack lives in the building, plus a @@ -245,18 +247,19 @@ message ListBuildingRacksResponse { string next_page_token = 2; } -// AssignRackToBuilding +// AssignRacksToBuilding -message AssignRackToBuildingRequest { +// RackPlacement carries one rack's identity plus its optional grid +// placement inside the target building. Used by AssignRacksToBuilding +// for bulk updates that mix placed + cleared positions in one call. +message RackPlacement { int64 rack_id = 1 [(buf.validate.field).int64.gt = 0]; - // Unset = unassign rack from any building. When present, must be > 0. - optional int64 building_id = 2 [(buf.validate.field).int64.gt = 0]; // Optional grid placement inside the target building. Must be set // together or both unset (a half-set position would be ambiguous). // Upper bounds are validated server-side against the target // building's aisles / racks_per_aisle. - optional int32 aisle_index = 3 [(buf.validate.field).int32.gte = 0]; - optional int32 position_in_aisle = 4 [(buf.validate.field).int32.gte = 0]; + optional int32 aisle_index = 2 [(buf.validate.field).int32.gte = 0]; + optional int32 position_in_aisle = 3 [(buf.validate.field).int32.gte = 0]; // aisle_index and position_in_aisle must be set together or both // unset. Setting one without the other is an invalid request. @@ -267,10 +270,19 @@ message AssignRackToBuildingRequest { }; } -message AssignRackToBuildingResponse { - // Cascade impact: how many descendant devices had their site_id - // re-stamped to the target building's site_id (or NULL when the - // rack moved to unassigned). Zero when no cascade was needed. +message AssignRacksToBuildingRequest { + repeated RackPlacement racks = 1 [(buf.validate.field).repeated = { + min_items: 1 + max_items: 1000 + }]; + // Unset = unassign racks from any building. When present, must be > 0. + optional int64 target_building_id = 2 [(buf.validate.field).int64.gt = 0]; +} + +message AssignRacksToBuildingResponse { + // Cascade impact: aggregate descendant devices that had their + // site_id re-stamped across every rack in the batch. Zero when no + // cascade was needed. int64 site_reassigned_device_count = 1; } diff --git a/server/generated/grpc/buildings/v1/buildings.pb.go b/server/generated/grpc/buildings/v1/buildings.pb.go index 543d7ab8c..a60be7fc6 100644 --- a/server/generated/grpc/buildings/v1/buildings.pb.go +++ b/server/generated/grpc/buildings/v1/buildings.pb.go @@ -1133,35 +1133,36 @@ func (x *ListBuildingRacksResponse) GetNextPageToken() string { return "" } -type AssignRackToBuildingRequest struct { +// RackPlacement carries one rack's identity plus its optional grid +// placement inside the target building. Used by AssignRacksToBuilding +// for bulk updates that mix placed + cleared positions in one call. +type RackPlacement struct { state protoimpl.MessageState `protogen:"open.v1"` RackId int64 `protobuf:"varint,1,opt,name=rack_id,json=rackId,proto3" json:"rack_id,omitempty"` - // Unset = unassign rack from any building. When present, must be > 0. - BuildingId *int64 `protobuf:"varint,2,opt,name=building_id,json=buildingId,proto3,oneof" json:"building_id,omitempty"` // Optional grid placement inside the target building. Must be set // together or both unset (a half-set position would be ambiguous). // Upper bounds are validated server-side against the target // building's aisles / racks_per_aisle. - AisleIndex *int32 `protobuf:"varint,3,opt,name=aisle_index,json=aisleIndex,proto3,oneof" json:"aisle_index,omitempty"` - PositionInAisle *int32 `protobuf:"varint,4,opt,name=position_in_aisle,json=positionInAisle,proto3,oneof" json:"position_in_aisle,omitempty"` + AisleIndex *int32 `protobuf:"varint,2,opt,name=aisle_index,json=aisleIndex,proto3,oneof" json:"aisle_index,omitempty"` + PositionInAisle *int32 `protobuf:"varint,3,opt,name=position_in_aisle,json=positionInAisle,proto3,oneof" json:"position_in_aisle,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AssignRackToBuildingRequest) Reset() { - *x = AssignRackToBuildingRequest{} +func (x *RackPlacement) Reset() { + *x = RackPlacement{} mi := &file_buildings_v1_buildings_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AssignRackToBuildingRequest) String() string { +func (x *RackPlacement) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AssignRackToBuildingRequest) ProtoMessage() {} +func (*RackPlacement) ProtoMessage() {} -func (x *AssignRackToBuildingRequest) ProtoReflect() protoreflect.Message { +func (x *RackPlacement) ProtoReflect() protoreflect.Message { mi := &file_buildings_v1_buildings_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1173,63 +1174,55 @@ func (x *AssignRackToBuildingRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AssignRackToBuildingRequest.ProtoReflect.Descriptor instead. -func (*AssignRackToBuildingRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use RackPlacement.ProtoReflect.Descriptor instead. +func (*RackPlacement) Descriptor() ([]byte, []int) { return file_buildings_v1_buildings_proto_rawDescGZIP(), []int{15} } -func (x *AssignRackToBuildingRequest) GetRackId() int64 { +func (x *RackPlacement) GetRackId() int64 { if x != nil { return x.RackId } return 0 } -func (x *AssignRackToBuildingRequest) GetBuildingId() int64 { - if x != nil && x.BuildingId != nil { - return *x.BuildingId - } - return 0 -} - -func (x *AssignRackToBuildingRequest) GetAisleIndex() int32 { +func (x *RackPlacement) GetAisleIndex() int32 { if x != nil && x.AisleIndex != nil { return *x.AisleIndex } return 0 } -func (x *AssignRackToBuildingRequest) GetPositionInAisle() int32 { +func (x *RackPlacement) GetPositionInAisle() int32 { if x != nil && x.PositionInAisle != nil { return *x.PositionInAisle } return 0 } -type AssignRackToBuildingResponse struct { +type AssignRacksToBuildingRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Cascade impact: how many descendant devices had their site_id - // re-stamped to the target building's site_id (or NULL when the - // rack moved to unassigned). Zero when no cascade was needed. - SiteReassignedDeviceCount int64 `protobuf:"varint,1,opt,name=site_reassigned_device_count,json=siteReassignedDeviceCount,proto3" json:"site_reassigned_device_count,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Racks []*RackPlacement `protobuf:"bytes,1,rep,name=racks,proto3" json:"racks,omitempty"` + // Unset = unassign racks from any building. When present, must be > 0. + TargetBuildingId *int64 `protobuf:"varint,2,opt,name=target_building_id,json=targetBuildingId,proto3,oneof" json:"target_building_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *AssignRackToBuildingResponse) Reset() { - *x = AssignRackToBuildingResponse{} +func (x *AssignRacksToBuildingRequest) Reset() { + *x = AssignRacksToBuildingRequest{} mi := &file_buildings_v1_buildings_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AssignRackToBuildingResponse) String() string { +func (x *AssignRacksToBuildingRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AssignRackToBuildingResponse) ProtoMessage() {} +func (*AssignRacksToBuildingRequest) ProtoMessage() {} -func (x *AssignRackToBuildingResponse) ProtoReflect() protoreflect.Message { +func (x *AssignRacksToBuildingRequest) ProtoReflect() protoreflect.Message { mi := &file_buildings_v1_buildings_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1241,12 +1234,66 @@ func (x *AssignRackToBuildingResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AssignRackToBuildingResponse.ProtoReflect.Descriptor instead. -func (*AssignRackToBuildingResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use AssignRacksToBuildingRequest.ProtoReflect.Descriptor instead. +func (*AssignRacksToBuildingRequest) Descriptor() ([]byte, []int) { return file_buildings_v1_buildings_proto_rawDescGZIP(), []int{16} } -func (x *AssignRackToBuildingResponse) GetSiteReassignedDeviceCount() int64 { +func (x *AssignRacksToBuildingRequest) GetRacks() []*RackPlacement { + if x != nil { + return x.Racks + } + return nil +} + +func (x *AssignRacksToBuildingRequest) GetTargetBuildingId() int64 { + if x != nil && x.TargetBuildingId != nil { + return *x.TargetBuildingId + } + return 0 +} + +type AssignRacksToBuildingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Cascade impact: aggregate descendant devices that had their + // site_id re-stamped across every rack in the batch. Zero when no + // cascade was needed. + SiteReassignedDeviceCount int64 `protobuf:"varint,1,opt,name=site_reassigned_device_count,json=siteReassignedDeviceCount,proto3" json:"site_reassigned_device_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssignRacksToBuildingResponse) Reset() { + *x = AssignRacksToBuildingResponse{} + mi := &file_buildings_v1_buildings_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssignRacksToBuildingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssignRacksToBuildingResponse) ProtoMessage() {} + +func (x *AssignRacksToBuildingResponse) ProtoReflect() protoreflect.Message { + mi := &file_buildings_v1_buildings_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssignRacksToBuildingResponse.ProtoReflect.Descriptor instead. +func (*AssignRacksToBuildingResponse) Descriptor() ([]byte, []int) { + return file_buildings_v1_buildings_proto_rawDescGZIP(), []int{17} +} + +func (x *AssignRacksToBuildingResponse) GetSiteReassignedDeviceCount() int64 { if x != nil { return x.SiteReassignedDeviceCount } @@ -1262,7 +1309,7 @@ type GetBuildingStatsRequest struct { func (x *GetBuildingStatsRequest) Reset() { *x = GetBuildingStatsRequest{} - mi := &file_buildings_v1_buildings_proto_msgTypes[17] + mi := &file_buildings_v1_buildings_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1274,7 +1321,7 @@ func (x *GetBuildingStatsRequest) String() string { func (*GetBuildingStatsRequest) ProtoMessage() {} func (x *GetBuildingStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_buildings_v1_buildings_proto_msgTypes[17] + mi := &file_buildings_v1_buildings_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1287,7 +1334,7 @@ func (x *GetBuildingStatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBuildingStatsRequest.ProtoReflect.Descriptor instead. func (*GetBuildingStatsRequest) Descriptor() ([]byte, []int) { - return file_buildings_v1_buildings_proto_rawDescGZIP(), []int{17} + return file_buildings_v1_buildings_proto_rawDescGZIP(), []int{18} } func (x *GetBuildingStatsRequest) GetBuildingId() int64 { @@ -1346,7 +1393,7 @@ type GetBuildingStatsResponse struct { func (x *GetBuildingStatsResponse) Reset() { *x = GetBuildingStatsResponse{} - mi := &file_buildings_v1_buildings_proto_msgTypes[18] + mi := &file_buildings_v1_buildings_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1358,7 +1405,7 @@ func (x *GetBuildingStatsResponse) String() string { func (*GetBuildingStatsResponse) ProtoMessage() {} func (x *GetBuildingStatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_buildings_v1_buildings_proto_msgTypes[18] + mi := &file_buildings_v1_buildings_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1371,7 +1418,7 @@ func (x *GetBuildingStatsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBuildingStatsResponse.ProtoReflect.Descriptor instead. func (*GetBuildingStatsResponse) Descriptor() ([]byte, []int) { - return file_buildings_v1_buildings_proto_rawDescGZIP(), []int{18} + return file_buildings_v1_buildings_proto_rawDescGZIP(), []int{19} } func (x *GetBuildingStatsResponse) GetBuildingId() int64 { @@ -1506,7 +1553,7 @@ type BuildingRackHealth struct { func (x *BuildingRackHealth) Reset() { *x = BuildingRackHealth{} - mi := &file_buildings_v1_buildings_proto_msgTypes[19] + mi := &file_buildings_v1_buildings_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1518,7 +1565,7 @@ func (x *BuildingRackHealth) String() string { func (*BuildingRackHealth) ProtoMessage() {} func (x *BuildingRackHealth) ProtoReflect() protoreflect.Message { - mi := &file_buildings_v1_buildings_proto_msgTypes[19] + mi := &file_buildings_v1_buildings_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1531,7 +1578,7 @@ func (x *BuildingRackHealth) ProtoReflect() protoreflect.Message { // Deprecated: Use BuildingRackHealth.ProtoReflect.Descriptor instead. func (*BuildingRackHealth) Descriptor() ([]byte, []int) { - return file_buildings_v1_buildings_proto_rawDescGZIP(), []int{19} + return file_buildings_v1_buildings_proto_rawDescGZIP(), []int{20} } func (x *BuildingRackHealth) GetRackId() int64 { @@ -1805,185 +1852,192 @@ var file_buildings_v1_buildings_proto_rawDesc = string([]byte{ 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x05, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xa7, - 0x03, 0x0a, 0x1b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x6f, 0x42, - 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, - 0x0a, 0x07, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, - 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x06, 0x72, 0x61, 0x63, 0x6b, 0x49, 0x64, - 0x12, 0x2d, 0x0a, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, - 0x52, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, - 0x2d, 0x0a, 0x0b, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xba, 0x48, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x48, 0x01, 0x52, - 0x0a, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x88, 0x01, 0x01, 0x12, 0x38, - 0x0a, 0x11, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x61, 0x69, - 0x73, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xba, 0x48, 0x04, 0x1a, 0x02, - 0x28, 0x00, 0x48, 0x02, 0x52, 0x0f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, - 0x41, 0x69, 0x73, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x3a, 0x97, 0x01, 0xba, 0x48, 0x93, 0x01, 0x1a, - 0x90, 0x01, 0x0a, 0x16, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x69, - 0x72, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x40, 0x61, 0x69, 0x73, 0x6c, - 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x20, 0x6d, 0x75, - 0x73, 0x74, 0x20, 0x62, 0x6f, 0x74, 0x68, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x74, 0x20, 0x6f, - 0x72, 0x20, 0x62, 0x6f, 0x74, 0x68, 0x20, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x1a, 0x34, 0x68, 0x61, - 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x61, 0x69, 0x73, 0x6c, - 0x65, 0x29, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x69, 0x64, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x6e, 0x5f, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x22, 0x5f, 0x0a, 0x1c, 0x41, 0x73, 0x73, 0x69, - 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x6f, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x1c, 0x73, 0x69, 0x74, 0x65, - 0x5f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, - 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x43, 0x0a, 0x17, 0x47, 0x65, 0x74, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, - 0x20, 0x00, 0x52, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0xda, - 0x05, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, - 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, - 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61, - 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x67, 0x5f, 0x65, 0x66, 0x66, - 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6a, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, 0x45, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, - 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x6f, 0x77, - 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, - 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, - 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, - 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, - 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, - 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x41, 0x0a, - 0x0b, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x63, 0x6b, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x52, 0x0a, 0x72, 0x61, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x12, 0x2d, 0x0a, 0x12, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, - 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x66, 0x66, - 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, - 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x65, - 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, - 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6f, 0x77, 0x65, 0x72, - 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xdd, 0x02, 0x0a, 0x12, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x72, 0x61, 0x63, 0x6b, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, - 0x61, 0x63, 0x6b, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x72, 0x61, 0x63, 0x6b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x24, 0x0a, 0x0b, 0x61, 0x69, - 0x73, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, - 0x00, 0x52, 0x0a, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x88, 0x01, 0x01, - 0x12, 0x2f, 0x0a, 0x11, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x5f, - 0x61, 0x69, 0x73, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x0f, 0x70, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x41, 0x69, 0x73, 0x6c, 0x65, 0x88, 0x01, - 0x01, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xda, + 0x02, 0x0a, 0x0d, 0x52, 0x61, 0x63, 0x6b, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x12, 0x20, 0x0a, 0x07, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x06, 0x72, 0x61, 0x63, 0x6b, + 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x0b, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xba, 0x48, 0x04, 0x1a, 0x02, 0x28, 0x00, + 0x48, 0x00, 0x52, 0x0a, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x88, 0x01, + 0x01, 0x12, 0x38, 0x0a, 0x11, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, + 0x5f, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xba, 0x48, + 0x04, 0x1a, 0x02, 0x28, 0x00, 0x48, 0x01, 0x52, 0x0f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x6e, 0x41, 0x69, 0x73, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x3a, 0x97, 0x01, 0xba, 0x48, + 0x93, 0x01, 0x1a, 0x90, 0x01, 0x0a, 0x16, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x70, 0x61, 0x69, 0x72, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x40, 0x61, + 0x69, 0x73, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x61, 0x69, 0x73, 0x6c, 0x65, + 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x6f, 0x74, 0x68, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, + 0x74, 0x20, 0x6f, 0x72, 0x20, 0x62, 0x6f, 0x74, 0x68, 0x20, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x1a, + 0x34, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x5f, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, + 0x69, 0x73, 0x2e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x61, + 0x69, 0x73, 0x6c, 0x65, 0x29, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x5f, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x22, 0xb1, 0x01, 0x0a, 0x1c, + 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x05, + 0x72, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x50, + 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x0b, 0xba, 0x48, 0x08, 0x92, 0x01, 0x05, + 0x08, 0x01, 0x10, 0xe8, 0x07, 0x52, 0x05, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x12, 0x3a, 0x0a, 0x12, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, + 0x00, 0x48, 0x00, 0x52, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x69, 0x6e, 0x67, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x22, + 0x60, 0x0a, 0x1d, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, + 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3f, 0x0a, 0x1c, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x22, 0x43, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0b, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0a, 0x62, 0x75, 0x69, 0x6c, + 0x64, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x22, 0xda, 0x05, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, + 0x6e, 0x67, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, + 0x65, 0x5f, 0x74, 0x68, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, 0x2c, 0x0a, + 0x12, 0x61, 0x76, 0x67, 0x5f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, + 0x6a, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, 0x45, 0x66, + 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4b, + 0x77, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, - 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, + 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x5f, - 0x69, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x2a, 0xb6, 0x01, 0x0a, 0x0e, - 0x52, 0x61, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, - 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, - 0x45, 0x58, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, - 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, 0x4f, 0x4d, 0x5f, 0x4c, 0x45, 0x46, 0x54, - 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, - 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, 0x4f, 0x50, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x10, - 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, - 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, 0x4f, 0x4d, 0x5f, 0x52, 0x49, 0x47, - 0x48, 0x54, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, - 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, 0x4f, 0x50, 0x5f, 0x52, 0x49, 0x47, - 0x48, 0x54, 0x10, 0x04, 0x32, 0x8e, 0x06, 0x0a, 0x0f, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, - 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x22, 0x2e, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, - 0x67, 0x12, 0x20, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, - 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x5b, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, - 0x6e, 0x67, 0x12, 0x23, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, - 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, - 0x11, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x63, - 0x6b, 0x73, 0x12, 0x26, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, - 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x62, 0x75, 0x69, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x41, 0x0a, 0x0b, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x68, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, + 0x6e, 0x67, 0x52, 0x61, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x0a, 0x72, 0x61, + 0x63, 0x6b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x2d, 0x0a, 0x12, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x18, 0x0d, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, + 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, + 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x32, 0x0a, 0x15, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, + 0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x22, 0xdd, 0x02, 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, + 0x52, 0x61, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x61, + 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x72, 0x61, 0x63, + 0x6b, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x12, 0x24, 0x0a, 0x0b, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x69, 0x73, 0x6c, 0x65, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x11, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x0f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x6e, 0x41, 0x69, 0x73, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, + 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, + 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, + 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x0e, 0x0a, + 0x0c, 0x5f, 0x61, 0x69, 0x73, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x14, 0x0a, + 0x12, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x5f, 0x61, 0x69, + 0x73, 0x6c, 0x65, 0x2a, 0xb6, 0x01, 0x0a, 0x0e, 0x52, 0x61, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, + 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, + 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, + 0x54, 0x4f, 0x4d, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x41, + 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, + 0x4f, 0x50, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, + 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, + 0x54, 0x54, 0x4f, 0x4d, 0x5f, 0x52, 0x49, 0x47, 0x48, 0x54, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, + 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, + 0x5f, 0x54, 0x4f, 0x50, 0x5f, 0x52, 0x49, 0x47, 0x48, 0x54, 0x10, 0x04, 0x32, 0x91, 0x06, 0x0a, + 0x0f, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x58, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, + 0x73, 0x12, 0x22, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, + 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0b, 0x47, 0x65, + 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x20, 0x2e, 0x62, 0x75, 0x69, 0x6c, + 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, + 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, + 0x12, 0x23, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x2e, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x2e, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x24, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x12, 0x26, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x6d, 0x0a, 0x14, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, - 0x6b, 0x54, 0x6f, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x29, 0x2e, 0x62, 0x75, - 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, - 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x6f, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, - 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, - 0x54, 0x6f, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x61, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, - 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, - 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, - 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xc0, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x62, 0x75, - 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x0e, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4b, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, - 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x62, 0x75, - 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x42, 0x58, 0x58, 0xaa, - 0x02, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, - 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, - 0x69, 0x6e, 0x67, 0x73, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, + 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x15, 0x41, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x69, 0x6e, 0x67, 0x12, 0x2a, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, + 0x6f, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2b, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, + 0x10, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x12, 0x25, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0xc0, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, + 0x67, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x0e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, + 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x62, 0x75, 0x69, 0x6c, + 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, + 0x67, 0x73, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x42, 0x58, 0x58, 0xaa, 0x02, 0x0c, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x69, 0x6e, 0x67, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x69, 0x6e, 0x67, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x3a, + 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -1999,35 +2053,36 @@ func file_buildings_v1_buildings_proto_rawDescGZIP() []byte { } var file_buildings_v1_buildings_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_buildings_v1_buildings_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_buildings_v1_buildings_proto_msgTypes = make([]protoimpl.MessageInfo, 21) var file_buildings_v1_buildings_proto_goTypes = []any{ - (RackOrderIndex)(0), // 0: buildings.v1.RackOrderIndex - (*Building)(nil), // 1: buildings.v1.Building - (*BuildingWithCounts)(nil), // 2: buildings.v1.BuildingWithCounts - (*ListBuildingsRequest)(nil), // 3: buildings.v1.ListBuildingsRequest - (*ListBuildingsResponse)(nil), // 4: buildings.v1.ListBuildingsResponse - (*GetBuildingRequest)(nil), // 5: buildings.v1.GetBuildingRequest - (*GetBuildingResponse)(nil), // 6: buildings.v1.GetBuildingResponse - (*CreateBuildingRequest)(nil), // 7: buildings.v1.CreateBuildingRequest - (*CreateBuildingResponse)(nil), // 8: buildings.v1.CreateBuildingResponse - (*UpdateBuildingRequest)(nil), // 9: buildings.v1.UpdateBuildingRequest - (*UpdateBuildingResponse)(nil), // 10: buildings.v1.UpdateBuildingResponse - (*DeleteBuildingRequest)(nil), // 11: buildings.v1.DeleteBuildingRequest - (*DeleteBuildingResponse)(nil), // 12: buildings.v1.DeleteBuildingResponse - (*ListBuildingRacksRequest)(nil), // 13: buildings.v1.ListBuildingRacksRequest - (*BuildingRack)(nil), // 14: buildings.v1.BuildingRack - (*ListBuildingRacksResponse)(nil), // 15: buildings.v1.ListBuildingRacksResponse - (*AssignRackToBuildingRequest)(nil), // 16: buildings.v1.AssignRackToBuildingRequest - (*AssignRackToBuildingResponse)(nil), // 17: buildings.v1.AssignRackToBuildingResponse - (*GetBuildingStatsRequest)(nil), // 18: buildings.v1.GetBuildingStatsRequest - (*GetBuildingStatsResponse)(nil), // 19: buildings.v1.GetBuildingStatsResponse - (*BuildingRackHealth)(nil), // 20: buildings.v1.BuildingRackHealth - (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp + (RackOrderIndex)(0), // 0: buildings.v1.RackOrderIndex + (*Building)(nil), // 1: buildings.v1.Building + (*BuildingWithCounts)(nil), // 2: buildings.v1.BuildingWithCounts + (*ListBuildingsRequest)(nil), // 3: buildings.v1.ListBuildingsRequest + (*ListBuildingsResponse)(nil), // 4: buildings.v1.ListBuildingsResponse + (*GetBuildingRequest)(nil), // 5: buildings.v1.GetBuildingRequest + (*GetBuildingResponse)(nil), // 6: buildings.v1.GetBuildingResponse + (*CreateBuildingRequest)(nil), // 7: buildings.v1.CreateBuildingRequest + (*CreateBuildingResponse)(nil), // 8: buildings.v1.CreateBuildingResponse + (*UpdateBuildingRequest)(nil), // 9: buildings.v1.UpdateBuildingRequest + (*UpdateBuildingResponse)(nil), // 10: buildings.v1.UpdateBuildingResponse + (*DeleteBuildingRequest)(nil), // 11: buildings.v1.DeleteBuildingRequest + (*DeleteBuildingResponse)(nil), // 12: buildings.v1.DeleteBuildingResponse + (*ListBuildingRacksRequest)(nil), // 13: buildings.v1.ListBuildingRacksRequest + (*BuildingRack)(nil), // 14: buildings.v1.BuildingRack + (*ListBuildingRacksResponse)(nil), // 15: buildings.v1.ListBuildingRacksResponse + (*RackPlacement)(nil), // 16: buildings.v1.RackPlacement + (*AssignRacksToBuildingRequest)(nil), // 17: buildings.v1.AssignRacksToBuildingRequest + (*AssignRacksToBuildingResponse)(nil), // 18: buildings.v1.AssignRacksToBuildingResponse + (*GetBuildingStatsRequest)(nil), // 19: buildings.v1.GetBuildingStatsRequest + (*GetBuildingStatsResponse)(nil), // 20: buildings.v1.GetBuildingStatsResponse + (*BuildingRackHealth)(nil), // 21: buildings.v1.BuildingRackHealth + (*timestamppb.Timestamp)(nil), // 22: google.protobuf.Timestamp } var file_buildings_v1_buildings_proto_depIdxs = []int32{ 0, // 0: buildings.v1.Building.default_rack_order_index:type_name -> buildings.v1.RackOrderIndex - 21, // 1: buildings.v1.Building.created_at:type_name -> google.protobuf.Timestamp - 21, // 2: buildings.v1.Building.updated_at:type_name -> google.protobuf.Timestamp + 22, // 1: buildings.v1.Building.created_at:type_name -> google.protobuf.Timestamp + 22, // 2: buildings.v1.Building.updated_at:type_name -> google.protobuf.Timestamp 1, // 3: buildings.v1.BuildingWithCounts.building:type_name -> buildings.v1.Building 2, // 4: buildings.v1.ListBuildingsResponse.buildings:type_name -> buildings.v1.BuildingWithCounts 1, // 5: buildings.v1.GetBuildingResponse.building:type_name -> buildings.v1.Building @@ -2036,28 +2091,29 @@ var file_buildings_v1_buildings_proto_depIdxs = []int32{ 0, // 8: buildings.v1.UpdateBuildingRequest.default_rack_order_index:type_name -> buildings.v1.RackOrderIndex 1, // 9: buildings.v1.UpdateBuildingResponse.building:type_name -> buildings.v1.Building 14, // 10: buildings.v1.ListBuildingRacksResponse.racks:type_name -> buildings.v1.BuildingRack - 20, // 11: buildings.v1.GetBuildingStatsResponse.rack_health:type_name -> buildings.v1.BuildingRackHealth - 3, // 12: buildings.v1.BuildingService.ListBuildings:input_type -> buildings.v1.ListBuildingsRequest - 5, // 13: buildings.v1.BuildingService.GetBuilding:input_type -> buildings.v1.GetBuildingRequest - 7, // 14: buildings.v1.BuildingService.CreateBuilding:input_type -> buildings.v1.CreateBuildingRequest - 9, // 15: buildings.v1.BuildingService.UpdateBuilding:input_type -> buildings.v1.UpdateBuildingRequest - 11, // 16: buildings.v1.BuildingService.DeleteBuilding:input_type -> buildings.v1.DeleteBuildingRequest - 13, // 17: buildings.v1.BuildingService.ListBuildingRacks:input_type -> buildings.v1.ListBuildingRacksRequest - 16, // 18: buildings.v1.BuildingService.AssignRackToBuilding:input_type -> buildings.v1.AssignRackToBuildingRequest - 18, // 19: buildings.v1.BuildingService.GetBuildingStats:input_type -> buildings.v1.GetBuildingStatsRequest - 4, // 20: buildings.v1.BuildingService.ListBuildings:output_type -> buildings.v1.ListBuildingsResponse - 6, // 21: buildings.v1.BuildingService.GetBuilding:output_type -> buildings.v1.GetBuildingResponse - 8, // 22: buildings.v1.BuildingService.CreateBuilding:output_type -> buildings.v1.CreateBuildingResponse - 10, // 23: buildings.v1.BuildingService.UpdateBuilding:output_type -> buildings.v1.UpdateBuildingResponse - 12, // 24: buildings.v1.BuildingService.DeleteBuilding:output_type -> buildings.v1.DeleteBuildingResponse - 15, // 25: buildings.v1.BuildingService.ListBuildingRacks:output_type -> buildings.v1.ListBuildingRacksResponse - 17, // 26: buildings.v1.BuildingService.AssignRackToBuilding:output_type -> buildings.v1.AssignRackToBuildingResponse - 19, // 27: buildings.v1.BuildingService.GetBuildingStats:output_type -> buildings.v1.GetBuildingStatsResponse - 20, // [20:28] is the sub-list for method output_type - 12, // [12:20] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 16, // 11: buildings.v1.AssignRacksToBuildingRequest.racks:type_name -> buildings.v1.RackPlacement + 21, // 12: buildings.v1.GetBuildingStatsResponse.rack_health:type_name -> buildings.v1.BuildingRackHealth + 3, // 13: buildings.v1.BuildingService.ListBuildings:input_type -> buildings.v1.ListBuildingsRequest + 5, // 14: buildings.v1.BuildingService.GetBuilding:input_type -> buildings.v1.GetBuildingRequest + 7, // 15: buildings.v1.BuildingService.CreateBuilding:input_type -> buildings.v1.CreateBuildingRequest + 9, // 16: buildings.v1.BuildingService.UpdateBuilding:input_type -> buildings.v1.UpdateBuildingRequest + 11, // 17: buildings.v1.BuildingService.DeleteBuilding:input_type -> buildings.v1.DeleteBuildingRequest + 13, // 18: buildings.v1.BuildingService.ListBuildingRacks:input_type -> buildings.v1.ListBuildingRacksRequest + 17, // 19: buildings.v1.BuildingService.AssignRacksToBuilding:input_type -> buildings.v1.AssignRacksToBuildingRequest + 19, // 20: buildings.v1.BuildingService.GetBuildingStats:input_type -> buildings.v1.GetBuildingStatsRequest + 4, // 21: buildings.v1.BuildingService.ListBuildings:output_type -> buildings.v1.ListBuildingsResponse + 6, // 22: buildings.v1.BuildingService.GetBuilding:output_type -> buildings.v1.GetBuildingResponse + 8, // 23: buildings.v1.BuildingService.CreateBuilding:output_type -> buildings.v1.CreateBuildingResponse + 10, // 24: buildings.v1.BuildingService.UpdateBuilding:output_type -> buildings.v1.UpdateBuildingResponse + 12, // 25: buildings.v1.BuildingService.DeleteBuilding:output_type -> buildings.v1.DeleteBuildingResponse + 15, // 26: buildings.v1.BuildingService.ListBuildingRacks:output_type -> buildings.v1.ListBuildingRacksResponse + 18, // 27: buildings.v1.BuildingService.AssignRacksToBuilding:output_type -> buildings.v1.AssignRacksToBuildingResponse + 20, // 28: buildings.v1.BuildingService.GetBuildingStats:output_type -> buildings.v1.GetBuildingStatsResponse + 21, // [21:29] is the sub-list for method output_type + 13, // [13:21] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_buildings_v1_buildings_proto_init() } @@ -2073,14 +2129,15 @@ func file_buildings_v1_buildings_proto_init() { file_buildings_v1_buildings_proto_msgTypes[6].OneofWrappers = []any{} file_buildings_v1_buildings_proto_msgTypes[13].OneofWrappers = []any{} file_buildings_v1_buildings_proto_msgTypes[15].OneofWrappers = []any{} - file_buildings_v1_buildings_proto_msgTypes[19].OneofWrappers = []any{} + file_buildings_v1_buildings_proto_msgTypes[16].OneofWrappers = []any{} + file_buildings_v1_buildings_proto_msgTypes[20].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_buildings_v1_buildings_proto_rawDesc), len(file_buildings_v1_buildings_proto_rawDesc)), NumEnums: 1, - NumMessages: 20, + NumMessages: 21, NumExtensions: 0, NumServices: 1, }, diff --git a/server/generated/grpc/buildings/v1/buildingsv1connect/buildings.connect.go b/server/generated/grpc/buildings/v1/buildingsv1connect/buildings.connect.go index 1fd1ea24e..82c86a977 100644 --- a/server/generated/grpc/buildings/v1/buildingsv1connect/buildings.connect.go +++ b/server/generated/grpc/buildings/v1/buildingsv1connect/buildings.connect.go @@ -52,9 +52,9 @@ const ( // BuildingServiceListBuildingRacksProcedure is the fully-qualified name of the BuildingService's // ListBuildingRacks RPC. BuildingServiceListBuildingRacksProcedure = "/buildings.v1.BuildingService/ListBuildingRacks" - // BuildingServiceAssignRackToBuildingProcedure is the fully-qualified name of the BuildingService's - // AssignRackToBuilding RPC. - BuildingServiceAssignRackToBuildingProcedure = "/buildings.v1.BuildingService/AssignRackToBuilding" + // BuildingServiceAssignRacksToBuildingProcedure is the fully-qualified name of the + // BuildingService's AssignRacksToBuilding RPC. + BuildingServiceAssignRacksToBuildingProcedure = "/buildings.v1.BuildingService/AssignRacksToBuilding" // BuildingServiceGetBuildingStatsProcedure is the fully-qualified name of the BuildingService's // GetBuildingStats RPC. BuildingServiceGetBuildingStatsProcedure = "/buildings.v1.BuildingService/GetBuildingStats" @@ -78,7 +78,7 @@ type BuildingServiceClient interface { CreateBuilding(context.Context, *connect.Request[v1.CreateBuildingRequest]) (*connect.Response[v1.CreateBuildingResponse], error) // UpdateBuilding mutates name, description, capacity, layout // defaults. Site assignment is *not* changed by UpdateBuilding — - // use AssignBuildingToSite (SiteService) for that, since it has + // use AssignBuildingsToSite (SiteService) for that, since it has // cross-collection enforcement. UpdateBuilding(context.Context, *connect.Request[v1.UpdateBuildingRequest]) (*connect.Response[v1.UpdateBuildingResponse], error) // DeleteBuilding soft-deletes the building and, in the same @@ -91,15 +91,17 @@ type BuildingServiceClient interface { // grid view; the broader ListDeviceSets RPC stays unaware of // grid placement. ListBuildingRacks(context.Context, *connect.Request[v1.ListBuildingRacksRequest]) (*connect.Response[v1.ListBuildingRacksResponse], error) - // AssignRackToBuilding sets the rack's building_id and optionally + // AssignRacksToBuilding sets each rack's building_id and optionally // positions it at (aisle_index, position_in_aisle) inside the - // building's grid. Leaving building_id unset unassigns the rack - // from any building. Position fields must be set together or - // both unset; the server clears them automatically on any - // building_id transition. Runs the same site-cascade for - // descendant device.site_id that SaveRack runs when the rack's - // site changes. - AssignRackToBuilding(context.Context, *connect.Request[v1.AssignRackToBuildingRequest]) (*connect.Response[v1.AssignRackToBuildingResponse], error) + // target building's grid. Leaving target_building_id unset + // unassigns every rack in the batch from any building. Each entry + // carries its own optional placement; entries with unset placement + // have their grid cell cleared (single-call mix of placed + cleared + // racks is supported). The entire batch runs in one transaction; if + // any rack fails validation or its update, no row is touched. Runs + // the same site-cascade for descendant device.site_id that SaveRack + // runs when a rack's site changes. + AssignRacksToBuilding(context.Context, *connect.Request[v1.AssignRacksToBuildingRequest]) (*connect.Response[v1.AssignRacksToBuildingResponse], error) // GetBuildingStats returns server-rolled telemetry + miner-state // counts for every device whose rack lives in the building, plus a // per-rack health summary (rack id, label, position, state counts) @@ -148,9 +150,9 @@ func NewBuildingServiceClient(httpClient connect.HTTPClient, baseURL string, opt baseURL+BuildingServiceListBuildingRacksProcedure, opts..., ), - assignRackToBuilding: connect.NewClient[v1.AssignRackToBuildingRequest, v1.AssignRackToBuildingResponse]( + assignRacksToBuilding: connect.NewClient[v1.AssignRacksToBuildingRequest, v1.AssignRacksToBuildingResponse]( httpClient, - baseURL+BuildingServiceAssignRackToBuildingProcedure, + baseURL+BuildingServiceAssignRacksToBuildingProcedure, opts..., ), getBuildingStats: connect.NewClient[v1.GetBuildingStatsRequest, v1.GetBuildingStatsResponse]( @@ -163,14 +165,14 @@ func NewBuildingServiceClient(httpClient connect.HTTPClient, baseURL string, opt // buildingServiceClient implements BuildingServiceClient. type buildingServiceClient struct { - listBuildings *connect.Client[v1.ListBuildingsRequest, v1.ListBuildingsResponse] - getBuilding *connect.Client[v1.GetBuildingRequest, v1.GetBuildingResponse] - createBuilding *connect.Client[v1.CreateBuildingRequest, v1.CreateBuildingResponse] - updateBuilding *connect.Client[v1.UpdateBuildingRequest, v1.UpdateBuildingResponse] - deleteBuilding *connect.Client[v1.DeleteBuildingRequest, v1.DeleteBuildingResponse] - listBuildingRacks *connect.Client[v1.ListBuildingRacksRequest, v1.ListBuildingRacksResponse] - assignRackToBuilding *connect.Client[v1.AssignRackToBuildingRequest, v1.AssignRackToBuildingResponse] - getBuildingStats *connect.Client[v1.GetBuildingStatsRequest, v1.GetBuildingStatsResponse] + listBuildings *connect.Client[v1.ListBuildingsRequest, v1.ListBuildingsResponse] + getBuilding *connect.Client[v1.GetBuildingRequest, v1.GetBuildingResponse] + createBuilding *connect.Client[v1.CreateBuildingRequest, v1.CreateBuildingResponse] + updateBuilding *connect.Client[v1.UpdateBuildingRequest, v1.UpdateBuildingResponse] + deleteBuilding *connect.Client[v1.DeleteBuildingRequest, v1.DeleteBuildingResponse] + listBuildingRacks *connect.Client[v1.ListBuildingRacksRequest, v1.ListBuildingRacksResponse] + assignRacksToBuilding *connect.Client[v1.AssignRacksToBuildingRequest, v1.AssignRacksToBuildingResponse] + getBuildingStats *connect.Client[v1.GetBuildingStatsRequest, v1.GetBuildingStatsResponse] } // ListBuildings calls buildings.v1.BuildingService.ListBuildings. @@ -203,9 +205,9 @@ func (c *buildingServiceClient) ListBuildingRacks(ctx context.Context, req *conn return c.listBuildingRacks.CallUnary(ctx, req) } -// AssignRackToBuilding calls buildings.v1.BuildingService.AssignRackToBuilding. -func (c *buildingServiceClient) AssignRackToBuilding(ctx context.Context, req *connect.Request[v1.AssignRackToBuildingRequest]) (*connect.Response[v1.AssignRackToBuildingResponse], error) { - return c.assignRackToBuilding.CallUnary(ctx, req) +// AssignRacksToBuilding calls buildings.v1.BuildingService.AssignRacksToBuilding. +func (c *buildingServiceClient) AssignRacksToBuilding(ctx context.Context, req *connect.Request[v1.AssignRacksToBuildingRequest]) (*connect.Response[v1.AssignRacksToBuildingResponse], error) { + return c.assignRacksToBuilding.CallUnary(ctx, req) } // GetBuildingStats calls buildings.v1.BuildingService.GetBuildingStats. @@ -231,7 +233,7 @@ type BuildingServiceHandler interface { CreateBuilding(context.Context, *connect.Request[v1.CreateBuildingRequest]) (*connect.Response[v1.CreateBuildingResponse], error) // UpdateBuilding mutates name, description, capacity, layout // defaults. Site assignment is *not* changed by UpdateBuilding — - // use AssignBuildingToSite (SiteService) for that, since it has + // use AssignBuildingsToSite (SiteService) for that, since it has // cross-collection enforcement. UpdateBuilding(context.Context, *connect.Request[v1.UpdateBuildingRequest]) (*connect.Response[v1.UpdateBuildingResponse], error) // DeleteBuilding soft-deletes the building and, in the same @@ -244,15 +246,17 @@ type BuildingServiceHandler interface { // grid view; the broader ListDeviceSets RPC stays unaware of // grid placement. ListBuildingRacks(context.Context, *connect.Request[v1.ListBuildingRacksRequest]) (*connect.Response[v1.ListBuildingRacksResponse], error) - // AssignRackToBuilding sets the rack's building_id and optionally + // AssignRacksToBuilding sets each rack's building_id and optionally // positions it at (aisle_index, position_in_aisle) inside the - // building's grid. Leaving building_id unset unassigns the rack - // from any building. Position fields must be set together or - // both unset; the server clears them automatically on any - // building_id transition. Runs the same site-cascade for - // descendant device.site_id that SaveRack runs when the rack's - // site changes. - AssignRackToBuilding(context.Context, *connect.Request[v1.AssignRackToBuildingRequest]) (*connect.Response[v1.AssignRackToBuildingResponse], error) + // target building's grid. Leaving target_building_id unset + // unassigns every rack in the batch from any building. Each entry + // carries its own optional placement; entries with unset placement + // have their grid cell cleared (single-call mix of placed + cleared + // racks is supported). The entire batch runs in one transaction; if + // any rack fails validation or its update, no row is touched. Runs + // the same site-cascade for descendant device.site_id that SaveRack + // runs when a rack's site changes. + AssignRacksToBuilding(context.Context, *connect.Request[v1.AssignRacksToBuildingRequest]) (*connect.Response[v1.AssignRacksToBuildingResponse], error) // GetBuildingStats returns server-rolled telemetry + miner-state // counts for every device whose rack lives in the building, plus a // per-rack health summary (rack id, label, position, state counts) @@ -297,9 +301,9 @@ func NewBuildingServiceHandler(svc BuildingServiceHandler, opts ...connect.Handl svc.ListBuildingRacks, opts..., ) - buildingServiceAssignRackToBuildingHandler := connect.NewUnaryHandler( - BuildingServiceAssignRackToBuildingProcedure, - svc.AssignRackToBuilding, + buildingServiceAssignRacksToBuildingHandler := connect.NewUnaryHandler( + BuildingServiceAssignRacksToBuildingProcedure, + svc.AssignRacksToBuilding, opts..., ) buildingServiceGetBuildingStatsHandler := connect.NewUnaryHandler( @@ -321,8 +325,8 @@ func NewBuildingServiceHandler(svc BuildingServiceHandler, opts ...connect.Handl buildingServiceDeleteBuildingHandler.ServeHTTP(w, r) case BuildingServiceListBuildingRacksProcedure: buildingServiceListBuildingRacksHandler.ServeHTTP(w, r) - case BuildingServiceAssignRackToBuildingProcedure: - buildingServiceAssignRackToBuildingHandler.ServeHTTP(w, r) + case BuildingServiceAssignRacksToBuildingProcedure: + buildingServiceAssignRacksToBuildingHandler.ServeHTTP(w, r) case BuildingServiceGetBuildingStatsProcedure: buildingServiceGetBuildingStatsHandler.ServeHTTP(w, r) default: @@ -358,8 +362,8 @@ func (UnimplementedBuildingServiceHandler) ListBuildingRacks(context.Context, *c return nil, connect.NewError(connect.CodeUnimplemented, errors.New("buildings.v1.BuildingService.ListBuildingRacks is not implemented")) } -func (UnimplementedBuildingServiceHandler) AssignRackToBuilding(context.Context, *connect.Request[v1.AssignRackToBuildingRequest]) (*connect.Response[v1.AssignRackToBuildingResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("buildings.v1.BuildingService.AssignRackToBuilding is not implemented")) +func (UnimplementedBuildingServiceHandler) AssignRacksToBuilding(context.Context, *connect.Request[v1.AssignRacksToBuildingRequest]) (*connect.Response[v1.AssignRacksToBuildingResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("buildings.v1.BuildingService.AssignRacksToBuilding is not implemented")) } func (UnimplementedBuildingServiceHandler) GetBuildingStats(context.Context, *connect.Request[v1.GetBuildingStatsRequest]) (*connect.Response[v1.GetBuildingStatsResponse], error) { diff --git a/server/internal/domain/buildings/models/models.go b/server/internal/domain/buildings/models/models.go index c17be9181..01c5267e3 100644 --- a/server/internal/domain/buildings/models/models.go +++ b/server/internal/domain/buildings/models/models.go @@ -110,13 +110,11 @@ type BuildingRack struct { PositionInAisle *int32 } -// AssignRackToBuildingParams is the input shape for assigning (or -// unassigning) a rack to a building, optionally with a grid cell. -type AssignRackToBuildingParams struct { - OrgID int64 +// RackPlacementParam carries one rack's identity plus its optional +// grid placement inside the target building. Used by +// AssignRacksToBuilding for bulk updates. +type RackPlacementParam struct { RackID int64 - // BuildingID is nil when unassigning the rack from any building. - BuildingID *int64 // AisleIndex / PositionInAisle are nil when the caller is not // positioning the rack at a specific cell. Must be paired (both // nil or both set); enforced at the service edge. @@ -124,9 +122,20 @@ type AssignRackToBuildingParams struct { PositionInAisle *int32 } -// AssignRackToBuildingResult is the response shape carrying the -// cascade impact count for the activity log + UI confirmation. -type AssignRackToBuildingResult struct { +// AssignRacksToBuildingParams is the input shape for the bulk +// rack→building assignment flow. TargetBuildingID is nil when +// unassigning every rack in the batch from any building. Each entry +// in Racks may carry its own grid placement (or leave it nil to clear +// the cell). +type AssignRacksToBuildingParams struct { + OrgID int64 + Racks []RackPlacementParam + TargetBuildingID *int64 +} + +// AssignRacksToBuildingResult is the aggregate response carrying the +// total cascade impact count across every rack in the batch. +type AssignRacksToBuildingResult struct { SiteReassignedDeviceCount int64 } diff --git a/server/internal/domain/buildings/service.go b/server/internal/domain/buildings/service.go index c4a486ee5..32821e4e2 100644 --- a/server/internal/domain/buildings/service.go +++ b/server/internal/domain/buildings/service.go @@ -7,6 +7,7 @@ package buildings import ( "context" "fmt" + "sort" fm "github.com/block/proto-fleet/server/generated/grpc/fleetmanagement/v1" "github.com/block/proto-fleet/server/internal/domain/activity" @@ -154,7 +155,7 @@ func (s *Service) UpdateBuilding(ctx context.Context, params models.UpdateParams var b *models.Building err := s.transactor.RunInTx(ctx, func(txCtx context.Context) error { // Lock the building row first so a concurrent - // AssignRackToBuilding can't race us into orphaned-position + // AssignRacksToBuilding can't race us into orphaned-position // state between the bounds check and the update. if err := s.siteStore.LockBuildingForWrite(txCtx, params.OrgID, params.ID); err != nil { return err @@ -259,141 +260,172 @@ func (s *Service) ListBuildingRacks(ctx context.Context, orgID, buildingID int64 return s.store.ListBuildingRacks(ctx, orgID, buildingID, pageSize, pageToken) } -// AssignRackToBuilding sets a rack's building_id and, optionally, its -// grid placement (aisle_index, position_in_aisle). Runs in a single -// transaction: +// AssignRacksToBuilding sets the building_id (and optional grid +// placement) of every rack in the batch. Runs in a single transaction: // -// 1. Lock the target building (when assigning) so a concurrent -// DeleteBuilding can't race the placement write. -// 2. Lock the rack row and read current placement. -// 3. Resolve the new site_id from the target building (or NULL when -// unassigning). -// 4. Validate the optional grid cell against the target building's -// aisles / racks_per_aisle. -// 5. Call collectionStore.UpdateRackPlacement to write site_id + -// building_id + zone atomically (zone is cleared on cross/leave -// building, mirroring the existing SaveRack cascade rule). -// 6. Cascade descendant device.site_id when the rack's site changes. -// 7. When the request includes a grid cell, write it via -// SetRackBuildingPosition. -func (s *Service) AssignRackToBuilding(ctx context.Context, params models.AssignRackToBuildingParams) (*models.AssignRackToBuildingResult, error) { - // Position fields must be paired. The proto CEL rule enforces - // this at the wire boundary; this is the defense-in-depth check - // for non-proto callers. - if (params.AisleIndex == nil) != (params.PositionInAisle == nil) { - return nil, fleeterror.NewInvalidArgumentError("aisle_index and position_in_aisle must both be set or both unset") - } - if params.AisleIndex != nil && params.BuildingID == nil { - return nil, fleeterror.NewInvalidArgumentError("a grid cell (aisle_index, position_in_aisle) requires a building_id") - } - if params.AisleIndex != nil && *params.AisleIndex < 0 { - return nil, fleeterror.NewInvalidArgumentError("aisle_index must be >= 0") - } - if params.PositionInAisle != nil && *params.PositionInAisle < 0 { - return nil, fleeterror.NewInvalidArgumentError("position_in_aisle must be >= 0") +// 1. Lock the target building once (when assigning), canonical lock +// order is building -> rack(s). +// 2. Validate every entry up-front (paired position fields, in-bounds +// aisle/position). The whole batch rejects on any invalid entry. +// 3. For each rack (sorted by id for deadlock-safe lock order): +// a. Lock the rack row and read current placement. +// b. Resolve the new site_id from the target building (or preserve +// current.SiteID on building-only unassign). +// c. Compute final zone (clear on leave/cross building). +// d. Persist site_id + building_id + zone via UpdateRackPlacement. +// e. Cascade descendant device.site_id when the site changes; sum +// the per-rack counts into the aggregate result. +// f. Write the grid cell via SetRackBuildingPosition when the rack +// is assigned to a building (placed or explicitly cleared). +// +// If any rack fails, the whole tx rolls back and no row is touched. +func (s *Service) AssignRacksToBuilding(ctx context.Context, params models.AssignRacksToBuildingParams) (*models.AssignRacksToBuildingResult, error) { + if len(params.Racks) == 0 { + return nil, fleeterror.NewInvalidArgumentError("racks must not be empty") + } + + // Per-entry validation runs before any I/O so a bad request fails + // fast without partial work. Defense-in-depth — the proto CEL rule + // also enforces position pairing. + for _, rp := range params.Racks { + if rp.RackID <= 0 { + return nil, fleeterror.NewInvalidArgumentError("rack_id must be > 0") + } + if (rp.AisleIndex == nil) != (rp.PositionInAisle == nil) { + return nil, fleeterror.NewInvalidArgumentError("aisle_index and position_in_aisle must both be set or both unset") + } + if rp.AisleIndex != nil && params.TargetBuildingID == nil { + return nil, fleeterror.NewInvalidArgumentError("a grid cell (aisle_index, position_in_aisle) requires a target_building_id") + } + if rp.AisleIndex != nil && *rp.AisleIndex < 0 { + return nil, fleeterror.NewInvalidArgumentError("aisle_index must be >= 0") + } + if rp.PositionInAisle != nil && *rp.PositionInAisle < 0 { + return nil, fleeterror.NewInvalidArgumentError("position_in_aisle must be >= 0") + } } + // Sort rack entries by id for stable lock order so two concurrent + // AssignRacksToBuilding calls overlapping on a rack set can't + // deadlock. + racks := make([]models.RackPlacementParam, len(params.Racks)) + copy(racks, params.Racks) + sort.Slice(racks, func(i, j int) bool { return racks[i].RackID < racks[j].RackID }) + var ( - out models.AssignRackToBuildingResult - newSiteID *int64 - cascadeRan bool + out models.AssignRacksToBuildingResult + targetSiteID *int64 + cascadeRackIDs []int64 + positionedRackIDs []int64 ) err := s.transactor.RunInTx(ctx, func(txCtx context.Context) error { // Lock the target building first (canonical lock order: // building -> rack). Skip when unassigning — there is no - // building row to lock — but we still lock the rack below. + // building row to lock — but each rack still gets row-locked + // below. var targetBuilding *models.Building - if params.BuildingID != nil { - if err := s.siteStore.LockBuildingForWrite(txCtx, params.OrgID, *params.BuildingID); err != nil { + if params.TargetBuildingID != nil { + if err := s.siteStore.LockBuildingForWrite(txCtx, params.OrgID, *params.TargetBuildingID); err != nil { return err } - b, err := s.store.GetBuilding(txCtx, params.OrgID, *params.BuildingID) + b, err := s.store.GetBuilding(txCtx, params.OrgID, *params.TargetBuildingID) if err != nil { return err } targetBuilding = b - newSiteID = b.SiteID + targetSiteID = b.SiteID } // Grid-cell upper-bound validation has to run after we know // the target building's layout dimensions. - if params.AisleIndex != nil && targetBuilding != nil { - if targetBuilding.Aisles <= 0 || *params.AisleIndex >= targetBuilding.Aisles { - return fleeterror.NewInvalidArgumentErrorf("aisle_index %d is out of bounds (building has %d aisles)", *params.AisleIndex, targetBuilding.Aisles) - } - if targetBuilding.RacksPerAisle <= 0 || *params.PositionInAisle >= targetBuilding.RacksPerAisle { - return fleeterror.NewInvalidArgumentErrorf("position_in_aisle %d is out of bounds (building allows %d racks per aisle)", *params.PositionInAisle, targetBuilding.RacksPerAisle) + if targetBuilding != nil { + for _, rp := range racks { + if rp.AisleIndex == nil { + continue + } + if targetBuilding.Aisles <= 0 || *rp.AisleIndex >= targetBuilding.Aisles { + return fleeterror.NewInvalidArgumentErrorf("aisle_index %d is out of bounds (building has %d aisles)", *rp.AisleIndex, targetBuilding.Aisles) + } + if targetBuilding.RacksPerAisle <= 0 || *rp.PositionInAisle >= targetBuilding.RacksPerAisle { + return fleeterror.NewInvalidArgumentErrorf("position_in_aisle %d is out of bounds (building allows %d racks per aisle)", *rp.PositionInAisle, targetBuilding.RacksPerAisle) + } } } - // Lock the rack row and read its current placement so we can - // decide whether the cascade needs to run + what zone value - // to persist. - current, err := s.collectionStore.LockRackPlacementForWrite(txCtx, params.RackID, params.OrgID) - if err != nil { - return err - } - - // Building-only unassign must NOT cascade-clear the rack's site - // (and, transitively, every descendant device.site_id). Removing - // a rack from a building is a building-membership change; the - // rack and its devices stay in their current site until an - // explicit site-level unassign happens elsewhere. Preserve - // current.SiteID in that branch so the siteChanged check below - // reads false and the cascade stays inert. - if params.BuildingID == nil { - newSiteID = current.SiteID - } + for _, rp := range racks { + // Lock the rack row and read its current placement so we + // can decide whether the cascade needs to run + what zone + // value to persist. + current, err := s.collectionStore.LockRackPlacementForWrite(txCtx, rp.RackID, params.OrgID) + if err != nil { + return err + } - // Mirror SaveRack's zone-clear cascade: clear zone when the - // rack leaves a building or crosses to a different one. - // Preserve the current zone on a no-op building transition - // so legacy callers don't strip zone unintentionally. - finalZone := current.Zone - leavingBuilding := current.BuildingID != nil && params.BuildingID == nil - crossingBuildings := current.BuildingID != nil && params.BuildingID != nil && *current.BuildingID != *params.BuildingID - if leavingBuilding || crossingBuildings { - finalZone = "" - } + // Building-only unassign must NOT cascade-clear the rack's + // site (and, transitively, every descendant device.site_id). + // Removing a rack from a building is a building-membership + // change; the rack and its devices stay in their current + // site until an explicit site-level unassign happens + // elsewhere. Preserve current.SiteID in that branch so the + // siteChanged check below reads false and the cascade stays + // inert. + newSiteID := targetSiteID + if params.TargetBuildingID == nil { + newSiteID = current.SiteID + } - // Persist site_id + building_id + zone in one write. The - // query also clears the grid position on building transition - // via a CASE expression, so a stale (aisle_index, - // position_in_aisle) never outlives its parent building. - if err := s.collectionStore.UpdateRackPlacement(txCtx, params.RackID, params.OrgID, newSiteID, params.BuildingID, finalZone); err != nil { - return err - } + // Mirror SaveRack's zone-clear cascade: clear zone when the + // rack leaves a building or crosses to a different one. + // Preserve the current zone on a no-op building transition + // so legacy callers don't strip zone unintentionally. + finalZone := current.Zone + leavingBuilding := current.BuildingID != nil && params.TargetBuildingID == nil + crossingBuildings := current.BuildingID != nil && params.TargetBuildingID != nil && *current.BuildingID != *params.TargetBuildingID + if leavingBuilding || crossingBuildings { + finalZone = "" + } - // Cascade descendant device.site_id when the rack's site - // changed. CascadeRackDeviceSites returns the row count. - siteChanged := !int64PtrEqual(current.SiteID, newSiteID) - if siteChanged { - count, err := s.collectionStore.CascadeRackDeviceSites(txCtx, params.RackID, params.OrgID, newSiteID) - if err != nil { + // Persist site_id + building_id + zone in one write. The + // query also clears the grid position on building + // transition via a CASE expression, so a stale + // (aisle_index, position_in_aisle) never outlives its + // parent building. + if err := s.collectionStore.UpdateRackPlacement(txCtx, rp.RackID, params.OrgID, newSiteID, params.TargetBuildingID, finalZone); err != nil { return err } - out.SiteReassignedDeviceCount = count - cascadeRan = true - } - // Grid-cell write. Two cases land here: - // - // - Both fields set → write the explicit (aisle, position). - // - Both fields nil + building_id is set → operator is - // unplacing the rack within the same building (or moving - // it across with no chosen cell yet). Write NULL/NULL so - // the cell on the rack row matches the operator's intent. - // UpdateRackPlacement's CASE only clears when building_id - // changes, so without this explicit write a same-building - // unplace would silently no-op and the old position would - // survive. - // - // When BuildingID is nil (full unassign) we skip this call — - // UpdateRackPlacement's CASE already nulls the position via - // the building-id-changed branch. - if params.BuildingID != nil { - if err := s.store.SetRackBuildingPosition(txCtx, params.OrgID, params.RackID, params.AisleIndex, params.PositionInAisle); err != nil { - return err + // Cascade descendant device.site_id when the rack's site + // changed. CascadeRackDeviceSites returns the row count. + siteChanged := !int64PtrEqual(current.SiteID, newSiteID) + if siteChanged { + count, err := s.collectionStore.CascadeRackDeviceSites(txCtx, rp.RackID, params.OrgID, newSiteID) + if err != nil { + return err + } + out.SiteReassignedDeviceCount += count + cascadeRackIDs = append(cascadeRackIDs, rp.RackID) + } + + // Grid-cell write. Two cases land here: + // + // - Both fields set → write the explicit (aisle, position). + // - Both fields nil + target_building_id is set → operator + // is unplacing the rack within the same building (or + // moving it across with no chosen cell yet). Write + // NULL/NULL so the cell on the rack row matches the + // operator's intent. UpdateRackPlacement's CASE only + // clears when building_id changes, so without this + // explicit write a same-building unplace would silently + // no-op and the old position would survive. + // + // When TargetBuildingID is nil (full unassign) we skip this + // call — UpdateRackPlacement's CASE already nulls the + // position via the building-id-changed branch. + if params.TargetBuildingID != nil { + if err := s.store.SetRackBuildingPosition(txCtx, params.OrgID, rp.RackID, rp.AisleIndex, rp.PositionInAisle); err != nil { + return err + } + positionedRackIDs = append(positionedRackIDs, rp.RackID) } } return nil @@ -402,43 +434,37 @@ func (s *Service) AssignRackToBuilding(ctx context.Context, params models.Assign return nil, err } - // Activity log fires AFTER tx commits. SiteID is the rack's final - // site after the write — same as newSiteID, which now equals - // current.SiteID on building-only unassign (so we don't lose the - // site filter on building-removal events) and the target - // building's site otherwise. Using cascadeSite here would only - // populate when CascadeRackDeviceSites ran, hiding same-site - // assigns from site-scoped activity queries. + // Activity log fires AFTER tx commits. orgIDVal := params.OrgID - // Dereference the building id stored in metadata so JSON shape - // matches DeleteBuilding (int64, not *int64). Downstream consumers - // doing `.(int64)` on the metadata field would crash on the - // pointer variant. var buildingIDMeta any - if params.BuildingID != nil { - buildingIDMeta = *params.BuildingID + if params.TargetBuildingID != nil { + buildingIDMeta = *params.TargetBuildingID + } + rackIDs := make([]int64, len(racks)) + for i, rp := range racks { + rackIDs[i] = rp.RackID } event := activitymodels.Event{ Category: activitymodels.CategoryFleetManagement, Type: eventRackAssignedBuilding, OrganizationID: &orgIDVal, - SiteID: newSiteID, + SiteID: targetSiteID, Description: fmt.Sprintf( - "Assigned rack %d to building %v", - params.RackID, derefInt64(params.BuildingID), + "Assigned %d rack(s) to building %v", + len(racks), derefInt64(params.TargetBuildingID), ), Metadata: map[string]any{ - "rack_id": params.RackID, + "rack_ids": rackIDs, "building_id": buildingIDMeta, }, } - if cascadeRan { + if len(cascadeRackIDs) > 0 { event.Metadata["site_cascade"] = true + event.Metadata["site_cascaded_rack_ids"] = cascadeRackIDs event.Metadata["site_reassigned_device_count"] = out.SiteReassignedDeviceCount } - if params.AisleIndex != nil { - event.Metadata["aisle_index"] = *params.AisleIndex - event.Metadata["position_in_aisle"] = *params.PositionInAisle + if len(positionedRackIDs) > 0 { + event.Metadata["positioned_rack_ids"] = positionedRackIDs } activity.StampActor(ctx, &event) s.activitySvc.Log(ctx, event) @@ -579,7 +605,7 @@ func (s *Service) GetBuildingStats(ctx context.Context, orgID, buildingID int64, // Resolve floor-plan bounds for the out-of-range filter below. A rack // with aisle_index >= aisles or position_in_aisle >= racks_per_aisle - // shouldn't normally exist (AssignRackToBuilding + UpdateBuilding both + // shouldn't normally exist (AssignRacksToBuilding + UpdateBuilding both // validate), but the FE silently drops cells outside the rendered // grid, so we clear the position fields server-side here for defense // in depth — the rack still appears in rack_health[] without a cell. @@ -607,7 +633,7 @@ func (s *Service) GetBuildingStats(ctx context.Context, orgID, buildingID int64, // Per-rack state counts via the existing collection-membership query. // // Residual race window (intentionally not guarded): if - // AssignRackToBuilding moves a rack out of this building between + // AssignRacksToBuilding moves a rack out of this building between // the ListBuildingRacks above and this counts read, the response // still includes per-rack state counts (hashing/broken/offline/ // sleeping totals) for that rack. The post-read building.SiteID diff --git a/server/internal/domain/buildings/service_test.go b/server/internal/domain/buildings/service_test.go index 3d3ffe26b..7374137ec 100644 --- a/server/internal/domain/buildings/service_test.go +++ b/server/internal/domain/buildings/service_test.go @@ -185,7 +185,7 @@ func TestListBuildings_rejectsExclusiveFilters(t *testing.T) { } } -// Helper: assemble the full mock set for AssignRackToBuilding tests. +// Helper: assemble the full mock set for AssignRacksToBuilding tests. type assignHarness struct { store *mocks.MockBuildingStore siteStore *mocks.MockSiteStore @@ -213,7 +213,7 @@ func newAssignHarness(t *testing.T) *assignHarness { // Assign with a grid cell: lock building, lock rack, write placement, // write grid cell, no site cascade because target site matches current. -func TestAssignRackToBuilding_placesRackWithGridCell(t *testing.T) { +func TestAssignRacksToBuilding_placesRackWithGridCell(t *testing.T) { h := newAssignHarness(t) buildingID := int64(11) rackID := int64(99) @@ -231,12 +231,14 @@ func TestAssignRackToBuilding_placesRackWithGridCell(t *testing.T) { h.store.EXPECT().SetRackBuildingPosition(inTxCtx, testOrgID, rackID, ptrInt32(1), ptrInt32(2)).Return(nil), ) - out, err := h.svc.AssignRackToBuilding(context.Background(), models.AssignRackToBuildingParams{ - OrgID: testOrgID, - RackID: rackID, - BuildingID: &buildingID, - AisleIndex: ptrInt32(1), - PositionInAisle: ptrInt32(2), + out, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: &buildingID, + Racks: []models.RackPlacementParam{{ + RackID: rackID, + AisleIndex: ptrInt32(1), + PositionInAisle: ptrInt32(2), + }}, }) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -253,7 +255,7 @@ func TestAssignRackToBuilding_placesRackWithGridCell(t *testing.T) { // SetRackBuildingPosition(nil, nil). The explicit clear is what makes // same-building unplace work — without it, UpdateRackPlacement's CASE // preserves the old position whenever building_id doesn't change. -func TestAssignRackToBuilding_membersWithoutPositionClearsCell(t *testing.T) { +func TestAssignRacksToBuilding_membersWithoutPositionClearsCell(t *testing.T) { h := newAssignHarness(t) buildingID := int64(11) rackID := int64(99) @@ -267,10 +269,10 @@ func TestAssignRackToBuilding_membersWithoutPositionClearsCell(t *testing.T) { h.collectionStore.EXPECT().UpdateRackPlacement(inTxCtx, rackID, testOrgID, &siteID, &buildingID, "").Return(nil) h.store.EXPECT().SetRackBuildingPosition(inTxCtx, testOrgID, rackID, (*int32)(nil), (*int32)(nil)).Return(nil) - _, err := h.svc.AssignRackToBuilding(context.Background(), models.AssignRackToBuildingParams{ - OrgID: testOrgID, - RackID: rackID, - BuildingID: &buildingID, + _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: &buildingID, + Racks: []models.RackPlacementParam{{RackID: rackID}}, }) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -282,7 +284,7 @@ func TestAssignRackToBuilding_membersWithoutPositionClearsCell(t *testing.T) { // must fire with nil/nil so the prior (aisle, position) is cleared from // the rack row. Guards against the "unplace within building silently // no-ops" regression. -func TestAssignRackToBuilding_sameBuildingUnplaceClearsPosition(t *testing.T) { +func TestAssignRacksToBuilding_sameBuildingUnplaceClearsPosition(t *testing.T) { h := newAssignHarness(t) buildingID := int64(11) rackID := int64(99) @@ -299,10 +301,10 @@ func TestAssignRackToBuilding_sameBuildingUnplaceClearsPosition(t *testing.T) { // Critical: explicit position clear fires. h.store.EXPECT().SetRackBuildingPosition(inTxCtx, testOrgID, rackID, (*int32)(nil), (*int32)(nil)).Return(nil) - _, err := h.svc.AssignRackToBuilding(context.Background(), models.AssignRackToBuildingParams{ - OrgID: testOrgID, - RackID: rackID, - BuildingID: &buildingID, + _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: &buildingID, + Racks: []models.RackPlacementParam{{RackID: rackID}}, }) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -312,7 +314,7 @@ func TestAssignRackToBuilding_sameBuildingUnplaceClearsPosition(t *testing.T) { // Building-only unassign must preserve the rack's site_id — the cascade // from the device level was the bug this test guards against. siteChanged // is false, so CascadeRackDeviceSites must never be called. -func TestAssignRackToBuilding_unassignPreservesSiteAndSkipsCascade(t *testing.T) { +func TestAssignRacksToBuilding_unassignPreservesSiteAndSkipsCascade(t *testing.T) { h := newAssignHarness(t) const rackID = int64(99) const priorBuildingID = int64(11) @@ -325,10 +327,10 @@ func TestAssignRackToBuilding_unassignPreservesSiteAndSkipsCascade(t *testing.T) h.collectionStore.EXPECT().UpdateRackPlacement(inTxCtx, rackID, testOrgID, &siteID, (*int64)(nil), "").Return(nil) // CascadeRackDeviceSites must NOT fire. - _, err := h.svc.AssignRackToBuilding(context.Background(), models.AssignRackToBuildingParams{ - OrgID: testOrgID, - RackID: rackID, - BuildingID: nil, + _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: nil, + Racks: []models.RackPlacementParam{{RackID: rackID}}, }) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -337,7 +339,7 @@ func TestAssignRackToBuilding_unassignPreservesSiteAndSkipsCascade(t *testing.T) // Cross-building move into a different site: zone clears, site cascade // runs with the new site. -func TestAssignRackToBuilding_crossBuildingClearsZoneAndCascadesSite(t *testing.T) { +func TestAssignRacksToBuilding_crossBuildingClearsZoneAndCascadesSite(t *testing.T) { h := newAssignHarness(t) targetBuildingID := int64(22) priorBuildingID := int64(11) @@ -357,10 +359,10 @@ func TestAssignRackToBuilding_crossBuildingClearsZoneAndCascadesSite(t *testing. // position write confirms the new row carries no stale placement. h.store.EXPECT().SetRackBuildingPosition(inTxCtx, testOrgID, rackID, (*int32)(nil), (*int32)(nil)).Return(nil) - out, err := h.svc.AssignRackToBuilding(context.Background(), models.AssignRackToBuildingParams{ - OrgID: testOrgID, - RackID: rackID, - BuildingID: ptrInt64(targetBuildingID), + out, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: ptrInt64(targetBuildingID), + Racks: []models.RackPlacementParam{{RackID: rackID}}, }) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -371,7 +373,7 @@ func TestAssignRackToBuilding_crossBuildingClearsZoneAndCascadesSite(t *testing. } // Grid cell out of bounds: validated after GetBuilding, before any write. -func TestAssignRackToBuilding_rejectsOutOfBoundsAisle(t *testing.T) { +func TestAssignRacksToBuilding_rejectsOutOfBoundsAisle(t *testing.T) { h := newAssignHarness(t) buildingID := int64(11) rackID := int64(99) @@ -382,12 +384,14 @@ func TestAssignRackToBuilding_rejectsOutOfBoundsAisle(t *testing.T) { // Reach LockRackPlacementForWrite via the closure ordering, but no // write or cascade fires because validation rejects first. - _, err := h.svc.AssignRackToBuilding(context.Background(), models.AssignRackToBuildingParams{ - OrgID: testOrgID, - RackID: rackID, - BuildingID: ptrInt64(buildingID), - AisleIndex: ptrInt32(2), // out of bounds (Aisles=2 means valid 0,1) - PositionInAisle: ptrInt32(0), + _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: ptrInt64(buildingID), + Racks: []models.RackPlacementParam{{ + RackID: rackID, + AisleIndex: ptrInt32(2), // out of bounds (Aisles=2 means valid 0,1) + PositionInAisle: ptrInt32(0), + }}, }) if err == nil { t.Fatal("expected InvalidArgument, got nil") @@ -395,13 +399,15 @@ func TestAssignRackToBuilding_rejectsOutOfBoundsAisle(t *testing.T) { } // Position pairing: aisle_index set, position_in_aisle absent. -func TestAssignRackToBuilding_rejectsHalfSetPosition(t *testing.T) { +func TestAssignRacksToBuilding_rejectsHalfSetPosition(t *testing.T) { h := newAssignHarness(t) - _, err := h.svc.AssignRackToBuilding(context.Background(), models.AssignRackToBuildingParams{ - OrgID: testOrgID, - RackID: 1, - BuildingID: ptrInt64(11), - AisleIndex: ptrInt32(0), + _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: ptrInt64(11), + Racks: []models.RackPlacementParam{{ + RackID: 1, + AisleIndex: ptrInt32(0), + }}, }) if err == nil { t.Fatal("expected InvalidArgument for half-set position pair, got nil") @@ -409,14 +415,16 @@ func TestAssignRackToBuilding_rejectsHalfSetPosition(t *testing.T) { } // Position-requires-building guard: grid cell set, building_id nil. -func TestAssignRackToBuilding_rejectsPositionWithoutBuilding(t *testing.T) { +func TestAssignRacksToBuilding_rejectsPositionWithoutBuilding(t *testing.T) { h := newAssignHarness(t) - _, err := h.svc.AssignRackToBuilding(context.Background(), models.AssignRackToBuildingParams{ - OrgID: testOrgID, - RackID: 1, - BuildingID: nil, - AisleIndex: ptrInt32(0), - PositionInAisle: ptrInt32(0), + _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: nil, + Racks: []models.RackPlacementParam{{ + RackID: 1, + AisleIndex: ptrInt32(0), + PositionInAisle: ptrInt32(0), + }}, }) if err == nil { t.Fatal("expected InvalidArgument for grid cell without building_id, got nil") diff --git a/server/internal/handlers/buildings/handler.go b/server/internal/handlers/buildings/handler.go index 46fe2e3bd..fb291b79c 100644 --- a/server/internal/handlers/buildings/handler.go +++ b/server/internal/handlers/buildings/handler.go @@ -113,16 +113,16 @@ func (h *Handler) ListBuildingRacks(ctx context.Context, req *connect.Request[pb return connect.NewResponse(toListBuildingRacksResponse(racks, nextPageToken)), nil } -func (h *Handler) AssignRackToBuilding(ctx context.Context, req *connect.Request[pb.AssignRackToBuildingRequest]) (*connect.Response[pb.AssignRackToBuildingResponse], error) { +func (h *Handler) AssignRacksToBuilding(ctx context.Context, req *connect.Request[pb.AssignRacksToBuildingRequest]) (*connect.Response[pb.AssignRacksToBuildingResponse], error) { info, err := middleware.RequirePermission(ctx, authz.PermSiteManage, authz.ResourceContext{}) if err != nil { return nil, err } - out, err := h.service.AssignRackToBuilding(ctx, toAssignRackToBuildingParams(req.Msg, info.OrganizationID)) + out, err := h.service.AssignRacksToBuilding(ctx, toAssignRacksToBuildingParams(req.Msg, info.OrganizationID)) if err != nil { return nil, err } - return connect.NewResponse(&pb.AssignRackToBuildingResponse{ + return connect.NewResponse(&pb.AssignRacksToBuildingResponse{ SiteReassignedDeviceCount: out.SiteReassignedDeviceCount, }), nil } diff --git a/server/internal/handlers/buildings/handler_test.go b/server/internal/handlers/buildings/handler_test.go index fb18f7eef..cf798b1b1 100644 --- a/server/internal/handlers/buildings/handler_test.go +++ b/server/internal/handlers/buildings/handler_test.go @@ -363,17 +363,17 @@ func TestHandler_ListBuildingRacks_happy(t *testing.T) { assert.Equal(t, "next-rack-page", resp.Msg.GetNextPageToken()) } -// AssignRackToBuilding requires PermSiteManage — callers without it +// AssignRacksToBuilding requires PermSiteManage — callers without it // are rejected before the service is touched. -func TestHandler_AssignRackToBuilding_rejectsCallerWithoutSiteManage(t *testing.T) { +func TestHandler_AssignRacksToBuilding_rejectsCallerWithoutSiteManage(t *testing.T) { t.Parallel() h := NewHandler(nil) // PermSiteRead alone does NOT satisfy PermSiteManage. ctx := handlerstest.CtxWithPermissions(t, 7, authz.PermSiteRead) buildingID := int64(11) - _, err := h.AssignRackToBuilding(ctx, connect.NewRequest(&pb.AssignRackToBuildingRequest{ - RackId: 99, - BuildingId: &buildingID, + _, err := h.AssignRacksToBuilding(ctx, connect.NewRequest(&pb.AssignRacksToBuildingRequest{ + Racks: []*pb.RackPlacement{{RackId: 99}}, + TargetBuildingId: &buildingID, })) require.Error(t, err) var fleetErr fleeterror.FleetError @@ -381,10 +381,10 @@ func TestHandler_AssignRackToBuilding_rejectsCallerWithoutSiteManage(t *testing. assert.Equal(t, connect.CodePermissionDenied, fleetErr.GRPCCode) } -// AssignRackToBuilding: PermSiteManage clears the gate and request +// AssignRacksToBuilding: PermSiteManage clears the gate and request // fields thread through to service params; response carries // SiteReassignedDeviceCount. -func TestHandler_AssignRackToBuilding_happy(t *testing.T) { +func TestHandler_AssignRacksToBuilding_happy(t *testing.T) { t.Parallel() h := newTestHandler(t) @@ -407,11 +407,13 @@ func TestHandler_AssignRackToBuilding_happy(t *testing.T) { aisle := int32(1) pos := int32(2) - resp, err := h.handler.AssignRackToBuilding(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignRackToBuildingRequest{ - RackId: 99, - BuildingId: &buildingID, - AisleIndex: &aisle, - PositionInAisle: &pos, + resp, err := h.handler.AssignRacksToBuilding(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignRacksToBuildingRequest{ + TargetBuildingId: &buildingID, + Racks: []*pb.RackPlacement{{ + RackId: 99, + AisleIndex: &aisle, + PositionInAisle: &pos, + }}, })) require.NoError(t, err) assert.Equal(t, int64(3), resp.Msg.GetSiteReassignedDeviceCount()) diff --git a/server/internal/handlers/buildings/translate.go b/server/internal/handlers/buildings/translate.go index f553c6ee1..2e8e8ad33 100644 --- a/server/internal/handlers/buildings/translate.go +++ b/server/internal/handlers/buildings/translate.go @@ -121,22 +121,26 @@ func toListBuildingRacksResponse(rows []models.BuildingRack, nextPageToken strin return &pb.ListBuildingRacksResponse{Racks: out, NextPageToken: nextPageToken} } -func toAssignRackToBuildingParams(req *pb.AssignRackToBuildingRequest, orgID int64) models.AssignRackToBuildingParams { - out := models.AssignRackToBuildingParams{ - OrgID: orgID, - RackID: req.GetRackId(), +func toAssignRacksToBuildingParams(req *pb.AssignRacksToBuildingRequest, orgID int64) models.AssignRacksToBuildingParams { + out := models.AssignRacksToBuildingParams{ + OrgID: orgID, + Racks: make([]models.RackPlacementParam, 0, len(req.GetRacks())), } - if req.BuildingId != nil { - v := req.GetBuildingId() - out.BuildingID = &v + if req.TargetBuildingId != nil { + v := req.GetTargetBuildingId() + out.TargetBuildingID = &v } - if req.AisleIndex != nil { - v := req.GetAisleIndex() - out.AisleIndex = &v - } - if req.PositionInAisle != nil { - v := req.GetPositionInAisle() - out.PositionInAisle = &v + for _, rp := range req.GetRacks() { + entry := models.RackPlacementParam{RackID: rp.GetRackId()} + if rp.AisleIndex != nil { + v := rp.GetAisleIndex() + entry.AisleIndex = &v + } + if rp.PositionInAisle != nil { + v := rp.GetPositionInAisle() + entry.PositionInAisle = &v + } + out.Racks = append(out.Racks, entry) } return out } diff --git a/server/internal/handlers/middleware/rpc_permissions.go b/server/internal/handlers/middleware/rpc_permissions.go index fd28ac7e1..feea19cab 100644 --- a/server/internal/handlers/middleware/rpc_permissions.go +++ b/server/internal/handlers/middleware/rpc_permissions.go @@ -89,15 +89,15 @@ var ProcedurePermissions = map[string]string{ authv1connect.AuthServiceListUsersProcedure: authz.PermUserRead, // Buildings CRUD — site:read for reads, site:manage for writes. - // ListBuildingRacks is a building-scoped read; AssignRackToBuilding + // ListBuildingRacks is a building-scoped read; AssignRacksToBuilding // mutates the rack's building/site/zone/grid placement. - buildingsv1connect.BuildingServiceListBuildingsProcedure: authz.PermSiteRead, - buildingsv1connect.BuildingServiceGetBuildingProcedure: authz.PermSiteRead, - buildingsv1connect.BuildingServiceListBuildingRacksProcedure: authz.PermSiteRead, - buildingsv1connect.BuildingServiceCreateBuildingProcedure: authz.PermSiteManage, - buildingsv1connect.BuildingServiceUpdateBuildingProcedure: authz.PermSiteManage, - buildingsv1connect.BuildingServiceDeleteBuildingProcedure: authz.PermSiteManage, - buildingsv1connect.BuildingServiceAssignRackToBuildingProcedure: authz.PermSiteManage, + buildingsv1connect.BuildingServiceListBuildingsProcedure: authz.PermSiteRead, + buildingsv1connect.BuildingServiceGetBuildingProcedure: authz.PermSiteRead, + buildingsv1connect.BuildingServiceListBuildingRacksProcedure: authz.PermSiteRead, + buildingsv1connect.BuildingServiceCreateBuildingProcedure: authz.PermSiteManage, + buildingsv1connect.BuildingServiceUpdateBuildingProcedure: authz.PermSiteManage, + buildingsv1connect.BuildingServiceDeleteBuildingProcedure: authz.PermSiteManage, + buildingsv1connect.BuildingServiceAssignRacksToBuildingProcedure: authz.PermSiteManage, // GetBuildingStats also calls RequirePermission(PermFleetRead) and // RequirePermission(PermMinerRead) inline — those gate the telemetry // rollup and the device_identifiers surface respectively. The map From ac41a67e89809e11569fda266644e0e65d07ed58 Mon Sep 17 00:00:00 2001 From: flesher Date: Wed, 10 Jun 2026 16:51:05 -0500 Subject: [PATCH 4/9] feat(racks): atomic AssignDevicesToRack RPC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the orphan window in the miner reparent flow. Previously the client orchestrated removeDevicesFromDeviceSet(source) + addDevicesToDeviceSet(target) as two independent RPCs; a server error or network blip between them left miners removed from their source rack but never added to the target. The cross-rack rename race (source rack renamed mid-confirm) had the same failure shape via the client-side label→id resolution. The new RPC bundles both halves into one server-side transaction: remove from any current rack (single SQL DELETE on device_set_membership filtered by device_set_type='rack'), insert into target rack, cascade device.site_id when the target rack's site differs. target_rack_id unset clears rack membership without re-assigning (site/building stay intact). MinerReparentPicker's rack path collapses from ~50 lines of remove-then-add orchestration + listRacks label resolution to a single dispatch. Site reparent path is unchanged. Server: new sqlc query RemoveDevicesFromAnyRack, new CollectionStore.RemoveDevicesFromAnyRack, new collection.Service.AssignDevicesToRack with 4 unit tests covering happy path, unassign, target-must-be-rack, and empty-input rejection. Refs #420. --- .../generated/device_set/v1/device_set_pb.ts | 138 +- client/src/protoFleet/api/useDeviceSets.ts | 38 + .../MinerActionsMenu/MinerReparentPicker.tsx | 84 +- proto/device_set/v1/device_set.proto | 43 + .../grpc/device_set/v1/device_set.pb.go | 545 ++++-- .../device_setv1connect/device_set.connect.go | 51 + server/generated/sqlc/db.go | 1722 +++++++---------- server/generated/sqlc/device_set.sql.go | 24 + server/generated/sqlc/site.sql.go | 4 +- server/internal/domain/collection/service.go | 148 ++ .../domain/collection/service_test.go | 86 + .../domain/stores/interfaces/collection.go | 9 + .../interfaces/mocks/mock_collection_store.go | 15 + .../domain/stores/sqlstores/collection.go | 11 + server/internal/handlers/deviceset/handler.go | 25 + .../handlers/middleware/rpc_permissions.go | 1 + server/sqlc/queries/device_set.sql | 10 + 17 files changed, 1685 insertions(+), 1269 deletions(-) diff --git a/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts b/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts index 58f422208..946e475e8 100644 --- a/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts +++ b/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.12.0 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file device_set/v1/device_set.proto (package device_set.v1, syntax proto3) /* eslint-disable */ @@ -23,7 +23,7 @@ import type { Message } from "@bufbuild/protobuf"; export const file_device_set_v1_device_set: GenFile = /*@__PURE__*/ fileDesc( - "Ch5kZXZpY2Vfc2V0L3YxL2RldmljZV9zZXQucHJvdG8SDWRldmljZV9zZXQudjEiywIKCURldmljZVNldBIKCgJpZBgBIAEoAxIqCgR0eXBlGAIgASgOMhwuZGV2aWNlX3NldC52MS5EZXZpY2VTZXRUeXBlEg0KBWxhYmVsGAMgASgJEhMKC2Rlc2NyaXB0aW9uGAQgASgJEhQKDGRldmljZV9jb3VudBgFIAEoBRIuCgpjcmVhdGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIsCglyYWNrX2luZm8YCCABKAsyFy5kZXZpY2Vfc2V0LnYxLlJhY2tJbmZvSAASLgoKZ3JvdXBfaW5mbxgJIAEoCzIYLmRldmljZV9zZXQudjEuR3JvdXBJbmZvSABCDgoMdHlwZV9kZXRhaWxzIogCCghSYWNrSW5mbxIVCgRyb3dzGAEgASgFQge6SAQaAiAAEhgKB2NvbHVtbnMYAiABKAVCB7pIBBoCIAASFQoEem9uZRgDIAEoCUIHukgEcgIYZBIyCgtvcmRlcl9pbmRleBgEIAEoDjIdLmRldmljZV9zZXQudjEuUmFja09yZGVySW5kZXgSNAoMY29vbGluZ190eXBlGAUgASgOMh4uZGV2aWNlX3NldC52MS5SYWNrQ29vbGluZ1R5cGUSFAoHc2l0ZV9pZBgGIAEoA0gAiAEBEhgKC2J1aWxkaW5nX2lkGAcgASgDSAGIAQFCCgoIX3NpdGVfaWRCDgoMX2J1aWxkaW5nX2lkIgsKCUdyb3VwSW5mbyKeAQoPRGV2aWNlU2V0TWVtYmVyEhkKEWRldmljZV9pZGVudGlmaWVyGAEgASgJEiwKCGFkZGVkX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIwCgRyYWNrGAMgASgLMiAuZGV2aWNlX3NldC52MS5SYWNrTWVtYmVyRGV0YWlsc0gAQhAKDm1lbWJlcl9kZXRhaWxzIksKEVJhY2tNZW1iZXJEZXRhaWxzEjYKDXNsb3RfcG9zaXRpb24YASABKAsyHy5kZXZpY2Vfc2V0LnYxLlJhY2tTbG90UG9zaXRpb24iQQoQUmFja1Nsb3RQb3NpdGlvbhIUCgNyb3cYASABKAVCB7pIBBoCKAASFwoGY29sdW1uGAIgASgFQge6SAQaAigAIscCChZDcmVhdGVEZXZpY2VTZXRSZXF1ZXN0EjYKBHR5cGUYASABKA4yHC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFR5cGVCCrpIB4IBBBABIAASGwoFbGFiZWwYAiABKAlCDLpICcgBAXIEEAEYZBIdCgtkZXNjcmlwdGlvbhgDIAEoCUIIukgFcgMY9AMSLAoJcmFja19pbmZvGAQgASgLMhcuZGV2aWNlX3NldC52MS5SYWNrSW5mb0gAEi4KCmdyb3VwX2luZm8YBSABKAsyGC5kZXZpY2Vfc2V0LnYxLkdyb3VwSW5mb0gAEjcKD2RldmljZV9zZWxlY3RvchgGIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckgBiAEBQg4KDHR5cGVfZGV0YWlsc0ISChBfZGV2aWNlX3NlbGVjdG9yIlwKF0NyZWF0ZURldmljZVNldFJlc3BvbnNlEiwKCmRldmljZV9zZXQYASABKAsyGC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldBITCgthZGRlZF9jb3VudBgCIAEoBSI1ChNHZXREZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAAiRAoUR2V0RGV2aWNlU2V0UmVzcG9uc2USLAoKZGV2aWNlX3NldBgBIAEoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0Ir0CChZVcGRhdGVEZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAASIAoFbGFiZWwYAiABKAlCDLpICdgBAXIEEAEYZEgBiAEBEiUKC2Rlc2NyaXB0aW9uGAMgASgJQgu6SAjYAQFyAxj0A0gCiAEBEiwKCXJhY2tfaW5mbxgEIAEoCzIXLmRldmljZV9zZXQudjEuUmFja0luZm9IABIuCgpncm91cF9pbmZvGAUgASgLMhguZGV2aWNlX3NldC52MS5Hcm91cEluZm9IABIyCg9kZXZpY2Vfc2VsZWN0b3IYBiABKAsyGS5jb21tb24udjEuRGV2aWNlU2VsZWN0b3JCDgoMdHlwZV9kZXRhaWxzQggKBl9sYWJlbEIOCgxfZGVzY3JpcHRpb24iRwoXVXBkYXRlRGV2aWNlU2V0UmVzcG9uc2USLAoKZGV2aWNlX3NldBgBIAEoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0IjgKFkRlbGV0ZURldmljZVNldFJlcXVlc3QSHgoNZGV2aWNlX3NldF9pZBgBIAEoA0IHukgEIgIgACIZChdEZWxldGVEZXZpY2VTZXRSZXNwb25zZSLIAgoVTGlzdERldmljZVNldHNSZXF1ZXN0EjQKBHR5cGUYASABKA4yHC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFR5cGVCCLpIBYIBAhABEhoKCXBhZ2Vfc2l6ZRgCIAEoBUIHukgEGgIoABISCgpwYWdlX3Rva2VuGAMgASgJEiMKBHNvcnQYBCABKAsyFS5jb21tb24udjEuU29ydENvbmZpZxI3ChVlcnJvcl9jb21wb25lbnRfdHlwZXMYBSADKA4yGC5lcnJvcnMudjEuQ29tcG9uZW50VHlwZRIRCgV6b25lcxgGIAMoCUICGAESFAoMYnVpbGRpbmdfaWRzGAcgAygDEhsKE2luY2x1ZGVfbm9fYnVpbGRpbmcYCCABKAgSJQoJem9uZV9rZXlzGAkgAygLMhIuY29tbW9uLnYxLlpvbmVLZXkidQoWTGlzdERldmljZVNldHNSZXNwb25zZRItCgtkZXZpY2Vfc2V0cxgBIAMoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0EhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRITCgt0b3RhbF9jb3VudBgDIAEoBSJ6ChxBZGREZXZpY2VzVG9EZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAASOgoPZGV2aWNlX3NlbGVjdG9yGAIgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQEiagodQWRkRGV2aWNlc1RvRGV2aWNlU2V0UmVzcG9uc2USFQoNZGV2aWNlX3NldF9pZBgBIAEoAxITCgthZGRlZF9jb3VudBgCIAEoBRIdChVzaXRlX3JlYXNzaWduZWRfY291bnQYAyABKAUifwohUmVtb3ZlRGV2aWNlc0Zyb21EZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAASOgoPZGV2aWNlX3NlbGVjdG9yGAIgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQEiOwoiUmVtb3ZlRGV2aWNlc0Zyb21EZXZpY2VTZXRSZXNwb25zZRIVCg1yZW1vdmVkX2NvdW50GAEgASgFIm0KG0xpc3REZXZpY2VTZXRNZW1iZXJzUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEhoKCXBhZ2Vfc2l6ZRgCIAEoBUIHukgEGgIoABISCgpwYWdlX3Rva2VuGAMgASgJImgKHExpc3REZXZpY2VTZXRNZW1iZXJzUmVzcG9uc2USLwoHbWVtYmVycxgBIAMoCzIeLmRldmljZV9zZXQudjEuRGV2aWNlU2V0TWVtYmVyEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSJsChpHZXREZXZpY2VEZXZpY2VTZXRzUmVxdWVzdBIiChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCUIHukgEcgIQARIqCgR0eXBlGAIgASgOMhwuZGV2aWNlX3NldC52MS5EZXZpY2VTZXRUeXBlIkwKG0dldERldmljZURldmljZVNldHNSZXNwb25zZRItCgtkZXZpY2Vfc2V0cxgBIAMoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0IpsBChpTZXRSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEiIKEWRldmljZV9pZGVudGlmaWVyGAIgASgJQge6SARyAhABEjkKCHBvc2l0aW9uGAMgASgLMh8uZGV2aWNlX3NldC52MS5SYWNrU2xvdFBvc2l0aW9uQga6SAPIAQEiWwobU2V0UmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlEhUKDWRldmljZV9zZXRfaWQYASABKAMSJQoEc2xvdBgCIAEoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3QiYgocQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEiIKEWRldmljZV9pZGVudGlmaWVyGAIgASgJQge6SARyAhABIh8KHUNsZWFyUmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlIjUKE0dldFJhY2tTbG90c1JlcXVlc3QSHgoNZGV2aWNlX3NldF9pZBgBIAEoA0IHukgEIgIgACJYCghSYWNrU2xvdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCghwb3NpdGlvbhgCIAEoCzIfLmRldmljZV9zZXQudjEuUmFja1Nsb3RQb3NpdGlvbiI+ChRHZXRSYWNrU2xvdHNSZXNwb25zZRImCgVzbG90cxgBIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3Qi7QQKDkRldmljZVNldFN0YXRzEhUKDWRldmljZV9zZXRfaWQYASABKAMSFAoMZGV2aWNlX2NvdW50GAIgASgFEhcKD3JlcG9ydGluZ19jb3VudBgDIAEoBRIaChJ0b3RhbF9oYXNocmF0ZV90aHMYBCABKAESGgoSYXZnX2VmZmljaWVuY3lfanRoGAUgASgBEhYKDnRvdGFsX3Bvd2VyX2t3GAYgASgBEhkKEW1pbl90ZW1wZXJhdHVyZV9jGAcgASgBEhkKEW1heF90ZW1wZXJhdHVyZV9jGAggASgBEhUKDWhhc2hpbmdfY291bnQYCSABKAUSFAoMYnJva2VuX2NvdW50GAogASgFEhUKDW9mZmxpbmVfY291bnQYCyABKAUSFgoOc2xlZXBpbmdfY291bnQYDCABKAUSIAoYaGFzaHJhdGVfcmVwb3J0aW5nX2NvdW50GA0gASgFEiIKGmVmZmljaWVuY3lfcmVwb3J0aW5nX2NvdW50GA4gASgFEh0KFXBvd2VyX3JlcG9ydGluZ19jb3VudBgPIAEoBRIjCht0ZW1wZXJhdHVyZV9yZXBvcnRpbmdfY291bnQYECABKAUSIQoZY29udHJvbF9ib2FyZF9pc3N1ZV9jb3VudBgRIAEoBRIXCg9mYW5faXNzdWVfY291bnQYEiABKAUSHgoWaGFzaF9ib2FyZF9pc3N1ZV9jb3VudBgTIAEoBRIXCg9wc3VfaXNzdWVfY291bnQYFCABKAUSNAoNc2xvdF9zdGF0dXNlcxgVIAMoCzIdLmRldmljZV9zZXQudjEuUmFja1Nsb3RTdGF0dXMiMgoYR2V0RGV2aWNlU2V0U3RhdHNSZXF1ZXN0EhYKDmRldmljZV9zZXRfaWRzGAEgAygDIkkKGUdldERldmljZVNldFN0YXRzUmVzcG9uc2USLAoFc3RhdHMYASADKAsyHS5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFN0YXRzIl4KDlJhY2tTbG90U3RhdHVzEgsKA3JvdxgBIAEoBRIOCgZjb2x1bW4YAiABKAUSLwoGc3RhdHVzGAMgASgOMh8uZGV2aWNlX3NldC52MS5TbG90RGV2aWNlU3RhdHVzIhYKFExpc3RSYWNrWm9uZXNSZXF1ZXN0IiYKFUxpc3RSYWNrWm9uZXNSZXNwb25zZRINCgV6b25lcxgBIAMoCSIZChdMaXN0UmFja1pvbmVSZWZzUmVxdWVzdCI9ChhMaXN0UmFja1pvbmVSZWZzUmVzcG9uc2USIQoFem9uZXMYASADKAsyEi5jb21tb24udjEuWm9uZVJlZiIWChRMaXN0UmFja1R5cGVzUmVxdWVzdCI9CghSYWNrVHlwZRIMCgRyb3dzGAEgASgFEg8KB2NvbHVtbnMYAiABKAUSEgoKcmFja19jb3VudBgDIAEoBSJEChVMaXN0UmFja1R5cGVzUmVzcG9uc2USKwoKcmFja190eXBlcxgBIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1R5cGUiiwIKD1NhdmVSYWNrUmVxdWVzdBImCg1kZXZpY2Vfc2V0X2lkGAEgASgDQgq6SAfYAQEiAiAASACIAQESGwoFbGFiZWwYAiABKAlCDLpICcgBAXIEEAEYZBIyCglyYWNrX2luZm8YAyABKAsyFy5kZXZpY2Vfc2V0LnYxLlJhY2tJbmZvQga6SAPIAQESOgoPZGV2aWNlX3NlbGVjdG9yGAQgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQESMQoQc2xvdF9hc3NpZ25tZW50cxgFIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3RCEAoOX2RldmljZV9zZXRfaWQidwoQU2F2ZVJhY2tSZXNwb25zZRIsCgpkZXZpY2Vfc2V0GAEgASgLMhguZGV2aWNlX3NldC52MS5EZXZpY2VTZXQSFgoOYXNzaWduZWRfY291bnQYAiABKAUSHQoVc2l0ZV9yZWFzc2lnbmVkX2NvdW50GAMgASgFKmUKDURldmljZVNldFR5cGUSHwobREVWSUNFX1NFVF9UWVBFX1VOU1BFQ0lGSUVEEAASGQoVREVWSUNFX1NFVF9UWVBFX0dST1VQEAESGAoUREVWSUNFX1NFVF9UWVBFX1JBQ0sQAiq2AQoOUmFja09yZGVySW5kZXgSIAocUkFDS19PUkRFUl9JTkRFWF9VTlNQRUNJRklFRBAAEiAKHFJBQ0tfT1JERVJfSU5ERVhfQk9UVE9NX0xFRlQQARIdChlSQUNLX09SREVSX0lOREVYX1RPUF9MRUZUEAISIQodUkFDS19PUkRFUl9JTkRFWF9CT1RUT01fUklHSFQQAxIeChpSQUNLX09SREVSX0lOREVYX1RPUF9SSUdIVBAEKnAKD1JhY2tDb29saW5nVHlwZRIhCh1SQUNLX0NPT0xJTkdfVFlQRV9VTlNQRUNJRklFRBAAEhkKFVJBQ0tfQ09PTElOR19UWVBFX0FJUhABEh8KG1JBQ0tfQ09PTElOR19UWVBFX0lNTUVSU0lPThACKt0BChBTbG90RGV2aWNlU3RhdHVzEiIKHlNMT1RfREVWSUNFX1NUQVRVU19VTlNQRUNJRklFRBAAEhwKGFNMT1RfREVWSUNFX1NUQVRVU19FTVBUWRABEh4KGlNMT1RfREVWSUNFX1NUQVRVU19IRUFMVEhZEAISJgoiU0xPVF9ERVZJQ0VfU1RBVFVTX05FRURTX0FUVEVOVElPThADEh4KGlNMT1RfREVWSUNFX1NUQVRVU19PRkZMSU5FEAQSHwobU0xPVF9ERVZJQ0VfU1RBVFVTX1NMRUVQSU5HEAUy1A0KEERldmljZVNldFNlcnZpY2USYAoPQ3JlYXRlRGV2aWNlU2V0EiUuZGV2aWNlX3NldC52MS5DcmVhdGVEZXZpY2VTZXRSZXF1ZXN0GiYuZGV2aWNlX3NldC52MS5DcmVhdGVEZXZpY2VTZXRSZXNwb25zZRJXCgxHZXREZXZpY2VTZXQSIi5kZXZpY2Vfc2V0LnYxLkdldERldmljZVNldFJlcXVlc3QaIy5kZXZpY2Vfc2V0LnYxLkdldERldmljZVNldFJlc3BvbnNlEmAKD1VwZGF0ZURldmljZVNldBIlLmRldmljZV9zZXQudjEuVXBkYXRlRGV2aWNlU2V0UmVxdWVzdBomLmRldmljZV9zZXQudjEuVXBkYXRlRGV2aWNlU2V0UmVzcG9uc2USYAoPRGVsZXRlRGV2aWNlU2V0EiUuZGV2aWNlX3NldC52MS5EZWxldGVEZXZpY2VTZXRSZXF1ZXN0GiYuZGV2aWNlX3NldC52MS5EZWxldGVEZXZpY2VTZXRSZXNwb25zZRJdCg5MaXN0RGV2aWNlU2V0cxIkLmRldmljZV9zZXQudjEuTGlzdERldmljZVNldHNSZXF1ZXN0GiUuZGV2aWNlX3NldC52MS5MaXN0RGV2aWNlU2V0c1Jlc3BvbnNlEnIKFUFkZERldmljZXNUb0RldmljZVNldBIrLmRldmljZV9zZXQudjEuQWRkRGV2aWNlc1RvRGV2aWNlU2V0UmVxdWVzdBosLmRldmljZV9zZXQudjEuQWRkRGV2aWNlc1RvRGV2aWNlU2V0UmVzcG9uc2USgQEKGlJlbW92ZURldmljZXNGcm9tRGV2aWNlU2V0EjAuZGV2aWNlX3NldC52MS5SZW1vdmVEZXZpY2VzRnJvbURldmljZVNldFJlcXVlc3QaMS5kZXZpY2Vfc2V0LnYxLlJlbW92ZURldmljZXNGcm9tRGV2aWNlU2V0UmVzcG9uc2USbwoUTGlzdERldmljZVNldE1lbWJlcnMSKi5kZXZpY2Vfc2V0LnYxLkxpc3REZXZpY2VTZXRNZW1iZXJzUmVxdWVzdBorLmRldmljZV9zZXQudjEuTGlzdERldmljZVNldE1lbWJlcnNSZXNwb25zZRJsChNHZXREZXZpY2VEZXZpY2VTZXRzEikuZGV2aWNlX3NldC52MS5HZXREZXZpY2VEZXZpY2VTZXRzUmVxdWVzdBoqLmRldmljZV9zZXQudjEuR2V0RGV2aWNlRGV2aWNlU2V0c1Jlc3BvbnNlEmwKE1NldFJhY2tTbG90UG9zaXRpb24SKS5kZXZpY2Vfc2V0LnYxLlNldFJhY2tTbG90UG9zaXRpb25SZXF1ZXN0GiouZGV2aWNlX3NldC52MS5TZXRSYWNrU2xvdFBvc2l0aW9uUmVzcG9uc2UScgoVQ2xlYXJSYWNrU2xvdFBvc2l0aW9uEisuZGV2aWNlX3NldC52MS5DbGVhclJhY2tTbG90UG9zaXRpb25SZXF1ZXN0GiwuZGV2aWNlX3NldC52MS5DbGVhclJhY2tTbG90UG9zaXRpb25SZXNwb25zZRJXCgxHZXRSYWNrU2xvdHMSIi5kZXZpY2Vfc2V0LnYxLkdldFJhY2tTbG90c1JlcXVlc3QaIy5kZXZpY2Vfc2V0LnYxLkdldFJhY2tTbG90c1Jlc3BvbnNlEmYKEUdldERldmljZVNldFN0YXRzEicuZGV2aWNlX3NldC52MS5HZXREZXZpY2VTZXRTdGF0c1JlcXVlc3QaKC5kZXZpY2Vfc2V0LnYxLkdldERldmljZVNldFN0YXRzUmVzcG9uc2USWgoNTGlzdFJhY2tab25lcxIjLmRldmljZV9zZXQudjEuTGlzdFJhY2tab25lc1JlcXVlc3QaJC5kZXZpY2Vfc2V0LnYxLkxpc3RSYWNrWm9uZXNSZXNwb25zZRJjChBMaXN0UmFja1pvbmVSZWZzEiYuZGV2aWNlX3NldC52MS5MaXN0UmFja1pvbmVSZWZzUmVxdWVzdBonLmRldmljZV9zZXQudjEuTGlzdFJhY2tab25lUmVmc1Jlc3BvbnNlEloKDUxpc3RSYWNrVHlwZXMSIy5kZXZpY2Vfc2V0LnYxLkxpc3RSYWNrVHlwZXNSZXF1ZXN0GiQuZGV2aWNlX3NldC52MS5MaXN0UmFja1R5cGVzUmVzcG9uc2USSwoIU2F2ZVJhY2sSHi5kZXZpY2Vfc2V0LnYxLlNhdmVSYWNrUmVxdWVzdBofLmRldmljZV9zZXQudjEuU2F2ZVJhY2tSZXNwb25zZULDAQoRY29tLmRldmljZV9zZXQudjFCDkRldmljZVNldFByb3RvUAFaTWdpdGh1Yi5jb20vYmxvY2svcHJvdG8tZmxlZXQvc2VydmVyL2dlbmVyYXRlZC9ncnBjL2RldmljZV9zZXQvdjE7ZGV2aWNlX3NldHYxogIDRFhYqgIMRGV2aWNlU2V0LlYxygIMRGV2aWNlU2V0XFYx4gIYRGV2aWNlU2V0XFYxXEdQQk1ldGFkYXRh6gINRGV2aWNlU2V0OjpWMWIGcHJvdG8z", + "Ch5kZXZpY2Vfc2V0L3YxL2RldmljZV9zZXQucHJvdG8SDWRldmljZV9zZXQudjEiywIKCURldmljZVNldBIKCgJpZBgBIAEoAxIqCgR0eXBlGAIgASgOMhwuZGV2aWNlX3NldC52MS5EZXZpY2VTZXRUeXBlEg0KBWxhYmVsGAMgASgJEhMKC2Rlc2NyaXB0aW9uGAQgASgJEhQKDGRldmljZV9jb3VudBgFIAEoBRIuCgpjcmVhdGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIsCglyYWNrX2luZm8YCCABKAsyFy5kZXZpY2Vfc2V0LnYxLlJhY2tJbmZvSAASLgoKZ3JvdXBfaW5mbxgJIAEoCzIYLmRldmljZV9zZXQudjEuR3JvdXBJbmZvSABCDgoMdHlwZV9kZXRhaWxzIogCCghSYWNrSW5mbxIVCgRyb3dzGAEgASgFQge6SAQaAiAAEhgKB2NvbHVtbnMYAiABKAVCB7pIBBoCIAASFQoEem9uZRgDIAEoCUIHukgEcgIYZBIyCgtvcmRlcl9pbmRleBgEIAEoDjIdLmRldmljZV9zZXQudjEuUmFja09yZGVySW5kZXgSNAoMY29vbGluZ190eXBlGAUgASgOMh4uZGV2aWNlX3NldC52MS5SYWNrQ29vbGluZ1R5cGUSFAoHc2l0ZV9pZBgGIAEoA0gAiAEBEhgKC2J1aWxkaW5nX2lkGAcgASgDSAGIAQFCCgoIX3NpdGVfaWRCDgoMX2J1aWxkaW5nX2lkIgsKCUdyb3VwSW5mbyKeAQoPRGV2aWNlU2V0TWVtYmVyEhkKEWRldmljZV9pZGVudGlmaWVyGAEgASgJEiwKCGFkZGVkX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIwCgRyYWNrGAMgASgLMiAuZGV2aWNlX3NldC52MS5SYWNrTWVtYmVyRGV0YWlsc0gAQhAKDm1lbWJlcl9kZXRhaWxzIksKEVJhY2tNZW1iZXJEZXRhaWxzEjYKDXNsb3RfcG9zaXRpb24YASABKAsyHy5kZXZpY2Vfc2V0LnYxLlJhY2tTbG90UG9zaXRpb24iQQoQUmFja1Nsb3RQb3NpdGlvbhIUCgNyb3cYASABKAVCB7pIBBoCKAASFwoGY29sdW1uGAIgASgFQge6SAQaAigAIscCChZDcmVhdGVEZXZpY2VTZXRSZXF1ZXN0EjYKBHR5cGUYASABKA4yHC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFR5cGVCCrpIB4IBBBABIAASGwoFbGFiZWwYAiABKAlCDLpICcgBAXIEEAEYZBIdCgtkZXNjcmlwdGlvbhgDIAEoCUIIukgFcgMY9AMSLAoJcmFja19pbmZvGAQgASgLMhcuZGV2aWNlX3NldC52MS5SYWNrSW5mb0gAEi4KCmdyb3VwX2luZm8YBSABKAsyGC5kZXZpY2Vfc2V0LnYxLkdyb3VwSW5mb0gAEjcKD2RldmljZV9zZWxlY3RvchgGIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckgBiAEBQg4KDHR5cGVfZGV0YWlsc0ISChBfZGV2aWNlX3NlbGVjdG9yIlwKF0NyZWF0ZURldmljZVNldFJlc3BvbnNlEiwKCmRldmljZV9zZXQYASABKAsyGC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldBITCgthZGRlZF9jb3VudBgCIAEoBSI1ChNHZXREZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAAiRAoUR2V0RGV2aWNlU2V0UmVzcG9uc2USLAoKZGV2aWNlX3NldBgBIAEoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0Ir0CChZVcGRhdGVEZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAASIAoFbGFiZWwYAiABKAlCDLpICdgBAXIEEAEYZEgBiAEBEiUKC2Rlc2NyaXB0aW9uGAMgASgJQgu6SAjYAQFyAxj0A0gCiAEBEiwKCXJhY2tfaW5mbxgEIAEoCzIXLmRldmljZV9zZXQudjEuUmFja0luZm9IABIuCgpncm91cF9pbmZvGAUgASgLMhguZGV2aWNlX3NldC52MS5Hcm91cEluZm9IABIyCg9kZXZpY2Vfc2VsZWN0b3IYBiABKAsyGS5jb21tb24udjEuRGV2aWNlU2VsZWN0b3JCDgoMdHlwZV9kZXRhaWxzQggKBl9sYWJlbEIOCgxfZGVzY3JpcHRpb24iRwoXVXBkYXRlRGV2aWNlU2V0UmVzcG9uc2USLAoKZGV2aWNlX3NldBgBIAEoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0IjgKFkRlbGV0ZURldmljZVNldFJlcXVlc3QSHgoNZGV2aWNlX3NldF9pZBgBIAEoA0IHukgEIgIgACIZChdEZWxldGVEZXZpY2VTZXRSZXNwb25zZSLIAgoVTGlzdERldmljZVNldHNSZXF1ZXN0EjQKBHR5cGUYASABKA4yHC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFR5cGVCCLpIBYIBAhABEhoKCXBhZ2Vfc2l6ZRgCIAEoBUIHukgEGgIoABISCgpwYWdlX3Rva2VuGAMgASgJEiMKBHNvcnQYBCABKAsyFS5jb21tb24udjEuU29ydENvbmZpZxI3ChVlcnJvcl9jb21wb25lbnRfdHlwZXMYBSADKA4yGC5lcnJvcnMudjEuQ29tcG9uZW50VHlwZRIRCgV6b25lcxgGIAMoCUICGAESFAoMYnVpbGRpbmdfaWRzGAcgAygDEhsKE2luY2x1ZGVfbm9fYnVpbGRpbmcYCCABKAgSJQoJem9uZV9rZXlzGAkgAygLMhIuY29tbW9uLnYxLlpvbmVLZXkidQoWTGlzdERldmljZVNldHNSZXNwb25zZRItCgtkZXZpY2Vfc2V0cxgBIAMoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0EhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRITCgt0b3RhbF9jb3VudBgDIAEoBSJ6ChxBZGREZXZpY2VzVG9EZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAASOgoPZGV2aWNlX3NlbGVjdG9yGAIgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQEiagodQWRkRGV2aWNlc1RvRGV2aWNlU2V0UmVzcG9uc2USFQoNZGV2aWNlX3NldF9pZBgBIAEoAxITCgthZGRlZF9jb3VudBgCIAEoBRIdChVzaXRlX3JlYXNzaWduZWRfY291bnQYAyABKAUifwohUmVtb3ZlRGV2aWNlc0Zyb21EZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAASOgoPZGV2aWNlX3NlbGVjdG9yGAIgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQEiOwoiUmVtb3ZlRGV2aWNlc0Zyb21EZXZpY2VTZXRSZXNwb25zZRIVCg1yZW1vdmVkX2NvdW50GAEgASgFIm0KG0xpc3REZXZpY2VTZXRNZW1iZXJzUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEhoKCXBhZ2Vfc2l6ZRgCIAEoBUIHukgEGgIoABISCgpwYWdlX3Rva2VuGAMgASgJImgKHExpc3REZXZpY2VTZXRNZW1iZXJzUmVzcG9uc2USLwoHbWVtYmVycxgBIAMoCzIeLmRldmljZV9zZXQudjEuRGV2aWNlU2V0TWVtYmVyEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSJsChpHZXREZXZpY2VEZXZpY2VTZXRzUmVxdWVzdBIiChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCUIHukgEcgIQARIqCgR0eXBlGAIgASgOMhwuZGV2aWNlX3NldC52MS5EZXZpY2VTZXRUeXBlIkwKG0dldERldmljZURldmljZVNldHNSZXNwb25zZRItCgtkZXZpY2Vfc2V0cxgBIAMoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0IpsBChpTZXRSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEiIKEWRldmljZV9pZGVudGlmaWVyGAIgASgJQge6SARyAhABEjkKCHBvc2l0aW9uGAMgASgLMh8uZGV2aWNlX3NldC52MS5SYWNrU2xvdFBvc2l0aW9uQga6SAPIAQEiWwobU2V0UmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlEhUKDWRldmljZV9zZXRfaWQYASABKAMSJQoEc2xvdBgCIAEoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3QiYgocQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEiIKEWRldmljZV9pZGVudGlmaWVyGAIgASgJQge6SARyAhABIh8KHUNsZWFyUmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlIjUKE0dldFJhY2tTbG90c1JlcXVlc3QSHgoNZGV2aWNlX3NldF9pZBgBIAEoA0IHukgEIgIgACJYCghSYWNrU2xvdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCghwb3NpdGlvbhgCIAEoCzIfLmRldmljZV9zZXQudjEuUmFja1Nsb3RQb3NpdGlvbiI+ChRHZXRSYWNrU2xvdHNSZXNwb25zZRImCgVzbG90cxgBIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3Qi7QQKDkRldmljZVNldFN0YXRzEhUKDWRldmljZV9zZXRfaWQYASABKAMSFAoMZGV2aWNlX2NvdW50GAIgASgFEhcKD3JlcG9ydGluZ19jb3VudBgDIAEoBRIaChJ0b3RhbF9oYXNocmF0ZV90aHMYBCABKAESGgoSYXZnX2VmZmljaWVuY3lfanRoGAUgASgBEhYKDnRvdGFsX3Bvd2VyX2t3GAYgASgBEhkKEW1pbl90ZW1wZXJhdHVyZV9jGAcgASgBEhkKEW1heF90ZW1wZXJhdHVyZV9jGAggASgBEhUKDWhhc2hpbmdfY291bnQYCSABKAUSFAoMYnJva2VuX2NvdW50GAogASgFEhUKDW9mZmxpbmVfY291bnQYCyABKAUSFgoOc2xlZXBpbmdfY291bnQYDCABKAUSIAoYaGFzaHJhdGVfcmVwb3J0aW5nX2NvdW50GA0gASgFEiIKGmVmZmljaWVuY3lfcmVwb3J0aW5nX2NvdW50GA4gASgFEh0KFXBvd2VyX3JlcG9ydGluZ19jb3VudBgPIAEoBRIjCht0ZW1wZXJhdHVyZV9yZXBvcnRpbmdfY291bnQYECABKAUSIQoZY29udHJvbF9ib2FyZF9pc3N1ZV9jb3VudBgRIAEoBRIXCg9mYW5faXNzdWVfY291bnQYEiABKAUSHgoWaGFzaF9ib2FyZF9pc3N1ZV9jb3VudBgTIAEoBRIXCg9wc3VfaXNzdWVfY291bnQYFCABKAUSNAoNc2xvdF9zdGF0dXNlcxgVIAMoCzIdLmRldmljZV9zZXQudjEuUmFja1Nsb3RTdGF0dXMiMgoYR2V0RGV2aWNlU2V0U3RhdHNSZXF1ZXN0EhYKDmRldmljZV9zZXRfaWRzGAEgAygDIkkKGUdldERldmljZVNldFN0YXRzUmVzcG9uc2USLAoFc3RhdHMYASADKAsyHS5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFN0YXRzIl4KDlJhY2tTbG90U3RhdHVzEgsKA3JvdxgBIAEoBRIOCgZjb2x1bW4YAiABKAUSLwoGc3RhdHVzGAMgASgOMh8uZGV2aWNlX3NldC52MS5TbG90RGV2aWNlU3RhdHVzIhYKFExpc3RSYWNrWm9uZXNSZXF1ZXN0IiYKFUxpc3RSYWNrWm9uZXNSZXNwb25zZRINCgV6b25lcxgBIAMoCSIZChdMaXN0UmFja1pvbmVSZWZzUmVxdWVzdCI9ChhMaXN0UmFja1pvbmVSZWZzUmVzcG9uc2USIQoFem9uZXMYASADKAsyEi5jb21tb24udjEuWm9uZVJlZiIWChRMaXN0UmFja1R5cGVzUmVxdWVzdCI9CghSYWNrVHlwZRIMCgRyb3dzGAEgASgFEg8KB2NvbHVtbnMYAiABKAUSEgoKcmFja19jb3VudBgDIAEoBSJEChVMaXN0UmFja1R5cGVzUmVzcG9uc2USKwoKcmFja190eXBlcxgBIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1R5cGUiiwIKD1NhdmVSYWNrUmVxdWVzdBImCg1kZXZpY2Vfc2V0X2lkGAEgASgDQgq6SAfYAQEiAiAASACIAQESGwoFbGFiZWwYAiABKAlCDLpICcgBAXIEEAEYZBIyCglyYWNrX2luZm8YAyABKAsyFy5kZXZpY2Vfc2V0LnYxLlJhY2tJbmZvQga6SAPIAQESOgoPZGV2aWNlX3NlbGVjdG9yGAQgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQESMQoQc2xvdF9hc3NpZ25tZW50cxgFIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3RCEAoOX2RldmljZV9zZXRfaWQidwoQU2F2ZVJhY2tSZXNwb25zZRIsCgpkZXZpY2Vfc2V0GAEgASgLMhguZGV2aWNlX3NldC52MS5EZXZpY2VTZXQSFgoOYXNzaWduZWRfY291bnQYAiABKAUSHQoVc2l0ZV9yZWFzc2lnbmVkX2NvdW50GAMgASgFIocBChpBc3NpZ25EZXZpY2VzVG9SYWNrUmVxdWVzdBIkCg50YXJnZXRfcmFja19pZBgBIAEoA0IHukgEIgIgAEgAiAEBEjAKEmRldmljZV9pZGVudGlmaWVycxgCIAMoCUIUukgRkgEOCAEQkE4iB3IFEAEYgAJCEQoPX3RhcmdldF9yYWNrX2lkImsKG0Fzc2lnbkRldmljZXNUb1JhY2tSZXNwb25zZRIWCg5hc3NpZ25lZF9jb3VudBgBIAEoAxIdChVzaXRlX3JlYXNzaWduZWRfY291bnQYAiABKAMSFQoNcmVtb3ZlZF9jb3VudBgDIAEoAyplCg1EZXZpY2VTZXRUeXBlEh8KG0RFVklDRV9TRVRfVFlQRV9VTlNQRUNJRklFRBAAEhkKFURFVklDRV9TRVRfVFlQRV9HUk9VUBABEhgKFERFVklDRV9TRVRfVFlQRV9SQUNLEAIqtgEKDlJhY2tPcmRlckluZGV4EiAKHFJBQ0tfT1JERVJfSU5ERVhfVU5TUEVDSUZJRUQQABIgChxSQUNLX09SREVSX0lOREVYX0JPVFRPTV9MRUZUEAESHQoZUkFDS19PUkRFUl9JTkRFWF9UT1BfTEVGVBACEiEKHVJBQ0tfT1JERVJfSU5ERVhfQk9UVE9NX1JJR0hUEAMSHgoaUkFDS19PUkRFUl9JTkRFWF9UT1BfUklHSFQQBCpwCg9SYWNrQ29vbGluZ1R5cGUSIQodUkFDS19DT09MSU5HX1RZUEVfVU5TUEVDSUZJRUQQABIZChVSQUNLX0NPT0xJTkdfVFlQRV9BSVIQARIfChtSQUNLX0NPT0xJTkdfVFlQRV9JTU1FUlNJT04QAirdAQoQU2xvdERldmljZVN0YXR1cxIiCh5TTE9UX0RFVklDRV9TVEFUVVNfVU5TUEVDSUZJRUQQABIcChhTTE9UX0RFVklDRV9TVEFUVVNfRU1QVFkQARIeChpTTE9UX0RFVklDRV9TVEFUVVNfSEVBTFRIWRACEiYKIlNMT1RfREVWSUNFX1NUQVRVU19ORUVEU19BVFRFTlRJT04QAxIeChpTTE9UX0RFVklDRV9TVEFUVVNfT0ZGTElORRAEEh8KG1NMT1RfREVWSUNFX1NUQVRVU19TTEVFUElORxAFMsIOChBEZXZpY2VTZXRTZXJ2aWNlEmAKD0NyZWF0ZURldmljZVNldBIlLmRldmljZV9zZXQudjEuQ3JlYXRlRGV2aWNlU2V0UmVxdWVzdBomLmRldmljZV9zZXQudjEuQ3JlYXRlRGV2aWNlU2V0UmVzcG9uc2USVwoMR2V0RGV2aWNlU2V0EiIuZGV2aWNlX3NldC52MS5HZXREZXZpY2VTZXRSZXF1ZXN0GiMuZGV2aWNlX3NldC52MS5HZXREZXZpY2VTZXRSZXNwb25zZRJgCg9VcGRhdGVEZXZpY2VTZXQSJS5kZXZpY2Vfc2V0LnYxLlVwZGF0ZURldmljZVNldFJlcXVlc3QaJi5kZXZpY2Vfc2V0LnYxLlVwZGF0ZURldmljZVNldFJlc3BvbnNlEmAKD0RlbGV0ZURldmljZVNldBIlLmRldmljZV9zZXQudjEuRGVsZXRlRGV2aWNlU2V0UmVxdWVzdBomLmRldmljZV9zZXQudjEuRGVsZXRlRGV2aWNlU2V0UmVzcG9uc2USXQoOTGlzdERldmljZVNldHMSJC5kZXZpY2Vfc2V0LnYxLkxpc3REZXZpY2VTZXRzUmVxdWVzdBolLmRldmljZV9zZXQudjEuTGlzdERldmljZVNldHNSZXNwb25zZRJyChVBZGREZXZpY2VzVG9EZXZpY2VTZXQSKy5kZXZpY2Vfc2V0LnYxLkFkZERldmljZXNUb0RldmljZVNldFJlcXVlc3QaLC5kZXZpY2Vfc2V0LnYxLkFkZERldmljZXNUb0RldmljZVNldFJlc3BvbnNlEoEBChpSZW1vdmVEZXZpY2VzRnJvbURldmljZVNldBIwLmRldmljZV9zZXQudjEuUmVtb3ZlRGV2aWNlc0Zyb21EZXZpY2VTZXRSZXF1ZXN0GjEuZGV2aWNlX3NldC52MS5SZW1vdmVEZXZpY2VzRnJvbURldmljZVNldFJlc3BvbnNlEm8KFExpc3REZXZpY2VTZXRNZW1iZXJzEiouZGV2aWNlX3NldC52MS5MaXN0RGV2aWNlU2V0TWVtYmVyc1JlcXVlc3QaKy5kZXZpY2Vfc2V0LnYxLkxpc3REZXZpY2VTZXRNZW1iZXJzUmVzcG9uc2USbAoTR2V0RGV2aWNlRGV2aWNlU2V0cxIpLmRldmljZV9zZXQudjEuR2V0RGV2aWNlRGV2aWNlU2V0c1JlcXVlc3QaKi5kZXZpY2Vfc2V0LnYxLkdldERldmljZURldmljZVNldHNSZXNwb25zZRJsChNTZXRSYWNrU2xvdFBvc2l0aW9uEikuZGV2aWNlX3NldC52MS5TZXRSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBoqLmRldmljZV9zZXQudjEuU2V0UmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlEnIKFUNsZWFyUmFja1Nsb3RQb3NpdGlvbhIrLmRldmljZV9zZXQudjEuQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBosLmRldmljZV9zZXQudjEuQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVzcG9uc2USVwoMR2V0UmFja1Nsb3RzEiIuZGV2aWNlX3NldC52MS5HZXRSYWNrU2xvdHNSZXF1ZXN0GiMuZGV2aWNlX3NldC52MS5HZXRSYWNrU2xvdHNSZXNwb25zZRJmChFHZXREZXZpY2VTZXRTdGF0cxInLmRldmljZV9zZXQudjEuR2V0RGV2aWNlU2V0U3RhdHNSZXF1ZXN0GiguZGV2aWNlX3NldC52MS5HZXREZXZpY2VTZXRTdGF0c1Jlc3BvbnNlEloKDUxpc3RSYWNrWm9uZXMSIy5kZXZpY2Vfc2V0LnYxLkxpc3RSYWNrWm9uZXNSZXF1ZXN0GiQuZGV2aWNlX3NldC52MS5MaXN0UmFja1pvbmVzUmVzcG9uc2USYwoQTGlzdFJhY2tab25lUmVmcxImLmRldmljZV9zZXQudjEuTGlzdFJhY2tab25lUmVmc1JlcXVlc3QaJy5kZXZpY2Vfc2V0LnYxLkxpc3RSYWNrWm9uZVJlZnNSZXNwb25zZRJaCg1MaXN0UmFja1R5cGVzEiMuZGV2aWNlX3NldC52MS5MaXN0UmFja1R5cGVzUmVxdWVzdBokLmRldmljZV9zZXQudjEuTGlzdFJhY2tUeXBlc1Jlc3BvbnNlEksKCFNhdmVSYWNrEh4uZGV2aWNlX3NldC52MS5TYXZlUmFja1JlcXVlc3QaHy5kZXZpY2Vfc2V0LnYxLlNhdmVSYWNrUmVzcG9uc2USbAoTQXNzaWduRGV2aWNlc1RvUmFjaxIpLmRldmljZV9zZXQudjEuQXNzaWduRGV2aWNlc1RvUmFja1JlcXVlc3QaKi5kZXZpY2Vfc2V0LnYxLkFzc2lnbkRldmljZXNUb1JhY2tSZXNwb25zZULDAQoRY29tLmRldmljZV9zZXQudjFCDkRldmljZVNldFByb3RvUAFaTWdpdGh1Yi5jb20vYmxvY2svcHJvdG8tZmxlZXQvc2VydmVyL2dlbmVyYXRlZC9ncnBjL2RldmljZV9zZXQvdjE7ZGV2aWNlX3NldHYxogIDRFhYqgIMRGV2aWNlU2V0LlYxygIMRGV2aWNlU2V0XFYx4gIYRGV2aWNlU2V0XFYxXEdQQk1ldGFkYXRh6gINRGV2aWNlU2V0OjpWMWIGcHJvdG8z", [ file_google_protobuf_timestamp, file_buf_validate_validate, @@ -80,14 +80,14 @@ export type DeviceSet = Message<"device_set.v1.DeviceSet"> & { * * @generated from field: google.protobuf.Timestamp created_at = 6; */ - createdAt?: Timestamp | undefined; + createdAt?: Timestamp; /** * When the device set was last updated * * @generated from field: google.protobuf.Timestamp updated_at = 7; */ - updatedAt?: Timestamp | undefined; + updatedAt?: Timestamp; /** * Type-specific metadata (enforces only one detail type is set) @@ -171,7 +171,7 @@ export type RackInfo = Message<"device_set.v1.RackInfo"> & { * * @generated from field: optional int64 site_id = 6; */ - siteId?: bigint | undefined; + siteId?: bigint; /** * Building this rack belongs to. Unset = directly under site_id (or @@ -180,7 +180,7 @@ export type RackInfo = Message<"device_set.v1.RackInfo"> & { * * @generated from field: optional int64 building_id = 7; */ - buildingId?: bigint | undefined; + buildingId?: bigint; }; /** @@ -221,7 +221,7 @@ export type DeviceSetMember = Message<"device_set.v1.DeviceSetMember"> & { * * @generated from field: google.protobuf.Timestamp added_at = 2; */ - addedAt?: Timestamp | undefined; + addedAt?: Timestamp; /** * Type-specific member details @@ -258,7 +258,7 @@ export type RackMemberDetails = Message<"device_set.v1.RackMemberDetails"> & { * * @generated from field: device_set.v1.RackSlotPosition slot_position = 1; */ - slotPosition?: RackSlotPosition | undefined; + slotPosition?: RackSlotPosition; }; /** @@ -353,7 +353,7 @@ export type CreateDeviceSetRequest = Message<"device_set.v1.CreateDeviceSetReque * * @generated from field: optional common.v1.DeviceSelector device_selector = 6; */ - deviceSelector?: DeviceSelector | undefined; + deviceSelector?: DeviceSelector; }; /** @@ -375,7 +375,7 @@ export type CreateDeviceSetResponse = Message<"device_set.v1.CreateDeviceSetResp * * @generated from field: device_set.v1.DeviceSet device_set = 1; */ - deviceSet?: DeviceSet | undefined; + deviceSet?: DeviceSet; /** * Number of devices added to the device set (0 if no device_selector was provided) @@ -426,7 +426,7 @@ export type GetDeviceSetResponse = Message<"device_set.v1.GetDeviceSetResponse"> * * @generated from field: device_set.v1.DeviceSet device_set = 1; */ - deviceSet?: DeviceSet | undefined; + deviceSet?: DeviceSet; }; /** @@ -455,7 +455,7 @@ export type UpdateDeviceSetRequest = Message<"device_set.v1.UpdateDeviceSetReque * * @generated from field: optional string label = 2; */ - label?: string | undefined; + label?: string; /** * New description (optional, max 500 characters if provided). @@ -463,7 +463,7 @@ export type UpdateDeviceSetRequest = Message<"device_set.v1.UpdateDeviceSetReque * * @generated from field: optional string description = 3; */ - description?: string | undefined; + description?: string; /** * Type-specific metadata updates (only applicable for the device set's type) @@ -492,7 +492,7 @@ export type UpdateDeviceSetRequest = Message<"device_set.v1.UpdateDeviceSetReque * * @generated from field: common.v1.DeviceSelector device_selector = 6; */ - deviceSelector?: DeviceSelector | undefined; + deviceSelector?: DeviceSelector; }; /** @@ -514,7 +514,7 @@ export type UpdateDeviceSetResponse = Message<"device_set.v1.UpdateDeviceSetResp * * @generated from field: device_set.v1.DeviceSet device_set = 1; */ - deviceSet?: DeviceSet | undefined; + deviceSet?: DeviceSet; }; /** @@ -597,7 +597,7 @@ export type ListDeviceSetsRequest = Message<"device_set.v1.ListDeviceSetsRequest * * @generated from field: common.v1.SortConfig sort = 4; */ - sort?: SortConfig | undefined; + sort?: SortConfig; /** * Filter by device sets containing devices with open errors of these component types. @@ -722,7 +722,7 @@ export type AddDevicesToDeviceSetRequest = Message<"device_set.v1.AddDevicesToDe * * @generated from field: common.v1.DeviceSelector device_selector = 2; */ - deviceSelector?: DeviceSelector | undefined; + deviceSelector?: DeviceSelector; }; /** @@ -788,7 +788,7 @@ export type RemoveDevicesFromDeviceSetRequest = Message<"device_set.v1.RemoveDev * * @generated from field: common.v1.DeviceSelector device_selector = 2; */ - deviceSelector?: DeviceSelector | undefined; + deviceSelector?: DeviceSelector; }; /** @@ -962,7 +962,7 @@ export type SetRackSlotPositionRequest = Message<"device_set.v1.SetRackSlotPosit * * @generated from field: device_set.v1.RackSlotPosition position = 3; */ - position?: RackSlotPosition | undefined; + position?: RackSlotPosition; }; /** @@ -991,7 +991,7 @@ export type SetRackSlotPositionResponse = Message<"device_set.v1.SetRackSlotPosi * * @generated from field: device_set.v1.RackSlot slot = 2; */ - slot?: RackSlot | undefined; + slot?: RackSlot; }; /** @@ -1086,7 +1086,7 @@ export type RackSlot = Message<"device_set.v1.RackSlot"> & { * * @generated from field: device_set.v1.RackSlotPosition position = 2; */ - position?: RackSlotPosition | undefined; + position?: RackSlotPosition; }; /** @@ -1508,7 +1508,7 @@ export type SaveRackRequest = Message<"device_set.v1.SaveRackRequest"> & { * * @generated from field: optional int64 device_set_id = 1; */ - deviceSetId?: bigint | undefined; + deviceSetId?: bigint; /** * Label for the rack (required, 1-100 characters) @@ -1522,7 +1522,7 @@ export type SaveRackRequest = Message<"device_set.v1.SaveRackRequest"> & { * * @generated from field: device_set.v1.RackInfo rack_info = 3; */ - rackInfo?: RackInfo | undefined; + rackInfo?: RackInfo; /** * Devices that should be members of this rack. @@ -1530,7 +1530,7 @@ export type SaveRackRequest = Message<"device_set.v1.SaveRackRequest"> & { * * @generated from field: common.v1.DeviceSelector device_selector = 4; */ - deviceSelector?: DeviceSelector | undefined; + deviceSelector?: DeviceSelector; /** * Slot assignments for devices within the rack. @@ -1561,7 +1561,7 @@ export type SaveRackResponse = Message<"device_set.v1.SaveRackResponse"> & { * * @generated from field: device_set.v1.DeviceSet device_set = 1; */ - deviceSet?: DeviceSet | undefined; + deviceSet?: DeviceSet; /** * Number of slot positions assigned @@ -1586,6 +1586,73 @@ export const SaveRackResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_device_set_v1_device_set, 43); +/** + * @generated from message device_set.v1.AssignDevicesToRackRequest + */ +export type AssignDevicesToRackRequest = Message<"device_set.v1.AssignDevicesToRackRequest"> & { + /** + * Unset = clear rack membership without re-assigning. When + * present, must be > 0. + * + * @generated from field: optional int64 target_rack_id = 1; + */ + targetRackId?: bigint; + + /** + * @generated from field: repeated string device_identifiers = 2; + */ + deviceIdentifiers: string[]; +}; + +/** + * Describes the message device_set.v1.AssignDevicesToRackRequest. + * Use `create(AssignDevicesToRackRequestSchema)` to create a new message. + */ +export const AssignDevicesToRackRequestSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_device_set_v1_device_set, 44); + +/** + * @generated from message device_set.v1.AssignDevicesToRackResponse + */ +export type AssignDevicesToRackResponse = Message<"device_set.v1.AssignDevicesToRackResponse"> & { + /** + * Number of devices whose rack membership now points at + * target_rack_id. When target_rack_id is unset, this is 0 and + * removed_count carries the cleared-rack count. + * + * @generated from field: int64 assigned_count = 1; + */ + assignedCount: bigint; + + /** + * Number of devices whose site_id was rewritten by the cascade + * because the target rack's site differed from the device's prior + * site. Zero on unassign and on same-site moves. + * + * @generated from field: int64 site_reassigned_count = 2; + */ + siteReassignedCount: bigint; + + /** + * Number of devices whose prior rack membership was cleared. On + * unassign (target_rack_id unset) this equals the number of devices + * that were in a rack. On a re-assign this counts the prior rack + * membership rows that were deleted before the new ones inserted. + * + * @generated from field: int64 removed_count = 3; + */ + removedCount: bigint; +}; + +/** + * Describes the message device_set.v1.AssignDevicesToRackResponse. + * Use `create(AssignDevicesToRackResponseSchema)` to create a new message. + */ +export const AssignDevicesToRackResponseSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_device_set_v1_device_set, 45); + /** * Type of device set * @@ -1910,4 +1977,25 @@ export const DeviceSetService: GenService<{ input: typeof SaveRackRequestSchema; output: typeof SaveRackResponseSchema; }; + /** + * AssignDevicesToRack atomically moves devices into the target rack + * in one transaction: removes any existing rack membership for each + * device, inserts new membership at target_rack_id, and cascades + * device.site_id when the target rack's site differs from the + * device's current site. target_rack_id unset clears rack + * membership without re-assigning (site/building stay intact). + * + * Closes the orphan window where a client-side + * RemoveDevicesFromDeviceSet + AddDevicesToDeviceSet pair could + * leave devices without rack membership on transport failure between + * the two calls, and the cross-rack rename race where the source + * rack's id resolution drifts between the resolve and the remove. + * + * @generated from rpc device_set.v1.DeviceSetService.AssignDevicesToRack + */ + assignDevicesToRack: { + methodKind: "unary"; + input: typeof AssignDevicesToRackRequestSchema; + output: typeof AssignDevicesToRackResponseSchema; + }; }> = /*@__PURE__*/ serviceDesc(file_device_set_v1_device_set, 0); diff --git a/client/src/protoFleet/api/useDeviceSets.ts b/client/src/protoFleet/api/useDeviceSets.ts index e7ef2603b..905840651 100644 --- a/client/src/protoFleet/api/useDeviceSets.ts +++ b/client/src/protoFleet/api/useDeviceSets.ts @@ -165,6 +165,16 @@ interface ClearRackSlotPositionProps { onFinally?: () => void; } +interface AssignDevicesToRackProps { + // Unset clears rack membership without re-assigning (site/building + // stay intact). + targetRackId?: bigint; + deviceIdentifiers: string[]; + onSuccess?: (assignedCount: bigint, siteReassignedCount: bigint, removedCount: bigint) => void; + onError?: (message: string) => void; + onFinally?: () => void; +} + interface SaveRackProps { deviceSetId?: bigint; label: string; @@ -601,6 +611,33 @@ const useDeviceSets = () => { [handleAuthErrors], ); + // assignDevicesToRack wraps the atomic rack-reassignment RPC. + // Replaces the old client-side remove + add orchestration so a + // server error / network blip between the two calls can't orphan + // miners from rack assignment (issue #420). Pass targetRackId + // unset to clear rack membership without re-assigning. + const assignDevicesToRack = useCallback( + async ({ targetRackId, deviceIdentifiers, onSuccess, onError, onFinally }: AssignDevicesToRackProps) => { + try { + const response = await deviceSetClient.assignDevicesToRack({ + targetRackId, + deviceIdentifiers, + }); + onSuccess?.(response.assignedCount, response.siteReassignedCount, response.removedCount); + } catch (err) { + handleAuthErrors({ + error: err, + onError: () => { + onError?.(getErrorMessage(err)); + }, + }); + } finally { + onFinally?.(); + } + }, + [handleAuthErrors], + ); + const removeDevicesFromDeviceSet = useCallback( async ({ deviceSetId, @@ -848,6 +885,7 @@ const useDeviceSets = () => { listGroupMembers, getDeviceSetStats, addDevicesToDeviceSet, + assignDevicesToRack, removeDevicesFromDeviceSet, getRackSlots, setRackSlotPosition, diff --git a/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx b/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx index fc4e5683d..9d6f46fce 100644 --- a/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx +++ b/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx @@ -86,32 +86,6 @@ const resolveRackMembers = async (rackId: bigint): Promise> => { return members; }; -// Group ids that need to move out of a source rack before they can be -// added to the target. `idx_one_rack_per_device` enforces a single -// rack per miner, so a same-batch INSERT against a different rack -// aborts the whole transaction; we orchestrate the remove→add ourselves. -const groupBySourceRack = ( - ids: string[], - miners: Record | undefined, - targetRack: DeviceSet, - currentMembers: Set, -): Map => { - const movesByLabel = new Map(); - if (!miners) return movesByLabel; - const targetLabel = targetRack.label; - for (const id of ids) { - if (currentMembers.has(id)) continue; - const snapshot = miners[id]; - if (!snapshot) continue; - const sourceLabel = snapshot.rackLabel; - if (!sourceLabel || sourceLabel === targetLabel) continue; - const bucket = movesByLabel.get(sourceLabel) ?? []; - bucket.push(id); - movesByLabel.set(sourceLabel, bucket); - } - return movesByLabel; -}; - // Detect miners whose current rack lives at a different site than the // target. `ReassignDevicesToSite` rejects these with // `DEVICE_IN_RACK_AT_OTHER_SITE` and aborts the whole batch; we @@ -189,7 +163,7 @@ const MinerReparentPicker = ({ onRefetchMiners, }: MinerReparentPickerProps) => { const { assignDevicesToSite } = useSites(); - const { addDevicesToDeviceSet, getDeviceSet, listRacks, removeDevicesFromDeviceSet } = useDeviceSets(); + const { assignDevicesToRack, getDeviceSet, listRacks, removeDevicesFromDeviceSet } = useDeviceSets(); const [siteMoveConfirmation, setSiteMoveConfirmation] = useState(null); const [siteMoveInFlight, setSiteMoveInFlight] = useState(false); // Resolver for the onConfirm promise during the cross-site confirm @@ -311,11 +285,7 @@ const MinerReparentPicker = ({ }); }; - const dispatchReparentToRack = async ( - targetRackId: bigint, - ids: string[], - minerSnapshots: Record | undefined, - ) => { + const dispatchReparentToRack = async (targetRackId: bigint, ids: string[]) => { let rack: DeviceSet; let currentMembers: Set; try { @@ -331,50 +301,20 @@ const MinerReparentPicker = ({ return; } - // Miners currently in a different rack need to leave that rack - // first — server enforces one rack per device. Orchestrate the - // remove-then-add so the picker behaves as a re-parent rather than - // a duplicate insert. - const movesByLabel = groupBySourceRack(ids, minerSnapshots, rack, currentMembers); - if (movesByLabel.size > 0) { - let racks: DeviceSet[]; - try { - racks = await fetchAllRacks(); - } catch (err) { - const message = err instanceof Error ? err.message : "Couldn't load racks."; - pushToast({ message, status: STATUSES.error }); - return; - } - const labelToRackId = new Map(); - for (const r of racks) if (r.label) labelToRackId.set(r.label, r.id); - - const movingToast = pushToast({ - message: `Moving miners from ${movesByLabel.size} other rack${movesByLabel.size === 1 ? "" : "s"}…`, - status: STATUSES.loading, - longRunning: true, - }); - try { - for (const [src, sourceIds] of movesByLabel) { - const sourceRackId = labelToRackId.get(src); - if (sourceRackId === undefined) continue; - await removeFromRack(sourceRackId, sourceIds); - } - } catch (err) { - const message = err instanceof Error ? err.message : "Failed to remove miners from current rack."; - updateToast(movingToast, { message, status: STATUSES.error }); - return; - } - removeToast(movingToast); - } - - void addDevicesToDeviceSet({ - deviceSetId: targetRackId, + // AssignDevicesToRack clears prior rack membership and inserts the + // new membership inside one server-side transaction. Previously + // this was a client-side remove-then-add loop that resolved source + // racks via listRacks and could (a) orphan miners on a transport + // failure between the two calls, and (b) skip a rack whose label + // got renamed mid-confirm. The atomic RPC closes both holes. + void assignDevicesToRack({ + targetRackId, deviceIdentifiers: ids, onSuccess: (count) => { pushToast({ message: successMessage(count, "rack"), status: STATUSES.success }); onRefetchMiners?.(); }, - onError: (msg) => pushToast({ message: `Couldn't add miners to rack: ${msg}`, status: STATUSES.error }), + onError: (msg) => pushToast({ message: `Couldn't move miners to rack: ${msg}`, status: STATUSES.error }), }); }; @@ -385,7 +325,7 @@ const MinerReparentPicker = ({ ) => kind === "site" ? dispatchReparentToSite(targetId, ids, minerSnapshots) - : dispatchReparentToRack(targetId, ids, minerSnapshots); + : dispatchReparentToRack(targetId, ids); const settleDialog = () => { const resolve = dialogResolveRef.current; diff --git a/proto/device_set/v1/device_set.proto b/proto/device_set/v1/device_set.proto index d8e25f6f4..29d594829 100644 --- a/proto/device_set/v1/device_set.proto +++ b/proto/device_set/v1/device_set.proto @@ -68,6 +68,20 @@ service DeviceSetService { // Atomically creates or updates a rack with its membership and slot assignments. // All operations (metadata, membership, slot positions) are applied in a single transaction. rpc SaveRack(SaveRackRequest) returns (SaveRackResponse); + + // AssignDevicesToRack atomically moves devices into the target rack + // in one transaction: removes any existing rack membership for each + // device, inserts new membership at target_rack_id, and cascades + // device.site_id when the target rack's site differs from the + // device's current site. target_rack_id unset clears rack + // membership without re-assigning (site/building stay intact). + // + // Closes the orphan window where a client-side + // RemoveDevicesFromDeviceSet + AddDevicesToDeviceSet pair could + // leave devices without rack membership on transport failure between + // the two calls, and the cross-rack rename race where the source + // rack's id resolution drifts between the resolve and the remove. + rpc AssignDevicesToRack(AssignDevicesToRackRequest) returns (AssignDevicesToRackResponse); } // Type of device set @@ -629,3 +643,32 @@ message SaveRackResponse { // Number of devices whose site_id was rewritten by the cascade. int32 site_reassigned_count = 3; } + +// AssignDevicesToRack + +message AssignDevicesToRackRequest { + // Unset = clear rack membership without re-assigning. When + // present, must be > 0. + optional int64 target_rack_id = 1 [(buf.validate.field).int64.gt = 0]; + repeated string device_identifiers = 2 [(buf.validate.field).repeated = { + min_items: 1 + max_items: 10000 + items: {string: {min_len: 1, max_len: 256}} + }]; +} + +message AssignDevicesToRackResponse { + // Number of devices whose rack membership now points at + // target_rack_id. When target_rack_id is unset, this is 0 and + // removed_count carries the cleared-rack count. + int64 assigned_count = 1; + // Number of devices whose site_id was rewritten by the cascade + // because the target rack's site differed from the device's prior + // site. Zero on unassign and on same-site moves. + int64 site_reassigned_count = 2; + // Number of devices whose prior rack membership was cleared. On + // unassign (target_rack_id unset) this equals the number of devices + // that were in a rack. On a re-assign this counts the prior rack + // membership rows that were deleted before the new ones inserted. + int64 removed_count = 3; +} diff --git a/server/generated/grpc/device_set/v1/device_set.pb.go b/server/generated/grpc/device_set/v1/device_set.pb.go index 8d9ebc532..4b155496c 100644 --- a/server/generated/grpc/device_set/v1/device_set.pb.go +++ b/server/generated/grpc/device_set/v1/device_set.pb.go @@ -3099,6 +3099,130 @@ func (x *SaveRackResponse) GetSiteReassignedCount() int32 { return 0 } +type AssignDevicesToRackRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Unset = clear rack membership without re-assigning. When + // present, must be > 0. + TargetRackId *int64 `protobuf:"varint,1,opt,name=target_rack_id,json=targetRackId,proto3,oneof" json:"target_rack_id,omitempty"` + DeviceIdentifiers []string `protobuf:"bytes,2,rep,name=device_identifiers,json=deviceIdentifiers,proto3" json:"device_identifiers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssignDevicesToRackRequest) Reset() { + *x = AssignDevicesToRackRequest{} + mi := &file_device_set_v1_device_set_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssignDevicesToRackRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssignDevicesToRackRequest) ProtoMessage() {} + +func (x *AssignDevicesToRackRequest) ProtoReflect() protoreflect.Message { + mi := &file_device_set_v1_device_set_proto_msgTypes[44] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssignDevicesToRackRequest.ProtoReflect.Descriptor instead. +func (*AssignDevicesToRackRequest) Descriptor() ([]byte, []int) { + return file_device_set_v1_device_set_proto_rawDescGZIP(), []int{44} +} + +func (x *AssignDevicesToRackRequest) GetTargetRackId() int64 { + if x != nil && x.TargetRackId != nil { + return *x.TargetRackId + } + return 0 +} + +func (x *AssignDevicesToRackRequest) GetDeviceIdentifiers() []string { + if x != nil { + return x.DeviceIdentifiers + } + return nil +} + +type AssignDevicesToRackResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Number of devices whose rack membership now points at + // target_rack_id. When target_rack_id is unset, this is 0 and + // removed_count carries the cleared-rack count. + AssignedCount int64 `protobuf:"varint,1,opt,name=assigned_count,json=assignedCount,proto3" json:"assigned_count,omitempty"` + // Number of devices whose site_id was rewritten by the cascade + // because the target rack's site differed from the device's prior + // site. Zero on unassign and on same-site moves. + SiteReassignedCount int64 `protobuf:"varint,2,opt,name=site_reassigned_count,json=siteReassignedCount,proto3" json:"site_reassigned_count,omitempty"` + // Number of devices whose prior rack membership was cleared. On + // unassign (target_rack_id unset) this equals the number of devices + // that were in a rack. On a re-assign this counts the prior rack + // membership rows that were deleted before the new ones inserted. + RemovedCount int64 `protobuf:"varint,3,opt,name=removed_count,json=removedCount,proto3" json:"removed_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssignDevicesToRackResponse) Reset() { + *x = AssignDevicesToRackResponse{} + mi := &file_device_set_v1_device_set_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssignDevicesToRackResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssignDevicesToRackResponse) ProtoMessage() {} + +func (x *AssignDevicesToRackResponse) ProtoReflect() protoreflect.Message { + mi := &file_device_set_v1_device_set_proto_msgTypes[45] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssignDevicesToRackResponse.ProtoReflect.Descriptor instead. +func (*AssignDevicesToRackResponse) Descriptor() ([]byte, []int) { + return file_device_set_v1_device_set_proto_rawDescGZIP(), []int{45} +} + +func (x *AssignDevicesToRackResponse) GetAssignedCount() int64 { + if x != nil { + return x.AssignedCount + } + return 0 +} + +func (x *AssignDevicesToRackResponse) GetSiteReassignedCount() int64 { + if x != nil { + return x.SiteReassignedCount + } + return 0 +} + +func (x *AssignDevicesToRackResponse) GetRemovedCount() int64 { + if x != nil { + return x.RemovedCount + } + return 0 +} + var File_device_set_v1_device_set_proto protoreflect.FileDescriptor var file_device_set_v1_device_set_proto_rawDesc = string([]byte{ @@ -3543,168 +3667,196 @@ var file_device_set_v1_device_set_proto_rawDesc = string([]byte{ 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0x65, 0x0a, 0x0d, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x44, 0x45, - 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x44, - 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, - 0x52, 0x4f, 0x55, 0x50, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, - 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x41, 0x43, 0x4b, 0x10, 0x02, - 0x2a, 0xb6, 0x01, 0x0a, 0x0e, 0x52, 0x61, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, - 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, - 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, 0x4f, 0x4d, - 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x41, 0x43, 0x4b, 0x5f, - 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, 0x4f, 0x50, 0x5f, - 0x4c, 0x45, 0x46, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, - 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, 0x4f, - 0x4d, 0x5f, 0x52, 0x49, 0x47, 0x48, 0x54, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x41, 0x43, - 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, 0x4f, - 0x50, 0x5f, 0x52, 0x49, 0x47, 0x48, 0x54, 0x10, 0x04, 0x2a, 0x70, 0x0a, 0x0f, 0x52, 0x61, 0x63, - 0x6b, 0x43, 0x6f, 0x6f, 0x6c, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x1d, - 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x19, 0x0a, 0x15, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x49, 0x52, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x41, + 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xa8, 0x01, 0x0a, 0x1a, 0x41, 0x73, + 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x61, 0x63, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x5f, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x43, 0x0a, 0x12, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, 0x14, 0xba, 0x48, 0x11, 0x92, 0x01, 0x0e, + 0x08, 0x01, 0x10, 0x90, 0x4e, 0x22, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x02, 0x52, 0x11, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x61, 0x63, + 0x6b, 0x5f, 0x69, 0x64, 0x22, 0x9d, 0x01, 0x0a, 0x1b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x73, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x73, + 0x69, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x73, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0x65, 0x0a, 0x0d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, + 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, + 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50, 0x10, + 0x01, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x45, 0x54, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x41, 0x43, 0x4b, 0x10, 0x02, 0x2a, 0xb6, 0x01, 0x0a, 0x0e, + 0x52, 0x61, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, + 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, + 0x45, 0x58, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, + 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, 0x4f, 0x4d, 0x5f, 0x4c, 0x45, 0x46, 0x54, + 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, + 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, 0x4f, 0x50, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x10, + 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, + 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, 0x4f, 0x4d, 0x5f, 0x52, 0x49, 0x47, + 0x48, 0x54, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, + 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, 0x4f, 0x50, 0x5f, 0x52, 0x49, 0x47, + 0x48, 0x54, 0x10, 0x04, 0x2a, 0x70, 0x0a, 0x0f, 0x52, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x6f, 0x6c, + 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, 0x4b, 0x5f, + 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x49, 0x4d, 0x4d, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x2a, 0xdd, 0x01, 0x0a, 0x10, - 0x53, 0x6c, 0x6f, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, - 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, - 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, - 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x4d, 0x50, 0x54, 0x59, - 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, - 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, - 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, - 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4e, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x41, - 0x54, 0x54, 0x45, 0x4e, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x4c, - 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, - 0x5f, 0x4f, 0x46, 0x46, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x4c, - 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, - 0x5f, 0x53, 0x4c, 0x45, 0x45, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x32, 0xd4, 0x0d, 0x0a, 0x10, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x60, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x53, 0x65, 0x74, 0x12, 0x25, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, - 0x65, 0x74, 0x12, 0x22, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x25, - 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, + 0x41, 0x49, 0x52, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, + 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4d, 0x4d, 0x45, 0x52, + 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x2a, 0xdd, 0x01, 0x0a, 0x10, 0x53, 0x6c, 0x6f, 0x74, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x1e, 0x53, + 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x1c, 0x0a, 0x18, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x10, 0x01, 0x12, 0x1e, 0x0a, + 0x1a, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, + 0x54, 0x55, 0x53, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x02, 0x12, 0x26, 0x0a, + 0x22, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, + 0x54, 0x55, 0x53, 0x5f, 0x4e, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x41, 0x54, 0x54, 0x45, 0x4e, 0x54, + 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, + 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4f, 0x46, 0x46, 0x4c, + 0x49, 0x4e, 0x45, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, + 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x4c, 0x45, 0x45, + 0x50, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x32, 0xc2, 0x0e, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x53, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x25, + 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, - 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, - 0x12, 0x25, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x5d, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, - 0x73, 0x12, 0x24, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, - 0x0a, 0x15, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x2b, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x54, 0x6f, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, - 0x6f, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, - 0x74, 0x12, 0x30, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, - 0x72, 0x6f, 0x6d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x2a, - 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x29, - 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, + 0x0c, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x22, 0x2e, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x25, 0x2e, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x25, 0x2e, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x4c, 0x69, + 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x41, 0x64, 0x64, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, + 0x65, 0x74, 0x12, 0x2b, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2c, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x81, 0x01, + 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, + 0x72, 0x6f, 0x6d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x30, 0x2e, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, + 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, + 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, - 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, - 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x69, 0x63, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x6c, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, - 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, - 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x2e, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, - 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, - 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x61, - 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, - 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, - 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x66, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, - 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, - 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, + 0x0a, 0x15, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, + 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, + 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, + 0x74, 0x73, 0x12, 0x22, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, + 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, + 0x12, 0x27, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, + 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, - 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, - 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, - 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, 0x73, 0x12, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, - 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x27, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x64, 0x65, 0x76, + 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x63, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, + 0x65, 0x66, 0x73, 0x12, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, + 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, + 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, + 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x24, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, - 0x6b, 0x12, 0x1e, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1f, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x42, 0xc3, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x42, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, - 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, - 0x02, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x56, 0x31, 0xca, 0x02, - 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x53, 0x65, 0x74, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x12, 0x1e, 0x2e, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x76, + 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x76, + 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, + 0x13, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, + 0x52, 0x61, 0x63, 0x6b, 0x12, 0x29, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x54, 0x6f, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x52, + 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xc3, 0x01, 0x0a, 0x11, + 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x42, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, + 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, + 0x76, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x53, 0x65, 0x74, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x53, 0x65, 0x74, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, + 0x65, 0x74, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0xea, 0x02, 0x0d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x3a, 0x3a, 0x56, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -3720,7 +3872,7 @@ func file_device_set_v1_device_set_proto_rawDescGZIP() []byte { } var file_device_set_v1_device_set_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_device_set_v1_device_set_proto_msgTypes = make([]protoimpl.MessageInfo, 44) +var file_device_set_v1_device_set_proto_msgTypes = make([]protoimpl.MessageInfo, 46) var file_device_set_v1_device_set_proto_goTypes = []any{ (DeviceSetType)(0), // 0: device_set.v1.DeviceSetType (RackOrderIndex)(0), // 1: device_set.v1.RackOrderIndex @@ -3770,41 +3922,43 @@ var file_device_set_v1_device_set_proto_goTypes = []any{ (*ListRackTypesResponse)(nil), // 45: device_set.v1.ListRackTypesResponse (*SaveRackRequest)(nil), // 46: device_set.v1.SaveRackRequest (*SaveRackResponse)(nil), // 47: device_set.v1.SaveRackResponse - (*timestamppb.Timestamp)(nil), // 48: google.protobuf.Timestamp - (*v1.DeviceSelector)(nil), // 49: common.v1.DeviceSelector - (*v1.SortConfig)(nil), // 50: common.v1.SortConfig - (v11.ComponentType)(0), // 51: errors.v1.ComponentType - (*v1.ZoneKey)(nil), // 52: common.v1.ZoneKey - (*v1.ZoneRef)(nil), // 53: common.v1.ZoneRef + (*AssignDevicesToRackRequest)(nil), // 48: device_set.v1.AssignDevicesToRackRequest + (*AssignDevicesToRackResponse)(nil), // 49: device_set.v1.AssignDevicesToRackResponse + (*timestamppb.Timestamp)(nil), // 50: google.protobuf.Timestamp + (*v1.DeviceSelector)(nil), // 51: common.v1.DeviceSelector + (*v1.SortConfig)(nil), // 52: common.v1.SortConfig + (v11.ComponentType)(0), // 53: errors.v1.ComponentType + (*v1.ZoneKey)(nil), // 54: common.v1.ZoneKey + (*v1.ZoneRef)(nil), // 55: common.v1.ZoneRef } var file_device_set_v1_device_set_proto_depIdxs = []int32{ 0, // 0: device_set.v1.DeviceSet.type:type_name -> device_set.v1.DeviceSetType - 48, // 1: device_set.v1.DeviceSet.created_at:type_name -> google.protobuf.Timestamp - 48, // 2: device_set.v1.DeviceSet.updated_at:type_name -> google.protobuf.Timestamp + 50, // 1: device_set.v1.DeviceSet.created_at:type_name -> google.protobuf.Timestamp + 50, // 2: device_set.v1.DeviceSet.updated_at:type_name -> google.protobuf.Timestamp 5, // 3: device_set.v1.DeviceSet.rack_info:type_name -> device_set.v1.RackInfo 6, // 4: device_set.v1.DeviceSet.group_info:type_name -> device_set.v1.GroupInfo 1, // 5: device_set.v1.RackInfo.order_index:type_name -> device_set.v1.RackOrderIndex 2, // 6: device_set.v1.RackInfo.cooling_type:type_name -> device_set.v1.RackCoolingType - 48, // 7: device_set.v1.DeviceSetMember.added_at:type_name -> google.protobuf.Timestamp + 50, // 7: device_set.v1.DeviceSetMember.added_at:type_name -> google.protobuf.Timestamp 8, // 8: device_set.v1.DeviceSetMember.rack:type_name -> device_set.v1.RackMemberDetails 9, // 9: device_set.v1.RackMemberDetails.slot_position:type_name -> device_set.v1.RackSlotPosition 0, // 10: device_set.v1.CreateDeviceSetRequest.type:type_name -> device_set.v1.DeviceSetType 5, // 11: device_set.v1.CreateDeviceSetRequest.rack_info:type_name -> device_set.v1.RackInfo 6, // 12: device_set.v1.CreateDeviceSetRequest.group_info:type_name -> device_set.v1.GroupInfo - 49, // 13: device_set.v1.CreateDeviceSetRequest.device_selector:type_name -> common.v1.DeviceSelector + 51, // 13: device_set.v1.CreateDeviceSetRequest.device_selector:type_name -> common.v1.DeviceSelector 4, // 14: device_set.v1.CreateDeviceSetResponse.device_set:type_name -> device_set.v1.DeviceSet 4, // 15: device_set.v1.GetDeviceSetResponse.device_set:type_name -> device_set.v1.DeviceSet 5, // 16: device_set.v1.UpdateDeviceSetRequest.rack_info:type_name -> device_set.v1.RackInfo 6, // 17: device_set.v1.UpdateDeviceSetRequest.group_info:type_name -> device_set.v1.GroupInfo - 49, // 18: device_set.v1.UpdateDeviceSetRequest.device_selector:type_name -> common.v1.DeviceSelector + 51, // 18: device_set.v1.UpdateDeviceSetRequest.device_selector:type_name -> common.v1.DeviceSelector 4, // 19: device_set.v1.UpdateDeviceSetResponse.device_set:type_name -> device_set.v1.DeviceSet 0, // 20: device_set.v1.ListDeviceSetsRequest.type:type_name -> device_set.v1.DeviceSetType - 50, // 21: device_set.v1.ListDeviceSetsRequest.sort:type_name -> common.v1.SortConfig - 51, // 22: device_set.v1.ListDeviceSetsRequest.error_component_types:type_name -> errors.v1.ComponentType - 52, // 23: device_set.v1.ListDeviceSetsRequest.zone_keys:type_name -> common.v1.ZoneKey + 52, // 21: device_set.v1.ListDeviceSetsRequest.sort:type_name -> common.v1.SortConfig + 53, // 22: device_set.v1.ListDeviceSetsRequest.error_component_types:type_name -> errors.v1.ComponentType + 54, // 23: device_set.v1.ListDeviceSetsRequest.zone_keys:type_name -> common.v1.ZoneKey 4, // 24: device_set.v1.ListDeviceSetsResponse.device_sets:type_name -> device_set.v1.DeviceSet - 49, // 25: device_set.v1.AddDevicesToDeviceSetRequest.device_selector:type_name -> common.v1.DeviceSelector - 49, // 26: device_set.v1.RemoveDevicesFromDeviceSetRequest.device_selector:type_name -> common.v1.DeviceSelector + 51, // 25: device_set.v1.AddDevicesToDeviceSetRequest.device_selector:type_name -> common.v1.DeviceSelector + 51, // 26: device_set.v1.RemoveDevicesFromDeviceSetRequest.device_selector:type_name -> common.v1.DeviceSelector 7, // 27: device_set.v1.ListDeviceSetMembersResponse.members:type_name -> device_set.v1.DeviceSetMember 0, // 28: device_set.v1.GetDeviceDeviceSetsRequest.type:type_name -> device_set.v1.DeviceSetType 4, // 29: device_set.v1.GetDeviceDeviceSetsResponse.device_sets:type_name -> device_set.v1.DeviceSet @@ -3815,10 +3969,10 @@ var file_device_set_v1_device_set_proto_depIdxs = []int32{ 38, // 34: device_set.v1.DeviceSetStats.slot_statuses:type_name -> device_set.v1.RackSlotStatus 35, // 35: device_set.v1.GetDeviceSetStatsResponse.stats:type_name -> device_set.v1.DeviceSetStats 3, // 36: device_set.v1.RackSlotStatus.status:type_name -> device_set.v1.SlotDeviceStatus - 53, // 37: device_set.v1.ListRackZoneRefsResponse.zones:type_name -> common.v1.ZoneRef + 55, // 37: device_set.v1.ListRackZoneRefsResponse.zones:type_name -> common.v1.ZoneRef 44, // 38: device_set.v1.ListRackTypesResponse.rack_types:type_name -> device_set.v1.RackType 5, // 39: device_set.v1.SaveRackRequest.rack_info:type_name -> device_set.v1.RackInfo - 49, // 40: device_set.v1.SaveRackRequest.device_selector:type_name -> common.v1.DeviceSelector + 51, // 40: device_set.v1.SaveRackRequest.device_selector:type_name -> common.v1.DeviceSelector 33, // 41: device_set.v1.SaveRackRequest.slot_assignments:type_name -> device_set.v1.RackSlot 4, // 42: device_set.v1.SaveRackResponse.device_set:type_name -> device_set.v1.DeviceSet 10, // 43: device_set.v1.DeviceSetService.CreateDeviceSet:input_type -> device_set.v1.CreateDeviceSetRequest @@ -3838,25 +3992,27 @@ var file_device_set_v1_device_set_proto_depIdxs = []int32{ 41, // 57: device_set.v1.DeviceSetService.ListRackZoneRefs:input_type -> device_set.v1.ListRackZoneRefsRequest 43, // 58: device_set.v1.DeviceSetService.ListRackTypes:input_type -> device_set.v1.ListRackTypesRequest 46, // 59: device_set.v1.DeviceSetService.SaveRack:input_type -> device_set.v1.SaveRackRequest - 11, // 60: device_set.v1.DeviceSetService.CreateDeviceSet:output_type -> device_set.v1.CreateDeviceSetResponse - 13, // 61: device_set.v1.DeviceSetService.GetDeviceSet:output_type -> device_set.v1.GetDeviceSetResponse - 15, // 62: device_set.v1.DeviceSetService.UpdateDeviceSet:output_type -> device_set.v1.UpdateDeviceSetResponse - 17, // 63: device_set.v1.DeviceSetService.DeleteDeviceSet:output_type -> device_set.v1.DeleteDeviceSetResponse - 19, // 64: device_set.v1.DeviceSetService.ListDeviceSets:output_type -> device_set.v1.ListDeviceSetsResponse - 21, // 65: device_set.v1.DeviceSetService.AddDevicesToDeviceSet:output_type -> device_set.v1.AddDevicesToDeviceSetResponse - 23, // 66: device_set.v1.DeviceSetService.RemoveDevicesFromDeviceSet:output_type -> device_set.v1.RemoveDevicesFromDeviceSetResponse - 25, // 67: device_set.v1.DeviceSetService.ListDeviceSetMembers:output_type -> device_set.v1.ListDeviceSetMembersResponse - 27, // 68: device_set.v1.DeviceSetService.GetDeviceDeviceSets:output_type -> device_set.v1.GetDeviceDeviceSetsResponse - 29, // 69: device_set.v1.DeviceSetService.SetRackSlotPosition:output_type -> device_set.v1.SetRackSlotPositionResponse - 31, // 70: device_set.v1.DeviceSetService.ClearRackSlotPosition:output_type -> device_set.v1.ClearRackSlotPositionResponse - 34, // 71: device_set.v1.DeviceSetService.GetRackSlots:output_type -> device_set.v1.GetRackSlotsResponse - 37, // 72: device_set.v1.DeviceSetService.GetDeviceSetStats:output_type -> device_set.v1.GetDeviceSetStatsResponse - 40, // 73: device_set.v1.DeviceSetService.ListRackZones:output_type -> device_set.v1.ListRackZonesResponse - 42, // 74: device_set.v1.DeviceSetService.ListRackZoneRefs:output_type -> device_set.v1.ListRackZoneRefsResponse - 45, // 75: device_set.v1.DeviceSetService.ListRackTypes:output_type -> device_set.v1.ListRackTypesResponse - 47, // 76: device_set.v1.DeviceSetService.SaveRack:output_type -> device_set.v1.SaveRackResponse - 60, // [60:77] is the sub-list for method output_type - 43, // [43:60] is the sub-list for method input_type + 48, // 60: device_set.v1.DeviceSetService.AssignDevicesToRack:input_type -> device_set.v1.AssignDevicesToRackRequest + 11, // 61: device_set.v1.DeviceSetService.CreateDeviceSet:output_type -> device_set.v1.CreateDeviceSetResponse + 13, // 62: device_set.v1.DeviceSetService.GetDeviceSet:output_type -> device_set.v1.GetDeviceSetResponse + 15, // 63: device_set.v1.DeviceSetService.UpdateDeviceSet:output_type -> device_set.v1.UpdateDeviceSetResponse + 17, // 64: device_set.v1.DeviceSetService.DeleteDeviceSet:output_type -> device_set.v1.DeleteDeviceSetResponse + 19, // 65: device_set.v1.DeviceSetService.ListDeviceSets:output_type -> device_set.v1.ListDeviceSetsResponse + 21, // 66: device_set.v1.DeviceSetService.AddDevicesToDeviceSet:output_type -> device_set.v1.AddDevicesToDeviceSetResponse + 23, // 67: device_set.v1.DeviceSetService.RemoveDevicesFromDeviceSet:output_type -> device_set.v1.RemoveDevicesFromDeviceSetResponse + 25, // 68: device_set.v1.DeviceSetService.ListDeviceSetMembers:output_type -> device_set.v1.ListDeviceSetMembersResponse + 27, // 69: device_set.v1.DeviceSetService.GetDeviceDeviceSets:output_type -> device_set.v1.GetDeviceDeviceSetsResponse + 29, // 70: device_set.v1.DeviceSetService.SetRackSlotPosition:output_type -> device_set.v1.SetRackSlotPositionResponse + 31, // 71: device_set.v1.DeviceSetService.ClearRackSlotPosition:output_type -> device_set.v1.ClearRackSlotPositionResponse + 34, // 72: device_set.v1.DeviceSetService.GetRackSlots:output_type -> device_set.v1.GetRackSlotsResponse + 37, // 73: device_set.v1.DeviceSetService.GetDeviceSetStats:output_type -> device_set.v1.GetDeviceSetStatsResponse + 40, // 74: device_set.v1.DeviceSetService.ListRackZones:output_type -> device_set.v1.ListRackZonesResponse + 42, // 75: device_set.v1.DeviceSetService.ListRackZoneRefs:output_type -> device_set.v1.ListRackZoneRefsResponse + 45, // 76: device_set.v1.DeviceSetService.ListRackTypes:output_type -> device_set.v1.ListRackTypesResponse + 47, // 77: device_set.v1.DeviceSetService.SaveRack:output_type -> device_set.v1.SaveRackResponse + 49, // 78: device_set.v1.DeviceSetService.AssignDevicesToRack:output_type -> device_set.v1.AssignDevicesToRackResponse + 61, // [61:79] is the sub-list for method output_type + 43, // [43:61] is the sub-list for method input_type 43, // [43:43] is the sub-list for extension type_name 43, // [43:43] is the sub-list for extension extendee 0, // [0:43] is the sub-list for field type_name @@ -3884,13 +4040,14 @@ func file_device_set_v1_device_set_proto_init() { (*UpdateDeviceSetRequest_GroupInfo)(nil), } file_device_set_v1_device_set_proto_msgTypes[42].OneofWrappers = []any{} + file_device_set_v1_device_set_proto_msgTypes[44].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_device_set_v1_device_set_proto_rawDesc), len(file_device_set_v1_device_set_proto_rawDesc)), NumEnums: 4, - NumMessages: 44, + NumMessages: 46, NumExtensions: 0, NumServices: 1, }, diff --git a/server/generated/grpc/device_set/v1/device_setv1connect/device_set.connect.go b/server/generated/grpc/device_set/v1/device_setv1connect/device_set.connect.go index 40441be54..afb3f3861 100644 --- a/server/generated/grpc/device_set/v1/device_setv1connect/device_set.connect.go +++ b/server/generated/grpc/device_set/v1/device_setv1connect/device_set.connect.go @@ -85,6 +85,9 @@ const ( // DeviceSetServiceSaveRackProcedure is the fully-qualified name of the DeviceSetService's SaveRack // RPC. DeviceSetServiceSaveRackProcedure = "/device_set.v1.DeviceSetService/SaveRack" + // DeviceSetServiceAssignDevicesToRackProcedure is the fully-qualified name of the + // DeviceSetService's AssignDevicesToRack RPC. + DeviceSetServiceAssignDevicesToRackProcedure = "/device_set.v1.DeviceSetService/AssignDevicesToRack" ) // DeviceSetServiceClient is a client for the device_set.v1.DeviceSetService service. @@ -129,6 +132,19 @@ type DeviceSetServiceClient interface { // Atomically creates or updates a rack with its membership and slot assignments. // All operations (metadata, membership, slot positions) are applied in a single transaction. SaveRack(context.Context, *connect.Request[v1.SaveRackRequest]) (*connect.Response[v1.SaveRackResponse], error) + // AssignDevicesToRack atomically moves devices into the target rack + // in one transaction: removes any existing rack membership for each + // device, inserts new membership at target_rack_id, and cascades + // device.site_id when the target rack's site differs from the + // device's current site. target_rack_id unset clears rack + // membership without re-assigning (site/building stay intact). + // + // Closes the orphan window where a client-side + // RemoveDevicesFromDeviceSet + AddDevicesToDeviceSet pair could + // leave devices without rack membership on transport failure between + // the two calls, and the cross-rack rename race where the source + // rack's id resolution drifts between the resolve and the remove. + AssignDevicesToRack(context.Context, *connect.Request[v1.AssignDevicesToRackRequest]) (*connect.Response[v1.AssignDevicesToRackResponse], error) } // NewDeviceSetServiceClient constructs a client for the device_set.v1.DeviceSetService service. By @@ -226,6 +242,11 @@ func NewDeviceSetServiceClient(httpClient connect.HTTPClient, baseURL string, op baseURL+DeviceSetServiceSaveRackProcedure, opts..., ), + assignDevicesToRack: connect.NewClient[v1.AssignDevicesToRackRequest, v1.AssignDevicesToRackResponse]( + httpClient, + baseURL+DeviceSetServiceAssignDevicesToRackProcedure, + opts..., + ), } } @@ -248,6 +269,7 @@ type deviceSetServiceClient struct { listRackZoneRefs *connect.Client[v1.ListRackZoneRefsRequest, v1.ListRackZoneRefsResponse] listRackTypes *connect.Client[v1.ListRackTypesRequest, v1.ListRackTypesResponse] saveRack *connect.Client[v1.SaveRackRequest, v1.SaveRackResponse] + assignDevicesToRack *connect.Client[v1.AssignDevicesToRackRequest, v1.AssignDevicesToRackResponse] } // CreateDeviceSet calls device_set.v1.DeviceSetService.CreateDeviceSet. @@ -335,6 +357,11 @@ func (c *deviceSetServiceClient) SaveRack(ctx context.Context, req *connect.Requ return c.saveRack.CallUnary(ctx, req) } +// AssignDevicesToRack calls device_set.v1.DeviceSetService.AssignDevicesToRack. +func (c *deviceSetServiceClient) AssignDevicesToRack(ctx context.Context, req *connect.Request[v1.AssignDevicesToRackRequest]) (*connect.Response[v1.AssignDevicesToRackResponse], error) { + return c.assignDevicesToRack.CallUnary(ctx, req) +} + // DeviceSetServiceHandler is an implementation of the device_set.v1.DeviceSetService service. type DeviceSetServiceHandler interface { // Creates a new device set @@ -377,6 +404,19 @@ type DeviceSetServiceHandler interface { // Atomically creates or updates a rack with its membership and slot assignments. // All operations (metadata, membership, slot positions) are applied in a single transaction. SaveRack(context.Context, *connect.Request[v1.SaveRackRequest]) (*connect.Response[v1.SaveRackResponse], error) + // AssignDevicesToRack atomically moves devices into the target rack + // in one transaction: removes any existing rack membership for each + // device, inserts new membership at target_rack_id, and cascades + // device.site_id when the target rack's site differs from the + // device's current site. target_rack_id unset clears rack + // membership without re-assigning (site/building stay intact). + // + // Closes the orphan window where a client-side + // RemoveDevicesFromDeviceSet + AddDevicesToDeviceSet pair could + // leave devices without rack membership on transport failure between + // the two calls, and the cross-rack rename race where the source + // rack's id resolution drifts between the resolve and the remove. + AssignDevicesToRack(context.Context, *connect.Request[v1.AssignDevicesToRackRequest]) (*connect.Response[v1.AssignDevicesToRackResponse], error) } // NewDeviceSetServiceHandler builds an HTTP handler from the service implementation. It returns the @@ -470,6 +510,11 @@ func NewDeviceSetServiceHandler(svc DeviceSetServiceHandler, opts ...connect.Han svc.SaveRack, opts..., ) + deviceSetServiceAssignDevicesToRackHandler := connect.NewUnaryHandler( + DeviceSetServiceAssignDevicesToRackProcedure, + svc.AssignDevicesToRack, + opts..., + ) return "/device_set.v1.DeviceSetService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case DeviceSetServiceCreateDeviceSetProcedure: @@ -506,6 +551,8 @@ func NewDeviceSetServiceHandler(svc DeviceSetServiceHandler, opts ...connect.Han deviceSetServiceListRackTypesHandler.ServeHTTP(w, r) case DeviceSetServiceSaveRackProcedure: deviceSetServiceSaveRackHandler.ServeHTTP(w, r) + case DeviceSetServiceAssignDevicesToRackProcedure: + deviceSetServiceAssignDevicesToRackHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -582,3 +629,7 @@ func (UnimplementedDeviceSetServiceHandler) ListRackTypes(context.Context, *conn func (UnimplementedDeviceSetServiceHandler) SaveRack(context.Context, *connect.Request[v1.SaveRackRequest]) (*connect.Response[v1.SaveRackResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("device_set.v1.DeviceSetService.SaveRack is not implemented")) } + +func (UnimplementedDeviceSetServiceHandler) AssignDevicesToRack(context.Context, *connect.Request[v1.AssignDevicesToRackRequest]) (*connect.Response[v1.AssignDevicesToRackResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("device_set.v1.DeviceSetService.AssignDevicesToRack is not implemented")) +} diff --git a/server/generated/sqlc/db.go b/server/generated/sqlc/db.go index 44f07ac72..95ef727ae 100644 --- a/server/generated/sqlc/db.go +++ b/server/generated/sqlc/db.go @@ -42,6 +42,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.assignBuildingToSiteStmt, err = db.PrepareContext(ctx, assignBuildingToSite); err != nil { return nil, fmt.Errorf("error preparing query AssignBuildingToSite: %w", err) } + if q.assignDevicesToSiteStmt, err = db.PrepareContext(ctx, assignDevicesToSite); err != nil { + return nil, fmt.Errorf("error preparing query AssignDevicesToSite: %w", err) + } if q.assignPermissionToRoleStmt, err = db.PrepareContext(ctx, assignPermissionToRole); err != nil { return nil, fmt.Errorf("error preparing query AssignPermissionToRole: %w", err) } @@ -81,9 +84,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.claimMessageForProcessingStmt, err = db.PrepareContext(ctx, claimMessageForProcessing); err != nil { return nil, fmt.Errorf("error preparing query ClaimMessageForProcessing: %w", err) } - if q.clearCurtailmentAutomationActiveEventStmt, err = db.PrepareContext(ctx, clearCurtailmentAutomationActiveEvent); err != nil { - return nil, fmt.Errorf("error preparing query ClearCurtailmentAutomationActiveEvent: %w", err) - } if q.clearRackPlacementForSoftDeleteStmt, err = db.PrepareContext(ctx, clearRackPlacementForSoftDelete); err != nil { return nil, fmt.Errorf("error preparing query ClearRackPlacementForSoftDelete: %w", err) } @@ -114,12 +114,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.countComponentsWithErrorsStmt, err = db.PrepareContext(ctx, countComponentsWithErrors); err != nil { return nil, fmt.Errorf("error preparing query CountComponentsWithErrors: %w", err) } - if q.countCurtailmentAutomationRulesByMQTTSourceStmt, err = db.PrepareContext(ctx, countCurtailmentAutomationRulesByMQTTSource); err != nil { - return nil, fmt.Errorf("error preparing query CountCurtailmentAutomationRulesByMQTTSource: %w", err) - } - if q.countCurtailmentAutomationRulesByResponseProfileStmt, err = db.PrepareContext(ctx, countCurtailmentAutomationRulesByResponseProfile); err != nil { - return nil, fmt.Errorf("error preparing query CountCurtailmentAutomationRulesByResponseProfile: %w", err) - } if q.countDevicesWithErrorsStmt, err = db.PrepareContext(ctx, countDevicesWithErrors); err != nil { return nil, fmt.Errorf("error preparing query CountDevicesWithErrors: %w", err) } @@ -189,15 +183,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.curtailmentEventHasInFlightTargetsStmt, err = db.PrepareContext(ctx, curtailmentEventHasInFlightTargets); err != nil { return nil, fmt.Errorf("error preparing query CurtailmentEventHasInFlightTargets: %w", err) } - if q.deleteCurtailmentAutomationRuleByOrgStmt, err = db.PrepareContext(ctx, deleteCurtailmentAutomationRuleByOrg); err != nil { - return nil, fmt.Errorf("error preparing query DeleteCurtailmentAutomationRuleByOrg: %w", err) - } - if q.deleteCurtailmentResponseProfileByOrgStmt, err = db.PrepareContext(ctx, deleteCurtailmentResponseProfileByOrg); err != nil { - return nil, fmt.Errorf("error preparing query DeleteCurtailmentResponseProfileByOrg: %w", err) - } - if q.deleteCurtailmentResponseProfilesBySiteStmt, err = db.PrepareContext(ctx, deleteCurtailmentResponseProfilesBySite); err != nil { - return nil, fmt.Errorf("error preparing query DeleteCurtailmentResponseProfilesBySite: %w", err) - } if q.deleteDisabledMQTTSourceConfigByOrgStmt, err = db.PrepareContext(ctx, deleteDisabledMQTTSourceConfigByOrg); err != nil { return nil, fmt.Errorf("error preparing query DeleteDisabledMQTTSourceConfigByOrg: %w", err) } @@ -219,9 +204,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.deviceHasActiveCloudPairingStmt, err = db.PrepareContext(ctx, deviceHasActiveCloudPairing); err != nil { return nil, fmt.Errorf("error preparing query DeviceHasActiveCloudPairing: %w", err) } - if q.deviceHasActivePairingStmt, err = db.PrepareContext(ctx, deviceHasActivePairing); err != nil { - return nil, fmt.Errorf("error preparing query DeviceHasActivePairing: %w", err) - } if q.deviceSetBelongsToOrgStmt, err = db.PrepareContext(ctx, deviceSetBelongsToOrg); err != nil { return nil, fmt.Errorf("error preparing query DeviceSetBelongsToOrg: %w", err) } @@ -294,9 +276,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.getBuiltinRoleForOrgStmt, err = db.PrepareContext(ctx, getBuiltinRoleForOrg); err != nil { return nil, fmt.Errorf("error preparing query GetBuiltinRoleForOrg: %w", err) } - if q.getCurtailmentAutomationRuleByOrgStmt, err = db.PrepareContext(ctx, getCurtailmentAutomationRuleByOrg); err != nil { - return nil, fmt.Errorf("error preparing query GetCurtailmentAutomationRuleByOrg: %w", err) - } if q.getCurtailmentEventByExternalReferenceStmt, err = db.PrepareContext(ctx, getCurtailmentEventByExternalReference); err != nil { return nil, fmt.Errorf("error preparing query GetCurtailmentEventByExternalReference: %w", err) } @@ -315,9 +294,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.getCurtailmentReconcilerHeartbeatStmt, err = db.PrepareContext(ctx, getCurtailmentReconcilerHeartbeat); err != nil { return nil, fmt.Errorf("error preparing query GetCurtailmentReconcilerHeartbeat: %w", err) } - if q.getCurtailmentResponseProfileByOrgStmt, err = db.PrepareContext(ctx, getCurtailmentResponseProfileByOrg); err != nil { - return nil, fmt.Errorf("error preparing query GetCurtailmentResponseProfileByOrg: %w", err) - } if q.getCurtailmentTargetRollupByEventStmt, err = db.PrepareContext(ctx, getCurtailmentTargetRollupByEvent); err != nil { return nil, fmt.Errorf("error preparing query GetCurtailmentTargetRollupByEvent: %w", err) } @@ -612,15 +588,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.insertActivityLogStmt, err = db.PrepareContext(ctx, insertActivityLog); err != nil { return nil, fmt.Errorf("error preparing query InsertActivityLog: %w", err) } - if q.insertCurtailmentAutomationRuleStmt, err = db.PrepareContext(ctx, insertCurtailmentAutomationRule); err != nil { - return nil, fmt.Errorf("error preparing query InsertCurtailmentAutomationRule: %w", err) - } if q.insertCurtailmentEventStmt, err = db.PrepareContext(ctx, insertCurtailmentEvent); err != nil { return nil, fmt.Errorf("error preparing query InsertCurtailmentEvent: %w", err) } - if q.insertCurtailmentResponseProfileStmt, err = db.PrepareContext(ctx, insertCurtailmentResponseProfile); err != nil { - return nil, fmt.Errorf("error preparing query InsertCurtailmentResponseProfile: %w", err) - } if q.insertDeviceStmt, err = db.PrepareContext(ctx, insertDevice); err != nil { return nil, fmt.Errorf("error preparing query InsertDevice: %w", err) } @@ -636,12 +606,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.insertMinerStateSnapshotStmt, err = db.PrepareContext(ctx, insertMinerStateSnapshot); err != nil { return nil, fmt.Errorf("error preparing query InsertMinerStateSnapshot: %w", err) } - if q.insertNotificationHistoryStmt, err = db.PrepareContext(ctx, insertNotificationHistory); err != nil { - return nil, fmt.Errorf("error preparing query InsertNotificationHistory: %w", err) - } - if q.insertNotificationMetricSamplesStmt, err = db.PrepareContext(ctx, insertNotificationMetricSamples); err != nil { - return nil, fmt.Errorf("error preparing query InsertNotificationMetricSamples: %w", err) - } if q.isBatchFinishedStmt, err = db.PrepareContext(ctx, isBatchFinished); err != nil { return nil, fmt.Errorf("error preparing query IsBatchFinished: %w", err) } @@ -681,18 +645,12 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.listBuiltinRolesForOrgStmt, err = db.PrepareContext(ctx, listBuiltinRolesForOrg); err != nil { return nil, fmt.Errorf("error preparing query ListBuiltinRolesForOrg: %w", err) } - if q.listCurtailmentAutomationRulesByOrgStmt, err = db.PrepareContext(ctx, listCurtailmentAutomationRulesByOrg); err != nil { - return nil, fmt.Errorf("error preparing query ListCurtailmentAutomationRulesByOrg: %w", err) - } if q.listCurtailmentCandidatesByOrgStmt, err = db.PrepareContext(ctx, listCurtailmentCandidatesByOrg); err != nil { return nil, fmt.Errorf("error preparing query ListCurtailmentCandidatesByOrg: %w", err) } if q.listCurtailmentEventsForOrgStmt, err = db.PrepareContext(ctx, listCurtailmentEventsForOrg); err != nil { return nil, fmt.Errorf("error preparing query ListCurtailmentEventsForOrg: %w", err) } - if q.listCurtailmentResponseProfilesByOrgStmt, err = db.PrepareContext(ctx, listCurtailmentResponseProfilesByOrg); err != nil { - return nil, fmt.Errorf("error preparing query ListCurtailmentResponseProfilesByOrg: %w", err) - } if q.listCurtailmentTargetsByEventStmt, err = db.PrepareContext(ctx, listCurtailmentTargetsByEvent); err != nil { return nil, fmt.Errorf("error preparing query ListCurtailmentTargetsByEvent: %w", err) } @@ -714,9 +672,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.listEffectivePermissionsForUserForUpdateStmt, err = db.PrepareContext(ctx, listEffectivePermissionsForUserForUpdate); err != nil { return nil, fmt.Errorf("error preparing query ListEffectivePermissionsForUserForUpdate: %w", err) } - if q.listEnabledCurtailmentAutomationRulesByMQTTSourceStmt, err = db.PrepareContext(ctx, listEnabledCurtailmentAutomationRulesByMQTTSource); err != nil { - return nil, fmt.Errorf("error preparing query ListEnabledCurtailmentAutomationRulesByMQTTSource: %w", err) - } if q.listEnabledMQTTSourcesStmt, err = db.PrepareContext(ctx, listEnabledMQTTSources); err != nil { return nil, fmt.Errorf("error preparing query ListEnabledMQTTSources: %w", err) } @@ -855,9 +810,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.reapStuckProcessingMessagesStmt, err = db.PrepareContext(ctx, reapStuckProcessingMessages); err != nil { return nil, fmt.Errorf("error preparing query ReapStuckProcessingMessages: %w", err) } - if q.reassignDevicesToSiteStmt, err = db.PrepareContext(ctx, reassignDevicesToSite); err != nil { - return nil, fmt.Errorf("error preparing query ReassignDevicesToSite: %w", err) - } if q.reassignDevicesUnderBuildingStmt, err = db.PrepareContext(ctx, reassignDevicesUnderBuilding); err != nil { return nil, fmt.Errorf("error preparing query ReassignDevicesUnderBuilding: %w", err) } @@ -867,6 +819,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.removeAllDevicesFromDeviceSetStmt, err = db.PrepareContext(ctx, removeAllDevicesFromDeviceSet); err != nil { return nil, fmt.Errorf("error preparing query RemoveAllDevicesFromDeviceSet: %w", err) } + if q.removeDevicesFromAnyRackStmt, err = db.PrepareContext(ctx, removeDevicesFromAnyRack); err != nil { + return nil, fmt.Errorf("error preparing query RemoveDevicesFromAnyRack: %w", err) + } if q.removeDevicesFromDeviceSetStmt, err = db.PrepareContext(ctx, removeDevicesFromDeviceSet); err != nil { return nil, fmt.Errorf("error preparing query RemoveDevicesFromDeviceSet: %w", err) } @@ -900,21 +855,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.revokeSessionStmt, err = db.PrepareContext(ctx, revokeSession); err != nil { return nil, fmt.Errorf("error preparing query RevokeSession: %w", err) } - if q.setCurtailmentAutomationActiveEventStmt, err = db.PrepareContext(ctx, setCurtailmentAutomationActiveEvent); err != nil { - return nil, fmt.Errorf("error preparing query SetCurtailmentAutomationActiveEvent: %w", err) - } - if q.setCurtailmentAutomationExecutionErrorStmt, err = db.PrepareContext(ctx, setCurtailmentAutomationExecutionError); err != nil { - return nil, fmt.Errorf("error preparing query SetCurtailmentAutomationExecutionError: %w", err) - } - if q.setCurtailmentAutomationRestoreStartedStmt, err = db.PrepareContext(ctx, setCurtailmentAutomationRestoreStarted); err != nil { - return nil, fmt.Errorf("error preparing query SetCurtailmentAutomationRestoreStarted: %w", err) - } - if q.setCurtailmentAutomationRuleEnabledStmt, err = db.PrepareContext(ctx, setCurtailmentAutomationRuleEnabled); err != nil { - return nil, fmt.Errorf("error preparing query SetCurtailmentAutomationRuleEnabled: %w", err) - } - if q.setDevicePairingAuthNeededIfNotPairedStmt, err = db.PrepareContext(ctx, setDevicePairingAuthNeededIfNotPaired); err != nil { - return nil, fmt.Errorf("error preparing query SetDevicePairingAuthNeededIfNotPaired: %w", err) - } if q.setFleetNodeEnrollmentStatusStmt, err = db.PrepareContext(ctx, setFleetNodeEnrollmentStatus); err != nil { return nil, fmt.Errorf("error preparing query SetFleetNodeEnrollmentStatus: %w", err) } @@ -1032,18 +972,12 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.updateBuildingStmt, err = db.PrepareContext(ctx, updateBuilding); err != nil { return nil, fmt.Errorf("error preparing query UpdateBuilding: %w", err) } - if q.updateCurtailmentAutomationRuleStmt, err = db.PrepareContext(ctx, updateCurtailmentAutomationRule); err != nil { - return nil, fmt.Errorf("error preparing query UpdateCurtailmentAutomationRule: %w", err) - } if q.updateCurtailmentEventOperatorFieldsStmt, err = db.PrepareContext(ctx, updateCurtailmentEventOperatorFields); err != nil { return nil, fmt.Errorf("error preparing query UpdateCurtailmentEventOperatorFields: %w", err) } if q.updateCurtailmentEventStateStmt, err = db.PrepareContext(ctx, updateCurtailmentEventState); err != nil { return nil, fmt.Errorf("error preparing query UpdateCurtailmentEventState: %w", err) } - if q.updateCurtailmentResponseProfileStmt, err = db.PrepareContext(ctx, updateCurtailmentResponseProfile); err != nil { - return nil, fmt.Errorf("error preparing query UpdateCurtailmentResponseProfile: %w", err) - } if q.updateCurtailmentTargetStateStmt, err = db.PrepareContext(ctx, updateCurtailmentTargetState); err != nil { return nil, fmt.Errorf("error preparing query UpdateCurtailmentTargetState: %w", err) } @@ -1146,9 +1080,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.upsertCommandOnDeviceLogStmt, err = db.PrepareContext(ctx, upsertCommandOnDeviceLog); err != nil { return nil, fmt.Errorf("error preparing query UpsertCommandOnDeviceLog: %w", err) } - if q.upsertCurtailmentAutomationSignalStateStmt, err = db.PrepareContext(ctx, upsertCurtailmentAutomationSignalState); err != nil { - return nil, fmt.Errorf("error preparing query UpsertCurtailmentAutomationSignalState: %w", err) - } if q.upsertCurtailmentReconcilerHeartbeatStmt, err = db.PrepareContext(ctx, upsertCurtailmentReconcilerHeartbeat); err != nil { return nil, fmt.Errorf("error preparing query UpsertCurtailmentReconcilerHeartbeat: %w", err) } @@ -1217,6 +1148,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing assignBuildingToSiteStmt: %w", cerr) } } + if q.assignDevicesToSiteStmt != nil { + if cerr := q.assignDevicesToSiteStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing assignDevicesToSiteStmt: %w", cerr) + } + } if q.assignPermissionToRoleStmt != nil { if cerr := q.assignPermissionToRoleStmt.Close(); cerr != nil { err = fmt.Errorf("error closing assignPermissionToRoleStmt: %w", cerr) @@ -1282,11 +1218,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing claimMessageForProcessingStmt: %w", cerr) } } - if q.clearCurtailmentAutomationActiveEventStmt != nil { - if cerr := q.clearCurtailmentAutomationActiveEventStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing clearCurtailmentAutomationActiveEventStmt: %w", cerr) - } - } if q.clearRackPlacementForSoftDeleteStmt != nil { if cerr := q.clearRackPlacementForSoftDeleteStmt.Close(); cerr != nil { err = fmt.Errorf("error closing clearRackPlacementForSoftDeleteStmt: %w", cerr) @@ -1337,16 +1268,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing countComponentsWithErrorsStmt: %w", cerr) } } - if q.countCurtailmentAutomationRulesByMQTTSourceStmt != nil { - if cerr := q.countCurtailmentAutomationRulesByMQTTSourceStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing countCurtailmentAutomationRulesByMQTTSourceStmt: %w", cerr) - } - } - if q.countCurtailmentAutomationRulesByResponseProfileStmt != nil { - if cerr := q.countCurtailmentAutomationRulesByResponseProfileStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing countCurtailmentAutomationRulesByResponseProfileStmt: %w", cerr) - } - } if q.countDevicesWithErrorsStmt != nil { if cerr := q.countDevicesWithErrorsStmt.Close(); cerr != nil { err = fmt.Errorf("error closing countDevicesWithErrorsStmt: %w", cerr) @@ -1462,21 +1383,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing curtailmentEventHasInFlightTargetsStmt: %w", cerr) } } - if q.deleteCurtailmentAutomationRuleByOrgStmt != nil { - if cerr := q.deleteCurtailmentAutomationRuleByOrgStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing deleteCurtailmentAutomationRuleByOrgStmt: %w", cerr) - } - } - if q.deleteCurtailmentResponseProfileByOrgStmt != nil { - if cerr := q.deleteCurtailmentResponseProfileByOrgStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing deleteCurtailmentResponseProfileByOrgStmt: %w", cerr) - } - } - if q.deleteCurtailmentResponseProfilesBySiteStmt != nil { - if cerr := q.deleteCurtailmentResponseProfilesBySiteStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing deleteCurtailmentResponseProfilesBySiteStmt: %w", cerr) - } - } if q.deleteDisabledMQTTSourceConfigByOrgStmt != nil { if cerr := q.deleteDisabledMQTTSourceConfigByOrgStmt.Close(); cerr != nil { err = fmt.Errorf("error closing deleteDisabledMQTTSourceConfigByOrgStmt: %w", cerr) @@ -1512,11 +1418,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing deviceHasActiveCloudPairingStmt: %w", cerr) } } - if q.deviceHasActivePairingStmt != nil { - if cerr := q.deviceHasActivePairingStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing deviceHasActivePairingStmt: %w", cerr) - } - } if q.deviceSetBelongsToOrgStmt != nil { if cerr := q.deviceSetBelongsToOrgStmt.Close(); cerr != nil { err = fmt.Errorf("error closing deviceSetBelongsToOrgStmt: %w", cerr) @@ -1637,11 +1538,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getBuiltinRoleForOrgStmt: %w", cerr) } } - if q.getCurtailmentAutomationRuleByOrgStmt != nil { - if cerr := q.getCurtailmentAutomationRuleByOrgStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getCurtailmentAutomationRuleByOrgStmt: %w", cerr) - } - } if q.getCurtailmentEventByExternalReferenceStmt != nil { if cerr := q.getCurtailmentEventByExternalReferenceStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getCurtailmentEventByExternalReferenceStmt: %w", cerr) @@ -1672,11 +1568,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getCurtailmentReconcilerHeartbeatStmt: %w", cerr) } } - if q.getCurtailmentResponseProfileByOrgStmt != nil { - if cerr := q.getCurtailmentResponseProfileByOrgStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getCurtailmentResponseProfileByOrgStmt: %w", cerr) - } - } if q.getCurtailmentTargetRollupByEventStmt != nil { if cerr := q.getCurtailmentTargetRollupByEventStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getCurtailmentTargetRollupByEventStmt: %w", cerr) @@ -2167,21 +2058,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing insertActivityLogStmt: %w", cerr) } } - if q.insertCurtailmentAutomationRuleStmt != nil { - if cerr := q.insertCurtailmentAutomationRuleStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing insertCurtailmentAutomationRuleStmt: %w", cerr) - } - } if q.insertCurtailmentEventStmt != nil { if cerr := q.insertCurtailmentEventStmt.Close(); cerr != nil { err = fmt.Errorf("error closing insertCurtailmentEventStmt: %w", cerr) } } - if q.insertCurtailmentResponseProfileStmt != nil { - if cerr := q.insertCurtailmentResponseProfileStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing insertCurtailmentResponseProfileStmt: %w", cerr) - } - } if q.insertDeviceStmt != nil { if cerr := q.insertDeviceStmt.Close(); cerr != nil { err = fmt.Errorf("error closing insertDeviceStmt: %w", cerr) @@ -2207,16 +2088,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing insertMinerStateSnapshotStmt: %w", cerr) } } - if q.insertNotificationHistoryStmt != nil { - if cerr := q.insertNotificationHistoryStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing insertNotificationHistoryStmt: %w", cerr) - } - } - if q.insertNotificationMetricSamplesStmt != nil { - if cerr := q.insertNotificationMetricSamplesStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing insertNotificationMetricSamplesStmt: %w", cerr) - } - } if q.isBatchFinishedStmt != nil { if cerr := q.isBatchFinishedStmt.Close(); cerr != nil { err = fmt.Errorf("error closing isBatchFinishedStmt: %w", cerr) @@ -2282,11 +2153,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing listBuiltinRolesForOrgStmt: %w", cerr) } } - if q.listCurtailmentAutomationRulesByOrgStmt != nil { - if cerr := q.listCurtailmentAutomationRulesByOrgStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing listCurtailmentAutomationRulesByOrgStmt: %w", cerr) - } - } if q.listCurtailmentCandidatesByOrgStmt != nil { if cerr := q.listCurtailmentCandidatesByOrgStmt.Close(); cerr != nil { err = fmt.Errorf("error closing listCurtailmentCandidatesByOrgStmt: %w", cerr) @@ -2297,11 +2163,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing listCurtailmentEventsForOrgStmt: %w", cerr) } } - if q.listCurtailmentResponseProfilesByOrgStmt != nil { - if cerr := q.listCurtailmentResponseProfilesByOrgStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing listCurtailmentResponseProfilesByOrgStmt: %w", cerr) - } - } if q.listCurtailmentTargetsByEventStmt != nil { if cerr := q.listCurtailmentTargetsByEventStmt.Close(); cerr != nil { err = fmt.Errorf("error closing listCurtailmentTargetsByEventStmt: %w", cerr) @@ -2337,11 +2198,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing listEffectivePermissionsForUserForUpdateStmt: %w", cerr) } } - if q.listEnabledCurtailmentAutomationRulesByMQTTSourceStmt != nil { - if cerr := q.listEnabledCurtailmentAutomationRulesByMQTTSourceStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing listEnabledCurtailmentAutomationRulesByMQTTSourceStmt: %w", cerr) - } - } if q.listEnabledMQTTSourcesStmt != nil { if cerr := q.listEnabledMQTTSourcesStmt.Close(); cerr != nil { err = fmt.Errorf("error closing listEnabledMQTTSourcesStmt: %w", cerr) @@ -2572,11 +2428,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing reapStuckProcessingMessagesStmt: %w", cerr) } } - if q.reassignDevicesToSiteStmt != nil { - if cerr := q.reassignDevicesToSiteStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing reassignDevicesToSiteStmt: %w", cerr) - } - } if q.reassignDevicesUnderBuildingStmt != nil { if cerr := q.reassignDevicesUnderBuildingStmt.Close(); cerr != nil { err = fmt.Errorf("error closing reassignDevicesUnderBuildingStmt: %w", cerr) @@ -2592,6 +2443,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing removeAllDevicesFromDeviceSetStmt: %w", cerr) } } + if q.removeDevicesFromAnyRackStmt != nil { + if cerr := q.removeDevicesFromAnyRackStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing removeDevicesFromAnyRackStmt: %w", cerr) + } + } if q.removeDevicesFromDeviceSetStmt != nil { if cerr := q.removeDevicesFromDeviceSetStmt.Close(); cerr != nil { err = fmt.Errorf("error closing removeDevicesFromDeviceSetStmt: %w", cerr) @@ -2647,31 +2503,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing revokeSessionStmt: %w", cerr) } } - if q.setCurtailmentAutomationActiveEventStmt != nil { - if cerr := q.setCurtailmentAutomationActiveEventStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing setCurtailmentAutomationActiveEventStmt: %w", cerr) - } - } - if q.setCurtailmentAutomationExecutionErrorStmt != nil { - if cerr := q.setCurtailmentAutomationExecutionErrorStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing setCurtailmentAutomationExecutionErrorStmt: %w", cerr) - } - } - if q.setCurtailmentAutomationRestoreStartedStmt != nil { - if cerr := q.setCurtailmentAutomationRestoreStartedStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing setCurtailmentAutomationRestoreStartedStmt: %w", cerr) - } - } - if q.setCurtailmentAutomationRuleEnabledStmt != nil { - if cerr := q.setCurtailmentAutomationRuleEnabledStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing setCurtailmentAutomationRuleEnabledStmt: %w", cerr) - } - } - if q.setDevicePairingAuthNeededIfNotPairedStmt != nil { - if cerr := q.setDevicePairingAuthNeededIfNotPairedStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing setDevicePairingAuthNeededIfNotPairedStmt: %w", cerr) - } - } if q.setFleetNodeEnrollmentStatusStmt != nil { if cerr := q.setFleetNodeEnrollmentStatusStmt.Close(); cerr != nil { err = fmt.Errorf("error closing setFleetNodeEnrollmentStatusStmt: %w", cerr) @@ -2867,11 +2698,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing updateBuildingStmt: %w", cerr) } } - if q.updateCurtailmentAutomationRuleStmt != nil { - if cerr := q.updateCurtailmentAutomationRuleStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing updateCurtailmentAutomationRuleStmt: %w", cerr) - } - } if q.updateCurtailmentEventOperatorFieldsStmt != nil { if cerr := q.updateCurtailmentEventOperatorFieldsStmt.Close(); cerr != nil { err = fmt.Errorf("error closing updateCurtailmentEventOperatorFieldsStmt: %w", cerr) @@ -2882,11 +2708,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing updateCurtailmentEventStateStmt: %w", cerr) } } - if q.updateCurtailmentResponseProfileStmt != nil { - if cerr := q.updateCurtailmentResponseProfileStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing updateCurtailmentResponseProfileStmt: %w", cerr) - } - } if q.updateCurtailmentTargetStateStmt != nil { if cerr := q.updateCurtailmentTargetStateStmt.Close(); cerr != nil { err = fmt.Errorf("error closing updateCurtailmentTargetStateStmt: %w", cerr) @@ -3057,11 +2878,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing upsertCommandOnDeviceLogStmt: %w", cerr) } } - if q.upsertCurtailmentAutomationSignalStateStmt != nil { - if cerr := q.upsertCurtailmentAutomationSignalStateStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing upsertCurtailmentAutomationSignalStateStmt: %w", cerr) - } - } if q.upsertCurtailmentReconcilerHeartbeatStmt != nil { if cerr := q.upsertCurtailmentReconcilerHeartbeatStmt.Close(); cerr != nil { err = fmt.Errorf("error closing upsertCurtailmentReconcilerHeartbeatStmt: %w", cerr) @@ -3154,785 +2970,739 @@ func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, ar } type Queries struct { - db DBTX - tx *sql.Tx - acquireReconcileLockStmt *sql.Stmt - addDevicesToDeviceSetStmt *sql.Stmt - adminResetUserPasswordStmt *sql.Stmt - adminTerminateCurtailmentEventStmt *sql.Stmt - allDevicesBelongToOrgStmt *sql.Stmt - assignBuildingToSiteStmt *sql.Stmt - assignPermissionToRoleStmt *sql.Stmt - assignRoleStmt *sql.Stmt - beginCurtailmentRestorationStmt *sql.Stmt - bindEnrollmentToFleetNodeStmt *sql.Stmt - buildingBelongsToOrgStmt *sql.Stmt - buildingsByIDsStmt *sql.Stmt - bulkInsertCurtailmentTargetsStmt *sql.Stmt - bumpCurtailmentTargetRetryStmt *sql.Stmt - cancelEnrollmentForFleetNodeStmt *sql.Stmt - cancelPendingEnrollmentStmt *sql.Stmt - cascadeAddedDeviceSitesStmt *sql.Stmt - cascadeRackDeviceSitesStmt *sql.Stmt - claimMessageForProcessingStmt *sql.Stmt - clearCurtailmentAutomationActiveEventStmt *sql.Stmt - clearRackPlacementForSoftDeleteStmt *sql.Stmt - clearRackSlotPositionStmt *sql.Stmt - clearRolePermissionsStmt *sql.Stmt - closeStaleErrorsStmt *sql.Stmt - confirmEnrollmentStmt *sql.Stmt - consumeFleetNodeAuthChallengeStmt *sql.Stmt - countActiveAssignmentsForRoleStmt *sql.Stmt - countActiveUnpairedDiscoveredDevicesStmt *sql.Stmt - countActivityLogsStmt *sql.Stmt - countComponentsWithErrorsStmt *sql.Stmt - countCurtailmentAutomationRulesByMQTTSourceStmt *sql.Stmt - countCurtailmentAutomationRulesByResponseProfileStmt *sql.Stmt - countDevicesWithErrorsStmt *sql.Stmt - countErrorsStmt *sql.Stmt - countMinersByStateStmt *sql.Stmt - countOrgScopeSuperAdminsExcludingUserStmt *sql.Stmt - createApiKeyStmt *sql.Stmt - createBuildingStmt *sql.Stmt - createCommandBatchLogStmt *sql.Stmt - createCustomRoleStmt *sql.Stmt - createDeviceSetStmt *sql.Stmt - createFleetNodeStmt *sql.Stmt - createFleetNodeApiKeyStmt *sql.Stmt - createOrganizationStmt *sql.Stmt - createPendingEnrollmentStmt *sql.Stmt - createPoolStmt *sql.Stmt - createQueueMessageStmt *sql.Stmt - createRackExtensionStmt *sql.Stmt - createScheduleStmt *sql.Stmt - createScheduleTargetStmt *sql.Stmt - createSessionStmt *sql.Stmt - createSiteStmt *sql.Stmt - createUserStmt *sql.Stmt - createUserOrganizationStmt *sql.Stmt - curtailmentEventHasInFlightTargetsStmt *sql.Stmt - deleteCurtailmentAutomationRuleByOrgStmt *sql.Stmt - deleteCurtailmentResponseProfileByOrgStmt *sql.Stmt - deleteCurtailmentResponseProfilesBySiteStmt *sql.Stmt - deleteDisabledMQTTSourceConfigByOrgStmt *sql.Stmt - deleteExpiredSessionsStmt *sql.Stmt - deleteOrganizationStmt *sql.Stmt - deletePairingsForFleetNodeStmt *sql.Stmt - deletePoolStmt *sql.Stmt - deleteScheduleTargetsStmt *sql.Stmt - deviceHasActiveCloudPairingStmt *sql.Stmt - deviceHasActivePairingStmt *sql.Stmt - deviceSetBelongsToOrgStmt *sql.Stmt - ensureCurtailmentOrgConfigStmt *sql.Stmt - findDeviceSiteConflictsStmt *sql.Stmt - getActiveCurtailmentEventStmt *sql.Stmt - getActiveSchedulesStmt *sql.Stmt - getActiveUnpairedDiscoveredDevicesStmt *sql.Stmt - getAddedDeviceSiteConflictsStmt *sql.Stmt - getAllDeviceInfoForCapabilityCheckStmt *sql.Stmt - getAllDeviceMetricsDailyAggregatesStmt *sql.Stmt - getAllDeviceMetricsHourlyAggregatesStmt *sql.Stmt - getAllDeviceMetricsTimeSeriesStmt *sql.Stmt - getAllDeviceStatusDailyAggregatesStmt *sql.Stmt - getAllDeviceStatusHourlyAggregatesStmt *sql.Stmt - getAllPairedDeviceIdentifiersStmt *sql.Stmt - getApiKeyByHashStmt *sql.Stmt - getAssignmentByIDStmt *sql.Stmt - getAvailableFirmwareVersionsStmt *sql.Stmt - getAvailableModelsStmt *sql.Stmt - getBatchHeaderForOrgStmt *sql.Stmt - getBatchLogStmt *sql.Stmt - getBatchStatusAndDeviceCountsStmt *sql.Stmt - getBuildingStmt *sql.Stmt - getBuildingSiteStmt *sql.Stmt - getBuiltinRoleForOrgStmt *sql.Stmt - getCurtailmentAutomationRuleByOrgStmt *sql.Stmt - getCurtailmentEventByExternalReferenceStmt *sql.Stmt - getCurtailmentEventByIdempotencyKeyStmt *sql.Stmt - getCurtailmentEventByUUIDStmt *sql.Stmt - getCurtailmentEventDetailByUUIDStmt *sql.Stmt - getCurtailmentOrgConfigStmt *sql.Stmt - getCurtailmentReconcilerHeartbeatStmt *sql.Stmt - getCurtailmentResponseProfileByOrgStmt *sql.Stmt - getCurtailmentTargetRollupByEventStmt *sql.Stmt - getDeviceByDeviceIdentifierStmt *sql.Stmt - getDeviceByIDStmt *sql.Stmt - getDeviceDeviceSetsStmt *sql.Stmt - getDeviceDeviceSetsByTypeStmt *sql.Stmt - getDeviceIDByDeviceIdentifierStmt *sql.Stmt - getDeviceIDByIdentifierStmt *sql.Stmt - getDeviceIDsByDeviceIdentifiersStmt *sql.Stmt - getDeviceIDsWithIdentifiersStmt *sql.Stmt - getDeviceIdentifierByIDStmt *sql.Stmt - getDeviceIdentifiersByDeviceSetIDStmt *sql.Stmt - getDeviceInfoForCapabilityCheckStmt *sql.Stmt - getDeviceMetricsDailyAggregatesStmt *sql.Stmt - getDeviceMetricsHourlyAggregatesStmt *sql.Stmt - getDeviceMetricsTimeSeriesStmt *sql.Stmt - getDevicePairingStatusByDeviceDatabaseIDStmt *sql.Stmt - getDevicePropertiesForRenameStmt *sql.Stmt - getDevicePropertiesForRenameWithoutTelemetryStmt *sql.Stmt - getDeviceSetStmt *sql.Stmt - getDeviceSetTypeStmt *sql.Stmt - getDeviceSetTypesBatchStmt *sql.Stmt - getDeviceSiteIDsByMembershipStmt *sql.Stmt - getDeviceStatusStmt *sql.Stmt - getDeviceStatusByDeviceIdentifierStmt *sql.Stmt - getDeviceStatusDailyAggregatesStmt *sql.Stmt - getDeviceStatusForDeviceIdentifiersStmt *sql.Stmt - getDeviceStatusHourlyAggregatesStmt *sql.Stmt - getDeviceWithCredentialsAndIPByDeviceIdentifierStmt *sql.Stmt - getDeviceWithCredentialsAndIPByIDStmt *sql.Stmt - getDiscoveredDeviceByDeviceIdentifierStmt *sql.Stmt - getDiscoveredDeviceByIDStmt *sql.Stmt - getDiscoveredDeviceByIPAndPortStmt *sql.Stmt - getDistinctActivityUsersStmt *sql.Stmt - getDistinctEventTypesStmt *sql.Stmt - getDistinctScopeTypesStmt *sql.Stmt - getErrorByErrorIDStmt *sql.Stmt - getErrorByIDStmt *sql.Stmt - getFilteredDeviceIdentifiersStmt *sql.Stmt - getFilteredDeviceIdsStmt *sql.Stmt - getFleetNodeByIDStmt *sql.Stmt - getFleetNodeByIDUnscopedStmt *sql.Stmt - getFleetNodeSessionByTokenHashStmt *sql.Stmt - getGroupLabelsForDevicesStmt *sql.Stmt - getKnownSubnetsStmt *sql.Stmt - getLatestAllDeviceMetricsStmt *sql.Stmt - getLatestDeviceMetricsStmt *sql.Stmt - getMQTTSourceConfigByOrgStmt *sql.Stmt - getMQTTSourceStateByIDStmt *sql.Stmt - getMaxPriorityStmt *sql.Stmt - getMessagesToProcessStmt *sql.Stmt - getMinerCredentialsByDeviceIDStmt *sql.Stmt - getMinerModelGroupsStmt *sql.Stmt - getMinerStateCountsByDeviceIDsStmt *sql.Stmt - getMinerStateSnapshotsStmt *sql.Stmt - getOfflineDevicesStmt *sql.Stmt - getOpenErrorByDedupKeyStmt *sql.Stmt - getOrgScopeAssignmentForUserStmt *sql.Stmt - getOrganizationByIDStmt *sql.Stmt - getOrganizationByNameStmt *sql.Stmt - getOrganizationByOrgIDStmt *sql.Stmt - getOrganizationPrivateKeyStmt *sql.Stmt - getOrganizationsForUserStmt *sql.Stmt - getPairedDeviceByMACAddressStmt *sql.Stmt - getPairedDeviceBySerialNumberStmt *sql.Stmt - getPairedDevicesByMACAddressesStmt *sql.Stmt - getPairedDevicesIdsStmt *sql.Stmt - getPendingEnrollmentByCodeHashStmt *sql.Stmt - getPendingEnrollmentByFleetNodeStmt *sql.Stmt - getPermissionByKeyStmt *sql.Stmt - getPermissionsByKeysStmt *sql.Stmt - getPoolStmt *sql.Stmt - getRackDetailsForDevicesStmt *sql.Stmt - getRackInfoStmt *sql.Stmt - getRackInfoBatchStmt *sql.Stmt - getRackSlotsStmt *sql.Stmt - getRoleByIDStmt *sql.Stmt - getRoleByIDForUpdateStmt *sql.Stmt - getRunningPowerTargetScheduleOverlapsStmt *sql.Stmt - getScheduleStmt *sql.Stmt - getScheduleByIDForProcessorStmt *sql.Stmt - getScheduleForUpdateStmt *sql.Stmt - getScheduleTargetsStmt *sql.Stmt - getScheduleTargetsByScheduleIDsStmt *sql.Stmt - getSessionByIDStmt *sql.Stmt - getSiteStmt *sql.Stmt - getTotalDevicesPendingAuthStmt *sql.Stmt - getTotalMinerStateSnapshotsStmt *sql.Stmt - getTotalPairedDevicesStmt *sql.Stmt - getTotalPoolsStmt *sql.Stmt - getUserByExternalIdStmt *sql.Stmt - getUserByIdStmt *sql.Stmt - getUserByIdForUpdateStmt *sql.Stmt - getUserByUsernameStmt *sql.Stmt - getUserRoleInOrganizationStmt *sql.Stmt - getUserRoleNameStmt *sql.Stmt - getUsersForOrganizationStmt *sql.Stmt - hasUserStmt *sql.Stmt - insertActivityLogStmt *sql.Stmt - insertCurtailmentAutomationRuleStmt *sql.Stmt - insertCurtailmentEventStmt *sql.Stmt - insertCurtailmentResponseProfileStmt *sql.Stmt - insertDeviceStmt *sql.Stmt - insertDeviceMetricsStmt *sql.Stmt - insertErrorStmt *sql.Stmt - insertMQTTSourceConfigStmt *sql.Stmt - insertMinerStateSnapshotStmt *sql.Stmt - insertNotificationHistoryStmt *sql.Stmt - insertNotificationMetricSamplesStmt *sql.Stmt - isBatchFinishedStmt *sql.Stmt - isBatchProcessingStmt *sql.Stmt - listActiveCurtailedDevicesByOrgStmt *sql.Stmt - listActiveCurtailmentEventsStmt *sql.Stmt - listActiveOrganizationIDsStmt *sql.Stmt - listActivityLogsStmt *sql.Stmt - listApiKeysByOrganizationStmt *sql.Stmt - listAssignmentsForRoleStmt *sql.Stmt - listAssignmentsForUserStmt *sql.Stmt - listBatchDeviceResultsStmt *sql.Stmt - listBuildingRacksStmt *sql.Stmt - listBuildingsByOrgStmt *sql.Stmt - listBuiltinRolesForOrgStmt *sql.Stmt - listCurtailmentAutomationRulesByOrgStmt *sql.Stmt - listCurtailmentCandidatesByOrgStmt *sql.Stmt - listCurtailmentEventsForOrgStmt *sql.Stmt - listCurtailmentResponseProfilesByOrgStmt *sql.Stmt - listCurtailmentTargetsByEventStmt *sql.Stmt - listCurtailmentTargetsByEventPageStmt *sql.Stmt - listCustomRolesForOrgStmt *sql.Stmt - listDeviceSetMembersPaginatedStmt *sql.Stmt - listDeviceSetMembersPaginatedAfterStmt *sql.Stmt - listEffectivePermissionsForUserStmt *sql.Stmt - listEffectivePermissionsForUserForUpdateStmt *sql.Stmt - listEnabledCurtailmentAutomationRulesByMQTTSourceStmt *sql.Stmt - listEnabledMQTTSourcesStmt *sql.Stmt - listExistingDeviceIdentifiersStmt *sql.Stmt - listFleetNodeDevicesStmt *sql.Stmt - listFleetNodeDiscoveredDevicesStmt *sql.Stmt - listFleetNodesForOrganizationStmt *sql.Stmt - listMQTTSourceConfigsByOrgStmt *sql.Stmt - listMQTTSourceStatesByOrgStmt *sql.Stmt - listMinerStateSnapshotsStmt *sql.Stmt - listNonTerminalCurtailmentEventsStmt *sql.Stmt - listOrganizationsStmt *sql.Stmt - listPermissionsStmt *sql.Stmt - listPoolsStmt *sql.Stmt - listRackTypesStmt *sql.Stmt - listRackZoneRefsStmt *sql.Stmt - listRackZonesStmt *sql.Stmt - listRacksOutsideBuildingBoundsStmt *sql.Stmt - listRecentlyResolvedCurtailedDevicesByOrgStmt *sql.Stmt - listRolePermissionKeysStmt *sql.Stmt - listRolesStmt *sql.Stmt - listRolesWithDetailsForOrgStmt *sql.Stmt - listScheduleIDStatusesStmt *sql.Stmt - listSchedulesStmt *sql.Stmt - listSiteNetworkConfigsForOverlapStmt *sql.Stmt - listSitesStmt *sql.Stmt - listUsersForOrganizationStmt *sql.Stmt - lockAndCountOrgScopeSuperAdminsStmt *sql.Stmt - lockBuildingForWriteStmt *sql.Stmt - lockBuildingsBySiteForWriteStmt *sql.Stmt - lockDevicesForReassignStmt *sql.Stmt - lockFleetNodeByIDStmt *sql.Stmt - lockRackPlacementForWriteStmt *sql.Stmt - lockSchedulePriorityStmt *sql.Stmt - lockSiteForWriteStmt *sql.Stmt - markCommandBatchFinishedStmt *sql.Stmt - markCommandBatchFinishedWithStartedAtStmt *sql.Stmt - markCommandBatchProcessingStmt *sql.Stmt - negateSchedulePrioritiesStmt *sql.Stmt - pairDeviceToFleetNodeStmt *sql.Stmt - passwordUpdatedAtStmt *sql.Stmt - pauseActiveScheduleStmt *sql.Stmt - prunePermissionsOutsideKeysStmt *sql.Stmt - queryComponentKeysWithErrorsStmt *sql.Stmt - queryDeviceIDsWithErrorsStmt *sql.Stmt - queryErrorsStmt *sql.Stmt - reapStuckFirmwareUpdateMessagesStmt *sql.Stmt - reapStuckProcessingMessagesStmt *sql.Stmt - reassignDevicesToSiteStmt *sql.Stmt - reassignDevicesUnderBuildingStmt *sql.Stmt - reassignRacksUnderBuildingStmt *sql.Stmt - removeAllDevicesFromDeviceSetStmt *sql.Stmt - removeDevicesFromDeviceSetStmt *sql.Stmt - resetCurtailmentTargetsForRecurtailStmt *sql.Stmt - resetCurtailmentTargetsForRestoreStmt *sql.Stmt - resumeCurtailmentFromRestoringStmt *sql.Stmt - resumePausedScheduleStmt *sql.Stmt - revertScheduleToActiveStmt *sql.Stmt - revokeAllSessionsByUserIDStmt *sql.Stmt - revokeApiKeyStmt *sql.Stmt - revokeApiKeysByFleetNodeIDStmt *sql.Stmt - revokePermissionFromRoleStmt *sql.Stmt - revokeSessionStmt *sql.Stmt - setCurtailmentAutomationActiveEventStmt *sql.Stmt - setCurtailmentAutomationExecutionErrorStmt *sql.Stmt - setCurtailmentAutomationRestoreStartedStmt *sql.Stmt - setCurtailmentAutomationRuleEnabledStmt *sql.Stmt - setDevicePairingAuthNeededIfNotPairedStmt *sql.Stmt - setFleetNodeEnrollmentStatusStmt *sql.Stmt - setMQTTSourceConfigEnabledStmt *sql.Stmt - setRackBuildingPositionStmt *sql.Stmt - setRackSlotPositionStmt *sql.Stmt - setSchedulePrioritiesStmt *sql.Stmt - setScheduleRunningStmt *sql.Stmt - siteBelongsToOrgStmt *sql.Stmt - softDeleteBuildingStmt *sql.Stmt - softDeleteBuildingsBySiteStmt *sql.Stmt - softDeleteCustomRoleStmt *sql.Stmt - softDeleteDeviceSetStmt *sql.Stmt - softDeleteDevicesStmt *sql.Stmt - softDeleteDiscoveredDeviceByIdentifierStmt *sql.Stmt - softDeleteDiscoveredDevicesForDeletedDevicesStmt *sql.Stmt - softDeleteFleetNodeStmt *sql.Stmt - softDeleteFleetNodesForExpiredEnrollmentsStmt *sql.Stmt - softDeleteOrganizationStmt *sql.Stmt - softDeletePoolStmt *sql.Stmt - softDeleteRoleStmt *sql.Stmt - softDeleteScheduleStmt *sql.Stmt - softDeleteSiteStmt *sql.Stmt - softDeleteUserStmt *sql.Stmt - softDeleteUserFromOrganizationStmt *sql.Stmt - sweepCurtailmentTargetsToRestoreFailedStmt *sql.Stmt - sweepExpiredEnrollmentsStmt *sql.Stmt - sweepExpiredFleetNodeAuthChallengesStmt *sql.Stmt - sweepExpiredFleetNodeSessionsStmt *sql.Stmt - transferDiscoveredDeviceAttributionStmt *sql.Stmt - unassignDeviceSitesByRackStmt *sql.Stmt - unassignDevicesFromSiteStmt *sql.Stmt - unassignRacksFromBuildingStmt *sql.Stmt - unassignRacksFromBuildingsBySiteStmt *sql.Stmt - unassignRacksFromSiteStmt *sql.Stmt - unassignRoleStmt *sql.Stmt - undeleteOrganizationStmt *sql.Stmt - undeleteRoleStmt *sql.Stmt - unpairDeviceStmt *sql.Stmt - updateApiKeyLastUsedStmt *sql.Stmt - updateBuildingStmt *sql.Stmt - updateCurtailmentAutomationRuleStmt *sql.Stmt - updateCurtailmentEventOperatorFieldsStmt *sql.Stmt - updateCurtailmentEventStateStmt *sql.Stmt - updateCurtailmentResponseProfileStmt *sql.Stmt - updateCurtailmentTargetStateStmt *sql.Stmt - updateCustomRoleNameStmt *sql.Stmt - updateDeviceIPAssignmentStmt *sql.Stmt - updateDeviceInfoStmt *sql.Stmt - updateDevicePairingStatusByIdentifierStmt *sql.Stmt - updateDeviceSetDescriptionStmt *sql.Stmt - updateDeviceSetLabelStmt *sql.Stmt - updateDeviceSetLabelAndDescriptionStmt *sql.Stmt - updateDeviceWorkerNameStmt *sql.Stmt - updateDeviceWorkerNamePoolSyncStatusByIDStmt *sql.Stmt - updateDiscoveredDeviceFirmwareVersionStmt *sql.Stmt - updateFleetNodeLastSeenAtStmt *sql.Stmt - updateLastLoginStmt *sql.Stmt - updateMQTTSourceConfigStmt *sql.Stmt - updateMessageAfterFailureStmt *sql.Stmt - updateMessagePermanentlyFailedStmt *sql.Stmt - updateMessageStatusStmt *sql.Stmt - updateMinerPasswordStmt *sql.Stmt - updateOpenErrorStmt *sql.Stmt - updateOrganizationStmt *sql.Stmt - updatePoolStmt *sql.Stmt - updateRackInfoStmt *sql.Stmt - updateRackPlacementStmt *sql.Stmt - updateRoleStmt *sql.Stmt - updateScheduleStmt *sql.Stmt - updateScheduleAfterRunStmt *sql.Stmt - updateSessionActivityStmt *sql.Stmt - updateSiteStmt *sql.Stmt - updateUserPasswordStmt *sql.Stmt - updateUserPasswordAndFlagStmt *sql.Stmt - updateUserRoleStmt *sql.Stmt - updateUserUsernameStmt *sql.Stmt - upsertBuiltinRoleForOrgStmt *sql.Stmt - upsertCommandOnDeviceLogStmt *sql.Stmt - upsertCurtailmentAutomationSignalStateStmt *sql.Stmt - upsertCurtailmentReconcilerHeartbeatStmt *sql.Stmt - upsertCustomRoleForOrgStmt *sql.Stmt - upsertDevicePairingStmt *sql.Stmt - upsertDeviceStatusStmt *sql.Stmt - upsertDiscoveredDeviceStmt *sql.Stmt - upsertDiscoveredDeviceFromFleetNodeStmt *sql.Stmt - upsertFleetNodeAuthChallengeStmt *sql.Stmt - upsertFleetNodeSessionStmt *sql.Stmt - upsertMQTTSourceStateStmt *sql.Stmt - upsertMinerCredentialsStmt *sql.Stmt - upsertPermissionStmt *sql.Stmt + db DBTX + tx *sql.Tx + acquireReconcileLockStmt *sql.Stmt + addDevicesToDeviceSetStmt *sql.Stmt + adminResetUserPasswordStmt *sql.Stmt + adminTerminateCurtailmentEventStmt *sql.Stmt + allDevicesBelongToOrgStmt *sql.Stmt + assignBuildingToSiteStmt *sql.Stmt + assignDevicesToSiteStmt *sql.Stmt + assignPermissionToRoleStmt *sql.Stmt + assignRoleStmt *sql.Stmt + beginCurtailmentRestorationStmt *sql.Stmt + bindEnrollmentToFleetNodeStmt *sql.Stmt + buildingBelongsToOrgStmt *sql.Stmt + buildingsByIDsStmt *sql.Stmt + bulkInsertCurtailmentTargetsStmt *sql.Stmt + bumpCurtailmentTargetRetryStmt *sql.Stmt + cancelEnrollmentForFleetNodeStmt *sql.Stmt + cancelPendingEnrollmentStmt *sql.Stmt + cascadeAddedDeviceSitesStmt *sql.Stmt + cascadeRackDeviceSitesStmt *sql.Stmt + claimMessageForProcessingStmt *sql.Stmt + clearRackPlacementForSoftDeleteStmt *sql.Stmt + clearRackSlotPositionStmt *sql.Stmt + clearRolePermissionsStmt *sql.Stmt + closeStaleErrorsStmt *sql.Stmt + confirmEnrollmentStmt *sql.Stmt + consumeFleetNodeAuthChallengeStmt *sql.Stmt + countActiveAssignmentsForRoleStmt *sql.Stmt + countActiveUnpairedDiscoveredDevicesStmt *sql.Stmt + countActivityLogsStmt *sql.Stmt + countComponentsWithErrorsStmt *sql.Stmt + countDevicesWithErrorsStmt *sql.Stmt + countErrorsStmt *sql.Stmt + countMinersByStateStmt *sql.Stmt + countOrgScopeSuperAdminsExcludingUserStmt *sql.Stmt + createApiKeyStmt *sql.Stmt + createBuildingStmt *sql.Stmt + createCommandBatchLogStmt *sql.Stmt + createCustomRoleStmt *sql.Stmt + createDeviceSetStmt *sql.Stmt + createFleetNodeStmt *sql.Stmt + createFleetNodeApiKeyStmt *sql.Stmt + createOrganizationStmt *sql.Stmt + createPendingEnrollmentStmt *sql.Stmt + createPoolStmt *sql.Stmt + createQueueMessageStmt *sql.Stmt + createRackExtensionStmt *sql.Stmt + createScheduleStmt *sql.Stmt + createScheduleTargetStmt *sql.Stmt + createSessionStmt *sql.Stmt + createSiteStmt *sql.Stmt + createUserStmt *sql.Stmt + createUserOrganizationStmt *sql.Stmt + curtailmentEventHasInFlightTargetsStmt *sql.Stmt + deleteDisabledMQTTSourceConfigByOrgStmt *sql.Stmt + deleteExpiredSessionsStmt *sql.Stmt + deleteOrganizationStmt *sql.Stmt + deletePairingsForFleetNodeStmt *sql.Stmt + deletePoolStmt *sql.Stmt + deleteScheduleTargetsStmt *sql.Stmt + deviceHasActiveCloudPairingStmt *sql.Stmt + deviceSetBelongsToOrgStmt *sql.Stmt + ensureCurtailmentOrgConfigStmt *sql.Stmt + findDeviceSiteConflictsStmt *sql.Stmt + getActiveCurtailmentEventStmt *sql.Stmt + getActiveSchedulesStmt *sql.Stmt + getActiveUnpairedDiscoveredDevicesStmt *sql.Stmt + getAddedDeviceSiteConflictsStmt *sql.Stmt + getAllDeviceInfoForCapabilityCheckStmt *sql.Stmt + getAllDeviceMetricsDailyAggregatesStmt *sql.Stmt + getAllDeviceMetricsHourlyAggregatesStmt *sql.Stmt + getAllDeviceMetricsTimeSeriesStmt *sql.Stmt + getAllDeviceStatusDailyAggregatesStmt *sql.Stmt + getAllDeviceStatusHourlyAggregatesStmt *sql.Stmt + getAllPairedDeviceIdentifiersStmt *sql.Stmt + getApiKeyByHashStmt *sql.Stmt + getAssignmentByIDStmt *sql.Stmt + getAvailableFirmwareVersionsStmt *sql.Stmt + getAvailableModelsStmt *sql.Stmt + getBatchHeaderForOrgStmt *sql.Stmt + getBatchLogStmt *sql.Stmt + getBatchStatusAndDeviceCountsStmt *sql.Stmt + getBuildingStmt *sql.Stmt + getBuildingSiteStmt *sql.Stmt + getBuiltinRoleForOrgStmt *sql.Stmt + getCurtailmentEventByExternalReferenceStmt *sql.Stmt + getCurtailmentEventByIdempotencyKeyStmt *sql.Stmt + getCurtailmentEventByUUIDStmt *sql.Stmt + getCurtailmentEventDetailByUUIDStmt *sql.Stmt + getCurtailmentOrgConfigStmt *sql.Stmt + getCurtailmentReconcilerHeartbeatStmt *sql.Stmt + getCurtailmentTargetRollupByEventStmt *sql.Stmt + getDeviceByDeviceIdentifierStmt *sql.Stmt + getDeviceByIDStmt *sql.Stmt + getDeviceDeviceSetsStmt *sql.Stmt + getDeviceDeviceSetsByTypeStmt *sql.Stmt + getDeviceIDByDeviceIdentifierStmt *sql.Stmt + getDeviceIDByIdentifierStmt *sql.Stmt + getDeviceIDsByDeviceIdentifiersStmt *sql.Stmt + getDeviceIDsWithIdentifiersStmt *sql.Stmt + getDeviceIdentifierByIDStmt *sql.Stmt + getDeviceIdentifiersByDeviceSetIDStmt *sql.Stmt + getDeviceInfoForCapabilityCheckStmt *sql.Stmt + getDeviceMetricsDailyAggregatesStmt *sql.Stmt + getDeviceMetricsHourlyAggregatesStmt *sql.Stmt + getDeviceMetricsTimeSeriesStmt *sql.Stmt + getDevicePairingStatusByDeviceDatabaseIDStmt *sql.Stmt + getDevicePropertiesForRenameStmt *sql.Stmt + getDevicePropertiesForRenameWithoutTelemetryStmt *sql.Stmt + getDeviceSetStmt *sql.Stmt + getDeviceSetTypeStmt *sql.Stmt + getDeviceSetTypesBatchStmt *sql.Stmt + getDeviceSiteIDsByMembershipStmt *sql.Stmt + getDeviceStatusStmt *sql.Stmt + getDeviceStatusByDeviceIdentifierStmt *sql.Stmt + getDeviceStatusDailyAggregatesStmt *sql.Stmt + getDeviceStatusForDeviceIdentifiersStmt *sql.Stmt + getDeviceStatusHourlyAggregatesStmt *sql.Stmt + getDeviceWithCredentialsAndIPByDeviceIdentifierStmt *sql.Stmt + getDeviceWithCredentialsAndIPByIDStmt *sql.Stmt + getDiscoveredDeviceByDeviceIdentifierStmt *sql.Stmt + getDiscoveredDeviceByIDStmt *sql.Stmt + getDiscoveredDeviceByIPAndPortStmt *sql.Stmt + getDistinctActivityUsersStmt *sql.Stmt + getDistinctEventTypesStmt *sql.Stmt + getDistinctScopeTypesStmt *sql.Stmt + getErrorByErrorIDStmt *sql.Stmt + getErrorByIDStmt *sql.Stmt + getFilteredDeviceIdentifiersStmt *sql.Stmt + getFilteredDeviceIdsStmt *sql.Stmt + getFleetNodeByIDStmt *sql.Stmt + getFleetNodeByIDUnscopedStmt *sql.Stmt + getFleetNodeSessionByTokenHashStmt *sql.Stmt + getGroupLabelsForDevicesStmt *sql.Stmt + getKnownSubnetsStmt *sql.Stmt + getLatestAllDeviceMetricsStmt *sql.Stmt + getLatestDeviceMetricsStmt *sql.Stmt + getMQTTSourceConfigByOrgStmt *sql.Stmt + getMQTTSourceStateByIDStmt *sql.Stmt + getMaxPriorityStmt *sql.Stmt + getMessagesToProcessStmt *sql.Stmt + getMinerCredentialsByDeviceIDStmt *sql.Stmt + getMinerModelGroupsStmt *sql.Stmt + getMinerStateCountsByDeviceIDsStmt *sql.Stmt + getMinerStateSnapshotsStmt *sql.Stmt + getOfflineDevicesStmt *sql.Stmt + getOpenErrorByDedupKeyStmt *sql.Stmt + getOrgScopeAssignmentForUserStmt *sql.Stmt + getOrganizationByIDStmt *sql.Stmt + getOrganizationByNameStmt *sql.Stmt + getOrganizationByOrgIDStmt *sql.Stmt + getOrganizationPrivateKeyStmt *sql.Stmt + getOrganizationsForUserStmt *sql.Stmt + getPairedDeviceByMACAddressStmt *sql.Stmt + getPairedDeviceBySerialNumberStmt *sql.Stmt + getPairedDevicesByMACAddressesStmt *sql.Stmt + getPairedDevicesIdsStmt *sql.Stmt + getPendingEnrollmentByCodeHashStmt *sql.Stmt + getPendingEnrollmentByFleetNodeStmt *sql.Stmt + getPermissionByKeyStmt *sql.Stmt + getPermissionsByKeysStmt *sql.Stmt + getPoolStmt *sql.Stmt + getRackDetailsForDevicesStmt *sql.Stmt + getRackInfoStmt *sql.Stmt + getRackInfoBatchStmt *sql.Stmt + getRackSlotsStmt *sql.Stmt + getRoleByIDStmt *sql.Stmt + getRoleByIDForUpdateStmt *sql.Stmt + getRunningPowerTargetScheduleOverlapsStmt *sql.Stmt + getScheduleStmt *sql.Stmt + getScheduleByIDForProcessorStmt *sql.Stmt + getScheduleForUpdateStmt *sql.Stmt + getScheduleTargetsStmt *sql.Stmt + getScheduleTargetsByScheduleIDsStmt *sql.Stmt + getSessionByIDStmt *sql.Stmt + getSiteStmt *sql.Stmt + getTotalDevicesPendingAuthStmt *sql.Stmt + getTotalMinerStateSnapshotsStmt *sql.Stmt + getTotalPairedDevicesStmt *sql.Stmt + getTotalPoolsStmt *sql.Stmt + getUserByExternalIdStmt *sql.Stmt + getUserByIdStmt *sql.Stmt + getUserByIdForUpdateStmt *sql.Stmt + getUserByUsernameStmt *sql.Stmt + getUserRoleInOrganizationStmt *sql.Stmt + getUserRoleNameStmt *sql.Stmt + getUsersForOrganizationStmt *sql.Stmt + hasUserStmt *sql.Stmt + insertActivityLogStmt *sql.Stmt + insertCurtailmentEventStmt *sql.Stmt + insertDeviceStmt *sql.Stmt + insertDeviceMetricsStmt *sql.Stmt + insertErrorStmt *sql.Stmt + insertMQTTSourceConfigStmt *sql.Stmt + insertMinerStateSnapshotStmt *sql.Stmt + isBatchFinishedStmt *sql.Stmt + isBatchProcessingStmt *sql.Stmt + listActiveCurtailedDevicesByOrgStmt *sql.Stmt + listActiveCurtailmentEventsStmt *sql.Stmt + listActiveOrganizationIDsStmt *sql.Stmt + listActivityLogsStmt *sql.Stmt + listApiKeysByOrganizationStmt *sql.Stmt + listAssignmentsForRoleStmt *sql.Stmt + listAssignmentsForUserStmt *sql.Stmt + listBatchDeviceResultsStmt *sql.Stmt + listBuildingRacksStmt *sql.Stmt + listBuildingsByOrgStmt *sql.Stmt + listBuiltinRolesForOrgStmt *sql.Stmt + listCurtailmentCandidatesByOrgStmt *sql.Stmt + listCurtailmentEventsForOrgStmt *sql.Stmt + listCurtailmentTargetsByEventStmt *sql.Stmt + listCurtailmentTargetsByEventPageStmt *sql.Stmt + listCustomRolesForOrgStmt *sql.Stmt + listDeviceSetMembersPaginatedStmt *sql.Stmt + listDeviceSetMembersPaginatedAfterStmt *sql.Stmt + listEffectivePermissionsForUserStmt *sql.Stmt + listEffectivePermissionsForUserForUpdateStmt *sql.Stmt + listEnabledMQTTSourcesStmt *sql.Stmt + listExistingDeviceIdentifiersStmt *sql.Stmt + listFleetNodeDevicesStmt *sql.Stmt + listFleetNodeDiscoveredDevicesStmt *sql.Stmt + listFleetNodesForOrganizationStmt *sql.Stmt + listMQTTSourceConfigsByOrgStmt *sql.Stmt + listMQTTSourceStatesByOrgStmt *sql.Stmt + listMinerStateSnapshotsStmt *sql.Stmt + listNonTerminalCurtailmentEventsStmt *sql.Stmt + listOrganizationsStmt *sql.Stmt + listPermissionsStmt *sql.Stmt + listPoolsStmt *sql.Stmt + listRackTypesStmt *sql.Stmt + listRackZoneRefsStmt *sql.Stmt + listRackZonesStmt *sql.Stmt + listRacksOutsideBuildingBoundsStmt *sql.Stmt + listRecentlyResolvedCurtailedDevicesByOrgStmt *sql.Stmt + listRolePermissionKeysStmt *sql.Stmt + listRolesStmt *sql.Stmt + listRolesWithDetailsForOrgStmt *sql.Stmt + listScheduleIDStatusesStmt *sql.Stmt + listSchedulesStmt *sql.Stmt + listSiteNetworkConfigsForOverlapStmt *sql.Stmt + listSitesStmt *sql.Stmt + listUsersForOrganizationStmt *sql.Stmt + lockAndCountOrgScopeSuperAdminsStmt *sql.Stmt + lockBuildingForWriteStmt *sql.Stmt + lockBuildingsBySiteForWriteStmt *sql.Stmt + lockDevicesForReassignStmt *sql.Stmt + lockFleetNodeByIDStmt *sql.Stmt + lockRackPlacementForWriteStmt *sql.Stmt + lockSchedulePriorityStmt *sql.Stmt + lockSiteForWriteStmt *sql.Stmt + markCommandBatchFinishedStmt *sql.Stmt + markCommandBatchFinishedWithStartedAtStmt *sql.Stmt + markCommandBatchProcessingStmt *sql.Stmt + negateSchedulePrioritiesStmt *sql.Stmt + pairDeviceToFleetNodeStmt *sql.Stmt + passwordUpdatedAtStmt *sql.Stmt + pauseActiveScheduleStmt *sql.Stmt + prunePermissionsOutsideKeysStmt *sql.Stmt + queryComponentKeysWithErrorsStmt *sql.Stmt + queryDeviceIDsWithErrorsStmt *sql.Stmt + queryErrorsStmt *sql.Stmt + reapStuckFirmwareUpdateMessagesStmt *sql.Stmt + reapStuckProcessingMessagesStmt *sql.Stmt + reassignDevicesUnderBuildingStmt *sql.Stmt + reassignRacksUnderBuildingStmt *sql.Stmt + removeAllDevicesFromDeviceSetStmt *sql.Stmt + removeDevicesFromAnyRackStmt *sql.Stmt + removeDevicesFromDeviceSetStmt *sql.Stmt + resetCurtailmentTargetsForRecurtailStmt *sql.Stmt + resetCurtailmentTargetsForRestoreStmt *sql.Stmt + resumeCurtailmentFromRestoringStmt *sql.Stmt + resumePausedScheduleStmt *sql.Stmt + revertScheduleToActiveStmt *sql.Stmt + revokeAllSessionsByUserIDStmt *sql.Stmt + revokeApiKeyStmt *sql.Stmt + revokeApiKeysByFleetNodeIDStmt *sql.Stmt + revokePermissionFromRoleStmt *sql.Stmt + revokeSessionStmt *sql.Stmt + setFleetNodeEnrollmentStatusStmt *sql.Stmt + setMQTTSourceConfigEnabledStmt *sql.Stmt + setRackBuildingPositionStmt *sql.Stmt + setRackSlotPositionStmt *sql.Stmt + setSchedulePrioritiesStmt *sql.Stmt + setScheduleRunningStmt *sql.Stmt + siteBelongsToOrgStmt *sql.Stmt + softDeleteBuildingStmt *sql.Stmt + softDeleteBuildingsBySiteStmt *sql.Stmt + softDeleteCustomRoleStmt *sql.Stmt + softDeleteDeviceSetStmt *sql.Stmt + softDeleteDevicesStmt *sql.Stmt + softDeleteDiscoveredDeviceByIdentifierStmt *sql.Stmt + softDeleteDiscoveredDevicesForDeletedDevicesStmt *sql.Stmt + softDeleteFleetNodeStmt *sql.Stmt + softDeleteFleetNodesForExpiredEnrollmentsStmt *sql.Stmt + softDeleteOrganizationStmt *sql.Stmt + softDeletePoolStmt *sql.Stmt + softDeleteRoleStmt *sql.Stmt + softDeleteScheduleStmt *sql.Stmt + softDeleteSiteStmt *sql.Stmt + softDeleteUserStmt *sql.Stmt + softDeleteUserFromOrganizationStmt *sql.Stmt + sweepCurtailmentTargetsToRestoreFailedStmt *sql.Stmt + sweepExpiredEnrollmentsStmt *sql.Stmt + sweepExpiredFleetNodeAuthChallengesStmt *sql.Stmt + sweepExpiredFleetNodeSessionsStmt *sql.Stmt + transferDiscoveredDeviceAttributionStmt *sql.Stmt + unassignDeviceSitesByRackStmt *sql.Stmt + unassignDevicesFromSiteStmt *sql.Stmt + unassignRacksFromBuildingStmt *sql.Stmt + unassignRacksFromBuildingsBySiteStmt *sql.Stmt + unassignRacksFromSiteStmt *sql.Stmt + unassignRoleStmt *sql.Stmt + undeleteOrganizationStmt *sql.Stmt + undeleteRoleStmt *sql.Stmt + unpairDeviceStmt *sql.Stmt + updateApiKeyLastUsedStmt *sql.Stmt + updateBuildingStmt *sql.Stmt + updateCurtailmentEventOperatorFieldsStmt *sql.Stmt + updateCurtailmentEventStateStmt *sql.Stmt + updateCurtailmentTargetStateStmt *sql.Stmt + updateCustomRoleNameStmt *sql.Stmt + updateDeviceIPAssignmentStmt *sql.Stmt + updateDeviceInfoStmt *sql.Stmt + updateDevicePairingStatusByIdentifierStmt *sql.Stmt + updateDeviceSetDescriptionStmt *sql.Stmt + updateDeviceSetLabelStmt *sql.Stmt + updateDeviceSetLabelAndDescriptionStmt *sql.Stmt + updateDeviceWorkerNameStmt *sql.Stmt + updateDeviceWorkerNamePoolSyncStatusByIDStmt *sql.Stmt + updateDiscoveredDeviceFirmwareVersionStmt *sql.Stmt + updateFleetNodeLastSeenAtStmt *sql.Stmt + updateLastLoginStmt *sql.Stmt + updateMQTTSourceConfigStmt *sql.Stmt + updateMessageAfterFailureStmt *sql.Stmt + updateMessagePermanentlyFailedStmt *sql.Stmt + updateMessageStatusStmt *sql.Stmt + updateMinerPasswordStmt *sql.Stmt + updateOpenErrorStmt *sql.Stmt + updateOrganizationStmt *sql.Stmt + updatePoolStmt *sql.Stmt + updateRackInfoStmt *sql.Stmt + updateRackPlacementStmt *sql.Stmt + updateRoleStmt *sql.Stmt + updateScheduleStmt *sql.Stmt + updateScheduleAfterRunStmt *sql.Stmt + updateSessionActivityStmt *sql.Stmt + updateSiteStmt *sql.Stmt + updateUserPasswordStmt *sql.Stmt + updateUserPasswordAndFlagStmt *sql.Stmt + updateUserRoleStmt *sql.Stmt + updateUserUsernameStmt *sql.Stmt + upsertBuiltinRoleForOrgStmt *sql.Stmt + upsertCommandOnDeviceLogStmt *sql.Stmt + upsertCurtailmentReconcilerHeartbeatStmt *sql.Stmt + upsertCustomRoleForOrgStmt *sql.Stmt + upsertDevicePairingStmt *sql.Stmt + upsertDeviceStatusStmt *sql.Stmt + upsertDiscoveredDeviceStmt *sql.Stmt + upsertDiscoveredDeviceFromFleetNodeStmt *sql.Stmt + upsertFleetNodeAuthChallengeStmt *sql.Stmt + upsertFleetNodeSessionStmt *sql.Stmt + upsertMQTTSourceStateStmt *sql.Stmt + upsertMinerCredentialsStmt *sql.Stmt + upsertPermissionStmt *sql.Stmt } func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ - db: tx, - tx: tx, - acquireReconcileLockStmt: q.acquireReconcileLockStmt, - addDevicesToDeviceSetStmt: q.addDevicesToDeviceSetStmt, - adminResetUserPasswordStmt: q.adminResetUserPasswordStmt, - adminTerminateCurtailmentEventStmt: q.adminTerminateCurtailmentEventStmt, - allDevicesBelongToOrgStmt: q.allDevicesBelongToOrgStmt, - assignBuildingToSiteStmt: q.assignBuildingToSiteStmt, - assignPermissionToRoleStmt: q.assignPermissionToRoleStmt, - assignRoleStmt: q.assignRoleStmt, - beginCurtailmentRestorationStmt: q.beginCurtailmentRestorationStmt, - bindEnrollmentToFleetNodeStmt: q.bindEnrollmentToFleetNodeStmt, - buildingBelongsToOrgStmt: q.buildingBelongsToOrgStmt, - buildingsByIDsStmt: q.buildingsByIDsStmt, - bulkInsertCurtailmentTargetsStmt: q.bulkInsertCurtailmentTargetsStmt, - bumpCurtailmentTargetRetryStmt: q.bumpCurtailmentTargetRetryStmt, - cancelEnrollmentForFleetNodeStmt: q.cancelEnrollmentForFleetNodeStmt, - cancelPendingEnrollmentStmt: q.cancelPendingEnrollmentStmt, - cascadeAddedDeviceSitesStmt: q.cascadeAddedDeviceSitesStmt, - cascadeRackDeviceSitesStmt: q.cascadeRackDeviceSitesStmt, - claimMessageForProcessingStmt: q.claimMessageForProcessingStmt, - clearCurtailmentAutomationActiveEventStmt: q.clearCurtailmentAutomationActiveEventStmt, - clearRackPlacementForSoftDeleteStmt: q.clearRackPlacementForSoftDeleteStmt, - clearRackSlotPositionStmt: q.clearRackSlotPositionStmt, - clearRolePermissionsStmt: q.clearRolePermissionsStmt, - closeStaleErrorsStmt: q.closeStaleErrorsStmt, - confirmEnrollmentStmt: q.confirmEnrollmentStmt, - consumeFleetNodeAuthChallengeStmt: q.consumeFleetNodeAuthChallengeStmt, - countActiveAssignmentsForRoleStmt: q.countActiveAssignmentsForRoleStmt, - countActiveUnpairedDiscoveredDevicesStmt: q.countActiveUnpairedDiscoveredDevicesStmt, - countActivityLogsStmt: q.countActivityLogsStmt, - countComponentsWithErrorsStmt: q.countComponentsWithErrorsStmt, - countCurtailmentAutomationRulesByMQTTSourceStmt: q.countCurtailmentAutomationRulesByMQTTSourceStmt, - countCurtailmentAutomationRulesByResponseProfileStmt: q.countCurtailmentAutomationRulesByResponseProfileStmt, - countDevicesWithErrorsStmt: q.countDevicesWithErrorsStmt, - countErrorsStmt: q.countErrorsStmt, - countMinersByStateStmt: q.countMinersByStateStmt, - countOrgScopeSuperAdminsExcludingUserStmt: q.countOrgScopeSuperAdminsExcludingUserStmt, - createApiKeyStmt: q.createApiKeyStmt, - createBuildingStmt: q.createBuildingStmt, - createCommandBatchLogStmt: q.createCommandBatchLogStmt, - createCustomRoleStmt: q.createCustomRoleStmt, - createDeviceSetStmt: q.createDeviceSetStmt, - createFleetNodeStmt: q.createFleetNodeStmt, - createFleetNodeApiKeyStmt: q.createFleetNodeApiKeyStmt, - createOrganizationStmt: q.createOrganizationStmt, - createPendingEnrollmentStmt: q.createPendingEnrollmentStmt, - createPoolStmt: q.createPoolStmt, - createQueueMessageStmt: q.createQueueMessageStmt, - createRackExtensionStmt: q.createRackExtensionStmt, - createScheduleStmt: q.createScheduleStmt, - createScheduleTargetStmt: q.createScheduleTargetStmt, - createSessionStmt: q.createSessionStmt, - createSiteStmt: q.createSiteStmt, - createUserStmt: q.createUserStmt, - createUserOrganizationStmt: q.createUserOrganizationStmt, - curtailmentEventHasInFlightTargetsStmt: q.curtailmentEventHasInFlightTargetsStmt, - deleteCurtailmentAutomationRuleByOrgStmt: q.deleteCurtailmentAutomationRuleByOrgStmt, - deleteCurtailmentResponseProfileByOrgStmt: q.deleteCurtailmentResponseProfileByOrgStmt, - deleteCurtailmentResponseProfilesBySiteStmt: q.deleteCurtailmentResponseProfilesBySiteStmt, - deleteDisabledMQTTSourceConfigByOrgStmt: q.deleteDisabledMQTTSourceConfigByOrgStmt, - deleteExpiredSessionsStmt: q.deleteExpiredSessionsStmt, - deleteOrganizationStmt: q.deleteOrganizationStmt, - deletePairingsForFleetNodeStmt: q.deletePairingsForFleetNodeStmt, - deletePoolStmt: q.deletePoolStmt, - deleteScheduleTargetsStmt: q.deleteScheduleTargetsStmt, - deviceHasActiveCloudPairingStmt: q.deviceHasActiveCloudPairingStmt, - deviceHasActivePairingStmt: q.deviceHasActivePairingStmt, - deviceSetBelongsToOrgStmt: q.deviceSetBelongsToOrgStmt, - ensureCurtailmentOrgConfigStmt: q.ensureCurtailmentOrgConfigStmt, - findDeviceSiteConflictsStmt: q.findDeviceSiteConflictsStmt, - getActiveCurtailmentEventStmt: q.getActiveCurtailmentEventStmt, - getActiveSchedulesStmt: q.getActiveSchedulesStmt, - getActiveUnpairedDiscoveredDevicesStmt: q.getActiveUnpairedDiscoveredDevicesStmt, - getAddedDeviceSiteConflictsStmt: q.getAddedDeviceSiteConflictsStmt, - getAllDeviceInfoForCapabilityCheckStmt: q.getAllDeviceInfoForCapabilityCheckStmt, - getAllDeviceMetricsDailyAggregatesStmt: q.getAllDeviceMetricsDailyAggregatesStmt, - getAllDeviceMetricsHourlyAggregatesStmt: q.getAllDeviceMetricsHourlyAggregatesStmt, - getAllDeviceMetricsTimeSeriesStmt: q.getAllDeviceMetricsTimeSeriesStmt, - getAllDeviceStatusDailyAggregatesStmt: q.getAllDeviceStatusDailyAggregatesStmt, - getAllDeviceStatusHourlyAggregatesStmt: q.getAllDeviceStatusHourlyAggregatesStmt, - getAllPairedDeviceIdentifiersStmt: q.getAllPairedDeviceIdentifiersStmt, - getApiKeyByHashStmt: q.getApiKeyByHashStmt, - getAssignmentByIDStmt: q.getAssignmentByIDStmt, - getAvailableFirmwareVersionsStmt: q.getAvailableFirmwareVersionsStmt, - getAvailableModelsStmt: q.getAvailableModelsStmt, - getBatchHeaderForOrgStmt: q.getBatchHeaderForOrgStmt, - getBatchLogStmt: q.getBatchLogStmt, - getBatchStatusAndDeviceCountsStmt: q.getBatchStatusAndDeviceCountsStmt, - getBuildingStmt: q.getBuildingStmt, - getBuildingSiteStmt: q.getBuildingSiteStmt, - getBuiltinRoleForOrgStmt: q.getBuiltinRoleForOrgStmt, - getCurtailmentAutomationRuleByOrgStmt: q.getCurtailmentAutomationRuleByOrgStmt, - getCurtailmentEventByExternalReferenceStmt: q.getCurtailmentEventByExternalReferenceStmt, - getCurtailmentEventByIdempotencyKeyStmt: q.getCurtailmentEventByIdempotencyKeyStmt, - getCurtailmentEventByUUIDStmt: q.getCurtailmentEventByUUIDStmt, - getCurtailmentEventDetailByUUIDStmt: q.getCurtailmentEventDetailByUUIDStmt, - getCurtailmentOrgConfigStmt: q.getCurtailmentOrgConfigStmt, - getCurtailmentReconcilerHeartbeatStmt: q.getCurtailmentReconcilerHeartbeatStmt, - getCurtailmentResponseProfileByOrgStmt: q.getCurtailmentResponseProfileByOrgStmt, - getCurtailmentTargetRollupByEventStmt: q.getCurtailmentTargetRollupByEventStmt, - getDeviceByDeviceIdentifierStmt: q.getDeviceByDeviceIdentifierStmt, - getDeviceByIDStmt: q.getDeviceByIDStmt, - getDeviceDeviceSetsStmt: q.getDeviceDeviceSetsStmt, - getDeviceDeviceSetsByTypeStmt: q.getDeviceDeviceSetsByTypeStmt, - getDeviceIDByDeviceIdentifierStmt: q.getDeviceIDByDeviceIdentifierStmt, - getDeviceIDByIdentifierStmt: q.getDeviceIDByIdentifierStmt, - getDeviceIDsByDeviceIdentifiersStmt: q.getDeviceIDsByDeviceIdentifiersStmt, - getDeviceIDsWithIdentifiersStmt: q.getDeviceIDsWithIdentifiersStmt, - getDeviceIdentifierByIDStmt: q.getDeviceIdentifierByIDStmt, - getDeviceIdentifiersByDeviceSetIDStmt: q.getDeviceIdentifiersByDeviceSetIDStmt, - getDeviceInfoForCapabilityCheckStmt: q.getDeviceInfoForCapabilityCheckStmt, - getDeviceMetricsDailyAggregatesStmt: q.getDeviceMetricsDailyAggregatesStmt, - getDeviceMetricsHourlyAggregatesStmt: q.getDeviceMetricsHourlyAggregatesStmt, - getDeviceMetricsTimeSeriesStmt: q.getDeviceMetricsTimeSeriesStmt, - getDevicePairingStatusByDeviceDatabaseIDStmt: q.getDevicePairingStatusByDeviceDatabaseIDStmt, - getDevicePropertiesForRenameStmt: q.getDevicePropertiesForRenameStmt, - getDevicePropertiesForRenameWithoutTelemetryStmt: q.getDevicePropertiesForRenameWithoutTelemetryStmt, - getDeviceSetStmt: q.getDeviceSetStmt, - getDeviceSetTypeStmt: q.getDeviceSetTypeStmt, - getDeviceSetTypesBatchStmt: q.getDeviceSetTypesBatchStmt, - getDeviceSiteIDsByMembershipStmt: q.getDeviceSiteIDsByMembershipStmt, - getDeviceStatusStmt: q.getDeviceStatusStmt, - getDeviceStatusByDeviceIdentifierStmt: q.getDeviceStatusByDeviceIdentifierStmt, - getDeviceStatusDailyAggregatesStmt: q.getDeviceStatusDailyAggregatesStmt, - getDeviceStatusForDeviceIdentifiersStmt: q.getDeviceStatusForDeviceIdentifiersStmt, - getDeviceStatusHourlyAggregatesStmt: q.getDeviceStatusHourlyAggregatesStmt, - getDeviceWithCredentialsAndIPByDeviceIdentifierStmt: q.getDeviceWithCredentialsAndIPByDeviceIdentifierStmt, - getDeviceWithCredentialsAndIPByIDStmt: q.getDeviceWithCredentialsAndIPByIDStmt, - getDiscoveredDeviceByDeviceIdentifierStmt: q.getDiscoveredDeviceByDeviceIdentifierStmt, - getDiscoveredDeviceByIDStmt: q.getDiscoveredDeviceByIDStmt, - getDiscoveredDeviceByIPAndPortStmt: q.getDiscoveredDeviceByIPAndPortStmt, - getDistinctActivityUsersStmt: q.getDistinctActivityUsersStmt, - getDistinctEventTypesStmt: q.getDistinctEventTypesStmt, - getDistinctScopeTypesStmt: q.getDistinctScopeTypesStmt, - getErrorByErrorIDStmt: q.getErrorByErrorIDStmt, - getErrorByIDStmt: q.getErrorByIDStmt, - getFilteredDeviceIdentifiersStmt: q.getFilteredDeviceIdentifiersStmt, - getFilteredDeviceIdsStmt: q.getFilteredDeviceIdsStmt, - getFleetNodeByIDStmt: q.getFleetNodeByIDStmt, - getFleetNodeByIDUnscopedStmt: q.getFleetNodeByIDUnscopedStmt, - getFleetNodeSessionByTokenHashStmt: q.getFleetNodeSessionByTokenHashStmt, - getGroupLabelsForDevicesStmt: q.getGroupLabelsForDevicesStmt, - getKnownSubnetsStmt: q.getKnownSubnetsStmt, - getLatestAllDeviceMetricsStmt: q.getLatestAllDeviceMetricsStmt, - getLatestDeviceMetricsStmt: q.getLatestDeviceMetricsStmt, - getMQTTSourceConfigByOrgStmt: q.getMQTTSourceConfigByOrgStmt, - getMQTTSourceStateByIDStmt: q.getMQTTSourceStateByIDStmt, - getMaxPriorityStmt: q.getMaxPriorityStmt, - getMessagesToProcessStmt: q.getMessagesToProcessStmt, - getMinerCredentialsByDeviceIDStmt: q.getMinerCredentialsByDeviceIDStmt, - getMinerModelGroupsStmt: q.getMinerModelGroupsStmt, - getMinerStateCountsByDeviceIDsStmt: q.getMinerStateCountsByDeviceIDsStmt, - getMinerStateSnapshotsStmt: q.getMinerStateSnapshotsStmt, - getOfflineDevicesStmt: q.getOfflineDevicesStmt, - getOpenErrorByDedupKeyStmt: q.getOpenErrorByDedupKeyStmt, - getOrgScopeAssignmentForUserStmt: q.getOrgScopeAssignmentForUserStmt, - getOrganizationByIDStmt: q.getOrganizationByIDStmt, - getOrganizationByNameStmt: q.getOrganizationByNameStmt, - getOrganizationByOrgIDStmt: q.getOrganizationByOrgIDStmt, - getOrganizationPrivateKeyStmt: q.getOrganizationPrivateKeyStmt, - getOrganizationsForUserStmt: q.getOrganizationsForUserStmt, - getPairedDeviceByMACAddressStmt: q.getPairedDeviceByMACAddressStmt, - getPairedDeviceBySerialNumberStmt: q.getPairedDeviceBySerialNumberStmt, - getPairedDevicesByMACAddressesStmt: q.getPairedDevicesByMACAddressesStmt, - getPairedDevicesIdsStmt: q.getPairedDevicesIdsStmt, - getPendingEnrollmentByCodeHashStmt: q.getPendingEnrollmentByCodeHashStmt, - getPendingEnrollmentByFleetNodeStmt: q.getPendingEnrollmentByFleetNodeStmt, - getPermissionByKeyStmt: q.getPermissionByKeyStmt, - getPermissionsByKeysStmt: q.getPermissionsByKeysStmt, - getPoolStmt: q.getPoolStmt, - getRackDetailsForDevicesStmt: q.getRackDetailsForDevicesStmt, - getRackInfoStmt: q.getRackInfoStmt, - getRackInfoBatchStmt: q.getRackInfoBatchStmt, - getRackSlotsStmt: q.getRackSlotsStmt, - getRoleByIDStmt: q.getRoleByIDStmt, - getRoleByIDForUpdateStmt: q.getRoleByIDForUpdateStmt, - getRunningPowerTargetScheduleOverlapsStmt: q.getRunningPowerTargetScheduleOverlapsStmt, - getScheduleStmt: q.getScheduleStmt, - getScheduleByIDForProcessorStmt: q.getScheduleByIDForProcessorStmt, - getScheduleForUpdateStmt: q.getScheduleForUpdateStmt, - getScheduleTargetsStmt: q.getScheduleTargetsStmt, - getScheduleTargetsByScheduleIDsStmt: q.getScheduleTargetsByScheduleIDsStmt, - getSessionByIDStmt: q.getSessionByIDStmt, - getSiteStmt: q.getSiteStmt, - getTotalDevicesPendingAuthStmt: q.getTotalDevicesPendingAuthStmt, - getTotalMinerStateSnapshotsStmt: q.getTotalMinerStateSnapshotsStmt, - getTotalPairedDevicesStmt: q.getTotalPairedDevicesStmt, - getTotalPoolsStmt: q.getTotalPoolsStmt, - getUserByExternalIdStmt: q.getUserByExternalIdStmt, - getUserByIdStmt: q.getUserByIdStmt, - getUserByIdForUpdateStmt: q.getUserByIdForUpdateStmt, - getUserByUsernameStmt: q.getUserByUsernameStmt, - getUserRoleInOrganizationStmt: q.getUserRoleInOrganizationStmt, - getUserRoleNameStmt: q.getUserRoleNameStmt, - getUsersForOrganizationStmt: q.getUsersForOrganizationStmt, - hasUserStmt: q.hasUserStmt, - insertActivityLogStmt: q.insertActivityLogStmt, - insertCurtailmentAutomationRuleStmt: q.insertCurtailmentAutomationRuleStmt, - insertCurtailmentEventStmt: q.insertCurtailmentEventStmt, - insertCurtailmentResponseProfileStmt: q.insertCurtailmentResponseProfileStmt, - insertDeviceStmt: q.insertDeviceStmt, - insertDeviceMetricsStmt: q.insertDeviceMetricsStmt, - insertErrorStmt: q.insertErrorStmt, - insertMQTTSourceConfigStmt: q.insertMQTTSourceConfigStmt, - insertMinerStateSnapshotStmt: q.insertMinerStateSnapshotStmt, - insertNotificationHistoryStmt: q.insertNotificationHistoryStmt, - insertNotificationMetricSamplesStmt: q.insertNotificationMetricSamplesStmt, - isBatchFinishedStmt: q.isBatchFinishedStmt, - isBatchProcessingStmt: q.isBatchProcessingStmt, - listActiveCurtailedDevicesByOrgStmt: q.listActiveCurtailedDevicesByOrgStmt, - listActiveCurtailmentEventsStmt: q.listActiveCurtailmentEventsStmt, - listActiveOrganizationIDsStmt: q.listActiveOrganizationIDsStmt, - listActivityLogsStmt: q.listActivityLogsStmt, - listApiKeysByOrganizationStmt: q.listApiKeysByOrganizationStmt, - listAssignmentsForRoleStmt: q.listAssignmentsForRoleStmt, - listAssignmentsForUserStmt: q.listAssignmentsForUserStmt, - listBatchDeviceResultsStmt: q.listBatchDeviceResultsStmt, - listBuildingRacksStmt: q.listBuildingRacksStmt, - listBuildingsByOrgStmt: q.listBuildingsByOrgStmt, - listBuiltinRolesForOrgStmt: q.listBuiltinRolesForOrgStmt, - listCurtailmentAutomationRulesByOrgStmt: q.listCurtailmentAutomationRulesByOrgStmt, - listCurtailmentCandidatesByOrgStmt: q.listCurtailmentCandidatesByOrgStmt, - listCurtailmentEventsForOrgStmt: q.listCurtailmentEventsForOrgStmt, - listCurtailmentResponseProfilesByOrgStmt: q.listCurtailmentResponseProfilesByOrgStmt, - listCurtailmentTargetsByEventStmt: q.listCurtailmentTargetsByEventStmt, - listCurtailmentTargetsByEventPageStmt: q.listCurtailmentTargetsByEventPageStmt, - listCustomRolesForOrgStmt: q.listCustomRolesForOrgStmt, - listDeviceSetMembersPaginatedStmt: q.listDeviceSetMembersPaginatedStmt, - listDeviceSetMembersPaginatedAfterStmt: q.listDeviceSetMembersPaginatedAfterStmt, - listEffectivePermissionsForUserStmt: q.listEffectivePermissionsForUserStmt, - listEffectivePermissionsForUserForUpdateStmt: q.listEffectivePermissionsForUserForUpdateStmt, - listEnabledCurtailmentAutomationRulesByMQTTSourceStmt: q.listEnabledCurtailmentAutomationRulesByMQTTSourceStmt, - listEnabledMQTTSourcesStmt: q.listEnabledMQTTSourcesStmt, - listExistingDeviceIdentifiersStmt: q.listExistingDeviceIdentifiersStmt, - listFleetNodeDevicesStmt: q.listFleetNodeDevicesStmt, - listFleetNodeDiscoveredDevicesStmt: q.listFleetNodeDiscoveredDevicesStmt, - listFleetNodesForOrganizationStmt: q.listFleetNodesForOrganizationStmt, - listMQTTSourceConfigsByOrgStmt: q.listMQTTSourceConfigsByOrgStmt, - listMQTTSourceStatesByOrgStmt: q.listMQTTSourceStatesByOrgStmt, - listMinerStateSnapshotsStmt: q.listMinerStateSnapshotsStmt, - listNonTerminalCurtailmentEventsStmt: q.listNonTerminalCurtailmentEventsStmt, - listOrganizationsStmt: q.listOrganizationsStmt, - listPermissionsStmt: q.listPermissionsStmt, - listPoolsStmt: q.listPoolsStmt, - listRackTypesStmt: q.listRackTypesStmt, - listRackZoneRefsStmt: q.listRackZoneRefsStmt, - listRackZonesStmt: q.listRackZonesStmt, - listRacksOutsideBuildingBoundsStmt: q.listRacksOutsideBuildingBoundsStmt, - listRecentlyResolvedCurtailedDevicesByOrgStmt: q.listRecentlyResolvedCurtailedDevicesByOrgStmt, - listRolePermissionKeysStmt: q.listRolePermissionKeysStmt, - listRolesStmt: q.listRolesStmt, - listRolesWithDetailsForOrgStmt: q.listRolesWithDetailsForOrgStmt, - listScheduleIDStatusesStmt: q.listScheduleIDStatusesStmt, - listSchedulesStmt: q.listSchedulesStmt, - listSiteNetworkConfigsForOverlapStmt: q.listSiteNetworkConfigsForOverlapStmt, - listSitesStmt: q.listSitesStmt, - listUsersForOrganizationStmt: q.listUsersForOrganizationStmt, - lockAndCountOrgScopeSuperAdminsStmt: q.lockAndCountOrgScopeSuperAdminsStmt, - lockBuildingForWriteStmt: q.lockBuildingForWriteStmt, - lockBuildingsBySiteForWriteStmt: q.lockBuildingsBySiteForWriteStmt, - lockDevicesForReassignStmt: q.lockDevicesForReassignStmt, - lockFleetNodeByIDStmt: q.lockFleetNodeByIDStmt, - lockRackPlacementForWriteStmt: q.lockRackPlacementForWriteStmt, - lockSchedulePriorityStmt: q.lockSchedulePriorityStmt, - lockSiteForWriteStmt: q.lockSiteForWriteStmt, - markCommandBatchFinishedStmt: q.markCommandBatchFinishedStmt, - markCommandBatchFinishedWithStartedAtStmt: q.markCommandBatchFinishedWithStartedAtStmt, - markCommandBatchProcessingStmt: q.markCommandBatchProcessingStmt, - negateSchedulePrioritiesStmt: q.negateSchedulePrioritiesStmt, - pairDeviceToFleetNodeStmt: q.pairDeviceToFleetNodeStmt, - passwordUpdatedAtStmt: q.passwordUpdatedAtStmt, - pauseActiveScheduleStmt: q.pauseActiveScheduleStmt, - prunePermissionsOutsideKeysStmt: q.prunePermissionsOutsideKeysStmt, - queryComponentKeysWithErrorsStmt: q.queryComponentKeysWithErrorsStmt, - queryDeviceIDsWithErrorsStmt: q.queryDeviceIDsWithErrorsStmt, - queryErrorsStmt: q.queryErrorsStmt, - reapStuckFirmwareUpdateMessagesStmt: q.reapStuckFirmwareUpdateMessagesStmt, - reapStuckProcessingMessagesStmt: q.reapStuckProcessingMessagesStmt, - reassignDevicesToSiteStmt: q.reassignDevicesToSiteStmt, - reassignDevicesUnderBuildingStmt: q.reassignDevicesUnderBuildingStmt, - reassignRacksUnderBuildingStmt: q.reassignRacksUnderBuildingStmt, - removeAllDevicesFromDeviceSetStmt: q.removeAllDevicesFromDeviceSetStmt, - removeDevicesFromDeviceSetStmt: q.removeDevicesFromDeviceSetStmt, - resetCurtailmentTargetsForRecurtailStmt: q.resetCurtailmentTargetsForRecurtailStmt, - resetCurtailmentTargetsForRestoreStmt: q.resetCurtailmentTargetsForRestoreStmt, - resumeCurtailmentFromRestoringStmt: q.resumeCurtailmentFromRestoringStmt, - resumePausedScheduleStmt: q.resumePausedScheduleStmt, - revertScheduleToActiveStmt: q.revertScheduleToActiveStmt, - revokeAllSessionsByUserIDStmt: q.revokeAllSessionsByUserIDStmt, - revokeApiKeyStmt: q.revokeApiKeyStmt, - revokeApiKeysByFleetNodeIDStmt: q.revokeApiKeysByFleetNodeIDStmt, - revokePermissionFromRoleStmt: q.revokePermissionFromRoleStmt, - revokeSessionStmt: q.revokeSessionStmt, - setCurtailmentAutomationActiveEventStmt: q.setCurtailmentAutomationActiveEventStmt, - setCurtailmentAutomationExecutionErrorStmt: q.setCurtailmentAutomationExecutionErrorStmt, - setCurtailmentAutomationRestoreStartedStmt: q.setCurtailmentAutomationRestoreStartedStmt, - setCurtailmentAutomationRuleEnabledStmt: q.setCurtailmentAutomationRuleEnabledStmt, - setDevicePairingAuthNeededIfNotPairedStmt: q.setDevicePairingAuthNeededIfNotPairedStmt, - setFleetNodeEnrollmentStatusStmt: q.setFleetNodeEnrollmentStatusStmt, - setMQTTSourceConfigEnabledStmt: q.setMQTTSourceConfigEnabledStmt, - setRackBuildingPositionStmt: q.setRackBuildingPositionStmt, - setRackSlotPositionStmt: q.setRackSlotPositionStmt, - setSchedulePrioritiesStmt: q.setSchedulePrioritiesStmt, - setScheduleRunningStmt: q.setScheduleRunningStmt, - siteBelongsToOrgStmt: q.siteBelongsToOrgStmt, - softDeleteBuildingStmt: q.softDeleteBuildingStmt, - softDeleteBuildingsBySiteStmt: q.softDeleteBuildingsBySiteStmt, - softDeleteCustomRoleStmt: q.softDeleteCustomRoleStmt, - softDeleteDeviceSetStmt: q.softDeleteDeviceSetStmt, - softDeleteDevicesStmt: q.softDeleteDevicesStmt, - softDeleteDiscoveredDeviceByIdentifierStmt: q.softDeleteDiscoveredDeviceByIdentifierStmt, - softDeleteDiscoveredDevicesForDeletedDevicesStmt: q.softDeleteDiscoveredDevicesForDeletedDevicesStmt, - softDeleteFleetNodeStmt: q.softDeleteFleetNodeStmt, - softDeleteFleetNodesForExpiredEnrollmentsStmt: q.softDeleteFleetNodesForExpiredEnrollmentsStmt, - softDeleteOrganizationStmt: q.softDeleteOrganizationStmt, - softDeletePoolStmt: q.softDeletePoolStmt, - softDeleteRoleStmt: q.softDeleteRoleStmt, - softDeleteScheduleStmt: q.softDeleteScheduleStmt, - softDeleteSiteStmt: q.softDeleteSiteStmt, - softDeleteUserStmt: q.softDeleteUserStmt, - softDeleteUserFromOrganizationStmt: q.softDeleteUserFromOrganizationStmt, - sweepCurtailmentTargetsToRestoreFailedStmt: q.sweepCurtailmentTargetsToRestoreFailedStmt, - sweepExpiredEnrollmentsStmt: q.sweepExpiredEnrollmentsStmt, - sweepExpiredFleetNodeAuthChallengesStmt: q.sweepExpiredFleetNodeAuthChallengesStmt, - sweepExpiredFleetNodeSessionsStmt: q.sweepExpiredFleetNodeSessionsStmt, - transferDiscoveredDeviceAttributionStmt: q.transferDiscoveredDeviceAttributionStmt, - unassignDeviceSitesByRackStmt: q.unassignDeviceSitesByRackStmt, - unassignDevicesFromSiteStmt: q.unassignDevicesFromSiteStmt, - unassignRacksFromBuildingStmt: q.unassignRacksFromBuildingStmt, - unassignRacksFromBuildingsBySiteStmt: q.unassignRacksFromBuildingsBySiteStmt, - unassignRacksFromSiteStmt: q.unassignRacksFromSiteStmt, - unassignRoleStmt: q.unassignRoleStmt, - undeleteOrganizationStmt: q.undeleteOrganizationStmt, - undeleteRoleStmt: q.undeleteRoleStmt, - unpairDeviceStmt: q.unpairDeviceStmt, - updateApiKeyLastUsedStmt: q.updateApiKeyLastUsedStmt, - updateBuildingStmt: q.updateBuildingStmt, - updateCurtailmentAutomationRuleStmt: q.updateCurtailmentAutomationRuleStmt, - updateCurtailmentEventOperatorFieldsStmt: q.updateCurtailmentEventOperatorFieldsStmt, - updateCurtailmentEventStateStmt: q.updateCurtailmentEventStateStmt, - updateCurtailmentResponseProfileStmt: q.updateCurtailmentResponseProfileStmt, - updateCurtailmentTargetStateStmt: q.updateCurtailmentTargetStateStmt, - updateCustomRoleNameStmt: q.updateCustomRoleNameStmt, - updateDeviceIPAssignmentStmt: q.updateDeviceIPAssignmentStmt, - updateDeviceInfoStmt: q.updateDeviceInfoStmt, - updateDevicePairingStatusByIdentifierStmt: q.updateDevicePairingStatusByIdentifierStmt, - updateDeviceSetDescriptionStmt: q.updateDeviceSetDescriptionStmt, - updateDeviceSetLabelStmt: q.updateDeviceSetLabelStmt, - updateDeviceSetLabelAndDescriptionStmt: q.updateDeviceSetLabelAndDescriptionStmt, - updateDeviceWorkerNameStmt: q.updateDeviceWorkerNameStmt, - updateDeviceWorkerNamePoolSyncStatusByIDStmt: q.updateDeviceWorkerNamePoolSyncStatusByIDStmt, - updateDiscoveredDeviceFirmwareVersionStmt: q.updateDiscoveredDeviceFirmwareVersionStmt, - updateFleetNodeLastSeenAtStmt: q.updateFleetNodeLastSeenAtStmt, - updateLastLoginStmt: q.updateLastLoginStmt, - updateMQTTSourceConfigStmt: q.updateMQTTSourceConfigStmt, - updateMessageAfterFailureStmt: q.updateMessageAfterFailureStmt, - updateMessagePermanentlyFailedStmt: q.updateMessagePermanentlyFailedStmt, - updateMessageStatusStmt: q.updateMessageStatusStmt, - updateMinerPasswordStmt: q.updateMinerPasswordStmt, - updateOpenErrorStmt: q.updateOpenErrorStmt, - updateOrganizationStmt: q.updateOrganizationStmt, - updatePoolStmt: q.updatePoolStmt, - updateRackInfoStmt: q.updateRackInfoStmt, - updateRackPlacementStmt: q.updateRackPlacementStmt, - updateRoleStmt: q.updateRoleStmt, - updateScheduleStmt: q.updateScheduleStmt, - updateScheduleAfterRunStmt: q.updateScheduleAfterRunStmt, - updateSessionActivityStmt: q.updateSessionActivityStmt, - updateSiteStmt: q.updateSiteStmt, - updateUserPasswordStmt: q.updateUserPasswordStmt, - updateUserPasswordAndFlagStmt: q.updateUserPasswordAndFlagStmt, - updateUserRoleStmt: q.updateUserRoleStmt, - updateUserUsernameStmt: q.updateUserUsernameStmt, - upsertBuiltinRoleForOrgStmt: q.upsertBuiltinRoleForOrgStmt, - upsertCommandOnDeviceLogStmt: q.upsertCommandOnDeviceLogStmt, - upsertCurtailmentAutomationSignalStateStmt: q.upsertCurtailmentAutomationSignalStateStmt, - upsertCurtailmentReconcilerHeartbeatStmt: q.upsertCurtailmentReconcilerHeartbeatStmt, - upsertCustomRoleForOrgStmt: q.upsertCustomRoleForOrgStmt, - upsertDevicePairingStmt: q.upsertDevicePairingStmt, - upsertDeviceStatusStmt: q.upsertDeviceStatusStmt, - upsertDiscoveredDeviceStmt: q.upsertDiscoveredDeviceStmt, - upsertDiscoveredDeviceFromFleetNodeStmt: q.upsertDiscoveredDeviceFromFleetNodeStmt, - upsertFleetNodeAuthChallengeStmt: q.upsertFleetNodeAuthChallengeStmt, - upsertFleetNodeSessionStmt: q.upsertFleetNodeSessionStmt, - upsertMQTTSourceStateStmt: q.upsertMQTTSourceStateStmt, - upsertMinerCredentialsStmt: q.upsertMinerCredentialsStmt, - upsertPermissionStmt: q.upsertPermissionStmt, + db: tx, + tx: tx, + acquireReconcileLockStmt: q.acquireReconcileLockStmt, + addDevicesToDeviceSetStmt: q.addDevicesToDeviceSetStmt, + adminResetUserPasswordStmt: q.adminResetUserPasswordStmt, + adminTerminateCurtailmentEventStmt: q.adminTerminateCurtailmentEventStmt, + allDevicesBelongToOrgStmt: q.allDevicesBelongToOrgStmt, + assignBuildingToSiteStmt: q.assignBuildingToSiteStmt, + assignDevicesToSiteStmt: q.assignDevicesToSiteStmt, + assignPermissionToRoleStmt: q.assignPermissionToRoleStmt, + assignRoleStmt: q.assignRoleStmt, + beginCurtailmentRestorationStmt: q.beginCurtailmentRestorationStmt, + bindEnrollmentToFleetNodeStmt: q.bindEnrollmentToFleetNodeStmt, + buildingBelongsToOrgStmt: q.buildingBelongsToOrgStmt, + buildingsByIDsStmt: q.buildingsByIDsStmt, + bulkInsertCurtailmentTargetsStmt: q.bulkInsertCurtailmentTargetsStmt, + bumpCurtailmentTargetRetryStmt: q.bumpCurtailmentTargetRetryStmt, + cancelEnrollmentForFleetNodeStmt: q.cancelEnrollmentForFleetNodeStmt, + cancelPendingEnrollmentStmt: q.cancelPendingEnrollmentStmt, + cascadeAddedDeviceSitesStmt: q.cascadeAddedDeviceSitesStmt, + cascadeRackDeviceSitesStmt: q.cascadeRackDeviceSitesStmt, + claimMessageForProcessingStmt: q.claimMessageForProcessingStmt, + clearRackPlacementForSoftDeleteStmt: q.clearRackPlacementForSoftDeleteStmt, + clearRackSlotPositionStmt: q.clearRackSlotPositionStmt, + clearRolePermissionsStmt: q.clearRolePermissionsStmt, + closeStaleErrorsStmt: q.closeStaleErrorsStmt, + confirmEnrollmentStmt: q.confirmEnrollmentStmt, + consumeFleetNodeAuthChallengeStmt: q.consumeFleetNodeAuthChallengeStmt, + countActiveAssignmentsForRoleStmt: q.countActiveAssignmentsForRoleStmt, + countActiveUnpairedDiscoveredDevicesStmt: q.countActiveUnpairedDiscoveredDevicesStmt, + countActivityLogsStmt: q.countActivityLogsStmt, + countComponentsWithErrorsStmt: q.countComponentsWithErrorsStmt, + countDevicesWithErrorsStmt: q.countDevicesWithErrorsStmt, + countErrorsStmt: q.countErrorsStmt, + countMinersByStateStmt: q.countMinersByStateStmt, + countOrgScopeSuperAdminsExcludingUserStmt: q.countOrgScopeSuperAdminsExcludingUserStmt, + createApiKeyStmt: q.createApiKeyStmt, + createBuildingStmt: q.createBuildingStmt, + createCommandBatchLogStmt: q.createCommandBatchLogStmt, + createCustomRoleStmt: q.createCustomRoleStmt, + createDeviceSetStmt: q.createDeviceSetStmt, + createFleetNodeStmt: q.createFleetNodeStmt, + createFleetNodeApiKeyStmt: q.createFleetNodeApiKeyStmt, + createOrganizationStmt: q.createOrganizationStmt, + createPendingEnrollmentStmt: q.createPendingEnrollmentStmt, + createPoolStmt: q.createPoolStmt, + createQueueMessageStmt: q.createQueueMessageStmt, + createRackExtensionStmt: q.createRackExtensionStmt, + createScheduleStmt: q.createScheduleStmt, + createScheduleTargetStmt: q.createScheduleTargetStmt, + createSessionStmt: q.createSessionStmt, + createSiteStmt: q.createSiteStmt, + createUserStmt: q.createUserStmt, + createUserOrganizationStmt: q.createUserOrganizationStmt, + curtailmentEventHasInFlightTargetsStmt: q.curtailmentEventHasInFlightTargetsStmt, + deleteDisabledMQTTSourceConfigByOrgStmt: q.deleteDisabledMQTTSourceConfigByOrgStmt, + deleteExpiredSessionsStmt: q.deleteExpiredSessionsStmt, + deleteOrganizationStmt: q.deleteOrganizationStmt, + deletePairingsForFleetNodeStmt: q.deletePairingsForFleetNodeStmt, + deletePoolStmt: q.deletePoolStmt, + deleteScheduleTargetsStmt: q.deleteScheduleTargetsStmt, + deviceHasActiveCloudPairingStmt: q.deviceHasActiveCloudPairingStmt, + deviceSetBelongsToOrgStmt: q.deviceSetBelongsToOrgStmt, + ensureCurtailmentOrgConfigStmt: q.ensureCurtailmentOrgConfigStmt, + findDeviceSiteConflictsStmt: q.findDeviceSiteConflictsStmt, + getActiveCurtailmentEventStmt: q.getActiveCurtailmentEventStmt, + getActiveSchedulesStmt: q.getActiveSchedulesStmt, + getActiveUnpairedDiscoveredDevicesStmt: q.getActiveUnpairedDiscoveredDevicesStmt, + getAddedDeviceSiteConflictsStmt: q.getAddedDeviceSiteConflictsStmt, + getAllDeviceInfoForCapabilityCheckStmt: q.getAllDeviceInfoForCapabilityCheckStmt, + getAllDeviceMetricsDailyAggregatesStmt: q.getAllDeviceMetricsDailyAggregatesStmt, + getAllDeviceMetricsHourlyAggregatesStmt: q.getAllDeviceMetricsHourlyAggregatesStmt, + getAllDeviceMetricsTimeSeriesStmt: q.getAllDeviceMetricsTimeSeriesStmt, + getAllDeviceStatusDailyAggregatesStmt: q.getAllDeviceStatusDailyAggregatesStmt, + getAllDeviceStatusHourlyAggregatesStmt: q.getAllDeviceStatusHourlyAggregatesStmt, + getAllPairedDeviceIdentifiersStmt: q.getAllPairedDeviceIdentifiersStmt, + getApiKeyByHashStmt: q.getApiKeyByHashStmt, + getAssignmentByIDStmt: q.getAssignmentByIDStmt, + getAvailableFirmwareVersionsStmt: q.getAvailableFirmwareVersionsStmt, + getAvailableModelsStmt: q.getAvailableModelsStmt, + getBatchHeaderForOrgStmt: q.getBatchHeaderForOrgStmt, + getBatchLogStmt: q.getBatchLogStmt, + getBatchStatusAndDeviceCountsStmt: q.getBatchStatusAndDeviceCountsStmt, + getBuildingStmt: q.getBuildingStmt, + getBuildingSiteStmt: q.getBuildingSiteStmt, + getBuiltinRoleForOrgStmt: q.getBuiltinRoleForOrgStmt, + getCurtailmentEventByExternalReferenceStmt: q.getCurtailmentEventByExternalReferenceStmt, + getCurtailmentEventByIdempotencyKeyStmt: q.getCurtailmentEventByIdempotencyKeyStmt, + getCurtailmentEventByUUIDStmt: q.getCurtailmentEventByUUIDStmt, + getCurtailmentEventDetailByUUIDStmt: q.getCurtailmentEventDetailByUUIDStmt, + getCurtailmentOrgConfigStmt: q.getCurtailmentOrgConfigStmt, + getCurtailmentReconcilerHeartbeatStmt: q.getCurtailmentReconcilerHeartbeatStmt, + getCurtailmentTargetRollupByEventStmt: q.getCurtailmentTargetRollupByEventStmt, + getDeviceByDeviceIdentifierStmt: q.getDeviceByDeviceIdentifierStmt, + getDeviceByIDStmt: q.getDeviceByIDStmt, + getDeviceDeviceSetsStmt: q.getDeviceDeviceSetsStmt, + getDeviceDeviceSetsByTypeStmt: q.getDeviceDeviceSetsByTypeStmt, + getDeviceIDByDeviceIdentifierStmt: q.getDeviceIDByDeviceIdentifierStmt, + getDeviceIDByIdentifierStmt: q.getDeviceIDByIdentifierStmt, + getDeviceIDsByDeviceIdentifiersStmt: q.getDeviceIDsByDeviceIdentifiersStmt, + getDeviceIDsWithIdentifiersStmt: q.getDeviceIDsWithIdentifiersStmt, + getDeviceIdentifierByIDStmt: q.getDeviceIdentifierByIDStmt, + getDeviceIdentifiersByDeviceSetIDStmt: q.getDeviceIdentifiersByDeviceSetIDStmt, + getDeviceInfoForCapabilityCheckStmt: q.getDeviceInfoForCapabilityCheckStmt, + getDeviceMetricsDailyAggregatesStmt: q.getDeviceMetricsDailyAggregatesStmt, + getDeviceMetricsHourlyAggregatesStmt: q.getDeviceMetricsHourlyAggregatesStmt, + getDeviceMetricsTimeSeriesStmt: q.getDeviceMetricsTimeSeriesStmt, + getDevicePairingStatusByDeviceDatabaseIDStmt: q.getDevicePairingStatusByDeviceDatabaseIDStmt, + getDevicePropertiesForRenameStmt: q.getDevicePropertiesForRenameStmt, + getDevicePropertiesForRenameWithoutTelemetryStmt: q.getDevicePropertiesForRenameWithoutTelemetryStmt, + getDeviceSetStmt: q.getDeviceSetStmt, + getDeviceSetTypeStmt: q.getDeviceSetTypeStmt, + getDeviceSetTypesBatchStmt: q.getDeviceSetTypesBatchStmt, + getDeviceSiteIDsByMembershipStmt: q.getDeviceSiteIDsByMembershipStmt, + getDeviceStatusStmt: q.getDeviceStatusStmt, + getDeviceStatusByDeviceIdentifierStmt: q.getDeviceStatusByDeviceIdentifierStmt, + getDeviceStatusDailyAggregatesStmt: q.getDeviceStatusDailyAggregatesStmt, + getDeviceStatusForDeviceIdentifiersStmt: q.getDeviceStatusForDeviceIdentifiersStmt, + getDeviceStatusHourlyAggregatesStmt: q.getDeviceStatusHourlyAggregatesStmt, + getDeviceWithCredentialsAndIPByDeviceIdentifierStmt: q.getDeviceWithCredentialsAndIPByDeviceIdentifierStmt, + getDeviceWithCredentialsAndIPByIDStmt: q.getDeviceWithCredentialsAndIPByIDStmt, + getDiscoveredDeviceByDeviceIdentifierStmt: q.getDiscoveredDeviceByDeviceIdentifierStmt, + getDiscoveredDeviceByIDStmt: q.getDiscoveredDeviceByIDStmt, + getDiscoveredDeviceByIPAndPortStmt: q.getDiscoveredDeviceByIPAndPortStmt, + getDistinctActivityUsersStmt: q.getDistinctActivityUsersStmt, + getDistinctEventTypesStmt: q.getDistinctEventTypesStmt, + getDistinctScopeTypesStmt: q.getDistinctScopeTypesStmt, + getErrorByErrorIDStmt: q.getErrorByErrorIDStmt, + getErrorByIDStmt: q.getErrorByIDStmt, + getFilteredDeviceIdentifiersStmt: q.getFilteredDeviceIdentifiersStmt, + getFilteredDeviceIdsStmt: q.getFilteredDeviceIdsStmt, + getFleetNodeByIDStmt: q.getFleetNodeByIDStmt, + getFleetNodeByIDUnscopedStmt: q.getFleetNodeByIDUnscopedStmt, + getFleetNodeSessionByTokenHashStmt: q.getFleetNodeSessionByTokenHashStmt, + getGroupLabelsForDevicesStmt: q.getGroupLabelsForDevicesStmt, + getKnownSubnetsStmt: q.getKnownSubnetsStmt, + getLatestAllDeviceMetricsStmt: q.getLatestAllDeviceMetricsStmt, + getLatestDeviceMetricsStmt: q.getLatestDeviceMetricsStmt, + getMQTTSourceConfigByOrgStmt: q.getMQTTSourceConfigByOrgStmt, + getMQTTSourceStateByIDStmt: q.getMQTTSourceStateByIDStmt, + getMaxPriorityStmt: q.getMaxPriorityStmt, + getMessagesToProcessStmt: q.getMessagesToProcessStmt, + getMinerCredentialsByDeviceIDStmt: q.getMinerCredentialsByDeviceIDStmt, + getMinerModelGroupsStmt: q.getMinerModelGroupsStmt, + getMinerStateCountsByDeviceIDsStmt: q.getMinerStateCountsByDeviceIDsStmt, + getMinerStateSnapshotsStmt: q.getMinerStateSnapshotsStmt, + getOfflineDevicesStmt: q.getOfflineDevicesStmt, + getOpenErrorByDedupKeyStmt: q.getOpenErrorByDedupKeyStmt, + getOrgScopeAssignmentForUserStmt: q.getOrgScopeAssignmentForUserStmt, + getOrganizationByIDStmt: q.getOrganizationByIDStmt, + getOrganizationByNameStmt: q.getOrganizationByNameStmt, + getOrganizationByOrgIDStmt: q.getOrganizationByOrgIDStmt, + getOrganizationPrivateKeyStmt: q.getOrganizationPrivateKeyStmt, + getOrganizationsForUserStmt: q.getOrganizationsForUserStmt, + getPairedDeviceByMACAddressStmt: q.getPairedDeviceByMACAddressStmt, + getPairedDeviceBySerialNumberStmt: q.getPairedDeviceBySerialNumberStmt, + getPairedDevicesByMACAddressesStmt: q.getPairedDevicesByMACAddressesStmt, + getPairedDevicesIdsStmt: q.getPairedDevicesIdsStmt, + getPendingEnrollmentByCodeHashStmt: q.getPendingEnrollmentByCodeHashStmt, + getPendingEnrollmentByFleetNodeStmt: q.getPendingEnrollmentByFleetNodeStmt, + getPermissionByKeyStmt: q.getPermissionByKeyStmt, + getPermissionsByKeysStmt: q.getPermissionsByKeysStmt, + getPoolStmt: q.getPoolStmt, + getRackDetailsForDevicesStmt: q.getRackDetailsForDevicesStmt, + getRackInfoStmt: q.getRackInfoStmt, + getRackInfoBatchStmt: q.getRackInfoBatchStmt, + getRackSlotsStmt: q.getRackSlotsStmt, + getRoleByIDStmt: q.getRoleByIDStmt, + getRoleByIDForUpdateStmt: q.getRoleByIDForUpdateStmt, + getRunningPowerTargetScheduleOverlapsStmt: q.getRunningPowerTargetScheduleOverlapsStmt, + getScheduleStmt: q.getScheduleStmt, + getScheduleByIDForProcessorStmt: q.getScheduleByIDForProcessorStmt, + getScheduleForUpdateStmt: q.getScheduleForUpdateStmt, + getScheduleTargetsStmt: q.getScheduleTargetsStmt, + getScheduleTargetsByScheduleIDsStmt: q.getScheduleTargetsByScheduleIDsStmt, + getSessionByIDStmt: q.getSessionByIDStmt, + getSiteStmt: q.getSiteStmt, + getTotalDevicesPendingAuthStmt: q.getTotalDevicesPendingAuthStmt, + getTotalMinerStateSnapshotsStmt: q.getTotalMinerStateSnapshotsStmt, + getTotalPairedDevicesStmt: q.getTotalPairedDevicesStmt, + getTotalPoolsStmt: q.getTotalPoolsStmt, + getUserByExternalIdStmt: q.getUserByExternalIdStmt, + getUserByIdStmt: q.getUserByIdStmt, + getUserByIdForUpdateStmt: q.getUserByIdForUpdateStmt, + getUserByUsernameStmt: q.getUserByUsernameStmt, + getUserRoleInOrganizationStmt: q.getUserRoleInOrganizationStmt, + getUserRoleNameStmt: q.getUserRoleNameStmt, + getUsersForOrganizationStmt: q.getUsersForOrganizationStmt, + hasUserStmt: q.hasUserStmt, + insertActivityLogStmt: q.insertActivityLogStmt, + insertCurtailmentEventStmt: q.insertCurtailmentEventStmt, + insertDeviceStmt: q.insertDeviceStmt, + insertDeviceMetricsStmt: q.insertDeviceMetricsStmt, + insertErrorStmt: q.insertErrorStmt, + insertMQTTSourceConfigStmt: q.insertMQTTSourceConfigStmt, + insertMinerStateSnapshotStmt: q.insertMinerStateSnapshotStmt, + isBatchFinishedStmt: q.isBatchFinishedStmt, + isBatchProcessingStmt: q.isBatchProcessingStmt, + listActiveCurtailedDevicesByOrgStmt: q.listActiveCurtailedDevicesByOrgStmt, + listActiveCurtailmentEventsStmt: q.listActiveCurtailmentEventsStmt, + listActiveOrganizationIDsStmt: q.listActiveOrganizationIDsStmt, + listActivityLogsStmt: q.listActivityLogsStmt, + listApiKeysByOrganizationStmt: q.listApiKeysByOrganizationStmt, + listAssignmentsForRoleStmt: q.listAssignmentsForRoleStmt, + listAssignmentsForUserStmt: q.listAssignmentsForUserStmt, + listBatchDeviceResultsStmt: q.listBatchDeviceResultsStmt, + listBuildingRacksStmt: q.listBuildingRacksStmt, + listBuildingsByOrgStmt: q.listBuildingsByOrgStmt, + listBuiltinRolesForOrgStmt: q.listBuiltinRolesForOrgStmt, + listCurtailmentCandidatesByOrgStmt: q.listCurtailmentCandidatesByOrgStmt, + listCurtailmentEventsForOrgStmt: q.listCurtailmentEventsForOrgStmt, + listCurtailmentTargetsByEventStmt: q.listCurtailmentTargetsByEventStmt, + listCurtailmentTargetsByEventPageStmt: q.listCurtailmentTargetsByEventPageStmt, + listCustomRolesForOrgStmt: q.listCustomRolesForOrgStmt, + listDeviceSetMembersPaginatedStmt: q.listDeviceSetMembersPaginatedStmt, + listDeviceSetMembersPaginatedAfterStmt: q.listDeviceSetMembersPaginatedAfterStmt, + listEffectivePermissionsForUserStmt: q.listEffectivePermissionsForUserStmt, + listEffectivePermissionsForUserForUpdateStmt: q.listEffectivePermissionsForUserForUpdateStmt, + listEnabledMQTTSourcesStmt: q.listEnabledMQTTSourcesStmt, + listExistingDeviceIdentifiersStmt: q.listExistingDeviceIdentifiersStmt, + listFleetNodeDevicesStmt: q.listFleetNodeDevicesStmt, + listFleetNodeDiscoveredDevicesStmt: q.listFleetNodeDiscoveredDevicesStmt, + listFleetNodesForOrganizationStmt: q.listFleetNodesForOrganizationStmt, + listMQTTSourceConfigsByOrgStmt: q.listMQTTSourceConfigsByOrgStmt, + listMQTTSourceStatesByOrgStmt: q.listMQTTSourceStatesByOrgStmt, + listMinerStateSnapshotsStmt: q.listMinerStateSnapshotsStmt, + listNonTerminalCurtailmentEventsStmt: q.listNonTerminalCurtailmentEventsStmt, + listOrganizationsStmt: q.listOrganizationsStmt, + listPermissionsStmt: q.listPermissionsStmt, + listPoolsStmt: q.listPoolsStmt, + listRackTypesStmt: q.listRackTypesStmt, + listRackZoneRefsStmt: q.listRackZoneRefsStmt, + listRackZonesStmt: q.listRackZonesStmt, + listRacksOutsideBuildingBoundsStmt: q.listRacksOutsideBuildingBoundsStmt, + listRecentlyResolvedCurtailedDevicesByOrgStmt: q.listRecentlyResolvedCurtailedDevicesByOrgStmt, + listRolePermissionKeysStmt: q.listRolePermissionKeysStmt, + listRolesStmt: q.listRolesStmt, + listRolesWithDetailsForOrgStmt: q.listRolesWithDetailsForOrgStmt, + listScheduleIDStatusesStmt: q.listScheduleIDStatusesStmt, + listSchedulesStmt: q.listSchedulesStmt, + listSiteNetworkConfigsForOverlapStmt: q.listSiteNetworkConfigsForOverlapStmt, + listSitesStmt: q.listSitesStmt, + listUsersForOrganizationStmt: q.listUsersForOrganizationStmt, + lockAndCountOrgScopeSuperAdminsStmt: q.lockAndCountOrgScopeSuperAdminsStmt, + lockBuildingForWriteStmt: q.lockBuildingForWriteStmt, + lockBuildingsBySiteForWriteStmt: q.lockBuildingsBySiteForWriteStmt, + lockDevicesForReassignStmt: q.lockDevicesForReassignStmt, + lockFleetNodeByIDStmt: q.lockFleetNodeByIDStmt, + lockRackPlacementForWriteStmt: q.lockRackPlacementForWriteStmt, + lockSchedulePriorityStmt: q.lockSchedulePriorityStmt, + lockSiteForWriteStmt: q.lockSiteForWriteStmt, + markCommandBatchFinishedStmt: q.markCommandBatchFinishedStmt, + markCommandBatchFinishedWithStartedAtStmt: q.markCommandBatchFinishedWithStartedAtStmt, + markCommandBatchProcessingStmt: q.markCommandBatchProcessingStmt, + negateSchedulePrioritiesStmt: q.negateSchedulePrioritiesStmt, + pairDeviceToFleetNodeStmt: q.pairDeviceToFleetNodeStmt, + passwordUpdatedAtStmt: q.passwordUpdatedAtStmt, + pauseActiveScheduleStmt: q.pauseActiveScheduleStmt, + prunePermissionsOutsideKeysStmt: q.prunePermissionsOutsideKeysStmt, + queryComponentKeysWithErrorsStmt: q.queryComponentKeysWithErrorsStmt, + queryDeviceIDsWithErrorsStmt: q.queryDeviceIDsWithErrorsStmt, + queryErrorsStmt: q.queryErrorsStmt, + reapStuckFirmwareUpdateMessagesStmt: q.reapStuckFirmwareUpdateMessagesStmt, + reapStuckProcessingMessagesStmt: q.reapStuckProcessingMessagesStmt, + reassignDevicesUnderBuildingStmt: q.reassignDevicesUnderBuildingStmt, + reassignRacksUnderBuildingStmt: q.reassignRacksUnderBuildingStmt, + removeAllDevicesFromDeviceSetStmt: q.removeAllDevicesFromDeviceSetStmt, + removeDevicesFromAnyRackStmt: q.removeDevicesFromAnyRackStmt, + removeDevicesFromDeviceSetStmt: q.removeDevicesFromDeviceSetStmt, + resetCurtailmentTargetsForRecurtailStmt: q.resetCurtailmentTargetsForRecurtailStmt, + resetCurtailmentTargetsForRestoreStmt: q.resetCurtailmentTargetsForRestoreStmt, + resumeCurtailmentFromRestoringStmt: q.resumeCurtailmentFromRestoringStmt, + resumePausedScheduleStmt: q.resumePausedScheduleStmt, + revertScheduleToActiveStmt: q.revertScheduleToActiveStmt, + revokeAllSessionsByUserIDStmt: q.revokeAllSessionsByUserIDStmt, + revokeApiKeyStmt: q.revokeApiKeyStmt, + revokeApiKeysByFleetNodeIDStmt: q.revokeApiKeysByFleetNodeIDStmt, + revokePermissionFromRoleStmt: q.revokePermissionFromRoleStmt, + revokeSessionStmt: q.revokeSessionStmt, + setFleetNodeEnrollmentStatusStmt: q.setFleetNodeEnrollmentStatusStmt, + setMQTTSourceConfigEnabledStmt: q.setMQTTSourceConfigEnabledStmt, + setRackBuildingPositionStmt: q.setRackBuildingPositionStmt, + setRackSlotPositionStmt: q.setRackSlotPositionStmt, + setSchedulePrioritiesStmt: q.setSchedulePrioritiesStmt, + setScheduleRunningStmt: q.setScheduleRunningStmt, + siteBelongsToOrgStmt: q.siteBelongsToOrgStmt, + softDeleteBuildingStmt: q.softDeleteBuildingStmt, + softDeleteBuildingsBySiteStmt: q.softDeleteBuildingsBySiteStmt, + softDeleteCustomRoleStmt: q.softDeleteCustomRoleStmt, + softDeleteDeviceSetStmt: q.softDeleteDeviceSetStmt, + softDeleteDevicesStmt: q.softDeleteDevicesStmt, + softDeleteDiscoveredDeviceByIdentifierStmt: q.softDeleteDiscoveredDeviceByIdentifierStmt, + softDeleteDiscoveredDevicesForDeletedDevicesStmt: q.softDeleteDiscoveredDevicesForDeletedDevicesStmt, + softDeleteFleetNodeStmt: q.softDeleteFleetNodeStmt, + softDeleteFleetNodesForExpiredEnrollmentsStmt: q.softDeleteFleetNodesForExpiredEnrollmentsStmt, + softDeleteOrganizationStmt: q.softDeleteOrganizationStmt, + softDeletePoolStmt: q.softDeletePoolStmt, + softDeleteRoleStmt: q.softDeleteRoleStmt, + softDeleteScheduleStmt: q.softDeleteScheduleStmt, + softDeleteSiteStmt: q.softDeleteSiteStmt, + softDeleteUserStmt: q.softDeleteUserStmt, + softDeleteUserFromOrganizationStmt: q.softDeleteUserFromOrganizationStmt, + sweepCurtailmentTargetsToRestoreFailedStmt: q.sweepCurtailmentTargetsToRestoreFailedStmt, + sweepExpiredEnrollmentsStmt: q.sweepExpiredEnrollmentsStmt, + sweepExpiredFleetNodeAuthChallengesStmt: q.sweepExpiredFleetNodeAuthChallengesStmt, + sweepExpiredFleetNodeSessionsStmt: q.sweepExpiredFleetNodeSessionsStmt, + transferDiscoveredDeviceAttributionStmt: q.transferDiscoveredDeviceAttributionStmt, + unassignDeviceSitesByRackStmt: q.unassignDeviceSitesByRackStmt, + unassignDevicesFromSiteStmt: q.unassignDevicesFromSiteStmt, + unassignRacksFromBuildingStmt: q.unassignRacksFromBuildingStmt, + unassignRacksFromBuildingsBySiteStmt: q.unassignRacksFromBuildingsBySiteStmt, + unassignRacksFromSiteStmt: q.unassignRacksFromSiteStmt, + unassignRoleStmt: q.unassignRoleStmt, + undeleteOrganizationStmt: q.undeleteOrganizationStmt, + undeleteRoleStmt: q.undeleteRoleStmt, + unpairDeviceStmt: q.unpairDeviceStmt, + updateApiKeyLastUsedStmt: q.updateApiKeyLastUsedStmt, + updateBuildingStmt: q.updateBuildingStmt, + updateCurtailmentEventOperatorFieldsStmt: q.updateCurtailmentEventOperatorFieldsStmt, + updateCurtailmentEventStateStmt: q.updateCurtailmentEventStateStmt, + updateCurtailmentTargetStateStmt: q.updateCurtailmentTargetStateStmt, + updateCustomRoleNameStmt: q.updateCustomRoleNameStmt, + updateDeviceIPAssignmentStmt: q.updateDeviceIPAssignmentStmt, + updateDeviceInfoStmt: q.updateDeviceInfoStmt, + updateDevicePairingStatusByIdentifierStmt: q.updateDevicePairingStatusByIdentifierStmt, + updateDeviceSetDescriptionStmt: q.updateDeviceSetDescriptionStmt, + updateDeviceSetLabelStmt: q.updateDeviceSetLabelStmt, + updateDeviceSetLabelAndDescriptionStmt: q.updateDeviceSetLabelAndDescriptionStmt, + updateDeviceWorkerNameStmt: q.updateDeviceWorkerNameStmt, + updateDeviceWorkerNamePoolSyncStatusByIDStmt: q.updateDeviceWorkerNamePoolSyncStatusByIDStmt, + updateDiscoveredDeviceFirmwareVersionStmt: q.updateDiscoveredDeviceFirmwareVersionStmt, + updateFleetNodeLastSeenAtStmt: q.updateFleetNodeLastSeenAtStmt, + updateLastLoginStmt: q.updateLastLoginStmt, + updateMQTTSourceConfigStmt: q.updateMQTTSourceConfigStmt, + updateMessageAfterFailureStmt: q.updateMessageAfterFailureStmt, + updateMessagePermanentlyFailedStmt: q.updateMessagePermanentlyFailedStmt, + updateMessageStatusStmt: q.updateMessageStatusStmt, + updateMinerPasswordStmt: q.updateMinerPasswordStmt, + updateOpenErrorStmt: q.updateOpenErrorStmt, + updateOrganizationStmt: q.updateOrganizationStmt, + updatePoolStmt: q.updatePoolStmt, + updateRackInfoStmt: q.updateRackInfoStmt, + updateRackPlacementStmt: q.updateRackPlacementStmt, + updateRoleStmt: q.updateRoleStmt, + updateScheduleStmt: q.updateScheduleStmt, + updateScheduleAfterRunStmt: q.updateScheduleAfterRunStmt, + updateSessionActivityStmt: q.updateSessionActivityStmt, + updateSiteStmt: q.updateSiteStmt, + updateUserPasswordStmt: q.updateUserPasswordStmt, + updateUserPasswordAndFlagStmt: q.updateUserPasswordAndFlagStmt, + updateUserRoleStmt: q.updateUserRoleStmt, + updateUserUsernameStmt: q.updateUserUsernameStmt, + upsertBuiltinRoleForOrgStmt: q.upsertBuiltinRoleForOrgStmt, + upsertCommandOnDeviceLogStmt: q.upsertCommandOnDeviceLogStmt, + upsertCurtailmentReconcilerHeartbeatStmt: q.upsertCurtailmentReconcilerHeartbeatStmt, + upsertCustomRoleForOrgStmt: q.upsertCustomRoleForOrgStmt, + upsertDevicePairingStmt: q.upsertDevicePairingStmt, + upsertDeviceStatusStmt: q.upsertDeviceStatusStmt, + upsertDiscoveredDeviceStmt: q.upsertDiscoveredDeviceStmt, + upsertDiscoveredDeviceFromFleetNodeStmt: q.upsertDiscoveredDeviceFromFleetNodeStmt, + upsertFleetNodeAuthChallengeStmt: q.upsertFleetNodeAuthChallengeStmt, + upsertFleetNodeSessionStmt: q.upsertFleetNodeSessionStmt, + upsertMQTTSourceStateStmt: q.upsertMQTTSourceStateStmt, + upsertMinerCredentialsStmt: q.upsertMinerCredentialsStmt, + upsertPermissionStmt: q.upsertPermissionStmt, } } diff --git a/server/generated/sqlc/device_set.sql.go b/server/generated/sqlc/device_set.sql.go index 1e892f773..316633c74 100644 --- a/server/generated/sqlc/device_set.sql.go +++ b/server/generated/sqlc/device_set.sql.go @@ -1164,6 +1164,30 @@ func (q *Queries) RemoveAllDevicesFromDeviceSet(ctx context.Context, arg RemoveA return result.RowsAffected() } +const removeDevicesFromAnyRack = `-- name: RemoveDevicesFromAnyRack :execrows +DELETE FROM device_set_membership +WHERE org_id = $1 + AND device_identifier = ANY($2::text[]) + AND device_set_type = 'rack' +` + +type RemoveDevicesFromAnyRackParams struct { + OrgID int64 + DeviceIdentifiers []string +} + +// Removes the given devices from whatever rack they're currently in. +// AssignDevicesToRack uses this to clear prior rack membership inside +// the same transaction as the new-rack insert, closing the orphan +// window the client-side remove + add orchestration had. +func (q *Queries) RemoveDevicesFromAnyRack(ctx context.Context, arg RemoveDevicesFromAnyRackParams) (int64, error) { + result, err := q.exec(ctx, q.removeDevicesFromAnyRackStmt, removeDevicesFromAnyRack, arg.OrgID, pq.Array(arg.DeviceIdentifiers)) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + const removeDevicesFromDeviceSet = `-- name: RemoveDevicesFromDeviceSet :execrows DELETE FROM device_set_membership WHERE device_set_id = $1 diff --git a/server/generated/sqlc/site.sql.go b/server/generated/sqlc/site.sql.go index 73920b5b7..2fc2faf9a 100644 --- a/server/generated/sqlc/site.sql.go +++ b/server/generated/sqlc/site.sql.go @@ -439,7 +439,7 @@ type LockBuildingForWriteParams struct { } // Row-locks a specific building so concurrent mutations (DeleteSite, -// AssignBuildingToSite, DeleteBuilding) serialize. Returns the building +// AssignBuildingsToSite, DeleteBuilding) serialize. Returns the building // id when alive; sql.ErrNoRows when soft-deleted or missing. func (q *Queries) LockBuildingForWrite(ctx context.Context, arg LockBuildingForWriteParams) (int64, error) { row := q.queryRow(ctx, q.lockBuildingForWriteStmt, lockBuildingForWrite, arg.ID, arg.OrgID) @@ -463,7 +463,7 @@ type LockBuildingsBySiteForWriteParams struct { // Row-locks every live building under the given site so DeleteSite's // cascade can rewrite their racks without a concurrent -// AssignBuildingToSite slipping a building out from under it. Returns +// AssignBuildingsToSite slipping a building out from under it. Returns // the locked ids (result is informational; the FOR UPDATE side-effect // is what matters). func (q *Queries) LockBuildingsBySiteForWrite(ctx context.Context, arg LockBuildingsBySiteForWriteParams) ([]int64, error) { diff --git a/server/internal/domain/collection/service.go b/server/internal/domain/collection/service.go index 2e250d5f1..00076207b 100644 --- a/server/internal/domain/collection/service.go +++ b/server/internal/domain/collection/service.go @@ -885,6 +885,154 @@ func (s *Service) RemoveDevicesFromCollection(ctx context.Context, req *pb.Remov return &pb.RemoveDevicesFromCollectionResponse{RemovedCount: int32(txResult.count)}, nil } +// AssignDevicesToRackParams is the domain-layer input shape for the +// atomic rack reassignment flow. TargetRackID == nil means "clear +// rack membership without re-assigning" (site/building stay intact). +type AssignDevicesToRackParams struct { + OrgID int64 + TargetRackID *int64 + DeviceIdentifiers []string +} + +// AssignDevicesToRackResult carries the per-step row counts the +// activity log + handler response surface. +type AssignDevicesToRackResult struct { + AssignedCount int64 + RemovedCount int64 + SiteReassignedCount int64 +} + +// AssignDevicesToRack atomically moves devices into target_rack_id (or +// clears their rack membership when target is unset) inside one +// transaction. The two-step "remove from prior rack, insert into new +// rack" happens under a single tx so a server error / network blip +// can't leave devices without rack membership (the orphan window the +// client-side orchestration had). Cascades device.site_id when the +// target rack's site differs from the device's current site, matching +// AddDevicesToCollection's cascade. +// +// Empty DeviceIdentifiers rejects with InvalidArgument so the caller +// learns up-front instead of getting a 0-row response. +func (s *Service) AssignDevicesToRack(ctx context.Context, params AssignDevicesToRackParams) (*AssignDevicesToRackResult, error) { + if len(params.DeviceIdentifiers) == 0 { + return nil, fleeterror.NewInvalidArgumentError("device_identifiers must not be empty") + } + + type txOut struct { + assigned int64 + removed int64 + siteReassigned int64 + targetLabel string + } + result, err := s.transactor.RunInTxWithResult(ctx, func(ctx context.Context) (any, error) { + var ( + targetSiteID *int64 + targetLabel string + ) + // Lock + verify target rack first so concurrent SaveRack / + // DeleteCollection on the target can't race us. Canonical lock + // order is rack-only here — site/building locks would invert + // against AddDevicesToCollection. + if params.TargetRackID != nil { + coll, err := s.collectionStore.GetCollection(ctx, params.OrgID, *params.TargetRackID) + if err != nil { + return nil, err + } + if coll.Type != pb.CollectionType_COLLECTION_TYPE_RACK { + return nil, fleeterror.NewInvalidArgumentErrorf("target_rack_id %d is not a rack", *params.TargetRackID) + } + placement, err := s.collectionStore.LockRackPlacementForWrite(ctx, *params.TargetRackID, params.OrgID) + if err != nil { + return nil, err + } + targetSiteID = placement.SiteID + targetLabel = coll.Label + } + + // Clear existing rack membership for the given devices regardless + // of which rack they sit in. This is the half of the operation + // that previously lived in the client-side + // RemoveDevicesFromDeviceSet call; bundling it into the tx is + // what closes the orphan window described in #420. + removed, err := s.collectionStore.RemoveDevicesFromAnyRack(ctx, params.OrgID, params.DeviceIdentifiers) + if err != nil { + return nil, err + } + + var ( + assigned int64 + siteReassigned int64 + ) + if params.TargetRackID != nil { + n, err := s.collectionStore.AddDevicesToCollection(ctx, params.OrgID, *params.TargetRackID, params.DeviceIdentifiers) + if err != nil { + return nil, err + } + assigned = n + if targetSiteID != nil { + c, err := s.collectionStore.CascadeAddedDeviceSites(ctx, params.OrgID, *params.TargetRackID, params.DeviceIdentifiers) + if err != nil { + return nil, err + } + siteReassigned = c + } + } + + return &txOut{ + assigned: assigned, + removed: removed, + siteReassigned: siteReassigned, + targetLabel: targetLabel, + }, nil + }) + if err != nil { + return nil, err + } + out, ok := result.(*txOut) + if !ok { + return nil, fleeterror.NewInternalErrorf("unexpected result type: %T", result) + } + + info, _ := session.GetInfo(ctx) + var ( + userID, username *string + orgIDPtr *int64 + eventDescription, eventScopeStr string + ) + if info != nil { + userID = &info.ExternalUserID + username = &info.Username + orgIDPtr = &info.OrganizationID + } + if params.TargetRackID != nil { + eventScopeStr = "rack" + eventDescription = fmt.Sprintf("Assigned devices to rack: %s", out.targetLabel) + } else { + eventDescription = "Cleared devices from rack" + } + assignedInt := int(out.assigned + out.removed) + event := activitymodels.Event{ + Category: activitymodels.CategoryCollection, + Type: "assign_devices_to_rack", + Description: eventDescription, + ScopeCount: &assignedInt, + UserID: userID, + Username: username, + OrganizationID: orgIDPtr, + } + if eventScopeStr != "" { + event.ScopeType = &eventScopeStr + event.ScopeLabel = &out.targetLabel + } + s.logActivity(ctx, event) + + return &AssignDevicesToRackResult{ + AssignedCount: out.assigned, + RemovedCount: out.removed, + SiteReassignedCount: out.siteReassigned, + }, nil +} + // ListCollectionMembers returns all members of a collection. func (s *Service) ListCollectionMembers(ctx context.Context, req *pb.ListCollectionMembersRequest) (*pb.ListCollectionMembersResponse, error) { info, err := session.GetInfo(ctx) diff --git a/server/internal/domain/collection/service_test.go b/server/internal/domain/collection/service_test.go index 1d505166a..f53d2994c 100644 --- a/server/internal/domain/collection/service_test.go +++ b/server/internal/domain/collection/service_test.go @@ -1902,3 +1902,89 @@ func TestService_AddDevicesToCollection_RackWithoutSiteSkipsCascade(t *testing.T require.NoError(t, err) assert.Equal(t, int32(1), resp.AddedCount) } + +// TestService_AssignDevicesToRack_atomicReassign covers the issue +// #420 atomic-rack-reassign happy path: the prior rack membership is +// cleared and the new membership written inside one transaction, with +// the cascade firing when the target rack has a stamped site. +func TestService_AssignDevicesToRack_atomicReassign(t *testing.T) { + svc, mockStore, _ := newTestServiceWithSites(t, nil) + ctx := testCtx(t) + + targetRackID := int64(42) + rackSite := int64(7) + deviceIDs := []string{"d1", "d2"} + + gomock.InOrder( + mockStore.EXPECT().GetCollection(gomock.Any(), testOrgID, targetRackID). + Return(&pb.DeviceCollection{Id: targetRackID, Label: "Rack-B", Type: pb.CollectionType_COLLECTION_TYPE_RACK}, nil), + mockStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), targetRackID, testOrgID). + Return(interfaces.RackPlacement{SiteID: &rackSite}, nil), + mockStore.EXPECT().RemoveDevicesFromAnyRack(gomock.Any(), testOrgID, deviceIDs).Return(int64(2), nil), + mockStore.EXPECT().AddDevicesToCollection(gomock.Any(), testOrgID, targetRackID, deviceIDs).Return(int64(2), nil), + mockStore.EXPECT().CascadeAddedDeviceSites(gomock.Any(), testOrgID, targetRackID, deviceIDs).Return(int64(1), nil), + ) + + out, err := svc.AssignDevicesToRack(ctx, AssignDevicesToRackParams{ + OrgID: testOrgID, + TargetRackID: &targetRackID, + DeviceIdentifiers: deviceIDs, + }) + require.NoError(t, err) + assert.Equal(t, int64(2), out.AssignedCount) + assert.Equal(t, int64(2), out.RemovedCount) + assert.Equal(t, int64(1), out.SiteReassignedCount) +} + +// TestService_AssignDevicesToRack_unassignClearsWithoutAdd covers the +// target_rack_id-unset branch: removes prior rack membership without +// touching site/building. No GetCollection/Lock/AddDevices call. +func TestService_AssignDevicesToRack_unassignClearsWithoutAdd(t *testing.T) { + svc, mockStore, _ := newTestServiceWithSites(t, nil) + ctx := testCtx(t) + + deviceIDs := []string{"d1"} + mockStore.EXPECT().RemoveDevicesFromAnyRack(gomock.Any(), testOrgID, deviceIDs).Return(int64(1), nil) + + out, err := svc.AssignDevicesToRack(ctx, AssignDevicesToRackParams{ + OrgID: testOrgID, + TargetRackID: nil, + DeviceIdentifiers: deviceIDs, + }) + require.NoError(t, err) + assert.Equal(t, int64(0), out.AssignedCount) + assert.Equal(t, int64(1), out.RemovedCount) + assert.Equal(t, int64(0), out.SiteReassignedCount) +} + +// TestService_AssignDevicesToRack_targetMustBeRack rejects when the +// target collection exists but isn't a rack. +func TestService_AssignDevicesToRack_targetMustBeRack(t *testing.T) { + svc, mockStore, _ := newTestServiceWithSites(t, nil) + ctx := testCtx(t) + + targetID := int64(99) + mockStore.EXPECT().GetCollection(gomock.Any(), testOrgID, targetID). + Return(&pb.DeviceCollection{Id: targetID, Label: "G1", Type: pb.CollectionType_COLLECTION_TYPE_GROUP}, nil) + + _, err := svc.AssignDevicesToRack(ctx, AssignDevicesToRackParams{ + OrgID: testOrgID, + TargetRackID: &targetID, + DeviceIdentifiers: []string{"d1"}, + }) + require.Error(t, err) +} + +// TestService_AssignDevicesToRack_emptyDevicesRejected guards the +// empty-input edge so callers get InvalidArgument instead of a +// silent 0-row response. +func TestService_AssignDevicesToRack_emptyDevicesRejected(t *testing.T) { + svc, _, _ := newTestServiceWithSites(t, nil) + ctx := testCtx(t) + + _, err := svc.AssignDevicesToRack(ctx, AssignDevicesToRackParams{ + OrgID: testOrgID, + DeviceIdentifiers: nil, + }) + require.Error(t, err) +} diff --git a/server/internal/domain/stores/interfaces/collection.go b/server/internal/domain/stores/interfaces/collection.go index 66956afd0..befbb1ccf 100644 --- a/server/internal/domain/stores/interfaces/collection.go +++ b/server/internal/domain/stores/interfaces/collection.go @@ -169,6 +169,15 @@ type CollectionStore interface { // Returns the number of devices actually removed. RemoveDevicesFromCollection(ctx context.Context, orgID int64, collectionID int64, deviceIdentifiers []string) (int64, error) + // RemoveDevicesFromAnyRack deletes the given devices' rack + // membership rows regardless of which rack they currently sit in. + // Used by AssignDevicesToRack to clear prior rack membership inside + // the same transaction as the new-rack insert, closing the orphan + // window the client-side RemoveDevicesFromDeviceSet + + // AddDevicesToDeviceSet orchestration had. No-op for devices + // without a rack membership. + RemoveDevicesFromAnyRack(ctx context.Context, orgID int64, deviceIdentifiers []string) (int64, error) + // ListCollectionMembers returns paginated members of a collection ordered by when they were added (newest first). // Returns the members and a next page token (empty if no more results). ListCollectionMembers(ctx context.Context, orgID int64, collectionID int64, pageSize int32, pageToken string) ([]*pb.CollectionMember, string, error) diff --git a/server/internal/domain/stores/interfaces/mocks/mock_collection_store.go b/server/internal/domain/stores/interfaces/mocks/mock_collection_store.go index a1b35df4f..7db7f6538 100644 --- a/server/internal/domain/stores/interfaces/mocks/mock_collection_store.go +++ b/server/internal/domain/stores/interfaces/mocks/mock_collection_store.go @@ -462,6 +462,21 @@ func (mr *MockCollectionStoreMockRecorder) RemoveAllDevicesFromCollection(ctx, o return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveAllDevicesFromCollection", reflect.TypeOf((*MockCollectionStore)(nil).RemoveAllDevicesFromCollection), ctx, orgID, collectionID) } +// RemoveDevicesFromAnyRack mocks base method. +func (m *MockCollectionStore) RemoveDevicesFromAnyRack(ctx context.Context, orgID int64, deviceIdentifiers []string) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveDevicesFromAnyRack", ctx, orgID, deviceIdentifiers) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RemoveDevicesFromAnyRack indicates an expected call of RemoveDevicesFromAnyRack. +func (mr *MockCollectionStoreMockRecorder) RemoveDevicesFromAnyRack(ctx, orgID, deviceIdentifiers any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveDevicesFromAnyRack", reflect.TypeOf((*MockCollectionStore)(nil).RemoveDevicesFromAnyRack), ctx, orgID, deviceIdentifiers) +} + // RemoveDevicesFromCollection mocks base method. func (m *MockCollectionStore) RemoveDevicesFromCollection(ctx context.Context, orgID, collectionID int64, deviceIdentifiers []string) (int64, error) { m.ctrl.T.Helper() diff --git a/server/internal/domain/stores/sqlstores/collection.go b/server/internal/domain/stores/sqlstores/collection.go index 8e832822c..3a15240b1 100644 --- a/server/internal/domain/stores/sqlstores/collection.go +++ b/server/internal/domain/stores/sqlstores/collection.go @@ -523,6 +523,17 @@ func (s *SQLCollectionStore) RemoveDevicesFromCollection(ctx context.Context, or return count, nil } +func (s *SQLCollectionStore) RemoveDevicesFromAnyRack(ctx context.Context, orgID int64, deviceIdentifiers []string) (int64, error) { + count, err := s.GetQueries(ctx).RemoveDevicesFromAnyRack(ctx, sqlc.RemoveDevicesFromAnyRackParams{ + OrgID: orgID, + DeviceIdentifiers: deviceIdentifiers, + }) + if err != nil { + return 0, fleeterror.NewInternalErrorf("failed to remove devices from rack: %v", err) + } + return count, nil +} + func (s *SQLCollectionStore) ListCollectionMembers(ctx context.Context, orgID int64, collectionID int64, pageSize int32, pageToken string) ([]*pb.CollectionMember, string, error) { cursor, err := decodeMemberCursor(pageToken) if err != nil { diff --git a/server/internal/handlers/deviceset/handler.go b/server/internal/handlers/deviceset/handler.go index a6c92bf47..0af9a4664 100644 --- a/server/internal/handlers/deviceset/handler.go +++ b/server/internal/handlers/deviceset/handler.go @@ -325,3 +325,28 @@ func (h *Handler) SaveRack(ctx context.Context, r *connect.Request[dspb.SaveRack SiteReassignedCount: result.SiteReassignedCount, }), nil } + +func (h *Handler) AssignDevicesToRack(ctx context.Context, r *connect.Request[dspb.AssignDevicesToRackRequest]) (*connect.Response[dspb.AssignDevicesToRackResponse], error) { + info, err := middleware.RequirePermission(ctx, authz.PermRackManage, authz.ResourceContext{}) + if err != nil { + return nil, err + } + var targetRackID *int64 + if r.Msg.TargetRackId != nil { + v := r.Msg.GetTargetRackId() + targetRackID = &v + } + result, err := h.svc.AssignDevicesToRack(ctx, collection.AssignDevicesToRackParams{ + OrgID: info.OrganizationID, + TargetRackID: targetRackID, + DeviceIdentifiers: r.Msg.GetDeviceIdentifiers(), + }) + if err != nil { + return nil, err + } + return connect.NewResponse(&dspb.AssignDevicesToRackResponse{ + AssignedCount: result.AssignedCount, + RemovedCount: result.RemovedCount, + SiteReassignedCount: result.SiteReassignedCount, + }), nil +} diff --git a/server/internal/handlers/middleware/rpc_permissions.go b/server/internal/handlers/middleware/rpc_permissions.go index feea19cab..98eb4478b 100644 --- a/server/internal/handlers/middleware/rpc_permissions.go +++ b/server/internal/handlers/middleware/rpc_permissions.go @@ -176,6 +176,7 @@ var ProcedurePermissions = map[string]string{ device_setv1connect.DeviceSetServiceAddDevicesToDeviceSetProcedure: authz.PermRackManage, device_setv1connect.DeviceSetServiceRemoveDevicesFromDeviceSetProcedure: authz.PermRackManage, device_setv1connect.DeviceSetServiceSaveRackProcedure: authz.PermRackManage, + device_setv1connect.DeviceSetServiceAssignDevicesToRackProcedure: authz.PermRackManage, device_setv1connect.DeviceSetServiceSetRackSlotPositionProcedure: authz.PermRackManage, device_setv1connect.DeviceSetServiceClearRackSlotPositionProcedure: authz.PermRackManage, diff --git a/server/sqlc/queries/device_set.sql b/server/sqlc/queries/device_set.sql index 3378b7b6a..15c587604 100644 --- a/server/sqlc/queries/device_set.sql +++ b/server/sqlc/queries/device_set.sql @@ -211,6 +211,16 @@ DELETE FROM device_set_membership WHERE device_set_id = $1 AND org_id = $2; +-- name: RemoveDevicesFromAnyRack :execrows +-- Removes the given devices from whatever rack they're currently in. +-- AssignDevicesToRack uses this to clear prior rack membership inside +-- the same transaction as the new-rack insert, closing the orphan +-- window the client-side remove + add orchestration had. +DELETE FROM device_set_membership +WHERE org_id = $1 + AND device_identifier = ANY(@device_identifiers::text[]) + AND device_set_type = 'rack'; + -- name: RemoveDevicesFromDeviceSet :execrows DELETE FROM device_set_membership WHERE device_set_id = $1 From cc4119d39bc5591fbd4413aeab5ca0bc238198b2 Mon Sep 17 00:00:00 2001 From: flesher Date: Wed, 10 Jun 2026 17:14:16 -0500 Subject: [PATCH 5/9] feat(sites): AssignRacksToSite partial-update RPC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the missing rack→site reparent RPC called out in #420. SaveRack was a full-replace requiring the rack's label, layout, members, and slot assignments to round-trip on every site change; the new RPC is a partial update — only site_id changes. Behavior: - target_site_id unset → racks move to "Unassigned" - building_id is auto-cleared on any site transition (a building belongs to one site, so the move invalidates building membership); the response carries the count of racks whose building was cleared so the UI can prompt the operator to pick a building in the new site - same transaction cascades device.site_id for every rack member - lock order: target site → each rack id ascending (matches AssignBuildingsToSite + AssignDevicesToSite) - bulk-friendly: pass repeated rack_ids; the batch rolls back on any per-rack failure Plumbs collectionStore through sites.Service so the new method can reuse LockRackPlacementForWrite + UpdateRackPlacement + CascadeRackDeviceSites instead of duplicating the rack-placement write path. Server: 4 handler tests (cascade + clear building, target-unset unassign, same-site no-op, bulk aggregation with sorted lock order), 2 service tests (empty rejection, nil-store guard). Client: useSites().assignRacksToSite wrapper. Refs #420. --- .../api/generated/sites/v1/sites_pb.ts | 174 +++++--- client/src/protoFleet/api/sites.ts | 50 ++- proto/sites/v1/sites.proto | 34 ++ server/cmd/fleetd/main.go | 2 +- server/generated/grpc/sites/v1/sites.pb.go | 388 ++++++++++++------ .../sites/v1/sitesv1connect/sites.connect.go | 43 ++ server/internal/domain/sites/models/models.go | 16 + server/internal/domain/sites/service.go | 146 ++++++- .../domain/sites/service_stats_test.go | 12 +- server/internal/domain/sites/service_test.go | 64 ++- .../handlers/middleware/rpc_permissions.go | 1 + server/internal/handlers/sites/handler.go | 25 ++ .../handlers/sites/handler_stats_test.go | 2 +- .../internal/handlers/sites/handler_test.go | 120 +++++- 14 files changed, 852 insertions(+), 225 deletions(-) diff --git a/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts b/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts index 62eb67f37..8b793189b 100644 --- a/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts +++ b/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.12.0 with parameter "target=ts" +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" // @generated from file sites/v1/sites.proto (package sites.v1, syntax proto3) /* eslint-disable */ @@ -12,12 +12,8 @@ import type { Message } from "@bufbuild/protobuf"; /** * Describes the file sites/v1/sites.proto. */ -export const file_sites_v1_sites: GenFile = - /*@__PURE__*/ - fileDesc( - "ChRzaXRlcy92MS9zaXRlcy5wcm90bxIIc2l0ZXMudjEi4gIKBFNpdGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIVCg1sb2NhdGlvbl9jaXR5GAQgASgJEhYKDmxvY2F0aW9uX3N0YXRlGAUgASgJEhAKCHRpbWV6b25lGAYgASgJEhkKEXBvd2VyX2NhcGFjaXR5X213GAcgASgBEhYKDm5ldHdvcmtfY29uZmlnGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB2FkZHJlc3MYCyABKAkSEwoLcG9zdGFsX2NvZGUYDSABKAkSDwoHY291bnRyeRgOIAEoCRINCgVub3RlcxgPIAEoCUoECAMQBEoECAwQDVILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIicAoOU2l0ZVdpdGhDb3VudHMSHAoEc2l0ZRgBIAEoCzIOLnNpdGVzLnYxLlNpdGUSFAoMZGV2aWNlX2NvdW50GAIgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAMgASgDEhIKCnJhY2tfY291bnQYBCABKAMiEgoQTGlzdFNpdGVzUmVxdWVzdCI8ChFMaXN0U2l0ZXNSZXNwb25zZRInCgVzaXRlcxgBIAMoCzIYLnNpdGVzLnYxLlNpdGVXaXRoQ291bnRzIu0CChFDcmVhdGVTaXRlUmVxdWVzdBIYCgRuYW1lGAEgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYAyABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAQgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgFIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgGIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYByABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAggASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgKIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAsgASgJQge6SARyAhgCEhcKBW5vdGVzGAwgASgJQgi6SAVyAxiAIEoECAIQA0oECAkQClILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSQ3JlYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIoIDChFVcGRhdGVTaXRlUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYBCABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAUgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgGIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgHIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYCCABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAkgASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgLIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAwgASgJQge6SARyAhgCEhcKBW5vdGVzGA0gASgJQgi6SAVyAxiAIEoECAMQBEoECAoQC1ILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSVXBkYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIigKEURlbGV0ZVNpdGVSZXF1ZXN0EhMKAmlkGAEgASgDQge6SAQiAiAAInQKEkRlbGV0ZVNpdGVSZXNwb25zZRIfChd1bmFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZkZWxldGVkX2J1aWxkaW5nX2NvdW50GAIgASgDEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgDIAEoAyKHAQoaQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QSJAoOdGFyZ2V0X3NpdGVfaWQYASABKANCB7pIBCICIABIAIgBARIwChJkZXZpY2VfaWRlbnRpZmllcnMYAiADKAlCFLpIEZIBDggBEJBOIgdyBRABGIACQhEKD190YXJnZXRfc2l0ZV9pZCJ+ChFQZXJEZXZpY2VDb25mbGljdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCgZyZWFzb24YAiABKA4yIS5zaXRlcy52MS5QZXJEZXZpY2VDb25mbGljdFJlYXNvbhIbChNjb25mbGljdGluZ19zaXRlX2lkGAMgASgDImcKG0Fzc2lnbkRldmljZXNUb1NpdGVSZXNwb25zZRIYChByZWFzc2lnbmVkX2NvdW50GAEgASgDEi4KCWNvbmZsaWN0cxgCIAMoCzIbLnNpdGVzLnYxLlBlckRldmljZUNvbmZsaWN0IoABChxBc3NpZ25CdWlsZGluZ3NUb1NpdGVSZXF1ZXN0EicKDGJ1aWxkaW5nX2lkcxgBIAMoA0IRukgOkgELCAEQ6AciBCICIAASJAoOdGFyZ2V0X3NpdGVfaWQYAiABKANCB7pIBCICIABIAIgBAUIRCg9fdGFyZ2V0X3NpdGVfaWQiXwodQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVzcG9uc2USHQoVcmVhc3NpZ25lZF9yYWNrX2NvdW50GAEgASgDEh8KF3JlYXNzaWduZWRfZGV2aWNlX2NvdW50GAIgASgDIi8KE0dldFNpdGVTdGF0c1JlcXVlc3QSGAoHc2l0ZV9pZBgBIAEoA0IHukgEIgIgACL/AgoUR2V0U2l0ZVN0YXRzUmVzcG9uc2USDwoHc2l0ZV9pZBgBIAEoAxIWCg5idWlsZGluZ19jb3VudBgCIAEoBRIUCgxkZXZpY2VfY291bnQYAyABKAUSFwoPcmVwb3J0aW5nX2NvdW50GAQgASgFEhoKEnRvdGFsX2hhc2hyYXRlX3RocxgFIAEoARIaChJhdmdfZWZmaWNpZW5jeV9qdGgYBiABKAESFgoOdG90YWxfcG93ZXJfa3cYByABKAESFQoNaGFzaGluZ19jb3VudBgIIAEoBRIUCgxicm9rZW5fY291bnQYCSABKAUSFQoNb2ZmbGluZV9jb3VudBgKIAEoBRIWCg5zbGVlcGluZ19jb3VudBgLIAEoBRIgChhoYXNocmF0ZV9yZXBvcnRpbmdfY291bnQYDCABKAUSIgoaZWZmaWNpZW5jeV9yZXBvcnRpbmdfY291bnQYDSABKAUSHQoVcG93ZXJfcmVwb3J0aW5nX2NvdW50GA4gASgFKrMBChdQZXJEZXZpY2VDb25mbGljdFJlYXNvbhIqCiZQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9VTlNQRUNJRklFRBAAEi8KK1BFUl9ERVZJQ0VfQ09ORkxJQ1RfUkVBU09OX0RFVklDRV9OT1RfRk9VTkQQARI7CjdQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9ERVZJQ0VfSU5fUkFDS19BVF9PVEhFUl9TSVRFEAIyywQKC1NpdGVTZXJ2aWNlEkQKCUxpc3RTaXRlcxIaLnNpdGVzLnYxLkxpc3RTaXRlc1JlcXVlc3QaGy5zaXRlcy52MS5MaXN0U2l0ZXNSZXNwb25zZRJHCgpDcmVhdGVTaXRlEhsuc2l0ZXMudjEuQ3JlYXRlU2l0ZVJlcXVlc3QaHC5zaXRlcy52MS5DcmVhdGVTaXRlUmVzcG9uc2USRwoKVXBkYXRlU2l0ZRIbLnNpdGVzLnYxLlVwZGF0ZVNpdGVSZXF1ZXN0Ghwuc2l0ZXMudjEuVXBkYXRlU2l0ZVJlc3BvbnNlEkcKCkRlbGV0ZVNpdGUSGy5zaXRlcy52MS5EZWxldGVTaXRlUmVxdWVzdBocLnNpdGVzLnYxLkRlbGV0ZVNpdGVSZXNwb25zZRJiChNBc3NpZ25EZXZpY2VzVG9TaXRlEiQuc2l0ZXMudjEuQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QaJS5zaXRlcy52MS5Bc3NpZ25EZXZpY2VzVG9TaXRlUmVzcG9uc2USaAoVQXNzaWduQnVpbGRpbmdzVG9TaXRlEiYuc2l0ZXMudjEuQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVxdWVzdBonLnNpdGVzLnYxLkFzc2lnbkJ1aWxkaW5nc1RvU2l0ZVJlc3BvbnNlEk0KDEdldFNpdGVTdGF0cxIdLnNpdGVzLnYxLkdldFNpdGVTdGF0c1JlcXVlc3QaHi5zaXRlcy52MS5HZXRTaXRlU3RhdHNSZXNwb25zZUKgAQoMY29tLnNpdGVzLnYxQgpTaXRlc1Byb3RvUAFaQ2dpdGh1Yi5jb20vYmxvY2svcHJvdG8tZmxlZXQvc2VydmVyL2dlbmVyYXRlZC9ncnBjL3NpdGVzL3YxO3NpdGVzdjGiAgNTWFiqAghTaXRlcy5WMcoCCFNpdGVzXFYx4gIUU2l0ZXNcVjFcR1BCTWV0YWRhdGHqAglTaXRlczo6VjFiBnByb3RvMw", - [file_buf_validate_validate, file_google_protobuf_timestamp], - ); +export const file_sites_v1_sites: GenFile = /*@__PURE__*/ + fileDesc("ChRzaXRlcy92MS9zaXRlcy5wcm90bxIIc2l0ZXMudjEi4gIKBFNpdGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIVCg1sb2NhdGlvbl9jaXR5GAQgASgJEhYKDmxvY2F0aW9uX3N0YXRlGAUgASgJEhAKCHRpbWV6b25lGAYgASgJEhkKEXBvd2VyX2NhcGFjaXR5X213GAcgASgBEhYKDm5ldHdvcmtfY29uZmlnGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB2FkZHJlc3MYCyABKAkSEwoLcG9zdGFsX2NvZGUYDSABKAkSDwoHY291bnRyeRgOIAEoCRINCgVub3RlcxgPIAEoCUoECAMQBEoECAwQDVILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIicAoOU2l0ZVdpdGhDb3VudHMSHAoEc2l0ZRgBIAEoCzIOLnNpdGVzLnYxLlNpdGUSFAoMZGV2aWNlX2NvdW50GAIgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAMgASgDEhIKCnJhY2tfY291bnQYBCABKAMiEgoQTGlzdFNpdGVzUmVxdWVzdCI8ChFMaXN0U2l0ZXNSZXNwb25zZRInCgVzaXRlcxgBIAMoCzIYLnNpdGVzLnYxLlNpdGVXaXRoQ291bnRzIu0CChFDcmVhdGVTaXRlUmVxdWVzdBIYCgRuYW1lGAEgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYAyABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAQgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgFIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgGIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYByABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAggASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgKIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAsgASgJQge6SARyAhgCEhcKBW5vdGVzGAwgASgJQgi6SAVyAxiAIEoECAIQA0oECAkQClILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSQ3JlYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIoIDChFVcGRhdGVTaXRlUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYBCABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAUgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgGIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgHIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYCCABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAkgASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgLIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAwgASgJQge6SARyAhgCEhcKBW5vdGVzGA0gASgJQgi6SAVyAxiAIEoECAMQBEoECAoQC1ILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSVXBkYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIigKEURlbGV0ZVNpdGVSZXF1ZXN0EhMKAmlkGAEgASgDQge6SAQiAiAAInQKEkRlbGV0ZVNpdGVSZXNwb25zZRIfChd1bmFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZkZWxldGVkX2J1aWxkaW5nX2NvdW50GAIgASgDEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgDIAEoAyKHAQoaQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QSJAoOdGFyZ2V0X3NpdGVfaWQYASABKANCB7pIBCICIABIAIgBARIwChJkZXZpY2VfaWRlbnRpZmllcnMYAiADKAlCFLpIEZIBDggBEJBOIgdyBRABGIACQhEKD190YXJnZXRfc2l0ZV9pZCJ+ChFQZXJEZXZpY2VDb25mbGljdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCgZyZWFzb24YAiABKA4yIS5zaXRlcy52MS5QZXJEZXZpY2VDb25mbGljdFJlYXNvbhIbChNjb25mbGljdGluZ19zaXRlX2lkGAMgASgDImcKG0Fzc2lnbkRldmljZXNUb1NpdGVSZXNwb25zZRIYChByZWFzc2lnbmVkX2NvdW50GAEgASgDEi4KCWNvbmZsaWN0cxgCIAMoCzIbLnNpdGVzLnYxLlBlckRldmljZUNvbmZsaWN0IoABChxBc3NpZ25CdWlsZGluZ3NUb1NpdGVSZXF1ZXN0EicKDGJ1aWxkaW5nX2lkcxgBIAMoA0IRukgOkgELCAEQ6AciBCICIAASJAoOdGFyZ2V0X3NpdGVfaWQYAiABKANCB7pIBCICIABIAIgBAUIRCg9fdGFyZ2V0X3NpdGVfaWQiXwodQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVzcG9uc2USHQoVcmVhc3NpZ25lZF9yYWNrX2NvdW50GAEgASgDEh8KF3JlYXNzaWduZWRfZGV2aWNlX2NvdW50GAIgASgDIngKGEFzc2lnblJhY2tzVG9TaXRlUmVxdWVzdBIjCghyYWNrX2lkcxgBIAMoA0IRukgOkgELCAEQ6AciBCICIAASJAoOdGFyZ2V0X3NpdGVfaWQYAiABKANCB7pIBCICIABIAIgBAUIRCg9fdGFyZ2V0X3NpdGVfaWQiXAoZQXNzaWduUmFja3NUb1NpdGVSZXNwb25zZRIfChdyZWFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZjbGVhcmVkX2J1aWxkaW5nX2NvdW50GAIgASgDIi8KE0dldFNpdGVTdGF0c1JlcXVlc3QSGAoHc2l0ZV9pZBgBIAEoA0IHukgEIgIgACL/AgoUR2V0U2l0ZVN0YXRzUmVzcG9uc2USDwoHc2l0ZV9pZBgBIAEoAxIWCg5idWlsZGluZ19jb3VudBgCIAEoBRIUCgxkZXZpY2VfY291bnQYAyABKAUSFwoPcmVwb3J0aW5nX2NvdW50GAQgASgFEhoKEnRvdGFsX2hhc2hyYXRlX3RocxgFIAEoARIaChJhdmdfZWZmaWNpZW5jeV9qdGgYBiABKAESFgoOdG90YWxfcG93ZXJfa3cYByABKAESFQoNaGFzaGluZ19jb3VudBgIIAEoBRIUCgxicm9rZW5fY291bnQYCSABKAUSFQoNb2ZmbGluZV9jb3VudBgKIAEoBRIWCg5zbGVlcGluZ19jb3VudBgLIAEoBRIgChhoYXNocmF0ZV9yZXBvcnRpbmdfY291bnQYDCABKAUSIgoaZWZmaWNpZW5jeV9yZXBvcnRpbmdfY291bnQYDSABKAUSHQoVcG93ZXJfcmVwb3J0aW5nX2NvdW50GA4gASgFKrMBChdQZXJEZXZpY2VDb25mbGljdFJlYXNvbhIqCiZQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9VTlNQRUNJRklFRBAAEi8KK1BFUl9ERVZJQ0VfQ09ORkxJQ1RfUkVBU09OX0RFVklDRV9OT1RfRk9VTkQQARI7CjdQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9ERVZJQ0VfSU5fUkFDS19BVF9PVEhFUl9TSVRFEAIyqQUKC1NpdGVTZXJ2aWNlEkQKCUxpc3RTaXRlcxIaLnNpdGVzLnYxLkxpc3RTaXRlc1JlcXVlc3QaGy5zaXRlcy52MS5MaXN0U2l0ZXNSZXNwb25zZRJHCgpDcmVhdGVTaXRlEhsuc2l0ZXMudjEuQ3JlYXRlU2l0ZVJlcXVlc3QaHC5zaXRlcy52MS5DcmVhdGVTaXRlUmVzcG9uc2USRwoKVXBkYXRlU2l0ZRIbLnNpdGVzLnYxLlVwZGF0ZVNpdGVSZXF1ZXN0Ghwuc2l0ZXMudjEuVXBkYXRlU2l0ZVJlc3BvbnNlEkcKCkRlbGV0ZVNpdGUSGy5zaXRlcy52MS5EZWxldGVTaXRlUmVxdWVzdBocLnNpdGVzLnYxLkRlbGV0ZVNpdGVSZXNwb25zZRJiChNBc3NpZ25EZXZpY2VzVG9TaXRlEiQuc2l0ZXMudjEuQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QaJS5zaXRlcy52MS5Bc3NpZ25EZXZpY2VzVG9TaXRlUmVzcG9uc2USaAoVQXNzaWduQnVpbGRpbmdzVG9TaXRlEiYuc2l0ZXMudjEuQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVxdWVzdBonLnNpdGVzLnYxLkFzc2lnbkJ1aWxkaW5nc1RvU2l0ZVJlc3BvbnNlElwKEUFzc2lnblJhY2tzVG9TaXRlEiIuc2l0ZXMudjEuQXNzaWduUmFja3NUb1NpdGVSZXF1ZXN0GiMuc2l0ZXMudjEuQXNzaWduUmFja3NUb1NpdGVSZXNwb25zZRJNCgxHZXRTaXRlU3RhdHMSHS5zaXRlcy52MS5HZXRTaXRlU3RhdHNSZXF1ZXN0Gh4uc2l0ZXMudjEuR2V0U2l0ZVN0YXRzUmVzcG9uc2VCoAEKDGNvbS5zaXRlcy52MUIKU2l0ZXNQcm90b1ABWkNnaXRodWIuY29tL2Jsb2NrL3Byb3RvLWZsZWV0L3NlcnZlci9nZW5lcmF0ZWQvZ3JwYy9zaXRlcy92MTtzaXRlc3YxogIDU1hYqgIIU2l0ZXMuVjHKAghTaXRlc1xWMeICFFNpdGVzXFYxXEdQQk1ldGFkYXRh6gIJU2l0ZXM6OlYxYgZwcm90bzM", [file_buf_validate_validate, file_google_protobuf_timestamp]); /** * Site is the read shape returned by Get/List/Create/Update RPCs. @@ -74,12 +70,12 @@ export type Site = Message<"sites.v1.Site"> & { /** * @generated from field: google.protobuf.Timestamp created_at = 9; */ - createdAt?: Timestamp | undefined; + createdAt?: Timestamp; /** * @generated from field: google.protobuf.Timestamp updated_at = 10; */ - updatedAt?: Timestamp | undefined; + updatedAt?: Timestamp; /** * @generated from field: string address = 11; @@ -112,7 +108,8 @@ export type Site = Message<"sites.v1.Site"> & { * Describes the message sites.v1.Site. * Use `create(SiteSchema)` to create a new message. */ -export const SiteSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 0); +export const SiteSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sites_v1_sites, 0); /** * SiteWithCounts pairs a Site with its attachment counts. Returned by @@ -124,7 +121,7 @@ export type SiteWithCounts = Message<"sites.v1.SiteWithCounts"> & { /** * @generated from field: sites.v1.Site site = 1; */ - site?: Site | undefined; + site?: Site; /** * @generated from field: int64 device_count = 2; @@ -146,18 +143,21 @@ export type SiteWithCounts = Message<"sites.v1.SiteWithCounts"> & { * Describes the message sites.v1.SiteWithCounts. * Use `create(SiteWithCountsSchema)` to create a new message. */ -export const SiteWithCountsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 1); +export const SiteWithCountsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sites_v1_sites, 1); /** * @generated from message sites.v1.ListSitesRequest */ -export type ListSitesRequest = Message<"sites.v1.ListSitesRequest"> & {}; +export type ListSitesRequest = Message<"sites.v1.ListSitesRequest"> & { +}; /** * Describes the message sites.v1.ListSitesRequest. * Use `create(ListSitesRequestSchema)` to create a new message. */ -export const ListSitesRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 2); +export const ListSitesRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sites_v1_sites, 2); /** * @generated from message sites.v1.ListSitesResponse @@ -173,7 +173,8 @@ export type ListSitesResponse = Message<"sites.v1.ListSitesResponse"> & { * Describes the message sites.v1.ListSitesResponse. * Use `create(ListSitesResponseSchema)` to create a new message. */ -export const ListSitesResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 3); +export const ListSitesResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sites_v1_sites, 3); /** * @generated from message sites.v1.CreateSiteRequest @@ -249,7 +250,8 @@ export type CreateSiteRequest = Message<"sites.v1.CreateSiteRequest"> & { * Describes the message sites.v1.CreateSiteRequest. * Use `create(CreateSiteRequestSchema)` to create a new message. */ -export const CreateSiteRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 4); +export const CreateSiteRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sites_v1_sites, 4); /** * @generated from message sites.v1.CreateSiteResponse @@ -258,7 +260,7 @@ export type CreateSiteResponse = Message<"sites.v1.CreateSiteResponse"> & { /** * @generated from field: sites.v1.Site site = 1; */ - site?: Site | undefined; + site?: Site; /** * network_config_warnings carries cross-site overlap warnings or @@ -274,8 +276,7 @@ export type CreateSiteResponse = Message<"sites.v1.CreateSiteResponse"> & { * Describes the message sites.v1.CreateSiteResponse. * Use `create(CreateSiteResponseSchema)` to create a new message. */ -export const CreateSiteResponseSchema: GenMessage = - /*@__PURE__*/ +export const CreateSiteResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 5); /** @@ -342,7 +343,8 @@ export type UpdateSiteRequest = Message<"sites.v1.UpdateSiteRequest"> & { * Describes the message sites.v1.UpdateSiteRequest. * Use `create(UpdateSiteRequestSchema)` to create a new message. */ -export const UpdateSiteRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 6); +export const UpdateSiteRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sites_v1_sites, 6); /** * @generated from message sites.v1.UpdateSiteResponse @@ -351,7 +353,7 @@ export type UpdateSiteResponse = Message<"sites.v1.UpdateSiteResponse"> & { /** * @generated from field: sites.v1.Site site = 1; */ - site?: Site | undefined; + site?: Site; /** * @generated from field: repeated string network_config_warnings = 2; @@ -363,8 +365,7 @@ export type UpdateSiteResponse = Message<"sites.v1.UpdateSiteResponse"> & { * Describes the message sites.v1.UpdateSiteResponse. * Use `create(UpdateSiteResponseSchema)` to create a new message. */ -export const UpdateSiteResponseSchema: GenMessage = - /*@__PURE__*/ +export const UpdateSiteResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 7); /** @@ -381,7 +382,8 @@ export type DeleteSiteRequest = Message<"sites.v1.DeleteSiteRequest"> & { * Describes the message sites.v1.DeleteSiteRequest. * Use `create(DeleteSiteRequestSchema)` to create a new message. */ -export const DeleteSiteRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 8); +export const DeleteSiteRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sites_v1_sites, 8); /** * @generated from message sites.v1.DeleteSiteResponse @@ -409,8 +411,7 @@ export type DeleteSiteResponse = Message<"sites.v1.DeleteSiteResponse"> & { * Describes the message sites.v1.DeleteSiteResponse. * Use `create(DeleteSiteResponseSchema)` to create a new message. */ -export const DeleteSiteResponseSchema: GenMessage = - /*@__PURE__*/ +export const DeleteSiteResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 9); /** @@ -422,7 +423,7 @@ export type AssignDevicesToSiteRequest = Message<"sites.v1.AssignDevicesToSiteRe * * @generated from field: optional int64 target_site_id = 1; */ - targetSiteId?: bigint | undefined; + targetSiteId?: bigint; /** * @generated from field: repeated string device_identifiers = 2; @@ -434,8 +435,7 @@ export type AssignDevicesToSiteRequest = Message<"sites.v1.AssignDevicesToSiteRe * Describes the message sites.v1.AssignDevicesToSiteRequest. * Use `create(AssignDevicesToSiteRequestSchema)` to create a new message. */ -export const AssignDevicesToSiteRequestSchema: GenMessage = - /*@__PURE__*/ +export const AssignDevicesToSiteRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 10); /** @@ -469,8 +469,7 @@ export type PerDeviceConflict = Message<"sites.v1.PerDeviceConflict"> & { * Describes the message sites.v1.PerDeviceConflict. * Use `create(PerDeviceConflictSchema)` to create a new message. */ -export const PerDeviceConflictSchema: GenMessage = - /*@__PURE__*/ +export const PerDeviceConflictSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 11); /** @@ -494,8 +493,7 @@ export type AssignDevicesToSiteResponse = Message<"sites.v1.AssignDevicesToSiteR * Describes the message sites.v1.AssignDevicesToSiteResponse. * Use `create(AssignDevicesToSiteResponseSchema)` to create a new message. */ -export const AssignDevicesToSiteResponseSchema: GenMessage = - /*@__PURE__*/ +export const AssignDevicesToSiteResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 12); /** @@ -512,15 +510,14 @@ export type AssignBuildingsToSiteRequest = Message<"sites.v1.AssignBuildingsToSi * * @generated from field: optional int64 target_site_id = 2; */ - targetSiteId?: bigint | undefined; + targetSiteId?: bigint; }; /** * Describes the message sites.v1.AssignBuildingsToSiteRequest. * Use `create(AssignBuildingsToSiteRequestSchema)` to create a new message. */ -export const AssignBuildingsToSiteRequestSchema: GenMessage = - /*@__PURE__*/ +export const AssignBuildingsToSiteRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 13); /** @@ -546,10 +543,63 @@ export type AssignBuildingsToSiteResponse = Message<"sites.v1.AssignBuildingsToS * Describes the message sites.v1.AssignBuildingsToSiteResponse. * Use `create(AssignBuildingsToSiteResponseSchema)` to create a new message. */ -export const AssignBuildingsToSiteResponseSchema: GenMessage = - /*@__PURE__*/ +export const AssignBuildingsToSiteResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 14); +/** + * @generated from message sites.v1.AssignRacksToSiteRequest + */ +export type AssignRacksToSiteRequest = Message<"sites.v1.AssignRacksToSiteRequest"> & { + /** + * @generated from field: repeated int64 rack_ids = 1; + */ + rackIds: bigint[]; + + /** + * Unset = move racks to "Unassigned" (site_id NULL). When present, + * must be > 0. + * + * @generated from field: optional int64 target_site_id = 2; + */ + targetSiteId?: bigint; +}; + +/** + * Describes the message sites.v1.AssignRacksToSiteRequest. + * Use `create(AssignRacksToSiteRequestSchema)` to create a new message. + */ +export const AssignRacksToSiteRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sites_v1_sites, 15); + +/** + * @generated from message sites.v1.AssignRacksToSiteResponse + */ +export type AssignRacksToSiteResponse = Message<"sites.v1.AssignRacksToSiteResponse"> & { + /** + * Cascade impact: how many devices had their site_id re-stamped + * across every rack in the batch. + * + * @generated from field: int64 reassigned_device_count = 1; + */ + reassignedDeviceCount: bigint; + + /** + * Number of racks whose building_id was cleared because the move + * crossed site boundaries. UI can use this to prompt for + * re-assignment to a building in the new site. + * + * @generated from field: int64 cleared_building_count = 2; + */ + clearedBuildingCount: bigint; +}; + +/** + * Describes the message sites.v1.AssignRacksToSiteResponse. + * Use `create(AssignRacksToSiteResponseSchema)` to create a new message. + */ +export const AssignRacksToSiteResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sites_v1_sites, 16); + /** * @generated from message sites.v1.GetSiteStatsRequest */ @@ -564,9 +614,8 @@ export type GetSiteStatsRequest = Message<"sites.v1.GetSiteStatsRequest"> & { * Describes the message sites.v1.GetSiteStatsRequest. * Use `create(GetSiteStatsRequestSchema)` to create a new message. */ -export const GetSiteStatsRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_sites_v1_sites, 15); +export const GetSiteStatsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sites_v1_sites, 17); /** * GetSiteStatsResponse mirrors device_set.v1.DeviceSetStats for the @@ -660,9 +709,8 @@ export type GetSiteStatsResponse = Message<"sites.v1.GetSiteStatsResponse"> & { * Describes the message sites.v1.GetSiteStatsResponse. * Use `create(GetSiteStatsResponseSchema)` to create a new message. */ -export const GetSiteStatsResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_sites_v1_sites, 16); +export const GetSiteStatsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_sites_v1_sites, 18); /** * PerDeviceConflictReason enumerates the reasons a device can be @@ -690,8 +738,7 @@ export enum PerDeviceConflictReason { /** * Describes the enum sites.v1.PerDeviceConflictReason. */ -export const PerDeviceConflictReasonSchema: GenEnum = - /*@__PURE__*/ +export const PerDeviceConflictReasonSchema: GenEnum = /*@__PURE__*/ enumDesc(file_sites_v1_sites, 0); /** @@ -714,7 +761,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof ListSitesRequestSchema; output: typeof ListSitesResponseSchema; - }; + }, /** * CreateSite inserts a new site. Name must be unique within the * org. network_config is parsed and canonicalized server-side; @@ -726,7 +773,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof CreateSiteRequestSchema; output: typeof CreateSiteResponseSchema; - }; + }, /** * UpdateSite mutates name + descriptive fields + network_config. * Same canonicalization + validation as CreateSite. @@ -737,7 +784,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof UpdateSiteRequestSchema; output: typeof UpdateSiteResponseSchema; - }; + }, /** * DeleteSite soft-deletes the site and, in the same transaction, * cascades to its buildings (soft-delete) and racks (unassign). @@ -750,7 +797,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof DeleteSiteRequestSchema; output: typeof DeleteSiteResponseSchema; - }; + }, /** * AssignDevicesToSite is an all-or-nothing bulk update of * device.site_id. If any device is in a rack whose site_id differs @@ -764,7 +811,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof AssignDevicesToSiteRequestSchema; output: typeof AssignDevicesToSiteResponseSchema; - }; + }, /** * AssignBuildingsToSite moves one or more buildings to a target site * (or to "Unassigned" when target_site_id is unset). All updates run @@ -779,7 +826,24 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof AssignBuildingsToSiteRequestSchema; output: typeof AssignBuildingsToSiteResponseSchema; - }; + }, + /** + * AssignRacksToSite moves one or more racks to a target site (or + * to "Unassigned" when target_site_id is unset) as a partial + * update — the rack's label, layout, members, and slot + * assignments stay untouched, only site_id changes. building_id is + * auto-cleared on any site transition since a building belongs to + * a single site; the response carries the count of racks whose + * building was cleared so the UI can prompt for re-assignment. + * Same transaction cascades device.site_id for every rack member. + * + * @generated from rpc sites.v1.SiteService.AssignRacksToSite + */ + assignRacksToSite: { + methodKind: "unary"; + input: typeof AssignRacksToSiteRequestSchema; + output: typeof AssignRacksToSiteResponseSchema; + }, /** * GetSiteStats returns server-rolled telemetry + miner-state counts * for every device assigned to the site, including devices whose @@ -792,5 +856,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof GetSiteStatsRequestSchema; output: typeof GetSiteStatsResponseSchema; - }; -}> = /*@__PURE__*/ serviceDesc(file_sites_v1_sites, 0); + }, +}> = /*@__PURE__*/ + serviceDesc(file_sites_v1_sites, 0); + diff --git a/client/src/protoFleet/api/sites.ts b/client/src/protoFleet/api/sites.ts index 5abbae47d..adc7aef7c 100644 --- a/client/src/protoFleet/api/sites.ts +++ b/client/src/protoFleet/api/sites.ts @@ -139,6 +139,19 @@ interface AssignBuildingsToSiteProps { onFinally?: () => void; } +interface AssignRacksToSiteProps { + // Bulk-friendly. Pass a single-element array for the singular case. + rackIds: bigint[]; + // Unset moves the racks to "Unassigned". + targetSiteId?: bigint; + signal?: AbortSignal; + // onSuccess args: device cascade count, count of racks whose + // building was auto-cleared because the move crossed sites. + onSuccess?: (reassignedDeviceCount: bigint, clearedBuildingCount: bigint) => void; + onError?: (message: string) => void; + onFinally?: () => void; +} + const useSites = () => { const { handleAuthErrors } = useAuthErrors(); @@ -326,7 +339,42 @@ const useSites = () => { [handleAuthErrors], ); - return { listSites, createSite, updateSite, deleteSite, assignDevicesToSite, assignBuildingsToSite }; + const assignRacksToSite = useCallback( + async ({ rackIds, targetSiteId, signal, onSuccess, onError, onFinally }: AssignRacksToSiteProps) => { + try { + const response = await sitesClient.assignRacksToSite( + { + rackIds, + targetSiteId, + }, + { signal }, + ); + if (signal?.aborted) return; + onSuccess?.(response.reassignedDeviceCount, response.clearedBuildingCount); + } catch (err) { + if (signal?.aborted) return; + handleAuthErrors({ + error: err, + onError: (error) => { + onError?.(getErrorMessage(error)); + }, + }); + } finally { + onFinally?.(); + } + }, + [handleAuthErrors], + ); + + return { + listSites, + createSite, + updateSite, + deleteSite, + assignDevicesToSite, + assignBuildingsToSite, + assignRacksToSite, + }; }; export { useSites }; diff --git a/proto/sites/v1/sites.proto b/proto/sites/v1/sites.proto index 7cb692fab..38f2e8313 100644 --- a/proto/sites/v1/sites.proto +++ b/proto/sites/v1/sites.proto @@ -47,6 +47,17 @@ service SiteService { rpc AssignBuildingsToSite(AssignBuildingsToSiteRequest) returns (AssignBuildingsToSiteResponse); + // AssignRacksToSite moves one or more racks to a target site (or + // to "Unassigned" when target_site_id is unset) as a partial + // update — the rack's label, layout, members, and slot + // assignments stay untouched, only site_id changes. building_id is + // auto-cleared on any site transition since a building belongs to + // a single site; the response carries the count of racks whose + // building was cleared so the UI can prompt for re-assignment. + // Same transaction cascades device.site_id for every rack member. + rpc AssignRacksToSite(AssignRacksToSiteRequest) + returns (AssignRacksToSiteResponse); + // GetSiteStats returns server-rolled telemetry + miner-state counts // for every device assigned to the site, including devices whose // rack has no building set and devices that have no rack at all. @@ -240,6 +251,29 @@ message AssignBuildingsToSiteResponse { int64 reassigned_device_count = 2; } +// AssignRacksToSite + +message AssignRacksToSiteRequest { + repeated int64 rack_ids = 1 [(buf.validate.field).repeated = { + min_items: 1 + max_items: 1000 + items: {int64: {gt: 0}} + }]; + // Unset = move racks to "Unassigned" (site_id NULL). When present, + // must be > 0. + optional int64 target_site_id = 2 [(buf.validate.field).int64.gt = 0]; +} + +message AssignRacksToSiteResponse { + // Cascade impact: how many devices had their site_id re-stamped + // across every rack in the batch. + int64 reassigned_device_count = 1; + // Number of racks whose building_id was cleared because the move + // crossed site boundaries. UI can use this to prompt for + // re-assignment to a building in the new site. + int64 cleared_building_count = 2; +} + // GetSiteStats message GetSiteStatsRequest { diff --git a/server/cmd/fleetd/main.go b/server/cmd/fleetd/main.go index a04fc8215..80ee5da87 100644 --- a/server/cmd/fleetd/main.go +++ b/server/cmd/fleetd/main.go @@ -453,7 +453,7 @@ func start(config *Config) error { ) curtailmentResponseProfileSvc := curtailmentDomain.NewResponseProfileService(curtailmentStore) - sitesSvc := sitesDomain.NewService(siteStore, buildingStore, deviceStore, telemetryService, transactor, activitySvc) + sitesSvc := sitesDomain.NewService(siteStore, buildingStore, collectionStore, deviceStore, telemetryService, transactor, activitySvc) buildingsSvc := buildingsDomain.NewService(buildingStore, siteStore, collectionStore, deviceStore, telemetryService, transactor, activitySvc) // Register the schedule-conflict preflight filter on commandSvc so every diff --git a/server/generated/grpc/sites/v1/sites.pb.go b/server/generated/grpc/sites/v1/sites.pb.go index 65d669114..197d08d92 100644 --- a/server/generated/grpc/sites/v1/sites.pb.go +++ b/server/generated/grpc/sites/v1/sites.pb.go @@ -1119,6 +1119,117 @@ func (x *AssignBuildingsToSiteResponse) GetReassignedDeviceCount() int64 { return 0 } +type AssignRacksToSiteRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RackIds []int64 `protobuf:"varint,1,rep,packed,name=rack_ids,json=rackIds,proto3" json:"rack_ids,omitempty"` + // Unset = move racks to "Unassigned" (site_id NULL). When present, + // must be > 0. + TargetSiteId *int64 `protobuf:"varint,2,opt,name=target_site_id,json=targetSiteId,proto3,oneof" json:"target_site_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssignRacksToSiteRequest) Reset() { + *x = AssignRacksToSiteRequest{} + mi := &file_sites_v1_sites_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssignRacksToSiteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssignRacksToSiteRequest) ProtoMessage() {} + +func (x *AssignRacksToSiteRequest) ProtoReflect() protoreflect.Message { + mi := &file_sites_v1_sites_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssignRacksToSiteRequest.ProtoReflect.Descriptor instead. +func (*AssignRacksToSiteRequest) Descriptor() ([]byte, []int) { + return file_sites_v1_sites_proto_rawDescGZIP(), []int{15} +} + +func (x *AssignRacksToSiteRequest) GetRackIds() []int64 { + if x != nil { + return x.RackIds + } + return nil +} + +func (x *AssignRacksToSiteRequest) GetTargetSiteId() int64 { + if x != nil && x.TargetSiteId != nil { + return *x.TargetSiteId + } + return 0 +} + +type AssignRacksToSiteResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Cascade impact: how many devices had their site_id re-stamped + // across every rack in the batch. + ReassignedDeviceCount int64 `protobuf:"varint,1,opt,name=reassigned_device_count,json=reassignedDeviceCount,proto3" json:"reassigned_device_count,omitempty"` + // Number of racks whose building_id was cleared because the move + // crossed site boundaries. UI can use this to prompt for + // re-assignment to a building in the new site. + ClearedBuildingCount int64 `protobuf:"varint,2,opt,name=cleared_building_count,json=clearedBuildingCount,proto3" json:"cleared_building_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssignRacksToSiteResponse) Reset() { + *x = AssignRacksToSiteResponse{} + mi := &file_sites_v1_sites_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssignRacksToSiteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssignRacksToSiteResponse) ProtoMessage() {} + +func (x *AssignRacksToSiteResponse) ProtoReflect() protoreflect.Message { + mi := &file_sites_v1_sites_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssignRacksToSiteResponse.ProtoReflect.Descriptor instead. +func (*AssignRacksToSiteResponse) Descriptor() ([]byte, []int) { + return file_sites_v1_sites_proto_rawDescGZIP(), []int{16} +} + +func (x *AssignRacksToSiteResponse) GetReassignedDeviceCount() int64 { + if x != nil { + return x.ReassignedDeviceCount + } + return 0 +} + +func (x *AssignRacksToSiteResponse) GetClearedBuildingCount() int64 { + if x != nil { + return x.ClearedBuildingCount + } + return 0 +} + type GetSiteStatsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` SiteId int64 `protobuf:"varint,1,opt,name=site_id,json=siteId,proto3" json:"site_id,omitempty"` @@ -1128,7 +1239,7 @@ type GetSiteStatsRequest struct { func (x *GetSiteStatsRequest) Reset() { *x = GetSiteStatsRequest{} - mi := &file_sites_v1_sites_proto_msgTypes[15] + mi := &file_sites_v1_sites_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1140,7 +1251,7 @@ func (x *GetSiteStatsRequest) String() string { func (*GetSiteStatsRequest) ProtoMessage() {} func (x *GetSiteStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_sites_v1_sites_proto_msgTypes[15] + mi := &file_sites_v1_sites_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1153,7 +1264,7 @@ func (x *GetSiteStatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSiteStatsRequest.ProtoReflect.Descriptor instead. func (*GetSiteStatsRequest) Descriptor() ([]byte, []int) { - return file_sites_v1_sites_proto_rawDescGZIP(), []int{15} + return file_sites_v1_sites_proto_rawDescGZIP(), []int{17} } func (x *GetSiteStatsRequest) GetSiteId() int64 { @@ -1195,7 +1306,7 @@ type GetSiteStatsResponse struct { func (x *GetSiteStatsResponse) Reset() { *x = GetSiteStatsResponse{} - mi := &file_sites_v1_sites_proto_msgTypes[16] + mi := &file_sites_v1_sites_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1207,7 +1318,7 @@ func (x *GetSiteStatsResponse) String() string { func (*GetSiteStatsResponse) ProtoMessage() {} func (x *GetSiteStatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_sites_v1_sites_proto_msgTypes[16] + mi := &file_sites_v1_sites_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1220,7 +1331,7 @@ func (x *GetSiteStatsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSiteStatsResponse.ProtoReflect.Descriptor instead. func (*GetSiteStatsResponse) Descriptor() ([]byte, []int) { - return file_sites_v1_sites_proto_rawDescGZIP(), []int{16} + return file_sites_v1_sites_proto_rawDescGZIP(), []int{18} } func (x *GetSiteStatsResponse) GetSiteId() int64 { @@ -1515,108 +1626,132 @@ var file_sites_v1_sites_proto_rawDesc = string([]byte{ 0x75, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x37, 0x0a, 0x13, 0x47, - 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x06, 0x73, 0x69, - 0x74, 0x65, 0x49, 0x64, 0x22, 0xe4, 0x04, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, - 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x73, 0x69, 0x74, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, - 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, - 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x61, 0x73, 0x68, - 0x72, 0x61, 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x67, 0x5f, 0x65, - 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6a, 0x74, 0x68, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, 0x45, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, - 0x63, 0x79, 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, - 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x23, 0x0a, 0x0d, 0x68, - 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, - 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, 0x65, 0x65, - 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x66, 0x66, - 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, - 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x65, - 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, - 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6f, 0x77, 0x65, 0x72, - 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0xb3, 0x01, 0x0a, 0x17, - 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, - 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x26, 0x50, 0x45, 0x52, 0x5f, 0x44, - 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, - 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, - 0x44, 0x10, 0x00, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, - 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, - 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, - 0x4e, 0x44, 0x10, 0x01, 0x12, 0x3b, 0x0a, 0x37, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, 0x49, - 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x53, - 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x52, 0x41, 0x43, - 0x4b, 0x5f, 0x41, 0x54, 0x5f, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x5f, 0x53, 0x49, 0x54, 0x45, 0x10, - 0x02, 0x32, 0xcb, 0x04, 0x0a, 0x0b, 0x53, 0x69, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x44, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x12, 0x1a, - 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, - 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x73, 0x69, 0x74, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8f, 0x01, 0x0a, 0x18, + 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x08, 0x72, 0x61, 0x63, 0x6b, + 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x42, 0x11, 0xba, 0x48, 0x0e, 0x92, + 0x01, 0x0b, 0x08, 0x01, 0x10, 0xe8, 0x07, 0x22, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x07, 0x72, + 0x61, 0x63, 0x6b, 0x49, 0x64, 0x73, 0x12, 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, + 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x22, 0x89, 0x01, + 0x0a, 0x19, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x53, + 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x72, + 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, + 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x34, 0x0a, 0x16, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x14, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x37, 0x0a, 0x13, 0x47, 0x65, 0x74, + 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x20, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x06, 0x73, 0x69, 0x74, 0x65, + 0x49, 0x64, 0x22, 0xe4, 0x04, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x73, + 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x69, + 0x74, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, + 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61, + 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x67, 0x5f, 0x65, 0x66, 0x66, + 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6a, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, 0x45, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, + 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x6f, 0x77, + 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, + 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, + 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, + 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x38, 0x0a, + 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x66, 0x66, 0x69, 0x63, + 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x65, 0x66, 0x66, + 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x72, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0xb3, 0x01, 0x0a, 0x17, 0x50, 0x65, + 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x26, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, + 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, + 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, + 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, + 0x10, 0x01, 0x12, 0x3b, 0x0a, 0x37, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, + 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x52, 0x41, 0x43, 0x4b, 0x5f, + 0x41, 0x54, 0x5f, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x5f, 0x53, 0x49, 0x54, 0x45, 0x10, 0x02, 0x32, + 0xa9, 0x05, 0x0a, 0x0b, 0x53, 0x69, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x44, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x73, + 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, + 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, + 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, + 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x47, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, - 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, - 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x62, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x73, 0x69, 0x74, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x25, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, - 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x15, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, - 0x26, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, - 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, - 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, - 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1e, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, - 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, - 0xa0, 0x01, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, - 0x42, 0x0a, 0x53, 0x69, 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x43, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, - 0x70, 0x63, 0x2f, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x73, 0x69, 0x74, 0x65, - 0x73, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x53, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, - 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0xe2, - 0x02, 0x14, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x53, 0x69, 0x74, 0x65, 0x73, 0x3a, 0x3a, - 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x62, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, + 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x15, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x26, 0x2e, + 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, + 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, + 0x0a, 0x11, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x53, + 0x69, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, + 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0c, + 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x73, + 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, + 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xa0, 0x01, 0x0a, 0x0c, + 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x53, 0x69, + 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x73, + 0x69, 0x74, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x73, 0x69, 0x74, 0x65, 0x73, 0x76, 0x31, 0xa2, + 0x02, 0x03, 0x53, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x56, 0x31, + 0xca, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x14, 0x53, 0x69, + 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0xea, 0x02, 0x09, 0x53, 0x69, 0x74, 0x65, 0x73, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -1632,7 +1767,7 @@ func file_sites_v1_sites_proto_rawDescGZIP() []byte { } var file_sites_v1_sites_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_sites_v1_sites_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_sites_v1_sites_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_sites_v1_sites_proto_goTypes = []any{ (PerDeviceConflictReason)(0), // 0: sites.v1.PerDeviceConflictReason (*Site)(nil), // 1: sites.v1.Site @@ -1650,13 +1785,15 @@ var file_sites_v1_sites_proto_goTypes = []any{ (*AssignDevicesToSiteResponse)(nil), // 13: sites.v1.AssignDevicesToSiteResponse (*AssignBuildingsToSiteRequest)(nil), // 14: sites.v1.AssignBuildingsToSiteRequest (*AssignBuildingsToSiteResponse)(nil), // 15: sites.v1.AssignBuildingsToSiteResponse - (*GetSiteStatsRequest)(nil), // 16: sites.v1.GetSiteStatsRequest - (*GetSiteStatsResponse)(nil), // 17: sites.v1.GetSiteStatsResponse - (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp + (*AssignRacksToSiteRequest)(nil), // 16: sites.v1.AssignRacksToSiteRequest + (*AssignRacksToSiteResponse)(nil), // 17: sites.v1.AssignRacksToSiteResponse + (*GetSiteStatsRequest)(nil), // 18: sites.v1.GetSiteStatsRequest + (*GetSiteStatsResponse)(nil), // 19: sites.v1.GetSiteStatsResponse + (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp } var file_sites_v1_sites_proto_depIdxs = []int32{ - 18, // 0: sites.v1.Site.created_at:type_name -> google.protobuf.Timestamp - 18, // 1: sites.v1.Site.updated_at:type_name -> google.protobuf.Timestamp + 20, // 0: sites.v1.Site.created_at:type_name -> google.protobuf.Timestamp + 20, // 1: sites.v1.Site.updated_at:type_name -> google.protobuf.Timestamp 1, // 2: sites.v1.SiteWithCounts.site:type_name -> sites.v1.Site 2, // 3: sites.v1.ListSitesResponse.sites:type_name -> sites.v1.SiteWithCounts 1, // 4: sites.v1.CreateSiteResponse.site:type_name -> sites.v1.Site @@ -1669,16 +1806,18 @@ var file_sites_v1_sites_proto_depIdxs = []int32{ 9, // 11: sites.v1.SiteService.DeleteSite:input_type -> sites.v1.DeleteSiteRequest 11, // 12: sites.v1.SiteService.AssignDevicesToSite:input_type -> sites.v1.AssignDevicesToSiteRequest 14, // 13: sites.v1.SiteService.AssignBuildingsToSite:input_type -> sites.v1.AssignBuildingsToSiteRequest - 16, // 14: sites.v1.SiteService.GetSiteStats:input_type -> sites.v1.GetSiteStatsRequest - 4, // 15: sites.v1.SiteService.ListSites:output_type -> sites.v1.ListSitesResponse - 6, // 16: sites.v1.SiteService.CreateSite:output_type -> sites.v1.CreateSiteResponse - 8, // 17: sites.v1.SiteService.UpdateSite:output_type -> sites.v1.UpdateSiteResponse - 10, // 18: sites.v1.SiteService.DeleteSite:output_type -> sites.v1.DeleteSiteResponse - 13, // 19: sites.v1.SiteService.AssignDevicesToSite:output_type -> sites.v1.AssignDevicesToSiteResponse - 15, // 20: sites.v1.SiteService.AssignBuildingsToSite:output_type -> sites.v1.AssignBuildingsToSiteResponse - 17, // 21: sites.v1.SiteService.GetSiteStats:output_type -> sites.v1.GetSiteStatsResponse - 15, // [15:22] is the sub-list for method output_type - 8, // [8:15] is the sub-list for method input_type + 16, // 14: sites.v1.SiteService.AssignRacksToSite:input_type -> sites.v1.AssignRacksToSiteRequest + 18, // 15: sites.v1.SiteService.GetSiteStats:input_type -> sites.v1.GetSiteStatsRequest + 4, // 16: sites.v1.SiteService.ListSites:output_type -> sites.v1.ListSitesResponse + 6, // 17: sites.v1.SiteService.CreateSite:output_type -> sites.v1.CreateSiteResponse + 8, // 18: sites.v1.SiteService.UpdateSite:output_type -> sites.v1.UpdateSiteResponse + 10, // 19: sites.v1.SiteService.DeleteSite:output_type -> sites.v1.DeleteSiteResponse + 13, // 20: sites.v1.SiteService.AssignDevicesToSite:output_type -> sites.v1.AssignDevicesToSiteResponse + 15, // 21: sites.v1.SiteService.AssignBuildingsToSite:output_type -> sites.v1.AssignBuildingsToSiteResponse + 17, // 22: sites.v1.SiteService.AssignRacksToSite:output_type -> sites.v1.AssignRacksToSiteResponse + 19, // 23: sites.v1.SiteService.GetSiteStats:output_type -> sites.v1.GetSiteStatsResponse + 16, // [16:24] is the sub-list for method output_type + 8, // [8:16] is the sub-list for method input_type 8, // [8:8] is the sub-list for extension type_name 8, // [8:8] is the sub-list for extension extendee 0, // [0:8] is the sub-list for field type_name @@ -1691,13 +1830,14 @@ func file_sites_v1_sites_proto_init() { } file_sites_v1_sites_proto_msgTypes[10].OneofWrappers = []any{} file_sites_v1_sites_proto_msgTypes[13].OneofWrappers = []any{} + file_sites_v1_sites_proto_msgTypes[15].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_sites_v1_sites_proto_rawDesc), len(file_sites_v1_sites_proto_rawDesc)), NumEnums: 1, - NumMessages: 17, + NumMessages: 19, NumExtensions: 0, NumServices: 1, }, diff --git a/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go b/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go index 1c67f49ec..7667f981c 100644 --- a/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go +++ b/server/generated/grpc/sites/v1/sitesv1connect/sites.connect.go @@ -48,6 +48,9 @@ const ( // SiteServiceAssignBuildingsToSiteProcedure is the fully-qualified name of the SiteService's // AssignBuildingsToSite RPC. SiteServiceAssignBuildingsToSiteProcedure = "/sites.v1.SiteService/AssignBuildingsToSite" + // SiteServiceAssignRacksToSiteProcedure is the fully-qualified name of the SiteService's + // AssignRacksToSite RPC. + SiteServiceAssignRacksToSiteProcedure = "/sites.v1.SiteService/AssignRacksToSite" // SiteServiceGetSiteStatsProcedure is the fully-qualified name of the SiteService's GetSiteStats // RPC. SiteServiceGetSiteStatsProcedure = "/sites.v1.SiteService/GetSiteStats" @@ -85,6 +88,15 @@ type SiteServiceClient interface { // building's racks and their devices. Returns the aggregate cascade // counts across every building in the batch. AssignBuildingsToSite(context.Context, *connect.Request[v1.AssignBuildingsToSiteRequest]) (*connect.Response[v1.AssignBuildingsToSiteResponse], error) + // AssignRacksToSite moves one or more racks to a target site (or + // to "Unassigned" when target_site_id is unset) as a partial + // update — the rack's label, layout, members, and slot + // assignments stay untouched, only site_id changes. building_id is + // auto-cleared on any site transition since a building belongs to + // a single site; the response carries the count of racks whose + // building was cleared so the UI can prompt for re-assignment. + // Same transaction cascades device.site_id for every rack member. + AssignRacksToSite(context.Context, *connect.Request[v1.AssignRacksToSiteRequest]) (*connect.Response[v1.AssignRacksToSiteResponse], error) // GetSiteStats returns server-rolled telemetry + miner-state counts // for every device assigned to the site, including devices whose // rack has no building set and devices that have no rack at all. @@ -132,6 +144,11 @@ func NewSiteServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. baseURL+SiteServiceAssignBuildingsToSiteProcedure, opts..., ), + assignRacksToSite: connect.NewClient[v1.AssignRacksToSiteRequest, v1.AssignRacksToSiteResponse]( + httpClient, + baseURL+SiteServiceAssignRacksToSiteProcedure, + opts..., + ), getSiteStats: connect.NewClient[v1.GetSiteStatsRequest, v1.GetSiteStatsResponse]( httpClient, baseURL+SiteServiceGetSiteStatsProcedure, @@ -148,6 +165,7 @@ type siteServiceClient struct { deleteSite *connect.Client[v1.DeleteSiteRequest, v1.DeleteSiteResponse] assignDevicesToSite *connect.Client[v1.AssignDevicesToSiteRequest, v1.AssignDevicesToSiteResponse] assignBuildingsToSite *connect.Client[v1.AssignBuildingsToSiteRequest, v1.AssignBuildingsToSiteResponse] + assignRacksToSite *connect.Client[v1.AssignRacksToSiteRequest, v1.AssignRacksToSiteResponse] getSiteStats *connect.Client[v1.GetSiteStatsRequest, v1.GetSiteStatsResponse] } @@ -181,6 +199,11 @@ func (c *siteServiceClient) AssignBuildingsToSite(ctx context.Context, req *conn return c.assignBuildingsToSite.CallUnary(ctx, req) } +// AssignRacksToSite calls sites.v1.SiteService.AssignRacksToSite. +func (c *siteServiceClient) AssignRacksToSite(ctx context.Context, req *connect.Request[v1.AssignRacksToSiteRequest]) (*connect.Response[v1.AssignRacksToSiteResponse], error) { + return c.assignRacksToSite.CallUnary(ctx, req) +} + // GetSiteStats calls sites.v1.SiteService.GetSiteStats. func (c *siteServiceClient) GetSiteStats(ctx context.Context, req *connect.Request[v1.GetSiteStatsRequest]) (*connect.Response[v1.GetSiteStatsResponse], error) { return c.getSiteStats.CallUnary(ctx, req) @@ -218,6 +241,15 @@ type SiteServiceHandler interface { // building's racks and their devices. Returns the aggregate cascade // counts across every building in the batch. AssignBuildingsToSite(context.Context, *connect.Request[v1.AssignBuildingsToSiteRequest]) (*connect.Response[v1.AssignBuildingsToSiteResponse], error) + // AssignRacksToSite moves one or more racks to a target site (or + // to "Unassigned" when target_site_id is unset) as a partial + // update — the rack's label, layout, members, and slot + // assignments stay untouched, only site_id changes. building_id is + // auto-cleared on any site transition since a building belongs to + // a single site; the response carries the count of racks whose + // building was cleared so the UI can prompt for re-assignment. + // Same transaction cascades device.site_id for every rack member. + AssignRacksToSite(context.Context, *connect.Request[v1.AssignRacksToSiteRequest]) (*connect.Response[v1.AssignRacksToSiteResponse], error) // GetSiteStats returns server-rolled telemetry + miner-state counts // for every device assigned to the site, including devices whose // rack has no building set and devices that have no rack at all. @@ -261,6 +293,11 @@ func NewSiteServiceHandler(svc SiteServiceHandler, opts ...connect.HandlerOption svc.AssignBuildingsToSite, opts..., ) + siteServiceAssignRacksToSiteHandler := connect.NewUnaryHandler( + SiteServiceAssignRacksToSiteProcedure, + svc.AssignRacksToSite, + opts..., + ) siteServiceGetSiteStatsHandler := connect.NewUnaryHandler( SiteServiceGetSiteStatsProcedure, svc.GetSiteStats, @@ -280,6 +317,8 @@ func NewSiteServiceHandler(svc SiteServiceHandler, opts ...connect.HandlerOption siteServiceAssignDevicesToSiteHandler.ServeHTTP(w, r) case SiteServiceAssignBuildingsToSiteProcedure: siteServiceAssignBuildingsToSiteHandler.ServeHTTP(w, r) + case SiteServiceAssignRacksToSiteProcedure: + siteServiceAssignRacksToSiteHandler.ServeHTTP(w, r) case SiteServiceGetSiteStatsProcedure: siteServiceGetSiteStatsHandler.ServeHTTP(w, r) default: @@ -315,6 +354,10 @@ func (UnimplementedSiteServiceHandler) AssignBuildingsToSite(context.Context, *c return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sites.v1.SiteService.AssignBuildingsToSite is not implemented")) } +func (UnimplementedSiteServiceHandler) AssignRacksToSite(context.Context, *connect.Request[v1.AssignRacksToSiteRequest]) (*connect.Response[v1.AssignRacksToSiteResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sites.v1.SiteService.AssignRacksToSite is not implemented")) +} + func (UnimplementedSiteServiceHandler) GetSiteStats(context.Context, *connect.Request[v1.GetSiteStatsRequest]) (*connect.Response[v1.GetSiteStatsResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("sites.v1.SiteService.GetSiteStats is not implemented")) } diff --git a/server/internal/domain/sites/models/models.go b/server/internal/domain/sites/models/models.go index c23776bf0..9dd4b3231 100644 --- a/server/internal/domain/sites/models/models.go +++ b/server/internal/domain/sites/models/models.go @@ -119,6 +119,22 @@ type AssignBuildingsToSiteResult struct { ReassignedDeviceCount int64 } +// AssignRacksToSiteParams is the input shape for the bulk rack→site +// partial-update flow. TargetSiteID == nil means "Unassigned"; the +// entire batch applies in one transaction. +type AssignRacksToSiteParams struct { + OrgID int64 + RackIDs []int64 + TargetSiteID *int64 +} + +// AssignRacksToSiteResult carries cascade impact + the count of racks +// whose building_id was cleared on the site transition. +type AssignRacksToSiteResult struct { + ReassignedDeviceCount int64 + ClearedBuildingCount int64 +} + // SiteNetworkConfigEntry is a (name, network_config) tuple used by the // service when computing cross-site overlap warnings on save. type SiteNetworkConfigEntry struct { diff --git a/server/internal/domain/sites/service.go b/server/internal/domain/sites/service.go index 0a493bc53..5167046b5 100644 --- a/server/internal/domain/sites/service.go +++ b/server/internal/domain/sites/service.go @@ -29,6 +29,7 @@ const ( eventSiteDeleted = "site.deleted" eventDevicesReassignedToSite = "devices.reassigned_to_site" eventBuildingAssignedToSite = "building.assigned_to_site" + eventRacksAssignedToSite = "racks.assigned_to_site" ) // maxDeviceIdentifiersInMetadata bounds how many identifiers we keep in @@ -50,12 +51,13 @@ const MaxDevicesPerSiteStatsRequest = 100_000 // site delete cascade and the bulk-reassign all-or-nothing semantics // both depend on it. type Service struct { - store interfaces.SiteStore - buildingStore interfaces.BuildingStore - deviceQueryer devicerollup.DeviceQueryer - telemetry devicerollup.TelemetryCollector - transactor interfaces.Transactor - activitySvc *activity.Service + store interfaces.SiteStore + buildingStore interfaces.BuildingStore + collectionStore interfaces.CollectionStore + deviceQueryer devicerollup.DeviceQueryer + telemetry devicerollup.TelemetryCollector + transactor interfaces.Transactor + activitySvc *activity.Service } // NewService wires a SiteStore, Transactor, and the activity Service @@ -66,21 +68,27 @@ type Service struct { // buildingStore, deviceQueryer, and telemetry power GetSiteStats only. // Any of them may be nil in test setups where the stats RPC isn't // exercised; GetSiteStats returns an internal error in that case. +// +// collectionStore powers AssignRacksToSite (it owns the rack +// placement read/write path shared with SaveRack). Nil collectionStore +// causes AssignRacksToSite to return an internal error. func NewService( store interfaces.SiteStore, buildingStore interfaces.BuildingStore, + collectionStore interfaces.CollectionStore, deviceQueryer devicerollup.DeviceQueryer, telemetry devicerollup.TelemetryCollector, transactor interfaces.Transactor, activitySvc *activity.Service, ) *Service { return &Service{ - store: store, - buildingStore: buildingStore, - deviceQueryer: deviceQueryer, - telemetry: telemetry, - transactor: transactor, - activitySvc: activitySvc, + store: store, + buildingStore: buildingStore, + collectionStore: collectionStore, + deviceQueryer: deviceQueryer, + telemetry: telemetry, + transactor: transactor, + activitySvc: activitySvc, } } @@ -447,6 +455,120 @@ func (s *Service) AssignBuildingsToSite(ctx context.Context, params models.Assig }, nil } +// AssignRacksToSite moves one or more racks to a target site (or to +// "Unassigned" when TargetSiteID is nil) as a partial update — label, +// layout, members, and slot assignments stay untouched. building_id is +// auto-cleared on any site transition because a building belongs to a +// single site; the response carries the count of racks whose building +// was cleared so the UI can prompt the operator to pick a building in +// the new site. Same transaction cascades device.site_id for every +// rack member. +// +// Lock order: target site → each rack id ascending. Matches +// AssignBuildingsToSite + AssignDevicesToSite so concurrent site-scope +// writers can't deadlock against an overlapping rack set. +func (s *Service) AssignRacksToSite(ctx context.Context, params models.AssignRacksToSiteParams) (*models.AssignRacksToSiteResult, error) { + if s.collectionStore == nil { + return nil, fleeterror.NewInternalErrorf("collection store not configured") + } + rackIDs := dedupeInt64s(params.RackIDs) + if len(rackIDs) == 0 { + return nil, fleeterror.NewInvalidArgumentError("rack_ids must not be empty") + } + sort.Slice(rackIDs, func(i, j int) bool { return rackIDs[i] < rackIDs[j] }) + + var ( + deviceCount int64 + clearedCount int64 + cascadedRackIDs []int64 + ) + err := s.transactor.RunInTx(ctx, func(txCtx context.Context) error { + // Lock target site first if assigning so a concurrent + // DeleteSite can't soft-delete it between the check and the + // cascade writes. target=nil/0 (Unassigned) needs no lock. + if params.TargetSiteID != nil && *params.TargetSiteID > 0 { + if err := s.store.LockSiteForWrite(txCtx, params.OrgID, *params.TargetSiteID); err != nil { + return err + } + } + for _, rackID := range rackIDs { + current, err := s.collectionStore.LockRackPlacementForWrite(txCtx, rackID, params.OrgID) + if err != nil { + return err + } + siteChanged := !int64PtrEqual(current.SiteID, params.TargetSiteID) + // building_id is bound to a single site, so any site + // transition invalidates the rack's building membership. + // Even an unchanged building_id read can't survive a site + // move — the operator must re-pick a building in the new + // site. + newBuildingID := current.BuildingID + finalZone := current.Zone + if siteChanged && current.BuildingID != nil { + newBuildingID = nil + finalZone = "" + clearedCount++ + } + if err := s.collectionStore.UpdateRackPlacement(txCtx, rackID, params.OrgID, params.TargetSiteID, newBuildingID, finalZone); err != nil { + return err + } + if siteChanged { + n, err := s.collectionStore.CascadeRackDeviceSites(txCtx, rackID, params.OrgID, params.TargetSiteID) + if err != nil { + return err + } + deviceCount += n + cascadedRackIDs = append(cascadedRackIDs, rackID) + } + } + return nil + }) + if err != nil { + return nil, err + } + + orgIDVal := params.OrgID + event := activitymodels.Event{ + Category: activitymodels.CategoryFleetManagement, + Type: eventRacksAssignedToSite, + OrganizationID: &orgIDVal, + SiteID: params.TargetSiteID, + Description: fmt.Sprintf( + "Assigned %d rack(s) to site %s (%d devices cascaded, %d building(s) cleared)", + len(rackIDs), formatSiteIDForDescription(params.TargetSiteID), deviceCount, clearedCount, + ), + Metadata: map[string]any{ + "rack_ids": rackIDs, + "target_site_id": params.TargetSiteID, + "reassigned_device_count": deviceCount, + "cleared_building_count": clearedCount, + }, + } + if len(cascadedRackIDs) > 0 { + event.Metadata["site_cascaded_rack_ids"] = cascadedRackIDs + } + activity.StampActor(ctx, &event) + s.activitySvc.Log(ctx, event) + + return &models.AssignRacksToSiteResult{ + ReassignedDeviceCount: deviceCount, + ClearedBuildingCount: clearedCount, + }, nil +} + +// int64PtrEqual treats two *int64 as equal when both are nil or both +// dereference to the same value. Local helper since this is the only +// caller in the sites domain; collection.Service has its own copy. +func int64PtrEqual(a, b *int64) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + return *a == *b +} + // --- helpers --- func (s *Service) computeReassignConflicts(ctx context.Context, orgID int64, targetSiteID *int64, identifiers []string) ([]models.PerDeviceConflict, error) { diff --git a/server/internal/domain/sites/service_stats_test.go b/server/internal/domain/sites/service_stats_test.go index 95e989bc7..dd14a1576 100644 --- a/server/internal/domain/sites/service_stats_test.go +++ b/server/internal/domain/sites/service_stats_test.go @@ -64,7 +64,7 @@ func TestGetSiteStats_notFoundWhenSiteMissing(t *testing.T) { store := mocks.NewMockSiteStore(ctrl) store.EXPECT().SiteBelongsToOrg(gomock.Any(), testOrgID, int64(99)).Return(false, nil) - svc := NewService(store, mocks.NewMockBuildingStore(ctrl), &fakeDeviceQueryer{}, &fakeTelemetryCollector{}, &fakeTransactor{}, nil) + svc := NewService(store, mocks.NewMockBuildingStore(ctrl), nil, &fakeDeviceQueryer{}, &fakeTelemetryCollector{}, &fakeTransactor{}, nil) _, err := svc.GetSiteStats(context.Background(), testOrgID, 99) var fe fleeterror.FleetError if !errors.As(err, &fe) || fe.GRPCCode != connect.CodeNotFound { @@ -108,7 +108,7 @@ func TestGetSiteStats_rollsUpEverything(t *testing.T) { }, } - svc := NewService(store, buildingStore, devices, telemetry, &fakeTransactor{}, nil) + svc := NewService(store, buildingStore, nil, devices, telemetry, &fakeTransactor{}, nil) stats, err := svc.GetSiteStats(context.Background(), testOrgID, 1) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -150,7 +150,7 @@ func TestGetSiteStats_includesAuthNeededInFilter(t *testing.T) { buildingStore.EXPECT().ListBuildings(gomock.Any(), gomock.Any()).Return(nil, nil) devices := &fakeDeviceQueryer{deviceIDs: nil} // no devices → telemetry not called - svc := NewService(store, buildingStore, devices, &fakeTelemetryCollector{}, &fakeTransactor{}, nil) + svc := NewService(store, buildingStore, nil, devices, &fakeTelemetryCollector{}, &fakeTransactor{}, nil) _, err := svc.GetSiteStats(context.Background(), testOrgID, 1) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -194,7 +194,7 @@ func TestGetSiteStats_failsFastOverCap(t *testing.T) { telemetry := &fakeTelemetryCollector{err: errors.New("should not be called")} devices := &fakeDeviceQueryer{deviceIDs: overCap} - svc := NewService(store, buildingStore, devices, telemetry, &fakeTransactor{}, nil) + svc := NewService(store, buildingStore, nil, devices, telemetry, &fakeTransactor{}, nil) _, err := svc.GetSiteStats(context.Background(), testOrgID, 1) var fe fleeterror.FleetError if !errors.As(err, &fe) || fe.GRPCCode != connect.CodeInternal { @@ -214,7 +214,7 @@ func TestGetSiteStats_emptyDevicesShortCircuits(t *testing.T) { telemetry := &fakeTelemetryCollector{err: errors.New("should not be called")} devices := &fakeDeviceQueryer{deviceIDs: nil} - svc := NewService(store, buildingStore, devices, telemetry, &fakeTransactor{}, nil) + svc := NewService(store, buildingStore, nil, devices, telemetry, &fakeTransactor{}, nil) stats, err := svc.GetSiteStats(context.Background(), testOrgID, 1) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -228,7 +228,7 @@ func TestGetSiteStats_internalErrorWhenStatsDepsMissing(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) // Nil deviceQueryer should short-circuit before any store call. - svc := NewService(store, nil, nil, nil, &fakeTransactor{}, nil) + svc := NewService(store, nil, nil, nil, nil, &fakeTransactor{}, nil) _, err := svc.GetSiteStats(context.Background(), testOrgID, 1) var fe fleeterror.FleetError if !errors.As(err, &fe) || fe.GRPCCode != connect.CodeInternal { diff --git a/server/internal/domain/sites/service_test.go b/server/internal/domain/sites/service_test.go index a16c78bf3..3f89e3272 100644 --- a/server/internal/domain/sites/service_test.go +++ b/server/internal/domain/sites/service_test.go @@ -109,7 +109,7 @@ func TestDeleteSite_cascadeInOneTransaction(t *testing.T) { tx := &fakeTransactor{} // activitySvc is nil; the service's logActivity is nil-safe. Production // wires a real *activity.Service from main.go. - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) gomock.InOrder( store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, int64(11)).Return(nil), @@ -142,7 +142,7 @@ func TestDeleteSite_notFoundWhenSoftDeleteAffectsZeroRows(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) // LockSiteForWrite succeeds (row exists at start of tx) but the // final SoftDeleteSite affects 0 rows because nothing matched the @@ -172,7 +172,7 @@ func TestAssignDevicesToSite_rejectsCrossCollectionConflict(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := tf.make() - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) identifiers := []string{"d1", "d2"} target := int64(20) @@ -221,7 +221,7 @@ func TestAssignDevicesToSite_reportsMissingDevices(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) identifiers := []string{"d1", "d-missing"} target := int64(20) @@ -252,7 +252,7 @@ func TestAssignDevicesToSite_writesOnSuccess(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) identifiers := []string{"d1", "d2"} target := int64(20) @@ -287,7 +287,7 @@ func TestAssignDevicesToSite_unassignedTargetSkipsBelongsCheck(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) identifiers := []string{"d1"} @@ -312,7 +312,7 @@ func TestAssignDevicesToSite_targetMatchesCurrentRackSiteIsNotAConflict(t *testi ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) identifiers := []string{"d1"} target := int64(42) @@ -343,7 +343,7 @@ func TestAssignBuildingsToSite_cascadeOnSuccess(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) target := int64(20) // The TOCTOU fix moved the site-alive check inside the tx and @@ -374,7 +374,7 @@ func TestAssignBuildingsToSite_notFoundWhenBuildingMissing(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) target := int64(20) // Both calls now live inside the tx after the TOCTOU fix. With the @@ -401,7 +401,7 @@ func TestAssignBuildingsToSite_bulkRollsBackOnLaterFailure(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) target := int64(20) store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, target).Return(nil) @@ -426,7 +426,7 @@ func TestCreateSite_invalidNetworkConfigBlocksWrite(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) // CreateSite must NOT be called when network_config validation fails. _, err := svc.CreateSite(context.Background(), models.CreateSiteParams{ @@ -443,7 +443,7 @@ func TestCreateSite_canonicalizesAndPersists(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) store.EXPECT().ListAllSiteNetworkConfigs(gomock.Any(), testOrgID, int64(0)).Return(nil, nil) store.EXPECT().CreateSite(gomock.Any(), gomock.AssignableToTypeOf(models.CreateSiteParams{})). @@ -471,7 +471,7 @@ func TestCreateSite_crossSiteOverlapSurfacesAsWarning(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) store.EXPECT().ListAllSiteNetworkConfigs(gomock.Any(), testOrgID, int64(0)).Return([]models.SiteNetworkConfigEntry{ {ID: 99, Name: "siteB", NetworkConfig: "10.0.0.0/22"}, @@ -495,7 +495,7 @@ func TestUpdateSite_canonicalizesAndPersists(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) store.EXPECT().ListAllSiteNetworkConfigs(gomock.Any(), testOrgID, int64(11)).Return(nil, nil) store.EXPECT().UpdateSite(gomock.Any(), gomock.AssignableToTypeOf(models.UpdateSiteParams{})). @@ -524,7 +524,7 @@ func TestUpdateSite_excludesSelfFromOverlapWarnings(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) store.EXPECT().ListAllSiteNetworkConfigs(gomock.Any(), testOrgID, int64(11)).Return(nil, nil) store.EXPECT().UpdateSite(gomock.Any(), gomock.Any()).Return(&models.Site{ID: 11}, nil) @@ -547,7 +547,7 @@ func TestUpdateSite_overlapWithDifferentSiteSurfacesWarning(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) store.EXPECT().ListAllSiteNetworkConfigs(gomock.Any(), testOrgID, int64(11)).Return([]models.SiteNetworkConfigEntry{ {ID: 99, Name: "siteB", NetworkConfig: "10.0.0.0/22"}, @@ -572,7 +572,7 @@ func TestUpdateSite_invalidNetworkConfigBlocksWrite(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) tx := &fakeTransactor{} - svc := NewService(store, nil, nil, nil, tx, nil) + svc := NewService(store, nil, nil, nil, nil, tx, nil) // UpdateSite must NOT be called when validation fails. _, err := svc.UpdateSite(context.Background(), models.UpdateSiteParams{ @@ -585,3 +585,33 @@ func TestUpdateSite_invalidNetworkConfigBlocksWrite(t *testing.T) { t.Fatal("expected validation error, got nil") } } + +func TestAssignRacksToSite_emptyRejected(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, mocks.NewMockCollectionStore(ctrl), nil, nil, tx, nil) + + _, err := svc.AssignRacksToSite(context.Background(), models.AssignRacksToSiteParams{ + OrgID: testOrgID, + RackIDs: nil, + }) + if err == nil { + t.Fatal("expected InvalidArgument for empty rack_ids, got nil") + } +} + +func TestAssignRacksToSite_nilCollectionStoreRejected(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, nil, nil, nil, tx, nil) + + _, err := svc.AssignRacksToSite(context.Background(), models.AssignRacksToSiteParams{ + OrgID: testOrgID, + RackIDs: []int64{1}, + }) + if err == nil { + t.Fatal("expected internal error when collection store is unconfigured") + } +} diff --git a/server/internal/handlers/middleware/rpc_permissions.go b/server/internal/handlers/middleware/rpc_permissions.go index 98eb4478b..df4cbe636 100644 --- a/server/internal/handlers/middleware/rpc_permissions.go +++ b/server/internal/handlers/middleware/rpc_permissions.go @@ -290,6 +290,7 @@ var ProcedurePermissions = map[string]string{ sitesv1connect.SiteServiceDeleteSiteProcedure: authz.PermSiteManage, sitesv1connect.SiteServiceAssignDevicesToSiteProcedure: authz.PermSiteManage, sitesv1connect.SiteServiceAssignBuildingsToSiteProcedure: authz.PermSiteManage, + sitesv1connect.SiteServiceAssignRacksToSiteProcedure: authz.PermSiteManage, // GetSiteStats also calls RequirePermission(PermFleetRead) inline to // cover the aggregate telemetry surface (matching the gate on // telemetry.GetCombinedMetrics). The map entry is the primary gate. diff --git a/server/internal/handlers/sites/handler.go b/server/internal/handlers/sites/handler.go index 4a8dfed08..f3b8a251a 100644 --- a/server/internal/handlers/sites/handler.go +++ b/server/internal/handlers/sites/handler.go @@ -12,6 +12,7 @@ import ( "github.com/block/proto-fleet/server/generated/grpc/sites/v1/sitesv1connect" "github.com/block/proto-fleet/server/internal/domain/authz" "github.com/block/proto-fleet/server/internal/domain/sites" + "github.com/block/proto-fleet/server/internal/domain/sites/models" "github.com/block/proto-fleet/server/internal/handlers/middleware" ) @@ -116,6 +117,30 @@ func (h *Handler) AssignBuildingsToSite(ctx context.Context, req *connect.Reques }), nil } +func (h *Handler) AssignRacksToSite(ctx context.Context, req *connect.Request[pb.AssignRacksToSiteRequest]) (*connect.Response[pb.AssignRacksToSiteResponse], error) { + info, err := middleware.RequirePermission(ctx, authz.PermSiteManage, authz.ResourceContext{}) + if err != nil { + return nil, err + } + var targetSiteID *int64 + if req.Msg.TargetSiteId != nil { + v := req.Msg.GetTargetSiteId() + targetSiteID = &v + } + out, err := h.service.AssignRacksToSite(ctx, models.AssignRacksToSiteParams{ + OrgID: info.OrganizationID, + RackIDs: req.Msg.GetRackIds(), + TargetSiteID: targetSiteID, + }) + if err != nil { + return nil, err + } + return connect.NewResponse(&pb.AssignRacksToSiteResponse{ + ReassignedDeviceCount: out.ReassignedDeviceCount, + ClearedBuildingCount: out.ClearedBuildingCount, + }), nil +} + func (h *Handler) GetSiteStats(ctx context.Context, req *connect.Request[pb.GetSiteStatsRequest]) (*connect.Response[pb.GetSiteStatsResponse], error) { // GetSiteStats returns telemetry rollups + miner health buckets, so // site:read alone isn't enough; we also gate on fleet:read. Both checks diff --git a/server/internal/handlers/sites/handler_stats_test.go b/server/internal/handlers/sites/handler_stats_test.go index 992d105d1..d15f2579b 100644 --- a/server/internal/handlers/sites/handler_stats_test.go +++ b/server/internal/handlers/sites/handler_stats_test.go @@ -69,7 +69,7 @@ func newStatsHandler(t *testing.T) *statsHarness { ) deviceQueryer := &fakeDeviceQueryer{} telemetry := &fakeTelemetryCollector{} - svc := sites.NewService(siteStore, buildingStore, deviceQueryer, telemetry, tx, nil) + svc := sites.NewService(siteStore, buildingStore, nil, deviceQueryer, telemetry, tx, nil) return &statsHarness{ handler: NewHandler(svc), siteStore: siteStore, diff --git a/server/internal/handlers/sites/handler_test.go b/server/internal/handlers/sites/handler_test.go index 2bc7c7e31..614b0354a 100644 --- a/server/internal/handlers/sites/handler_test.go +++ b/server/internal/handlers/sites/handler_test.go @@ -14,6 +14,7 @@ import ( "github.com/block/proto-fleet/server/internal/domain/fleeterror" "github.com/block/proto-fleet/server/internal/domain/sites" "github.com/block/proto-fleet/server/internal/domain/sites/models" + "github.com/block/proto-fleet/server/internal/domain/stores/interfaces" "github.com/block/proto-fleet/server/internal/domain/stores/interfaces/mocks" "github.com/block/proto-fleet/server/internal/handlers/handlerstest" ) @@ -23,16 +24,18 @@ import ( // the service's logActivity guards against that path so audit fire-and- // forget no-ops in tests. type testHarness struct { - handler *Handler - siteStore *mocks.MockSiteStore - tx *mocks.MockTransactor - ctrl *gomock.Controller + handler *Handler + siteStore *mocks.MockSiteStore + collectionStore *mocks.MockCollectionStore + tx *mocks.MockTransactor + ctrl *gomock.Controller } func newTestHandler(t *testing.T) *testHarness { t.Helper() ctrl := gomock.NewController(t) siteStore := mocks.NewMockSiteStore(ctrl) + collectionStore := mocks.NewMockCollectionStore(ctrl) tx := mocks.NewMockTransactor(ctrl) // RunInTx fake: runs the closure inline so cascade calls land // against the mock store without a real DB. @@ -43,12 +46,13 @@ func newTestHandler(t *testing.T) *testHarness { ) // GetSiteStats isn't exercised by these tests; pass nil for the // stats-only dependencies and rely on the service's nil-guard. - svc := sites.NewService(siteStore, nil, nil, nil, tx, nil) + svc := sites.NewService(siteStore, nil, collectionStore, nil, nil, tx, nil) return &testHarness{ - handler: NewHandler(svc), - siteStore: siteStore, - tx: tx, - ctrl: ctrl, + handler: NewHandler(svc), + siteStore: siteStore, + collectionStore: collectionStore, + tx: tx, + ctrl: ctrl, } } @@ -341,3 +345,101 @@ func TestHandler_AssignBuildingsToSite_bulkAggregatesCascadeCounts(t *testing.T) assert.Equal(t, int64(6), resp.Msg.GetReassignedRackCount()) assert.Equal(t, int64(30), resp.Msg.GetReassignedDeviceCount()) } + +func TestHandler_AssignRacksToSite_partialUpdateCascadesAndClearsBuilding(t *testing.T) { + t.Parallel() + h := newTestHandler(t) + + target := int64(20) + rackID := int64(50) + priorSite := int64(9) + priorBuilding := int64(11) + + h.siteStore.EXPECT().LockSiteForWrite(gomock.Any(), int64(7), target).Return(nil) + h.collectionStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), rackID, int64(7)). + Return(interfaces.RackPlacement{SiteID: &priorSite, BuildingID: &priorBuilding, Zone: "Z1"}, nil) + // site changes & rack has a building → building clears, zone clears. + h.collectionStore.EXPECT().UpdateRackPlacement(gomock.Any(), rackID, int64(7), &target, (*int64)(nil), "").Return(nil) + h.collectionStore.EXPECT().CascadeRackDeviceSites(gomock.Any(), rackID, int64(7), &target).Return(int64(8), nil) + + resp, err := h.handler.AssignRacksToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignRacksToSiteRequest{ + RackIds: []int64{rackID}, + TargetSiteId: &target, + })) + require.NoError(t, err) + assert.Equal(t, int64(8), resp.Msg.GetReassignedDeviceCount()) + assert.Equal(t, int64(1), resp.Msg.GetClearedBuildingCount()) +} + +func TestHandler_AssignRacksToSite_targetUnsetUnassigns(t *testing.T) { + t.Parallel() + h := newTestHandler(t) + + rackID := int64(50) + priorSite := int64(9) + + // target_site_id unset → no LockSiteForWrite (no target to lock). + h.collectionStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), rackID, int64(7)). + Return(interfaces.RackPlacement{SiteID: &priorSite}, nil) // no building set + // site changes (priorSite → nil) but no building to clear. + h.collectionStore.EXPECT().UpdateRackPlacement(gomock.Any(), rackID, int64(7), gomock.Nil(), gomock.Nil(), "").Return(nil) + h.collectionStore.EXPECT().CascadeRackDeviceSites(gomock.Any(), rackID, int64(7), gomock.Nil()).Return(int64(3), nil) + + resp, err := h.handler.AssignRacksToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignRacksToSiteRequest{ + RackIds: []int64{rackID}, + })) + require.NoError(t, err) + assert.Equal(t, int64(3), resp.Msg.GetReassignedDeviceCount()) + assert.Equal(t, int64(0), resp.Msg.GetClearedBuildingCount()) +} + +func TestHandler_AssignRacksToSite_sameSiteIsNoOp(t *testing.T) { + t.Parallel() + h := newTestHandler(t) + + target := int64(20) + rackID := int64(50) + + h.siteStore.EXPECT().LockSiteForWrite(gomock.Any(), int64(7), target).Return(nil) + // rack already at target site, has a building — no clear, no cascade. + h.collectionStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), rackID, int64(7)). + Return(interfaces.RackPlacement{SiteID: &target, BuildingID: ptrInt64(11), Zone: "Z1"}, nil) + h.collectionStore.EXPECT().UpdateRackPlacement(gomock.Any(), rackID, int64(7), &target, ptrInt64(11), "Z1").Return(nil) + // No CascadeRackDeviceSites — siteChanged is false. + + resp, err := h.handler.AssignRacksToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignRacksToSiteRequest{ + RackIds: []int64{rackID}, + TargetSiteId: &target, + })) + require.NoError(t, err) + assert.Equal(t, int64(0), resp.Msg.GetReassignedDeviceCount()) + assert.Equal(t, int64(0), resp.Msg.GetClearedBuildingCount()) +} + +func TestHandler_AssignRacksToSite_bulkAggregates(t *testing.T) { + t.Parallel() + h := newTestHandler(t) + + target := int64(20) + priorSite := int64(9) + + h.siteStore.EXPECT().LockSiteForWrite(gomock.Any(), int64(7), target).Return(nil) + // Two racks, processed in sorted id order. + h.collectionStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), int64(50), int64(7)). + Return(interfaces.RackPlacement{SiteID: &priorSite, BuildingID: ptrInt64(11)}, nil) + h.collectionStore.EXPECT().UpdateRackPlacement(gomock.Any(), int64(50), int64(7), &target, (*int64)(nil), "").Return(nil) + h.collectionStore.EXPECT().CascadeRackDeviceSites(gomock.Any(), int64(50), int64(7), &target).Return(int64(4), nil) + h.collectionStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), int64(51), int64(7)). + Return(interfaces.RackPlacement{SiteID: &priorSite}, nil) // no building + h.collectionStore.EXPECT().UpdateRackPlacement(gomock.Any(), int64(51), int64(7), &target, gomock.Nil(), "").Return(nil) + h.collectionStore.EXPECT().CascadeRackDeviceSites(gomock.Any(), int64(51), int64(7), &target).Return(int64(2), nil) + + // IDs passed out-of-order to verify the sort happens. + resp, err := h.handler.AssignRacksToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignRacksToSiteRequest{ + RackIds: []int64{51, 50}, + TargetSiteId: &target, + })) + require.NoError(t, err) + assert.Equal(t, int64(6), resp.Msg.GetReassignedDeviceCount()) + assert.Equal(t, int64(1), resp.Msg.GetClearedBuildingCount(), "only rack with prior building counts") +} From 37117e1520a3f776622c6589b466a014f2790123 Mon Sep 17 00:00:00 2001 From: flesher Date: Thu, 11 Jun 2026 12:17:52 -0500 Subject: [PATCH 6/9] chore(client): regen with protoc-gen-es 2.12.0 + prettier fix CI's generated-code check failed because the local regen on the naming-normalization commits used protoc-gen-es 2.11.0 (a sibling worktree's cached node_modules) instead of the pinned 2.12.0. The 2.12.0 output annotates optional Timestamp fields as `Timestamp | undefined` rather than `Timestamp?`. Also fixes a prettier ternary formatting issue prettier --write applied to MinerReparentPicker.tsx during CI's gen pass that didn't run locally. No semantic change. --- .../generated/device_set/v1/device_set_pb.ts | 50 ++++---- .../api/generated/sites/v1/sites_pb.ts | 109 ++++++++++-------- .../MinerActionsMenu/MinerReparentPicker.tsx | 4 +- 3 files changed, 84 insertions(+), 79 deletions(-) diff --git a/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts b/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts index 946e475e8..861125d6d 100644 --- a/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts +++ b/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" +// @generated by protoc-gen-es v2.12.0 with parameter "target=ts" // @generated from file device_set/v1/device_set.proto (package device_set.v1, syntax proto3) /* eslint-disable */ @@ -80,14 +80,14 @@ export type DeviceSet = Message<"device_set.v1.DeviceSet"> & { * * @generated from field: google.protobuf.Timestamp created_at = 6; */ - createdAt?: Timestamp; + createdAt?: Timestamp | undefined; /** * When the device set was last updated * * @generated from field: google.protobuf.Timestamp updated_at = 7; */ - updatedAt?: Timestamp; + updatedAt?: Timestamp | undefined; /** * Type-specific metadata (enforces only one detail type is set) @@ -171,7 +171,7 @@ export type RackInfo = Message<"device_set.v1.RackInfo"> & { * * @generated from field: optional int64 site_id = 6; */ - siteId?: bigint; + siteId?: bigint | undefined; /** * Building this rack belongs to. Unset = directly under site_id (or @@ -180,7 +180,7 @@ export type RackInfo = Message<"device_set.v1.RackInfo"> & { * * @generated from field: optional int64 building_id = 7; */ - buildingId?: bigint; + buildingId?: bigint | undefined; }; /** @@ -221,7 +221,7 @@ export type DeviceSetMember = Message<"device_set.v1.DeviceSetMember"> & { * * @generated from field: google.protobuf.Timestamp added_at = 2; */ - addedAt?: Timestamp; + addedAt?: Timestamp | undefined; /** * Type-specific member details @@ -258,7 +258,7 @@ export type RackMemberDetails = Message<"device_set.v1.RackMemberDetails"> & { * * @generated from field: device_set.v1.RackSlotPosition slot_position = 1; */ - slotPosition?: RackSlotPosition; + slotPosition?: RackSlotPosition | undefined; }; /** @@ -353,7 +353,7 @@ export type CreateDeviceSetRequest = Message<"device_set.v1.CreateDeviceSetReque * * @generated from field: optional common.v1.DeviceSelector device_selector = 6; */ - deviceSelector?: DeviceSelector; + deviceSelector?: DeviceSelector | undefined; }; /** @@ -375,7 +375,7 @@ export type CreateDeviceSetResponse = Message<"device_set.v1.CreateDeviceSetResp * * @generated from field: device_set.v1.DeviceSet device_set = 1; */ - deviceSet?: DeviceSet; + deviceSet?: DeviceSet | undefined; /** * Number of devices added to the device set (0 if no device_selector was provided) @@ -426,7 +426,7 @@ export type GetDeviceSetResponse = Message<"device_set.v1.GetDeviceSetResponse"> * * @generated from field: device_set.v1.DeviceSet device_set = 1; */ - deviceSet?: DeviceSet; + deviceSet?: DeviceSet | undefined; }; /** @@ -455,7 +455,7 @@ export type UpdateDeviceSetRequest = Message<"device_set.v1.UpdateDeviceSetReque * * @generated from field: optional string label = 2; */ - label?: string; + label?: string | undefined; /** * New description (optional, max 500 characters if provided). @@ -463,7 +463,7 @@ export type UpdateDeviceSetRequest = Message<"device_set.v1.UpdateDeviceSetReque * * @generated from field: optional string description = 3; */ - description?: string; + description?: string | undefined; /** * Type-specific metadata updates (only applicable for the device set's type) @@ -492,7 +492,7 @@ export type UpdateDeviceSetRequest = Message<"device_set.v1.UpdateDeviceSetReque * * @generated from field: common.v1.DeviceSelector device_selector = 6; */ - deviceSelector?: DeviceSelector; + deviceSelector?: DeviceSelector | undefined; }; /** @@ -514,7 +514,7 @@ export type UpdateDeviceSetResponse = Message<"device_set.v1.UpdateDeviceSetResp * * @generated from field: device_set.v1.DeviceSet device_set = 1; */ - deviceSet?: DeviceSet; + deviceSet?: DeviceSet | undefined; }; /** @@ -597,7 +597,7 @@ export type ListDeviceSetsRequest = Message<"device_set.v1.ListDeviceSetsRequest * * @generated from field: common.v1.SortConfig sort = 4; */ - sort?: SortConfig; + sort?: SortConfig | undefined; /** * Filter by device sets containing devices with open errors of these component types. @@ -722,7 +722,7 @@ export type AddDevicesToDeviceSetRequest = Message<"device_set.v1.AddDevicesToDe * * @generated from field: common.v1.DeviceSelector device_selector = 2; */ - deviceSelector?: DeviceSelector; + deviceSelector?: DeviceSelector | undefined; }; /** @@ -788,7 +788,7 @@ export type RemoveDevicesFromDeviceSetRequest = Message<"device_set.v1.RemoveDev * * @generated from field: common.v1.DeviceSelector device_selector = 2; */ - deviceSelector?: DeviceSelector; + deviceSelector?: DeviceSelector | undefined; }; /** @@ -962,7 +962,7 @@ export type SetRackSlotPositionRequest = Message<"device_set.v1.SetRackSlotPosit * * @generated from field: device_set.v1.RackSlotPosition position = 3; */ - position?: RackSlotPosition; + position?: RackSlotPosition | undefined; }; /** @@ -991,7 +991,7 @@ export type SetRackSlotPositionResponse = Message<"device_set.v1.SetRackSlotPosi * * @generated from field: device_set.v1.RackSlot slot = 2; */ - slot?: RackSlot; + slot?: RackSlot | undefined; }; /** @@ -1086,7 +1086,7 @@ export type RackSlot = Message<"device_set.v1.RackSlot"> & { * * @generated from field: device_set.v1.RackSlotPosition position = 2; */ - position?: RackSlotPosition; + position?: RackSlotPosition | undefined; }; /** @@ -1508,7 +1508,7 @@ export type SaveRackRequest = Message<"device_set.v1.SaveRackRequest"> & { * * @generated from field: optional int64 device_set_id = 1; */ - deviceSetId?: bigint; + deviceSetId?: bigint | undefined; /** * Label for the rack (required, 1-100 characters) @@ -1522,7 +1522,7 @@ export type SaveRackRequest = Message<"device_set.v1.SaveRackRequest"> & { * * @generated from field: device_set.v1.RackInfo rack_info = 3; */ - rackInfo?: RackInfo; + rackInfo?: RackInfo | undefined; /** * Devices that should be members of this rack. @@ -1530,7 +1530,7 @@ export type SaveRackRequest = Message<"device_set.v1.SaveRackRequest"> & { * * @generated from field: common.v1.DeviceSelector device_selector = 4; */ - deviceSelector?: DeviceSelector; + deviceSelector?: DeviceSelector | undefined; /** * Slot assignments for devices within the rack. @@ -1561,7 +1561,7 @@ export type SaveRackResponse = Message<"device_set.v1.SaveRackResponse"> & { * * @generated from field: device_set.v1.DeviceSet device_set = 1; */ - deviceSet?: DeviceSet; + deviceSet?: DeviceSet | undefined; /** * Number of slot positions assigned @@ -1596,7 +1596,7 @@ export type AssignDevicesToRackRequest = Message<"device_set.v1.AssignDevicesToR * * @generated from field: optional int64 target_rack_id = 1; */ - targetRackId?: bigint; + targetRackId?: bigint | undefined; /** * @generated from field: repeated string device_identifiers = 2; diff --git a/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts b/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts index 8b793189b..2d0ecad55 100644 --- a/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts +++ b/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts @@ -1,4 +1,4 @@ -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" +// @generated by protoc-gen-es v2.12.0 with parameter "target=ts" // @generated from file sites/v1/sites.proto (package sites.v1, syntax proto3) /* eslint-disable */ @@ -12,8 +12,12 @@ import type { Message } from "@bufbuild/protobuf"; /** * Describes the file sites/v1/sites.proto. */ -export const file_sites_v1_sites: GenFile = /*@__PURE__*/ - fileDesc("ChRzaXRlcy92MS9zaXRlcy5wcm90bxIIc2l0ZXMudjEi4gIKBFNpdGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIVCg1sb2NhdGlvbl9jaXR5GAQgASgJEhYKDmxvY2F0aW9uX3N0YXRlGAUgASgJEhAKCHRpbWV6b25lGAYgASgJEhkKEXBvd2VyX2NhcGFjaXR5X213GAcgASgBEhYKDm5ldHdvcmtfY29uZmlnGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB2FkZHJlc3MYCyABKAkSEwoLcG9zdGFsX2NvZGUYDSABKAkSDwoHY291bnRyeRgOIAEoCRINCgVub3RlcxgPIAEoCUoECAMQBEoECAwQDVILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIicAoOU2l0ZVdpdGhDb3VudHMSHAoEc2l0ZRgBIAEoCzIOLnNpdGVzLnYxLlNpdGUSFAoMZGV2aWNlX2NvdW50GAIgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAMgASgDEhIKCnJhY2tfY291bnQYBCABKAMiEgoQTGlzdFNpdGVzUmVxdWVzdCI8ChFMaXN0U2l0ZXNSZXNwb25zZRInCgVzaXRlcxgBIAMoCzIYLnNpdGVzLnYxLlNpdGVXaXRoQ291bnRzIu0CChFDcmVhdGVTaXRlUmVxdWVzdBIYCgRuYW1lGAEgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYAyABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAQgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgFIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgGIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYByABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAggASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgKIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAsgASgJQge6SARyAhgCEhcKBW5vdGVzGAwgASgJQgi6SAVyAxiAIEoECAIQA0oECAkQClILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSQ3JlYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIoIDChFVcGRhdGVTaXRlUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYBCABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAUgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgGIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgHIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYCCABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAkgASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgLIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAwgASgJQge6SARyAhgCEhcKBW5vdGVzGA0gASgJQgi6SAVyAxiAIEoECAMQBEoECAoQC1ILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSVXBkYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIigKEURlbGV0ZVNpdGVSZXF1ZXN0EhMKAmlkGAEgASgDQge6SAQiAiAAInQKEkRlbGV0ZVNpdGVSZXNwb25zZRIfChd1bmFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZkZWxldGVkX2J1aWxkaW5nX2NvdW50GAIgASgDEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgDIAEoAyKHAQoaQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QSJAoOdGFyZ2V0X3NpdGVfaWQYASABKANCB7pIBCICIABIAIgBARIwChJkZXZpY2VfaWRlbnRpZmllcnMYAiADKAlCFLpIEZIBDggBEJBOIgdyBRABGIACQhEKD190YXJnZXRfc2l0ZV9pZCJ+ChFQZXJEZXZpY2VDb25mbGljdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCgZyZWFzb24YAiABKA4yIS5zaXRlcy52MS5QZXJEZXZpY2VDb25mbGljdFJlYXNvbhIbChNjb25mbGljdGluZ19zaXRlX2lkGAMgASgDImcKG0Fzc2lnbkRldmljZXNUb1NpdGVSZXNwb25zZRIYChByZWFzc2lnbmVkX2NvdW50GAEgASgDEi4KCWNvbmZsaWN0cxgCIAMoCzIbLnNpdGVzLnYxLlBlckRldmljZUNvbmZsaWN0IoABChxBc3NpZ25CdWlsZGluZ3NUb1NpdGVSZXF1ZXN0EicKDGJ1aWxkaW5nX2lkcxgBIAMoA0IRukgOkgELCAEQ6AciBCICIAASJAoOdGFyZ2V0X3NpdGVfaWQYAiABKANCB7pIBCICIABIAIgBAUIRCg9fdGFyZ2V0X3NpdGVfaWQiXwodQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVzcG9uc2USHQoVcmVhc3NpZ25lZF9yYWNrX2NvdW50GAEgASgDEh8KF3JlYXNzaWduZWRfZGV2aWNlX2NvdW50GAIgASgDIngKGEFzc2lnblJhY2tzVG9TaXRlUmVxdWVzdBIjCghyYWNrX2lkcxgBIAMoA0IRukgOkgELCAEQ6AciBCICIAASJAoOdGFyZ2V0X3NpdGVfaWQYAiABKANCB7pIBCICIABIAIgBAUIRCg9fdGFyZ2V0X3NpdGVfaWQiXAoZQXNzaWduUmFja3NUb1NpdGVSZXNwb25zZRIfChdyZWFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZjbGVhcmVkX2J1aWxkaW5nX2NvdW50GAIgASgDIi8KE0dldFNpdGVTdGF0c1JlcXVlc3QSGAoHc2l0ZV9pZBgBIAEoA0IHukgEIgIgACL/AgoUR2V0U2l0ZVN0YXRzUmVzcG9uc2USDwoHc2l0ZV9pZBgBIAEoAxIWCg5idWlsZGluZ19jb3VudBgCIAEoBRIUCgxkZXZpY2VfY291bnQYAyABKAUSFwoPcmVwb3J0aW5nX2NvdW50GAQgASgFEhoKEnRvdGFsX2hhc2hyYXRlX3RocxgFIAEoARIaChJhdmdfZWZmaWNpZW5jeV9qdGgYBiABKAESFgoOdG90YWxfcG93ZXJfa3cYByABKAESFQoNaGFzaGluZ19jb3VudBgIIAEoBRIUCgxicm9rZW5fY291bnQYCSABKAUSFQoNb2ZmbGluZV9jb3VudBgKIAEoBRIWCg5zbGVlcGluZ19jb3VudBgLIAEoBRIgChhoYXNocmF0ZV9yZXBvcnRpbmdfY291bnQYDCABKAUSIgoaZWZmaWNpZW5jeV9yZXBvcnRpbmdfY291bnQYDSABKAUSHQoVcG93ZXJfcmVwb3J0aW5nX2NvdW50GA4gASgFKrMBChdQZXJEZXZpY2VDb25mbGljdFJlYXNvbhIqCiZQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9VTlNQRUNJRklFRBAAEi8KK1BFUl9ERVZJQ0VfQ09ORkxJQ1RfUkVBU09OX0RFVklDRV9OT1RfRk9VTkQQARI7CjdQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9ERVZJQ0VfSU5fUkFDS19BVF9PVEhFUl9TSVRFEAIyqQUKC1NpdGVTZXJ2aWNlEkQKCUxpc3RTaXRlcxIaLnNpdGVzLnYxLkxpc3RTaXRlc1JlcXVlc3QaGy5zaXRlcy52MS5MaXN0U2l0ZXNSZXNwb25zZRJHCgpDcmVhdGVTaXRlEhsuc2l0ZXMudjEuQ3JlYXRlU2l0ZVJlcXVlc3QaHC5zaXRlcy52MS5DcmVhdGVTaXRlUmVzcG9uc2USRwoKVXBkYXRlU2l0ZRIbLnNpdGVzLnYxLlVwZGF0ZVNpdGVSZXF1ZXN0Ghwuc2l0ZXMudjEuVXBkYXRlU2l0ZVJlc3BvbnNlEkcKCkRlbGV0ZVNpdGUSGy5zaXRlcy52MS5EZWxldGVTaXRlUmVxdWVzdBocLnNpdGVzLnYxLkRlbGV0ZVNpdGVSZXNwb25zZRJiChNBc3NpZ25EZXZpY2VzVG9TaXRlEiQuc2l0ZXMudjEuQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QaJS5zaXRlcy52MS5Bc3NpZ25EZXZpY2VzVG9TaXRlUmVzcG9uc2USaAoVQXNzaWduQnVpbGRpbmdzVG9TaXRlEiYuc2l0ZXMudjEuQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVxdWVzdBonLnNpdGVzLnYxLkFzc2lnbkJ1aWxkaW5nc1RvU2l0ZVJlc3BvbnNlElwKEUFzc2lnblJhY2tzVG9TaXRlEiIuc2l0ZXMudjEuQXNzaWduUmFja3NUb1NpdGVSZXF1ZXN0GiMuc2l0ZXMudjEuQXNzaWduUmFja3NUb1NpdGVSZXNwb25zZRJNCgxHZXRTaXRlU3RhdHMSHS5zaXRlcy52MS5HZXRTaXRlU3RhdHNSZXF1ZXN0Gh4uc2l0ZXMudjEuR2V0U2l0ZVN0YXRzUmVzcG9uc2VCoAEKDGNvbS5zaXRlcy52MUIKU2l0ZXNQcm90b1ABWkNnaXRodWIuY29tL2Jsb2NrL3Byb3RvLWZsZWV0L3NlcnZlci9nZW5lcmF0ZWQvZ3JwYy9zaXRlcy92MTtzaXRlc3YxogIDU1hYqgIIU2l0ZXMuVjHKAghTaXRlc1xWMeICFFNpdGVzXFYxXEdQQk1ldGFkYXRh6gIJU2l0ZXM6OlYxYgZwcm90bzM", [file_buf_validate_validate, file_google_protobuf_timestamp]); +export const file_sites_v1_sites: GenFile = + /*@__PURE__*/ + fileDesc( + "ChRzaXRlcy92MS9zaXRlcy5wcm90bxIIc2l0ZXMudjEi4gIKBFNpdGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIVCg1sb2NhdGlvbl9jaXR5GAQgASgJEhYKDmxvY2F0aW9uX3N0YXRlGAUgASgJEhAKCHRpbWV6b25lGAYgASgJEhkKEXBvd2VyX2NhcGFjaXR5X213GAcgASgBEhYKDm5ldHdvcmtfY29uZmlnGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB2FkZHJlc3MYCyABKAkSEwoLcG9zdGFsX2NvZGUYDSABKAkSDwoHY291bnRyeRgOIAEoCRINCgVub3RlcxgPIAEoCUoECAMQBEoECAwQDVILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIicAoOU2l0ZVdpdGhDb3VudHMSHAoEc2l0ZRgBIAEoCzIOLnNpdGVzLnYxLlNpdGUSFAoMZGV2aWNlX2NvdW50GAIgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAMgASgDEhIKCnJhY2tfY291bnQYBCABKAMiEgoQTGlzdFNpdGVzUmVxdWVzdCI8ChFMaXN0U2l0ZXNSZXNwb25zZRInCgVzaXRlcxgBIAMoCzIYLnNpdGVzLnYxLlNpdGVXaXRoQ291bnRzIu0CChFDcmVhdGVTaXRlUmVxdWVzdBIYCgRuYW1lGAEgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYAyABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAQgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgFIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgGIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYByABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAggASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgKIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAsgASgJQge6SARyAhgCEhcKBW5vdGVzGAwgASgJQgi6SAVyAxiAIEoECAIQA0oECAkQClILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSQ3JlYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIoIDChFVcGRhdGVTaXRlUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYBCABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAUgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgGIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgHIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYCCABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAkgASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgLIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAwgASgJQge6SARyAhgCEhcKBW5vdGVzGA0gASgJQgi6SAVyAxiAIEoECAMQBEoECAoQC1ILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSVXBkYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIigKEURlbGV0ZVNpdGVSZXF1ZXN0EhMKAmlkGAEgASgDQge6SAQiAiAAInQKEkRlbGV0ZVNpdGVSZXNwb25zZRIfChd1bmFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZkZWxldGVkX2J1aWxkaW5nX2NvdW50GAIgASgDEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgDIAEoAyKHAQoaQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QSJAoOdGFyZ2V0X3NpdGVfaWQYASABKANCB7pIBCICIABIAIgBARIwChJkZXZpY2VfaWRlbnRpZmllcnMYAiADKAlCFLpIEZIBDggBEJBOIgdyBRABGIACQhEKD190YXJnZXRfc2l0ZV9pZCJ+ChFQZXJEZXZpY2VDb25mbGljdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCgZyZWFzb24YAiABKA4yIS5zaXRlcy52MS5QZXJEZXZpY2VDb25mbGljdFJlYXNvbhIbChNjb25mbGljdGluZ19zaXRlX2lkGAMgASgDImcKG0Fzc2lnbkRldmljZXNUb1NpdGVSZXNwb25zZRIYChByZWFzc2lnbmVkX2NvdW50GAEgASgDEi4KCWNvbmZsaWN0cxgCIAMoCzIbLnNpdGVzLnYxLlBlckRldmljZUNvbmZsaWN0IoABChxBc3NpZ25CdWlsZGluZ3NUb1NpdGVSZXF1ZXN0EicKDGJ1aWxkaW5nX2lkcxgBIAMoA0IRukgOkgELCAEQ6AciBCICIAASJAoOdGFyZ2V0X3NpdGVfaWQYAiABKANCB7pIBCICIABIAIgBAUIRCg9fdGFyZ2V0X3NpdGVfaWQiXwodQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVzcG9uc2USHQoVcmVhc3NpZ25lZF9yYWNrX2NvdW50GAEgASgDEh8KF3JlYXNzaWduZWRfZGV2aWNlX2NvdW50GAIgASgDIngKGEFzc2lnblJhY2tzVG9TaXRlUmVxdWVzdBIjCghyYWNrX2lkcxgBIAMoA0IRukgOkgELCAEQ6AciBCICIAASJAoOdGFyZ2V0X3NpdGVfaWQYAiABKANCB7pIBCICIABIAIgBAUIRCg9fdGFyZ2V0X3NpdGVfaWQiXAoZQXNzaWduUmFja3NUb1NpdGVSZXNwb25zZRIfChdyZWFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZjbGVhcmVkX2J1aWxkaW5nX2NvdW50GAIgASgDIi8KE0dldFNpdGVTdGF0c1JlcXVlc3QSGAoHc2l0ZV9pZBgBIAEoA0IHukgEIgIgACL/AgoUR2V0U2l0ZVN0YXRzUmVzcG9uc2USDwoHc2l0ZV9pZBgBIAEoAxIWCg5idWlsZGluZ19jb3VudBgCIAEoBRIUCgxkZXZpY2VfY291bnQYAyABKAUSFwoPcmVwb3J0aW5nX2NvdW50GAQgASgFEhoKEnRvdGFsX2hhc2hyYXRlX3RocxgFIAEoARIaChJhdmdfZWZmaWNpZW5jeV9qdGgYBiABKAESFgoOdG90YWxfcG93ZXJfa3cYByABKAESFQoNaGFzaGluZ19jb3VudBgIIAEoBRIUCgxicm9rZW5fY291bnQYCSABKAUSFQoNb2ZmbGluZV9jb3VudBgKIAEoBRIWCg5zbGVlcGluZ19jb3VudBgLIAEoBRIgChhoYXNocmF0ZV9yZXBvcnRpbmdfY291bnQYDCABKAUSIgoaZWZmaWNpZW5jeV9yZXBvcnRpbmdfY291bnQYDSABKAUSHQoVcG93ZXJfcmVwb3J0aW5nX2NvdW50GA4gASgFKrMBChdQZXJEZXZpY2VDb25mbGljdFJlYXNvbhIqCiZQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9VTlNQRUNJRklFRBAAEi8KK1BFUl9ERVZJQ0VfQ09ORkxJQ1RfUkVBU09OX0RFVklDRV9OT1RfRk9VTkQQARI7CjdQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9ERVZJQ0VfSU5fUkFDS19BVF9PVEhFUl9TSVRFEAIyqQUKC1NpdGVTZXJ2aWNlEkQKCUxpc3RTaXRlcxIaLnNpdGVzLnYxLkxpc3RTaXRlc1JlcXVlc3QaGy5zaXRlcy52MS5MaXN0U2l0ZXNSZXNwb25zZRJHCgpDcmVhdGVTaXRlEhsuc2l0ZXMudjEuQ3JlYXRlU2l0ZVJlcXVlc3QaHC5zaXRlcy52MS5DcmVhdGVTaXRlUmVzcG9uc2USRwoKVXBkYXRlU2l0ZRIbLnNpdGVzLnYxLlVwZGF0ZVNpdGVSZXF1ZXN0Ghwuc2l0ZXMudjEuVXBkYXRlU2l0ZVJlc3BvbnNlEkcKCkRlbGV0ZVNpdGUSGy5zaXRlcy52MS5EZWxldGVTaXRlUmVxdWVzdBocLnNpdGVzLnYxLkRlbGV0ZVNpdGVSZXNwb25zZRJiChNBc3NpZ25EZXZpY2VzVG9TaXRlEiQuc2l0ZXMudjEuQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QaJS5zaXRlcy52MS5Bc3NpZ25EZXZpY2VzVG9TaXRlUmVzcG9uc2USaAoVQXNzaWduQnVpbGRpbmdzVG9TaXRlEiYuc2l0ZXMudjEuQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVxdWVzdBonLnNpdGVzLnYxLkFzc2lnbkJ1aWxkaW5nc1RvU2l0ZVJlc3BvbnNlElwKEUFzc2lnblJhY2tzVG9TaXRlEiIuc2l0ZXMudjEuQXNzaWduUmFja3NUb1NpdGVSZXF1ZXN0GiMuc2l0ZXMudjEuQXNzaWduUmFja3NUb1NpdGVSZXNwb25zZRJNCgxHZXRTaXRlU3RhdHMSHS5zaXRlcy52MS5HZXRTaXRlU3RhdHNSZXF1ZXN0Gh4uc2l0ZXMudjEuR2V0U2l0ZVN0YXRzUmVzcG9uc2VCoAEKDGNvbS5zaXRlcy52MUIKU2l0ZXNQcm90b1ABWkNnaXRodWIuY29tL2Jsb2NrL3Byb3RvLWZsZWV0L3NlcnZlci9nZW5lcmF0ZWQvZ3JwYy9zaXRlcy92MTtzaXRlc3YxogIDU1hYqgIIU2l0ZXMuVjHKAghTaXRlc1xWMeICFFNpdGVzXFYxXEdQQk1ldGFkYXRh6gIJU2l0ZXM6OlYxYgZwcm90bzM", + [file_buf_validate_validate, file_google_protobuf_timestamp], + ); /** * Site is the read shape returned by Get/List/Create/Update RPCs. @@ -70,12 +74,12 @@ export type Site = Message<"sites.v1.Site"> & { /** * @generated from field: google.protobuf.Timestamp created_at = 9; */ - createdAt?: Timestamp; + createdAt?: Timestamp | undefined; /** * @generated from field: google.protobuf.Timestamp updated_at = 10; */ - updatedAt?: Timestamp; + updatedAt?: Timestamp | undefined; /** * @generated from field: string address = 11; @@ -108,8 +112,7 @@ export type Site = Message<"sites.v1.Site"> & { * Describes the message sites.v1.Site. * Use `create(SiteSchema)` to create a new message. */ -export const SiteSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sites_v1_sites, 0); +export const SiteSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 0); /** * SiteWithCounts pairs a Site with its attachment counts. Returned by @@ -121,7 +124,7 @@ export type SiteWithCounts = Message<"sites.v1.SiteWithCounts"> & { /** * @generated from field: sites.v1.Site site = 1; */ - site?: Site; + site?: Site | undefined; /** * @generated from field: int64 device_count = 2; @@ -143,21 +146,18 @@ export type SiteWithCounts = Message<"sites.v1.SiteWithCounts"> & { * Describes the message sites.v1.SiteWithCounts. * Use `create(SiteWithCountsSchema)` to create a new message. */ -export const SiteWithCountsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sites_v1_sites, 1); +export const SiteWithCountsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 1); /** * @generated from message sites.v1.ListSitesRequest */ -export type ListSitesRequest = Message<"sites.v1.ListSitesRequest"> & { -}; +export type ListSitesRequest = Message<"sites.v1.ListSitesRequest"> & {}; /** * Describes the message sites.v1.ListSitesRequest. * Use `create(ListSitesRequestSchema)` to create a new message. */ -export const ListSitesRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sites_v1_sites, 2); +export const ListSitesRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 2); /** * @generated from message sites.v1.ListSitesResponse @@ -173,8 +173,7 @@ export type ListSitesResponse = Message<"sites.v1.ListSitesResponse"> & { * Describes the message sites.v1.ListSitesResponse. * Use `create(ListSitesResponseSchema)` to create a new message. */ -export const ListSitesResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sites_v1_sites, 3); +export const ListSitesResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 3); /** * @generated from message sites.v1.CreateSiteRequest @@ -250,8 +249,7 @@ export type CreateSiteRequest = Message<"sites.v1.CreateSiteRequest"> & { * Describes the message sites.v1.CreateSiteRequest. * Use `create(CreateSiteRequestSchema)` to create a new message. */ -export const CreateSiteRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sites_v1_sites, 4); +export const CreateSiteRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 4); /** * @generated from message sites.v1.CreateSiteResponse @@ -260,7 +258,7 @@ export type CreateSiteResponse = Message<"sites.v1.CreateSiteResponse"> & { /** * @generated from field: sites.v1.Site site = 1; */ - site?: Site; + site?: Site | undefined; /** * network_config_warnings carries cross-site overlap warnings or @@ -276,7 +274,8 @@ export type CreateSiteResponse = Message<"sites.v1.CreateSiteResponse"> & { * Describes the message sites.v1.CreateSiteResponse. * Use `create(CreateSiteResponseSchema)` to create a new message. */ -export const CreateSiteResponseSchema: GenMessage = /*@__PURE__*/ +export const CreateSiteResponseSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 5); /** @@ -343,8 +342,7 @@ export type UpdateSiteRequest = Message<"sites.v1.UpdateSiteRequest"> & { * Describes the message sites.v1.UpdateSiteRequest. * Use `create(UpdateSiteRequestSchema)` to create a new message. */ -export const UpdateSiteRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sites_v1_sites, 6); +export const UpdateSiteRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 6); /** * @generated from message sites.v1.UpdateSiteResponse @@ -353,7 +351,7 @@ export type UpdateSiteResponse = Message<"sites.v1.UpdateSiteResponse"> & { /** * @generated from field: sites.v1.Site site = 1; */ - site?: Site; + site?: Site | undefined; /** * @generated from field: repeated string network_config_warnings = 2; @@ -365,7 +363,8 @@ export type UpdateSiteResponse = Message<"sites.v1.UpdateSiteResponse"> & { * Describes the message sites.v1.UpdateSiteResponse. * Use `create(UpdateSiteResponseSchema)` to create a new message. */ -export const UpdateSiteResponseSchema: GenMessage = /*@__PURE__*/ +export const UpdateSiteResponseSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 7); /** @@ -382,8 +381,7 @@ export type DeleteSiteRequest = Message<"sites.v1.DeleteSiteRequest"> & { * Describes the message sites.v1.DeleteSiteRequest. * Use `create(DeleteSiteRequestSchema)` to create a new message. */ -export const DeleteSiteRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_sites_v1_sites, 8); +export const DeleteSiteRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_sites_v1_sites, 8); /** * @generated from message sites.v1.DeleteSiteResponse @@ -411,7 +409,8 @@ export type DeleteSiteResponse = Message<"sites.v1.DeleteSiteResponse"> & { * Describes the message sites.v1.DeleteSiteResponse. * Use `create(DeleteSiteResponseSchema)` to create a new message. */ -export const DeleteSiteResponseSchema: GenMessage = /*@__PURE__*/ +export const DeleteSiteResponseSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 9); /** @@ -423,7 +422,7 @@ export type AssignDevicesToSiteRequest = Message<"sites.v1.AssignDevicesToSiteRe * * @generated from field: optional int64 target_site_id = 1; */ - targetSiteId?: bigint; + targetSiteId?: bigint | undefined; /** * @generated from field: repeated string device_identifiers = 2; @@ -435,7 +434,8 @@ export type AssignDevicesToSiteRequest = Message<"sites.v1.AssignDevicesToSiteRe * Describes the message sites.v1.AssignDevicesToSiteRequest. * Use `create(AssignDevicesToSiteRequestSchema)` to create a new message. */ -export const AssignDevicesToSiteRequestSchema: GenMessage = /*@__PURE__*/ +export const AssignDevicesToSiteRequestSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 10); /** @@ -469,7 +469,8 @@ export type PerDeviceConflict = Message<"sites.v1.PerDeviceConflict"> & { * Describes the message sites.v1.PerDeviceConflict. * Use `create(PerDeviceConflictSchema)` to create a new message. */ -export const PerDeviceConflictSchema: GenMessage = /*@__PURE__*/ +export const PerDeviceConflictSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 11); /** @@ -493,7 +494,8 @@ export type AssignDevicesToSiteResponse = Message<"sites.v1.AssignDevicesToSiteR * Describes the message sites.v1.AssignDevicesToSiteResponse. * Use `create(AssignDevicesToSiteResponseSchema)` to create a new message. */ -export const AssignDevicesToSiteResponseSchema: GenMessage = /*@__PURE__*/ +export const AssignDevicesToSiteResponseSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 12); /** @@ -510,14 +512,15 @@ export type AssignBuildingsToSiteRequest = Message<"sites.v1.AssignBuildingsToSi * * @generated from field: optional int64 target_site_id = 2; */ - targetSiteId?: bigint; + targetSiteId?: bigint | undefined; }; /** * Describes the message sites.v1.AssignBuildingsToSiteRequest. * Use `create(AssignBuildingsToSiteRequestSchema)` to create a new message. */ -export const AssignBuildingsToSiteRequestSchema: GenMessage = /*@__PURE__*/ +export const AssignBuildingsToSiteRequestSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 13); /** @@ -543,7 +546,8 @@ export type AssignBuildingsToSiteResponse = Message<"sites.v1.AssignBuildingsToS * Describes the message sites.v1.AssignBuildingsToSiteResponse. * Use `create(AssignBuildingsToSiteResponseSchema)` to create a new message. */ -export const AssignBuildingsToSiteResponseSchema: GenMessage = /*@__PURE__*/ +export const AssignBuildingsToSiteResponseSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 14); /** @@ -561,14 +565,15 @@ export type AssignRacksToSiteRequest = Message<"sites.v1.AssignRacksToSiteReques * * @generated from field: optional int64 target_site_id = 2; */ - targetSiteId?: bigint; + targetSiteId?: bigint | undefined; }; /** * Describes the message sites.v1.AssignRacksToSiteRequest. * Use `create(AssignRacksToSiteRequestSchema)` to create a new message. */ -export const AssignRacksToSiteRequestSchema: GenMessage = /*@__PURE__*/ +export const AssignRacksToSiteRequestSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 15); /** @@ -597,7 +602,8 @@ export type AssignRacksToSiteResponse = Message<"sites.v1.AssignRacksToSiteRespo * Describes the message sites.v1.AssignRacksToSiteResponse. * Use `create(AssignRacksToSiteResponseSchema)` to create a new message. */ -export const AssignRacksToSiteResponseSchema: GenMessage = /*@__PURE__*/ +export const AssignRacksToSiteResponseSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 16); /** @@ -614,7 +620,8 @@ export type GetSiteStatsRequest = Message<"sites.v1.GetSiteStatsRequest"> & { * Describes the message sites.v1.GetSiteStatsRequest. * Use `create(GetSiteStatsRequestSchema)` to create a new message. */ -export const GetSiteStatsRequestSchema: GenMessage = /*@__PURE__*/ +export const GetSiteStatsRequestSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 17); /** @@ -709,7 +716,8 @@ export type GetSiteStatsResponse = Message<"sites.v1.GetSiteStatsResponse"> & { * Describes the message sites.v1.GetSiteStatsResponse. * Use `create(GetSiteStatsResponseSchema)` to create a new message. */ -export const GetSiteStatsResponseSchema: GenMessage = /*@__PURE__*/ +export const GetSiteStatsResponseSchema: GenMessage = + /*@__PURE__*/ messageDesc(file_sites_v1_sites, 18); /** @@ -738,7 +746,8 @@ export enum PerDeviceConflictReason { /** * Describes the enum sites.v1.PerDeviceConflictReason. */ -export const PerDeviceConflictReasonSchema: GenEnum = /*@__PURE__*/ +export const PerDeviceConflictReasonSchema: GenEnum = + /*@__PURE__*/ enumDesc(file_sites_v1_sites, 0); /** @@ -761,7 +770,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof ListSitesRequestSchema; output: typeof ListSitesResponseSchema; - }, + }; /** * CreateSite inserts a new site. Name must be unique within the * org. network_config is parsed and canonicalized server-side; @@ -773,7 +782,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof CreateSiteRequestSchema; output: typeof CreateSiteResponseSchema; - }, + }; /** * UpdateSite mutates name + descriptive fields + network_config. * Same canonicalization + validation as CreateSite. @@ -784,7 +793,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof UpdateSiteRequestSchema; output: typeof UpdateSiteResponseSchema; - }, + }; /** * DeleteSite soft-deletes the site and, in the same transaction, * cascades to its buildings (soft-delete) and racks (unassign). @@ -797,7 +806,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof DeleteSiteRequestSchema; output: typeof DeleteSiteResponseSchema; - }, + }; /** * AssignDevicesToSite is an all-or-nothing bulk update of * device.site_id. If any device is in a rack whose site_id differs @@ -811,7 +820,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof AssignDevicesToSiteRequestSchema; output: typeof AssignDevicesToSiteResponseSchema; - }, + }; /** * AssignBuildingsToSite moves one or more buildings to a target site * (or to "Unassigned" when target_site_id is unset). All updates run @@ -826,7 +835,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof AssignBuildingsToSiteRequestSchema; output: typeof AssignBuildingsToSiteResponseSchema; - }, + }; /** * AssignRacksToSite moves one or more racks to a target site (or * to "Unassigned" when target_site_id is unset) as a partial @@ -843,7 +852,7 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof AssignRacksToSiteRequestSchema; output: typeof AssignRacksToSiteResponseSchema; - }, + }; /** * GetSiteStats returns server-rolled telemetry + miner-state counts * for every device assigned to the site, including devices whose @@ -856,7 +865,5 @@ export const SiteService: GenService<{ methodKind: "unary"; input: typeof GetSiteStatsRequestSchema; output: typeof GetSiteStatsResponseSchema; - }, -}> = /*@__PURE__*/ - serviceDesc(file_sites_v1_sites, 0); - + }; +}> = /*@__PURE__*/ serviceDesc(file_sites_v1_sites, 0); diff --git a/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx b/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx index 9d6f46fce..57a374bb8 100644 --- a/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx +++ b/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx @@ -323,9 +323,7 @@ const MinerReparentPicker = ({ ids: string[], minerSnapshots: Record | undefined, ) => - kind === "site" - ? dispatchReparentToSite(targetId, ids, minerSnapshots) - : dispatchReparentToRack(targetId, ids); + kind === "site" ? dispatchReparentToSite(targetId, ids, minerSnapshots) : dispatchReparentToRack(targetId, ids); const settleDialog = () => { const resolve = dialogResolveRef.current; From 093fa81cb4c2c85a180b06a3b313738ea5b84038 Mon Sep 17 00:00:00 2001 From: flesher Date: Mon, 15 Jun 2026 12:10:39 -0700 Subject: [PATCH 7/9] refactor(reparent): close orphan windows, atomic group RPCs, bulk SQL family Cross-cutting review fixes for the atomic reparent PR. Orphan-window closure: - AssignDevicesToSite: optional force_clear_conflicting_rack_membership flag clears rack memberships in the same tx; MinerReparentPicker collapses to a single call - AssignDevicesToRack: source-rack FOR UPDATE lock pre-pass serializes concurrent overlapping reparent calls; LockRackPlacementForWrite reordered before GetCollection so activity-log labels read under lock - RemoveDevicesFromAnyRack: target-rack exclusion predicate prevents cascade-delete of rack_slot rows on no-op reassigns Reparent surface cleanup: - AssignDevicesToRack switched to DeviceSelector - AddDevicesToGroup / RemoveDevicesFromGroup new RPCs replace the generic collection/v1 device-membership pair; type-checked at the service boundary, atomic clear-then-add for racks - collection/v1 AddDevicesToCollection / RemoveDevicesFromCollection RPCs and Service methods removed; fleetimport migrated to the type-specific methods - DeviceSetService added to reflectEnabledServices Bulk SQL batch family (N+1 elimination): - AssignRacksToBuilding: 5N -> 4 writes (constant) - AssignBuildingsToSite: 4N -> N+3 - AssignRacksToSite: 3N -> N+2 - Sequential sorted-order locks preserved (deadlock invariant) ManageBuildingModal collapse: - handleSave two-phase vacate+place replaced by a single RPC - Server-side two-pass ordering (clear-all then place-all in one tx) handles position swaps without the partial-failure window Frontend reliability: - MinerReparentPicker: AbortSignal threading, Promise wrapping, double- invoke guard, fire-and-forget RPCs awaited - useDeviceSets.assignDevicesToRack: AbortSignal support - useSiteModals.detailsSaveEdit: functional setState removes stale- closure guard race - ManageBuildingModal handleSave: savingRef double-invoke guard Validation + prior-comment hygiene: - AssignBuildingsToSite / AssignRacksToSite reject TargetSiteID == 0 - AssignRacksToBuilding rejects duplicate rack_ids - ScopeCount uses len(DeviceIdentifiers) instead of double-counting - Single-rack building-only unassign captures the rack's current SiteID Test coverage: - AssignRacksToSite service-layer tests (cascade, no-op, rollback) - AssignDevicesToRack handler tests (perm, happy, unassign, source-rack lock ordering) - AssignRacksToBuilding rollback + empty-rejected + duplicate-rejected - AssignBuildingsToSite empty-rejected - DeviceSelector variant rejection - AddDevicesToGroup / RemoveDevicesFromGroup rack-target rejection - Bulk-write large-batch stress tests across three RPCs Handler hygiene: - deviceset/translate.go (new) extracts toAssignDevicesToRackParams - sites/translate.go adds toAssignRacksToSiteParams Co-Authored-By: Claude Opus 4.7 --- .../generated/collection/v1/collection_pb.ts | 187 +-- .../generated/device_set/v1/device_set_pb.ts | 152 +- .../api/generated/sites/v1/sites_pb.ts | 19 +- client/src/protoFleet/api/sites.ts | 22 +- client/src/protoFleet/api/useDeviceSets.ts | 109 +- .../ManageBuildingModal.tsx | 85 +- .../MinerActionModalStack.tsx | 8 +- .../MinerActionsMenu/MinerReparentPicker.tsx | 144 +- .../pages/RackOverviewPage.test.tsx | 2 +- .../pages/RackOverviewPage.tsx | 15 +- .../features/sites/hooks/useSiteModals.ts | 17 +- proto/collection/v1/collection.proto | 47 - proto/device_set/v1/device_set.proto | 92 +- proto/sites/v1/sites.proto | 12 + server/cmd/fleetd/main.go | 1 + .../grpc/collection/v1/collection.pb.go | 1291 +++++++---------- .../collectionv1connect/collection.connect.go | 87 +- .../grpc/device_set/v1/device_set.pb.go | 1202 ++++++++------- .../device_setv1connect/device_set.connect.go | 146 +- server/generated/grpc/sites/v1/sites.pb.go | 355 ++--- server/generated/sqlc/building.sql.go | 96 ++ server/generated/sqlc/db.go | 90 ++ server/generated/sqlc/device_set.sql.go | 202 ++- server/generated/sqlc/site.sql.go | 66 + server/internal/domain/buildings/service.go | 174 ++- .../internal/domain/buildings/service_test.go | 306 +++- server/internal/domain/collection/service.go | 241 ++- .../domain/collection/service_test.go | 184 +-- .../internal/domain/fleetimport/importer.go | 50 +- .../domain/foremanimport/service_test.go | 18 +- server/internal/domain/sites/models/models.go | 15 +- server/internal/domain/sites/service.go | 145 +- server/internal/domain/sites/service_test.go | 502 ++++++- .../domain/stores/interfaces/building.go | 11 + .../domain/stores/interfaces/collection.go | 52 +- .../interfaces/mocks/mock_building_store.go | 28 + .../interfaces/mocks/mock_collection_store.go | 66 +- .../interfaces/mocks/mock_site_store.go | 45 + .../internal/domain/stores/interfaces/site.go | 15 + .../domain/stores/sqlstores/building.go | 31 + .../domain/stores/sqlstores/collection.go | 59 +- .../internal/domain/stores/sqlstores/site.go | 48 + .../handlers/buildings/handler_test.go | 13 +- .../internal/handlers/collection/handler.go | 24 - server/internal/handlers/deviceset/handler.go | 41 +- .../handlers/deviceset/handler_test.go | 328 +++++ .../internal/handlers/deviceset/translate.go | 55 + .../handlers/middleware/rpc_permissions.go | 66 +- server/internal/handlers/sites/handler.go | 12 +- .../internal/handlers/sites/handler_test.go | 53 +- server/internal/handlers/sites/translate.go | 20 +- server/sqlc/queries/building.sql | 52 + server/sqlc/queries/device_set.sql | 122 +- server/sqlc/queries/site.sql | 36 + 54 files changed, 4555 insertions(+), 2704 deletions(-) create mode 100644 server/internal/handlers/deviceset/translate.go diff --git a/client/src/protoFleet/api/generated/collection/v1/collection_pb.ts b/client/src/protoFleet/api/generated/collection/v1/collection_pb.ts index 4050e9176..3b654bc29 100644 --- a/client/src/protoFleet/api/generated/collection/v1/collection_pb.ts +++ b/client/src/protoFleet/api/generated/collection/v1/collection_pb.ts @@ -21,7 +21,7 @@ import type { Message } from "@bufbuild/protobuf"; export const file_collection_v1_collection: GenFile = /*@__PURE__*/ fileDesc( - "Ch5jb2xsZWN0aW9uL3YxL2NvbGxlY3Rpb24ucHJvdG8SDWNvbGxlY3Rpb24udjEi0wIKEERldmljZUNvbGxlY3Rpb24SCgoCaWQYASABKAMSKwoEdHlwZRgCIAEoDjIdLmNvbGxlY3Rpb24udjEuQ29sbGVjdGlvblR5cGUSDQoFbGFiZWwYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSFAoMZGV2aWNlX2NvdW50GAUgASgFEi4KCmNyZWF0ZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEiwKCXJhY2tfaW5mbxgIIAEoCzIXLmNvbGxlY3Rpb24udjEuUmFja0luZm9IABIuCgpncm91cF9pbmZvGAkgASgLMhguY29sbGVjdGlvbi52MS5Hcm91cEluZm9IAEIOCgx0eXBlX2RldGFpbHMiiAIKCFJhY2tJbmZvEhUKBHJvd3MYASABKAVCB7pIBBoCIAASGAoHY29sdW1ucxgCIAEoBUIHukgEGgIgABIVCgR6b25lGAMgASgJQge6SARyAhhkEjIKC29yZGVyX2luZGV4GAQgASgOMh0uY29sbGVjdGlvbi52MS5SYWNrT3JkZXJJbmRleBI0Cgxjb29saW5nX3R5cGUYBSABKA4yHi5jb2xsZWN0aW9uLnYxLlJhY2tDb29saW5nVHlwZRIUCgdzaXRlX2lkGAYgASgDSACIAQESGAoLYnVpbGRpbmdfaWQYByABKANIAYgBAUIKCghfc2l0ZV9pZEIOCgxfYnVpbGRpbmdfaWQiCwoJR3JvdXBJbmZvIp8BChBDb2xsZWN0aW9uTWVtYmVyEhkKEWRldmljZV9pZGVudGlmaWVyGAEgASgJEiwKCGFkZGVkX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIwCgRyYWNrGAMgASgLMiAuY29sbGVjdGlvbi52MS5SYWNrTWVtYmVyRGV0YWlsc0gAQhAKDm1lbWJlcl9kZXRhaWxzIksKEVJhY2tNZW1iZXJEZXRhaWxzEjYKDXNsb3RfcG9zaXRpb24YASABKAsyHy5jb2xsZWN0aW9uLnYxLlJhY2tTbG90UG9zaXRpb24iQQoQUmFja1Nsb3RQb3NpdGlvbhIUCgNyb3cYASABKAVCB7pIBBoCKAASFwoGY29sdW1uGAIgASgFQge6SAQaAigAIskCChdDcmVhdGVDb2xsZWN0aW9uUmVxdWVzdBI3CgR0eXBlGAEgASgOMh0uY29sbGVjdGlvbi52MS5Db2xsZWN0aW9uVHlwZUIKukgHggEEEAEgABIbCgVsYWJlbBgCIAEoCUIMukgJyAEBcgQQARhkEh0KC2Rlc2NyaXB0aW9uGAMgASgJQgi6SAVyAxj0AxIsCglyYWNrX2luZm8YBCABKAsyFy5jb2xsZWN0aW9uLnYxLlJhY2tJbmZvSAASLgoKZ3JvdXBfaW5mbxgFIAEoCzIYLmNvbGxlY3Rpb24udjEuR3JvdXBJbmZvSAASNwoPZGV2aWNlX3NlbGVjdG9yGAYgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9ySAGIAQFCDgoMdHlwZV9kZXRhaWxzQhIKEF9kZXZpY2Vfc2VsZWN0b3IiZAoYQ3JlYXRlQ29sbGVjdGlvblJlc3BvbnNlEjMKCmNvbGxlY3Rpb24YASABKAsyHy5jb2xsZWN0aW9uLnYxLkRldmljZUNvbGxlY3Rpb24SEwoLYWRkZWRfY291bnQYAiABKAUiNgoUR2V0Q29sbGVjdGlvblJlcXVlc3QSHgoNY29sbGVjdGlvbl9pZBgBIAEoA0IHukgEIgIgACJMChVHZXRDb2xsZWN0aW9uUmVzcG9uc2USMwoKY29sbGVjdGlvbhgBIAEoCzIfLmNvbGxlY3Rpb24udjEuRGV2aWNlQ29sbGVjdGlvbiK+AgoXVXBkYXRlQ29sbGVjdGlvblJlcXVlc3QSHgoNY29sbGVjdGlvbl9pZBgBIAEoA0IHukgEIgIgABIgCgVsYWJlbBgCIAEoCUIMukgJ2AEBcgQQARhkSAGIAQESJQoLZGVzY3JpcHRpb24YAyABKAlCC7pICNgBAXIDGPQDSAKIAQESLAoJcmFja19pbmZvGAQgASgLMhcuY29sbGVjdGlvbi52MS5SYWNrSW5mb0gAEi4KCmdyb3VwX2luZm8YBSABKAsyGC5jb2xsZWN0aW9uLnYxLkdyb3VwSW5mb0gAEjIKD2RldmljZV9zZWxlY3RvchgGIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckIOCgx0eXBlX2RldGFpbHNCCAoGX2xhYmVsQg4KDF9kZXNjcmlwdGlvbiJPChhVcGRhdGVDb2xsZWN0aW9uUmVzcG9uc2USMwoKY29sbGVjdGlvbhgBIAEoCzIfLmNvbGxlY3Rpb24udjEuRGV2aWNlQ29sbGVjdGlvbiI5ChdEZWxldGVDb2xsZWN0aW9uUmVxdWVzdBIeCg1jb2xsZWN0aW9uX2lkGAEgASgDQge6SAQiAiAAIhoKGERlbGV0ZUNvbGxlY3Rpb25SZXNwb25zZSLsAQoWTGlzdENvbGxlY3Rpb25zUmVxdWVzdBI1CgR0eXBlGAEgASgOMh0uY29sbGVjdGlvbi52MS5Db2xsZWN0aW9uVHlwZUIIukgFggECEAESGgoJcGFnZV9zaXplGAIgASgFQge6SAQaAigAEhIKCnBhZ2VfdG9rZW4YAyABKAkSIwoEc29ydBgEIAEoCzIVLmNvbW1vbi52MS5Tb3J0Q29uZmlnEjcKFWVycm9yX2NvbXBvbmVudF90eXBlcxgFIAMoDjIYLmVycm9ycy52MS5Db21wb25lbnRUeXBlEg0KBXpvbmVzGAYgAygJIn0KF0xpc3RDb2xsZWN0aW9uc1Jlc3BvbnNlEjQKC2NvbGxlY3Rpb25zGAEgAygLMh8uY29sbGVjdGlvbi52MS5EZXZpY2VDb2xsZWN0aW9uEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRITCgt0b3RhbF9jb3VudBgDIAEoBSJ7Ch1BZGREZXZpY2VzVG9Db2xsZWN0aW9uUmVxdWVzdBIeCg1jb2xsZWN0aW9uX2lkGAEgASgDQge6SAQiAiAAEjoKD2RldmljZV9zZWxlY3RvchgCIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckIGukgDyAEBImsKHkFkZERldmljZXNUb0NvbGxlY3Rpb25SZXNwb25zZRIVCg1jb2xsZWN0aW9uX2lkGAEgASgDEhMKC2FkZGVkX2NvdW50GAIgASgFEh0KFXNpdGVfcmVhc3NpZ25lZF9jb3VudBgDIAEoBSKAAQoiUmVtb3ZlRGV2aWNlc0Zyb21Db2xsZWN0aW9uUmVxdWVzdBIeCg1jb2xsZWN0aW9uX2lkGAEgASgDQge6SAQiAiAAEjoKD2RldmljZV9zZWxlY3RvchgCIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckIGukgDyAEBIjwKI1JlbW92ZURldmljZXNGcm9tQ29sbGVjdGlvblJlc3BvbnNlEhUKDXJlbW92ZWRfY291bnQYASABKAUibgocTGlzdENvbGxlY3Rpb25NZW1iZXJzUmVxdWVzdBIeCg1jb2xsZWN0aW9uX2lkGAEgASgDQge6SAQiAiAAEhoKCXBhZ2Vfc2l6ZRgCIAEoBUIHukgEGgIoABISCgpwYWdlX3Rva2VuGAMgASgJImoKHUxpc3RDb2xsZWN0aW9uTWVtYmVyc1Jlc3BvbnNlEjAKB21lbWJlcnMYASADKAsyHy5jb2xsZWN0aW9uLnYxLkNvbGxlY3Rpb25NZW1iZXISFwoPbmV4dF9wYWdlX3Rva2VuGAIgASgJIm4KG0dldERldmljZUNvbGxlY3Rpb25zUmVxdWVzdBIiChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCUIHukgEcgIQARIrCgR0eXBlGAIgASgOMh0uY29sbGVjdGlvbi52MS5Db2xsZWN0aW9uVHlwZSJUChxHZXREZXZpY2VDb2xsZWN0aW9uc1Jlc3BvbnNlEjQKC2NvbGxlY3Rpb25zGAEgAygLMh8uY29sbGVjdGlvbi52MS5EZXZpY2VDb2xsZWN0aW9uIpsBChpTZXRSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBIeCg1jb2xsZWN0aW9uX2lkGAEgASgDQge6SAQiAiAAEiIKEWRldmljZV9pZGVudGlmaWVyGAIgASgJQge6SARyAhABEjkKCHBvc2l0aW9uGAMgASgLMh8uY29sbGVjdGlvbi52MS5SYWNrU2xvdFBvc2l0aW9uQga6SAPIAQEiWwobU2V0UmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlEhUKDWNvbGxlY3Rpb25faWQYASABKAMSJQoEc2xvdBgCIAEoCzIXLmNvbGxlY3Rpb24udjEuUmFja1Nsb3QiYgocQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBIeCg1jb2xsZWN0aW9uX2lkGAEgASgDQge6SAQiAiAAEiIKEWRldmljZV9pZGVudGlmaWVyGAIgASgJQge6SARyAhABIh8KHUNsZWFyUmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlIjUKE0dldFJhY2tTbG90c1JlcXVlc3QSHgoNY29sbGVjdGlvbl9pZBgBIAEoA0IHukgEIgIgACJYCghSYWNrU2xvdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCghwb3NpdGlvbhgCIAEoCzIfLmNvbGxlY3Rpb24udjEuUmFja1Nsb3RQb3NpdGlvbiI+ChRHZXRSYWNrU2xvdHNSZXNwb25zZRImCgVzbG90cxgBIAMoCzIXLmNvbGxlY3Rpb24udjEuUmFja1Nsb3Qi7gQKD0NvbGxlY3Rpb25TdGF0cxIVCg1jb2xsZWN0aW9uX2lkGAEgASgDEhQKDGRldmljZV9jb3VudBgCIAEoBRIXCg9yZXBvcnRpbmdfY291bnQYAyABKAUSGgoSdG90YWxfaGFzaHJhdGVfdGhzGAQgASgBEhoKEmF2Z19lZmZpY2llbmN5X2p0aBgFIAEoARIWCg50b3RhbF9wb3dlcl9rdxgGIAEoARIZChFtaW5fdGVtcGVyYXR1cmVfYxgHIAEoARIZChFtYXhfdGVtcGVyYXR1cmVfYxgIIAEoARIVCg1oYXNoaW5nX2NvdW50GAkgASgFEhQKDGJyb2tlbl9jb3VudBgKIAEoBRIVCg1vZmZsaW5lX2NvdW50GAsgASgFEhYKDnNsZWVwaW5nX2NvdW50GAwgASgFEiAKGGhhc2hyYXRlX3JlcG9ydGluZ19jb3VudBgNIAEoBRIiChplZmZpY2llbmN5X3JlcG9ydGluZ19jb3VudBgOIAEoBRIdChVwb3dlcl9yZXBvcnRpbmdfY291bnQYDyABKAUSIwobdGVtcGVyYXR1cmVfcmVwb3J0aW5nX2NvdW50GBAgASgFEiEKGWNvbnRyb2xfYm9hcmRfaXNzdWVfY291bnQYESABKAUSFwoPZmFuX2lzc3VlX2NvdW50GBIgASgFEh4KFmhhc2hfYm9hcmRfaXNzdWVfY291bnQYEyABKAUSFwoPcHN1X2lzc3VlX2NvdW50GBQgASgFEjQKDXNsb3Rfc3RhdHVzZXMYFSADKAsyHS5jb2xsZWN0aW9uLnYxLlJhY2tTbG90U3RhdHVzIjMKGUdldENvbGxlY3Rpb25TdGF0c1JlcXVlc3QSFgoOY29sbGVjdGlvbl9pZHMYASADKAMiSwoaR2V0Q29sbGVjdGlvblN0YXRzUmVzcG9uc2USLQoFc3RhdHMYASADKAsyHi5jb2xsZWN0aW9uLnYxLkNvbGxlY3Rpb25TdGF0cyJeCg5SYWNrU2xvdFN0YXR1cxILCgNyb3cYASABKAUSDgoGY29sdW1uGAIgASgFEi8KBnN0YXR1cxgDIAEoDjIfLmNvbGxlY3Rpb24udjEuU2xvdERldmljZVN0YXR1cyIWChRMaXN0UmFja1pvbmVzUmVxdWVzdCImChVMaXN0UmFja1pvbmVzUmVzcG9uc2USDQoFem9uZXMYASADKAkiFgoUTGlzdFJhY2tUeXBlc1JlcXVlc3QiPQoIUmFja1R5cGUSDAoEcm93cxgBIAEoBRIPCgdjb2x1bW5zGAIgASgFEhIKCnJhY2tfY291bnQYAyABKAUiRAoVTGlzdFJhY2tUeXBlc1Jlc3BvbnNlEisKCnJhY2tfdHlwZXMYASADKAsyFy5jb2xsZWN0aW9uLnYxLlJhY2tUeXBlIosCCg9TYXZlUmFja1JlcXVlc3QSJgoNY29sbGVjdGlvbl9pZBgBIAEoA0IKukgH2AEBIgIgAEgAiAEBEhsKBWxhYmVsGAIgASgJQgy6SAnIAQFyBBABGGQSMgoJcmFja19pbmZvGAMgASgLMhcuY29sbGVjdGlvbi52MS5SYWNrSW5mb0IGukgDyAEBEjoKD2RldmljZV9zZWxlY3RvchgEIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckIGukgDyAEBEjEKEHNsb3RfYXNzaWdubWVudHMYBSADKAsyFy5jb2xsZWN0aW9uLnYxLlJhY2tTbG90QhAKDl9jb2xsZWN0aW9uX2lkIn4KEFNhdmVSYWNrUmVzcG9uc2USMwoKY29sbGVjdGlvbhgBIAEoCzIfLmNvbGxlY3Rpb24udjEuRGV2aWNlQ29sbGVjdGlvbhIWCg5hc3NpZ25lZF9jb3VudBgCIAEoBRIdChVzaXRlX3JlYXNzaWduZWRfY291bnQYAyABKAUqZgoOQ29sbGVjdGlvblR5cGUSHwobQ09MTEVDVElPTl9UWVBFX1VOU1BFQ0lGSUVEEAASGQoVQ09MTEVDVElPTl9UWVBFX0dST1VQEAESGAoUQ09MTEVDVElPTl9UWVBFX1JBQ0sQAiq2AQoOUmFja09yZGVySW5kZXgSIAocUkFDS19PUkRFUl9JTkRFWF9VTlNQRUNJRklFRBAAEiAKHFJBQ0tfT1JERVJfSU5ERVhfQk9UVE9NX0xFRlQQARIdChlSQUNLX09SREVSX0lOREVYX1RPUF9MRUZUEAISIQodUkFDS19PUkRFUl9JTkRFWF9CT1RUT01fUklHSFQQAxIeChpSQUNLX09SREVSX0lOREVYX1RPUF9SSUdIVBAEKnAKD1JhY2tDb29saW5nVHlwZRIhCh1SQUNLX0NPT0xJTkdfVFlQRV9VTlNQRUNJRklFRBAAEhkKFVJBQ0tfQ09PTElOR19UWVBFX0FJUhABEh8KG1JBQ0tfQ09PTElOR19UWVBFX0lNTUVSU0lPThACKt0BChBTbG90RGV2aWNlU3RhdHVzEiIKHlNMT1RfREVWSUNFX1NUQVRVU19VTlNQRUNJRklFRBAAEhwKGFNMT1RfREVWSUNFX1NUQVRVU19FTVBUWRABEh4KGlNMT1RfREVWSUNFX1NUQVRVU19IRUFMVEhZEAISJgoiU0xPVF9ERVZJQ0VfU1RBVFVTX05FRURTX0FUVEVOVElPThADEh4KGlNMT1RfREVWSUNFX1NUQVRVU19PRkZMSU5FEAQSHwobU0xPVF9ERVZJQ0VfU1RBVFVTX1NMRUVQSU5HEAUylA0KF0RldmljZUNvbGxlY3Rpb25TZXJ2aWNlEmMKEENyZWF0ZUNvbGxlY3Rpb24SJi5jb2xsZWN0aW9uLnYxLkNyZWF0ZUNvbGxlY3Rpb25SZXF1ZXN0GicuY29sbGVjdGlvbi52MS5DcmVhdGVDb2xsZWN0aW9uUmVzcG9uc2USWgoNR2V0Q29sbGVjdGlvbhIjLmNvbGxlY3Rpb24udjEuR2V0Q29sbGVjdGlvblJlcXVlc3QaJC5jb2xsZWN0aW9uLnYxLkdldENvbGxlY3Rpb25SZXNwb25zZRJjChBVcGRhdGVDb2xsZWN0aW9uEiYuY29sbGVjdGlvbi52MS5VcGRhdGVDb2xsZWN0aW9uUmVxdWVzdBonLmNvbGxlY3Rpb24udjEuVXBkYXRlQ29sbGVjdGlvblJlc3BvbnNlEmMKEERlbGV0ZUNvbGxlY3Rpb24SJi5jb2xsZWN0aW9uLnYxLkRlbGV0ZUNvbGxlY3Rpb25SZXF1ZXN0GicuY29sbGVjdGlvbi52MS5EZWxldGVDb2xsZWN0aW9uUmVzcG9uc2USYAoPTGlzdENvbGxlY3Rpb25zEiUuY29sbGVjdGlvbi52MS5MaXN0Q29sbGVjdGlvbnNSZXF1ZXN0GiYuY29sbGVjdGlvbi52MS5MaXN0Q29sbGVjdGlvbnNSZXNwb25zZRJ1ChZBZGREZXZpY2VzVG9Db2xsZWN0aW9uEiwuY29sbGVjdGlvbi52MS5BZGREZXZpY2VzVG9Db2xsZWN0aW9uUmVxdWVzdBotLmNvbGxlY3Rpb24udjEuQWRkRGV2aWNlc1RvQ29sbGVjdGlvblJlc3BvbnNlEoQBChtSZW1vdmVEZXZpY2VzRnJvbUNvbGxlY3Rpb24SMS5jb2xsZWN0aW9uLnYxLlJlbW92ZURldmljZXNGcm9tQ29sbGVjdGlvblJlcXVlc3QaMi5jb2xsZWN0aW9uLnYxLlJlbW92ZURldmljZXNGcm9tQ29sbGVjdGlvblJlc3BvbnNlEnIKFUxpc3RDb2xsZWN0aW9uTWVtYmVycxIrLmNvbGxlY3Rpb24udjEuTGlzdENvbGxlY3Rpb25NZW1iZXJzUmVxdWVzdBosLmNvbGxlY3Rpb24udjEuTGlzdENvbGxlY3Rpb25NZW1iZXJzUmVzcG9uc2USbwoUR2V0RGV2aWNlQ29sbGVjdGlvbnMSKi5jb2xsZWN0aW9uLnYxLkdldERldmljZUNvbGxlY3Rpb25zUmVxdWVzdBorLmNvbGxlY3Rpb24udjEuR2V0RGV2aWNlQ29sbGVjdGlvbnNSZXNwb25zZRJsChNTZXRSYWNrU2xvdFBvc2l0aW9uEikuY29sbGVjdGlvbi52MS5TZXRSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBoqLmNvbGxlY3Rpb24udjEuU2V0UmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlEnIKFUNsZWFyUmFja1Nsb3RQb3NpdGlvbhIrLmNvbGxlY3Rpb24udjEuQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBosLmNvbGxlY3Rpb24udjEuQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVzcG9uc2USVwoMR2V0UmFja1Nsb3RzEiIuY29sbGVjdGlvbi52MS5HZXRSYWNrU2xvdHNSZXF1ZXN0GiMuY29sbGVjdGlvbi52MS5HZXRSYWNrU2xvdHNSZXNwb25zZRJpChJHZXRDb2xsZWN0aW9uU3RhdHMSKC5jb2xsZWN0aW9uLnYxLkdldENvbGxlY3Rpb25TdGF0c1JlcXVlc3QaKS5jb2xsZWN0aW9uLnYxLkdldENvbGxlY3Rpb25TdGF0c1Jlc3BvbnNlEloKDUxpc3RSYWNrWm9uZXMSIy5jb2xsZWN0aW9uLnYxLkxpc3RSYWNrWm9uZXNSZXF1ZXN0GiQuY29sbGVjdGlvbi52MS5MaXN0UmFja1pvbmVzUmVzcG9uc2USWgoNTGlzdFJhY2tUeXBlcxIjLmNvbGxlY3Rpb24udjEuTGlzdFJhY2tUeXBlc1JlcXVlc3QaJC5jb2xsZWN0aW9uLnYxLkxpc3RSYWNrVHlwZXNSZXNwb25zZRJLCghTYXZlUmFjaxIeLmNvbGxlY3Rpb24udjEuU2F2ZVJhY2tSZXF1ZXN0Gh8uY29sbGVjdGlvbi52MS5TYXZlUmFja1Jlc3BvbnNlQsgBChFjb20uY29sbGVjdGlvbi52MUIPQ29sbGVjdGlvblByb3RvUAFaTWdpdGh1Yi5jb20vYmxvY2svcHJvdG8tZmxlZXQvc2VydmVyL2dlbmVyYXRlZC9ncnBjL2NvbGxlY3Rpb24vdjE7Y29sbGVjdGlvbnYxogIDQ1hYqgINQ29sbGVjdGlvbi5WMcoCDUNvbGxlY3Rpb25cVjHiAhlDb2xsZWN0aW9uXFYxXEdQQk1ldGFkYXRh6gIOQ29sbGVjdGlvbjo6VjFiBnByb3RvMw", + "Ch5jb2xsZWN0aW9uL3YxL2NvbGxlY3Rpb24ucHJvdG8SDWNvbGxlY3Rpb24udjEi0wIKEERldmljZUNvbGxlY3Rpb24SCgoCaWQYASABKAMSKwoEdHlwZRgCIAEoDjIdLmNvbGxlY3Rpb24udjEuQ29sbGVjdGlvblR5cGUSDQoFbGFiZWwYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSFAoMZGV2aWNlX2NvdW50GAUgASgFEi4KCmNyZWF0ZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEiwKCXJhY2tfaW5mbxgIIAEoCzIXLmNvbGxlY3Rpb24udjEuUmFja0luZm9IABIuCgpncm91cF9pbmZvGAkgASgLMhguY29sbGVjdGlvbi52MS5Hcm91cEluZm9IAEIOCgx0eXBlX2RldGFpbHMiiAIKCFJhY2tJbmZvEhUKBHJvd3MYASABKAVCB7pIBBoCIAASGAoHY29sdW1ucxgCIAEoBUIHukgEGgIgABIVCgR6b25lGAMgASgJQge6SARyAhhkEjIKC29yZGVyX2luZGV4GAQgASgOMh0uY29sbGVjdGlvbi52MS5SYWNrT3JkZXJJbmRleBI0Cgxjb29saW5nX3R5cGUYBSABKA4yHi5jb2xsZWN0aW9uLnYxLlJhY2tDb29saW5nVHlwZRIUCgdzaXRlX2lkGAYgASgDSACIAQESGAoLYnVpbGRpbmdfaWQYByABKANIAYgBAUIKCghfc2l0ZV9pZEIOCgxfYnVpbGRpbmdfaWQiCwoJR3JvdXBJbmZvIp8BChBDb2xsZWN0aW9uTWVtYmVyEhkKEWRldmljZV9pZGVudGlmaWVyGAEgASgJEiwKCGFkZGVkX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIwCgRyYWNrGAMgASgLMiAuY29sbGVjdGlvbi52MS5SYWNrTWVtYmVyRGV0YWlsc0gAQhAKDm1lbWJlcl9kZXRhaWxzIksKEVJhY2tNZW1iZXJEZXRhaWxzEjYKDXNsb3RfcG9zaXRpb24YASABKAsyHy5jb2xsZWN0aW9uLnYxLlJhY2tTbG90UG9zaXRpb24iQQoQUmFja1Nsb3RQb3NpdGlvbhIUCgNyb3cYASABKAVCB7pIBBoCKAASFwoGY29sdW1uGAIgASgFQge6SAQaAigAIskCChdDcmVhdGVDb2xsZWN0aW9uUmVxdWVzdBI3CgR0eXBlGAEgASgOMh0uY29sbGVjdGlvbi52MS5Db2xsZWN0aW9uVHlwZUIKukgHggEEEAEgABIbCgVsYWJlbBgCIAEoCUIMukgJyAEBcgQQARhkEh0KC2Rlc2NyaXB0aW9uGAMgASgJQgi6SAVyAxj0AxIsCglyYWNrX2luZm8YBCABKAsyFy5jb2xsZWN0aW9uLnYxLlJhY2tJbmZvSAASLgoKZ3JvdXBfaW5mbxgFIAEoCzIYLmNvbGxlY3Rpb24udjEuR3JvdXBJbmZvSAASNwoPZGV2aWNlX3NlbGVjdG9yGAYgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9ySAGIAQFCDgoMdHlwZV9kZXRhaWxzQhIKEF9kZXZpY2Vfc2VsZWN0b3IiZAoYQ3JlYXRlQ29sbGVjdGlvblJlc3BvbnNlEjMKCmNvbGxlY3Rpb24YASABKAsyHy5jb2xsZWN0aW9uLnYxLkRldmljZUNvbGxlY3Rpb24SEwoLYWRkZWRfY291bnQYAiABKAUiNgoUR2V0Q29sbGVjdGlvblJlcXVlc3QSHgoNY29sbGVjdGlvbl9pZBgBIAEoA0IHukgEIgIgACJMChVHZXRDb2xsZWN0aW9uUmVzcG9uc2USMwoKY29sbGVjdGlvbhgBIAEoCzIfLmNvbGxlY3Rpb24udjEuRGV2aWNlQ29sbGVjdGlvbiK+AgoXVXBkYXRlQ29sbGVjdGlvblJlcXVlc3QSHgoNY29sbGVjdGlvbl9pZBgBIAEoA0IHukgEIgIgABIgCgVsYWJlbBgCIAEoCUIMukgJ2AEBcgQQARhkSAGIAQESJQoLZGVzY3JpcHRpb24YAyABKAlCC7pICNgBAXIDGPQDSAKIAQESLAoJcmFja19pbmZvGAQgASgLMhcuY29sbGVjdGlvbi52MS5SYWNrSW5mb0gAEi4KCmdyb3VwX2luZm8YBSABKAsyGC5jb2xsZWN0aW9uLnYxLkdyb3VwSW5mb0gAEjIKD2RldmljZV9zZWxlY3RvchgGIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckIOCgx0eXBlX2RldGFpbHNCCAoGX2xhYmVsQg4KDF9kZXNjcmlwdGlvbiJPChhVcGRhdGVDb2xsZWN0aW9uUmVzcG9uc2USMwoKY29sbGVjdGlvbhgBIAEoCzIfLmNvbGxlY3Rpb24udjEuRGV2aWNlQ29sbGVjdGlvbiI5ChdEZWxldGVDb2xsZWN0aW9uUmVxdWVzdBIeCg1jb2xsZWN0aW9uX2lkGAEgASgDQge6SAQiAiAAIhoKGERlbGV0ZUNvbGxlY3Rpb25SZXNwb25zZSLsAQoWTGlzdENvbGxlY3Rpb25zUmVxdWVzdBI1CgR0eXBlGAEgASgOMh0uY29sbGVjdGlvbi52MS5Db2xsZWN0aW9uVHlwZUIIukgFggECEAESGgoJcGFnZV9zaXplGAIgASgFQge6SAQaAigAEhIKCnBhZ2VfdG9rZW4YAyABKAkSIwoEc29ydBgEIAEoCzIVLmNvbW1vbi52MS5Tb3J0Q29uZmlnEjcKFWVycm9yX2NvbXBvbmVudF90eXBlcxgFIAMoDjIYLmVycm9ycy52MS5Db21wb25lbnRUeXBlEg0KBXpvbmVzGAYgAygJIn0KF0xpc3RDb2xsZWN0aW9uc1Jlc3BvbnNlEjQKC2NvbGxlY3Rpb25zGAEgAygLMh8uY29sbGVjdGlvbi52MS5EZXZpY2VDb2xsZWN0aW9uEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRITCgt0b3RhbF9jb3VudBgDIAEoBSJuChxMaXN0Q29sbGVjdGlvbk1lbWJlcnNSZXF1ZXN0Eh4KDWNvbGxlY3Rpb25faWQYASABKANCB7pIBCICIAASGgoJcGFnZV9zaXplGAIgASgFQge6SAQaAigAEhIKCnBhZ2VfdG9rZW4YAyABKAkiagodTGlzdENvbGxlY3Rpb25NZW1iZXJzUmVzcG9uc2USMAoHbWVtYmVycxgBIAMoCzIfLmNvbGxlY3Rpb24udjEuQ29sbGVjdGlvbk1lbWJlchIXCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAkibgobR2V0RGV2aWNlQ29sbGVjdGlvbnNSZXF1ZXN0EiIKEWRldmljZV9pZGVudGlmaWVyGAEgASgJQge6SARyAhABEisKBHR5cGUYAiABKA4yHS5jb2xsZWN0aW9uLnYxLkNvbGxlY3Rpb25UeXBlIlQKHEdldERldmljZUNvbGxlY3Rpb25zUmVzcG9uc2USNAoLY29sbGVjdGlvbnMYASADKAsyHy5jb2xsZWN0aW9uLnYxLkRldmljZUNvbGxlY3Rpb24imwEKGlNldFJhY2tTbG90UG9zaXRpb25SZXF1ZXN0Eh4KDWNvbGxlY3Rpb25faWQYASABKANCB7pIBCICIAASIgoRZGV2aWNlX2lkZW50aWZpZXIYAiABKAlCB7pIBHICEAESOQoIcG9zaXRpb24YAyABKAsyHy5jb2xsZWN0aW9uLnYxLlJhY2tTbG90UG9zaXRpb25CBrpIA8gBASJbChtTZXRSYWNrU2xvdFBvc2l0aW9uUmVzcG9uc2USFQoNY29sbGVjdGlvbl9pZBgBIAEoAxIlCgRzbG90GAIgASgLMhcuY29sbGVjdGlvbi52MS5SYWNrU2xvdCJiChxDbGVhclJhY2tTbG90UG9zaXRpb25SZXF1ZXN0Eh4KDWNvbGxlY3Rpb25faWQYASABKANCB7pIBCICIAASIgoRZGV2aWNlX2lkZW50aWZpZXIYAiABKAlCB7pIBHICEAEiHwodQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVzcG9uc2UiNQoTR2V0UmFja1Nsb3RzUmVxdWVzdBIeCg1jb2xsZWN0aW9uX2lkGAEgASgDQge6SAQiAiAAIlgKCFJhY2tTbG90EhkKEWRldmljZV9pZGVudGlmaWVyGAEgASgJEjEKCHBvc2l0aW9uGAIgASgLMh8uY29sbGVjdGlvbi52MS5SYWNrU2xvdFBvc2l0aW9uIj4KFEdldFJhY2tTbG90c1Jlc3BvbnNlEiYKBXNsb3RzGAEgAygLMhcuY29sbGVjdGlvbi52MS5SYWNrU2xvdCLuBAoPQ29sbGVjdGlvblN0YXRzEhUKDWNvbGxlY3Rpb25faWQYASABKAMSFAoMZGV2aWNlX2NvdW50GAIgASgFEhcKD3JlcG9ydGluZ19jb3VudBgDIAEoBRIaChJ0b3RhbF9oYXNocmF0ZV90aHMYBCABKAESGgoSYXZnX2VmZmljaWVuY3lfanRoGAUgASgBEhYKDnRvdGFsX3Bvd2VyX2t3GAYgASgBEhkKEW1pbl90ZW1wZXJhdHVyZV9jGAcgASgBEhkKEW1heF90ZW1wZXJhdHVyZV9jGAggASgBEhUKDWhhc2hpbmdfY291bnQYCSABKAUSFAoMYnJva2VuX2NvdW50GAogASgFEhUKDW9mZmxpbmVfY291bnQYCyABKAUSFgoOc2xlZXBpbmdfY291bnQYDCABKAUSIAoYaGFzaHJhdGVfcmVwb3J0aW5nX2NvdW50GA0gASgFEiIKGmVmZmljaWVuY3lfcmVwb3J0aW5nX2NvdW50GA4gASgFEh0KFXBvd2VyX3JlcG9ydGluZ19jb3VudBgPIAEoBRIjCht0ZW1wZXJhdHVyZV9yZXBvcnRpbmdfY291bnQYECABKAUSIQoZY29udHJvbF9ib2FyZF9pc3N1ZV9jb3VudBgRIAEoBRIXCg9mYW5faXNzdWVfY291bnQYEiABKAUSHgoWaGFzaF9ib2FyZF9pc3N1ZV9jb3VudBgTIAEoBRIXCg9wc3VfaXNzdWVfY291bnQYFCABKAUSNAoNc2xvdF9zdGF0dXNlcxgVIAMoCzIdLmNvbGxlY3Rpb24udjEuUmFja1Nsb3RTdGF0dXMiMwoZR2V0Q29sbGVjdGlvblN0YXRzUmVxdWVzdBIWCg5jb2xsZWN0aW9uX2lkcxgBIAMoAyJLChpHZXRDb2xsZWN0aW9uU3RhdHNSZXNwb25zZRItCgVzdGF0cxgBIAMoCzIeLmNvbGxlY3Rpb24udjEuQ29sbGVjdGlvblN0YXRzIl4KDlJhY2tTbG90U3RhdHVzEgsKA3JvdxgBIAEoBRIOCgZjb2x1bW4YAiABKAUSLwoGc3RhdHVzGAMgASgOMh8uY29sbGVjdGlvbi52MS5TbG90RGV2aWNlU3RhdHVzIhYKFExpc3RSYWNrWm9uZXNSZXF1ZXN0IiYKFUxpc3RSYWNrWm9uZXNSZXNwb25zZRINCgV6b25lcxgBIAMoCSIWChRMaXN0UmFja1R5cGVzUmVxdWVzdCI9CghSYWNrVHlwZRIMCgRyb3dzGAEgASgFEg8KB2NvbHVtbnMYAiABKAUSEgoKcmFja19jb3VudBgDIAEoBSJEChVMaXN0UmFja1R5cGVzUmVzcG9uc2USKwoKcmFja190eXBlcxgBIAMoCzIXLmNvbGxlY3Rpb24udjEuUmFja1R5cGUiiwIKD1NhdmVSYWNrUmVxdWVzdBImCg1jb2xsZWN0aW9uX2lkGAEgASgDQgq6SAfYAQEiAiAASACIAQESGwoFbGFiZWwYAiABKAlCDLpICcgBAXIEEAEYZBIyCglyYWNrX2luZm8YAyABKAsyFy5jb2xsZWN0aW9uLnYxLlJhY2tJbmZvQga6SAPIAQESOgoPZGV2aWNlX3NlbGVjdG9yGAQgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQESMQoQc2xvdF9hc3NpZ25tZW50cxgFIAMoCzIXLmNvbGxlY3Rpb24udjEuUmFja1Nsb3RCEAoOX2NvbGxlY3Rpb25faWQifgoQU2F2ZVJhY2tSZXNwb25zZRIzCgpjb2xsZWN0aW9uGAEgASgLMh8uY29sbGVjdGlvbi52MS5EZXZpY2VDb2xsZWN0aW9uEhYKDmFzc2lnbmVkX2NvdW50GAIgASgFEh0KFXNpdGVfcmVhc3NpZ25lZF9jb3VudBgDIAEoBSpmCg5Db2xsZWN0aW9uVHlwZRIfChtDT0xMRUNUSU9OX1RZUEVfVU5TUEVDSUZJRUQQABIZChVDT0xMRUNUSU9OX1RZUEVfR1JPVVAQARIYChRDT0xMRUNUSU9OX1RZUEVfUkFDSxACKrYBCg5SYWNrT3JkZXJJbmRleBIgChxSQUNLX09SREVSX0lOREVYX1VOU1BFQ0lGSUVEEAASIAocUkFDS19PUkRFUl9JTkRFWF9CT1RUT01fTEVGVBABEh0KGVJBQ0tfT1JERVJfSU5ERVhfVE9QX0xFRlQQAhIhCh1SQUNLX09SREVSX0lOREVYX0JPVFRPTV9SSUdIVBADEh4KGlJBQ0tfT1JERVJfSU5ERVhfVE9QX1JJR0hUEAQqcAoPUmFja0Nvb2xpbmdUeXBlEiEKHVJBQ0tfQ09PTElOR19UWVBFX1VOU1BFQ0lGSUVEEAASGQoVUkFDS19DT09MSU5HX1RZUEVfQUlSEAESHwobUkFDS19DT09MSU5HX1RZUEVfSU1NRVJTSU9OEAIq3QEKEFNsb3REZXZpY2VTdGF0dXMSIgoeU0xPVF9ERVZJQ0VfU1RBVFVTX1VOU1BFQ0lGSUVEEAASHAoYU0xPVF9ERVZJQ0VfU1RBVFVTX0VNUFRZEAESHgoaU0xPVF9ERVZJQ0VfU1RBVFVTX0hFQUxUSFkQAhImCiJTTE9UX0RFVklDRV9TVEFUVVNfTkVFRFNfQVRURU5USU9OEAMSHgoaU0xPVF9ERVZJQ0VfU1RBVFVTX09GRkxJTkUQBBIfChtTTE9UX0RFVklDRV9TVEFUVVNfU0xFRVBJTkcQBTKWCwoXRGV2aWNlQ29sbGVjdGlvblNlcnZpY2USYwoQQ3JlYXRlQ29sbGVjdGlvbhImLmNvbGxlY3Rpb24udjEuQ3JlYXRlQ29sbGVjdGlvblJlcXVlc3QaJy5jb2xsZWN0aW9uLnYxLkNyZWF0ZUNvbGxlY3Rpb25SZXNwb25zZRJaCg1HZXRDb2xsZWN0aW9uEiMuY29sbGVjdGlvbi52MS5HZXRDb2xsZWN0aW9uUmVxdWVzdBokLmNvbGxlY3Rpb24udjEuR2V0Q29sbGVjdGlvblJlc3BvbnNlEmMKEFVwZGF0ZUNvbGxlY3Rpb24SJi5jb2xsZWN0aW9uLnYxLlVwZGF0ZUNvbGxlY3Rpb25SZXF1ZXN0GicuY29sbGVjdGlvbi52MS5VcGRhdGVDb2xsZWN0aW9uUmVzcG9uc2USYwoQRGVsZXRlQ29sbGVjdGlvbhImLmNvbGxlY3Rpb24udjEuRGVsZXRlQ29sbGVjdGlvblJlcXVlc3QaJy5jb2xsZWN0aW9uLnYxLkRlbGV0ZUNvbGxlY3Rpb25SZXNwb25zZRJgCg9MaXN0Q29sbGVjdGlvbnMSJS5jb2xsZWN0aW9uLnYxLkxpc3RDb2xsZWN0aW9uc1JlcXVlc3QaJi5jb2xsZWN0aW9uLnYxLkxpc3RDb2xsZWN0aW9uc1Jlc3BvbnNlEnIKFUxpc3RDb2xsZWN0aW9uTWVtYmVycxIrLmNvbGxlY3Rpb24udjEuTGlzdENvbGxlY3Rpb25NZW1iZXJzUmVxdWVzdBosLmNvbGxlY3Rpb24udjEuTGlzdENvbGxlY3Rpb25NZW1iZXJzUmVzcG9uc2USbwoUR2V0RGV2aWNlQ29sbGVjdGlvbnMSKi5jb2xsZWN0aW9uLnYxLkdldERldmljZUNvbGxlY3Rpb25zUmVxdWVzdBorLmNvbGxlY3Rpb24udjEuR2V0RGV2aWNlQ29sbGVjdGlvbnNSZXNwb25zZRJsChNTZXRSYWNrU2xvdFBvc2l0aW9uEikuY29sbGVjdGlvbi52MS5TZXRSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBoqLmNvbGxlY3Rpb24udjEuU2V0UmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlEnIKFUNsZWFyUmFja1Nsb3RQb3NpdGlvbhIrLmNvbGxlY3Rpb24udjEuQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBosLmNvbGxlY3Rpb24udjEuQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVzcG9uc2USVwoMR2V0UmFja1Nsb3RzEiIuY29sbGVjdGlvbi52MS5HZXRSYWNrU2xvdHNSZXF1ZXN0GiMuY29sbGVjdGlvbi52MS5HZXRSYWNrU2xvdHNSZXNwb25zZRJpChJHZXRDb2xsZWN0aW9uU3RhdHMSKC5jb2xsZWN0aW9uLnYxLkdldENvbGxlY3Rpb25TdGF0c1JlcXVlc3QaKS5jb2xsZWN0aW9uLnYxLkdldENvbGxlY3Rpb25TdGF0c1Jlc3BvbnNlEloKDUxpc3RSYWNrWm9uZXMSIy5jb2xsZWN0aW9uLnYxLkxpc3RSYWNrWm9uZXNSZXF1ZXN0GiQuY29sbGVjdGlvbi52MS5MaXN0UmFja1pvbmVzUmVzcG9uc2USWgoNTGlzdFJhY2tUeXBlcxIjLmNvbGxlY3Rpb24udjEuTGlzdFJhY2tUeXBlc1JlcXVlc3QaJC5jb2xsZWN0aW9uLnYxLkxpc3RSYWNrVHlwZXNSZXNwb25zZRJLCghTYXZlUmFjaxIeLmNvbGxlY3Rpb24udjEuU2F2ZVJhY2tSZXF1ZXN0Gh8uY29sbGVjdGlvbi52MS5TYXZlUmFja1Jlc3BvbnNlQsgBChFjb20uY29sbGVjdGlvbi52MUIPQ29sbGVjdGlvblByb3RvUAFaTWdpdGh1Yi5jb20vYmxvY2svcHJvdG8tZmxlZXQvc2VydmVyL2dlbmVyYXRlZC9ncnBjL2NvbGxlY3Rpb24vdjE7Y29sbGVjdGlvbnYxogIDQ1hYqgINQ29sbGVjdGlvbi5WMcoCDUNvbGxlY3Rpb25cVjHiAhlDb2xsZWN0aW9uXFYxXEdQQk1ldGFkYXRh6gIOQ29sbGVjdGlvbjo6VjFiBnByb3RvMw", [ file_google_protobuf_timestamp, file_buf_validate_validate, @@ -660,127 +660,6 @@ export const ListCollectionsResponseSchema: GenMessage /*@__PURE__*/ messageDesc(file_collection_v1_collection, 15); -/** - * Request to add devices to a collection. - * - * When the target is a site-stamped rack, the server rewrites - * device.site_id to match the rack for every added device. The affected - * count is returned in site_reassigned_count. Groups are exempt. - * - * @generated from message collection.v1.AddDevicesToCollectionRequest - */ -export type AddDevicesToCollectionRequest = Message<"collection.v1.AddDevicesToCollectionRequest"> & { - /** - * ID of the collection to add devices to - * - * @generated from field: int64 collection_id = 1; - */ - collectionId: bigint; - - /** - * Devices to add: specific list or all paired devices - * - * @generated from field: common.v1.DeviceSelector device_selector = 2; - */ - deviceSelector?: DeviceSelector | undefined; -}; - -/** - * Describes the message collection.v1.AddDevicesToCollectionRequest. - * Use `create(AddDevicesToCollectionRequestSchema)` to create a new message. - */ -export const AddDevicesToCollectionRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 16); - -/** - * Response after adding devices to a collection - * - * @generated from message collection.v1.AddDevicesToCollectionResponse - */ -export type AddDevicesToCollectionResponse = Message<"collection.v1.AddDevicesToCollectionResponse"> & { - /** - * ID of the collection devices were added to - * - * @generated from field: int64 collection_id = 1; - */ - collectionId: bigint; - - /** - * Number of devices successfully added - * May be less than requested if some devices were already members - * - * @generated from field: int32 added_count = 2; - */ - addedCount: number; - - /** - * Number of devices whose site_id was rewritten by the cascade. - * - * @generated from field: int32 site_reassigned_count = 3; - */ - siteReassignedCount: number; -}; - -/** - * Describes the message collection.v1.AddDevicesToCollectionResponse. - * Use `create(AddDevicesToCollectionResponseSchema)` to create a new message. - */ -export const AddDevicesToCollectionResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 17); - -/** - * Request to remove devices from a collection - * - * @generated from message collection.v1.RemoveDevicesFromCollectionRequest - */ -export type RemoveDevicesFromCollectionRequest = Message<"collection.v1.RemoveDevicesFromCollectionRequest"> & { - /** - * ID of the collection to remove devices from - * - * @generated from field: int64 collection_id = 1; - */ - collectionId: bigint; - - /** - * Devices to remove: specific list or all paired devices - * - * @generated from field: common.v1.DeviceSelector device_selector = 2; - */ - deviceSelector?: DeviceSelector | undefined; -}; - -/** - * Describes the message collection.v1.RemoveDevicesFromCollectionRequest. - * Use `create(RemoveDevicesFromCollectionRequestSchema)` to create a new message. - */ -export const RemoveDevicesFromCollectionRequestSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 18); - -/** - * Response after removing devices from a collection - * - * @generated from message collection.v1.RemoveDevicesFromCollectionResponse - */ -export type RemoveDevicesFromCollectionResponse = Message<"collection.v1.RemoveDevicesFromCollectionResponse"> & { - /** - * Number of devices successfully removed - * - * @generated from field: int32 removed_count = 1; - */ - removedCount: number; -}; - -/** - * Describes the message collection.v1.RemoveDevicesFromCollectionResponse. - * Use `create(RemoveDevicesFromCollectionResponseSchema)` to create a new message. - */ -export const RemoveDevicesFromCollectionResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 19); - /** * Request to list members of a collection * @@ -815,7 +694,7 @@ export type ListCollectionMembersRequest = Message<"collection.v1.ListCollection */ export const ListCollectionMembersRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 20); + messageDesc(file_collection_v1_collection, 16); /** * Response containing collection members @@ -844,7 +723,7 @@ export type ListCollectionMembersResponse = Message<"collection.v1.ListCollectio */ export const ListCollectionMembersResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 21); + messageDesc(file_collection_v1_collection, 17); /** * Request to get collections for a device @@ -873,7 +752,7 @@ export type GetDeviceCollectionsRequest = Message<"collection.v1.GetDeviceCollec */ export const GetDeviceCollectionsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 22); + messageDesc(file_collection_v1_collection, 18); /** * Response containing collections the device belongs to @@ -895,7 +774,7 @@ export type GetDeviceCollectionsResponse = Message<"collection.v1.GetDeviceColle */ export const GetDeviceCollectionsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 23); + messageDesc(file_collection_v1_collection, 19); /** * Request to set a device's slot position within a rack @@ -931,7 +810,7 @@ export type SetRackSlotPositionRequest = Message<"collection.v1.SetRackSlotPosit */ export const SetRackSlotPositionRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 24); + messageDesc(file_collection_v1_collection, 20); /** * Response after setting a rack slot position @@ -960,7 +839,7 @@ export type SetRackSlotPositionResponse = Message<"collection.v1.SetRackSlotPosi */ export const SetRackSlotPositionResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 25); + messageDesc(file_collection_v1_collection, 21); /** * Request to clear a device's slot position within a rack @@ -989,7 +868,7 @@ export type ClearRackSlotPositionRequest = Message<"collection.v1.ClearRackSlotP */ export const ClearRackSlotPositionRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 26); + messageDesc(file_collection_v1_collection, 22); /** * Response after clearing a rack slot position @@ -1004,7 +883,7 @@ export type ClearRackSlotPositionResponse = Message<"collection.v1.ClearRackSlot */ export const ClearRackSlotPositionResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 27); + messageDesc(file_collection_v1_collection, 23); /** * Request to list all occupied slots in a rack @@ -1026,7 +905,7 @@ export type GetRackSlotsRequest = Message<"collection.v1.GetRackSlotsRequest"> & */ export const GetRackSlotsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 28); + messageDesc(file_collection_v1_collection, 24); /** * Represents a device assigned to a specific slot in a rack @@ -1053,7 +932,7 @@ export type RackSlot = Message<"collection.v1.RackSlot"> & { * Describes the message collection.v1.RackSlot. * Use `create(RackSlotSchema)` to create a new message. */ -export const RackSlotSchema: GenMessage = /*@__PURE__*/ messageDesc(file_collection_v1_collection, 29); +export const RackSlotSchema: GenMessage = /*@__PURE__*/ messageDesc(file_collection_v1_collection, 25); /** * Response containing all occupied rack slots @@ -1075,7 +954,7 @@ export type GetRackSlotsResponse = Message<"collection.v1.GetRackSlotsResponse"> */ export const GetRackSlotsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 30); + messageDesc(file_collection_v1_collection, 26); /** * Aggregated telemetry stats for a single collection @@ -1220,7 +1099,7 @@ export type CollectionStats = Message<"collection.v1.CollectionStats"> & { */ export const CollectionStatsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 31); + messageDesc(file_collection_v1_collection, 27); /** * Request to get aggregated stats for collections @@ -1242,7 +1121,7 @@ export type GetCollectionStatsRequest = Message<"collection.v1.GetCollectionStat */ export const GetCollectionStatsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 32); + messageDesc(file_collection_v1_collection, 28); /** * Response containing stats for each requested collection @@ -1264,7 +1143,7 @@ export type GetCollectionStatsResponse = Message<"collection.v1.GetCollectionSta */ export const GetCollectionStatsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 33); + messageDesc(file_collection_v1_collection, 29); /** * Status of a single slot in a rack grid @@ -1300,7 +1179,7 @@ export type RackSlotStatus = Message<"collection.v1.RackSlotStatus"> & { */ export const RackSlotStatusSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 34); + messageDesc(file_collection_v1_collection, 30); /** * Request to list all distinct rack zones for the organization @@ -1315,7 +1194,7 @@ export type ListRackZonesRequest = Message<"collection.v1.ListRackZonesRequest"> */ export const ListRackZonesRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 35); + messageDesc(file_collection_v1_collection, 31); /** * Response containing all distinct rack zones @@ -1337,7 +1216,7 @@ export type ListRackZonesResponse = Message<"collection.v1.ListRackZonesResponse */ export const ListRackZonesResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 36); + messageDesc(file_collection_v1_collection, 32); /** * Request to list all distinct rack types for the organization @@ -1352,7 +1231,7 @@ export type ListRackTypesRequest = Message<"collection.v1.ListRackTypesRequest"> */ export const ListRackTypesRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 37); + messageDesc(file_collection_v1_collection, 33); /** * A rack type defined by its row/column dimensions and how many racks use it @@ -1386,7 +1265,7 @@ export type RackType = Message<"collection.v1.RackType"> & { * Describes the message collection.v1.RackType. * Use `create(RackTypeSchema)` to create a new message. */ -export const RackTypeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_collection_v1_collection, 38); +export const RackTypeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_collection_v1_collection, 34); /** * Response containing all distinct rack types @@ -1408,7 +1287,7 @@ export type ListRackTypesResponse = Message<"collection.v1.ListRackTypesResponse */ export const ListRackTypesResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 39); + messageDesc(file_collection_v1_collection, 35); /** * Request to atomically create or update a rack with membership and slot assignments. @@ -1465,7 +1344,7 @@ export type SaveRackRequest = Message<"collection.v1.SaveRackRequest"> & { */ export const SaveRackRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 40); + messageDesc(file_collection_v1_collection, 36); /** * Response after saving a rack. @@ -1501,7 +1380,7 @@ export type SaveRackResponse = Message<"collection.v1.SaveRackResponse"> & { */ export const SaveRackResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_collection_v1_collection, 41); + messageDesc(file_collection_v1_collection, 37); /** * Type of collection @@ -1702,26 +1581,6 @@ export const DeviceCollectionService: GenService<{ input: typeof ListCollectionsRequestSchema; output: typeof ListCollectionsResponseSchema; }; - /** - * Adds devices to a collection - * - * @generated from rpc collection.v1.DeviceCollectionService.AddDevicesToCollection - */ - addDevicesToCollection: { - methodKind: "unary"; - input: typeof AddDevicesToCollectionRequestSchema; - output: typeof AddDevicesToCollectionResponseSchema; - }; - /** - * Removes devices from a collection - * - * @generated from rpc collection.v1.DeviceCollectionService.RemoveDevicesFromCollection - */ - removeDevicesFromCollection: { - methodKind: "unary"; - input: typeof RemoveDevicesFromCollectionRequestSchema; - output: typeof RemoveDevicesFromCollectionResponseSchema; - }; /** * Lists members of a collection * diff --git a/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts b/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts index 861125d6d..65d9e28ca 100644 --- a/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts +++ b/client/src/protoFleet/api/generated/device_set/v1/device_set_pb.ts @@ -23,7 +23,7 @@ import type { Message } from "@bufbuild/protobuf"; export const file_device_set_v1_device_set: GenFile = /*@__PURE__*/ fileDesc( - "Ch5kZXZpY2Vfc2V0L3YxL2RldmljZV9zZXQucHJvdG8SDWRldmljZV9zZXQudjEiywIKCURldmljZVNldBIKCgJpZBgBIAEoAxIqCgR0eXBlGAIgASgOMhwuZGV2aWNlX3NldC52MS5EZXZpY2VTZXRUeXBlEg0KBWxhYmVsGAMgASgJEhMKC2Rlc2NyaXB0aW9uGAQgASgJEhQKDGRldmljZV9jb3VudBgFIAEoBRIuCgpjcmVhdGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIsCglyYWNrX2luZm8YCCABKAsyFy5kZXZpY2Vfc2V0LnYxLlJhY2tJbmZvSAASLgoKZ3JvdXBfaW5mbxgJIAEoCzIYLmRldmljZV9zZXQudjEuR3JvdXBJbmZvSABCDgoMdHlwZV9kZXRhaWxzIogCCghSYWNrSW5mbxIVCgRyb3dzGAEgASgFQge6SAQaAiAAEhgKB2NvbHVtbnMYAiABKAVCB7pIBBoCIAASFQoEem9uZRgDIAEoCUIHukgEcgIYZBIyCgtvcmRlcl9pbmRleBgEIAEoDjIdLmRldmljZV9zZXQudjEuUmFja09yZGVySW5kZXgSNAoMY29vbGluZ190eXBlGAUgASgOMh4uZGV2aWNlX3NldC52MS5SYWNrQ29vbGluZ1R5cGUSFAoHc2l0ZV9pZBgGIAEoA0gAiAEBEhgKC2J1aWxkaW5nX2lkGAcgASgDSAGIAQFCCgoIX3NpdGVfaWRCDgoMX2J1aWxkaW5nX2lkIgsKCUdyb3VwSW5mbyKeAQoPRGV2aWNlU2V0TWVtYmVyEhkKEWRldmljZV9pZGVudGlmaWVyGAEgASgJEiwKCGFkZGVkX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIwCgRyYWNrGAMgASgLMiAuZGV2aWNlX3NldC52MS5SYWNrTWVtYmVyRGV0YWlsc0gAQhAKDm1lbWJlcl9kZXRhaWxzIksKEVJhY2tNZW1iZXJEZXRhaWxzEjYKDXNsb3RfcG9zaXRpb24YASABKAsyHy5kZXZpY2Vfc2V0LnYxLlJhY2tTbG90UG9zaXRpb24iQQoQUmFja1Nsb3RQb3NpdGlvbhIUCgNyb3cYASABKAVCB7pIBBoCKAASFwoGY29sdW1uGAIgASgFQge6SAQaAigAIscCChZDcmVhdGVEZXZpY2VTZXRSZXF1ZXN0EjYKBHR5cGUYASABKA4yHC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFR5cGVCCrpIB4IBBBABIAASGwoFbGFiZWwYAiABKAlCDLpICcgBAXIEEAEYZBIdCgtkZXNjcmlwdGlvbhgDIAEoCUIIukgFcgMY9AMSLAoJcmFja19pbmZvGAQgASgLMhcuZGV2aWNlX3NldC52MS5SYWNrSW5mb0gAEi4KCmdyb3VwX2luZm8YBSABKAsyGC5kZXZpY2Vfc2V0LnYxLkdyb3VwSW5mb0gAEjcKD2RldmljZV9zZWxlY3RvchgGIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckgBiAEBQg4KDHR5cGVfZGV0YWlsc0ISChBfZGV2aWNlX3NlbGVjdG9yIlwKF0NyZWF0ZURldmljZVNldFJlc3BvbnNlEiwKCmRldmljZV9zZXQYASABKAsyGC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldBITCgthZGRlZF9jb3VudBgCIAEoBSI1ChNHZXREZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAAiRAoUR2V0RGV2aWNlU2V0UmVzcG9uc2USLAoKZGV2aWNlX3NldBgBIAEoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0Ir0CChZVcGRhdGVEZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAASIAoFbGFiZWwYAiABKAlCDLpICdgBAXIEEAEYZEgBiAEBEiUKC2Rlc2NyaXB0aW9uGAMgASgJQgu6SAjYAQFyAxj0A0gCiAEBEiwKCXJhY2tfaW5mbxgEIAEoCzIXLmRldmljZV9zZXQudjEuUmFja0luZm9IABIuCgpncm91cF9pbmZvGAUgASgLMhguZGV2aWNlX3NldC52MS5Hcm91cEluZm9IABIyCg9kZXZpY2Vfc2VsZWN0b3IYBiABKAsyGS5jb21tb24udjEuRGV2aWNlU2VsZWN0b3JCDgoMdHlwZV9kZXRhaWxzQggKBl9sYWJlbEIOCgxfZGVzY3JpcHRpb24iRwoXVXBkYXRlRGV2aWNlU2V0UmVzcG9uc2USLAoKZGV2aWNlX3NldBgBIAEoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0IjgKFkRlbGV0ZURldmljZVNldFJlcXVlc3QSHgoNZGV2aWNlX3NldF9pZBgBIAEoA0IHukgEIgIgACIZChdEZWxldGVEZXZpY2VTZXRSZXNwb25zZSLIAgoVTGlzdERldmljZVNldHNSZXF1ZXN0EjQKBHR5cGUYASABKA4yHC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFR5cGVCCLpIBYIBAhABEhoKCXBhZ2Vfc2l6ZRgCIAEoBUIHukgEGgIoABISCgpwYWdlX3Rva2VuGAMgASgJEiMKBHNvcnQYBCABKAsyFS5jb21tb24udjEuU29ydENvbmZpZxI3ChVlcnJvcl9jb21wb25lbnRfdHlwZXMYBSADKA4yGC5lcnJvcnMudjEuQ29tcG9uZW50VHlwZRIRCgV6b25lcxgGIAMoCUICGAESFAoMYnVpbGRpbmdfaWRzGAcgAygDEhsKE2luY2x1ZGVfbm9fYnVpbGRpbmcYCCABKAgSJQoJem9uZV9rZXlzGAkgAygLMhIuY29tbW9uLnYxLlpvbmVLZXkidQoWTGlzdERldmljZVNldHNSZXNwb25zZRItCgtkZXZpY2Vfc2V0cxgBIAMoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0EhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRITCgt0b3RhbF9jb3VudBgDIAEoBSJ6ChxBZGREZXZpY2VzVG9EZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAASOgoPZGV2aWNlX3NlbGVjdG9yGAIgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQEiagodQWRkRGV2aWNlc1RvRGV2aWNlU2V0UmVzcG9uc2USFQoNZGV2aWNlX3NldF9pZBgBIAEoAxITCgthZGRlZF9jb3VudBgCIAEoBRIdChVzaXRlX3JlYXNzaWduZWRfY291bnQYAyABKAUifwohUmVtb3ZlRGV2aWNlc0Zyb21EZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAASOgoPZGV2aWNlX3NlbGVjdG9yGAIgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQEiOwoiUmVtb3ZlRGV2aWNlc0Zyb21EZXZpY2VTZXRSZXNwb25zZRIVCg1yZW1vdmVkX2NvdW50GAEgASgFIm0KG0xpc3REZXZpY2VTZXRNZW1iZXJzUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEhoKCXBhZ2Vfc2l6ZRgCIAEoBUIHukgEGgIoABISCgpwYWdlX3Rva2VuGAMgASgJImgKHExpc3REZXZpY2VTZXRNZW1iZXJzUmVzcG9uc2USLwoHbWVtYmVycxgBIAMoCzIeLmRldmljZV9zZXQudjEuRGV2aWNlU2V0TWVtYmVyEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSJsChpHZXREZXZpY2VEZXZpY2VTZXRzUmVxdWVzdBIiChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCUIHukgEcgIQARIqCgR0eXBlGAIgASgOMhwuZGV2aWNlX3NldC52MS5EZXZpY2VTZXRUeXBlIkwKG0dldERldmljZURldmljZVNldHNSZXNwb25zZRItCgtkZXZpY2Vfc2V0cxgBIAMoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0IpsBChpTZXRSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEiIKEWRldmljZV9pZGVudGlmaWVyGAIgASgJQge6SARyAhABEjkKCHBvc2l0aW9uGAMgASgLMh8uZGV2aWNlX3NldC52MS5SYWNrU2xvdFBvc2l0aW9uQga6SAPIAQEiWwobU2V0UmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlEhUKDWRldmljZV9zZXRfaWQYASABKAMSJQoEc2xvdBgCIAEoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3QiYgocQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEiIKEWRldmljZV9pZGVudGlmaWVyGAIgASgJQge6SARyAhABIh8KHUNsZWFyUmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlIjUKE0dldFJhY2tTbG90c1JlcXVlc3QSHgoNZGV2aWNlX3NldF9pZBgBIAEoA0IHukgEIgIgACJYCghSYWNrU2xvdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCghwb3NpdGlvbhgCIAEoCzIfLmRldmljZV9zZXQudjEuUmFja1Nsb3RQb3NpdGlvbiI+ChRHZXRSYWNrU2xvdHNSZXNwb25zZRImCgVzbG90cxgBIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3Qi7QQKDkRldmljZVNldFN0YXRzEhUKDWRldmljZV9zZXRfaWQYASABKAMSFAoMZGV2aWNlX2NvdW50GAIgASgFEhcKD3JlcG9ydGluZ19jb3VudBgDIAEoBRIaChJ0b3RhbF9oYXNocmF0ZV90aHMYBCABKAESGgoSYXZnX2VmZmljaWVuY3lfanRoGAUgASgBEhYKDnRvdGFsX3Bvd2VyX2t3GAYgASgBEhkKEW1pbl90ZW1wZXJhdHVyZV9jGAcgASgBEhkKEW1heF90ZW1wZXJhdHVyZV9jGAggASgBEhUKDWhhc2hpbmdfY291bnQYCSABKAUSFAoMYnJva2VuX2NvdW50GAogASgFEhUKDW9mZmxpbmVfY291bnQYCyABKAUSFgoOc2xlZXBpbmdfY291bnQYDCABKAUSIAoYaGFzaHJhdGVfcmVwb3J0aW5nX2NvdW50GA0gASgFEiIKGmVmZmljaWVuY3lfcmVwb3J0aW5nX2NvdW50GA4gASgFEh0KFXBvd2VyX3JlcG9ydGluZ19jb3VudBgPIAEoBRIjCht0ZW1wZXJhdHVyZV9yZXBvcnRpbmdfY291bnQYECABKAUSIQoZY29udHJvbF9ib2FyZF9pc3N1ZV9jb3VudBgRIAEoBRIXCg9mYW5faXNzdWVfY291bnQYEiABKAUSHgoWaGFzaF9ib2FyZF9pc3N1ZV9jb3VudBgTIAEoBRIXCg9wc3VfaXNzdWVfY291bnQYFCABKAUSNAoNc2xvdF9zdGF0dXNlcxgVIAMoCzIdLmRldmljZV9zZXQudjEuUmFja1Nsb3RTdGF0dXMiMgoYR2V0RGV2aWNlU2V0U3RhdHNSZXF1ZXN0EhYKDmRldmljZV9zZXRfaWRzGAEgAygDIkkKGUdldERldmljZVNldFN0YXRzUmVzcG9uc2USLAoFc3RhdHMYASADKAsyHS5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFN0YXRzIl4KDlJhY2tTbG90U3RhdHVzEgsKA3JvdxgBIAEoBRIOCgZjb2x1bW4YAiABKAUSLwoGc3RhdHVzGAMgASgOMh8uZGV2aWNlX3NldC52MS5TbG90RGV2aWNlU3RhdHVzIhYKFExpc3RSYWNrWm9uZXNSZXF1ZXN0IiYKFUxpc3RSYWNrWm9uZXNSZXNwb25zZRINCgV6b25lcxgBIAMoCSIZChdMaXN0UmFja1pvbmVSZWZzUmVxdWVzdCI9ChhMaXN0UmFja1pvbmVSZWZzUmVzcG9uc2USIQoFem9uZXMYASADKAsyEi5jb21tb24udjEuWm9uZVJlZiIWChRMaXN0UmFja1R5cGVzUmVxdWVzdCI9CghSYWNrVHlwZRIMCgRyb3dzGAEgASgFEg8KB2NvbHVtbnMYAiABKAUSEgoKcmFja19jb3VudBgDIAEoBSJEChVMaXN0UmFja1R5cGVzUmVzcG9uc2USKwoKcmFja190eXBlcxgBIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1R5cGUiiwIKD1NhdmVSYWNrUmVxdWVzdBImCg1kZXZpY2Vfc2V0X2lkGAEgASgDQgq6SAfYAQEiAiAASACIAQESGwoFbGFiZWwYAiABKAlCDLpICcgBAXIEEAEYZBIyCglyYWNrX2luZm8YAyABKAsyFy5kZXZpY2Vfc2V0LnYxLlJhY2tJbmZvQga6SAPIAQESOgoPZGV2aWNlX3NlbGVjdG9yGAQgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQESMQoQc2xvdF9hc3NpZ25tZW50cxgFIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3RCEAoOX2RldmljZV9zZXRfaWQidwoQU2F2ZVJhY2tSZXNwb25zZRIsCgpkZXZpY2Vfc2V0GAEgASgLMhguZGV2aWNlX3NldC52MS5EZXZpY2VTZXQSFgoOYXNzaWduZWRfY291bnQYAiABKAUSHQoVc2l0ZV9yZWFzc2lnbmVkX2NvdW50GAMgASgFIocBChpBc3NpZ25EZXZpY2VzVG9SYWNrUmVxdWVzdBIkCg50YXJnZXRfcmFja19pZBgBIAEoA0IHukgEIgIgAEgAiAEBEjAKEmRldmljZV9pZGVudGlmaWVycxgCIAMoCUIUukgRkgEOCAEQkE4iB3IFEAEYgAJCEQoPX3RhcmdldF9yYWNrX2lkImsKG0Fzc2lnbkRldmljZXNUb1JhY2tSZXNwb25zZRIWCg5hc3NpZ25lZF9jb3VudBgBIAEoAxIdChVzaXRlX3JlYXNzaWduZWRfY291bnQYAiABKAMSFQoNcmVtb3ZlZF9jb3VudBgDIAEoAyplCg1EZXZpY2VTZXRUeXBlEh8KG0RFVklDRV9TRVRfVFlQRV9VTlNQRUNJRklFRBAAEhkKFURFVklDRV9TRVRfVFlQRV9HUk9VUBABEhgKFERFVklDRV9TRVRfVFlQRV9SQUNLEAIqtgEKDlJhY2tPcmRlckluZGV4EiAKHFJBQ0tfT1JERVJfSU5ERVhfVU5TUEVDSUZJRUQQABIgChxSQUNLX09SREVSX0lOREVYX0JPVFRPTV9MRUZUEAESHQoZUkFDS19PUkRFUl9JTkRFWF9UT1BfTEVGVBACEiEKHVJBQ0tfT1JERVJfSU5ERVhfQk9UVE9NX1JJR0hUEAMSHgoaUkFDS19PUkRFUl9JTkRFWF9UT1BfUklHSFQQBCpwCg9SYWNrQ29vbGluZ1R5cGUSIQodUkFDS19DT09MSU5HX1RZUEVfVU5TUEVDSUZJRUQQABIZChVSQUNLX0NPT0xJTkdfVFlQRV9BSVIQARIfChtSQUNLX0NPT0xJTkdfVFlQRV9JTU1FUlNJT04QAirdAQoQU2xvdERldmljZVN0YXR1cxIiCh5TTE9UX0RFVklDRV9TVEFUVVNfVU5TUEVDSUZJRUQQABIcChhTTE9UX0RFVklDRV9TVEFUVVNfRU1QVFkQARIeChpTTE9UX0RFVklDRV9TVEFUVVNfSEVBTFRIWRACEiYKIlNMT1RfREVWSUNFX1NUQVRVU19ORUVEU19BVFRFTlRJT04QAxIeChpTTE9UX0RFVklDRV9TVEFUVVNfT0ZGTElORRAEEh8KG1NMT1RfREVWSUNFX1NUQVRVU19TTEVFUElORxAFMsIOChBEZXZpY2VTZXRTZXJ2aWNlEmAKD0NyZWF0ZURldmljZVNldBIlLmRldmljZV9zZXQudjEuQ3JlYXRlRGV2aWNlU2V0UmVxdWVzdBomLmRldmljZV9zZXQudjEuQ3JlYXRlRGV2aWNlU2V0UmVzcG9uc2USVwoMR2V0RGV2aWNlU2V0EiIuZGV2aWNlX3NldC52MS5HZXREZXZpY2VTZXRSZXF1ZXN0GiMuZGV2aWNlX3NldC52MS5HZXREZXZpY2VTZXRSZXNwb25zZRJgCg9VcGRhdGVEZXZpY2VTZXQSJS5kZXZpY2Vfc2V0LnYxLlVwZGF0ZURldmljZVNldFJlcXVlc3QaJi5kZXZpY2Vfc2V0LnYxLlVwZGF0ZURldmljZVNldFJlc3BvbnNlEmAKD0RlbGV0ZURldmljZVNldBIlLmRldmljZV9zZXQudjEuRGVsZXRlRGV2aWNlU2V0UmVxdWVzdBomLmRldmljZV9zZXQudjEuRGVsZXRlRGV2aWNlU2V0UmVzcG9uc2USXQoOTGlzdERldmljZVNldHMSJC5kZXZpY2Vfc2V0LnYxLkxpc3REZXZpY2VTZXRzUmVxdWVzdBolLmRldmljZV9zZXQudjEuTGlzdERldmljZVNldHNSZXNwb25zZRJyChVBZGREZXZpY2VzVG9EZXZpY2VTZXQSKy5kZXZpY2Vfc2V0LnYxLkFkZERldmljZXNUb0RldmljZVNldFJlcXVlc3QaLC5kZXZpY2Vfc2V0LnYxLkFkZERldmljZXNUb0RldmljZVNldFJlc3BvbnNlEoEBChpSZW1vdmVEZXZpY2VzRnJvbURldmljZVNldBIwLmRldmljZV9zZXQudjEuUmVtb3ZlRGV2aWNlc0Zyb21EZXZpY2VTZXRSZXF1ZXN0GjEuZGV2aWNlX3NldC52MS5SZW1vdmVEZXZpY2VzRnJvbURldmljZVNldFJlc3BvbnNlEm8KFExpc3REZXZpY2VTZXRNZW1iZXJzEiouZGV2aWNlX3NldC52MS5MaXN0RGV2aWNlU2V0TWVtYmVyc1JlcXVlc3QaKy5kZXZpY2Vfc2V0LnYxLkxpc3REZXZpY2VTZXRNZW1iZXJzUmVzcG9uc2USbAoTR2V0RGV2aWNlRGV2aWNlU2V0cxIpLmRldmljZV9zZXQudjEuR2V0RGV2aWNlRGV2aWNlU2V0c1JlcXVlc3QaKi5kZXZpY2Vfc2V0LnYxLkdldERldmljZURldmljZVNldHNSZXNwb25zZRJsChNTZXRSYWNrU2xvdFBvc2l0aW9uEikuZGV2aWNlX3NldC52MS5TZXRSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBoqLmRldmljZV9zZXQudjEuU2V0UmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlEnIKFUNsZWFyUmFja1Nsb3RQb3NpdGlvbhIrLmRldmljZV9zZXQudjEuQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBosLmRldmljZV9zZXQudjEuQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVzcG9uc2USVwoMR2V0UmFja1Nsb3RzEiIuZGV2aWNlX3NldC52MS5HZXRSYWNrU2xvdHNSZXF1ZXN0GiMuZGV2aWNlX3NldC52MS5HZXRSYWNrU2xvdHNSZXNwb25zZRJmChFHZXREZXZpY2VTZXRTdGF0cxInLmRldmljZV9zZXQudjEuR2V0RGV2aWNlU2V0U3RhdHNSZXF1ZXN0GiguZGV2aWNlX3NldC52MS5HZXREZXZpY2VTZXRTdGF0c1Jlc3BvbnNlEloKDUxpc3RSYWNrWm9uZXMSIy5kZXZpY2Vfc2V0LnYxLkxpc3RSYWNrWm9uZXNSZXF1ZXN0GiQuZGV2aWNlX3NldC52MS5MaXN0UmFja1pvbmVzUmVzcG9uc2USYwoQTGlzdFJhY2tab25lUmVmcxImLmRldmljZV9zZXQudjEuTGlzdFJhY2tab25lUmVmc1JlcXVlc3QaJy5kZXZpY2Vfc2V0LnYxLkxpc3RSYWNrWm9uZVJlZnNSZXNwb25zZRJaCg1MaXN0UmFja1R5cGVzEiMuZGV2aWNlX3NldC52MS5MaXN0UmFja1R5cGVzUmVxdWVzdBokLmRldmljZV9zZXQudjEuTGlzdFJhY2tUeXBlc1Jlc3BvbnNlEksKCFNhdmVSYWNrEh4uZGV2aWNlX3NldC52MS5TYXZlUmFja1JlcXVlc3QaHy5kZXZpY2Vfc2V0LnYxLlNhdmVSYWNrUmVzcG9uc2USbAoTQXNzaWduRGV2aWNlc1RvUmFjaxIpLmRldmljZV9zZXQudjEuQXNzaWduRGV2aWNlc1RvUmFja1JlcXVlc3QaKi5kZXZpY2Vfc2V0LnYxLkFzc2lnbkRldmljZXNUb1JhY2tSZXNwb25zZULDAQoRY29tLmRldmljZV9zZXQudjFCDkRldmljZVNldFByb3RvUAFaTWdpdGh1Yi5jb20vYmxvY2svcHJvdG8tZmxlZXQvc2VydmVyL2dlbmVyYXRlZC9ncnBjL2RldmljZV9zZXQvdjE7ZGV2aWNlX3NldHYxogIDRFhYqgIMRGV2aWNlU2V0LlYxygIMRGV2aWNlU2V0XFYx4gIYRGV2aWNlU2V0XFYxXEdQQk1ldGFkYXRh6gINRGV2aWNlU2V0OjpWMWIGcHJvdG8z", + "Ch5kZXZpY2Vfc2V0L3YxL2RldmljZV9zZXQucHJvdG8SDWRldmljZV9zZXQudjEiywIKCURldmljZVNldBIKCgJpZBgBIAEoAxIqCgR0eXBlGAIgASgOMhwuZGV2aWNlX3NldC52MS5EZXZpY2VTZXRUeXBlEg0KBWxhYmVsGAMgASgJEhMKC2Rlc2NyaXB0aW9uGAQgASgJEhQKDGRldmljZV9jb3VudBgFIAEoBRIuCgpjcmVhdGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIsCglyYWNrX2luZm8YCCABKAsyFy5kZXZpY2Vfc2V0LnYxLlJhY2tJbmZvSAASLgoKZ3JvdXBfaW5mbxgJIAEoCzIYLmRldmljZV9zZXQudjEuR3JvdXBJbmZvSABCDgoMdHlwZV9kZXRhaWxzIogCCghSYWNrSW5mbxIVCgRyb3dzGAEgASgFQge6SAQaAiAAEhgKB2NvbHVtbnMYAiABKAVCB7pIBBoCIAASFQoEem9uZRgDIAEoCUIHukgEcgIYZBIyCgtvcmRlcl9pbmRleBgEIAEoDjIdLmRldmljZV9zZXQudjEuUmFja09yZGVySW5kZXgSNAoMY29vbGluZ190eXBlGAUgASgOMh4uZGV2aWNlX3NldC52MS5SYWNrQ29vbGluZ1R5cGUSFAoHc2l0ZV9pZBgGIAEoA0gAiAEBEhgKC2J1aWxkaW5nX2lkGAcgASgDSAGIAQFCCgoIX3NpdGVfaWRCDgoMX2J1aWxkaW5nX2lkIgsKCUdyb3VwSW5mbyKeAQoPRGV2aWNlU2V0TWVtYmVyEhkKEWRldmljZV9pZGVudGlmaWVyGAEgASgJEiwKCGFkZGVkX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIwCgRyYWNrGAMgASgLMiAuZGV2aWNlX3NldC52MS5SYWNrTWVtYmVyRGV0YWlsc0gAQhAKDm1lbWJlcl9kZXRhaWxzIksKEVJhY2tNZW1iZXJEZXRhaWxzEjYKDXNsb3RfcG9zaXRpb24YASABKAsyHy5kZXZpY2Vfc2V0LnYxLlJhY2tTbG90UG9zaXRpb24iQQoQUmFja1Nsb3RQb3NpdGlvbhIUCgNyb3cYASABKAVCB7pIBBoCKAASFwoGY29sdW1uGAIgASgFQge6SAQaAigAIscCChZDcmVhdGVEZXZpY2VTZXRSZXF1ZXN0EjYKBHR5cGUYASABKA4yHC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFR5cGVCCrpIB4IBBBABIAASGwoFbGFiZWwYAiABKAlCDLpICcgBAXIEEAEYZBIdCgtkZXNjcmlwdGlvbhgDIAEoCUIIukgFcgMY9AMSLAoJcmFja19pbmZvGAQgASgLMhcuZGV2aWNlX3NldC52MS5SYWNrSW5mb0gAEi4KCmdyb3VwX2luZm8YBSABKAsyGC5kZXZpY2Vfc2V0LnYxLkdyb3VwSW5mb0gAEjcKD2RldmljZV9zZWxlY3RvchgGIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckgBiAEBQg4KDHR5cGVfZGV0YWlsc0ISChBfZGV2aWNlX3NlbGVjdG9yIlwKF0NyZWF0ZURldmljZVNldFJlc3BvbnNlEiwKCmRldmljZV9zZXQYASABKAsyGC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldBITCgthZGRlZF9jb3VudBgCIAEoBSI1ChNHZXREZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAAiRAoUR2V0RGV2aWNlU2V0UmVzcG9uc2USLAoKZGV2aWNlX3NldBgBIAEoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0Ir0CChZVcGRhdGVEZXZpY2VTZXRSZXF1ZXN0Eh4KDWRldmljZV9zZXRfaWQYASABKANCB7pIBCICIAASIAoFbGFiZWwYAiABKAlCDLpICdgBAXIEEAEYZEgBiAEBEiUKC2Rlc2NyaXB0aW9uGAMgASgJQgu6SAjYAQFyAxj0A0gCiAEBEiwKCXJhY2tfaW5mbxgEIAEoCzIXLmRldmljZV9zZXQudjEuUmFja0luZm9IABIuCgpncm91cF9pbmZvGAUgASgLMhguZGV2aWNlX3NldC52MS5Hcm91cEluZm9IABIyCg9kZXZpY2Vfc2VsZWN0b3IYBiABKAsyGS5jb21tb24udjEuRGV2aWNlU2VsZWN0b3JCDgoMdHlwZV9kZXRhaWxzQggKBl9sYWJlbEIOCgxfZGVzY3JpcHRpb24iRwoXVXBkYXRlRGV2aWNlU2V0UmVzcG9uc2USLAoKZGV2aWNlX3NldBgBIAEoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0IjgKFkRlbGV0ZURldmljZVNldFJlcXVlc3QSHgoNZGV2aWNlX3NldF9pZBgBIAEoA0IHukgEIgIgACIZChdEZWxldGVEZXZpY2VTZXRSZXNwb25zZSLIAgoVTGlzdERldmljZVNldHNSZXF1ZXN0EjQKBHR5cGUYASABKA4yHC5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFR5cGVCCLpIBYIBAhABEhoKCXBhZ2Vfc2l6ZRgCIAEoBUIHukgEGgIoABISCgpwYWdlX3Rva2VuGAMgASgJEiMKBHNvcnQYBCABKAsyFS5jb21tb24udjEuU29ydENvbmZpZxI3ChVlcnJvcl9jb21wb25lbnRfdHlwZXMYBSADKA4yGC5lcnJvcnMudjEuQ29tcG9uZW50VHlwZRIRCgV6b25lcxgGIAMoCUICGAESFAoMYnVpbGRpbmdfaWRzGAcgAygDEhsKE2luY2x1ZGVfbm9fYnVpbGRpbmcYCCABKAgSJQoJem9uZV9rZXlzGAkgAygLMhIuY29tbW9uLnYxLlpvbmVLZXkidQoWTGlzdERldmljZVNldHNSZXNwb25zZRItCgtkZXZpY2Vfc2V0cxgBIAMoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0EhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRITCgt0b3RhbF9jb3VudBgDIAEoBSJ4ChhBZGREZXZpY2VzVG9Hcm91cFJlcXVlc3QSIAoPdGFyZ2V0X2dyb3VwX2lkGAEgASgDQge6SAQiAiAAEjoKD2RldmljZV9zZWxlY3RvchgCIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckIGukgDyAEBIjAKGUFkZERldmljZXNUb0dyb3VwUmVzcG9uc2USEwoLYWRkZWRfY291bnQYASABKAMifQodUmVtb3ZlRGV2aWNlc0Zyb21Hcm91cFJlcXVlc3QSIAoPdGFyZ2V0X2dyb3VwX2lkGAEgASgDQge6SAQiAiAAEjoKD2RldmljZV9zZWxlY3RvchgCIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckIGukgDyAEBIjcKHlJlbW92ZURldmljZXNGcm9tR3JvdXBSZXNwb25zZRIVCg1yZW1vdmVkX2NvdW50GAEgASgDIm0KG0xpc3REZXZpY2VTZXRNZW1iZXJzUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEhoKCXBhZ2Vfc2l6ZRgCIAEoBUIHukgEGgIoABISCgpwYWdlX3Rva2VuGAMgASgJImgKHExpc3REZXZpY2VTZXRNZW1iZXJzUmVzcG9uc2USLwoHbWVtYmVycxgBIAMoCzIeLmRldmljZV9zZXQudjEuRGV2aWNlU2V0TWVtYmVyEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSJsChpHZXREZXZpY2VEZXZpY2VTZXRzUmVxdWVzdBIiChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCUIHukgEcgIQARIqCgR0eXBlGAIgASgOMhwuZGV2aWNlX3NldC52MS5EZXZpY2VTZXRUeXBlIkwKG0dldERldmljZURldmljZVNldHNSZXNwb25zZRItCgtkZXZpY2Vfc2V0cxgBIAMoCzIYLmRldmljZV9zZXQudjEuRGV2aWNlU2V0IpsBChpTZXRSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEiIKEWRldmljZV9pZGVudGlmaWVyGAIgASgJQge6SARyAhABEjkKCHBvc2l0aW9uGAMgASgLMh8uZGV2aWNlX3NldC52MS5SYWNrU2xvdFBvc2l0aW9uQga6SAPIAQEiWwobU2V0UmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlEhUKDWRldmljZV9zZXRfaWQYASABKAMSJQoEc2xvdBgCIAEoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3QiYgocQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBIeCg1kZXZpY2Vfc2V0X2lkGAEgASgDQge6SAQiAiAAEiIKEWRldmljZV9pZGVudGlmaWVyGAIgASgJQge6SARyAhABIh8KHUNsZWFyUmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlIjUKE0dldFJhY2tTbG90c1JlcXVlc3QSHgoNZGV2aWNlX3NldF9pZBgBIAEoA0IHukgEIgIgACJYCghSYWNrU2xvdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCghwb3NpdGlvbhgCIAEoCzIfLmRldmljZV9zZXQudjEuUmFja1Nsb3RQb3NpdGlvbiI+ChRHZXRSYWNrU2xvdHNSZXNwb25zZRImCgVzbG90cxgBIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3Qi7QQKDkRldmljZVNldFN0YXRzEhUKDWRldmljZV9zZXRfaWQYASABKAMSFAoMZGV2aWNlX2NvdW50GAIgASgFEhcKD3JlcG9ydGluZ19jb3VudBgDIAEoBRIaChJ0b3RhbF9oYXNocmF0ZV90aHMYBCABKAESGgoSYXZnX2VmZmljaWVuY3lfanRoGAUgASgBEhYKDnRvdGFsX3Bvd2VyX2t3GAYgASgBEhkKEW1pbl90ZW1wZXJhdHVyZV9jGAcgASgBEhkKEW1heF90ZW1wZXJhdHVyZV9jGAggASgBEhUKDWhhc2hpbmdfY291bnQYCSABKAUSFAoMYnJva2VuX2NvdW50GAogASgFEhUKDW9mZmxpbmVfY291bnQYCyABKAUSFgoOc2xlZXBpbmdfY291bnQYDCABKAUSIAoYaGFzaHJhdGVfcmVwb3J0aW5nX2NvdW50GA0gASgFEiIKGmVmZmljaWVuY3lfcmVwb3J0aW5nX2NvdW50GA4gASgFEh0KFXBvd2VyX3JlcG9ydGluZ19jb3VudBgPIAEoBRIjCht0ZW1wZXJhdHVyZV9yZXBvcnRpbmdfY291bnQYECABKAUSIQoZY29udHJvbF9ib2FyZF9pc3N1ZV9jb3VudBgRIAEoBRIXCg9mYW5faXNzdWVfY291bnQYEiABKAUSHgoWaGFzaF9ib2FyZF9pc3N1ZV9jb3VudBgTIAEoBRIXCg9wc3VfaXNzdWVfY291bnQYFCABKAUSNAoNc2xvdF9zdGF0dXNlcxgVIAMoCzIdLmRldmljZV9zZXQudjEuUmFja1Nsb3RTdGF0dXMiMgoYR2V0RGV2aWNlU2V0U3RhdHNSZXF1ZXN0EhYKDmRldmljZV9zZXRfaWRzGAEgAygDIkkKGUdldERldmljZVNldFN0YXRzUmVzcG9uc2USLAoFc3RhdHMYASADKAsyHS5kZXZpY2Vfc2V0LnYxLkRldmljZVNldFN0YXRzIl4KDlJhY2tTbG90U3RhdHVzEgsKA3JvdxgBIAEoBRIOCgZjb2x1bW4YAiABKAUSLwoGc3RhdHVzGAMgASgOMh8uZGV2aWNlX3NldC52MS5TbG90RGV2aWNlU3RhdHVzIhYKFExpc3RSYWNrWm9uZXNSZXF1ZXN0IiYKFUxpc3RSYWNrWm9uZXNSZXNwb25zZRINCgV6b25lcxgBIAMoCSIZChdMaXN0UmFja1pvbmVSZWZzUmVxdWVzdCI9ChhMaXN0UmFja1pvbmVSZWZzUmVzcG9uc2USIQoFem9uZXMYASADKAsyEi5jb21tb24udjEuWm9uZVJlZiIWChRMaXN0UmFja1R5cGVzUmVxdWVzdCI9CghSYWNrVHlwZRIMCgRyb3dzGAEgASgFEg8KB2NvbHVtbnMYAiABKAUSEgoKcmFja19jb3VudBgDIAEoBSJEChVMaXN0UmFja1R5cGVzUmVzcG9uc2USKwoKcmFja190eXBlcxgBIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1R5cGUiiwIKD1NhdmVSYWNrUmVxdWVzdBImCg1kZXZpY2Vfc2V0X2lkGAEgASgDQgq6SAfYAQEiAiAASACIAQESGwoFbGFiZWwYAiABKAlCDLpICcgBAXIEEAEYZBIyCglyYWNrX2luZm8YAyABKAsyFy5kZXZpY2Vfc2V0LnYxLlJhY2tJbmZvQga6SAPIAQESOgoPZGV2aWNlX3NlbGVjdG9yGAQgASgLMhkuY29tbW9uLnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQESMQoQc2xvdF9hc3NpZ25tZW50cxgFIAMoCzIXLmRldmljZV9zZXQudjEuUmFja1Nsb3RCEAoOX2RldmljZV9zZXRfaWQidwoQU2F2ZVJhY2tSZXNwb25zZRIsCgpkZXZpY2Vfc2V0GAEgASgLMhguZGV2aWNlX3NldC52MS5EZXZpY2VTZXQSFgoOYXNzaWduZWRfY291bnQYAiABKAUSHQoVc2l0ZV9yZWFzc2lnbmVkX2NvdW50GAMgASgFIpEBChpBc3NpZ25EZXZpY2VzVG9SYWNrUmVxdWVzdBIkCg50YXJnZXRfcmFja19pZBgBIAEoA0IHukgEIgIgAEgAiAEBEjoKD2RldmljZV9zZWxlY3RvchgCIAEoCzIZLmNvbW1vbi52MS5EZXZpY2VTZWxlY3RvckIGukgDyAEBQhEKD190YXJnZXRfcmFja19pZCJrChtBc3NpZ25EZXZpY2VzVG9SYWNrUmVzcG9uc2USFgoOYXNzaWduZWRfY291bnQYASABKAMSHQoVc2l0ZV9yZWFzc2lnbmVkX2NvdW50GAIgASgDEhUKDXJlbW92ZWRfY291bnQYAyABKAMqZQoNRGV2aWNlU2V0VHlwZRIfChtERVZJQ0VfU0VUX1RZUEVfVU5TUEVDSUZJRUQQABIZChVERVZJQ0VfU0VUX1RZUEVfR1JPVVAQARIYChRERVZJQ0VfU0VUX1RZUEVfUkFDSxACKrYBCg5SYWNrT3JkZXJJbmRleBIgChxSQUNLX09SREVSX0lOREVYX1VOU1BFQ0lGSUVEEAASIAocUkFDS19PUkRFUl9JTkRFWF9CT1RUT01fTEVGVBABEh0KGVJBQ0tfT1JERVJfSU5ERVhfVE9QX0xFRlQQAhIhCh1SQUNLX09SREVSX0lOREVYX0JPVFRPTV9SSUdIVBADEh4KGlJBQ0tfT1JERVJfSU5ERVhfVE9QX1JJR0hUEAQqcAoPUmFja0Nvb2xpbmdUeXBlEiEKHVJBQ0tfQ09PTElOR19UWVBFX1VOU1BFQ0lGSUVEEAASGQoVUkFDS19DT09MSU5HX1RZUEVfQUlSEAESHwobUkFDS19DT09MSU5HX1RZUEVfSU1NRVJTSU9OEAIq3QEKEFNsb3REZXZpY2VTdGF0dXMSIgoeU0xPVF9ERVZJQ0VfU1RBVFVTX1VOU1BFQ0lGSUVEEAASHAoYU0xPVF9ERVZJQ0VfU1RBVFVTX0VNUFRZEAESHgoaU0xPVF9ERVZJQ0VfU1RBVFVTX0hFQUxUSFkQAhImCiJTTE9UX0RFVklDRV9TVEFUVVNfTkVFRFNfQVRURU5USU9OEAMSHgoaU0xPVF9ERVZJQ0VfU1RBVFVTX09GRkxJTkUQBBIfChtTTE9UX0RFVklDRV9TVEFUVVNfU0xFRVBJTkcQBTKpDgoQRGV2aWNlU2V0U2VydmljZRJgCg9DcmVhdGVEZXZpY2VTZXQSJS5kZXZpY2Vfc2V0LnYxLkNyZWF0ZURldmljZVNldFJlcXVlc3QaJi5kZXZpY2Vfc2V0LnYxLkNyZWF0ZURldmljZVNldFJlc3BvbnNlElcKDEdldERldmljZVNldBIiLmRldmljZV9zZXQudjEuR2V0RGV2aWNlU2V0UmVxdWVzdBojLmRldmljZV9zZXQudjEuR2V0RGV2aWNlU2V0UmVzcG9uc2USYAoPVXBkYXRlRGV2aWNlU2V0EiUuZGV2aWNlX3NldC52MS5VcGRhdGVEZXZpY2VTZXRSZXF1ZXN0GiYuZGV2aWNlX3NldC52MS5VcGRhdGVEZXZpY2VTZXRSZXNwb25zZRJgCg9EZWxldGVEZXZpY2VTZXQSJS5kZXZpY2Vfc2V0LnYxLkRlbGV0ZURldmljZVNldFJlcXVlc3QaJi5kZXZpY2Vfc2V0LnYxLkRlbGV0ZURldmljZVNldFJlc3BvbnNlEl0KDkxpc3REZXZpY2VTZXRzEiQuZGV2aWNlX3NldC52MS5MaXN0RGV2aWNlU2V0c1JlcXVlc3QaJS5kZXZpY2Vfc2V0LnYxLkxpc3REZXZpY2VTZXRzUmVzcG9uc2USZgoRQWRkRGV2aWNlc1RvR3JvdXASJy5kZXZpY2Vfc2V0LnYxLkFkZERldmljZXNUb0dyb3VwUmVxdWVzdBooLmRldmljZV9zZXQudjEuQWRkRGV2aWNlc1RvR3JvdXBSZXNwb25zZRJ1ChZSZW1vdmVEZXZpY2VzRnJvbUdyb3VwEiwuZGV2aWNlX3NldC52MS5SZW1vdmVEZXZpY2VzRnJvbUdyb3VwUmVxdWVzdBotLmRldmljZV9zZXQudjEuUmVtb3ZlRGV2aWNlc0Zyb21Hcm91cFJlc3BvbnNlEm8KFExpc3REZXZpY2VTZXRNZW1iZXJzEiouZGV2aWNlX3NldC52MS5MaXN0RGV2aWNlU2V0TWVtYmVyc1JlcXVlc3QaKy5kZXZpY2Vfc2V0LnYxLkxpc3REZXZpY2VTZXRNZW1iZXJzUmVzcG9uc2USbAoTR2V0RGV2aWNlRGV2aWNlU2V0cxIpLmRldmljZV9zZXQudjEuR2V0RGV2aWNlRGV2aWNlU2V0c1JlcXVlc3QaKi5kZXZpY2Vfc2V0LnYxLkdldERldmljZURldmljZVNldHNSZXNwb25zZRJsChNTZXRSYWNrU2xvdFBvc2l0aW9uEikuZGV2aWNlX3NldC52MS5TZXRSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBoqLmRldmljZV9zZXQudjEuU2V0UmFja1Nsb3RQb3NpdGlvblJlc3BvbnNlEnIKFUNsZWFyUmFja1Nsb3RQb3NpdGlvbhIrLmRldmljZV9zZXQudjEuQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVxdWVzdBosLmRldmljZV9zZXQudjEuQ2xlYXJSYWNrU2xvdFBvc2l0aW9uUmVzcG9uc2USVwoMR2V0UmFja1Nsb3RzEiIuZGV2aWNlX3NldC52MS5HZXRSYWNrU2xvdHNSZXF1ZXN0GiMuZGV2aWNlX3NldC52MS5HZXRSYWNrU2xvdHNSZXNwb25zZRJmChFHZXREZXZpY2VTZXRTdGF0cxInLmRldmljZV9zZXQudjEuR2V0RGV2aWNlU2V0U3RhdHNSZXF1ZXN0GiguZGV2aWNlX3NldC52MS5HZXREZXZpY2VTZXRTdGF0c1Jlc3BvbnNlEloKDUxpc3RSYWNrWm9uZXMSIy5kZXZpY2Vfc2V0LnYxLkxpc3RSYWNrWm9uZXNSZXF1ZXN0GiQuZGV2aWNlX3NldC52MS5MaXN0UmFja1pvbmVzUmVzcG9uc2USYwoQTGlzdFJhY2tab25lUmVmcxImLmRldmljZV9zZXQudjEuTGlzdFJhY2tab25lUmVmc1JlcXVlc3QaJy5kZXZpY2Vfc2V0LnYxLkxpc3RSYWNrWm9uZVJlZnNSZXNwb25zZRJaCg1MaXN0UmFja1R5cGVzEiMuZGV2aWNlX3NldC52MS5MaXN0UmFja1R5cGVzUmVxdWVzdBokLmRldmljZV9zZXQudjEuTGlzdFJhY2tUeXBlc1Jlc3BvbnNlEksKCFNhdmVSYWNrEh4uZGV2aWNlX3NldC52MS5TYXZlUmFja1JlcXVlc3QaHy5kZXZpY2Vfc2V0LnYxLlNhdmVSYWNrUmVzcG9uc2USbAoTQXNzaWduRGV2aWNlc1RvUmFjaxIpLmRldmljZV9zZXQudjEuQXNzaWduRGV2aWNlc1RvUmFja1JlcXVlc3QaKi5kZXZpY2Vfc2V0LnYxLkFzc2lnbkRldmljZXNUb1JhY2tSZXNwb25zZULDAQoRY29tLmRldmljZV9zZXQudjFCDkRldmljZVNldFByb3RvUAFaTWdpdGh1Yi5jb20vYmxvY2svcHJvdG8tZmxlZXQvc2VydmVyL2dlbmVyYXRlZC9ncnBjL2RldmljZV9zZXQvdjE7ZGV2aWNlX3NldHYxogIDRFhYqgIMRGV2aWNlU2V0LlYxygIMRGV2aWNlU2V0XFYx4gIYRGV2aWNlU2V0XFYxXEdQQk1ldGFkYXRh6gINRGV2aWNlU2V0OjpWMWIGcHJvdG8z", [ file_google_protobuf_timestamp, file_buf_validate_validate, @@ -701,24 +701,24 @@ export const ListDeviceSetsResponseSchema: GenMessage = messageDesc(file_device_set_v1_device_set, 15); /** - * Request to add devices to a device set. + * Request to add devices to a group. Groups have many-to-many device + * membership: devices may already belong to other groups and to a rack, + * none of which are touched. Server rejects with InvalidArgument when + * target_group_id refers to a non-group device set. * - * When the target is a site-stamped rack, the server rewrites - * device.site_id to match the rack for every added device. The affected - * count is returned in site_reassigned_count. Groups are exempt. - * - * @generated from message device_set.v1.AddDevicesToDeviceSetRequest + * @generated from message device_set.v1.AddDevicesToGroupRequest */ -export type AddDevicesToDeviceSetRequest = Message<"device_set.v1.AddDevicesToDeviceSetRequest"> & { +export type AddDevicesToGroupRequest = Message<"device_set.v1.AddDevicesToGroupRequest"> & { /** - * ID of the device set to add devices to + * ID of the group to add devices to. Must reference a device set of + * type GROUP; racks are rejected. * - * @generated from field: int64 device_set_id = 1; + * @generated from field: int64 target_group_id = 1; */ - deviceSetId: bigint; + targetGroupId: bigint; /** - * Devices to add: specific list or all paired devices + * Devices to add: specific list or all paired devices. * * @generated from field: common.v1.DeviceSelector device_selector = 2; */ @@ -726,65 +726,54 @@ export type AddDevicesToDeviceSetRequest = Message<"device_set.v1.AddDevicesToDe }; /** - * Describes the message device_set.v1.AddDevicesToDeviceSetRequest. - * Use `create(AddDevicesToDeviceSetRequestSchema)` to create a new message. + * Describes the message device_set.v1.AddDevicesToGroupRequest. + * Use `create(AddDevicesToGroupRequestSchema)` to create a new message. */ -export const AddDevicesToDeviceSetRequestSchema: GenMessage = +export const AddDevicesToGroupRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_device_set_v1_device_set, 16); /** - * Response after adding devices to a device set + * Response after adding devices to a group. * - * @generated from message device_set.v1.AddDevicesToDeviceSetResponse + * @generated from message device_set.v1.AddDevicesToGroupResponse */ -export type AddDevicesToDeviceSetResponse = Message<"device_set.v1.AddDevicesToDeviceSetResponse"> & { +export type AddDevicesToGroupResponse = Message<"device_set.v1.AddDevicesToGroupResponse"> & { /** - * ID of the device set devices were added to + * Number of devices successfully added. May be less than requested + * if some devices were already members. * - * @generated from field: int64 device_set_id = 1; + * @generated from field: int64 added_count = 1; */ - deviceSetId: bigint; - - /** - * Number of devices successfully added - * May be less than requested if some devices were already members - * - * @generated from field: int32 added_count = 2; - */ - addedCount: number; - - /** - * Number of devices whose site_id was rewritten by the cascade. - * - * @generated from field: int32 site_reassigned_count = 3; - */ - siteReassignedCount: number; + addedCount: bigint; }; /** - * Describes the message device_set.v1.AddDevicesToDeviceSetResponse. - * Use `create(AddDevicesToDeviceSetResponseSchema)` to create a new message. + * Describes the message device_set.v1.AddDevicesToGroupResponse. + * Use `create(AddDevicesToGroupResponseSchema)` to create a new message. */ -export const AddDevicesToDeviceSetResponseSchema: GenMessage = +export const AddDevicesToGroupResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_device_set_v1_device_set, 17); /** - * Request to remove devices from a device set + * Request to remove devices from a group. Server rejects with + * InvalidArgument when target_group_id refers to a non-group device set; + * use AssignDevicesToRack to clear rack membership. * - * @generated from message device_set.v1.RemoveDevicesFromDeviceSetRequest + * @generated from message device_set.v1.RemoveDevicesFromGroupRequest */ -export type RemoveDevicesFromDeviceSetRequest = Message<"device_set.v1.RemoveDevicesFromDeviceSetRequest"> & { +export type RemoveDevicesFromGroupRequest = Message<"device_set.v1.RemoveDevicesFromGroupRequest"> & { /** - * ID of the device set to remove devices from + * ID of the group to remove devices from. Must reference a device + * set of type GROUP; racks are rejected. * - * @generated from field: int64 device_set_id = 1; + * @generated from field: int64 target_group_id = 1; */ - deviceSetId: bigint; + targetGroupId: bigint; /** - * Devices to remove: specific list or all paired devices + * Devices to remove: specific list or all paired devices. * * @generated from field: common.v1.DeviceSelector device_selector = 2; */ @@ -792,32 +781,32 @@ export type RemoveDevicesFromDeviceSetRequest = Message<"device_set.v1.RemoveDev }; /** - * Describes the message device_set.v1.RemoveDevicesFromDeviceSetRequest. - * Use `create(RemoveDevicesFromDeviceSetRequestSchema)` to create a new message. + * Describes the message device_set.v1.RemoveDevicesFromGroupRequest. + * Use `create(RemoveDevicesFromGroupRequestSchema)` to create a new message. */ -export const RemoveDevicesFromDeviceSetRequestSchema: GenMessage = +export const RemoveDevicesFromGroupRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_device_set_v1_device_set, 18); /** - * Response after removing devices from a device set + * Response after removing devices from a group. * - * @generated from message device_set.v1.RemoveDevicesFromDeviceSetResponse + * @generated from message device_set.v1.RemoveDevicesFromGroupResponse */ -export type RemoveDevicesFromDeviceSetResponse = Message<"device_set.v1.RemoveDevicesFromDeviceSetResponse"> & { +export type RemoveDevicesFromGroupResponse = Message<"device_set.v1.RemoveDevicesFromGroupResponse"> & { /** - * Number of devices successfully removed + * Number of devices successfully removed. * - * @generated from field: int32 removed_count = 1; + * @generated from field: int64 removed_count = 1; */ - removedCount: number; + removedCount: bigint; }; /** - * Describes the message device_set.v1.RemoveDevicesFromDeviceSetResponse. - * Use `create(RemoveDevicesFromDeviceSetResponseSchema)` to create a new message. + * Describes the message device_set.v1.RemoveDevicesFromGroupResponse. + * Use `create(RemoveDevicesFromGroupResponseSchema)` to create a new message. */ -export const RemoveDevicesFromDeviceSetResponseSchema: GenMessage = +export const RemoveDevicesFromGroupResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_device_set_v1_device_set, 19); @@ -1599,9 +1588,15 @@ export type AssignDevicesToRackRequest = Message<"device_set.v1.AssignDevicesToR targetRackId?: bigint | undefined; /** - * @generated from field: repeated string device_identifiers = 2; + * Devices to assign. Only the device_list variant is accepted; the + * all_devices variant is rejected with InvalidArgument because + * moving every paired device into a single rack is never the + * intended operation. Filter-based selectors are reserved for a + * future expansion. + * + * @generated from field: common.v1.DeviceSelector device_selector = 2; */ - deviceIdentifiers: string[]; + deviceSelector?: DeviceSelector | undefined; }; /** @@ -1852,24 +1847,27 @@ export const DeviceSetService: GenService<{ output: typeof ListDeviceSetsResponseSchema; }; /** - * Adds devices to a device set + * Adds devices to a group (many-to-many). Rejects non-group targets + * with InvalidArgument; use AssignDevicesToRack for racks. * - * @generated from rpc device_set.v1.DeviceSetService.AddDevicesToDeviceSet + * @generated from rpc device_set.v1.DeviceSetService.AddDevicesToGroup */ - addDevicesToDeviceSet: { + addDevicesToGroup: { methodKind: "unary"; - input: typeof AddDevicesToDeviceSetRequestSchema; - output: typeof AddDevicesToDeviceSetResponseSchema; + input: typeof AddDevicesToGroupRequestSchema; + output: typeof AddDevicesToGroupResponseSchema; }; /** - * Removes devices from a device set + * Removes devices from a group. Rejects non-group targets with + * InvalidArgument; rack membership is cleared via + * AssignDevicesToRack with target_rack_id unset. * - * @generated from rpc device_set.v1.DeviceSetService.RemoveDevicesFromDeviceSet + * @generated from rpc device_set.v1.DeviceSetService.RemoveDevicesFromGroup */ - removeDevicesFromDeviceSet: { + removeDevicesFromGroup: { methodKind: "unary"; - input: typeof RemoveDevicesFromDeviceSetRequestSchema; - output: typeof RemoveDevicesFromDeviceSetResponseSchema; + input: typeof RemoveDevicesFromGroupRequestSchema; + output: typeof RemoveDevicesFromGroupResponseSchema; }; /** * Lists members of a device set @@ -1985,11 +1983,15 @@ export const DeviceSetService: GenService<{ * device's current site. target_rack_id unset clears rack * membership without re-assigning (site/building stay intact). * - * Closes the orphan window where a client-side - * RemoveDevicesFromDeviceSet + AddDevicesToDeviceSet pair could - * leave devices without rack membership on transport failure between - * the two calls, and the cross-rack rename race where the source - * rack's id resolution drifts between the resolve and the remove. + * Closes the orphan window where a client-side remove + add pair + * could leave devices without rack membership on transport failure + * between the two calls, and the cross-rack rename race where the + * source rack's id resolution drifts between the resolve and the + * remove. + * + * device_selector accepts only the device_list variant. all_devices + * is rejected with InvalidArgument because moving every paired + * device into a single rack is never the intended operation. * * @generated from rpc device_set.v1.DeviceSetService.AssignDevicesToRack */ diff --git a/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts b/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts index 2d0ecad55..68ca9a270 100644 --- a/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts +++ b/client/src/protoFleet/api/generated/sites/v1/sites_pb.ts @@ -15,7 +15,7 @@ import type { Message } from "@bufbuild/protobuf"; export const file_sites_v1_sites: GenFile = /*@__PURE__*/ fileDesc( - "ChRzaXRlcy92MS9zaXRlcy5wcm90bxIIc2l0ZXMudjEi4gIKBFNpdGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIVCg1sb2NhdGlvbl9jaXR5GAQgASgJEhYKDmxvY2F0aW9uX3N0YXRlGAUgASgJEhAKCHRpbWV6b25lGAYgASgJEhkKEXBvd2VyX2NhcGFjaXR5X213GAcgASgBEhYKDm5ldHdvcmtfY29uZmlnGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB2FkZHJlc3MYCyABKAkSEwoLcG9zdGFsX2NvZGUYDSABKAkSDwoHY291bnRyeRgOIAEoCRINCgVub3RlcxgPIAEoCUoECAMQBEoECAwQDVILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIicAoOU2l0ZVdpdGhDb3VudHMSHAoEc2l0ZRgBIAEoCzIOLnNpdGVzLnYxLlNpdGUSFAoMZGV2aWNlX2NvdW50GAIgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAMgASgDEhIKCnJhY2tfY291bnQYBCABKAMiEgoQTGlzdFNpdGVzUmVxdWVzdCI8ChFMaXN0U2l0ZXNSZXNwb25zZRInCgVzaXRlcxgBIAMoCzIYLnNpdGVzLnYxLlNpdGVXaXRoQ291bnRzIu0CChFDcmVhdGVTaXRlUmVxdWVzdBIYCgRuYW1lGAEgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYAyABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAQgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgFIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgGIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYByABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAggASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgKIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAsgASgJQge6SARyAhgCEhcKBW5vdGVzGAwgASgJQgi6SAVyAxiAIEoECAIQA0oECAkQClILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSQ3JlYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIoIDChFVcGRhdGVTaXRlUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYBCABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAUgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgGIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgHIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYCCABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAkgASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgLIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAwgASgJQge6SARyAhgCEhcKBW5vdGVzGA0gASgJQgi6SAVyAxiAIEoECAMQBEoECAoQC1ILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSVXBkYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIigKEURlbGV0ZVNpdGVSZXF1ZXN0EhMKAmlkGAEgASgDQge6SAQiAiAAInQKEkRlbGV0ZVNpdGVSZXNwb25zZRIfChd1bmFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZkZWxldGVkX2J1aWxkaW5nX2NvdW50GAIgASgDEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgDIAEoAyKHAQoaQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QSJAoOdGFyZ2V0X3NpdGVfaWQYASABKANCB7pIBCICIABIAIgBARIwChJkZXZpY2VfaWRlbnRpZmllcnMYAiADKAlCFLpIEZIBDggBEJBOIgdyBRABGIACQhEKD190YXJnZXRfc2l0ZV9pZCJ+ChFQZXJEZXZpY2VDb25mbGljdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIxCgZyZWFzb24YAiABKA4yIS5zaXRlcy52MS5QZXJEZXZpY2VDb25mbGljdFJlYXNvbhIbChNjb25mbGljdGluZ19zaXRlX2lkGAMgASgDImcKG0Fzc2lnbkRldmljZXNUb1NpdGVSZXNwb25zZRIYChByZWFzc2lnbmVkX2NvdW50GAEgASgDEi4KCWNvbmZsaWN0cxgCIAMoCzIbLnNpdGVzLnYxLlBlckRldmljZUNvbmZsaWN0IoABChxBc3NpZ25CdWlsZGluZ3NUb1NpdGVSZXF1ZXN0EicKDGJ1aWxkaW5nX2lkcxgBIAMoA0IRukgOkgELCAEQ6AciBCICIAASJAoOdGFyZ2V0X3NpdGVfaWQYAiABKANCB7pIBCICIABIAIgBAUIRCg9fdGFyZ2V0X3NpdGVfaWQiXwodQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVzcG9uc2USHQoVcmVhc3NpZ25lZF9yYWNrX2NvdW50GAEgASgDEh8KF3JlYXNzaWduZWRfZGV2aWNlX2NvdW50GAIgASgDIngKGEFzc2lnblJhY2tzVG9TaXRlUmVxdWVzdBIjCghyYWNrX2lkcxgBIAMoA0IRukgOkgELCAEQ6AciBCICIAASJAoOdGFyZ2V0X3NpdGVfaWQYAiABKANCB7pIBCICIABIAIgBAUIRCg9fdGFyZ2V0X3NpdGVfaWQiXAoZQXNzaWduUmFja3NUb1NpdGVSZXNwb25zZRIfChdyZWFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZjbGVhcmVkX2J1aWxkaW5nX2NvdW50GAIgASgDIi8KE0dldFNpdGVTdGF0c1JlcXVlc3QSGAoHc2l0ZV9pZBgBIAEoA0IHukgEIgIgACL/AgoUR2V0U2l0ZVN0YXRzUmVzcG9uc2USDwoHc2l0ZV9pZBgBIAEoAxIWCg5idWlsZGluZ19jb3VudBgCIAEoBRIUCgxkZXZpY2VfY291bnQYAyABKAUSFwoPcmVwb3J0aW5nX2NvdW50GAQgASgFEhoKEnRvdGFsX2hhc2hyYXRlX3RocxgFIAEoARIaChJhdmdfZWZmaWNpZW5jeV9qdGgYBiABKAESFgoOdG90YWxfcG93ZXJfa3cYByABKAESFQoNaGFzaGluZ19jb3VudBgIIAEoBRIUCgxicm9rZW5fY291bnQYCSABKAUSFQoNb2ZmbGluZV9jb3VudBgKIAEoBRIWCg5zbGVlcGluZ19jb3VudBgLIAEoBRIgChhoYXNocmF0ZV9yZXBvcnRpbmdfY291bnQYDCABKAUSIgoaZWZmaWNpZW5jeV9yZXBvcnRpbmdfY291bnQYDSABKAUSHQoVcG93ZXJfcmVwb3J0aW5nX2NvdW50GA4gASgFKrMBChdQZXJEZXZpY2VDb25mbGljdFJlYXNvbhIqCiZQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9VTlNQRUNJRklFRBAAEi8KK1BFUl9ERVZJQ0VfQ09ORkxJQ1RfUkVBU09OX0RFVklDRV9OT1RfRk9VTkQQARI7CjdQRVJfREVWSUNFX0NPTkZMSUNUX1JFQVNPTl9ERVZJQ0VfSU5fUkFDS19BVF9PVEhFUl9TSVRFEAIyqQUKC1NpdGVTZXJ2aWNlEkQKCUxpc3RTaXRlcxIaLnNpdGVzLnYxLkxpc3RTaXRlc1JlcXVlc3QaGy5zaXRlcy52MS5MaXN0U2l0ZXNSZXNwb25zZRJHCgpDcmVhdGVTaXRlEhsuc2l0ZXMudjEuQ3JlYXRlU2l0ZVJlcXVlc3QaHC5zaXRlcy52MS5DcmVhdGVTaXRlUmVzcG9uc2USRwoKVXBkYXRlU2l0ZRIbLnNpdGVzLnYxLlVwZGF0ZVNpdGVSZXF1ZXN0Ghwuc2l0ZXMudjEuVXBkYXRlU2l0ZVJlc3BvbnNlEkcKCkRlbGV0ZVNpdGUSGy5zaXRlcy52MS5EZWxldGVTaXRlUmVxdWVzdBocLnNpdGVzLnYxLkRlbGV0ZVNpdGVSZXNwb25zZRJiChNBc3NpZ25EZXZpY2VzVG9TaXRlEiQuc2l0ZXMudjEuQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QaJS5zaXRlcy52MS5Bc3NpZ25EZXZpY2VzVG9TaXRlUmVzcG9uc2USaAoVQXNzaWduQnVpbGRpbmdzVG9TaXRlEiYuc2l0ZXMudjEuQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVxdWVzdBonLnNpdGVzLnYxLkFzc2lnbkJ1aWxkaW5nc1RvU2l0ZVJlc3BvbnNlElwKEUFzc2lnblJhY2tzVG9TaXRlEiIuc2l0ZXMudjEuQXNzaWduUmFja3NUb1NpdGVSZXF1ZXN0GiMuc2l0ZXMudjEuQXNzaWduUmFja3NUb1NpdGVSZXNwb25zZRJNCgxHZXRTaXRlU3RhdHMSHS5zaXRlcy52MS5HZXRTaXRlU3RhdHNSZXF1ZXN0Gh4uc2l0ZXMudjEuR2V0U2l0ZVN0YXRzUmVzcG9uc2VCoAEKDGNvbS5zaXRlcy52MUIKU2l0ZXNQcm90b1ABWkNnaXRodWIuY29tL2Jsb2NrL3Byb3RvLWZsZWV0L3NlcnZlci9nZW5lcmF0ZWQvZ3JwYy9zaXRlcy92MTtzaXRlc3YxogIDU1hYqgIIU2l0ZXMuVjHKAghTaXRlc1xWMeICFFNpdGVzXFYxXEdQQk1ldGFkYXRh6gIJU2l0ZXM6OlYxYgZwcm90bzM", + "ChRzaXRlcy92MS9zaXRlcy5wcm90bxIIc2l0ZXMudjEi4gIKBFNpdGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIVCg1sb2NhdGlvbl9jaXR5GAQgASgJEhYKDmxvY2F0aW9uX3N0YXRlGAUgASgJEhAKCHRpbWV6b25lGAYgASgJEhkKEXBvd2VyX2NhcGFjaXR5X213GAcgASgBEhYKDm5ldHdvcmtfY29uZmlnGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB2FkZHJlc3MYCyABKAkSEwoLcG9zdGFsX2NvZGUYDSABKAkSDwoHY291bnRyeRgOIAEoCRINCgVub3RlcxgPIAEoCUoECAMQBEoECAwQDVILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIicAoOU2l0ZVdpdGhDb3VudHMSHAoEc2l0ZRgBIAEoCzIOLnNpdGVzLnYxLlNpdGUSFAoMZGV2aWNlX2NvdW50GAIgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAMgASgDEhIKCnJhY2tfY291bnQYBCABKAMiEgoQTGlzdFNpdGVzUmVxdWVzdCI8ChFMaXN0U2l0ZXNSZXNwb25zZRInCgVzaXRlcxgBIAMoCzIYLnNpdGVzLnYxLlNpdGVXaXRoQ291bnRzIu0CChFDcmVhdGVTaXRlUmVxdWVzdBIYCgRuYW1lGAEgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYAyABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAQgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgFIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgGIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYByABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAggASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgKIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAsgASgJQge6SARyAhgCEhcKBW5vdGVzGAwgASgJQgi6SAVyAxiAIEoECAIQA0oECAkQClILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSQ3JlYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIoIDChFVcGRhdGVTaXRlUmVxdWVzdBITCgJpZBgBIAEoA0IHukgEIgIgABIYCgRuYW1lGAIgASgJQgq6SAdyBRABGP8BEh8KDWxvY2F0aW9uX2NpdHkYBCABKAlCCLpIBXIDGP8BEiAKDmxvY2F0aW9uX3N0YXRlGAUgASgJQgi6SAVyAxj/ARIZCgh0aW1lem9uZRgGIAEoCUIHukgEcgIYQBIpChFwb3dlcl9jYXBhY2l0eV9tdxgHIAEoAUIOukgLEgkpAAAAAAAAAAASIQoObmV0d29ya19jb25maWcYCCABKAlCCbpIBnIEKICAARIZCgdhZGRyZXNzGAkgASgJQgi6SAVyAxj/ARIcCgtwb3N0YWxfY29kZRgLIAEoCUIHukgEcgIYIBIYCgdjb3VudHJ5GAwgASgJQge6SARyAhgCEhcKBW5vdGVzGA0gASgJQgi6SAVyAxiAIEoECAMQBEoECAoQC1ILZGVzY3JpcHRpb25SDWFkZHJlc3NfbGluZTIiUwoSVXBkYXRlU2l0ZVJlc3BvbnNlEhwKBHNpdGUYASABKAsyDi5zaXRlcy52MS5TaXRlEh8KF25ldHdvcmtfY29uZmlnX3dhcm5pbmdzGAIgAygJIigKEURlbGV0ZVNpdGVSZXF1ZXN0EhMKAmlkGAEgASgDQge6SAQiAiAAInQKEkRlbGV0ZVNpdGVSZXNwb25zZRIfChd1bmFzc2lnbmVkX2RldmljZV9jb3VudBgBIAEoAxIeChZkZWxldGVkX2J1aWxkaW5nX2NvdW50GAIgASgDEh0KFXVuYXNzaWduZWRfcmFja19jb3VudBgDIAEoAyLpAQoaQXNzaWduRGV2aWNlc1RvU2l0ZVJlcXVlc3QSJAoOdGFyZ2V0X3NpdGVfaWQYASABKANCB7pIBCICIABIAIgBARIwChJkZXZpY2VfaWRlbnRpZmllcnMYAiADKAlCFLpIEZIBDggBEJBOIgdyBRABGIACEjQKJ2ZvcmNlX2NsZWFyX2NvbmZsaWN0aW5nX3JhY2tfbWVtYmVyc2hpcBgDIAEoCEgBiAEBQhEKD190YXJnZXRfc2l0ZV9pZEIqCihfZm9yY2VfY2xlYXJfY29uZmxpY3RpbmdfcmFja19tZW1iZXJzaGlwIn4KEVBlckRldmljZUNvbmZsaWN0EhkKEWRldmljZV9pZGVudGlmaWVyGAEgASgJEjEKBnJlYXNvbhgCIAEoDjIhLnNpdGVzLnYxLlBlckRldmljZUNvbmZsaWN0UmVhc29uEhsKE2NvbmZsaWN0aW5nX3NpdGVfaWQYAyABKAMiZwobQXNzaWduRGV2aWNlc1RvU2l0ZVJlc3BvbnNlEhgKEHJlYXNzaWduZWRfY291bnQYASABKAMSLgoJY29uZmxpY3RzGAIgAygLMhsuc2l0ZXMudjEuUGVyRGV2aWNlQ29uZmxpY3QigAEKHEFzc2lnbkJ1aWxkaW5nc1RvU2l0ZVJlcXVlc3QSJwoMYnVpbGRpbmdfaWRzGAEgAygDQhG6SA6SAQsIARDoByIEIgIgABIkCg50YXJnZXRfc2l0ZV9pZBgCIAEoA0IHukgEIgIgAEgAiAEBQhEKD190YXJnZXRfc2l0ZV9pZCJfCh1Bc3NpZ25CdWlsZGluZ3NUb1NpdGVSZXNwb25zZRIdChVyZWFzc2lnbmVkX3JhY2tfY291bnQYASABKAMSHwoXcmVhc3NpZ25lZF9kZXZpY2VfY291bnQYAiABKAMieAoYQXNzaWduUmFja3NUb1NpdGVSZXF1ZXN0EiMKCHJhY2tfaWRzGAEgAygDQhG6SA6SAQsIARDoByIEIgIgABIkCg50YXJnZXRfc2l0ZV9pZBgCIAEoA0IHukgEIgIgAEgAiAEBQhEKD190YXJnZXRfc2l0ZV9pZCJcChlBc3NpZ25SYWNrc1RvU2l0ZVJlc3BvbnNlEh8KF3JlYXNzaWduZWRfZGV2aWNlX2NvdW50GAEgASgDEh4KFmNsZWFyZWRfYnVpbGRpbmdfY291bnQYAiABKAMiLwoTR2V0U2l0ZVN0YXRzUmVxdWVzdBIYCgdzaXRlX2lkGAEgASgDQge6SAQiAiAAIv8CChRHZXRTaXRlU3RhdHNSZXNwb25zZRIPCgdzaXRlX2lkGAEgASgDEhYKDmJ1aWxkaW5nX2NvdW50GAIgASgFEhQKDGRldmljZV9jb3VudBgDIAEoBRIXCg9yZXBvcnRpbmdfY291bnQYBCABKAUSGgoSdG90YWxfaGFzaHJhdGVfdGhzGAUgASgBEhoKEmF2Z19lZmZpY2llbmN5X2p0aBgGIAEoARIWCg50b3RhbF9wb3dlcl9rdxgHIAEoARIVCg1oYXNoaW5nX2NvdW50GAggASgFEhQKDGJyb2tlbl9jb3VudBgJIAEoBRIVCg1vZmZsaW5lX2NvdW50GAogASgFEhYKDnNsZWVwaW5nX2NvdW50GAsgASgFEiAKGGhhc2hyYXRlX3JlcG9ydGluZ19jb3VudBgMIAEoBRIiChplZmZpY2llbmN5X3JlcG9ydGluZ19jb3VudBgNIAEoBRIdChVwb3dlcl9yZXBvcnRpbmdfY291bnQYDiABKAUqswEKF1BlckRldmljZUNvbmZsaWN0UmVhc29uEioKJlBFUl9ERVZJQ0VfQ09ORkxJQ1RfUkVBU09OX1VOU1BFQ0lGSUVEEAASLworUEVSX0RFVklDRV9DT05GTElDVF9SRUFTT05fREVWSUNFX05PVF9GT1VORBABEjsKN1BFUl9ERVZJQ0VfQ09ORkxJQ1RfUkVBU09OX0RFVklDRV9JTl9SQUNLX0FUX09USEVSX1NJVEUQAjKpBQoLU2l0ZVNlcnZpY2USRAoJTGlzdFNpdGVzEhouc2l0ZXMudjEuTGlzdFNpdGVzUmVxdWVzdBobLnNpdGVzLnYxLkxpc3RTaXRlc1Jlc3BvbnNlEkcKCkNyZWF0ZVNpdGUSGy5zaXRlcy52MS5DcmVhdGVTaXRlUmVxdWVzdBocLnNpdGVzLnYxLkNyZWF0ZVNpdGVSZXNwb25zZRJHCgpVcGRhdGVTaXRlEhsuc2l0ZXMudjEuVXBkYXRlU2l0ZVJlcXVlc3QaHC5zaXRlcy52MS5VcGRhdGVTaXRlUmVzcG9uc2USRwoKRGVsZXRlU2l0ZRIbLnNpdGVzLnYxLkRlbGV0ZVNpdGVSZXF1ZXN0Ghwuc2l0ZXMudjEuRGVsZXRlU2l0ZVJlc3BvbnNlEmIKE0Fzc2lnbkRldmljZXNUb1NpdGUSJC5zaXRlcy52MS5Bc3NpZ25EZXZpY2VzVG9TaXRlUmVxdWVzdBolLnNpdGVzLnYxLkFzc2lnbkRldmljZXNUb1NpdGVSZXNwb25zZRJoChVBc3NpZ25CdWlsZGluZ3NUb1NpdGUSJi5zaXRlcy52MS5Bc3NpZ25CdWlsZGluZ3NUb1NpdGVSZXF1ZXN0Gicuc2l0ZXMudjEuQXNzaWduQnVpbGRpbmdzVG9TaXRlUmVzcG9uc2USXAoRQXNzaWduUmFja3NUb1NpdGUSIi5zaXRlcy52MS5Bc3NpZ25SYWNrc1RvU2l0ZVJlcXVlc3QaIy5zaXRlcy52MS5Bc3NpZ25SYWNrc1RvU2l0ZVJlc3BvbnNlEk0KDEdldFNpdGVTdGF0cxIdLnNpdGVzLnYxLkdldFNpdGVTdGF0c1JlcXVlc3QaHi5zaXRlcy52MS5HZXRTaXRlU3RhdHNSZXNwb25zZUKgAQoMY29tLnNpdGVzLnYxQgpTaXRlc1Byb3RvUAFaQ2dpdGh1Yi5jb20vYmxvY2svcHJvdG8tZmxlZXQvc2VydmVyL2dlbmVyYXRlZC9ncnBjL3NpdGVzL3YxO3NpdGVzdjGiAgNTWFiqAghTaXRlcy5WMcoCCFNpdGVzXFYx4gIUU2l0ZXNcVjFcR1BCTWV0YWRhdGHqAglTaXRlczo6VjFiBnByb3RvMw", [file_buf_validate_validate, file_google_protobuf_timestamp], ); @@ -428,6 +428,23 @@ export type AssignDevicesToSiteRequest = Message<"sites.v1.AssignDevicesToSiteRe * @generated from field: repeated string device_identifiers = 2; */ deviceIdentifiers: string[]; + + /** + * When true, the server — inside the same transaction as the site + * write — removes any conflicting rack memberships for the listed + * devices before applying the site assignment. This closes the + * cross-site reparent orphan window the client-side + * remove-then-assign loop in MinerReparentPicker had: a transport + * failure between the per-rack unassign and the site reassign + * would leave miners rack-less but still pinned to the old site. + * + * When false (default), preserves today's behavior: any device + * currently in a rack at a different site rejects the whole batch + * with a PerDeviceConflict[] of DEVICE_IN_RACK_AT_OTHER_SITE. + * + * @generated from field: optional bool force_clear_conflicting_rack_membership = 3; + */ + forceClearConflictingRackMembership?: boolean | undefined; }; /** diff --git a/client/src/protoFleet/api/sites.ts b/client/src/protoFleet/api/sites.ts index adc7aef7c..fef95e25b 100644 --- a/client/src/protoFleet/api/sites.ts +++ b/client/src/protoFleet/api/sites.ts @@ -119,6 +119,12 @@ interface AssignDevicesToSiteProps { // always supplies a target so this is typically set in practice. targetSiteId?: bigint; deviceIdentifiers: string[]; + // When true, the server clears any conflicting rack memberships + // inside the same transaction as the site write. Lets cross-site + // reparent skip the client-side remove-from-rack loop and the + // orphan window it created. When false/unset the server returns + // DEVICE_IN_RACK_AT_OTHER_SITE conflicts (today's behavior). + forceClearConflictingRackMembership?: boolean; signal?: AbortSignal; onSuccess?: (reassignedCount: bigint) => void; // conflicts is populated when the server rejects the batch on @@ -147,6 +153,11 @@ interface AssignRacksToSiteProps { signal?: AbortSignal; // onSuccess args: device cascade count, count of racks whose // building was auto-cleared because the move crossed sites. + // TODO(issue-420 follow-up): consumers must surface clearedBuildingCount + // to the operator (toast or modal) — buildings belong to a single site, + // so crossing sites silently clears the rack's building. No UI consumer + // of this RPC exists yet; when one is wired, push a toast on + // clearedBuildingCount > 0 directing the operator to reassign. onSuccess?: (reassignedDeviceCount: bigint, clearedBuildingCount: bigint) => void; onError?: (message: string) => void; onFinally?: () => void; @@ -282,12 +293,21 @@ const useSites = () => { ); const assignDevicesToSite = useCallback( - async ({ targetSiteId, deviceIdentifiers, signal, onSuccess, onError, onFinally }: AssignDevicesToSiteProps) => { + async ({ + targetSiteId, + deviceIdentifiers, + forceClearConflictingRackMembership, + signal, + onSuccess, + onError, + onFinally, + }: AssignDevicesToSiteProps) => { try { const response = await sitesClient.assignDevicesToSite( { targetSiteId, deviceIdentifiers, + forceClearConflictingRackMembership, }, { signal }, ); diff --git a/client/src/protoFleet/api/useDeviceSets.ts b/client/src/protoFleet/api/useDeviceSets.ts index 905840651..3ab2924f0 100644 --- a/client/src/protoFleet/api/useDeviceSets.ts +++ b/client/src/protoFleet/api/useDeviceSets.ts @@ -63,11 +63,12 @@ interface ListDeviceSetsProps { onFinally?: () => void; } -interface AddDevicesToDeviceSetProps { - deviceSetId: bigint; +interface AddDevicesToGroupProps { + targetGroupId: bigint; deviceIdentifiers?: string[]; allDevices?: boolean; - onSuccess?: (addedCount: number) => void; + signal?: AbortSignal; + onSuccess?: (addedCount: bigint) => void; onError?: (message: string) => void; onFinally?: () => void; } @@ -119,11 +120,12 @@ interface ListGroupMembersProps { onFinally?: () => void; } -interface RemoveDevicesFromDeviceSetProps { - deviceSetId: bigint; +interface RemoveDevicesFromGroupProps { + targetGroupId: bigint; deviceIdentifiers?: string[]; allDevices?: boolean; - onSuccess?: (removedCount: number) => void; + signal?: AbortSignal; + onSuccess?: (removedCount: bigint) => void; onError?: (message: string) => void; onFinally?: () => void; } @@ -170,6 +172,7 @@ interface AssignDevicesToRackProps { // stay intact). targetRackId?: bigint; deviceIdentifiers: string[]; + signal?: AbortSignal; onSuccess?: (assignedCount: bigint, siteReassignedCount: bigint, removedCount: bigint) => void; onError?: (message: string) => void; onFinally?: () => void; @@ -497,28 +500,37 @@ const useDeviceSets = () => { [handleAuthErrors], ); - const addDevicesToDeviceSet = useCallback( + // addDevicesToGroup adds devices to a group (many-to-many). The + // server rejects non-group targets with InvalidArgument; for rack + // adds use assignDevicesToRack, which atomically clears any prior + // rack membership and cascades the rack's site onto the device. + const addDevicesToGroup = useCallback( async ({ - deviceSetId, + targetGroupId, deviceIdentifiers, allDevices, + signal, onSuccess, onError, onFinally, - }: AddDevicesToDeviceSetProps) => { + }: AddDevicesToGroupProps) => { try { - const deviceSelector = - allDevices || (deviceIdentifiers && deviceIdentifiers.length > 0) - ? buildDeviceSelector(deviceIdentifiers, allDevices) - : undefined; + const deviceSelector = buildDeviceSelector(deviceIdentifiers, allDevices); - const response = await deviceSetClient.addDevicesToDeviceSet({ - deviceSetId, - deviceSelector, - }); + const response = await deviceSetClient.addDevicesToGroup( + { + targetGroupId, + deviceSelector, + }, + { signal }, + ); + if (signal?.aborted) return; onSuccess?.(response.addedCount); } catch (err) { + if (isAbortError(err, signal)) { + return; + } handleAuthErrors({ error: err, onError: () => { @@ -617,14 +629,34 @@ const useDeviceSets = () => { // miners from rack assignment (issue #420). Pass targetRackId // unset to clear rack membership without re-assigning. const assignDevicesToRack = useCallback( - async ({ targetRackId, deviceIdentifiers, onSuccess, onError, onFinally }: AssignDevicesToRackProps) => { + async ({ targetRackId, deviceIdentifiers, signal, onSuccess, onError, onFinally }: AssignDevicesToRackProps) => { try { - const response = await deviceSetClient.assignDevicesToRack({ - targetRackId, - deviceIdentifiers, + // Server requires the device_list variant; the all_devices + // variant is rejected with InvalidArgument upstream. Always + // build the device_list selector explicitly so callers can't + // accidentally trigger that error by passing an empty array. + const deviceSelector = create(DeviceSelectorSchema, { + selectionType: { + case: "deviceList", + value: create(DeviceIdentifierListSchema, { + deviceIdentifiers, + }), + }, }); + + const response = await deviceSetClient.assignDevicesToRack( + { + targetRackId, + deviceSelector, + }, + { signal }, + ); + if (signal?.aborted) return; onSuccess?.(response.assignedCount, response.siteReassignedCount, response.removedCount); } catch (err) { + if (isAbortError(err, signal)) { + return; + } handleAuthErrors({ error: err, onError: () => { @@ -638,28 +670,37 @@ const useDeviceSets = () => { [handleAuthErrors], ); - const removeDevicesFromDeviceSet = useCallback( + // removeDevicesFromGroup drops devices from a group. The server + // rejects non-group targets with InvalidArgument; for rack removal + // use assignDevicesToRack with targetRackId unset, which clears rack + // membership in a single transaction (site/building stay intact). + const removeDevicesFromGroup = useCallback( async ({ - deviceSetId, + targetGroupId, deviceIdentifiers, allDevices, + signal, onSuccess, onError, onFinally, - }: RemoveDevicesFromDeviceSetProps) => { + }: RemoveDevicesFromGroupProps) => { try { - const deviceSelector = - allDevices || (deviceIdentifiers && deviceIdentifiers.length > 0) - ? buildDeviceSelector(deviceIdentifiers, allDevices) - : undefined; + const deviceSelector = buildDeviceSelector(deviceIdentifiers, allDevices); - const response = await deviceSetClient.removeDevicesFromDeviceSet({ - deviceSetId, - deviceSelector, - }); + const response = await deviceSetClient.removeDevicesFromGroup( + { + targetGroupId, + deviceSelector, + }, + { signal }, + ); + if (signal?.aborted) return; onSuccess?.(response.removedCount); } catch (err) { + if (isAbortError(err, signal)) { + return; + } handleAuthErrors({ error: err, onError: () => { @@ -884,9 +925,9 @@ const useDeviceSets = () => { listRackTypes, listGroupMembers, getDeviceSetStats, - addDevicesToDeviceSet, + addDevicesToGroup, assignDevicesToRack, - removeDevicesFromDeviceSet, + removeDevicesFromGroup, getRackSlots, setRackSlotPosition, clearRackSlotPosition, diff --git a/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx b/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx index babbcbe06..75611c856 100644 --- a/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx +++ b/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx @@ -80,6 +80,12 @@ const ManageBuildingModal = ({ // by rackId → "aisle:position" (or "unplaced") so we can string-compare. const initialPlacementRef = useRef>(new Map()); + // Synchronous in-flight guard for Save dispatches. setState batching + // means the `isSaving` prop driving the button's `disabled` lags one + // render behind the click — a double-click would otherwise reach the + // dispatch path twice. Mirrors useSiteModals' savingRef pattern. + const savingRef = useRef(false); + // (Re)load assignments when the modal opens. useEffect(() => { if (!open) return; @@ -331,33 +337,30 @@ const ManageBuildingModal = ({ ); // Save: walk activeAssignments, diff against the load-time snapshot, and - // fire AssignRacksToBuilding in two phases: - // - // Phase 1 (vacate): every rack whose placement is changing AND that was - // previously placed sends a "clear position" write first (same building, - // no aisle/position). Removed-from-building racks unassign in a - // separate call (different target_building_id). This frees every - // (building, aisle, position) tuple that will be re-used in phase 2 - // BEFORE any placement write runs. + // fire AssignRacksToBuilding once per target building bucket. // - // Phase 2 (place): every rack with a new placement target writes its - // final (aisle, position). Because phase 1 already cleared every cell - // that's about to be re-used, swaps and "move into occupied cell" cases - // no longer collide on the uk_device_set_rack_building_position partial - // unique index. + // All racks staying in this building (placements, unplacements, + // swaps, "move into occupied cell") ship as a single mixed batch. + // The server's AssignRacksToBuilding transaction now runs a two-pass + // write internally — pass 1 clears every requested rack's cell, then + // pass 2 writes the new (aisle, position) values — so the partial + // unique index uk_device_set_rack_building_position can't collide + // mid-batch. That removes the old client-side vacate-then-place + // split (and its "retry to finish saving" partial-failure path). // - // Each phase issues at most two bulk calls (one per target building_id - // bucket: this building, or unassigned). Layout writes live in - // BuildingSettingsModal. + // Racks removed from this building go in a second call with + // targetBuildingId=undefined since they need a different building + // bucket. Layout writes live in BuildingSettingsModal. const handleSave = useCallback(async () => { + if (savingRef.current) return; + savingRef.current = true; setErrorMsg(""); setIsSaving(true); try { const initial = initialPlacementRef.current; const currentIds = new Set(entries.map((e) => e.rackId.toString())); - const vacateInBuilding: RackPlacementInput[] = []; - const placeInBuilding: RackPlacementInput[] = []; + const inBuilding: RackPlacementInput[] = []; const unassign: RackPlacementInput[] = []; for (const entry of entries) { @@ -372,38 +375,34 @@ const ManageBuildingModal = ({ const prior = initial.get(idStr) ?? "missing"; if (prior === next) continue; - // If the rack was previously placed, vacate its old cell first. - // Sending the same building with no aisle/position clears position - // without touching membership. - if (prior !== "unplaced" && prior !== "missing") { - vacateInBuilding.push({ rackId: entry.rackId }); - } - - // Phase 2: write the new state. + // Single mixed batch. // - placedKey present → place at the new (aisle, position). + // Covers both first-time placement and moves; the server's + // pass-1 clear handles any prior occupant inside the batch. + // - placedKey absent + prior previously placed → send a + // member-only entry. The server NULLs the rack's cell in + // pass 1 (no pass-2 write because no position is supplied). // - placedKey absent + prior === "missing" → rack is new to // the working set with no chosen cell yet. Send a member- // only assign so the BE links the rack to this building // even without a position. Without this branch, racks // added via Manage racks but never dragged to a cell // silently drop on save. - // - placedKey absent + prior was placed → already handled - // by the phase-1 vacate above, which clears the cell. if (placedKey) { const { aisle, position } = parseCellKey(placedKey); - placeInBuilding.push({ + inBuilding.push({ rackId: entry.rackId, aisleIndex: aisle, positionInAisle: position, }); - } else if (prior === "missing") { - placeInBuilding.push({ rackId: entry.rackId }); + } else { + inBuilding.push({ rackId: entry.rackId }); } } - // Racks removed from this building (in snapshot, not in entries) need - // an explicit unassign. Land them in phase 1 so their cells free up - // before any phase-2 placement targets them. + // Racks removed from this building (in snapshot, not in entries) + // need an explicit unassign — different target building bucket so + // they can't ride the in-building batch. for (const idStr of initial.keys()) { if (currentIds.has(idStr)) continue; unassign.push({ rackId: BigInt(idStr) }); @@ -424,23 +423,10 @@ const ManageBuildingModal = ({ }); try { - await Promise.all([dispatch(vacateInBuilding, building.id), dispatch(unassign, undefined)]); - } catch (err) { - setErrorMsg( - err instanceof Error - ? `Failed to clear previous rack positions: ${err.message}. No new positions were written — retry to apply your changes.` - : "Failed to clear previous rack positions.", - ); - return; - } - - try { - await dispatch(placeInBuilding, building.id); + await Promise.all([dispatch(inBuilding, building.id), dispatch(unassign, undefined)]); } catch (err) { setErrorMsg( - err instanceof Error - ? `Failed to apply new rack positions: ${err.message}. Some cells may now be empty — retry to finish saving.` - : "Failed to apply new rack positions.", + err instanceof Error ? `Failed to save rack positions: ${err.message}.` : "Failed to save rack positions.", ); return; } @@ -449,6 +435,7 @@ const ManageBuildingModal = ({ onSaved?.(building); onDismiss(); } finally { + savingRef.current = false; setIsSaving(false); } }, [building, rackToCell, entries, assignRacksToBuilding, onSaved, onDismiss]); diff --git a/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerActionModalStack.tsx b/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerActionModalStack.tsx index f16bbaf3e..f603cbee8 100644 --- a/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerActionModalStack.tsx +++ b/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerActionModalStack.tsx @@ -31,7 +31,7 @@ const MinerActionModalStack = ({ displayCount, onActionBoundary, }: MinerActionModalStackProps) => { - const { addDevicesToDeviceSet, createGroup } = useDeviceSets(); + const { addDevicesToGroup, createGroup } = useDeviceSets(); const wrap = useCallback( (handler: (...args: Args) => void) => onActionBoundary @@ -54,8 +54,8 @@ const MinerActionModalStack = ({ const dispatchAddToGroup = useCallback( (groupId: bigint) => new Promise((resolve, reject) => { - void addDevicesToDeviceSet({ - deviceSetId: groupId, + void addDevicesToGroup({ + targetGroupId: groupId, deviceIdentifiers, allDevices, onSuccess: () => { @@ -71,7 +71,7 @@ const MinerActionModalStack = ({ }, }); }), - [addDevicesToDeviceSet, deviceIdentifiers, allDevices, sourceLabel], + [addDevicesToGroup, deviceIdentifiers, allDevices, sourceLabel], ); const handleAddToGroupConfirm = useCallback( diff --git a/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx b/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx index 57a374bb8..25ebc5bed 100644 --- a/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx +++ b/client/src/protoFleet/features/fleetManagement/components/MinerActionsMenu/MinerReparentPicker.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { create } from "@bufbuild/protobuf"; import { fleetManagementClient } from "@/protoFleet/api/clients"; @@ -62,17 +62,21 @@ const rackOverflowMessage = (rack: DeviceSet, currentMembers: Set, ids: // Paginate listMinerStateSnapshots filtered to the target rack so the // capacity guard can discount ids that are already members. Capped at // MAX_MINERS for the same reason as resolveAllModeIds. -const resolveRackMembers = async (rackId: bigint): Promise> => { +const resolveRackMembers = async (rackId: bigint, signal?: AbortSignal): Promise> => { const filter = create(MinerListFilterSchema, { rackIds: [rackId] }); const members = new Set(); let cursor = ""; let exhausted = false; for (let i = 0; i < MAX_SNAPSHOT_PAGES; i++) { - const response = await fleetManagementClient.listMinerStateSnapshots({ - pageSize: SNAPSHOT_PAGE_SIZE, - cursor, - filter, - }); + const response = await fleetManagementClient.listMinerStateSnapshots( + { + pageSize: SNAPSHOT_PAGE_SIZE, + cursor, + filter, + }, + { signal }, + ); + if (signal?.aborted) return members; for (const miner of response.miners) members.add(miner.deviceIdentifier); if (!response.cursor) { exhausted = true; @@ -116,17 +120,22 @@ const groupRackSiteConflicts = ( const resolveAllModeIds = async ( filter: MinerListFilter, + signal?: AbortSignal, ): Promise<{ ids: string[]; snapshots: Record }> => { const ids: string[] = []; const snapshots: Record = {}; let cursor = ""; let exhausted = false; for (let i = 0; i < MAX_SNAPSHOT_PAGES; i++) { - const response = await fleetManagementClient.listMinerStateSnapshots({ - pageSize: SNAPSHOT_PAGE_SIZE, - cursor, - filter, - }); + const response = await fleetManagementClient.listMinerStateSnapshots( + { + pageSize: SNAPSHOT_PAGE_SIZE, + cursor, + filter, + }, + { signal }, + ); + if (signal?.aborted) return { ids, snapshots }; for (const miner of response.miners) { ids.push(miner.deviceIdentifier); snapshots[miner.deviceIdentifier] = miner; @@ -163,7 +172,7 @@ const MinerReparentPicker = ({ onRefetchMiners, }: MinerReparentPickerProps) => { const { assignDevicesToSite } = useSites(); - const { assignDevicesToRack, getDeviceSet, listRacks, removeDevicesFromDeviceSet } = useDeviceSets(); + const { assignDevicesToRack, getDeviceSet, listRacks } = useDeviceSets(); const [siteMoveConfirmation, setSiteMoveConfirmation] = useState(null); const [siteMoveInFlight, setSiteMoveInFlight] = useState(false); // Resolver for the onConfirm promise during the cross-site confirm @@ -172,6 +181,19 @@ const MinerReparentPicker = ({ // → onClose) after the operator picks Continue or Cancel. const dialogResolveRef = useRef<(() => void) | null>(null); + // Abort in-flight snapshot pagination and bulk RPCs on unmount so a + // long-running resolveAllModeIds / resolveRackMembers / dispatch + // loop doesn't keep firing after the operator dismisses the picker. + const abortRef = useRef(null); + if (abortRef.current === null) { + abortRef.current = new AbortController(); + } + useEffect(() => { + return () => { + abortRef.current?.abort(); + }; + }, []); + const fetchAllRacks = () => new Promise((resolve, reject) => { void listRacks({ @@ -180,16 +202,6 @@ const MinerReparentPicker = ({ }); }); - const removeFromRack = (rackId: bigint, ids: string[]) => - new Promise((resolve, reject) => { - void removeDevicesFromDeviceSet({ - deviceSetId: rackId, - deviceIdentifiers: ids, - onSuccess: () => resolve(), - onError: (msg) => reject(new Error(msg)), - }); - }); - const fetchRack = (rackId: bigint) => new Promise((resolve, reject) => { void getDeviceSet({ @@ -200,40 +212,39 @@ const MinerReparentPicker = ({ }); }); - const dispatchSiteReassign = (targetSiteId: bigint, ids: string[]) => { - void assignDevicesToSite({ - targetSiteId, - deviceIdentifiers: ids, - onSuccess: (count) => { - pushToast({ message: successMessage(count, "site"), status: STATUSES.success }); - onRefetchMiners?.(); - }, - onError: (msg) => pushToast({ message: `Couldn't move miners: ${msg}`, status: STATUSES.error }), + const dispatchSiteReassign = (targetSiteId: bigint, ids: string[], forceClearConflictingRackMembership = false) => + new Promise((resolve) => { + void assignDevicesToSite({ + targetSiteId, + deviceIdentifiers: ids, + forceClearConflictingRackMembership, + onSuccess: (count) => { + pushToast({ message: successMessage(count, "site"), status: STATUSES.success }); + onRefetchMiners?.(); + resolve(); + }, + onError: (msg) => { + pushToast({ message: `Couldn't move miners: ${msg}`, status: STATUSES.error }); + resolve(); + }, + }); }); - }; + // Cross-site move with conflicting rack memberships: a single + // server-side transaction strips the prior rack rows and applies + // the site write. Previously this was a client-side loop of + // removeDevicesFromDeviceSet calls followed by assignDevicesToSite, + // which left a window where a transport failure between the two + // RPCs would orphan miners (rack-less but still on the old site). const dispatchSiteMoveWithUnassign = async (confirmation: SiteMoveConfirmation) => { setSiteMoveInFlight(true); const movingToast = pushToast({ - message: `Unassigning miners from ${confirmation.conflictsByLabel.size} rack${confirmation.conflictsByLabel.size === 1 ? "" : "s"}…`, + message: "Moving miners to the new site…", status: STATUSES.loading, longRunning: true, }); - try { - for (const [sourceLabel, sourceIds] of confirmation.conflictsByLabel) { - const sourceRackId = confirmation.labelToRackId.get(sourceLabel); - if (sourceRackId === undefined) continue; - await removeFromRack(sourceRackId, sourceIds); - } - } catch (err) { - const message = err instanceof Error ? err.message : "Failed to remove miners from current rack."; - updateToast(movingToast, { message, status: STATUSES.error }); - setSiteMoveInFlight(false); - setSiteMoveConfirmation(null); - return; - } + await dispatchSiteReassign(confirmation.targetSiteId, confirmation.ids, true); removeToast(movingToast); - dispatchSiteReassign(confirmation.targetSiteId, confirmation.ids); setSiteMoveInFlight(false); setSiteMoveConfirmation(null); }; @@ -272,7 +283,7 @@ const MinerReparentPicker = ({ const conflictsByLabel = groupRackSiteConflicts(ids, minerSnapshots, labelToSiteId, targetSiteId); if (conflictsByLabel.size === 0) { - dispatchSiteReassign(targetSiteId, ids); + await dispatchSiteReassign(targetSiteId, ids); return; } @@ -289,12 +300,16 @@ const MinerReparentPicker = ({ let rack: DeviceSet; let currentMembers: Set; try { - [rack, currentMembers] = await Promise.all([fetchRack(targetRackId), resolveRackMembers(targetRackId)]); + [rack, currentMembers] = await Promise.all([ + fetchRack(targetRackId), + resolveRackMembers(targetRackId, abortRef.current?.signal), + ]); } catch (err) { const message = err instanceof Error ? err.message : "Couldn't load rack."; pushToast({ message, status: STATUSES.error }); return; } + if (abortRef.current?.signal.aborted) return; const overflow = rackOverflowMessage(rack, currentMembers, ids); if (overflow) { pushToast({ message: overflow, status: STATUSES.error }); @@ -307,14 +322,21 @@ const MinerReparentPicker = ({ // racks via listRacks and could (a) orphan miners on a transport // failure between the two calls, and (b) skip a rack whose label // got renamed mid-confirm. The atomic RPC closes both holes. - void assignDevicesToRack({ - targetRackId, - deviceIdentifiers: ids, - onSuccess: (count) => { - pushToast({ message: successMessage(count, "rack"), status: STATUSES.success }); - onRefetchMiners?.(); - }, - onError: (msg) => pushToast({ message: `Couldn't move miners to rack: ${msg}`, status: STATUSES.error }), + await new Promise((resolve) => { + void assignDevicesToRack({ + targetRackId, + deviceIdentifiers: ids, + signal: abortRef.current?.signal, + onSuccess: (count) => { + pushToast({ message: successMessage(count, "rack"), status: STATUSES.success }); + onRefetchMiners?.(); + resolve(); + }, + onError: (msg) => { + pushToast({ message: `Couldn't move miners to rack: ${msg}`, status: STATUSES.error }); + resolve(); + }, + }); }); }; @@ -380,7 +402,11 @@ const MinerReparentPicker = ({ longRunning: true, }); try { - const resolved = await resolveAllModeIds(effectiveFilter); + const resolved = await resolveAllModeIds(effectiveFilter, abortRef.current?.signal); + if (abortRef.current?.signal.aborted) { + removeToast(loadingToast); + return; + } ids = resolved.ids; snapshots = resolved.snapshots; } catch (err) { diff --git a/client/src/protoFleet/features/fleetManagement/pages/RackOverviewPage.test.tsx b/client/src/protoFleet/features/fleetManagement/pages/RackOverviewPage.test.tsx index dafcc5edb..3356142a8 100644 --- a/client/src/protoFleet/features/fleetManagement/pages/RackOverviewPage.test.tsx +++ b/client/src/protoFleet/features/fleetManagement/pages/RackOverviewPage.test.tsx @@ -119,7 +119,7 @@ function mockResolvedRackPageData(deviceSet = rack): void { mockUseDeviceSets.mockReturnValue({ getDeviceSet: ({ onSuccess }: { onSuccess: (resolvedDeviceSet: typeof rack) => void }) => onSuccess(deviceSet), listGroupMembers: ({ onSuccess }: { onSuccess: (deviceIds: string[]) => void }) => onSuccess([]), - addDevicesToDeviceSet: vi.fn(), + assignDevicesToRack: vi.fn(), setRackSlotPosition: vi.fn(), deleteGroup: vi.fn(), }); diff --git a/client/src/protoFleet/features/fleetManagement/pages/RackOverviewPage.tsx b/client/src/protoFleet/features/fleetManagement/pages/RackOverviewPage.tsx index 3d8792807..e31c7f473 100644 --- a/client/src/protoFleet/features/fleetManagement/pages/RackOverviewPage.tsx +++ b/client/src/protoFleet/features/fleetManagement/pages/RackOverviewPage.tsx @@ -59,7 +59,7 @@ const RackOverviewPage = () => { const sleepActionRef = useRef<(() => void) | null>(null); const actionActiveRef = useRef(false); - const { getDeviceSet, listGroupMembers, addDevicesToDeviceSet, setRackSlotPosition, deleteGroup } = useDeviceSets(); + const { getDeviceSet, listGroupMembers, assignDevicesToRack, setRackSlotPosition, deleteGroup } = useDeviceSets(); // Request versioning to guard against stale resolution callbacks const resolveVersionRef = useRef(0); @@ -449,11 +449,14 @@ const RackOverviewPage = () => { const slot = searchMinerSlot; setSearchMinerSlot(null); - // Two-step: add miner to rack membership, then assign to slot. - // No single API supports both atomically without resending the full rack state. - // On partial success (added but slot failed), we still refresh so the UI stays consistent. - addDevicesToDeviceSet({ - deviceSetId: rack.id, + // Two-step: atomically move the miner into this rack + // (clearing any prior rack membership in one tx), then + // assign the slot. No single API supports both atomically + // without resending the full rack state. On partial + // success (assigned but slot failed), we still refresh so + // the UI stays consistent. + assignDevicesToRack({ + targetRackId: rack.id, deviceIdentifiers: [minerId], onSuccess: () => { setRackSlotPosition({ diff --git a/client/src/protoFleet/features/sites/hooks/useSiteModals.ts b/client/src/protoFleet/features/sites/hooks/useSiteModals.ts index 842ca93ac..3d21b1e93 100644 --- a/client/src/protoFleet/features/sites/hooks/useSiteModals.ts +++ b/client/src/protoFleet/features/sites/hooks/useSiteModals.ts @@ -144,8 +144,19 @@ const useSiteModals = ({ refetchSites }: UseSiteModalsOptions): SiteModalsApi => const detailsSaveEdit = useCallback( async (values: SiteFormValues) => { if (savingRef.current) return; - if (state.kind !== "manageEditEditingDetails") return; - const id = state.site.id; + // Functional setState reads the current state synchronously so a + // mid-flight dismiss (state transition back to manageEdit or none) + // can't drive a save against a stale captured `state` value. + // Matches the onSuccess setState pattern below. + let resolvedId: bigint | null = null; + setState((prev) => { + if (prev.kind === "manageEditEditingDetails") { + resolvedId = prev.site.id; + } + return prev; + }); + if (resolvedId === null) return; + const id: bigint = resolvedId; savingRef.current = true; setSaving(true); await new Promise((resolve) => { @@ -180,7 +191,7 @@ const useSiteModals = ({ refetchSites }: UseSiteModalsOptions): SiteModalsApi => }); }); }, - [state, updateSite, refetchSites], + [updateSite, refetchSites], ); const manageEditDetails = useCallback(() => { diff --git a/proto/collection/v1/collection.proto b/proto/collection/v1/collection.proto index d9973e002..f2fdca2d0 100644 --- a/proto/collection/v1/collection.proto +++ b/proto/collection/v1/collection.proto @@ -27,12 +27,6 @@ service DeviceCollectionService { // Lists all collections for the organization rpc ListCollections(ListCollectionsRequest) returns (ListCollectionsResponse); - // Adds devices to a collection - rpc AddDevicesToCollection(AddDevicesToCollectionRequest) returns (AddDevicesToCollectionResponse); - - // Removes devices from a collection - rpc RemoveDevicesFromCollection(RemoveDevicesFromCollectionRequest) returns (RemoveDevicesFromCollectionResponse); - // Lists members of a collection rpc ListCollectionMembers(ListCollectionMembersRequest) returns (ListCollectionMembersResponse); @@ -308,47 +302,6 @@ message ListCollectionsResponse { int32 total_count = 3; } -// Request to add devices to a collection. -// -// When the target is a site-stamped rack, the server rewrites -// device.site_id to match the rack for every added device. The affected -// count is returned in site_reassigned_count. Groups are exempt. -message AddDevicesToCollectionRequest { - // ID of the collection to add devices to - int64 collection_id = 1 [(buf.validate.field).int64.gt = 0]; - - // Devices to add: specific list or all paired devices - common.v1.DeviceSelector device_selector = 2 [(buf.validate.field).required = true]; -} - -// Response after adding devices to a collection -message AddDevicesToCollectionResponse { - // ID of the collection devices were added to - int64 collection_id = 1; - - // Number of devices successfully added - // May be less than requested if some devices were already members - int32 added_count = 2; - - // Number of devices whose site_id was rewritten by the cascade. - int32 site_reassigned_count = 3; -} - -// Request to remove devices from a collection -message RemoveDevicesFromCollectionRequest { - // ID of the collection to remove devices from - int64 collection_id = 1 [(buf.validate.field).int64.gt = 0]; - - // Devices to remove: specific list or all paired devices - common.v1.DeviceSelector device_selector = 2 [(buf.validate.field).required = true]; -} - -// Response after removing devices from a collection -message RemoveDevicesFromCollectionResponse { - // Number of devices successfully removed - int32 removed_count = 1; -} - // Request to list members of a collection message ListCollectionMembersRequest { // ID of the collection to list members for diff --git a/proto/device_set/v1/device_set.proto b/proto/device_set/v1/device_set.proto index 29d594829..e6df2d96d 100644 --- a/proto/device_set/v1/device_set.proto +++ b/proto/device_set/v1/device_set.proto @@ -27,11 +27,14 @@ service DeviceSetService { // Lists all device sets for the organization rpc ListDeviceSets(ListDeviceSetsRequest) returns (ListDeviceSetsResponse); - // Adds devices to a device set - rpc AddDevicesToDeviceSet(AddDevicesToDeviceSetRequest) returns (AddDevicesToDeviceSetResponse); + // Adds devices to a group (many-to-many). Rejects non-group targets + // with InvalidArgument; use AssignDevicesToRack for racks. + rpc AddDevicesToGroup(AddDevicesToGroupRequest) returns (AddDevicesToGroupResponse); - // Removes devices from a device set - rpc RemoveDevicesFromDeviceSet(RemoveDevicesFromDeviceSetRequest) returns (RemoveDevicesFromDeviceSetResponse); + // Removes devices from a group. Rejects non-group targets with + // InvalidArgument; rack membership is cleared via + // AssignDevicesToRack with target_rack_id unset. + rpc RemoveDevicesFromGroup(RemoveDevicesFromGroupRequest) returns (RemoveDevicesFromGroupResponse); // Lists members of a device set rpc ListDeviceSetMembers(ListDeviceSetMembersRequest) returns (ListDeviceSetMembersResponse); @@ -76,11 +79,15 @@ service DeviceSetService { // device's current site. target_rack_id unset clears rack // membership without re-assigning (site/building stay intact). // - // Closes the orphan window where a client-side - // RemoveDevicesFromDeviceSet + AddDevicesToDeviceSet pair could - // leave devices without rack membership on transport failure between - // the two calls, and the cross-rack rename race where the source - // rack's id resolution drifts between the resolve and the remove. + // Closes the orphan window where a client-side remove + add pair + // could leave devices without rack membership on transport failure + // between the two calls, and the cross-rack rename race where the + // source rack's id resolution drifts between the resolve and the + // remove. + // + // device_selector accepts only the device_list variant. all_devices + // is rejected with InvalidArgument because moving every paired + // device into a single rack is never the intended operation. rpc AssignDevicesToRack(AssignDevicesToRackRequest) returns (AssignDevicesToRackResponse); } @@ -356,45 +363,42 @@ message ListDeviceSetsResponse { int32 total_count = 3; } -// Request to add devices to a device set. -// -// When the target is a site-stamped rack, the server rewrites -// device.site_id to match the rack for every added device. The affected -// count is returned in site_reassigned_count. Groups are exempt. -message AddDevicesToDeviceSetRequest { - // ID of the device set to add devices to - int64 device_set_id = 1 [(buf.validate.field).int64.gt = 0]; +// Request to add devices to a group. Groups have many-to-many device +// membership: devices may already belong to other groups and to a rack, +// none of which are touched. Server rejects with InvalidArgument when +// target_group_id refers to a non-group device set. +message AddDevicesToGroupRequest { + // ID of the group to add devices to. Must reference a device set of + // type GROUP; racks are rejected. + int64 target_group_id = 1 [(buf.validate.field).int64.gt = 0]; - // Devices to add: specific list or all paired devices + // Devices to add: specific list or all paired devices. common.v1.DeviceSelector device_selector = 2 [(buf.validate.field).required = true]; } -// Response after adding devices to a device set -message AddDevicesToDeviceSetResponse { - // ID of the device set devices were added to - int64 device_set_id = 1; - - // Number of devices successfully added - // May be less than requested if some devices were already members - int32 added_count = 2; - - // Number of devices whose site_id was rewritten by the cascade. - int32 site_reassigned_count = 3; +// Response after adding devices to a group. +message AddDevicesToGroupResponse { + // Number of devices successfully added. May be less than requested + // if some devices were already members. + int64 added_count = 1; } -// Request to remove devices from a device set -message RemoveDevicesFromDeviceSetRequest { - // ID of the device set to remove devices from - int64 device_set_id = 1 [(buf.validate.field).int64.gt = 0]; +// Request to remove devices from a group. Server rejects with +// InvalidArgument when target_group_id refers to a non-group device set; +// use AssignDevicesToRack to clear rack membership. +message RemoveDevicesFromGroupRequest { + // ID of the group to remove devices from. Must reference a device + // set of type GROUP; racks are rejected. + int64 target_group_id = 1 [(buf.validate.field).int64.gt = 0]; - // Devices to remove: specific list or all paired devices + // Devices to remove: specific list or all paired devices. common.v1.DeviceSelector device_selector = 2 [(buf.validate.field).required = true]; } -// Response after removing devices from a device set -message RemoveDevicesFromDeviceSetResponse { - // Number of devices successfully removed - int32 removed_count = 1; +// Response after removing devices from a group. +message RemoveDevicesFromGroupResponse { + // Number of devices successfully removed. + int64 removed_count = 1; } // Request to list members of a device set @@ -650,11 +654,13 @@ message AssignDevicesToRackRequest { // Unset = clear rack membership without re-assigning. When // present, must be > 0. optional int64 target_rack_id = 1 [(buf.validate.field).int64.gt = 0]; - repeated string device_identifiers = 2 [(buf.validate.field).repeated = { - min_items: 1 - max_items: 10000 - items: {string: {min_len: 1, max_len: 256}} - }]; + + // Devices to assign. Only the device_list variant is accepted; the + // all_devices variant is rejected with InvalidArgument because + // moving every paired device into a single rack is never the + // intended operation. Filter-based selectors are reserved for a + // future expansion. + common.v1.DeviceSelector device_selector = 2 [(buf.validate.field).required = true]; } message AssignDevicesToRackResponse { diff --git a/proto/sites/v1/sites.proto b/proto/sites/v1/sites.proto index 38f2e8313..3d5871133 100644 --- a/proto/sites/v1/sites.proto +++ b/proto/sites/v1/sites.proto @@ -204,6 +204,18 @@ message AssignDevicesToSiteRequest { max_items: 10000 items: {string: {min_len: 1, max_len: 256}} }]; + // When true, the server — inside the same transaction as the site + // write — removes any conflicting rack memberships for the listed + // devices before applying the site assignment. This closes the + // cross-site reparent orphan window the client-side + // remove-then-assign loop in MinerReparentPicker had: a transport + // failure between the per-rack unassign and the site reassign + // would leave miners rack-less but still pinned to the old site. + // + // When false (default), preserves today's behavior: any device + // currently in a rack at a different site rejects the whole batch + // with a PerDeviceConflict[] of DEVICE_IN_RACK_AT_OTHER_SITE. + optional bool force_clear_conflicting_rack_membership = 3; } // PerDeviceConflictReason enumerates the reasons a device can be diff --git a/server/cmd/fleetd/main.go b/server/cmd/fleetd/main.go index 80ee5da87..8cbbf0bd2 100644 --- a/server/cmd/fleetd/main.go +++ b/server/cmd/fleetd/main.go @@ -158,6 +158,7 @@ var reflectEnabledServices = []string{ sitesv1connect.SiteServiceName, buildingsv1connect.BuildingServiceName, curtailmentv1connect.CurtailmentServiceName, + device_setv1connect.DeviceSetServiceName, } func start(config *Config) error { diff --git a/server/generated/grpc/collection/v1/collection.pb.go b/server/generated/grpc/collection/v1/collection.pb.go index 7a3fad121..506a9b068 100644 --- a/server/generated/grpc/collection/v1/collection.pb.go +++ b/server/generated/grpc/collection/v1/collection.pb.go @@ -1406,231 +1406,6 @@ func (x *ListCollectionsResponse) GetTotalCount() int32 { return 0 } -// Request to add devices to a collection. -// -// When the target is a site-stamped rack, the server rewrites -// device.site_id to match the rack for every added device. The affected -// count is returned in site_reassigned_count. Groups are exempt. -type AddDevicesToCollectionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // ID of the collection to add devices to - CollectionId int64 `protobuf:"varint,1,opt,name=collection_id,json=collectionId,proto3" json:"collection_id,omitempty"` - // Devices to add: specific list or all paired devices - DeviceSelector *v1.DeviceSelector `protobuf:"bytes,2,opt,name=device_selector,json=deviceSelector,proto3" json:"device_selector,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AddDevicesToCollectionRequest) Reset() { - *x = AddDevicesToCollectionRequest{} - mi := &file_collection_v1_collection_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AddDevicesToCollectionRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AddDevicesToCollectionRequest) ProtoMessage() {} - -func (x *AddDevicesToCollectionRequest) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[16] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AddDevicesToCollectionRequest.ProtoReflect.Descriptor instead. -func (*AddDevicesToCollectionRequest) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{16} -} - -func (x *AddDevicesToCollectionRequest) GetCollectionId() int64 { - if x != nil { - return x.CollectionId - } - return 0 -} - -func (x *AddDevicesToCollectionRequest) GetDeviceSelector() *v1.DeviceSelector { - if x != nil { - return x.DeviceSelector - } - return nil -} - -// Response after adding devices to a collection -type AddDevicesToCollectionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // ID of the collection devices were added to - CollectionId int64 `protobuf:"varint,1,opt,name=collection_id,json=collectionId,proto3" json:"collection_id,omitempty"` - // Number of devices successfully added - // May be less than requested if some devices were already members - AddedCount int32 `protobuf:"varint,2,opt,name=added_count,json=addedCount,proto3" json:"added_count,omitempty"` - // Number of devices whose site_id was rewritten by the cascade. - SiteReassignedCount int32 `protobuf:"varint,3,opt,name=site_reassigned_count,json=siteReassignedCount,proto3" json:"site_reassigned_count,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AddDevicesToCollectionResponse) Reset() { - *x = AddDevicesToCollectionResponse{} - mi := &file_collection_v1_collection_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AddDevicesToCollectionResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AddDevicesToCollectionResponse) ProtoMessage() {} - -func (x *AddDevicesToCollectionResponse) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[17] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AddDevicesToCollectionResponse.ProtoReflect.Descriptor instead. -func (*AddDevicesToCollectionResponse) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{17} -} - -func (x *AddDevicesToCollectionResponse) GetCollectionId() int64 { - if x != nil { - return x.CollectionId - } - return 0 -} - -func (x *AddDevicesToCollectionResponse) GetAddedCount() int32 { - if x != nil { - return x.AddedCount - } - return 0 -} - -func (x *AddDevicesToCollectionResponse) GetSiteReassignedCount() int32 { - if x != nil { - return x.SiteReassignedCount - } - return 0 -} - -// Request to remove devices from a collection -type RemoveDevicesFromCollectionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // ID of the collection to remove devices from - CollectionId int64 `protobuf:"varint,1,opt,name=collection_id,json=collectionId,proto3" json:"collection_id,omitempty"` - // Devices to remove: specific list or all paired devices - DeviceSelector *v1.DeviceSelector `protobuf:"bytes,2,opt,name=device_selector,json=deviceSelector,proto3" json:"device_selector,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *RemoveDevicesFromCollectionRequest) Reset() { - *x = RemoveDevicesFromCollectionRequest{} - mi := &file_collection_v1_collection_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *RemoveDevicesFromCollectionRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemoveDevicesFromCollectionRequest) ProtoMessage() {} - -func (x *RemoveDevicesFromCollectionRequest) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[18] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemoveDevicesFromCollectionRequest.ProtoReflect.Descriptor instead. -func (*RemoveDevicesFromCollectionRequest) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{18} -} - -func (x *RemoveDevicesFromCollectionRequest) GetCollectionId() int64 { - if x != nil { - return x.CollectionId - } - return 0 -} - -func (x *RemoveDevicesFromCollectionRequest) GetDeviceSelector() *v1.DeviceSelector { - if x != nil { - return x.DeviceSelector - } - return nil -} - -// Response after removing devices from a collection -type RemoveDevicesFromCollectionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Number of devices successfully removed - RemovedCount int32 `protobuf:"varint,1,opt,name=removed_count,json=removedCount,proto3" json:"removed_count,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *RemoveDevicesFromCollectionResponse) Reset() { - *x = RemoveDevicesFromCollectionResponse{} - mi := &file_collection_v1_collection_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *RemoveDevicesFromCollectionResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemoveDevicesFromCollectionResponse) ProtoMessage() {} - -func (x *RemoveDevicesFromCollectionResponse) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[19] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemoveDevicesFromCollectionResponse.ProtoReflect.Descriptor instead. -func (*RemoveDevicesFromCollectionResponse) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{19} -} - -func (x *RemoveDevicesFromCollectionResponse) GetRemovedCount() int32 { - if x != nil { - return x.RemovedCount - } - return 0 -} - // Request to list members of a collection type ListCollectionMembersRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1646,7 +1421,7 @@ type ListCollectionMembersRequest struct { func (x *ListCollectionMembersRequest) Reset() { *x = ListCollectionMembersRequest{} - mi := &file_collection_v1_collection_proto_msgTypes[20] + mi := &file_collection_v1_collection_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1658,7 +1433,7 @@ func (x *ListCollectionMembersRequest) String() string { func (*ListCollectionMembersRequest) ProtoMessage() {} func (x *ListCollectionMembersRequest) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[20] + mi := &file_collection_v1_collection_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1671,7 +1446,7 @@ func (x *ListCollectionMembersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCollectionMembersRequest.ProtoReflect.Descriptor instead. func (*ListCollectionMembersRequest) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{20} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{16} } func (x *ListCollectionMembersRequest) GetCollectionId() int64 { @@ -1708,7 +1483,7 @@ type ListCollectionMembersResponse struct { func (x *ListCollectionMembersResponse) Reset() { *x = ListCollectionMembersResponse{} - mi := &file_collection_v1_collection_proto_msgTypes[21] + mi := &file_collection_v1_collection_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1720,7 +1495,7 @@ func (x *ListCollectionMembersResponse) String() string { func (*ListCollectionMembersResponse) ProtoMessage() {} func (x *ListCollectionMembersResponse) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[21] + mi := &file_collection_v1_collection_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1733,7 +1508,7 @@ func (x *ListCollectionMembersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCollectionMembersResponse.ProtoReflect.Descriptor instead. func (*ListCollectionMembersResponse) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{21} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{17} } func (x *ListCollectionMembersResponse) GetMembers() []*CollectionMember { @@ -1763,7 +1538,7 @@ type GetDeviceCollectionsRequest struct { func (x *GetDeviceCollectionsRequest) Reset() { *x = GetDeviceCollectionsRequest{} - mi := &file_collection_v1_collection_proto_msgTypes[22] + mi := &file_collection_v1_collection_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1775,7 +1550,7 @@ func (x *GetDeviceCollectionsRequest) String() string { func (*GetDeviceCollectionsRequest) ProtoMessage() {} func (x *GetDeviceCollectionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[22] + mi := &file_collection_v1_collection_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1788,7 +1563,7 @@ func (x *GetDeviceCollectionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDeviceCollectionsRequest.ProtoReflect.Descriptor instead. func (*GetDeviceCollectionsRequest) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{22} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{18} } func (x *GetDeviceCollectionsRequest) GetDeviceIdentifier() string { @@ -1816,7 +1591,7 @@ type GetDeviceCollectionsResponse struct { func (x *GetDeviceCollectionsResponse) Reset() { *x = GetDeviceCollectionsResponse{} - mi := &file_collection_v1_collection_proto_msgTypes[23] + mi := &file_collection_v1_collection_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1828,7 +1603,7 @@ func (x *GetDeviceCollectionsResponse) String() string { func (*GetDeviceCollectionsResponse) ProtoMessage() {} func (x *GetDeviceCollectionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[23] + mi := &file_collection_v1_collection_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1841,7 +1616,7 @@ func (x *GetDeviceCollectionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDeviceCollectionsResponse.ProtoReflect.Descriptor instead. func (*GetDeviceCollectionsResponse) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{23} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{19} } func (x *GetDeviceCollectionsResponse) GetCollections() []*DeviceCollection { @@ -1866,7 +1641,7 @@ type SetRackSlotPositionRequest struct { func (x *SetRackSlotPositionRequest) Reset() { *x = SetRackSlotPositionRequest{} - mi := &file_collection_v1_collection_proto_msgTypes[24] + mi := &file_collection_v1_collection_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1878,7 +1653,7 @@ func (x *SetRackSlotPositionRequest) String() string { func (*SetRackSlotPositionRequest) ProtoMessage() {} func (x *SetRackSlotPositionRequest) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[24] + mi := &file_collection_v1_collection_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1891,7 +1666,7 @@ func (x *SetRackSlotPositionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRackSlotPositionRequest.ProtoReflect.Descriptor instead. func (*SetRackSlotPositionRequest) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{24} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{20} } func (x *SetRackSlotPositionRequest) GetCollectionId() int64 { @@ -1928,7 +1703,7 @@ type SetRackSlotPositionResponse struct { func (x *SetRackSlotPositionResponse) Reset() { *x = SetRackSlotPositionResponse{} - mi := &file_collection_v1_collection_proto_msgTypes[25] + mi := &file_collection_v1_collection_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1940,7 +1715,7 @@ func (x *SetRackSlotPositionResponse) String() string { func (*SetRackSlotPositionResponse) ProtoMessage() {} func (x *SetRackSlotPositionResponse) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[25] + mi := &file_collection_v1_collection_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1953,7 +1728,7 @@ func (x *SetRackSlotPositionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRackSlotPositionResponse.ProtoReflect.Descriptor instead. func (*SetRackSlotPositionResponse) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{25} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{21} } func (x *SetRackSlotPositionResponse) GetCollectionId() int64 { @@ -1983,7 +1758,7 @@ type ClearRackSlotPositionRequest struct { func (x *ClearRackSlotPositionRequest) Reset() { *x = ClearRackSlotPositionRequest{} - mi := &file_collection_v1_collection_proto_msgTypes[26] + mi := &file_collection_v1_collection_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1995,7 +1770,7 @@ func (x *ClearRackSlotPositionRequest) String() string { func (*ClearRackSlotPositionRequest) ProtoMessage() {} func (x *ClearRackSlotPositionRequest) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[26] + mi := &file_collection_v1_collection_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2008,7 +1783,7 @@ func (x *ClearRackSlotPositionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearRackSlotPositionRequest.ProtoReflect.Descriptor instead. func (*ClearRackSlotPositionRequest) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{26} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{22} } func (x *ClearRackSlotPositionRequest) GetCollectionId() int64 { @@ -2034,7 +1809,7 @@ type ClearRackSlotPositionResponse struct { func (x *ClearRackSlotPositionResponse) Reset() { *x = ClearRackSlotPositionResponse{} - mi := &file_collection_v1_collection_proto_msgTypes[27] + mi := &file_collection_v1_collection_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2046,7 +1821,7 @@ func (x *ClearRackSlotPositionResponse) String() string { func (*ClearRackSlotPositionResponse) ProtoMessage() {} func (x *ClearRackSlotPositionResponse) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[27] + mi := &file_collection_v1_collection_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2059,7 +1834,7 @@ func (x *ClearRackSlotPositionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearRackSlotPositionResponse.ProtoReflect.Descriptor instead. func (*ClearRackSlotPositionResponse) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{27} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{23} } // Request to list all occupied slots in a rack @@ -2073,7 +1848,7 @@ type GetRackSlotsRequest struct { func (x *GetRackSlotsRequest) Reset() { *x = GetRackSlotsRequest{} - mi := &file_collection_v1_collection_proto_msgTypes[28] + mi := &file_collection_v1_collection_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2085,7 +1860,7 @@ func (x *GetRackSlotsRequest) String() string { func (*GetRackSlotsRequest) ProtoMessage() {} func (x *GetRackSlotsRequest) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[28] + mi := &file_collection_v1_collection_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2098,7 +1873,7 @@ func (x *GetRackSlotsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRackSlotsRequest.ProtoReflect.Descriptor instead. func (*GetRackSlotsRequest) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{28} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{24} } func (x *GetRackSlotsRequest) GetCollectionId() int64 { @@ -2121,7 +1896,7 @@ type RackSlot struct { func (x *RackSlot) Reset() { *x = RackSlot{} - mi := &file_collection_v1_collection_proto_msgTypes[29] + mi := &file_collection_v1_collection_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2133,7 +1908,7 @@ func (x *RackSlot) String() string { func (*RackSlot) ProtoMessage() {} func (x *RackSlot) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[29] + mi := &file_collection_v1_collection_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2146,7 +1921,7 @@ func (x *RackSlot) ProtoReflect() protoreflect.Message { // Deprecated: Use RackSlot.ProtoReflect.Descriptor instead. func (*RackSlot) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{29} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{25} } func (x *RackSlot) GetDeviceIdentifier() string { @@ -2174,7 +1949,7 @@ type GetRackSlotsResponse struct { func (x *GetRackSlotsResponse) Reset() { *x = GetRackSlotsResponse{} - mi := &file_collection_v1_collection_proto_msgTypes[30] + mi := &file_collection_v1_collection_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2186,7 +1961,7 @@ func (x *GetRackSlotsResponse) String() string { func (*GetRackSlotsResponse) ProtoMessage() {} func (x *GetRackSlotsResponse) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[30] + mi := &file_collection_v1_collection_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2199,7 +1974,7 @@ func (x *GetRackSlotsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRackSlotsResponse.ProtoReflect.Descriptor instead. func (*GetRackSlotsResponse) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{30} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{26} } func (x *GetRackSlotsResponse) GetSlots() []*RackSlot { @@ -2248,7 +2023,7 @@ type CollectionStats struct { func (x *CollectionStats) Reset() { *x = CollectionStats{} - mi := &file_collection_v1_collection_proto_msgTypes[31] + mi := &file_collection_v1_collection_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2260,7 +2035,7 @@ func (x *CollectionStats) String() string { func (*CollectionStats) ProtoMessage() {} func (x *CollectionStats) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[31] + mi := &file_collection_v1_collection_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2273,7 +2048,7 @@ func (x *CollectionStats) ProtoReflect() protoreflect.Message { // Deprecated: Use CollectionStats.ProtoReflect.Descriptor instead. func (*CollectionStats) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{31} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{27} } func (x *CollectionStats) GetCollectionId() int64 { @@ -2434,7 +2209,7 @@ type GetCollectionStatsRequest struct { func (x *GetCollectionStatsRequest) Reset() { *x = GetCollectionStatsRequest{} - mi := &file_collection_v1_collection_proto_msgTypes[32] + mi := &file_collection_v1_collection_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2446,7 +2221,7 @@ func (x *GetCollectionStatsRequest) String() string { func (*GetCollectionStatsRequest) ProtoMessage() {} func (x *GetCollectionStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[32] + mi := &file_collection_v1_collection_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2459,7 +2234,7 @@ func (x *GetCollectionStatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCollectionStatsRequest.ProtoReflect.Descriptor instead. func (*GetCollectionStatsRequest) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{32} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{28} } func (x *GetCollectionStatsRequest) GetCollectionIds() []int64 { @@ -2480,7 +2255,7 @@ type GetCollectionStatsResponse struct { func (x *GetCollectionStatsResponse) Reset() { *x = GetCollectionStatsResponse{} - mi := &file_collection_v1_collection_proto_msgTypes[33] + mi := &file_collection_v1_collection_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2492,7 +2267,7 @@ func (x *GetCollectionStatsResponse) String() string { func (*GetCollectionStatsResponse) ProtoMessage() {} func (x *GetCollectionStatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[33] + mi := &file_collection_v1_collection_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2505,7 +2280,7 @@ func (x *GetCollectionStatsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCollectionStatsResponse.ProtoReflect.Descriptor instead. func (*GetCollectionStatsResponse) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{33} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{29} } func (x *GetCollectionStatsResponse) GetStats() []*CollectionStats { @@ -2530,7 +2305,7 @@ type RackSlotStatus struct { func (x *RackSlotStatus) Reset() { *x = RackSlotStatus{} - mi := &file_collection_v1_collection_proto_msgTypes[34] + mi := &file_collection_v1_collection_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2542,7 +2317,7 @@ func (x *RackSlotStatus) String() string { func (*RackSlotStatus) ProtoMessage() {} func (x *RackSlotStatus) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[34] + mi := &file_collection_v1_collection_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2555,7 +2330,7 @@ func (x *RackSlotStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use RackSlotStatus.ProtoReflect.Descriptor instead. func (*RackSlotStatus) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{34} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{30} } func (x *RackSlotStatus) GetRow() int32 { @@ -2588,7 +2363,7 @@ type ListRackZonesRequest struct { func (x *ListRackZonesRequest) Reset() { *x = ListRackZonesRequest{} - mi := &file_collection_v1_collection_proto_msgTypes[35] + mi := &file_collection_v1_collection_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2600,7 +2375,7 @@ func (x *ListRackZonesRequest) String() string { func (*ListRackZonesRequest) ProtoMessage() {} func (x *ListRackZonesRequest) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[35] + mi := &file_collection_v1_collection_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2613,7 +2388,7 @@ func (x *ListRackZonesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRackZonesRequest.ProtoReflect.Descriptor instead. func (*ListRackZonesRequest) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{35} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{31} } // Response containing all distinct rack zones @@ -2627,7 +2402,7 @@ type ListRackZonesResponse struct { func (x *ListRackZonesResponse) Reset() { *x = ListRackZonesResponse{} - mi := &file_collection_v1_collection_proto_msgTypes[36] + mi := &file_collection_v1_collection_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2639,7 +2414,7 @@ func (x *ListRackZonesResponse) String() string { func (*ListRackZonesResponse) ProtoMessage() {} func (x *ListRackZonesResponse) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[36] + mi := &file_collection_v1_collection_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2652,7 +2427,7 @@ func (x *ListRackZonesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRackZonesResponse.ProtoReflect.Descriptor instead. func (*ListRackZonesResponse) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{36} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{32} } func (x *ListRackZonesResponse) GetZones() []string { @@ -2671,7 +2446,7 @@ type ListRackTypesRequest struct { func (x *ListRackTypesRequest) Reset() { *x = ListRackTypesRequest{} - mi := &file_collection_v1_collection_proto_msgTypes[37] + mi := &file_collection_v1_collection_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2683,7 +2458,7 @@ func (x *ListRackTypesRequest) String() string { func (*ListRackTypesRequest) ProtoMessage() {} func (x *ListRackTypesRequest) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[37] + mi := &file_collection_v1_collection_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2696,7 +2471,7 @@ func (x *ListRackTypesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRackTypesRequest.ProtoReflect.Descriptor instead. func (*ListRackTypesRequest) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{37} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{33} } // A rack type defined by its row/column dimensions and how many racks use it @@ -2714,7 +2489,7 @@ type RackType struct { func (x *RackType) Reset() { *x = RackType{} - mi := &file_collection_v1_collection_proto_msgTypes[38] + mi := &file_collection_v1_collection_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2726,7 +2501,7 @@ func (x *RackType) String() string { func (*RackType) ProtoMessage() {} func (x *RackType) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[38] + mi := &file_collection_v1_collection_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2739,7 +2514,7 @@ func (x *RackType) ProtoReflect() protoreflect.Message { // Deprecated: Use RackType.ProtoReflect.Descriptor instead. func (*RackType) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{38} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{34} } func (x *RackType) GetRows() int32 { @@ -2774,7 +2549,7 @@ type ListRackTypesResponse struct { func (x *ListRackTypesResponse) Reset() { *x = ListRackTypesResponse{} - mi := &file_collection_v1_collection_proto_msgTypes[39] + mi := &file_collection_v1_collection_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2786,7 +2561,7 @@ func (x *ListRackTypesResponse) String() string { func (*ListRackTypesResponse) ProtoMessage() {} func (x *ListRackTypesResponse) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[39] + mi := &file_collection_v1_collection_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2799,7 +2574,7 @@ func (x *ListRackTypesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRackTypesResponse.ProtoReflect.Descriptor instead. func (*ListRackTypesResponse) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{39} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{35} } func (x *ListRackTypesResponse) GetRackTypes() []*RackType { @@ -2835,7 +2610,7 @@ type SaveRackRequest struct { func (x *SaveRackRequest) Reset() { *x = SaveRackRequest{} - mi := &file_collection_v1_collection_proto_msgTypes[40] + mi := &file_collection_v1_collection_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2847,7 +2622,7 @@ func (x *SaveRackRequest) String() string { func (*SaveRackRequest) ProtoMessage() {} func (x *SaveRackRequest) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[40] + mi := &file_collection_v1_collection_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2860,7 +2635,7 @@ func (x *SaveRackRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SaveRackRequest.ProtoReflect.Descriptor instead. func (*SaveRackRequest) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{40} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{36} } func (x *SaveRackRequest) GetCollectionId() int64 { @@ -2913,7 +2688,7 @@ type SaveRackResponse struct { func (x *SaveRackResponse) Reset() { *x = SaveRackResponse{} - mi := &file_collection_v1_collection_proto_msgTypes[41] + mi := &file_collection_v1_collection_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2925,7 +2700,7 @@ func (x *SaveRackResponse) String() string { func (*SaveRackResponse) ProtoMessage() {} func (x *SaveRackResponse) ProtoReflect() protoreflect.Message { - mi := &file_collection_v1_collection_proto_msgTypes[41] + mi := &file_collection_v1_collection_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2938,7 +2713,7 @@ func (x *SaveRackResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SaveRackResponse.ProtoReflect.Descriptor instead. func (*SaveRackResponse) Descriptor() ([]byte, []int) { - return file_collection_v1_collection_proto_rawDescGZIP(), []int{41} + return file_collection_v1_collection_proto_rawDescGZIP(), []int{37} } func (x *SaveRackResponse) GetCollection() *DeviceCollection { @@ -3156,405 +2931,355 @@ var file_collection_v1_collection_proto_rawDesc = string([]byte{ 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x99, 0x01, 0x0a, 0x1d, 0x41, 0x64, 0x64, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x4a, 0x0a, 0x0f, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x06, 0xba, 0x48, 0x03, - 0xc8, 0x01, 0x01, 0x52, 0x0e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x22, 0x9a, 0x01, 0x0a, 0x1e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x54, 0x6f, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, - 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, - 0x64, 0x64, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0a, 0x61, 0x64, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, - 0x73, 0x69, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x73, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x22, 0x9e, 0x01, 0x0a, 0x22, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, - 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x4a, 0x0a, 0x0f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, - 0x01, 0x52, 0x0e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x22, 0x4a, 0x0a, 0x23, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, - 0x76, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x91, 0x01, - 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, - 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0c, - 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x09, - 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, - 0x07, 0xba, 0x48, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, - 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0x82, 0x01, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x26, - 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, - 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x86, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x31, 0x0a, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x61, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x41, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x22, 0xc5, 0x01, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, - 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2c, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, - 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x34, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, - 0x02, 0x10, 0x01, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x43, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, - 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, - 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6f, 0x0a, 0x1b, 0x53, 0x65, - 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2b, - 0x0a, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, - 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, - 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x22, 0x82, 0x01, 0x0a, 0x1c, - 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x0d, - 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0c, 0x63, 0x6f, - 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x34, 0x0a, 0x11, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x10, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x22, 0x1f, 0x0a, 0x1d, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, - 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x43, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, + 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x91, 0x01, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x74, 0x0a, 0x08, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, - 0x6f, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xba, 0x48, 0x04, 0x1a, 0x02, + 0x28, 0x00, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x82, 0x01, 0x0a, 0x1d, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, + 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, + 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, + 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x22, 0x86, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, + 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x34, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, + 0x72, 0x02, 0x10, 0x01, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x61, 0x0a, 0x1c, 0x47, 0x65, 0x74, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xc5, 0x01, 0x0a, + 0x1a, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x0d, 0x63, + 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x34, 0x0a, 0x11, 0x64, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, - 0x3b, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x43, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x45, 0x0a, 0x14, - 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x6c, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x05, 0x73, 0x6c, - 0x6f, 0x74, 0x73, 0x22, 0xe0, 0x07, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x61, 0x73, 0x68, 0x72, - 0x61, 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x67, 0x5f, 0x65, 0x66, - 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6a, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, 0x45, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, - 0x79, 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x6f, - 0x77, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, - 0x6e, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x6d, 0x69, 0x6e, 0x54, 0x65, 0x6d, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x5f, 0x74, 0x65, - 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x01, 0x52, 0x0f, 0x6d, 0x61, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x43, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, - 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, - 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, - 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x25, 0x0a, 0x0e, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, - 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, - 0x74, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, - 0x0a, 0x15, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, - 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, - 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x3e, 0x0a, 0x1b, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x42, 0x6f, - 0x61, 0x72, 0x64, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, - 0x0f, 0x66, 0x61, 0x6e, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x12, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x66, 0x61, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x16, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x13, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x68, 0x61, 0x73, 0x68, 0x42, 0x6f, 0x61, 0x72, 0x64, - 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x73, - 0x75, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x14, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0d, 0x70, 0x73, 0x75, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x73, 0x6c, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x65, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, - 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0c, 0x73, 0x6c, 0x6f, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x22, 0x42, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x22, 0x52, 0x0a, 0x1a, 0x47, 0x65, - 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x73, - 0x0a, 0x0e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x10, 0x0a, 0x03, 0x72, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x72, - 0x6f, 0x77, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, - 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2d, 0x0a, 0x15, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x57, 0x0a, 0x08, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x72, 0x6f, - 0x77, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, - 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x4f, 0x0a, 0x15, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0a, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, - 0x65, 0x52, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0xcb, 0x02, 0x0a, - 0x0f, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x34, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xd8, 0x01, 0x01, 0x22, - 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0c, 0xba, 0x48, 0x09, 0xc8, 0x01, 0x01, 0x72, 0x04, 0x10, - 0x01, 0x18, 0x64, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x3c, 0x0a, 0x09, 0x72, 0x61, - 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x6f, 0x6e, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6f, 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, + 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x04, 0x73, 0x6c, 0x6f, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x52, + 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x22, 0x82, 0x01, 0x0a, 0x1c, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, + 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, + 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x34, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x22, 0x1f, 0x0a, 0x1d, 0x43, 0x6c, + 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x0a, 0x13, 0x47, + 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, + 0x20, 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x22, 0x74, 0x0a, 0x08, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x12, 0x2b, 0x0a, 0x11, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x08, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x6f, + 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, + 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x45, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, + 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, + 0x0a, 0x05, 0x73, 0x6c, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, - 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, - 0x72, 0x61, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4a, 0x0a, 0x0f, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x06, 0xba, 0x48, - 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x6f, 0x72, 0x12, 0x42, 0x0a, 0x10, 0x73, 0x6c, 0x6f, 0x74, 0x5f, 0x61, 0x73, 0x73, - 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, - 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x0f, 0x73, 0x6c, 0x6f, 0x74, 0x41, 0x73, 0x73, - 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xae, 0x01, 0x0a, 0x10, 0x53, - 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3f, 0x0a, 0x0a, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, - 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x69, 0x74, 0x65, 0x5f, - 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0x66, 0x0a, 0x0e, 0x43, - 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, - 0x1b, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, - 0x0a, 0x15, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4c, - 0x4c, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x41, 0x43, - 0x4b, 0x10, 0x02, 0x2a, 0xb6, 0x01, 0x0a, 0x0e, 0x52, 0x61, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, - 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, - 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, - 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, - 0x54, 0x4f, 0x4d, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x41, - 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, - 0x4f, 0x50, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, - 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, - 0x54, 0x54, 0x4f, 0x4d, 0x5f, 0x52, 0x49, 0x47, 0x48, 0x54, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, - 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, - 0x5f, 0x54, 0x4f, 0x50, 0x5f, 0x52, 0x49, 0x47, 0x48, 0x54, 0x10, 0x04, 0x2a, 0x70, 0x0a, 0x0f, - 0x52, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x6f, 0x6c, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, - 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x49, 0x52, 0x10, 0x01, 0x12, 0x1f, 0x0a, - 0x1b, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x49, 0x4d, 0x4d, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x2a, 0xdd, - 0x01, 0x0a, 0x10, 0x53, 0x6c, 0x6f, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, - 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x4c, 0x4f, 0x54, 0x5f, - 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x4d, - 0x50, 0x54, 0x59, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, - 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x48, 0x45, 0x41, 0x4c, - 0x54, 0x48, 0x59, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, - 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4e, 0x45, 0x45, 0x44, - 0x53, 0x5f, 0x41, 0x54, 0x54, 0x45, 0x4e, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x1e, 0x0a, - 0x1a, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x4f, 0x46, 0x46, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x04, 0x12, 0x1f, 0x0a, - 0x1b, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x53, 0x4c, 0x45, 0x45, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x32, 0x94, - 0x0d, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, - 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x5a, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x26, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, - 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x63, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, - 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x26, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x16, 0x41, 0x64, 0x64, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x2c, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x43, 0x6f, + 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x05, 0x73, 0x6c, 0x6f, 0x74, 0x73, 0x22, 0xe0, 0x07, + 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x72, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x54, 0x68, 0x73, + 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x67, 0x5f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, + 0x63, 0x79, 0x5f, 0x6a, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, + 0x67, 0x45, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x74, 0x68, 0x12, 0x24, + 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6b, 0x77, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x77, + 0x65, 0x72, 0x4b, 0x77, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x65, 0x6d, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x0f, 0x6d, 0x69, 0x6e, 0x54, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, + 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x5f, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x6d, 0x61, 0x78, + 0x54, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x12, 0x23, 0x0a, 0x0d, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, 0x66, + 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, 0x65, + 0x65, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x66, + 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, + 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6f, 0x77, 0x65, + 0x72, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3e, 0x0a, 0x1b, + 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x19, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x73, + 0x73, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x16, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x73, 0x73, + 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x61, 0x6e, 0x5f, 0x69, + 0x73, 0x73, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0d, 0x66, 0x61, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x33, 0x0a, 0x16, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x73, + 0x73, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x13, 0x68, 0x61, 0x73, 0x68, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x73, 0x75, 0x5f, 0x69, 0x73, 0x73, 0x75, + 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x70, + 0x73, 0x75, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0d, + 0x73, 0x6c, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x15, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x0c, 0x73, 0x6c, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, + 0x22, 0x42, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, + 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x73, 0x22, 0x52, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x73, 0x0a, 0x0e, 0x52, 0x61, 0x63, 0x6b, + 0x53, 0x6c, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x6f, + 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x72, 0x6f, 0x77, 0x12, 0x16, 0x0a, 0x06, + 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x6f, + 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x16, 0x0a, + 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2d, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, + 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x7a, + 0x6f, 0x6e, 0x65, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, + 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x08, + 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, + 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x72, 0x61, 0x63, 0x6b, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x4f, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, + 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, + 0x0a, 0x0a, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x72, 0x61, 0x63, + 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0xcb, 0x02, 0x0a, 0x0f, 0x53, 0x61, 0x76, 0x65, 0x52, + 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x0d, 0x63, 0x6f, + 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x42, 0x0a, 0xba, 0x48, 0x07, 0xd8, 0x01, 0x01, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, + 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, + 0x12, 0x22, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x0c, 0xba, 0x48, 0x09, 0xc8, 0x01, 0x01, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x12, 0x3c, 0x0a, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x66, + 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, + 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, 0x72, 0x61, 0x63, 0x6b, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x4a, 0x0a, 0x0f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0e, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x42, + 0x0a, 0x10, 0x73, 0x6c, 0x6f, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, + 0x74, 0x52, 0x0f, 0x73, 0x6c, 0x6f, 0x74, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xae, 0x01, 0x0a, 0x10, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0a, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, + 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x73, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0d, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x13, 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0x66, 0x0a, 0x0e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x43, 0x4f, 0x4c, 0x4c, 0x45, + 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4c, 0x4c, + 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, + 0x50, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x4f, + 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x41, 0x43, 0x4b, 0x10, 0x02, 0x2a, 0xb6, 0x01, + 0x0a, 0x0e, 0x52, 0x61, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, + 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, + 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, 0x4f, 0x4d, 0x5f, 0x4c, 0x45, + 0x46, 0x54, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, + 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, 0x4f, 0x50, 0x5f, 0x4c, 0x45, 0x46, + 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, + 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, 0x4f, 0x4d, 0x5f, 0x52, + 0x49, 0x47, 0x48, 0x54, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, + 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, 0x4f, 0x50, 0x5f, 0x52, + 0x49, 0x47, 0x48, 0x54, 0x10, 0x04, 0x2a, 0x70, 0x0a, 0x0f, 0x52, 0x61, 0x63, 0x6b, 0x43, 0x6f, + 0x6f, 0x6c, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, + 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, + 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x41, 0x49, 0x52, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x41, 0x43, 0x4b, 0x5f, + 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4d, 0x4d, + 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x2a, 0xdd, 0x01, 0x0a, 0x10, 0x53, 0x6c, 0x6f, + 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, + 0x1e, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, + 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, + 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x10, 0x01, 0x12, + 0x1e, 0x0a, 0x1a, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x02, 0x12, + 0x26, 0x0a, 0x22, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4e, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x41, 0x54, 0x54, 0x45, + 0x4e, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x4c, 0x4f, 0x54, 0x5f, + 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4f, 0x46, + 0x46, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x4c, 0x4f, 0x54, 0x5f, + 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x4c, + 0x45, 0x45, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x32, 0x96, 0x0b, 0x0a, 0x17, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, + 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, + 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x47, 0x65, 0x74, + 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2d, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x43, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x84, - 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, - 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, + 0x24, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, + 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, - 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x2b, - 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, - 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x47, 0x65, 0x74, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, + 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, + 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x60, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x25, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, + 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x72, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6c, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x53, 0x65, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, + 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, + 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x29, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, - 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, + 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, + 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x2e, + 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, + 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x43, 0x6c, 0x65, 0x61, - 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, - 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, - 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, + 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, + 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x63, 0x6f, - 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, - 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, - 0x73, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, - 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x23, 0x2e, + 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x69, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x29, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x61, 0x76, 0x65, - 0x52, 0x61, 0x63, 0x6b, 0x12, 0x1e, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xc8, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, - 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x42, 0x0f, 0x43, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4d, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, - 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x31, - 0x3b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x76, 0x31, 0xa2, 0x02, 0x03, - 0x43, 0x58, 0x58, 0xaa, 0x02, 0x0d, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0d, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x19, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, - 0x02, 0x0e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x3a, 0x56, 0x31, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, + 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, + 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, + 0x12, 0x1e, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x42, 0xc8, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x42, 0x0f, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x63, + 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x58, 0x58, 0xaa, + 0x02, 0x0d, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x56, 0x31, 0xca, + 0x02, 0x0d, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0xe2, + 0x02, 0x19, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0x5c, + 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x43, 0x6f, + 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -3570,138 +3295,128 @@ func file_collection_v1_collection_proto_rawDescGZIP() []byte { } var file_collection_v1_collection_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_collection_v1_collection_proto_msgTypes = make([]protoimpl.MessageInfo, 42) +var file_collection_v1_collection_proto_msgTypes = make([]protoimpl.MessageInfo, 38) var file_collection_v1_collection_proto_goTypes = []any{ - (CollectionType)(0), // 0: collection.v1.CollectionType - (RackOrderIndex)(0), // 1: collection.v1.RackOrderIndex - (RackCoolingType)(0), // 2: collection.v1.RackCoolingType - (SlotDeviceStatus)(0), // 3: collection.v1.SlotDeviceStatus - (*DeviceCollection)(nil), // 4: collection.v1.DeviceCollection - (*RackInfo)(nil), // 5: collection.v1.RackInfo - (*GroupInfo)(nil), // 6: collection.v1.GroupInfo - (*CollectionMember)(nil), // 7: collection.v1.CollectionMember - (*RackMemberDetails)(nil), // 8: collection.v1.RackMemberDetails - (*RackSlotPosition)(nil), // 9: collection.v1.RackSlotPosition - (*CreateCollectionRequest)(nil), // 10: collection.v1.CreateCollectionRequest - (*CreateCollectionResponse)(nil), // 11: collection.v1.CreateCollectionResponse - (*GetCollectionRequest)(nil), // 12: collection.v1.GetCollectionRequest - (*GetCollectionResponse)(nil), // 13: collection.v1.GetCollectionResponse - (*UpdateCollectionRequest)(nil), // 14: collection.v1.UpdateCollectionRequest - (*UpdateCollectionResponse)(nil), // 15: collection.v1.UpdateCollectionResponse - (*DeleteCollectionRequest)(nil), // 16: collection.v1.DeleteCollectionRequest - (*DeleteCollectionResponse)(nil), // 17: collection.v1.DeleteCollectionResponse - (*ListCollectionsRequest)(nil), // 18: collection.v1.ListCollectionsRequest - (*ListCollectionsResponse)(nil), // 19: collection.v1.ListCollectionsResponse - (*AddDevicesToCollectionRequest)(nil), // 20: collection.v1.AddDevicesToCollectionRequest - (*AddDevicesToCollectionResponse)(nil), // 21: collection.v1.AddDevicesToCollectionResponse - (*RemoveDevicesFromCollectionRequest)(nil), // 22: collection.v1.RemoveDevicesFromCollectionRequest - (*RemoveDevicesFromCollectionResponse)(nil), // 23: collection.v1.RemoveDevicesFromCollectionResponse - (*ListCollectionMembersRequest)(nil), // 24: collection.v1.ListCollectionMembersRequest - (*ListCollectionMembersResponse)(nil), // 25: collection.v1.ListCollectionMembersResponse - (*GetDeviceCollectionsRequest)(nil), // 26: collection.v1.GetDeviceCollectionsRequest - (*GetDeviceCollectionsResponse)(nil), // 27: collection.v1.GetDeviceCollectionsResponse - (*SetRackSlotPositionRequest)(nil), // 28: collection.v1.SetRackSlotPositionRequest - (*SetRackSlotPositionResponse)(nil), // 29: collection.v1.SetRackSlotPositionResponse - (*ClearRackSlotPositionRequest)(nil), // 30: collection.v1.ClearRackSlotPositionRequest - (*ClearRackSlotPositionResponse)(nil), // 31: collection.v1.ClearRackSlotPositionResponse - (*GetRackSlotsRequest)(nil), // 32: collection.v1.GetRackSlotsRequest - (*RackSlot)(nil), // 33: collection.v1.RackSlot - (*GetRackSlotsResponse)(nil), // 34: collection.v1.GetRackSlotsResponse - (*CollectionStats)(nil), // 35: collection.v1.CollectionStats - (*GetCollectionStatsRequest)(nil), // 36: collection.v1.GetCollectionStatsRequest - (*GetCollectionStatsResponse)(nil), // 37: collection.v1.GetCollectionStatsResponse - (*RackSlotStatus)(nil), // 38: collection.v1.RackSlotStatus - (*ListRackZonesRequest)(nil), // 39: collection.v1.ListRackZonesRequest - (*ListRackZonesResponse)(nil), // 40: collection.v1.ListRackZonesResponse - (*ListRackTypesRequest)(nil), // 41: collection.v1.ListRackTypesRequest - (*RackType)(nil), // 42: collection.v1.RackType - (*ListRackTypesResponse)(nil), // 43: collection.v1.ListRackTypesResponse - (*SaveRackRequest)(nil), // 44: collection.v1.SaveRackRequest - (*SaveRackResponse)(nil), // 45: collection.v1.SaveRackResponse - (*timestamppb.Timestamp)(nil), // 46: google.protobuf.Timestamp - (*v1.DeviceSelector)(nil), // 47: common.v1.DeviceSelector - (*v1.SortConfig)(nil), // 48: common.v1.SortConfig - (v11.ComponentType)(0), // 49: errors.v1.ComponentType + (CollectionType)(0), // 0: collection.v1.CollectionType + (RackOrderIndex)(0), // 1: collection.v1.RackOrderIndex + (RackCoolingType)(0), // 2: collection.v1.RackCoolingType + (SlotDeviceStatus)(0), // 3: collection.v1.SlotDeviceStatus + (*DeviceCollection)(nil), // 4: collection.v1.DeviceCollection + (*RackInfo)(nil), // 5: collection.v1.RackInfo + (*GroupInfo)(nil), // 6: collection.v1.GroupInfo + (*CollectionMember)(nil), // 7: collection.v1.CollectionMember + (*RackMemberDetails)(nil), // 8: collection.v1.RackMemberDetails + (*RackSlotPosition)(nil), // 9: collection.v1.RackSlotPosition + (*CreateCollectionRequest)(nil), // 10: collection.v1.CreateCollectionRequest + (*CreateCollectionResponse)(nil), // 11: collection.v1.CreateCollectionResponse + (*GetCollectionRequest)(nil), // 12: collection.v1.GetCollectionRequest + (*GetCollectionResponse)(nil), // 13: collection.v1.GetCollectionResponse + (*UpdateCollectionRequest)(nil), // 14: collection.v1.UpdateCollectionRequest + (*UpdateCollectionResponse)(nil), // 15: collection.v1.UpdateCollectionResponse + (*DeleteCollectionRequest)(nil), // 16: collection.v1.DeleteCollectionRequest + (*DeleteCollectionResponse)(nil), // 17: collection.v1.DeleteCollectionResponse + (*ListCollectionsRequest)(nil), // 18: collection.v1.ListCollectionsRequest + (*ListCollectionsResponse)(nil), // 19: collection.v1.ListCollectionsResponse + (*ListCollectionMembersRequest)(nil), // 20: collection.v1.ListCollectionMembersRequest + (*ListCollectionMembersResponse)(nil), // 21: collection.v1.ListCollectionMembersResponse + (*GetDeviceCollectionsRequest)(nil), // 22: collection.v1.GetDeviceCollectionsRequest + (*GetDeviceCollectionsResponse)(nil), // 23: collection.v1.GetDeviceCollectionsResponse + (*SetRackSlotPositionRequest)(nil), // 24: collection.v1.SetRackSlotPositionRequest + (*SetRackSlotPositionResponse)(nil), // 25: collection.v1.SetRackSlotPositionResponse + (*ClearRackSlotPositionRequest)(nil), // 26: collection.v1.ClearRackSlotPositionRequest + (*ClearRackSlotPositionResponse)(nil), // 27: collection.v1.ClearRackSlotPositionResponse + (*GetRackSlotsRequest)(nil), // 28: collection.v1.GetRackSlotsRequest + (*RackSlot)(nil), // 29: collection.v1.RackSlot + (*GetRackSlotsResponse)(nil), // 30: collection.v1.GetRackSlotsResponse + (*CollectionStats)(nil), // 31: collection.v1.CollectionStats + (*GetCollectionStatsRequest)(nil), // 32: collection.v1.GetCollectionStatsRequest + (*GetCollectionStatsResponse)(nil), // 33: collection.v1.GetCollectionStatsResponse + (*RackSlotStatus)(nil), // 34: collection.v1.RackSlotStatus + (*ListRackZonesRequest)(nil), // 35: collection.v1.ListRackZonesRequest + (*ListRackZonesResponse)(nil), // 36: collection.v1.ListRackZonesResponse + (*ListRackTypesRequest)(nil), // 37: collection.v1.ListRackTypesRequest + (*RackType)(nil), // 38: collection.v1.RackType + (*ListRackTypesResponse)(nil), // 39: collection.v1.ListRackTypesResponse + (*SaveRackRequest)(nil), // 40: collection.v1.SaveRackRequest + (*SaveRackResponse)(nil), // 41: collection.v1.SaveRackResponse + (*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp + (*v1.DeviceSelector)(nil), // 43: common.v1.DeviceSelector + (*v1.SortConfig)(nil), // 44: common.v1.SortConfig + (v11.ComponentType)(0), // 45: errors.v1.ComponentType } var file_collection_v1_collection_proto_depIdxs = []int32{ 0, // 0: collection.v1.DeviceCollection.type:type_name -> collection.v1.CollectionType - 46, // 1: collection.v1.DeviceCollection.created_at:type_name -> google.protobuf.Timestamp - 46, // 2: collection.v1.DeviceCollection.updated_at:type_name -> google.protobuf.Timestamp + 42, // 1: collection.v1.DeviceCollection.created_at:type_name -> google.protobuf.Timestamp + 42, // 2: collection.v1.DeviceCollection.updated_at:type_name -> google.protobuf.Timestamp 5, // 3: collection.v1.DeviceCollection.rack_info:type_name -> collection.v1.RackInfo 6, // 4: collection.v1.DeviceCollection.group_info:type_name -> collection.v1.GroupInfo 1, // 5: collection.v1.RackInfo.order_index:type_name -> collection.v1.RackOrderIndex 2, // 6: collection.v1.RackInfo.cooling_type:type_name -> collection.v1.RackCoolingType - 46, // 7: collection.v1.CollectionMember.added_at:type_name -> google.protobuf.Timestamp + 42, // 7: collection.v1.CollectionMember.added_at:type_name -> google.protobuf.Timestamp 8, // 8: collection.v1.CollectionMember.rack:type_name -> collection.v1.RackMemberDetails 9, // 9: collection.v1.RackMemberDetails.slot_position:type_name -> collection.v1.RackSlotPosition 0, // 10: collection.v1.CreateCollectionRequest.type:type_name -> collection.v1.CollectionType 5, // 11: collection.v1.CreateCollectionRequest.rack_info:type_name -> collection.v1.RackInfo 6, // 12: collection.v1.CreateCollectionRequest.group_info:type_name -> collection.v1.GroupInfo - 47, // 13: collection.v1.CreateCollectionRequest.device_selector:type_name -> common.v1.DeviceSelector + 43, // 13: collection.v1.CreateCollectionRequest.device_selector:type_name -> common.v1.DeviceSelector 4, // 14: collection.v1.CreateCollectionResponse.collection:type_name -> collection.v1.DeviceCollection 4, // 15: collection.v1.GetCollectionResponse.collection:type_name -> collection.v1.DeviceCollection 5, // 16: collection.v1.UpdateCollectionRequest.rack_info:type_name -> collection.v1.RackInfo 6, // 17: collection.v1.UpdateCollectionRequest.group_info:type_name -> collection.v1.GroupInfo - 47, // 18: collection.v1.UpdateCollectionRequest.device_selector:type_name -> common.v1.DeviceSelector + 43, // 18: collection.v1.UpdateCollectionRequest.device_selector:type_name -> common.v1.DeviceSelector 4, // 19: collection.v1.UpdateCollectionResponse.collection:type_name -> collection.v1.DeviceCollection 0, // 20: collection.v1.ListCollectionsRequest.type:type_name -> collection.v1.CollectionType - 48, // 21: collection.v1.ListCollectionsRequest.sort:type_name -> common.v1.SortConfig - 49, // 22: collection.v1.ListCollectionsRequest.error_component_types:type_name -> errors.v1.ComponentType + 44, // 21: collection.v1.ListCollectionsRequest.sort:type_name -> common.v1.SortConfig + 45, // 22: collection.v1.ListCollectionsRequest.error_component_types:type_name -> errors.v1.ComponentType 4, // 23: collection.v1.ListCollectionsResponse.collections:type_name -> collection.v1.DeviceCollection - 47, // 24: collection.v1.AddDevicesToCollectionRequest.device_selector:type_name -> common.v1.DeviceSelector - 47, // 25: collection.v1.RemoveDevicesFromCollectionRequest.device_selector:type_name -> common.v1.DeviceSelector - 7, // 26: collection.v1.ListCollectionMembersResponse.members:type_name -> collection.v1.CollectionMember - 0, // 27: collection.v1.GetDeviceCollectionsRequest.type:type_name -> collection.v1.CollectionType - 4, // 28: collection.v1.GetDeviceCollectionsResponse.collections:type_name -> collection.v1.DeviceCollection - 9, // 29: collection.v1.SetRackSlotPositionRequest.position:type_name -> collection.v1.RackSlotPosition - 33, // 30: collection.v1.SetRackSlotPositionResponse.slot:type_name -> collection.v1.RackSlot - 9, // 31: collection.v1.RackSlot.position:type_name -> collection.v1.RackSlotPosition - 33, // 32: collection.v1.GetRackSlotsResponse.slots:type_name -> collection.v1.RackSlot - 38, // 33: collection.v1.CollectionStats.slot_statuses:type_name -> collection.v1.RackSlotStatus - 35, // 34: collection.v1.GetCollectionStatsResponse.stats:type_name -> collection.v1.CollectionStats - 3, // 35: collection.v1.RackSlotStatus.status:type_name -> collection.v1.SlotDeviceStatus - 42, // 36: collection.v1.ListRackTypesResponse.rack_types:type_name -> collection.v1.RackType - 5, // 37: collection.v1.SaveRackRequest.rack_info:type_name -> collection.v1.RackInfo - 47, // 38: collection.v1.SaveRackRequest.device_selector:type_name -> common.v1.DeviceSelector - 33, // 39: collection.v1.SaveRackRequest.slot_assignments:type_name -> collection.v1.RackSlot - 4, // 40: collection.v1.SaveRackResponse.collection:type_name -> collection.v1.DeviceCollection - 10, // 41: collection.v1.DeviceCollectionService.CreateCollection:input_type -> collection.v1.CreateCollectionRequest - 12, // 42: collection.v1.DeviceCollectionService.GetCollection:input_type -> collection.v1.GetCollectionRequest - 14, // 43: collection.v1.DeviceCollectionService.UpdateCollection:input_type -> collection.v1.UpdateCollectionRequest - 16, // 44: collection.v1.DeviceCollectionService.DeleteCollection:input_type -> collection.v1.DeleteCollectionRequest - 18, // 45: collection.v1.DeviceCollectionService.ListCollections:input_type -> collection.v1.ListCollectionsRequest - 20, // 46: collection.v1.DeviceCollectionService.AddDevicesToCollection:input_type -> collection.v1.AddDevicesToCollectionRequest - 22, // 47: collection.v1.DeviceCollectionService.RemoveDevicesFromCollection:input_type -> collection.v1.RemoveDevicesFromCollectionRequest - 24, // 48: collection.v1.DeviceCollectionService.ListCollectionMembers:input_type -> collection.v1.ListCollectionMembersRequest - 26, // 49: collection.v1.DeviceCollectionService.GetDeviceCollections:input_type -> collection.v1.GetDeviceCollectionsRequest - 28, // 50: collection.v1.DeviceCollectionService.SetRackSlotPosition:input_type -> collection.v1.SetRackSlotPositionRequest - 30, // 51: collection.v1.DeviceCollectionService.ClearRackSlotPosition:input_type -> collection.v1.ClearRackSlotPositionRequest - 32, // 52: collection.v1.DeviceCollectionService.GetRackSlots:input_type -> collection.v1.GetRackSlotsRequest - 36, // 53: collection.v1.DeviceCollectionService.GetCollectionStats:input_type -> collection.v1.GetCollectionStatsRequest - 39, // 54: collection.v1.DeviceCollectionService.ListRackZones:input_type -> collection.v1.ListRackZonesRequest - 41, // 55: collection.v1.DeviceCollectionService.ListRackTypes:input_type -> collection.v1.ListRackTypesRequest - 44, // 56: collection.v1.DeviceCollectionService.SaveRack:input_type -> collection.v1.SaveRackRequest - 11, // 57: collection.v1.DeviceCollectionService.CreateCollection:output_type -> collection.v1.CreateCollectionResponse - 13, // 58: collection.v1.DeviceCollectionService.GetCollection:output_type -> collection.v1.GetCollectionResponse - 15, // 59: collection.v1.DeviceCollectionService.UpdateCollection:output_type -> collection.v1.UpdateCollectionResponse - 17, // 60: collection.v1.DeviceCollectionService.DeleteCollection:output_type -> collection.v1.DeleteCollectionResponse - 19, // 61: collection.v1.DeviceCollectionService.ListCollections:output_type -> collection.v1.ListCollectionsResponse - 21, // 62: collection.v1.DeviceCollectionService.AddDevicesToCollection:output_type -> collection.v1.AddDevicesToCollectionResponse - 23, // 63: collection.v1.DeviceCollectionService.RemoveDevicesFromCollection:output_type -> collection.v1.RemoveDevicesFromCollectionResponse - 25, // 64: collection.v1.DeviceCollectionService.ListCollectionMembers:output_type -> collection.v1.ListCollectionMembersResponse - 27, // 65: collection.v1.DeviceCollectionService.GetDeviceCollections:output_type -> collection.v1.GetDeviceCollectionsResponse - 29, // 66: collection.v1.DeviceCollectionService.SetRackSlotPosition:output_type -> collection.v1.SetRackSlotPositionResponse - 31, // 67: collection.v1.DeviceCollectionService.ClearRackSlotPosition:output_type -> collection.v1.ClearRackSlotPositionResponse - 34, // 68: collection.v1.DeviceCollectionService.GetRackSlots:output_type -> collection.v1.GetRackSlotsResponse - 37, // 69: collection.v1.DeviceCollectionService.GetCollectionStats:output_type -> collection.v1.GetCollectionStatsResponse - 40, // 70: collection.v1.DeviceCollectionService.ListRackZones:output_type -> collection.v1.ListRackZonesResponse - 43, // 71: collection.v1.DeviceCollectionService.ListRackTypes:output_type -> collection.v1.ListRackTypesResponse - 45, // 72: collection.v1.DeviceCollectionService.SaveRack:output_type -> collection.v1.SaveRackResponse - 57, // [57:73] is the sub-list for method output_type - 41, // [41:57] is the sub-list for method input_type - 41, // [41:41] is the sub-list for extension type_name - 41, // [41:41] is the sub-list for extension extendee - 0, // [0:41] is the sub-list for field type_name + 7, // 24: collection.v1.ListCollectionMembersResponse.members:type_name -> collection.v1.CollectionMember + 0, // 25: collection.v1.GetDeviceCollectionsRequest.type:type_name -> collection.v1.CollectionType + 4, // 26: collection.v1.GetDeviceCollectionsResponse.collections:type_name -> collection.v1.DeviceCollection + 9, // 27: collection.v1.SetRackSlotPositionRequest.position:type_name -> collection.v1.RackSlotPosition + 29, // 28: collection.v1.SetRackSlotPositionResponse.slot:type_name -> collection.v1.RackSlot + 9, // 29: collection.v1.RackSlot.position:type_name -> collection.v1.RackSlotPosition + 29, // 30: collection.v1.GetRackSlotsResponse.slots:type_name -> collection.v1.RackSlot + 34, // 31: collection.v1.CollectionStats.slot_statuses:type_name -> collection.v1.RackSlotStatus + 31, // 32: collection.v1.GetCollectionStatsResponse.stats:type_name -> collection.v1.CollectionStats + 3, // 33: collection.v1.RackSlotStatus.status:type_name -> collection.v1.SlotDeviceStatus + 38, // 34: collection.v1.ListRackTypesResponse.rack_types:type_name -> collection.v1.RackType + 5, // 35: collection.v1.SaveRackRequest.rack_info:type_name -> collection.v1.RackInfo + 43, // 36: collection.v1.SaveRackRequest.device_selector:type_name -> common.v1.DeviceSelector + 29, // 37: collection.v1.SaveRackRequest.slot_assignments:type_name -> collection.v1.RackSlot + 4, // 38: collection.v1.SaveRackResponse.collection:type_name -> collection.v1.DeviceCollection + 10, // 39: collection.v1.DeviceCollectionService.CreateCollection:input_type -> collection.v1.CreateCollectionRequest + 12, // 40: collection.v1.DeviceCollectionService.GetCollection:input_type -> collection.v1.GetCollectionRequest + 14, // 41: collection.v1.DeviceCollectionService.UpdateCollection:input_type -> collection.v1.UpdateCollectionRequest + 16, // 42: collection.v1.DeviceCollectionService.DeleteCollection:input_type -> collection.v1.DeleteCollectionRequest + 18, // 43: collection.v1.DeviceCollectionService.ListCollections:input_type -> collection.v1.ListCollectionsRequest + 20, // 44: collection.v1.DeviceCollectionService.ListCollectionMembers:input_type -> collection.v1.ListCollectionMembersRequest + 22, // 45: collection.v1.DeviceCollectionService.GetDeviceCollections:input_type -> collection.v1.GetDeviceCollectionsRequest + 24, // 46: collection.v1.DeviceCollectionService.SetRackSlotPosition:input_type -> collection.v1.SetRackSlotPositionRequest + 26, // 47: collection.v1.DeviceCollectionService.ClearRackSlotPosition:input_type -> collection.v1.ClearRackSlotPositionRequest + 28, // 48: collection.v1.DeviceCollectionService.GetRackSlots:input_type -> collection.v1.GetRackSlotsRequest + 32, // 49: collection.v1.DeviceCollectionService.GetCollectionStats:input_type -> collection.v1.GetCollectionStatsRequest + 35, // 50: collection.v1.DeviceCollectionService.ListRackZones:input_type -> collection.v1.ListRackZonesRequest + 37, // 51: collection.v1.DeviceCollectionService.ListRackTypes:input_type -> collection.v1.ListRackTypesRequest + 40, // 52: collection.v1.DeviceCollectionService.SaveRack:input_type -> collection.v1.SaveRackRequest + 11, // 53: collection.v1.DeviceCollectionService.CreateCollection:output_type -> collection.v1.CreateCollectionResponse + 13, // 54: collection.v1.DeviceCollectionService.GetCollection:output_type -> collection.v1.GetCollectionResponse + 15, // 55: collection.v1.DeviceCollectionService.UpdateCollection:output_type -> collection.v1.UpdateCollectionResponse + 17, // 56: collection.v1.DeviceCollectionService.DeleteCollection:output_type -> collection.v1.DeleteCollectionResponse + 19, // 57: collection.v1.DeviceCollectionService.ListCollections:output_type -> collection.v1.ListCollectionsResponse + 21, // 58: collection.v1.DeviceCollectionService.ListCollectionMembers:output_type -> collection.v1.ListCollectionMembersResponse + 23, // 59: collection.v1.DeviceCollectionService.GetDeviceCollections:output_type -> collection.v1.GetDeviceCollectionsResponse + 25, // 60: collection.v1.DeviceCollectionService.SetRackSlotPosition:output_type -> collection.v1.SetRackSlotPositionResponse + 27, // 61: collection.v1.DeviceCollectionService.ClearRackSlotPosition:output_type -> collection.v1.ClearRackSlotPositionResponse + 30, // 62: collection.v1.DeviceCollectionService.GetRackSlots:output_type -> collection.v1.GetRackSlotsResponse + 33, // 63: collection.v1.DeviceCollectionService.GetCollectionStats:output_type -> collection.v1.GetCollectionStatsResponse + 36, // 64: collection.v1.DeviceCollectionService.ListRackZones:output_type -> collection.v1.ListRackZonesResponse + 39, // 65: collection.v1.DeviceCollectionService.ListRackTypes:output_type -> collection.v1.ListRackTypesResponse + 41, // 66: collection.v1.DeviceCollectionService.SaveRack:output_type -> collection.v1.SaveRackResponse + 53, // [53:67] is the sub-list for method output_type + 39, // [39:53] is the sub-list for method input_type + 39, // [39:39] is the sub-list for extension type_name + 39, // [39:39] is the sub-list for extension extendee + 0, // [0:39] is the sub-list for field type_name } func init() { file_collection_v1_collection_proto_init() } @@ -3725,14 +3440,14 @@ func file_collection_v1_collection_proto_init() { (*UpdateCollectionRequest_RackInfo)(nil), (*UpdateCollectionRequest_GroupInfo)(nil), } - file_collection_v1_collection_proto_msgTypes[40].OneofWrappers = []any{} + file_collection_v1_collection_proto_msgTypes[36].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_collection_v1_collection_proto_rawDesc), len(file_collection_v1_collection_proto_rawDesc)), NumEnums: 4, - NumMessages: 42, + NumMessages: 38, NumExtensions: 0, NumServices: 1, }, diff --git a/server/generated/grpc/collection/v1/collectionv1connect/collection.connect.go b/server/generated/grpc/collection/v1/collectionv1connect/collection.connect.go index ad3d9d194..12eda73da 100644 --- a/server/generated/grpc/collection/v1/collectionv1connect/collection.connect.go +++ b/server/generated/grpc/collection/v1/collectionv1connect/collection.connect.go @@ -49,12 +49,6 @@ const ( // DeviceCollectionServiceListCollectionsProcedure is the fully-qualified name of the // DeviceCollectionService's ListCollections RPC. DeviceCollectionServiceListCollectionsProcedure = "/collection.v1.DeviceCollectionService/ListCollections" - // DeviceCollectionServiceAddDevicesToCollectionProcedure is the fully-qualified name of the - // DeviceCollectionService's AddDevicesToCollection RPC. - DeviceCollectionServiceAddDevicesToCollectionProcedure = "/collection.v1.DeviceCollectionService/AddDevicesToCollection" - // DeviceCollectionServiceRemoveDevicesFromCollectionProcedure is the fully-qualified name of the - // DeviceCollectionService's RemoveDevicesFromCollection RPC. - DeviceCollectionServiceRemoveDevicesFromCollectionProcedure = "/collection.v1.DeviceCollectionService/RemoveDevicesFromCollection" // DeviceCollectionServiceListCollectionMembersProcedure is the fully-qualified name of the // DeviceCollectionService's ListCollectionMembers RPC. DeviceCollectionServiceListCollectionMembersProcedure = "/collection.v1.DeviceCollectionService/ListCollectionMembers" @@ -96,10 +90,6 @@ type DeviceCollectionServiceClient interface { DeleteCollection(context.Context, *connect.Request[v1.DeleteCollectionRequest]) (*connect.Response[v1.DeleteCollectionResponse], error) // Lists all collections for the organization ListCollections(context.Context, *connect.Request[v1.ListCollectionsRequest]) (*connect.Response[v1.ListCollectionsResponse], error) - // Adds devices to a collection - AddDevicesToCollection(context.Context, *connect.Request[v1.AddDevicesToCollectionRequest]) (*connect.Response[v1.AddDevicesToCollectionResponse], error) - // Removes devices from a collection - RemoveDevicesFromCollection(context.Context, *connect.Request[v1.RemoveDevicesFromCollectionRequest]) (*connect.Response[v1.RemoveDevicesFromCollectionResponse], error) // Lists members of a collection ListCollectionMembers(context.Context, *connect.Request[v1.ListCollectionMembersRequest]) (*connect.Response[v1.ListCollectionMembersResponse], error) // Gets collections that a device belongs to @@ -156,16 +146,6 @@ func NewDeviceCollectionServiceClient(httpClient connect.HTTPClient, baseURL str baseURL+DeviceCollectionServiceListCollectionsProcedure, opts..., ), - addDevicesToCollection: connect.NewClient[v1.AddDevicesToCollectionRequest, v1.AddDevicesToCollectionResponse]( - httpClient, - baseURL+DeviceCollectionServiceAddDevicesToCollectionProcedure, - opts..., - ), - removeDevicesFromCollection: connect.NewClient[v1.RemoveDevicesFromCollectionRequest, v1.RemoveDevicesFromCollectionResponse]( - httpClient, - baseURL+DeviceCollectionServiceRemoveDevicesFromCollectionProcedure, - opts..., - ), listCollectionMembers: connect.NewClient[v1.ListCollectionMembersRequest, v1.ListCollectionMembersResponse]( httpClient, baseURL+DeviceCollectionServiceListCollectionMembersProcedure, @@ -216,22 +196,20 @@ func NewDeviceCollectionServiceClient(httpClient connect.HTTPClient, baseURL str // deviceCollectionServiceClient implements DeviceCollectionServiceClient. type deviceCollectionServiceClient struct { - createCollection *connect.Client[v1.CreateCollectionRequest, v1.CreateCollectionResponse] - getCollection *connect.Client[v1.GetCollectionRequest, v1.GetCollectionResponse] - updateCollection *connect.Client[v1.UpdateCollectionRequest, v1.UpdateCollectionResponse] - deleteCollection *connect.Client[v1.DeleteCollectionRequest, v1.DeleteCollectionResponse] - listCollections *connect.Client[v1.ListCollectionsRequest, v1.ListCollectionsResponse] - addDevicesToCollection *connect.Client[v1.AddDevicesToCollectionRequest, v1.AddDevicesToCollectionResponse] - removeDevicesFromCollection *connect.Client[v1.RemoveDevicesFromCollectionRequest, v1.RemoveDevicesFromCollectionResponse] - listCollectionMembers *connect.Client[v1.ListCollectionMembersRequest, v1.ListCollectionMembersResponse] - getDeviceCollections *connect.Client[v1.GetDeviceCollectionsRequest, v1.GetDeviceCollectionsResponse] - setRackSlotPosition *connect.Client[v1.SetRackSlotPositionRequest, v1.SetRackSlotPositionResponse] - clearRackSlotPosition *connect.Client[v1.ClearRackSlotPositionRequest, v1.ClearRackSlotPositionResponse] - getRackSlots *connect.Client[v1.GetRackSlotsRequest, v1.GetRackSlotsResponse] - getCollectionStats *connect.Client[v1.GetCollectionStatsRequest, v1.GetCollectionStatsResponse] - listRackZones *connect.Client[v1.ListRackZonesRequest, v1.ListRackZonesResponse] - listRackTypes *connect.Client[v1.ListRackTypesRequest, v1.ListRackTypesResponse] - saveRack *connect.Client[v1.SaveRackRequest, v1.SaveRackResponse] + createCollection *connect.Client[v1.CreateCollectionRequest, v1.CreateCollectionResponse] + getCollection *connect.Client[v1.GetCollectionRequest, v1.GetCollectionResponse] + updateCollection *connect.Client[v1.UpdateCollectionRequest, v1.UpdateCollectionResponse] + deleteCollection *connect.Client[v1.DeleteCollectionRequest, v1.DeleteCollectionResponse] + listCollections *connect.Client[v1.ListCollectionsRequest, v1.ListCollectionsResponse] + listCollectionMembers *connect.Client[v1.ListCollectionMembersRequest, v1.ListCollectionMembersResponse] + getDeviceCollections *connect.Client[v1.GetDeviceCollectionsRequest, v1.GetDeviceCollectionsResponse] + setRackSlotPosition *connect.Client[v1.SetRackSlotPositionRequest, v1.SetRackSlotPositionResponse] + clearRackSlotPosition *connect.Client[v1.ClearRackSlotPositionRequest, v1.ClearRackSlotPositionResponse] + getRackSlots *connect.Client[v1.GetRackSlotsRequest, v1.GetRackSlotsResponse] + getCollectionStats *connect.Client[v1.GetCollectionStatsRequest, v1.GetCollectionStatsResponse] + listRackZones *connect.Client[v1.ListRackZonesRequest, v1.ListRackZonesResponse] + listRackTypes *connect.Client[v1.ListRackTypesRequest, v1.ListRackTypesResponse] + saveRack *connect.Client[v1.SaveRackRequest, v1.SaveRackResponse] } // CreateCollection calls collection.v1.DeviceCollectionService.CreateCollection. @@ -259,17 +237,6 @@ func (c *deviceCollectionServiceClient) ListCollections(ctx context.Context, req return c.listCollections.CallUnary(ctx, req) } -// AddDevicesToCollection calls collection.v1.DeviceCollectionService.AddDevicesToCollection. -func (c *deviceCollectionServiceClient) AddDevicesToCollection(ctx context.Context, req *connect.Request[v1.AddDevicesToCollectionRequest]) (*connect.Response[v1.AddDevicesToCollectionResponse], error) { - return c.addDevicesToCollection.CallUnary(ctx, req) -} - -// RemoveDevicesFromCollection calls -// collection.v1.DeviceCollectionService.RemoveDevicesFromCollection. -func (c *deviceCollectionServiceClient) RemoveDevicesFromCollection(ctx context.Context, req *connect.Request[v1.RemoveDevicesFromCollectionRequest]) (*connect.Response[v1.RemoveDevicesFromCollectionResponse], error) { - return c.removeDevicesFromCollection.CallUnary(ctx, req) -} - // ListCollectionMembers calls collection.v1.DeviceCollectionService.ListCollectionMembers. func (c *deviceCollectionServiceClient) ListCollectionMembers(ctx context.Context, req *connect.Request[v1.ListCollectionMembersRequest]) (*connect.Response[v1.ListCollectionMembersResponse], error) { return c.listCollectionMembers.CallUnary(ctx, req) @@ -328,10 +295,6 @@ type DeviceCollectionServiceHandler interface { DeleteCollection(context.Context, *connect.Request[v1.DeleteCollectionRequest]) (*connect.Response[v1.DeleteCollectionResponse], error) // Lists all collections for the organization ListCollections(context.Context, *connect.Request[v1.ListCollectionsRequest]) (*connect.Response[v1.ListCollectionsResponse], error) - // Adds devices to a collection - AddDevicesToCollection(context.Context, *connect.Request[v1.AddDevicesToCollectionRequest]) (*connect.Response[v1.AddDevicesToCollectionResponse], error) - // Removes devices from a collection - RemoveDevicesFromCollection(context.Context, *connect.Request[v1.RemoveDevicesFromCollectionRequest]) (*connect.Response[v1.RemoveDevicesFromCollectionResponse], error) // Lists members of a collection ListCollectionMembers(context.Context, *connect.Request[v1.ListCollectionMembersRequest]) (*connect.Response[v1.ListCollectionMembersResponse], error) // Gets collections that a device belongs to @@ -384,16 +347,6 @@ func NewDeviceCollectionServiceHandler(svc DeviceCollectionServiceHandler, opts svc.ListCollections, opts..., ) - deviceCollectionServiceAddDevicesToCollectionHandler := connect.NewUnaryHandler( - DeviceCollectionServiceAddDevicesToCollectionProcedure, - svc.AddDevicesToCollection, - opts..., - ) - deviceCollectionServiceRemoveDevicesFromCollectionHandler := connect.NewUnaryHandler( - DeviceCollectionServiceRemoveDevicesFromCollectionProcedure, - svc.RemoveDevicesFromCollection, - opts..., - ) deviceCollectionServiceListCollectionMembersHandler := connect.NewUnaryHandler( DeviceCollectionServiceListCollectionMembersProcedure, svc.ListCollectionMembers, @@ -451,10 +404,6 @@ func NewDeviceCollectionServiceHandler(svc DeviceCollectionServiceHandler, opts deviceCollectionServiceDeleteCollectionHandler.ServeHTTP(w, r) case DeviceCollectionServiceListCollectionsProcedure: deviceCollectionServiceListCollectionsHandler.ServeHTTP(w, r) - case DeviceCollectionServiceAddDevicesToCollectionProcedure: - deviceCollectionServiceAddDevicesToCollectionHandler.ServeHTTP(w, r) - case DeviceCollectionServiceRemoveDevicesFromCollectionProcedure: - deviceCollectionServiceRemoveDevicesFromCollectionHandler.ServeHTTP(w, r) case DeviceCollectionServiceListCollectionMembersProcedure: deviceCollectionServiceListCollectionMembersHandler.ServeHTTP(w, r) case DeviceCollectionServiceGetDeviceCollectionsProcedure: @@ -502,14 +451,6 @@ func (UnimplementedDeviceCollectionServiceHandler) ListCollections(context.Conte return nil, connect.NewError(connect.CodeUnimplemented, errors.New("collection.v1.DeviceCollectionService.ListCollections is not implemented")) } -func (UnimplementedDeviceCollectionServiceHandler) AddDevicesToCollection(context.Context, *connect.Request[v1.AddDevicesToCollectionRequest]) (*connect.Response[v1.AddDevicesToCollectionResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("collection.v1.DeviceCollectionService.AddDevicesToCollection is not implemented")) -} - -func (UnimplementedDeviceCollectionServiceHandler) RemoveDevicesFromCollection(context.Context, *connect.Request[v1.RemoveDevicesFromCollectionRequest]) (*connect.Response[v1.RemoveDevicesFromCollectionResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("collection.v1.DeviceCollectionService.RemoveDevicesFromCollection is not implemented")) -} - func (UnimplementedDeviceCollectionServiceHandler) ListCollectionMembers(context.Context, *connect.Request[v1.ListCollectionMembersRequest]) (*connect.Response[v1.ListCollectionMembersResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("collection.v1.DeviceCollectionService.ListCollectionMembers is not implemented")) } diff --git a/server/generated/grpc/device_set/v1/device_set.pb.go b/server/generated/grpc/device_set/v1/device_set.pb.go index 4b155496c..de2646852 100644 --- a/server/generated/grpc/device_set/v1/device_set.pb.go +++ b/server/generated/grpc/device_set/v1/device_set.pb.go @@ -1453,35 +1453,35 @@ func (x *ListDeviceSetsResponse) GetTotalCount() int32 { return 0 } -// Request to add devices to a device set. -// -// When the target is a site-stamped rack, the server rewrites -// device.site_id to match the rack for every added device. The affected -// count is returned in site_reassigned_count. Groups are exempt. -type AddDevicesToDeviceSetRequest struct { +// Request to add devices to a group. Groups have many-to-many device +// membership: devices may already belong to other groups and to a rack, +// none of which are touched. Server rejects with InvalidArgument when +// target_group_id refers to a non-group device set. +type AddDevicesToGroupRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // ID of the device set to add devices to - DeviceSetId int64 `protobuf:"varint,1,opt,name=device_set_id,json=deviceSetId,proto3" json:"device_set_id,omitempty"` - // Devices to add: specific list or all paired devices + // ID of the group to add devices to. Must reference a device set of + // type GROUP; racks are rejected. + TargetGroupId int64 `protobuf:"varint,1,opt,name=target_group_id,json=targetGroupId,proto3" json:"target_group_id,omitempty"` + // Devices to add: specific list or all paired devices. DeviceSelector *v1.DeviceSelector `protobuf:"bytes,2,opt,name=device_selector,json=deviceSelector,proto3" json:"device_selector,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AddDevicesToDeviceSetRequest) Reset() { - *x = AddDevicesToDeviceSetRequest{} +func (x *AddDevicesToGroupRequest) Reset() { + *x = AddDevicesToGroupRequest{} mi := &file_device_set_v1_device_set_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AddDevicesToDeviceSetRequest) String() string { +func (x *AddDevicesToGroupRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AddDevicesToDeviceSetRequest) ProtoMessage() {} +func (*AddDevicesToGroupRequest) ProtoMessage() {} -func (x *AddDevicesToDeviceSetRequest) ProtoReflect() protoreflect.Message { +func (x *AddDevicesToGroupRequest) ProtoReflect() protoreflect.Message { mi := &file_device_set_v1_device_set_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1493,53 +1493,49 @@ func (x *AddDevicesToDeviceSetRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AddDevicesToDeviceSetRequest.ProtoReflect.Descriptor instead. -func (*AddDevicesToDeviceSetRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use AddDevicesToGroupRequest.ProtoReflect.Descriptor instead. +func (*AddDevicesToGroupRequest) Descriptor() ([]byte, []int) { return file_device_set_v1_device_set_proto_rawDescGZIP(), []int{16} } -func (x *AddDevicesToDeviceSetRequest) GetDeviceSetId() int64 { +func (x *AddDevicesToGroupRequest) GetTargetGroupId() int64 { if x != nil { - return x.DeviceSetId + return x.TargetGroupId } return 0 } -func (x *AddDevicesToDeviceSetRequest) GetDeviceSelector() *v1.DeviceSelector { +func (x *AddDevicesToGroupRequest) GetDeviceSelector() *v1.DeviceSelector { if x != nil { return x.DeviceSelector } return nil } -// Response after adding devices to a device set -type AddDevicesToDeviceSetResponse struct { +// Response after adding devices to a group. +type AddDevicesToGroupResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - // ID of the device set devices were added to - DeviceSetId int64 `protobuf:"varint,1,opt,name=device_set_id,json=deviceSetId,proto3" json:"device_set_id,omitempty"` - // Number of devices successfully added - // May be less than requested if some devices were already members - AddedCount int32 `protobuf:"varint,2,opt,name=added_count,json=addedCount,proto3" json:"added_count,omitempty"` - // Number of devices whose site_id was rewritten by the cascade. - SiteReassignedCount int32 `protobuf:"varint,3,opt,name=site_reassigned_count,json=siteReassignedCount,proto3" json:"site_reassigned_count,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Number of devices successfully added. May be less than requested + // if some devices were already members. + AddedCount int64 `protobuf:"varint,1,opt,name=added_count,json=addedCount,proto3" json:"added_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *AddDevicesToDeviceSetResponse) Reset() { - *x = AddDevicesToDeviceSetResponse{} +func (x *AddDevicesToGroupResponse) Reset() { + *x = AddDevicesToGroupResponse{} mi := &file_device_set_v1_device_set_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AddDevicesToDeviceSetResponse) String() string { +func (x *AddDevicesToGroupResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AddDevicesToDeviceSetResponse) ProtoMessage() {} +func (*AddDevicesToGroupResponse) ProtoMessage() {} -func (x *AddDevicesToDeviceSetResponse) ProtoReflect() protoreflect.Message { +func (x *AddDevicesToGroupResponse) ProtoReflect() protoreflect.Message { mi := &file_device_set_v1_device_set_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1551,57 +1547,46 @@ func (x *AddDevicesToDeviceSetResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AddDevicesToDeviceSetResponse.ProtoReflect.Descriptor instead. -func (*AddDevicesToDeviceSetResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use AddDevicesToGroupResponse.ProtoReflect.Descriptor instead. +func (*AddDevicesToGroupResponse) Descriptor() ([]byte, []int) { return file_device_set_v1_device_set_proto_rawDescGZIP(), []int{17} } -func (x *AddDevicesToDeviceSetResponse) GetDeviceSetId() int64 { - if x != nil { - return x.DeviceSetId - } - return 0 -} - -func (x *AddDevicesToDeviceSetResponse) GetAddedCount() int32 { +func (x *AddDevicesToGroupResponse) GetAddedCount() int64 { if x != nil { return x.AddedCount } return 0 } -func (x *AddDevicesToDeviceSetResponse) GetSiteReassignedCount() int32 { - if x != nil { - return x.SiteReassignedCount - } - return 0 -} - -// Request to remove devices from a device set -type RemoveDevicesFromDeviceSetRequest struct { +// Request to remove devices from a group. Server rejects with +// InvalidArgument when target_group_id refers to a non-group device set; +// use AssignDevicesToRack to clear rack membership. +type RemoveDevicesFromGroupRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // ID of the device set to remove devices from - DeviceSetId int64 `protobuf:"varint,1,opt,name=device_set_id,json=deviceSetId,proto3" json:"device_set_id,omitempty"` - // Devices to remove: specific list or all paired devices + // ID of the group to remove devices from. Must reference a device + // set of type GROUP; racks are rejected. + TargetGroupId int64 `protobuf:"varint,1,opt,name=target_group_id,json=targetGroupId,proto3" json:"target_group_id,omitempty"` + // Devices to remove: specific list or all paired devices. DeviceSelector *v1.DeviceSelector `protobuf:"bytes,2,opt,name=device_selector,json=deviceSelector,proto3" json:"device_selector,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *RemoveDevicesFromDeviceSetRequest) Reset() { - *x = RemoveDevicesFromDeviceSetRequest{} +func (x *RemoveDevicesFromGroupRequest) Reset() { + *x = RemoveDevicesFromGroupRequest{} mi := &file_device_set_v1_device_set_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *RemoveDevicesFromDeviceSetRequest) String() string { +func (x *RemoveDevicesFromGroupRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RemoveDevicesFromDeviceSetRequest) ProtoMessage() {} +func (*RemoveDevicesFromGroupRequest) ProtoMessage() {} -func (x *RemoveDevicesFromDeviceSetRequest) ProtoReflect() protoreflect.Message { +func (x *RemoveDevicesFromGroupRequest) ProtoReflect() protoreflect.Message { mi := &file_device_set_v1_device_set_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1613,48 +1598,48 @@ func (x *RemoveDevicesFromDeviceSetRequest) ProtoReflect() protoreflect.Message return mi.MessageOf(x) } -// Deprecated: Use RemoveDevicesFromDeviceSetRequest.ProtoReflect.Descriptor instead. -func (*RemoveDevicesFromDeviceSetRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use RemoveDevicesFromGroupRequest.ProtoReflect.Descriptor instead. +func (*RemoveDevicesFromGroupRequest) Descriptor() ([]byte, []int) { return file_device_set_v1_device_set_proto_rawDescGZIP(), []int{18} } -func (x *RemoveDevicesFromDeviceSetRequest) GetDeviceSetId() int64 { +func (x *RemoveDevicesFromGroupRequest) GetTargetGroupId() int64 { if x != nil { - return x.DeviceSetId + return x.TargetGroupId } return 0 } -func (x *RemoveDevicesFromDeviceSetRequest) GetDeviceSelector() *v1.DeviceSelector { +func (x *RemoveDevicesFromGroupRequest) GetDeviceSelector() *v1.DeviceSelector { if x != nil { return x.DeviceSelector } return nil } -// Response after removing devices from a device set -type RemoveDevicesFromDeviceSetResponse struct { +// Response after removing devices from a group. +type RemoveDevicesFromGroupResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - // Number of devices successfully removed - RemovedCount int32 `protobuf:"varint,1,opt,name=removed_count,json=removedCount,proto3" json:"removed_count,omitempty"` + // Number of devices successfully removed. + RemovedCount int64 `protobuf:"varint,1,opt,name=removed_count,json=removedCount,proto3" json:"removed_count,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *RemoveDevicesFromDeviceSetResponse) Reset() { - *x = RemoveDevicesFromDeviceSetResponse{} +func (x *RemoveDevicesFromGroupResponse) Reset() { + *x = RemoveDevicesFromGroupResponse{} mi := &file_device_set_v1_device_set_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *RemoveDevicesFromDeviceSetResponse) String() string { +func (x *RemoveDevicesFromGroupResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RemoveDevicesFromDeviceSetResponse) ProtoMessage() {} +func (*RemoveDevicesFromGroupResponse) ProtoMessage() {} -func (x *RemoveDevicesFromDeviceSetResponse) ProtoReflect() protoreflect.Message { +func (x *RemoveDevicesFromGroupResponse) ProtoReflect() protoreflect.Message { mi := &file_device_set_v1_device_set_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1666,12 +1651,12 @@ func (x *RemoveDevicesFromDeviceSetResponse) ProtoReflect() protoreflect.Message return mi.MessageOf(x) } -// Deprecated: Use RemoveDevicesFromDeviceSetResponse.ProtoReflect.Descriptor instead. -func (*RemoveDevicesFromDeviceSetResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use RemoveDevicesFromGroupResponse.ProtoReflect.Descriptor instead. +func (*RemoveDevicesFromGroupResponse) Descriptor() ([]byte, []int) { return file_device_set_v1_device_set_proto_rawDescGZIP(), []int{19} } -func (x *RemoveDevicesFromDeviceSetResponse) GetRemovedCount() int32 { +func (x *RemoveDevicesFromGroupResponse) GetRemovedCount() int64 { if x != nil { return x.RemovedCount } @@ -3103,10 +3088,15 @@ type AssignDevicesToRackRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Unset = clear rack membership without re-assigning. When // present, must be > 0. - TargetRackId *int64 `protobuf:"varint,1,opt,name=target_rack_id,json=targetRackId,proto3,oneof" json:"target_rack_id,omitempty"` - DeviceIdentifiers []string `protobuf:"bytes,2,rep,name=device_identifiers,json=deviceIdentifiers,proto3" json:"device_identifiers,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + TargetRackId *int64 `protobuf:"varint,1,opt,name=target_rack_id,json=targetRackId,proto3,oneof" json:"target_rack_id,omitempty"` + // Devices to assign. Only the device_list variant is accepted; the + // all_devices variant is rejected with InvalidArgument because + // moving every paired device into a single rack is never the + // intended operation. Filter-based selectors are reserved for a + // future expansion. + DeviceSelector *v1.DeviceSelector `protobuf:"bytes,2,opt,name=device_selector,json=deviceSelector,proto3" json:"device_selector,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AssignDevicesToRackRequest) Reset() { @@ -3146,9 +3136,9 @@ func (x *AssignDevicesToRackRequest) GetTargetRackId() int64 { return 0 } -func (x *AssignDevicesToRackRequest) GetDeviceIdentifiers() []string { +func (x *AssignDevicesToRackRequest) GetDeviceSelector() *v1.DeviceSelector { if x != nil { - return x.DeviceIdentifiers + return x.DeviceSelector } return nil } @@ -3424,439 +3414,432 @@ var file_device_set_v1_device_set_proto_rawDesc = string([]byte{ 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x97, 0x01, - 0x0a, 0x1c, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, - 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0b, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x4a, 0x0a, 0x0f, 0x64, + 0x0a, 0x18, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x0f, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0d, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x4a, 0x0a, 0x0f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, - 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x98, 0x01, 0x0a, 0x1d, 0x41, 0x64, 0x64, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x65, 0x76, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x3c, 0x0a, 0x19, 0x41, 0x64, 0x64, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x64, 0x64, 0x65, 0x64, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x9c, 0x01, 0x0a, 0x1d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x4a, 0x0a, 0x0f, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x06, 0xba, 0x48, + 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x22, 0x45, 0x0a, 0x1e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8f, 0x01, 0x0a, 0x1b, + 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x0d, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xba, 0x48, 0x04, + 0x1a, 0x02, 0x28, 0x00, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x80, 0x01, + 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x4d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, + 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1e, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, + 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, + 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x22, 0x84, 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x34, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, + 0x02, 0x10, 0x01, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x58, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, + 0x73, 0x22, 0xc4, 0x01, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, + 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2b, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, + 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x34, 0x0a, + 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, + 0x01, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x12, 0x43, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6e, 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x52, + 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x04, 0x73, + 0x6c, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, + 0x6f, 0x74, 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x22, 0x81, 0x01, 0x0a, 0x1c, 0x43, 0x6c, 0x65, + 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, - 0x0b, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0a, 0x61, 0x64, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, - 0x0a, 0x15, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x73, - 0x69, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x22, 0x9c, 0x01, 0x0a, 0x21, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, - 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x4a, 0x0a, 0x0f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, - 0x01, 0x52, 0x0e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x22, 0x49, 0x0a, 0x22, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, - 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8f, 0x01, 0x0a, - 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x0d, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0b, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x09, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xba, 0x48, - 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, - 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x80, - 0x01, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, - 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x38, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1e, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, - 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, - 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0x84, 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x34, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, - 0x72, 0x02, 0x10, 0x01, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x58, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, - 0x74, 0x73, 0x22, 0xc4, 0x01, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, - 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2b, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, - 0x00, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x34, - 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, - 0x69, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, - 0x10, 0x01, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x12, 0x43, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, - 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6e, 0x0a, 0x1b, 0x53, 0x65, 0x74, - 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x04, - 0x73, 0x6c, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, - 0x6c, 0x6f, 0x74, 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x22, 0x81, 0x01, 0x0a, 0x1c, 0x43, 0x6c, - 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x0d, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x34, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x10, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x22, 0x1f, 0x0a, - 0x1d, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x42, - 0x0a, 0x13, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, - 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, - 0x49, 0x64, 0x22, 0x74, 0x0a, 0x08, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x12, 0x2b, - 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, - 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x08, 0x70, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, - 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x45, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, - 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x6c, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x17, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x05, 0x73, 0x6c, 0x6f, 0x74, 0x73, 0x22, - 0xde, 0x07, 0x0a, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, - 0x74, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x72, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x54, 0x68, 0x73, - 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x67, 0x5f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, - 0x63, 0x79, 0x5f, 0x6a, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, - 0x67, 0x45, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x74, 0x68, 0x12, 0x24, - 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6b, 0x77, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x77, - 0x65, 0x72, 0x4b, 0x77, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x65, 0x6d, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x0f, 0x6d, 0x69, 0x6e, 0x54, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, - 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x5f, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x6d, 0x61, 0x78, - 0x54, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x12, 0x23, 0x0a, 0x0d, - 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, 0x66, - 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, 0x65, - 0x65, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x66, - 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, - 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, - 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6f, 0x77, 0x65, - 0x72, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3e, 0x0a, 0x1b, - 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x19, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x73, - 0x73, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x16, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x73, 0x73, - 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x61, 0x6e, 0x5f, 0x69, - 0x73, 0x73, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0d, 0x66, 0x61, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x33, 0x0a, 0x16, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x73, - 0x73, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x13, 0x68, 0x61, 0x73, 0x68, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x73, 0x75, 0x5f, 0x69, 0x73, 0x73, 0x75, - 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x70, - 0x73, 0x75, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0d, - 0x73, 0x6c, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x15, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x0c, 0x73, 0x6c, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, - 0x22, 0x40, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0e, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x03, 0x52, 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, - 0x64, 0x73, 0x22, 0x50, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, - 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x33, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, - 0x74, 0x61, 0x74, 0x73, 0x22, 0x73, 0x0a, 0x0e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x6f, 0x77, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x03, 0x72, 0x6f, 0x77, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x75, - 0x6d, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, - 0x12, 0x37, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1f, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0x2d, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x7a, 0x6f, - 0x6e, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, - 0x22, 0x19, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, - 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x44, 0x0a, 0x18, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x76, 0x31, 0x2e, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, 0x52, 0x05, 0x7a, 0x6f, 0x6e, 0x65, - 0x73, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x08, 0x52, 0x61, 0x63, - 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6c, - 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x6f, 0x6c, 0x75, - 0x6d, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x22, 0x4f, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, - 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0a, 0x72, - 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x17, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x54, 0x79, - 0x70, 0x65, 0x73, 0x22, 0xca, 0x02, 0x0a, 0x0f, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0a, - 0xba, 0x48, 0x07, 0xd8, 0x01, 0x01, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x05, - 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0c, 0xba, 0x48, 0x09, - 0xc8, 0x01, 0x01, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, - 0x12, 0x3c, 0x0a, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x06, 0xba, 0x48, - 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, 0x72, 0x61, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4a, - 0x0a, 0x0f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0e, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x42, 0x0a, 0x10, 0x73, 0x6c, - 0x6f, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x0f, 0x73, - 0x6c, 0x6f, 0x74, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x10, - 0x0a, 0x0e, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, - 0x22, 0xa6, 0x01, 0x0a, 0x10, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x53, 0x65, 0x74, 0x52, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x25, - 0x0a, 0x0e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x72, 0x65, - 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xa8, 0x01, 0x0a, 0x1a, 0x41, 0x73, - 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x61, 0x63, - 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x43, 0x0a, 0x12, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, - 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, 0x14, 0xba, 0x48, 0x11, 0x92, 0x01, 0x0e, - 0x08, 0x01, 0x10, 0x90, 0x4e, 0x22, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x02, 0x52, 0x11, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x61, 0x63, - 0x6b, 0x5f, 0x69, 0x64, 0x22, 0x9d, 0x01, 0x0a, 0x1b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x73, - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x73, - 0x69, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x73, 0x69, 0x74, 0x65, - 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0x65, 0x0a, 0x0d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, - 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, - 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, - 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50, 0x10, - 0x01, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x45, 0x54, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x41, 0x43, 0x4b, 0x10, 0x02, 0x2a, 0xb6, 0x01, 0x0a, 0x0e, - 0x52, 0x61, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, - 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, - 0x45, 0x58, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, - 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, 0x4f, 0x4d, 0x5f, 0x4c, 0x45, 0x46, 0x54, - 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, - 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, 0x4f, 0x50, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x10, - 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, - 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, 0x4f, 0x4d, 0x5f, 0x52, 0x49, 0x47, - 0x48, 0x54, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, - 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, 0x4f, 0x50, 0x5f, 0x52, 0x49, 0x47, - 0x48, 0x54, 0x10, 0x04, 0x2a, 0x70, 0x0a, 0x0f, 0x52, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x6f, 0x6c, - 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, 0x4b, 0x5f, - 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x41, - 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x41, 0x49, 0x52, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, - 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4d, 0x4d, 0x45, 0x52, - 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x2a, 0xdd, 0x01, 0x0a, 0x10, 0x53, 0x6c, 0x6f, 0x74, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x1e, 0x53, + 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x34, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x22, 0x1f, 0x0a, 0x1d, + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x42, 0x0a, + 0x13, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, + 0x22, 0x02, 0x20, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, + 0x64, 0x22, 0x74, 0x0a, 0x08, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x12, 0x2b, 0x0a, + 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x08, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, + 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x45, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x61, + 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2d, 0x0a, 0x05, 0x73, 0x6c, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x05, 0x73, 0x6c, 0x6f, 0x74, 0x73, 0x22, 0xde, + 0x07, 0x0a, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, + 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, + 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x67, 0x5f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, + 0x79, 0x5f, 0x6a, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, + 0x45, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, + 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, + 0x72, 0x4b, 0x77, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, + 0x6d, 0x69, 0x6e, 0x54, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x12, + 0x2a, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x5f, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x6d, 0x61, 0x78, 0x54, + 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x12, 0x23, 0x0a, 0x0d, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, + 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, 0x65, 0x65, + 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x66, 0x66, + 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, + 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x65, + 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6f, 0x77, 0x65, 0x72, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3e, 0x0a, 0x1b, 0x74, + 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x19, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x73, 0x73, + 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x73, 0x73, 0x75, + 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x61, 0x6e, 0x5f, 0x69, 0x73, + 0x73, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0d, 0x66, 0x61, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x33, + 0x0a, 0x16, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x73, 0x73, + 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, + 0x68, 0x61, 0x73, 0x68, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x73, 0x75, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x70, 0x73, + 0x75, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x73, + 0x6c, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x15, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x0c, 0x73, 0x6c, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x22, + 0x40, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x03, 0x52, 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, + 0x73, 0x22, 0x50, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x22, 0x73, 0x0a, 0x0e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x03, 0x72, 0x6f, 0x77, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, + 0x37, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1f, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x6c, 0x6f, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0x2d, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x7a, 0x6f, 0x6e, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x22, + 0x19, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, + 0x65, 0x66, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x44, 0x0a, 0x18, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, 0x52, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, + 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x08, 0x52, 0x61, 0x63, 0x6b, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, + 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x22, 0x4f, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0a, 0x72, 0x61, + 0x63, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, + 0x65, 0x73, 0x22, 0xca, 0x02, 0x0a, 0x0f, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x0d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0a, 0xba, + 0x48, 0x07, 0xd8, 0x01, 0x01, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0c, 0xba, 0x48, 0x09, 0xc8, + 0x01, 0x01, 0x72, 0x04, 0x10, 0x01, 0x18, 0x64, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, + 0x3c, 0x0a, 0x09, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x06, 0xba, 0x48, 0x03, + 0xc8, 0x01, 0x01, 0x52, 0x08, 0x72, 0x61, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4a, 0x0a, + 0x0f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0e, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x42, 0x0a, 0x10, 0x73, 0x6c, 0x6f, + 0x74, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x0f, 0x73, 0x6c, + 0x6f, 0x74, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x10, 0x0a, + 0x0e, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x22, + 0xa6, 0x01, 0x0a, 0x10, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, + 0x65, 0x74, 0x52, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x25, 0x0a, + 0x0e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x61, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x13, 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xaf, 0x01, 0x0a, 0x1a, 0x41, 0x73, 0x73, + 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x61, 0x63, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, + 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x4a, 0x0a, 0x0f, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, + 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x5f, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x22, 0x9d, 0x01, 0x0a, 0x1b, 0x41, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x61, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x73, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0d, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x13, 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0x65, 0x0a, 0x0d, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x44, + 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, + 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x47, 0x52, 0x4f, 0x55, 0x50, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x56, 0x49, 0x43, + 0x45, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x41, 0x43, 0x4b, 0x10, + 0x02, 0x2a, 0xb6, 0x01, 0x0a, 0x0e, 0x52, 0x61, 0x63, 0x6b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, + 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x4f, + 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, 0x4f, + 0x4d, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x41, 0x43, 0x4b, + 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, 0x4f, 0x50, + 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x41, 0x43, 0x4b, 0x5f, + 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x42, 0x4f, 0x54, 0x54, + 0x4f, 0x4d, 0x5f, 0x52, 0x49, 0x47, 0x48, 0x54, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x41, + 0x43, 0x4b, 0x5f, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x54, + 0x4f, 0x50, 0x5f, 0x52, 0x49, 0x47, 0x48, 0x54, 0x10, 0x04, 0x2a, 0x70, 0x0a, 0x0f, 0x52, 0x61, + 0x63, 0x6b, 0x43, 0x6f, 0x6f, 0x6c, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, + 0x1d, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x19, 0x0a, 0x15, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x49, 0x52, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x52, + 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4f, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x49, 0x4d, 0x4d, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x2a, 0xdd, 0x01, 0x0a, + 0x10, 0x53, 0x6c, 0x6f, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, + 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, + 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x4d, 0x50, 0x54, + 0x59, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, + 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, + 0x59, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, + 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4e, 0x45, 0x45, 0x44, 0x53, 0x5f, + 0x41, 0x54, 0x54, 0x45, 0x4e, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, - 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x1c, 0x0a, 0x18, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, - 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x10, 0x01, 0x12, 0x1e, 0x0a, - 0x1a, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x02, 0x12, 0x26, 0x0a, - 0x22, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x4e, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x41, 0x54, 0x54, 0x45, 0x4e, 0x54, - 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, - 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4f, 0x46, 0x46, 0x4c, - 0x49, 0x4e, 0x45, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, - 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x4c, 0x45, 0x45, - 0x50, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x32, 0xc2, 0x0e, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x53, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x25, - 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, - 0x0c, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x22, 0x2e, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x25, 0x2e, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x25, 0x2e, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, - 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x0e, 0x4c, 0x69, - 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, + 0x53, 0x5f, 0x4f, 0x46, 0x46, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x53, + 0x4c, 0x4f, 0x54, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x53, 0x4c, 0x45, 0x45, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x32, 0xa9, 0x0e, 0x0a, + 0x10, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x53, 0x65, 0x74, 0x12, 0x25, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x53, 0x65, 0x74, 0x12, 0x22, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x0f, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, + 0x25, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, + 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, + 0x74, 0x12, 0x25, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x5d, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, + 0x74, 0x73, 0x12, 0x24, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x41, 0x64, 0x64, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, - 0x65, 0x74, 0x12, 0x2b, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2c, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x81, 0x01, - 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, - 0x72, 0x6f, 0x6d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x30, 0x2e, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, - 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, - 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x6c, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, - 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, - 0x0a, 0x15, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, - 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, - 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, - 0x74, 0x73, 0x12, 0x22, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, - 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x47, - 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x12, 0x27, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, - 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, - 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x63, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, - 0x65, 0x66, 0x73, 0x12, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, - 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, - 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, - 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x12, 0x1e, 0x2e, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x76, - 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x76, - 0x65, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, - 0x13, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, - 0x52, 0x61, 0x63, 0x6b, 0x12, 0x29, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x54, 0x6f, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x66, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x12, 0x27, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, + 0x6f, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, + 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x12, 0x2c, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, + 0x72, 0x6f, 0x6d, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2d, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x46, 0x72, 0x6f, + 0x6d, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, + 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x4d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, + 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x6c, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, + 0x13, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, + 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x52, - 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xc3, 0x01, 0x0a, 0x11, - 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x42, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x50, 0x01, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, - 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, - 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, - 0x65, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, - 0x76, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x53, 0x65, 0x74, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x53, 0x65, 0x74, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, - 0x65, 0x74, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0xea, 0x02, 0x0d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x3a, 0x3a, 0x56, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x53, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x43, + 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, + 0x6f, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2c, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x50, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x57, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x12, + 0x22, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x53, 0x6c, 0x6f, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, + 0x73, 0x12, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, + 0x6f, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, 0x73, + 0x12, 0x26, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, + 0x6b, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, + 0x65, 0x73, 0x12, 0x23, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x6b, + 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, + 0x08, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, 0x63, 0x6b, 0x12, 0x1e, 0x2e, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, + 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x52, 0x61, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x41, 0x73, + 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x61, 0x63, + 0x6b, 0x12, 0x29, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, + 0x6f, 0x52, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, + 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x61, 0x63, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xc3, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, + 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x42, 0x0e, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, + 0x67, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x2f, + 0x76, 0x31, 0x3b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x76, 0x31, 0xa2, + 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, + 0x74, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, + 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x5c, + 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, + 0x0d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -3874,62 +3857,62 @@ func file_device_set_v1_device_set_proto_rawDescGZIP() []byte { var file_device_set_v1_device_set_proto_enumTypes = make([]protoimpl.EnumInfo, 4) var file_device_set_v1_device_set_proto_msgTypes = make([]protoimpl.MessageInfo, 46) var file_device_set_v1_device_set_proto_goTypes = []any{ - (DeviceSetType)(0), // 0: device_set.v1.DeviceSetType - (RackOrderIndex)(0), // 1: device_set.v1.RackOrderIndex - (RackCoolingType)(0), // 2: device_set.v1.RackCoolingType - (SlotDeviceStatus)(0), // 3: device_set.v1.SlotDeviceStatus - (*DeviceSet)(nil), // 4: device_set.v1.DeviceSet - (*RackInfo)(nil), // 5: device_set.v1.RackInfo - (*GroupInfo)(nil), // 6: device_set.v1.GroupInfo - (*DeviceSetMember)(nil), // 7: device_set.v1.DeviceSetMember - (*RackMemberDetails)(nil), // 8: device_set.v1.RackMemberDetails - (*RackSlotPosition)(nil), // 9: device_set.v1.RackSlotPosition - (*CreateDeviceSetRequest)(nil), // 10: device_set.v1.CreateDeviceSetRequest - (*CreateDeviceSetResponse)(nil), // 11: device_set.v1.CreateDeviceSetResponse - (*GetDeviceSetRequest)(nil), // 12: device_set.v1.GetDeviceSetRequest - (*GetDeviceSetResponse)(nil), // 13: device_set.v1.GetDeviceSetResponse - (*UpdateDeviceSetRequest)(nil), // 14: device_set.v1.UpdateDeviceSetRequest - (*UpdateDeviceSetResponse)(nil), // 15: device_set.v1.UpdateDeviceSetResponse - (*DeleteDeviceSetRequest)(nil), // 16: device_set.v1.DeleteDeviceSetRequest - (*DeleteDeviceSetResponse)(nil), // 17: device_set.v1.DeleteDeviceSetResponse - (*ListDeviceSetsRequest)(nil), // 18: device_set.v1.ListDeviceSetsRequest - (*ListDeviceSetsResponse)(nil), // 19: device_set.v1.ListDeviceSetsResponse - (*AddDevicesToDeviceSetRequest)(nil), // 20: device_set.v1.AddDevicesToDeviceSetRequest - (*AddDevicesToDeviceSetResponse)(nil), // 21: device_set.v1.AddDevicesToDeviceSetResponse - (*RemoveDevicesFromDeviceSetRequest)(nil), // 22: device_set.v1.RemoveDevicesFromDeviceSetRequest - (*RemoveDevicesFromDeviceSetResponse)(nil), // 23: device_set.v1.RemoveDevicesFromDeviceSetResponse - (*ListDeviceSetMembersRequest)(nil), // 24: device_set.v1.ListDeviceSetMembersRequest - (*ListDeviceSetMembersResponse)(nil), // 25: device_set.v1.ListDeviceSetMembersResponse - (*GetDeviceDeviceSetsRequest)(nil), // 26: device_set.v1.GetDeviceDeviceSetsRequest - (*GetDeviceDeviceSetsResponse)(nil), // 27: device_set.v1.GetDeviceDeviceSetsResponse - (*SetRackSlotPositionRequest)(nil), // 28: device_set.v1.SetRackSlotPositionRequest - (*SetRackSlotPositionResponse)(nil), // 29: device_set.v1.SetRackSlotPositionResponse - (*ClearRackSlotPositionRequest)(nil), // 30: device_set.v1.ClearRackSlotPositionRequest - (*ClearRackSlotPositionResponse)(nil), // 31: device_set.v1.ClearRackSlotPositionResponse - (*GetRackSlotsRequest)(nil), // 32: device_set.v1.GetRackSlotsRequest - (*RackSlot)(nil), // 33: device_set.v1.RackSlot - (*GetRackSlotsResponse)(nil), // 34: device_set.v1.GetRackSlotsResponse - (*DeviceSetStats)(nil), // 35: device_set.v1.DeviceSetStats - (*GetDeviceSetStatsRequest)(nil), // 36: device_set.v1.GetDeviceSetStatsRequest - (*GetDeviceSetStatsResponse)(nil), // 37: device_set.v1.GetDeviceSetStatsResponse - (*RackSlotStatus)(nil), // 38: device_set.v1.RackSlotStatus - (*ListRackZonesRequest)(nil), // 39: device_set.v1.ListRackZonesRequest - (*ListRackZonesResponse)(nil), // 40: device_set.v1.ListRackZonesResponse - (*ListRackZoneRefsRequest)(nil), // 41: device_set.v1.ListRackZoneRefsRequest - (*ListRackZoneRefsResponse)(nil), // 42: device_set.v1.ListRackZoneRefsResponse - (*ListRackTypesRequest)(nil), // 43: device_set.v1.ListRackTypesRequest - (*RackType)(nil), // 44: device_set.v1.RackType - (*ListRackTypesResponse)(nil), // 45: device_set.v1.ListRackTypesResponse - (*SaveRackRequest)(nil), // 46: device_set.v1.SaveRackRequest - (*SaveRackResponse)(nil), // 47: device_set.v1.SaveRackResponse - (*AssignDevicesToRackRequest)(nil), // 48: device_set.v1.AssignDevicesToRackRequest - (*AssignDevicesToRackResponse)(nil), // 49: device_set.v1.AssignDevicesToRackResponse - (*timestamppb.Timestamp)(nil), // 50: google.protobuf.Timestamp - (*v1.DeviceSelector)(nil), // 51: common.v1.DeviceSelector - (*v1.SortConfig)(nil), // 52: common.v1.SortConfig - (v11.ComponentType)(0), // 53: errors.v1.ComponentType - (*v1.ZoneKey)(nil), // 54: common.v1.ZoneKey - (*v1.ZoneRef)(nil), // 55: common.v1.ZoneRef + (DeviceSetType)(0), // 0: device_set.v1.DeviceSetType + (RackOrderIndex)(0), // 1: device_set.v1.RackOrderIndex + (RackCoolingType)(0), // 2: device_set.v1.RackCoolingType + (SlotDeviceStatus)(0), // 3: device_set.v1.SlotDeviceStatus + (*DeviceSet)(nil), // 4: device_set.v1.DeviceSet + (*RackInfo)(nil), // 5: device_set.v1.RackInfo + (*GroupInfo)(nil), // 6: device_set.v1.GroupInfo + (*DeviceSetMember)(nil), // 7: device_set.v1.DeviceSetMember + (*RackMemberDetails)(nil), // 8: device_set.v1.RackMemberDetails + (*RackSlotPosition)(nil), // 9: device_set.v1.RackSlotPosition + (*CreateDeviceSetRequest)(nil), // 10: device_set.v1.CreateDeviceSetRequest + (*CreateDeviceSetResponse)(nil), // 11: device_set.v1.CreateDeviceSetResponse + (*GetDeviceSetRequest)(nil), // 12: device_set.v1.GetDeviceSetRequest + (*GetDeviceSetResponse)(nil), // 13: device_set.v1.GetDeviceSetResponse + (*UpdateDeviceSetRequest)(nil), // 14: device_set.v1.UpdateDeviceSetRequest + (*UpdateDeviceSetResponse)(nil), // 15: device_set.v1.UpdateDeviceSetResponse + (*DeleteDeviceSetRequest)(nil), // 16: device_set.v1.DeleteDeviceSetRequest + (*DeleteDeviceSetResponse)(nil), // 17: device_set.v1.DeleteDeviceSetResponse + (*ListDeviceSetsRequest)(nil), // 18: device_set.v1.ListDeviceSetsRequest + (*ListDeviceSetsResponse)(nil), // 19: device_set.v1.ListDeviceSetsResponse + (*AddDevicesToGroupRequest)(nil), // 20: device_set.v1.AddDevicesToGroupRequest + (*AddDevicesToGroupResponse)(nil), // 21: device_set.v1.AddDevicesToGroupResponse + (*RemoveDevicesFromGroupRequest)(nil), // 22: device_set.v1.RemoveDevicesFromGroupRequest + (*RemoveDevicesFromGroupResponse)(nil), // 23: device_set.v1.RemoveDevicesFromGroupResponse + (*ListDeviceSetMembersRequest)(nil), // 24: device_set.v1.ListDeviceSetMembersRequest + (*ListDeviceSetMembersResponse)(nil), // 25: device_set.v1.ListDeviceSetMembersResponse + (*GetDeviceDeviceSetsRequest)(nil), // 26: device_set.v1.GetDeviceDeviceSetsRequest + (*GetDeviceDeviceSetsResponse)(nil), // 27: device_set.v1.GetDeviceDeviceSetsResponse + (*SetRackSlotPositionRequest)(nil), // 28: device_set.v1.SetRackSlotPositionRequest + (*SetRackSlotPositionResponse)(nil), // 29: device_set.v1.SetRackSlotPositionResponse + (*ClearRackSlotPositionRequest)(nil), // 30: device_set.v1.ClearRackSlotPositionRequest + (*ClearRackSlotPositionResponse)(nil), // 31: device_set.v1.ClearRackSlotPositionResponse + (*GetRackSlotsRequest)(nil), // 32: device_set.v1.GetRackSlotsRequest + (*RackSlot)(nil), // 33: device_set.v1.RackSlot + (*GetRackSlotsResponse)(nil), // 34: device_set.v1.GetRackSlotsResponse + (*DeviceSetStats)(nil), // 35: device_set.v1.DeviceSetStats + (*GetDeviceSetStatsRequest)(nil), // 36: device_set.v1.GetDeviceSetStatsRequest + (*GetDeviceSetStatsResponse)(nil), // 37: device_set.v1.GetDeviceSetStatsResponse + (*RackSlotStatus)(nil), // 38: device_set.v1.RackSlotStatus + (*ListRackZonesRequest)(nil), // 39: device_set.v1.ListRackZonesRequest + (*ListRackZonesResponse)(nil), // 40: device_set.v1.ListRackZonesResponse + (*ListRackZoneRefsRequest)(nil), // 41: device_set.v1.ListRackZoneRefsRequest + (*ListRackZoneRefsResponse)(nil), // 42: device_set.v1.ListRackZoneRefsResponse + (*ListRackTypesRequest)(nil), // 43: device_set.v1.ListRackTypesRequest + (*RackType)(nil), // 44: device_set.v1.RackType + (*ListRackTypesResponse)(nil), // 45: device_set.v1.ListRackTypesResponse + (*SaveRackRequest)(nil), // 46: device_set.v1.SaveRackRequest + (*SaveRackResponse)(nil), // 47: device_set.v1.SaveRackResponse + (*AssignDevicesToRackRequest)(nil), // 48: device_set.v1.AssignDevicesToRackRequest + (*AssignDevicesToRackResponse)(nil), // 49: device_set.v1.AssignDevicesToRackResponse + (*timestamppb.Timestamp)(nil), // 50: google.protobuf.Timestamp + (*v1.DeviceSelector)(nil), // 51: common.v1.DeviceSelector + (*v1.SortConfig)(nil), // 52: common.v1.SortConfig + (v11.ComponentType)(0), // 53: errors.v1.ComponentType + (*v1.ZoneKey)(nil), // 54: common.v1.ZoneKey + (*v1.ZoneRef)(nil), // 55: common.v1.ZoneRef } var file_device_set_v1_device_set_proto_depIdxs = []int32{ 0, // 0: device_set.v1.DeviceSet.type:type_name -> device_set.v1.DeviceSetType @@ -3957,8 +3940,8 @@ var file_device_set_v1_device_set_proto_depIdxs = []int32{ 53, // 22: device_set.v1.ListDeviceSetsRequest.error_component_types:type_name -> errors.v1.ComponentType 54, // 23: device_set.v1.ListDeviceSetsRequest.zone_keys:type_name -> common.v1.ZoneKey 4, // 24: device_set.v1.ListDeviceSetsResponse.device_sets:type_name -> device_set.v1.DeviceSet - 51, // 25: device_set.v1.AddDevicesToDeviceSetRequest.device_selector:type_name -> common.v1.DeviceSelector - 51, // 26: device_set.v1.RemoveDevicesFromDeviceSetRequest.device_selector:type_name -> common.v1.DeviceSelector + 51, // 25: device_set.v1.AddDevicesToGroupRequest.device_selector:type_name -> common.v1.DeviceSelector + 51, // 26: device_set.v1.RemoveDevicesFromGroupRequest.device_selector:type_name -> common.v1.DeviceSelector 7, // 27: device_set.v1.ListDeviceSetMembersResponse.members:type_name -> device_set.v1.DeviceSetMember 0, // 28: device_set.v1.GetDeviceDeviceSetsRequest.type:type_name -> device_set.v1.DeviceSetType 4, // 29: device_set.v1.GetDeviceDeviceSetsResponse.device_sets:type_name -> device_set.v1.DeviceSet @@ -3975,47 +3958,48 @@ var file_device_set_v1_device_set_proto_depIdxs = []int32{ 51, // 40: device_set.v1.SaveRackRequest.device_selector:type_name -> common.v1.DeviceSelector 33, // 41: device_set.v1.SaveRackRequest.slot_assignments:type_name -> device_set.v1.RackSlot 4, // 42: device_set.v1.SaveRackResponse.device_set:type_name -> device_set.v1.DeviceSet - 10, // 43: device_set.v1.DeviceSetService.CreateDeviceSet:input_type -> device_set.v1.CreateDeviceSetRequest - 12, // 44: device_set.v1.DeviceSetService.GetDeviceSet:input_type -> device_set.v1.GetDeviceSetRequest - 14, // 45: device_set.v1.DeviceSetService.UpdateDeviceSet:input_type -> device_set.v1.UpdateDeviceSetRequest - 16, // 46: device_set.v1.DeviceSetService.DeleteDeviceSet:input_type -> device_set.v1.DeleteDeviceSetRequest - 18, // 47: device_set.v1.DeviceSetService.ListDeviceSets:input_type -> device_set.v1.ListDeviceSetsRequest - 20, // 48: device_set.v1.DeviceSetService.AddDevicesToDeviceSet:input_type -> device_set.v1.AddDevicesToDeviceSetRequest - 22, // 49: device_set.v1.DeviceSetService.RemoveDevicesFromDeviceSet:input_type -> device_set.v1.RemoveDevicesFromDeviceSetRequest - 24, // 50: device_set.v1.DeviceSetService.ListDeviceSetMembers:input_type -> device_set.v1.ListDeviceSetMembersRequest - 26, // 51: device_set.v1.DeviceSetService.GetDeviceDeviceSets:input_type -> device_set.v1.GetDeviceDeviceSetsRequest - 28, // 52: device_set.v1.DeviceSetService.SetRackSlotPosition:input_type -> device_set.v1.SetRackSlotPositionRequest - 30, // 53: device_set.v1.DeviceSetService.ClearRackSlotPosition:input_type -> device_set.v1.ClearRackSlotPositionRequest - 32, // 54: device_set.v1.DeviceSetService.GetRackSlots:input_type -> device_set.v1.GetRackSlotsRequest - 36, // 55: device_set.v1.DeviceSetService.GetDeviceSetStats:input_type -> device_set.v1.GetDeviceSetStatsRequest - 39, // 56: device_set.v1.DeviceSetService.ListRackZones:input_type -> device_set.v1.ListRackZonesRequest - 41, // 57: device_set.v1.DeviceSetService.ListRackZoneRefs:input_type -> device_set.v1.ListRackZoneRefsRequest - 43, // 58: device_set.v1.DeviceSetService.ListRackTypes:input_type -> device_set.v1.ListRackTypesRequest - 46, // 59: device_set.v1.DeviceSetService.SaveRack:input_type -> device_set.v1.SaveRackRequest - 48, // 60: device_set.v1.DeviceSetService.AssignDevicesToRack:input_type -> device_set.v1.AssignDevicesToRackRequest - 11, // 61: device_set.v1.DeviceSetService.CreateDeviceSet:output_type -> device_set.v1.CreateDeviceSetResponse - 13, // 62: device_set.v1.DeviceSetService.GetDeviceSet:output_type -> device_set.v1.GetDeviceSetResponse - 15, // 63: device_set.v1.DeviceSetService.UpdateDeviceSet:output_type -> device_set.v1.UpdateDeviceSetResponse - 17, // 64: device_set.v1.DeviceSetService.DeleteDeviceSet:output_type -> device_set.v1.DeleteDeviceSetResponse - 19, // 65: device_set.v1.DeviceSetService.ListDeviceSets:output_type -> device_set.v1.ListDeviceSetsResponse - 21, // 66: device_set.v1.DeviceSetService.AddDevicesToDeviceSet:output_type -> device_set.v1.AddDevicesToDeviceSetResponse - 23, // 67: device_set.v1.DeviceSetService.RemoveDevicesFromDeviceSet:output_type -> device_set.v1.RemoveDevicesFromDeviceSetResponse - 25, // 68: device_set.v1.DeviceSetService.ListDeviceSetMembers:output_type -> device_set.v1.ListDeviceSetMembersResponse - 27, // 69: device_set.v1.DeviceSetService.GetDeviceDeviceSets:output_type -> device_set.v1.GetDeviceDeviceSetsResponse - 29, // 70: device_set.v1.DeviceSetService.SetRackSlotPosition:output_type -> device_set.v1.SetRackSlotPositionResponse - 31, // 71: device_set.v1.DeviceSetService.ClearRackSlotPosition:output_type -> device_set.v1.ClearRackSlotPositionResponse - 34, // 72: device_set.v1.DeviceSetService.GetRackSlots:output_type -> device_set.v1.GetRackSlotsResponse - 37, // 73: device_set.v1.DeviceSetService.GetDeviceSetStats:output_type -> device_set.v1.GetDeviceSetStatsResponse - 40, // 74: device_set.v1.DeviceSetService.ListRackZones:output_type -> device_set.v1.ListRackZonesResponse - 42, // 75: device_set.v1.DeviceSetService.ListRackZoneRefs:output_type -> device_set.v1.ListRackZoneRefsResponse - 45, // 76: device_set.v1.DeviceSetService.ListRackTypes:output_type -> device_set.v1.ListRackTypesResponse - 47, // 77: device_set.v1.DeviceSetService.SaveRack:output_type -> device_set.v1.SaveRackResponse - 49, // 78: device_set.v1.DeviceSetService.AssignDevicesToRack:output_type -> device_set.v1.AssignDevicesToRackResponse - 61, // [61:79] is the sub-list for method output_type - 43, // [43:61] is the sub-list for method input_type - 43, // [43:43] is the sub-list for extension type_name - 43, // [43:43] is the sub-list for extension extendee - 0, // [0:43] is the sub-list for field type_name + 51, // 43: device_set.v1.AssignDevicesToRackRequest.device_selector:type_name -> common.v1.DeviceSelector + 10, // 44: device_set.v1.DeviceSetService.CreateDeviceSet:input_type -> device_set.v1.CreateDeviceSetRequest + 12, // 45: device_set.v1.DeviceSetService.GetDeviceSet:input_type -> device_set.v1.GetDeviceSetRequest + 14, // 46: device_set.v1.DeviceSetService.UpdateDeviceSet:input_type -> device_set.v1.UpdateDeviceSetRequest + 16, // 47: device_set.v1.DeviceSetService.DeleteDeviceSet:input_type -> device_set.v1.DeleteDeviceSetRequest + 18, // 48: device_set.v1.DeviceSetService.ListDeviceSets:input_type -> device_set.v1.ListDeviceSetsRequest + 20, // 49: device_set.v1.DeviceSetService.AddDevicesToGroup:input_type -> device_set.v1.AddDevicesToGroupRequest + 22, // 50: device_set.v1.DeviceSetService.RemoveDevicesFromGroup:input_type -> device_set.v1.RemoveDevicesFromGroupRequest + 24, // 51: device_set.v1.DeviceSetService.ListDeviceSetMembers:input_type -> device_set.v1.ListDeviceSetMembersRequest + 26, // 52: device_set.v1.DeviceSetService.GetDeviceDeviceSets:input_type -> device_set.v1.GetDeviceDeviceSetsRequest + 28, // 53: device_set.v1.DeviceSetService.SetRackSlotPosition:input_type -> device_set.v1.SetRackSlotPositionRequest + 30, // 54: device_set.v1.DeviceSetService.ClearRackSlotPosition:input_type -> device_set.v1.ClearRackSlotPositionRequest + 32, // 55: device_set.v1.DeviceSetService.GetRackSlots:input_type -> device_set.v1.GetRackSlotsRequest + 36, // 56: device_set.v1.DeviceSetService.GetDeviceSetStats:input_type -> device_set.v1.GetDeviceSetStatsRequest + 39, // 57: device_set.v1.DeviceSetService.ListRackZones:input_type -> device_set.v1.ListRackZonesRequest + 41, // 58: device_set.v1.DeviceSetService.ListRackZoneRefs:input_type -> device_set.v1.ListRackZoneRefsRequest + 43, // 59: device_set.v1.DeviceSetService.ListRackTypes:input_type -> device_set.v1.ListRackTypesRequest + 46, // 60: device_set.v1.DeviceSetService.SaveRack:input_type -> device_set.v1.SaveRackRequest + 48, // 61: device_set.v1.DeviceSetService.AssignDevicesToRack:input_type -> device_set.v1.AssignDevicesToRackRequest + 11, // 62: device_set.v1.DeviceSetService.CreateDeviceSet:output_type -> device_set.v1.CreateDeviceSetResponse + 13, // 63: device_set.v1.DeviceSetService.GetDeviceSet:output_type -> device_set.v1.GetDeviceSetResponse + 15, // 64: device_set.v1.DeviceSetService.UpdateDeviceSet:output_type -> device_set.v1.UpdateDeviceSetResponse + 17, // 65: device_set.v1.DeviceSetService.DeleteDeviceSet:output_type -> device_set.v1.DeleteDeviceSetResponse + 19, // 66: device_set.v1.DeviceSetService.ListDeviceSets:output_type -> device_set.v1.ListDeviceSetsResponse + 21, // 67: device_set.v1.DeviceSetService.AddDevicesToGroup:output_type -> device_set.v1.AddDevicesToGroupResponse + 23, // 68: device_set.v1.DeviceSetService.RemoveDevicesFromGroup:output_type -> device_set.v1.RemoveDevicesFromGroupResponse + 25, // 69: device_set.v1.DeviceSetService.ListDeviceSetMembers:output_type -> device_set.v1.ListDeviceSetMembersResponse + 27, // 70: device_set.v1.DeviceSetService.GetDeviceDeviceSets:output_type -> device_set.v1.GetDeviceDeviceSetsResponse + 29, // 71: device_set.v1.DeviceSetService.SetRackSlotPosition:output_type -> device_set.v1.SetRackSlotPositionResponse + 31, // 72: device_set.v1.DeviceSetService.ClearRackSlotPosition:output_type -> device_set.v1.ClearRackSlotPositionResponse + 34, // 73: device_set.v1.DeviceSetService.GetRackSlots:output_type -> device_set.v1.GetRackSlotsResponse + 37, // 74: device_set.v1.DeviceSetService.GetDeviceSetStats:output_type -> device_set.v1.GetDeviceSetStatsResponse + 40, // 75: device_set.v1.DeviceSetService.ListRackZones:output_type -> device_set.v1.ListRackZonesResponse + 42, // 76: device_set.v1.DeviceSetService.ListRackZoneRefs:output_type -> device_set.v1.ListRackZoneRefsResponse + 45, // 77: device_set.v1.DeviceSetService.ListRackTypes:output_type -> device_set.v1.ListRackTypesResponse + 47, // 78: device_set.v1.DeviceSetService.SaveRack:output_type -> device_set.v1.SaveRackResponse + 49, // 79: device_set.v1.DeviceSetService.AssignDevicesToRack:output_type -> device_set.v1.AssignDevicesToRackResponse + 62, // [62:80] is the sub-list for method output_type + 44, // [44:62] is the sub-list for method input_type + 44, // [44:44] is the sub-list for extension type_name + 44, // [44:44] is the sub-list for extension extendee + 0, // [0:44] is the sub-list for field type_name } func init() { file_device_set_v1_device_set_proto_init() } diff --git a/server/generated/grpc/device_set/v1/device_setv1connect/device_set.connect.go b/server/generated/grpc/device_set/v1/device_setv1connect/device_set.connect.go index afb3f3861..ff1c76bb7 100644 --- a/server/generated/grpc/device_set/v1/device_setv1connect/device_set.connect.go +++ b/server/generated/grpc/device_set/v1/device_setv1connect/device_set.connect.go @@ -49,12 +49,12 @@ const ( // DeviceSetServiceListDeviceSetsProcedure is the fully-qualified name of the DeviceSetService's // ListDeviceSets RPC. DeviceSetServiceListDeviceSetsProcedure = "/device_set.v1.DeviceSetService/ListDeviceSets" - // DeviceSetServiceAddDevicesToDeviceSetProcedure is the fully-qualified name of the - // DeviceSetService's AddDevicesToDeviceSet RPC. - DeviceSetServiceAddDevicesToDeviceSetProcedure = "/device_set.v1.DeviceSetService/AddDevicesToDeviceSet" - // DeviceSetServiceRemoveDevicesFromDeviceSetProcedure is the fully-qualified name of the - // DeviceSetService's RemoveDevicesFromDeviceSet RPC. - DeviceSetServiceRemoveDevicesFromDeviceSetProcedure = "/device_set.v1.DeviceSetService/RemoveDevicesFromDeviceSet" + // DeviceSetServiceAddDevicesToGroupProcedure is the fully-qualified name of the DeviceSetService's + // AddDevicesToGroup RPC. + DeviceSetServiceAddDevicesToGroupProcedure = "/device_set.v1.DeviceSetService/AddDevicesToGroup" + // DeviceSetServiceRemoveDevicesFromGroupProcedure is the fully-qualified name of the + // DeviceSetService's RemoveDevicesFromGroup RPC. + DeviceSetServiceRemoveDevicesFromGroupProcedure = "/device_set.v1.DeviceSetService/RemoveDevicesFromGroup" // DeviceSetServiceListDeviceSetMembersProcedure is the fully-qualified name of the // DeviceSetService's ListDeviceSetMembers RPC. DeviceSetServiceListDeviceSetMembersProcedure = "/device_set.v1.DeviceSetService/ListDeviceSetMembers" @@ -102,10 +102,13 @@ type DeviceSetServiceClient interface { DeleteDeviceSet(context.Context, *connect.Request[v1.DeleteDeviceSetRequest]) (*connect.Response[v1.DeleteDeviceSetResponse], error) // Lists all device sets for the organization ListDeviceSets(context.Context, *connect.Request[v1.ListDeviceSetsRequest]) (*connect.Response[v1.ListDeviceSetsResponse], error) - // Adds devices to a device set - AddDevicesToDeviceSet(context.Context, *connect.Request[v1.AddDevicesToDeviceSetRequest]) (*connect.Response[v1.AddDevicesToDeviceSetResponse], error) - // Removes devices from a device set - RemoveDevicesFromDeviceSet(context.Context, *connect.Request[v1.RemoveDevicesFromDeviceSetRequest]) (*connect.Response[v1.RemoveDevicesFromDeviceSetResponse], error) + // Adds devices to a group (many-to-many). Rejects non-group targets + // with InvalidArgument; use AssignDevicesToRack for racks. + AddDevicesToGroup(context.Context, *connect.Request[v1.AddDevicesToGroupRequest]) (*connect.Response[v1.AddDevicesToGroupResponse], error) + // Removes devices from a group. Rejects non-group targets with + // InvalidArgument; rack membership is cleared via + // AssignDevicesToRack with target_rack_id unset. + RemoveDevicesFromGroup(context.Context, *connect.Request[v1.RemoveDevicesFromGroupRequest]) (*connect.Response[v1.RemoveDevicesFromGroupResponse], error) // Lists members of a device set ListDeviceSetMembers(context.Context, *connect.Request[v1.ListDeviceSetMembersRequest]) (*connect.Response[v1.ListDeviceSetMembersResponse], error) // Gets device sets that a device belongs to @@ -139,11 +142,15 @@ type DeviceSetServiceClient interface { // device's current site. target_rack_id unset clears rack // membership without re-assigning (site/building stay intact). // - // Closes the orphan window where a client-side - // RemoveDevicesFromDeviceSet + AddDevicesToDeviceSet pair could - // leave devices without rack membership on transport failure between - // the two calls, and the cross-rack rename race where the source - // rack's id resolution drifts between the resolve and the remove. + // Closes the orphan window where a client-side remove + add pair + // could leave devices without rack membership on transport failure + // between the two calls, and the cross-rack rename race where the + // source rack's id resolution drifts between the resolve and the + // remove. + // + // device_selector accepts only the device_list variant. all_devices + // is rejected with InvalidArgument because moving every paired + // device into a single rack is never the intended operation. AssignDevicesToRack(context.Context, *connect.Request[v1.AssignDevicesToRackRequest]) (*connect.Response[v1.AssignDevicesToRackResponse], error) } @@ -182,14 +189,14 @@ func NewDeviceSetServiceClient(httpClient connect.HTTPClient, baseURL string, op baseURL+DeviceSetServiceListDeviceSetsProcedure, opts..., ), - addDevicesToDeviceSet: connect.NewClient[v1.AddDevicesToDeviceSetRequest, v1.AddDevicesToDeviceSetResponse]( + addDevicesToGroup: connect.NewClient[v1.AddDevicesToGroupRequest, v1.AddDevicesToGroupResponse]( httpClient, - baseURL+DeviceSetServiceAddDevicesToDeviceSetProcedure, + baseURL+DeviceSetServiceAddDevicesToGroupProcedure, opts..., ), - removeDevicesFromDeviceSet: connect.NewClient[v1.RemoveDevicesFromDeviceSetRequest, v1.RemoveDevicesFromDeviceSetResponse]( + removeDevicesFromGroup: connect.NewClient[v1.RemoveDevicesFromGroupRequest, v1.RemoveDevicesFromGroupResponse]( httpClient, - baseURL+DeviceSetServiceRemoveDevicesFromDeviceSetProcedure, + baseURL+DeviceSetServiceRemoveDevicesFromGroupProcedure, opts..., ), listDeviceSetMembers: connect.NewClient[v1.ListDeviceSetMembersRequest, v1.ListDeviceSetMembersResponse]( @@ -252,24 +259,24 @@ func NewDeviceSetServiceClient(httpClient connect.HTTPClient, baseURL string, op // deviceSetServiceClient implements DeviceSetServiceClient. type deviceSetServiceClient struct { - createDeviceSet *connect.Client[v1.CreateDeviceSetRequest, v1.CreateDeviceSetResponse] - getDeviceSet *connect.Client[v1.GetDeviceSetRequest, v1.GetDeviceSetResponse] - updateDeviceSet *connect.Client[v1.UpdateDeviceSetRequest, v1.UpdateDeviceSetResponse] - deleteDeviceSet *connect.Client[v1.DeleteDeviceSetRequest, v1.DeleteDeviceSetResponse] - listDeviceSets *connect.Client[v1.ListDeviceSetsRequest, v1.ListDeviceSetsResponse] - addDevicesToDeviceSet *connect.Client[v1.AddDevicesToDeviceSetRequest, v1.AddDevicesToDeviceSetResponse] - removeDevicesFromDeviceSet *connect.Client[v1.RemoveDevicesFromDeviceSetRequest, v1.RemoveDevicesFromDeviceSetResponse] - listDeviceSetMembers *connect.Client[v1.ListDeviceSetMembersRequest, v1.ListDeviceSetMembersResponse] - getDeviceDeviceSets *connect.Client[v1.GetDeviceDeviceSetsRequest, v1.GetDeviceDeviceSetsResponse] - setRackSlotPosition *connect.Client[v1.SetRackSlotPositionRequest, v1.SetRackSlotPositionResponse] - clearRackSlotPosition *connect.Client[v1.ClearRackSlotPositionRequest, v1.ClearRackSlotPositionResponse] - getRackSlots *connect.Client[v1.GetRackSlotsRequest, v1.GetRackSlotsResponse] - getDeviceSetStats *connect.Client[v1.GetDeviceSetStatsRequest, v1.GetDeviceSetStatsResponse] - listRackZones *connect.Client[v1.ListRackZonesRequest, v1.ListRackZonesResponse] - listRackZoneRefs *connect.Client[v1.ListRackZoneRefsRequest, v1.ListRackZoneRefsResponse] - listRackTypes *connect.Client[v1.ListRackTypesRequest, v1.ListRackTypesResponse] - saveRack *connect.Client[v1.SaveRackRequest, v1.SaveRackResponse] - assignDevicesToRack *connect.Client[v1.AssignDevicesToRackRequest, v1.AssignDevicesToRackResponse] + createDeviceSet *connect.Client[v1.CreateDeviceSetRequest, v1.CreateDeviceSetResponse] + getDeviceSet *connect.Client[v1.GetDeviceSetRequest, v1.GetDeviceSetResponse] + updateDeviceSet *connect.Client[v1.UpdateDeviceSetRequest, v1.UpdateDeviceSetResponse] + deleteDeviceSet *connect.Client[v1.DeleteDeviceSetRequest, v1.DeleteDeviceSetResponse] + listDeviceSets *connect.Client[v1.ListDeviceSetsRequest, v1.ListDeviceSetsResponse] + addDevicesToGroup *connect.Client[v1.AddDevicesToGroupRequest, v1.AddDevicesToGroupResponse] + removeDevicesFromGroup *connect.Client[v1.RemoveDevicesFromGroupRequest, v1.RemoveDevicesFromGroupResponse] + listDeviceSetMembers *connect.Client[v1.ListDeviceSetMembersRequest, v1.ListDeviceSetMembersResponse] + getDeviceDeviceSets *connect.Client[v1.GetDeviceDeviceSetsRequest, v1.GetDeviceDeviceSetsResponse] + setRackSlotPosition *connect.Client[v1.SetRackSlotPositionRequest, v1.SetRackSlotPositionResponse] + clearRackSlotPosition *connect.Client[v1.ClearRackSlotPositionRequest, v1.ClearRackSlotPositionResponse] + getRackSlots *connect.Client[v1.GetRackSlotsRequest, v1.GetRackSlotsResponse] + getDeviceSetStats *connect.Client[v1.GetDeviceSetStatsRequest, v1.GetDeviceSetStatsResponse] + listRackZones *connect.Client[v1.ListRackZonesRequest, v1.ListRackZonesResponse] + listRackZoneRefs *connect.Client[v1.ListRackZoneRefsRequest, v1.ListRackZoneRefsResponse] + listRackTypes *connect.Client[v1.ListRackTypesRequest, v1.ListRackTypesResponse] + saveRack *connect.Client[v1.SaveRackRequest, v1.SaveRackResponse] + assignDevicesToRack *connect.Client[v1.AssignDevicesToRackRequest, v1.AssignDevicesToRackResponse] } // CreateDeviceSet calls device_set.v1.DeviceSetService.CreateDeviceSet. @@ -297,14 +304,14 @@ func (c *deviceSetServiceClient) ListDeviceSets(ctx context.Context, req *connec return c.listDeviceSets.CallUnary(ctx, req) } -// AddDevicesToDeviceSet calls device_set.v1.DeviceSetService.AddDevicesToDeviceSet. -func (c *deviceSetServiceClient) AddDevicesToDeviceSet(ctx context.Context, req *connect.Request[v1.AddDevicesToDeviceSetRequest]) (*connect.Response[v1.AddDevicesToDeviceSetResponse], error) { - return c.addDevicesToDeviceSet.CallUnary(ctx, req) +// AddDevicesToGroup calls device_set.v1.DeviceSetService.AddDevicesToGroup. +func (c *deviceSetServiceClient) AddDevicesToGroup(ctx context.Context, req *connect.Request[v1.AddDevicesToGroupRequest]) (*connect.Response[v1.AddDevicesToGroupResponse], error) { + return c.addDevicesToGroup.CallUnary(ctx, req) } -// RemoveDevicesFromDeviceSet calls device_set.v1.DeviceSetService.RemoveDevicesFromDeviceSet. -func (c *deviceSetServiceClient) RemoveDevicesFromDeviceSet(ctx context.Context, req *connect.Request[v1.RemoveDevicesFromDeviceSetRequest]) (*connect.Response[v1.RemoveDevicesFromDeviceSetResponse], error) { - return c.removeDevicesFromDeviceSet.CallUnary(ctx, req) +// RemoveDevicesFromGroup calls device_set.v1.DeviceSetService.RemoveDevicesFromGroup. +func (c *deviceSetServiceClient) RemoveDevicesFromGroup(ctx context.Context, req *connect.Request[v1.RemoveDevicesFromGroupRequest]) (*connect.Response[v1.RemoveDevicesFromGroupResponse], error) { + return c.removeDevicesFromGroup.CallUnary(ctx, req) } // ListDeviceSetMembers calls device_set.v1.DeviceSetService.ListDeviceSetMembers. @@ -374,10 +381,13 @@ type DeviceSetServiceHandler interface { DeleteDeviceSet(context.Context, *connect.Request[v1.DeleteDeviceSetRequest]) (*connect.Response[v1.DeleteDeviceSetResponse], error) // Lists all device sets for the organization ListDeviceSets(context.Context, *connect.Request[v1.ListDeviceSetsRequest]) (*connect.Response[v1.ListDeviceSetsResponse], error) - // Adds devices to a device set - AddDevicesToDeviceSet(context.Context, *connect.Request[v1.AddDevicesToDeviceSetRequest]) (*connect.Response[v1.AddDevicesToDeviceSetResponse], error) - // Removes devices from a device set - RemoveDevicesFromDeviceSet(context.Context, *connect.Request[v1.RemoveDevicesFromDeviceSetRequest]) (*connect.Response[v1.RemoveDevicesFromDeviceSetResponse], error) + // Adds devices to a group (many-to-many). Rejects non-group targets + // with InvalidArgument; use AssignDevicesToRack for racks. + AddDevicesToGroup(context.Context, *connect.Request[v1.AddDevicesToGroupRequest]) (*connect.Response[v1.AddDevicesToGroupResponse], error) + // Removes devices from a group. Rejects non-group targets with + // InvalidArgument; rack membership is cleared via + // AssignDevicesToRack with target_rack_id unset. + RemoveDevicesFromGroup(context.Context, *connect.Request[v1.RemoveDevicesFromGroupRequest]) (*connect.Response[v1.RemoveDevicesFromGroupResponse], error) // Lists members of a device set ListDeviceSetMembers(context.Context, *connect.Request[v1.ListDeviceSetMembersRequest]) (*connect.Response[v1.ListDeviceSetMembersResponse], error) // Gets device sets that a device belongs to @@ -411,11 +421,15 @@ type DeviceSetServiceHandler interface { // device's current site. target_rack_id unset clears rack // membership without re-assigning (site/building stay intact). // - // Closes the orphan window where a client-side - // RemoveDevicesFromDeviceSet + AddDevicesToDeviceSet pair could - // leave devices without rack membership on transport failure between - // the two calls, and the cross-rack rename race where the source - // rack's id resolution drifts between the resolve and the remove. + // Closes the orphan window where a client-side remove + add pair + // could leave devices without rack membership on transport failure + // between the two calls, and the cross-rack rename race where the + // source rack's id resolution drifts between the resolve and the + // remove. + // + // device_selector accepts only the device_list variant. all_devices + // is rejected with InvalidArgument because moving every paired + // device into a single rack is never the intended operation. AssignDevicesToRack(context.Context, *connect.Request[v1.AssignDevicesToRackRequest]) (*connect.Response[v1.AssignDevicesToRackResponse], error) } @@ -450,14 +464,14 @@ func NewDeviceSetServiceHandler(svc DeviceSetServiceHandler, opts ...connect.Han svc.ListDeviceSets, opts..., ) - deviceSetServiceAddDevicesToDeviceSetHandler := connect.NewUnaryHandler( - DeviceSetServiceAddDevicesToDeviceSetProcedure, - svc.AddDevicesToDeviceSet, + deviceSetServiceAddDevicesToGroupHandler := connect.NewUnaryHandler( + DeviceSetServiceAddDevicesToGroupProcedure, + svc.AddDevicesToGroup, opts..., ) - deviceSetServiceRemoveDevicesFromDeviceSetHandler := connect.NewUnaryHandler( - DeviceSetServiceRemoveDevicesFromDeviceSetProcedure, - svc.RemoveDevicesFromDeviceSet, + deviceSetServiceRemoveDevicesFromGroupHandler := connect.NewUnaryHandler( + DeviceSetServiceRemoveDevicesFromGroupProcedure, + svc.RemoveDevicesFromGroup, opts..., ) deviceSetServiceListDeviceSetMembersHandler := connect.NewUnaryHandler( @@ -527,10 +541,10 @@ func NewDeviceSetServiceHandler(svc DeviceSetServiceHandler, opts ...connect.Han deviceSetServiceDeleteDeviceSetHandler.ServeHTTP(w, r) case DeviceSetServiceListDeviceSetsProcedure: deviceSetServiceListDeviceSetsHandler.ServeHTTP(w, r) - case DeviceSetServiceAddDevicesToDeviceSetProcedure: - deviceSetServiceAddDevicesToDeviceSetHandler.ServeHTTP(w, r) - case DeviceSetServiceRemoveDevicesFromDeviceSetProcedure: - deviceSetServiceRemoveDevicesFromDeviceSetHandler.ServeHTTP(w, r) + case DeviceSetServiceAddDevicesToGroupProcedure: + deviceSetServiceAddDevicesToGroupHandler.ServeHTTP(w, r) + case DeviceSetServiceRemoveDevicesFromGroupProcedure: + deviceSetServiceRemoveDevicesFromGroupHandler.ServeHTTP(w, r) case DeviceSetServiceListDeviceSetMembersProcedure: deviceSetServiceListDeviceSetMembersHandler.ServeHTTP(w, r) case DeviceSetServiceGetDeviceDeviceSetsProcedure: @@ -582,12 +596,12 @@ func (UnimplementedDeviceSetServiceHandler) ListDeviceSets(context.Context, *con return nil, connect.NewError(connect.CodeUnimplemented, errors.New("device_set.v1.DeviceSetService.ListDeviceSets is not implemented")) } -func (UnimplementedDeviceSetServiceHandler) AddDevicesToDeviceSet(context.Context, *connect.Request[v1.AddDevicesToDeviceSetRequest]) (*connect.Response[v1.AddDevicesToDeviceSetResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("device_set.v1.DeviceSetService.AddDevicesToDeviceSet is not implemented")) +func (UnimplementedDeviceSetServiceHandler) AddDevicesToGroup(context.Context, *connect.Request[v1.AddDevicesToGroupRequest]) (*connect.Response[v1.AddDevicesToGroupResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("device_set.v1.DeviceSetService.AddDevicesToGroup is not implemented")) } -func (UnimplementedDeviceSetServiceHandler) RemoveDevicesFromDeviceSet(context.Context, *connect.Request[v1.RemoveDevicesFromDeviceSetRequest]) (*connect.Response[v1.RemoveDevicesFromDeviceSetResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("device_set.v1.DeviceSetService.RemoveDevicesFromDeviceSet is not implemented")) +func (UnimplementedDeviceSetServiceHandler) RemoveDevicesFromGroup(context.Context, *connect.Request[v1.RemoveDevicesFromGroupRequest]) (*connect.Response[v1.RemoveDevicesFromGroupResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("device_set.v1.DeviceSetService.RemoveDevicesFromGroup is not implemented")) } func (UnimplementedDeviceSetServiceHandler) ListDeviceSetMembers(context.Context, *connect.Request[v1.ListDeviceSetMembersRequest]) (*connect.Response[v1.ListDeviceSetMembersResponse], error) { diff --git a/server/generated/grpc/sites/v1/sites.pb.go b/server/generated/grpc/sites/v1/sites.pb.go index 197d08d92..f25b93937 100644 --- a/server/generated/grpc/sites/v1/sites.pb.go +++ b/server/generated/grpc/sites/v1/sites.pb.go @@ -845,8 +845,20 @@ type AssignDevicesToSiteRequest struct { // Unset = move to the "Unassigned" bucket. When present, must be > 0. TargetSiteId *int64 `protobuf:"varint,1,opt,name=target_site_id,json=targetSiteId,proto3,oneof" json:"target_site_id,omitempty"` DeviceIdentifiers []string `protobuf:"bytes,2,rep,name=device_identifiers,json=deviceIdentifiers,proto3" json:"device_identifiers,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // When true, the server — inside the same transaction as the site + // write — removes any conflicting rack memberships for the listed + // devices before applying the site assignment. This closes the + // cross-site reparent orphan window the client-side + // remove-then-assign loop in MinerReparentPicker had: a transport + // failure between the per-rack unassign and the site reassign + // would leave miners rack-less but still pinned to the old site. + // + // When false (default), preserves today's behavior: any device + // currently in a rack at a different site rejects the whole batch + // with a PerDeviceConflict[] of DEVICE_IN_RACK_AT_OTHER_SITE. + ForceClearConflictingRackMembership *bool `protobuf:"varint,3,opt,name=force_clear_conflicting_rack_membership,json=forceClearConflictingRackMembership,proto3,oneof" json:"force_clear_conflicting_rack_membership,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AssignDevicesToSiteRequest) Reset() { @@ -893,6 +905,13 @@ func (x *AssignDevicesToSiteRequest) GetDeviceIdentifiers() []string { return nil } +func (x *AssignDevicesToSiteRequest) GetForceClearConflictingRackMembership() bool { + if x != nil && x.ForceClearConflictingRackMembership != nil { + return *x.ForceClearConflictingRackMembership + } + return false +} + // PerDeviceConflict carries the per-device error details surfaced // when AssignDevicesToSite rejects. The whole batch is rejected; // no rows are mutated. @@ -1578,7 +1597,7 @@ var file_sites_v1_sites_proto_rawDesc = string([]byte{ 0x75, 0x6e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x75, 0x6e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x22, 0xa8, 0x01, 0x0a, 0x1a, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x22, 0xaf, 0x02, 0x0a, 0x1a, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, @@ -1587,171 +1606,179 @@ var file_sites_v1_sites_proto_rawDesc = string([]byte{ 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, 0x14, 0xba, 0x48, 0x11, 0x92, 0x01, 0x0e, 0x08, 0x01, 0x10, 0x90, 0x4e, 0x22, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x02, 0x52, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x22, 0xab, 0x01, 0x0a, 0x11, - 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, - 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x39, - 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x59, 0x0a, 0x27, 0x66, 0x6f, 0x72, 0x63, + 0x65, 0x5f, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, + 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x68, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x23, 0x66, 0x6f, 0x72, + 0x63, 0x65, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, + 0x6e, 0x67, 0x52, 0x61, 0x63, 0x6b, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, + 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, + 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x42, 0x2a, 0x0a, 0x28, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, + 0x5f, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, + 0x6e, 0x67, 0x5f, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, + 0x69, 0x70, 0x22, 0xab, 0x01, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, + 0x63, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, + 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x63, + 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, + 0x22, 0x83, 0x01, 0x0a, 0x1b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x61, 0x73, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x09, 0x63, + 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x6f, 0x6e, - 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, - 0x69, 0x6e, 0x67, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x22, 0x83, 0x01, 0x0a, 0x1b, 0x41, 0x73, - 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x61, - 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x6c, 0x69, 0x63, 0x74, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x22, - 0x9b, 0x01, 0x0a, 0x1c, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, - 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x34, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x42, 0x11, 0xba, 0x48, 0x0e, 0x92, 0x01, 0x0b, 0x08, 0x01, - 0x10, 0xe8, 0x07, 0x22, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x69, 0x6e, 0x67, 0x49, 0x64, 0x73, 0x12, 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, - 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x22, 0x8b, 0x01, - 0x0a, 0x1d, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, - 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x32, 0x0a, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x61, - 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, - 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x61, 0x63, 0x6b, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8f, 0x01, 0x0a, 0x18, - 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x08, 0x72, 0x61, 0x63, 0x6b, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x42, 0x11, 0xba, 0x48, 0x0e, 0x92, - 0x01, 0x0b, 0x08, 0x01, 0x10, 0xe8, 0x07, 0x22, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x07, 0x72, - 0x61, 0x63, 0x6b, 0x49, 0x64, 0x73, 0x12, 0x32, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, - 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x22, 0x89, 0x01, - 0x0a, 0x19, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x53, - 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x72, - 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, - 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x34, 0x0a, 0x16, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x62, + 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x09, 0x63, 0x6f, 0x6e, + 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x22, 0x9b, 0x01, 0x0a, 0x1c, 0x41, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x42, 0x11, 0xba, + 0x48, 0x0e, 0x92, 0x01, 0x0b, 0x08, 0x01, 0x10, 0xe8, 0x07, 0x22, 0x04, 0x22, 0x02, 0x20, 0x00, + 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x73, 0x12, 0x32, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, + 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x88, 0x01, + 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, + 0x65, 0x5f, 0x69, 0x64, 0x22, 0x8b, 0x01, 0x0a, 0x1d, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x64, 0x52, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, + 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, 0x61, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0x8f, 0x01, 0x0a, 0x18, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, + 0x6b, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2c, 0x0a, 0x08, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x03, 0x42, 0x11, 0xba, 0x48, 0x0e, 0x92, 0x01, 0x0b, 0x08, 0x01, 0x10, 0xe8, 0x07, 0x22, 0x04, + 0x22, 0x02, 0x20, 0x00, 0x52, 0x07, 0x72, 0x61, 0x63, 0x6b, 0x49, 0x64, 0x73, 0x12, 0x32, 0x0a, + 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x48, 0x00, + 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x64, 0x88, 0x01, + 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x74, + 0x65, 0x5f, 0x69, 0x64, 0x22, 0x89, 0x01, 0x0a, 0x19, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, + 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x34, 0x0a, 0x16, 0x63, 0x6c, + 0x65, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x63, 0x6c, 0x65, 0x61, + 0x72, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x22, 0x37, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, + 0x00, 0x52, 0x06, 0x73, 0x69, 0x74, 0x65, 0x49, 0x64, 0x22, 0xe4, 0x04, 0x0a, 0x14, 0x47, 0x65, + 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x69, 0x74, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x14, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x37, 0x0a, 0x13, 0x47, 0x65, 0x74, - 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x20, 0x0a, 0x07, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x42, 0x07, 0xba, 0x48, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x06, 0x73, 0x69, 0x74, 0x65, - 0x49, 0x64, 0x22, 0xe4, 0x04, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x73, - 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x69, - 0x74, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x62, 0x75, - 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, - 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, - 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61, - 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x67, 0x5f, 0x65, 0x66, 0x66, - 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6a, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, 0x45, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, - 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x6f, 0x77, - 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, - 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, - 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, - 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, - 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, - 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x38, 0x0a, - 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, - 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x66, 0x66, 0x69, 0x63, - 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x65, 0x66, 0x66, - 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x72, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0xb3, 0x01, 0x0a, 0x17, 0x50, 0x65, - 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x26, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, - 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, - 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, - 0x00, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, - 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, - 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, - 0x10, 0x01, 0x12, 0x3b, 0x0a, 0x37, 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, - 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, - 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x52, 0x41, 0x43, 0x4b, 0x5f, - 0x41, 0x54, 0x5f, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x5f, 0x53, 0x49, 0x54, 0x45, 0x10, 0x02, 0x32, - 0xa9, 0x05, 0x0a, 0x0b, 0x53, 0x69, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x44, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x73, - 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, - 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x01, 0x28, 0x05, 0x52, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, + 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, + 0x5f, 0x74, 0x68, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x48, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, 0x54, 0x68, 0x73, 0x12, 0x2c, 0x0a, 0x12, + 0x61, 0x76, 0x67, 0x5f, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6a, + 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x67, 0x45, 0x66, 0x66, + 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x4b, 0x77, + 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x72, 0x6f, + 0x6b, 0x65, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x66, 0x66, 0x6c, + 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, + 0x0e, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x16, 0x68, 0x61, 0x73, 0x68, 0x72, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, + 0x0a, 0x1a, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x72, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x18, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, + 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x6f, 0x77, + 0x65, 0x72, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x2a, 0xb3, 0x01, 0x0a, 0x17, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x26, + 0x50, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, + 0x49, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2f, 0x0a, 0x2b, 0x50, 0x45, 0x52, 0x5f, + 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, 0x5f, + 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, + 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x01, 0x12, 0x3b, 0x0a, 0x37, 0x50, 0x45, 0x52, + 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x49, 0x43, 0x54, + 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x49, + 0x4e, 0x5f, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x41, 0x54, 0x5f, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x5f, + 0x53, 0x49, 0x54, 0x45, 0x10, 0x02, 0x32, 0xa9, 0x05, 0x0a, 0x0b, 0x53, 0x69, 0x74, 0x65, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, + 0x74, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, + 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, + 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, - 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, - 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, + 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, + 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x53, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x62, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, - 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x15, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x26, 0x2e, - 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, - 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, - 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, - 0x0a, 0x11, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x53, - 0x69, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, - 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, - 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0c, - 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x73, - 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, - 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xa0, 0x01, 0x0a, 0x0c, - 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x53, 0x69, - 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, - 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x73, - 0x69, 0x74, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x73, 0x69, 0x74, 0x65, 0x73, 0x76, 0x31, 0xa2, - 0x02, 0x03, 0x53, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x56, 0x31, - 0xca, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x14, 0x53, 0x69, - 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0xea, 0x02, 0x09, 0x53, 0x69, 0x74, 0x65, 0x73, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x24, + 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x53, + 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x15, 0x41, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, + 0x53, 0x69, 0x74, 0x65, 0x12, 0x26, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, + 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x73, + 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x11, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, + 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x73, 0x69, 0x74, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x61, 0x63, 0x6b, + 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, + 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, + 0x52, 0x61, 0x63, 0x6b, 0x73, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x53, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0xa0, 0x01, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x69, 0x74, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x53, 0x69, 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, + 0x01, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x2f, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, + 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x73, + 0x69, 0x74, 0x65, 0x73, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x53, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x53, + 0x69, 0x74, 0x65, 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, + 0x56, 0x31, 0xe2, 0x02, 0x14, 0x53, 0x69, 0x74, 0x65, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, + 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x53, 0x69, 0x74, 0x65, + 0x73, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( diff --git a/server/generated/sqlc/building.sql.go b/server/generated/sqlc/building.sql.go index ca0a75629..fd64081e1 100644 --- a/server/generated/sqlc/building.sql.go +++ b/server/generated/sqlc/building.sql.go @@ -40,6 +40,34 @@ func (q *Queries) AssignBuildingToSite(ctx context.Context, arg AssignBuildingTo return result.RowsAffected() } +const assignBuildingsToSiteBulk = `-- name: AssignBuildingsToSiteBulk :execrows +UPDATE building +SET site_id = $1, + updated_at = CURRENT_TIMESTAMP +WHERE id = ANY($2::bigint[]) + AND org_id = $3 + AND deleted_at IS NULL +` + +type AssignBuildingsToSiteBulkParams struct { + SiteID sql.NullInt64 + BuildingIds []int64 + OrgID int64 +} + +// Bulk variant of AssignBuildingToSite. Updates building.site_id for +// every building in @building_ids in one statement. Caller is +// expected to have row-locked each building first (canonical lock +// order: site → buildings). Returns the row count of buildings +// actually moved (skips soft-deleted rows). +func (q *Queries) AssignBuildingsToSiteBulk(ctx context.Context, arg AssignBuildingsToSiteBulkParams) (int64, error) { + result, err := q.exec(ctx, q.assignBuildingsToSiteBulkStmt, assignBuildingsToSiteBulk, arg.SiteID, pq.Array(arg.BuildingIds), arg.OrgID) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + const buildingBelongsToOrg = `-- name: BuildingBelongsToOrg :one SELECT EXISTS( SELECT 1 FROM building @@ -502,6 +530,74 @@ func (q *Queries) SetRackBuildingPosition(ctx context.Context, arg SetRackBuildi return err } +const setRackBuildingPositionBulkClear = `-- name: SetRackBuildingPositionBulkClear :exec +UPDATE device_set_rack +SET aisle_index = NULL, + position_in_aisle = NULL +WHERE device_set_id = ANY($1::bigint[]) + AND org_id = $2 +` + +type SetRackBuildingPositionBulkClearParams struct { + RackIds []int64 + OrgID int64 +} + +// Bulk variant of SetRackBuildingPosition that nulls (aisle_index, +// position_in_aisle) for every rack in @rack_ids. Used by +// AssignRacksToBuilding's pass-1 vacate so every rack in the batch +// holds NULL position before pass-2 reclaims cells. Mirrors the +// single-row query's intentional no-touch on building_id — +// UpdateRackPlacement already settled that. +func (q *Queries) SetRackBuildingPositionBulkClear(ctx context.Context, arg SetRackBuildingPositionBulkClearParams) error { + _, err := q.exec(ctx, q.setRackBuildingPositionBulkClearStmt, setRackBuildingPositionBulkClear, pq.Array(arg.RackIds), arg.OrgID) + return err +} + +const setRackBuildingPositionBulkPlace = `-- name: SetRackBuildingPositionBulkPlace :exec +UPDATE device_set_rack dsr +SET aisle_index = u.aisle_index, + position_in_aisle = u.position_in_aisle +FROM ( + SELECT + rack_ids[i] AS rack_id, + aisle_indexes[i] AS aisle_index, + position_in_aisles[i] AS position_in_aisle + FROM ( + SELECT + $2::bigint[] AS rack_ids, + $3::int[] AS aisle_indexes, + $4::int[] AS position_in_aisles + ) arrs + CROSS JOIN generate_subscripts(arrs.rack_ids, 1) AS i +) AS u +WHERE dsr.device_set_id = u.rack_id + AND dsr.org_id = $1 +` + +type SetRackBuildingPositionBulkPlaceParams struct { + OrgID int64 + RackIds []int64 + AisleIndexes []int32 + PositionInAisles []int32 +} + +// Bulk variant of SetRackBuildingPosition that writes per-rack +// (aisle_index, position_in_aisle) via parallel arrays joined on +// ordinal position. Used by AssignRacksToBuilding's pass-2 after +// pass-1 has vacated every cell touched by the batch. Arrays must be +// the same length and parallel-aligned with @rack_ids; callers that +// have a "place nothing" pass-2 should skip the query entirely. +func (q *Queries) SetRackBuildingPositionBulkPlace(ctx context.Context, arg SetRackBuildingPositionBulkPlaceParams) error { + _, err := q.exec(ctx, q.setRackBuildingPositionBulkPlaceStmt, setRackBuildingPositionBulkPlace, + arg.OrgID, + pq.Array(arg.RackIds), + pq.Array(arg.AisleIndexes), + pq.Array(arg.PositionInAisles), + ) + return err +} + const softDeleteBuilding = `-- name: SoftDeleteBuilding :execrows UPDATE building SET deleted_at = CURRENT_TIMESTAMP diff --git a/server/generated/sqlc/db.go b/server/generated/sqlc/db.go index 95ef727ae..f43278f36 100644 --- a/server/generated/sqlc/db.go +++ b/server/generated/sqlc/db.go @@ -42,6 +42,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.assignBuildingToSiteStmt, err = db.PrepareContext(ctx, assignBuildingToSite); err != nil { return nil, fmt.Errorf("error preparing query AssignBuildingToSite: %w", err) } + if q.assignBuildingsToSiteBulkStmt, err = db.PrepareContext(ctx, assignBuildingsToSiteBulk); err != nil { + return nil, fmt.Errorf("error preparing query AssignBuildingsToSiteBulk: %w", err) + } if q.assignDevicesToSiteStmt, err = db.PrepareContext(ctx, assignDevicesToSite); err != nil { return nil, fmt.Errorf("error preparing query AssignDevicesToSite: %w", err) } @@ -81,6 +84,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.cascadeRackDeviceSitesStmt, err = db.PrepareContext(ctx, cascadeRackDeviceSites); err != nil { return nil, fmt.Errorf("error preparing query CascadeRackDeviceSites: %w", err) } + if q.cascadeRackDeviceSitesBulkStmt, err = db.PrepareContext(ctx, cascadeRackDeviceSitesBulk); err != nil { + return nil, fmt.Errorf("error preparing query CascadeRackDeviceSitesBulk: %w", err) + } if q.claimMessageForProcessingStmt, err = db.PrepareContext(ctx, claimMessageForProcessing); err != nil { return nil, fmt.Errorf("error preparing query ClaimMessageForProcessing: %w", err) } @@ -771,6 +777,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.lockSiteForWriteStmt, err = db.PrepareContext(ctx, lockSiteForWrite); err != nil { return nil, fmt.Errorf("error preparing query LockSiteForWrite: %w", err) } + if q.lockSourceRacksForDevicesStmt, err = db.PrepareContext(ctx, lockSourceRacksForDevices); err != nil { + return nil, fmt.Errorf("error preparing query LockSourceRacksForDevices: %w", err) + } if q.markCommandBatchFinishedStmt, err = db.PrepareContext(ctx, markCommandBatchFinished); err != nil { return nil, fmt.Errorf("error preparing query MarkCommandBatchFinished: %w", err) } @@ -813,9 +822,15 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.reassignDevicesUnderBuildingStmt, err = db.PrepareContext(ctx, reassignDevicesUnderBuilding); err != nil { return nil, fmt.Errorf("error preparing query ReassignDevicesUnderBuilding: %w", err) } + if q.reassignDevicesUnderBuildingsBulkStmt, err = db.PrepareContext(ctx, reassignDevicesUnderBuildingsBulk); err != nil { + return nil, fmt.Errorf("error preparing query ReassignDevicesUnderBuildingsBulk: %w", err) + } if q.reassignRacksUnderBuildingStmt, err = db.PrepareContext(ctx, reassignRacksUnderBuilding); err != nil { return nil, fmt.Errorf("error preparing query ReassignRacksUnderBuilding: %w", err) } + if q.reassignRacksUnderBuildingsBulkStmt, err = db.PrepareContext(ctx, reassignRacksUnderBuildingsBulk); err != nil { + return nil, fmt.Errorf("error preparing query ReassignRacksUnderBuildingsBulk: %w", err) + } if q.removeAllDevicesFromDeviceSetStmt, err = db.PrepareContext(ctx, removeAllDevicesFromDeviceSet); err != nil { return nil, fmt.Errorf("error preparing query RemoveAllDevicesFromDeviceSet: %w", err) } @@ -864,6 +879,12 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.setRackBuildingPositionStmt, err = db.PrepareContext(ctx, setRackBuildingPosition); err != nil { return nil, fmt.Errorf("error preparing query SetRackBuildingPosition: %w", err) } + if q.setRackBuildingPositionBulkClearStmt, err = db.PrepareContext(ctx, setRackBuildingPositionBulkClear); err != nil { + return nil, fmt.Errorf("error preparing query SetRackBuildingPositionBulkClear: %w", err) + } + if q.setRackBuildingPositionBulkPlaceStmt, err = db.PrepareContext(ctx, setRackBuildingPositionBulkPlace); err != nil { + return nil, fmt.Errorf("error preparing query SetRackBuildingPositionBulkPlace: %w", err) + } if q.setRackSlotPositionStmt, err = db.PrepareContext(ctx, setRackSlotPosition); err != nil { return nil, fmt.Errorf("error preparing query SetRackSlotPosition: %w", err) } @@ -1047,6 +1068,12 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.updateRackPlacementStmt, err = db.PrepareContext(ctx, updateRackPlacement); err != nil { return nil, fmt.Errorf("error preparing query UpdateRackPlacement: %w", err) } + if q.updateRackPlacementBulkForBuildingStmt, err = db.PrepareContext(ctx, updateRackPlacementBulkForBuilding); err != nil { + return nil, fmt.Errorf("error preparing query UpdateRackPlacementBulkForBuilding: %w", err) + } + if q.updateRackPlacementBulkForSiteStmt, err = db.PrepareContext(ctx, updateRackPlacementBulkForSite); err != nil { + return nil, fmt.Errorf("error preparing query UpdateRackPlacementBulkForSite: %w", err) + } if q.updateRoleStmt, err = db.PrepareContext(ctx, updateRole); err != nil { return nil, fmt.Errorf("error preparing query UpdateRole: %w", err) } @@ -1148,6 +1175,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing assignBuildingToSiteStmt: %w", cerr) } } + if q.assignBuildingsToSiteBulkStmt != nil { + if cerr := q.assignBuildingsToSiteBulkStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing assignBuildingsToSiteBulkStmt: %w", cerr) + } + } if q.assignDevicesToSiteStmt != nil { if cerr := q.assignDevicesToSiteStmt.Close(); cerr != nil { err = fmt.Errorf("error closing assignDevicesToSiteStmt: %w", cerr) @@ -1213,6 +1245,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing cascadeRackDeviceSitesStmt: %w", cerr) } } + if q.cascadeRackDeviceSitesBulkStmt != nil { + if cerr := q.cascadeRackDeviceSitesBulkStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing cascadeRackDeviceSitesBulkStmt: %w", cerr) + } + } if q.claimMessageForProcessingStmt != nil { if cerr := q.claimMessageForProcessingStmt.Close(); cerr != nil { err = fmt.Errorf("error closing claimMessageForProcessingStmt: %w", cerr) @@ -2363,6 +2400,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing lockSiteForWriteStmt: %w", cerr) } } + if q.lockSourceRacksForDevicesStmt != nil { + if cerr := q.lockSourceRacksForDevicesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing lockSourceRacksForDevicesStmt: %w", cerr) + } + } if q.markCommandBatchFinishedStmt != nil { if cerr := q.markCommandBatchFinishedStmt.Close(); cerr != nil { err = fmt.Errorf("error closing markCommandBatchFinishedStmt: %w", cerr) @@ -2433,11 +2475,21 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing reassignDevicesUnderBuildingStmt: %w", cerr) } } + if q.reassignDevicesUnderBuildingsBulkStmt != nil { + if cerr := q.reassignDevicesUnderBuildingsBulkStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing reassignDevicesUnderBuildingsBulkStmt: %w", cerr) + } + } if q.reassignRacksUnderBuildingStmt != nil { if cerr := q.reassignRacksUnderBuildingStmt.Close(); cerr != nil { err = fmt.Errorf("error closing reassignRacksUnderBuildingStmt: %w", cerr) } } + if q.reassignRacksUnderBuildingsBulkStmt != nil { + if cerr := q.reassignRacksUnderBuildingsBulkStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing reassignRacksUnderBuildingsBulkStmt: %w", cerr) + } + } if q.removeAllDevicesFromDeviceSetStmt != nil { if cerr := q.removeAllDevicesFromDeviceSetStmt.Close(); cerr != nil { err = fmt.Errorf("error closing removeAllDevicesFromDeviceSetStmt: %w", cerr) @@ -2518,6 +2570,16 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing setRackBuildingPositionStmt: %w", cerr) } } + if q.setRackBuildingPositionBulkClearStmt != nil { + if cerr := q.setRackBuildingPositionBulkClearStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing setRackBuildingPositionBulkClearStmt: %w", cerr) + } + } + if q.setRackBuildingPositionBulkPlaceStmt != nil { + if cerr := q.setRackBuildingPositionBulkPlaceStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing setRackBuildingPositionBulkPlaceStmt: %w", cerr) + } + } if q.setRackSlotPositionStmt != nil { if cerr := q.setRackSlotPositionStmt.Close(); cerr != nil { err = fmt.Errorf("error closing setRackSlotPositionStmt: %w", cerr) @@ -2823,6 +2885,16 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing updateRackPlacementStmt: %w", cerr) } } + if q.updateRackPlacementBulkForBuildingStmt != nil { + if cerr := q.updateRackPlacementBulkForBuildingStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateRackPlacementBulkForBuildingStmt: %w", cerr) + } + } + if q.updateRackPlacementBulkForSiteStmt != nil { + if cerr := q.updateRackPlacementBulkForSiteStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateRackPlacementBulkForSiteStmt: %w", cerr) + } + } if q.updateRoleStmt != nil { if cerr := q.updateRoleStmt.Close(); cerr != nil { err = fmt.Errorf("error closing updateRoleStmt: %w", cerr) @@ -2978,6 +3050,7 @@ type Queries struct { adminTerminateCurtailmentEventStmt *sql.Stmt allDevicesBelongToOrgStmt *sql.Stmt assignBuildingToSiteStmt *sql.Stmt + assignBuildingsToSiteBulkStmt *sql.Stmt assignDevicesToSiteStmt *sql.Stmt assignPermissionToRoleStmt *sql.Stmt assignRoleStmt *sql.Stmt @@ -2991,6 +3064,7 @@ type Queries struct { cancelPendingEnrollmentStmt *sql.Stmt cascadeAddedDeviceSitesStmt *sql.Stmt cascadeRackDeviceSitesStmt *sql.Stmt + cascadeRackDeviceSitesBulkStmt *sql.Stmt claimMessageForProcessingStmt *sql.Stmt clearRackPlacementForSoftDeleteStmt *sql.Stmt clearRackSlotPositionStmt *sql.Stmt @@ -3221,6 +3295,7 @@ type Queries struct { lockRackPlacementForWriteStmt *sql.Stmt lockSchedulePriorityStmt *sql.Stmt lockSiteForWriteStmt *sql.Stmt + lockSourceRacksForDevicesStmt *sql.Stmt markCommandBatchFinishedStmt *sql.Stmt markCommandBatchFinishedWithStartedAtStmt *sql.Stmt markCommandBatchProcessingStmt *sql.Stmt @@ -3235,7 +3310,9 @@ type Queries struct { reapStuckFirmwareUpdateMessagesStmt *sql.Stmt reapStuckProcessingMessagesStmt *sql.Stmt reassignDevicesUnderBuildingStmt *sql.Stmt + reassignDevicesUnderBuildingsBulkStmt *sql.Stmt reassignRacksUnderBuildingStmt *sql.Stmt + reassignRacksUnderBuildingsBulkStmt *sql.Stmt removeAllDevicesFromDeviceSetStmt *sql.Stmt removeDevicesFromAnyRackStmt *sql.Stmt removeDevicesFromDeviceSetStmt *sql.Stmt @@ -3252,6 +3329,8 @@ type Queries struct { setFleetNodeEnrollmentStatusStmt *sql.Stmt setMQTTSourceConfigEnabledStmt *sql.Stmt setRackBuildingPositionStmt *sql.Stmt + setRackBuildingPositionBulkClearStmt *sql.Stmt + setRackBuildingPositionBulkPlaceStmt *sql.Stmt setRackSlotPositionStmt *sql.Stmt setSchedulePrioritiesStmt *sql.Stmt setScheduleRunningStmt *sql.Stmt @@ -3313,6 +3392,8 @@ type Queries struct { updatePoolStmt *sql.Stmt updateRackInfoStmt *sql.Stmt updateRackPlacementStmt *sql.Stmt + updateRackPlacementBulkForBuildingStmt *sql.Stmt + updateRackPlacementBulkForSiteStmt *sql.Stmt updateRoleStmt *sql.Stmt updateScheduleStmt *sql.Stmt updateScheduleAfterRunStmt *sql.Stmt @@ -3347,6 +3428,7 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { adminTerminateCurtailmentEventStmt: q.adminTerminateCurtailmentEventStmt, allDevicesBelongToOrgStmt: q.allDevicesBelongToOrgStmt, assignBuildingToSiteStmt: q.assignBuildingToSiteStmt, + assignBuildingsToSiteBulkStmt: q.assignBuildingsToSiteBulkStmt, assignDevicesToSiteStmt: q.assignDevicesToSiteStmt, assignPermissionToRoleStmt: q.assignPermissionToRoleStmt, assignRoleStmt: q.assignRoleStmt, @@ -3360,6 +3442,7 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { cancelPendingEnrollmentStmt: q.cancelPendingEnrollmentStmt, cascadeAddedDeviceSitesStmt: q.cascadeAddedDeviceSitesStmt, cascadeRackDeviceSitesStmt: q.cascadeRackDeviceSitesStmt, + cascadeRackDeviceSitesBulkStmt: q.cascadeRackDeviceSitesBulkStmt, claimMessageForProcessingStmt: q.claimMessageForProcessingStmt, clearRackPlacementForSoftDeleteStmt: q.clearRackPlacementForSoftDeleteStmt, clearRackSlotPositionStmt: q.clearRackSlotPositionStmt, @@ -3590,6 +3673,7 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { lockRackPlacementForWriteStmt: q.lockRackPlacementForWriteStmt, lockSchedulePriorityStmt: q.lockSchedulePriorityStmt, lockSiteForWriteStmt: q.lockSiteForWriteStmt, + lockSourceRacksForDevicesStmt: q.lockSourceRacksForDevicesStmt, markCommandBatchFinishedStmt: q.markCommandBatchFinishedStmt, markCommandBatchFinishedWithStartedAtStmt: q.markCommandBatchFinishedWithStartedAtStmt, markCommandBatchProcessingStmt: q.markCommandBatchProcessingStmt, @@ -3604,7 +3688,9 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { reapStuckFirmwareUpdateMessagesStmt: q.reapStuckFirmwareUpdateMessagesStmt, reapStuckProcessingMessagesStmt: q.reapStuckProcessingMessagesStmt, reassignDevicesUnderBuildingStmt: q.reassignDevicesUnderBuildingStmt, + reassignDevicesUnderBuildingsBulkStmt: q.reassignDevicesUnderBuildingsBulkStmt, reassignRacksUnderBuildingStmt: q.reassignRacksUnderBuildingStmt, + reassignRacksUnderBuildingsBulkStmt: q.reassignRacksUnderBuildingsBulkStmt, removeAllDevicesFromDeviceSetStmt: q.removeAllDevicesFromDeviceSetStmt, removeDevicesFromAnyRackStmt: q.removeDevicesFromAnyRackStmt, removeDevicesFromDeviceSetStmt: q.removeDevicesFromDeviceSetStmt, @@ -3621,6 +3707,8 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { setFleetNodeEnrollmentStatusStmt: q.setFleetNodeEnrollmentStatusStmt, setMQTTSourceConfigEnabledStmt: q.setMQTTSourceConfigEnabledStmt, setRackBuildingPositionStmt: q.setRackBuildingPositionStmt, + setRackBuildingPositionBulkClearStmt: q.setRackBuildingPositionBulkClearStmt, + setRackBuildingPositionBulkPlaceStmt: q.setRackBuildingPositionBulkPlaceStmt, setRackSlotPositionStmt: q.setRackSlotPositionStmt, setSchedulePrioritiesStmt: q.setSchedulePrioritiesStmt, setScheduleRunningStmt: q.setScheduleRunningStmt, @@ -3682,6 +3770,8 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { updatePoolStmt: q.updatePoolStmt, updateRackInfoStmt: q.updateRackInfoStmt, updateRackPlacementStmt: q.updateRackPlacementStmt, + updateRackPlacementBulkForBuildingStmt: q.updateRackPlacementBulkForBuildingStmt, + updateRackPlacementBulkForSiteStmt: q.updateRackPlacementBulkForSiteStmt, updateRoleStmt: q.updateRoleStmt, updateScheduleStmt: q.updateScheduleStmt, updateScheduleAfterRunStmt: q.updateScheduleAfterRunStmt, diff --git a/server/generated/sqlc/device_set.sql.go b/server/generated/sqlc/device_set.sql.go index 316633c74..48f568a02 100644 --- a/server/generated/sqlc/device_set.sql.go +++ b/server/generated/sqlc/device_set.sql.go @@ -102,6 +102,37 @@ func (q *Queries) CascadeRackDeviceSites(ctx context.Context, arg CascadeRackDev return result.RowsAffected() } +const cascadeRackDeviceSitesBulk = `-- name: CascadeRackDeviceSitesBulk :execrows +UPDATE device d +SET site_id = $1::bigint, + updated_at = CURRENT_TIMESTAMP +FROM device_set_membership dsm +WHERE dsm.device_set_id = ANY($2::bigint[]) + AND dsm.org_id = $3 + AND dsm.device_set_type = 'rack' + AND dsm.device_id = d.id + AND d.deleted_at IS NULL + AND d.site_id IS DISTINCT FROM $1::bigint +` + +type CascadeRackDeviceSitesBulkParams struct { + TargetSiteID sql.NullInt64 + RackIds []int64 + OrgID int64 +} + +// Bulk variant of CascadeRackDeviceSites. Rewrites device.site_id to +// target_site_id for every paired member of every rack in @rack_ids. +// NULL target unassigns. IS DISTINCT FROM skips no-op rows. Returns +// the total affected row count across all racks. +func (q *Queries) CascadeRackDeviceSitesBulk(ctx context.Context, arg CascadeRackDeviceSitesBulkParams) (int64, error) { + result, err := q.exec(ctx, q.cascadeRackDeviceSitesBulkStmt, cascadeRackDeviceSitesBulk, arg.TargetSiteID, pq.Array(arg.RackIds), arg.OrgID) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + const clearRackPlacementForSoftDelete = `-- name: ClearRackPlacementForSoftDelete :exec UPDATE device_set_rack SET aisle_index = NULL, @@ -1145,6 +1176,58 @@ func (q *Queries) LockRackPlacementForWrite(ctx context.Context, arg LockRackPla return i, err } +const lockSourceRacksForDevices = `-- name: LockSourceRacksForDevices :many +SELECT DISTINCT dsm.device_set_id +FROM device_set_membership dsm +JOIN device_set ds ON ds.id = dsm.device_set_id +WHERE ds.org_id = $1 + AND dsm.device_set_type = 'rack' + AND ds.deleted_at IS NULL + AND dsm.device_identifier = ANY($2::text[]) + AND dsm.device_set_id != $3::bigint +ORDER BY dsm.device_set_id ASC +FOR UPDATE OF ds +` + +type LockSourceRacksForDevicesParams struct { + OrgID int64 + DeviceIdentifiers []string + ExcludeRackID int64 +} + +// Returns the distinct source rack ids currently holding any of the +// provided device identifiers, locked FOR UPDATE in ascending id order. +// AssignDevicesToRack calls this BEFORE the DELETE-from-source + +// INSERT-into-target cascade so concurrent reparent calls touching +// overlapping device sets serialize on the source rack rows instead of +// racing the device_set_membership unique constraint. Excludes the +// target rack (@exclude_rack_id) so the caller can take the target +// lock once via LockRackPlacementForWrite without re-locking here. +// Pass 0 for @exclude_rack_id to lock every source rack (clear-rack +// path where there is no target). +func (q *Queries) LockSourceRacksForDevices(ctx context.Context, arg LockSourceRacksForDevicesParams) ([]int64, error) { + rows, err := q.query(ctx, q.lockSourceRacksForDevicesStmt, lockSourceRacksForDevices, arg.OrgID, pq.Array(arg.DeviceIdentifiers), arg.ExcludeRackID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []int64 + for rows.Next() { + var device_set_id int64 + if err := rows.Scan(&device_set_id); err != nil { + return nil, err + } + items = append(items, device_set_id) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const removeAllDevicesFromDeviceSet = `-- name: RemoveAllDevicesFromDeviceSet :execrows DELETE FROM device_set_membership WHERE device_set_id = $1 @@ -1169,19 +1252,26 @@ DELETE FROM device_set_membership WHERE org_id = $1 AND device_identifier = ANY($2::text[]) AND device_set_type = 'rack' + AND device_set_id != $3::bigint ` type RemoveDevicesFromAnyRackParams struct { OrgID int64 DeviceIdentifiers []string -} - -// Removes the given devices from whatever rack they're currently in. -// AssignDevicesToRack uses this to clear prior rack membership inside -// the same transaction as the new-rack insert, closing the orphan -// window the client-side remove + add orchestration had. + TargetRackID int64 +} + +// Removes the given devices from whatever rack they're currently in, +// EXCEPT the target rack (@target_rack_id). AssignDevicesToRack uses +// this to clear prior rack membership inside the same transaction as +// the new-rack insert, closing the orphan window the client-side +// remove + add orchestration had. Skipping the target rack preserves +// the existing membership row (and its rack_slot child) when a device +// is reassigned to the same rack it's already in -- otherwise the +// DELETE would cascade rack_slot rows that we'd silently lose. Pass +// 0 for an unconditional clear (caller intends to unassign). func (q *Queries) RemoveDevicesFromAnyRack(ctx context.Context, arg RemoveDevicesFromAnyRackParams) (int64, error) { - result, err := q.exec(ctx, q.removeDevicesFromAnyRackStmt, removeDevicesFromAnyRack, arg.OrgID, pq.Array(arg.DeviceIdentifiers)) + result, err := q.exec(ctx, q.removeDevicesFromAnyRackStmt, removeDevicesFromAnyRack, arg.OrgID, pq.Array(arg.DeviceIdentifiers), arg.TargetRackID) if err != nil { return 0, err } @@ -1426,3 +1516,101 @@ func (q *Queries) UpdateRackPlacement(ctx context.Context, arg UpdateRackPlaceme ) return err } + +const updateRackPlacementBulkForBuilding = `-- name: UpdateRackPlacementBulkForBuilding :exec +UPDATE device_set_rack dsr +SET site_id = CASE + WHEN $1::bigint IS NULL THEN dsr.site_id + ELSE $2::bigint + END, + building_id = $1::bigint, + zone = CASE + WHEN dsr.building_id IS NOT NULL + AND dsr.building_id IS DISTINCT FROM $1::bigint + THEN '' + ELSE dsr.zone + END, + aisle_index = CASE + WHEN $1::bigint IS DISTINCT FROM dsr.building_id THEN NULL + ELSE dsr.aisle_index + END, + position_in_aisle = CASE + WHEN $1::bigint IS DISTINCT FROM dsr.building_id THEN NULL + ELSE dsr.position_in_aisle + END +WHERE dsr.device_set_id = ANY($3::bigint[]) + AND dsr.org_id = $4 + AND EXISTS ( + SELECT 1 FROM device_set ds + WHERE ds.id = dsr.device_set_id + AND ds.org_id = $4 + AND ds.deleted_at IS NULL + ) +` + +type UpdateRackPlacementBulkForBuildingParams struct { + TargetBuildingID sql.NullInt64 + TargetSiteID sql.NullInt64 + RackIds []int64 + OrgID int64 +} + +// Bulk variant of UpdateRackPlacement scoped to AssignRacksToBuilding. +// Sets site_id, building_id, and zone for every rack in @rack_ids in a +// single update. +// +// Semantics match the per-row UpdateRackPlacement + service-layer +// zone/site rules: +// +// - When @target_building_id IS NULL (unassign branch), each rack +// keeps its current site_id (no cascade fires later). Otherwise +// every rack is stamped with @target_site_id. +// - Zone clears to ” for any rack that had a building and is +// transitioning to a different (or NULL) building. Racks staying in +// the same building preserve their zone. +// - aisle_index / position_in_aisle clear when building_id changes, +// matching the single-row CASE. +func (q *Queries) UpdateRackPlacementBulkForBuilding(ctx context.Context, arg UpdateRackPlacementBulkForBuildingParams) error { + _, err := q.exec(ctx, q.updateRackPlacementBulkForBuildingStmt, updateRackPlacementBulkForBuilding, + arg.TargetBuildingID, + arg.TargetSiteID, + pq.Array(arg.RackIds), + arg.OrgID, + ) + return err +} + +const updateRackPlacementBulkForSite = `-- name: UpdateRackPlacementBulkForSite :exec +UPDATE device_set_rack dsr +SET site_id = $1::bigint, + building_id = NULL, + zone = '', + aisle_index = NULL, + position_in_aisle = NULL +WHERE dsr.device_set_id = ANY($2::bigint[]) + AND dsr.org_id = $3 + AND EXISTS ( + SELECT 1 FROM device_set ds + WHERE ds.id = dsr.device_set_id + AND ds.org_id = $3 + AND ds.deleted_at IS NULL + ) +` + +type UpdateRackPlacementBulkForSiteParams struct { + TargetSiteID sql.NullInt64 + RackIds []int64 + OrgID int64 +} + +// Bulk variant used by AssignRacksToSite. Stamps every rack in +// @rack_ids with the target site, clears building_id and zone (because +// a building belongs to one site, the rack's building membership is +// invalidated by any site transition), and clears the grid placement +// as a downstream effect of building_id changing. Caller is expected +// to only pass racks whose current site differs from the target so the +// response counts stay accurate. +func (q *Queries) UpdateRackPlacementBulkForSite(ctx context.Context, arg UpdateRackPlacementBulkForSiteParams) error { + _, err := q.exec(ctx, q.updateRackPlacementBulkForSiteStmt, updateRackPlacementBulkForSite, arg.TargetSiteID, pq.Array(arg.RackIds), arg.OrgID) + return err +} diff --git a/server/generated/sqlc/site.sql.go b/server/generated/sqlc/site.sql.go index 2fc2faf9a..40a6417ac 100644 --- a/server/generated/sqlc/site.sql.go +++ b/server/generated/sqlc/site.sql.go @@ -590,6 +590,42 @@ func (q *Queries) ReassignDevicesUnderBuilding(ctx context.Context, arg Reassign return result.RowsAffected() } +const reassignDevicesUnderBuildingsBulk = `-- name: ReassignDevicesUnderBuildingsBulk :execrows +UPDATE device d +SET site_id = $1, + updated_at = CURRENT_TIMESTAMP +FROM device_set_membership dsm +JOIN device_set ds + ON ds.id = dsm.device_set_id + AND ds.deleted_at IS NULL +JOIN device_set_rack dsr + ON dsr.device_set_id = dsm.device_set_id + AND dsr.org_id = dsm.org_id +WHERE d.id = dsm.device_id + AND d.org_id = dsm.org_id + AND dsm.device_set_type = 'rack' + AND d.org_id = $2 + AND dsr.building_id = ANY($3::bigint[]) + AND d.deleted_at IS NULL +` + +type ReassignDevicesUnderBuildingsBulkParams struct { + TargetSiteID sql.NullInt64 + OrgID int64 + BuildingIds []int64 +} + +// Bulk variant of ReassignDevicesUnderBuilding. Sets device.site_id = +// $target for every device in any live rack of any building in +// @building_ids. Caller wraps in the same tx as the building UPDATE. +func (q *Queries) ReassignDevicesUnderBuildingsBulk(ctx context.Context, arg ReassignDevicesUnderBuildingsBulkParams) (int64, error) { + result, err := q.exec(ctx, q.reassignDevicesUnderBuildingsBulkStmt, reassignDevicesUnderBuildingsBulk, arg.TargetSiteID, arg.OrgID, pq.Array(arg.BuildingIds)) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + const reassignRacksUnderBuilding = `-- name: ReassignRacksUnderBuilding :execrows UPDATE device_set_rack dsr SET site_id = $1 @@ -621,6 +657,36 @@ func (q *Queries) ReassignRacksUnderBuilding(ctx context.Context, arg ReassignRa return result.RowsAffected() } +const reassignRacksUnderBuildingsBulk = `-- name: ReassignRacksUnderBuildingsBulk :execrows +UPDATE device_set_rack dsr +SET site_id = $1 +WHERE dsr.org_id = $2 + AND dsr.building_id = ANY($3::bigint[]) + AND EXISTS ( + SELECT 1 FROM device_set ds + WHERE ds.id = dsr.device_set_id + AND ds.deleted_at IS NULL + ) +` + +type ReassignRacksUnderBuildingsBulkParams struct { + TargetSiteID sql.NullInt64 + OrgID int64 + BuildingIds []int64 +} + +// Bulk variant of ReassignRacksUnderBuilding. Sets rack.site_id = +// $target for every live rack pointing at any of @building_ids in one +// statement. Caller wraps in the same tx as the building UPDATE so +// the building/rack/device site_ids stay in lockstep. +func (q *Queries) ReassignRacksUnderBuildingsBulk(ctx context.Context, arg ReassignRacksUnderBuildingsBulkParams) (int64, error) { + result, err := q.exec(ctx, q.reassignRacksUnderBuildingsBulkStmt, reassignRacksUnderBuildingsBulk, arg.TargetSiteID, arg.OrgID, pq.Array(arg.BuildingIds)) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + const siteBelongsToOrg = `-- name: SiteBelongsToOrg :one SELECT EXISTS( SELECT 1 FROM site diff --git a/server/internal/domain/buildings/service.go b/server/internal/domain/buildings/service.go index 32821e4e2..1c54ac105 100644 --- a/server/internal/domain/buildings/service.go +++ b/server/internal/domain/buildings/service.go @@ -267,7 +267,8 @@ func (s *Service) ListBuildingRacks(ctx context.Context, orgID, buildingID int64 // order is building -> rack(s). // 2. Validate every entry up-front (paired position fields, in-bounds // aisle/position). The whole batch rejects on any invalid entry. -// 3. For each rack (sorted by id for deadlock-safe lock order): +// 3. Pass 1 — for each rack (sorted by id for deadlock-safe lock +// order): // a. Lock the rack row and read current placement. // b. Resolve the new site_id from the target building (or preserve // current.SiteID on building-only unassign). @@ -275,8 +276,17 @@ func (s *Service) ListBuildingRacks(ctx context.Context, orgID, buildingID int64 // d. Persist site_id + building_id + zone via UpdateRackPlacement. // e. Cascade descendant device.site_id when the site changes; sum // the per-rack counts into the aggregate result. -// f. Write the grid cell via SetRackBuildingPosition when the rack -// is assigned to a building (placed or explicitly cleared). +// f. When assigning (TargetBuildingID != nil), clear the rack's +// grid cell to (NULL, NULL) via SetRackBuildingPosition. +// 4. Pass 2 — for each rack that carries an explicit (aisle, position), +// write the final cell via SetRackBuildingPosition. +// +// The two-pass split is what lets a single batch contain heterogeneous +// position changes (swaps, "move into occupied cell", clear + reuse) +// without tripping uk_device_set_rack_building_position. By the time +// any rack tries to claim a cell in pass 2, every rack in the batch is +// guaranteed to hold NULL position — so no partial-unique-index +// collision can fire mid-batch. // // If any rack fails, the whole tx rolls back and no row is touched. func (s *Service) AssignRacksToBuilding(ctx context.Context, params models.AssignRacksToBuildingParams) (*models.AssignRacksToBuildingResult, error) { @@ -284,6 +294,18 @@ func (s *Service) AssignRacksToBuilding(ctx context.Context, params models.Assig return nil, fleeterror.NewInvalidArgumentError("racks must not be empty") } + // Reject duplicate rack_ids up front. The handler / proto layers + // don't enforce uniqueness, and the per-entry grid-cell write would + // silently clobber an earlier same-rack entry inside the tx — + // surface the inconsistency to the caller instead. + seenRackIDs := make(map[int64]struct{}, len(params.Racks)) + for _, rp := range params.Racks { + if _, dup := seenRackIDs[rp.RackID]; dup { + return nil, fleeterror.NewInvalidArgumentErrorf("duplicate rack_id %d in racks", rp.RackID) + } + seenRackIDs[rp.RackID] = struct{}{} + } + // Per-entry validation runs before any I/O so a bad request fails // fast without partial work. Defense-in-depth — the proto CEL rule // also enforces position pairing. @@ -317,6 +339,11 @@ func (s *Service) AssignRacksToBuilding(ctx context.Context, params models.Assig targetSiteID *int64 cascadeRackIDs []int64 positionedRackIDs []int64 + // For a building-only unassign (TargetBuildingID == nil) of a + // single rack, capture the rack's current SiteID so the activity + // log preserves "the site this rack lives in" instead of nil + // (which would make the event look site-less). + fallbackSiteID *int64 ) err := s.transactor.RunInTx(ctx, func(txCtx context.Context) error { // Lock the target building first (canonical lock order: @@ -352,80 +379,110 @@ func (s *Service) AssignRacksToBuilding(ctx context.Context, params models.Assig } } + // Phase A: sequential per-rack lock acquisition in sorted order. + // Locks must be acquired one-by-one to avoid the deadlock that + // would happen if two concurrent calls grabbed an overlapping + // rack set in different orders. Only locks + snapshot reads + // run here — every write happens in Phase B as a bulk + // statement once every row lock is held. + allRackIDs := make([]int64, 0, len(racks)) + // cascadeRackIDs is populated in this phase so the bulk + // CascadeRackDeviceSites call in Phase B knows which racks + // actually transitioned sites. It's still appended in + // sorted-rack order to keep the activity-log metadata stable. for _, rp := range racks { // Lock the rack row and read its current placement so we - // can decide whether the cascade needs to run + what zone - // value to persist. + // can decide whether the cascade needs to run later and + // capture per-rack state for the activity-log fallback. current, err := s.collectionStore.LockRackPlacementForWrite(txCtx, rp.RackID, params.OrgID) if err != nil { return err } + allRackIDs = append(allRackIDs, rp.RackID) + + // Capture the source SiteID for a single-rack building-only + // unassign so the activity log carries the rack's site + // instead of nil. Only meaningful when the batch is exactly + // one rack — multi-rack batches may straddle sites and + // surfacing the first rack's site would be misleading. + if params.TargetBuildingID == nil && len(racks) == 1 { + fallbackSiteID = current.SiteID + } // Building-only unassign must NOT cascade-clear the rack's // site (and, transitively, every descendant device.site_id). - // Removing a rack from a building is a building-membership - // change; the rack and its devices stay in their current - // site until an explicit site-level unassign happens - // elsewhere. Preserve current.SiteID in that branch so the - // siteChanged check below reads false and the cascade stays - // inert. + // Preserve current.SiteID in that branch so siteChanged + // reads false and the cascade stays inert. newSiteID := targetSiteID if params.TargetBuildingID == nil { newSiteID = current.SiteID } + if !int64PtrEqual(current.SiteID, newSiteID) { + cascadeRackIDs = append(cascadeRackIDs, rp.RackID) + } + } + + // Phase B1: single bulk write for site_id + building_id + zone + // + grid-position-on-building-change across every rack. The + // SQL CASE expressions mirror the per-row UpdateRackPlacement + + // service-layer zone rules so the swap/mixed-clear-and-place + // cases still behave like the F5 two-pass shape. + if err := s.collectionStore.UpdateRackPlacementBulkForBuilding( + txCtx, params.OrgID, allRackIDs, targetSiteID, params.TargetBuildingID, + ); err != nil { + return err + } - // Mirror SaveRack's zone-clear cascade: clear zone when the - // rack leaves a building or crosses to a different one. - // Preserve the current zone on a no-op building transition - // so legacy callers don't strip zone unintentionally. - finalZone := current.Zone - leavingBuilding := current.BuildingID != nil && params.TargetBuildingID == nil - crossingBuildings := current.BuildingID != nil && params.TargetBuildingID != nil && *current.BuildingID != *params.TargetBuildingID - if leavingBuilding || crossingBuildings { - finalZone = "" + // Phase B2: single bulk cascade for the subset of racks whose + // site actually changed. CascadeRackDeviceSitesBulk no-ops on + // an empty rack set, but skip the call to keep the wire log + // clean. + if len(cascadeRackIDs) > 0 { + count, err := s.collectionStore.CascadeRackDeviceSitesBulk( + txCtx, params.OrgID, cascadeRackIDs, targetSiteID, + ) + if err != nil { + return err } + out.SiteReassignedDeviceCount += count + } - // Persist site_id + building_id + zone in one write. The - // query also clears the grid position on building - // transition via a CASE expression, so a stale - // (aisle_index, position_in_aisle) never outlives its - // parent building. - if err := s.collectionStore.UpdateRackPlacement(txCtx, rp.RackID, params.OrgID, newSiteID, params.TargetBuildingID, finalZone); err != nil { + // Phase B3: single bulk pass-1 vacate. Force (aisle, position) + // to (NULL, NULL) for every rack in the batch so pass-2 below + // can reclaim any cell without colliding mid-batch on the + // partial unique index uk_device_set_rack_building_position. + // When TargetBuildingID is nil the UpdateRackPlacement bulk + // already nulled positions via its CASE — skip here. + if params.TargetBuildingID != nil { + if err := s.store.SetRackBuildingPositionBulkClear(txCtx, params.OrgID, allRackIDs); err != nil { return err } + positionedRackIDs = append(positionedRackIDs, allRackIDs...) + } - // Cascade descendant device.site_id when the rack's site - // changed. CascadeRackDeviceSites returns the row count. - siteChanged := !int64PtrEqual(current.SiteID, newSiteID) - if siteChanged { - count, err := s.collectionStore.CascadeRackDeviceSites(txCtx, rp.RackID, params.OrgID, newSiteID) - if err != nil { - return err + // Phase B4: single bulk pass-2 place for racks carrying a + // (aisle, position). Pass-1 vacated every cell touched by the + // batch so no two writes can collide. + if params.TargetBuildingID != nil { + var ( + placeRackIDs []int64 + placeAisles []int32 + placePos []int32 + ) + for _, rp := range racks { + if rp.AisleIndex == nil || rp.PositionInAisle == nil { + continue } - out.SiteReassignedDeviceCount += count - cascadeRackIDs = append(cascadeRackIDs, rp.RackID) + placeRackIDs = append(placeRackIDs, rp.RackID) + placeAisles = append(placeAisles, *rp.AisleIndex) + placePos = append(placePos, *rp.PositionInAisle) } - - // Grid-cell write. Two cases land here: - // - // - Both fields set → write the explicit (aisle, position). - // - Both fields nil + target_building_id is set → operator - // is unplacing the rack within the same building (or - // moving it across with no chosen cell yet). Write - // NULL/NULL so the cell on the rack row matches the - // operator's intent. UpdateRackPlacement's CASE only - // clears when building_id changes, so without this - // explicit write a same-building unplace would silently - // no-op and the old position would survive. - // - // When TargetBuildingID is nil (full unassign) we skip this - // call — UpdateRackPlacement's CASE already nulls the - // position via the building-id-changed branch. - if params.TargetBuildingID != nil { - if err := s.store.SetRackBuildingPosition(txCtx, params.OrgID, rp.RackID, rp.AisleIndex, rp.PositionInAisle); err != nil { + if len(placeRackIDs) > 0 { + if err := s.store.SetRackBuildingPositionBulkPlace( + txCtx, params.OrgID, placeRackIDs, placeAisles, placePos, + ); err != nil { return err } - positionedRackIDs = append(positionedRackIDs, rp.RackID) } } return nil @@ -444,11 +501,18 @@ func (s *Service) AssignRacksToBuilding(ctx context.Context, params models.Assig for i, rp := range racks { rackIDs[i] = rp.RackID } + // For a single-rack building-only unassign, fall back to the rack's + // own SiteID captured during the lock so the event still records + // which site the operator was working in. + eventSiteID := targetSiteID + if eventSiteID == nil && fallbackSiteID != nil { + eventSiteID = fallbackSiteID + } event := activitymodels.Event{ Category: activitymodels.CategoryFleetManagement, Type: eventRackAssignedBuilding, OrganizationID: &orgIDVal, - SiteID: targetSiteID, + SiteID: eventSiteID, Description: fmt.Sprintf( "Assigned %d rack(s) to building %v", len(racks), derefInt64(params.TargetBuildingID), diff --git a/server/internal/domain/buildings/service_test.go b/server/internal/domain/buildings/service_test.go index 7374137ec..436ab22cf 100644 --- a/server/internal/domain/buildings/service_test.go +++ b/server/internal/domain/buildings/service_test.go @@ -212,7 +212,8 @@ func newAssignHarness(t *testing.T) *assignHarness { } // Assign with a grid cell: lock building, lock rack, write placement, -// write grid cell, no site cascade because target site matches current. +// vacate cell in pass 1 (NULL/NULL), then write the actual cell in +// pass 2. No site cascade because target site matches current. func TestAssignRacksToBuilding_placesRackWithGridCell(t *testing.T) { h := newAssignHarness(t) buildingID := int64(11) @@ -223,12 +224,17 @@ func TestAssignRacksToBuilding_placesRackWithGridCell(t *testing.T) { h.siteStore.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, buildingID).Return(nil), h.store.EXPECT().GetBuilding(inTxCtx, testOrgID, buildingID). Return(&models.Building{ID: buildingID, SiteID: &siteID, Aisles: 4, RacksPerAisle: 6}, nil), + // Phase A: lock + read. h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackID, testOrgID). Return(interfaces.RackPlacement{SiteID: nil, BuildingID: nil, Zone: ""}, nil), - h.collectionStore.EXPECT().UpdateRackPlacement(inTxCtx, rackID, testOrgID, &siteID, &buildingID, "").Return(nil), - // siteChanged is true (nil -> &siteID); cascade fires. - h.collectionStore.EXPECT().CascadeRackDeviceSites(inTxCtx, rackID, testOrgID, &siteID).Return(int64(2), nil), - h.store.EXPECT().SetRackBuildingPosition(inTxCtx, testOrgID, rackID, ptrInt32(1), ptrInt32(2)).Return(nil), + // Phase B1: single bulk placement update. + h.collectionStore.EXPECT().UpdateRackPlacementBulkForBuilding(inTxCtx, testOrgID, []int64{rackID}, &siteID, &buildingID).Return(nil), + // Phase B2: single bulk cascade — siteChanged (nil -> &siteID). + h.collectionStore.EXPECT().CascadeRackDeviceSitesBulk(inTxCtx, testOrgID, []int64{rackID}, &siteID).Return(int64(2), nil), + // Phase B3: bulk pass-1 vacate. + h.store.EXPECT().SetRackBuildingPositionBulkClear(inTxCtx, testOrgID, []int64{rackID}).Return(nil), + // Phase B4: bulk pass-2 place. + h.store.EXPECT().SetRackBuildingPositionBulkPlace(inTxCtx, testOrgID, []int64{rackID}, []int32{1}, []int32{2}).Return(nil), ) out, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ @@ -266,8 +272,11 @@ func TestAssignRacksToBuilding_membersWithoutPositionClearsCell(t *testing.T) { Return(&models.Building{ID: buildingID, SiteID: &siteID, Aisles: 4, RacksPerAisle: 6}, nil) h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackID, testOrgID). Return(interfaces.RackPlacement{SiteID: &siteID}, nil) - h.collectionStore.EXPECT().UpdateRackPlacement(inTxCtx, rackID, testOrgID, &siteID, &buildingID, "").Return(nil) - h.store.EXPECT().SetRackBuildingPosition(inTxCtx, testOrgID, rackID, (*int32)(nil), (*int32)(nil)).Return(nil) + // No cascade — site unchanged. Bulk placement update + bulk pass-1 + // vacate fire; pass-2 place is skipped because no positions were + // requested. + h.collectionStore.EXPECT().UpdateRackPlacementBulkForBuilding(inTxCtx, testOrgID, []int64{rackID}, &siteID, &buildingID).Return(nil) + h.store.EXPECT().SetRackBuildingPositionBulkClear(inTxCtx, testOrgID, []int64{rackID}).Return(nil) _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ OrgID: testOrgID, @@ -280,10 +289,10 @@ func TestAssignRacksToBuilding_membersWithoutPositionClearsCell(t *testing.T) { } // Same-building unplace: rack already in this building at a known cell, -// caller resends building_id with no position. SetRackBuildingPosition -// must fire with nil/nil so the prior (aisle, position) is cleared from -// the rack row. Guards against the "unplace within building silently -// no-ops" regression. +// caller resends building_id with no position. The bulk pass-1 vacate +// is what clears the prior (aisle, position) so the unplace doesn't +// silently no-op. Guards against the "unplace within building silently +// no-ops" regression on the post-bulk refactor. func TestAssignRacksToBuilding_sameBuildingUnplaceClearsPosition(t *testing.T) { h := newAssignHarness(t) buildingID := int64(11) @@ -295,11 +304,11 @@ func TestAssignRacksToBuilding_sameBuildingUnplaceClearsPosition(t *testing.T) { Return(&models.Building{ID: buildingID, SiteID: &siteID, Aisles: 4, RacksPerAisle: 6}, nil) h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackID, testOrgID). Return(interfaces.RackPlacement{SiteID: &siteID, BuildingID: ptrInt64(buildingID), Zone: "Z1"}, nil) - // Same building → zone preserved → finalZone is "Z1". - h.collectionStore.EXPECT().UpdateRackPlacement(inTxCtx, rackID, testOrgID, &siteID, &buildingID, "Z1").Return(nil) - // No site change → no cascade. - // Critical: explicit position clear fires. - h.store.EXPECT().SetRackBuildingPosition(inTxCtx, testOrgID, rackID, (*int32)(nil), (*int32)(nil)).Return(nil) + // Bulk placement update — zone preservation is now decided in SQL + // per-row, so the bulk call only carries (target_site, target_building). + h.collectionStore.EXPECT().UpdateRackPlacementBulkForBuilding(inTxCtx, testOrgID, []int64{rackID}, &siteID, &buildingID).Return(nil) + // Critical: explicit bulk pass-1 vacate fires. + h.store.EXPECT().SetRackBuildingPositionBulkClear(inTxCtx, testOrgID, []int64{rackID}).Return(nil) _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ OrgID: testOrgID, @@ -323,9 +332,11 @@ func TestAssignRacksToBuilding_unassignPreservesSiteAndSkipsCascade(t *testing.T // No LockBuildingForWrite / GetBuilding expected — params.BuildingID is nil. h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackID, testOrgID). Return(interfaces.RackPlacement{SiteID: &siteID, BuildingID: ptrInt64(priorBuildingID), Zone: "Z1"}, nil) - // site stays &siteID; zone clears (leavingBuilding). - h.collectionStore.EXPECT().UpdateRackPlacement(inTxCtx, rackID, testOrgID, &siteID, (*int64)(nil), "").Return(nil) - // CascadeRackDeviceSites must NOT fire. + // Bulk placement update: site target nil (preserve), building target nil. + // CascadeRackDeviceSitesBulk must NOT fire — site is preserved. + // Bulk pass-1 vacate is skipped — building_id change inside SQL CASE + // nulls aisle/position automatically. + h.collectionStore.EXPECT().UpdateRackPlacementBulkForBuilding(inTxCtx, testOrgID, []int64{rackID}, (*int64)(nil), (*int64)(nil)).Return(nil) _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ OrgID: testOrgID, @@ -352,12 +363,12 @@ func TestAssignRacksToBuilding_crossBuildingClearsZoneAndCascadesSite(t *testing Return(&models.Building{ID: targetBuildingID, SiteID: &newSite, Aisles: 4, RacksPerAisle: 6}, nil) h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackID, testOrgID). Return(interfaces.RackPlacement{SiteID: &priorSite, BuildingID: ptrInt64(priorBuildingID), Zone: "Z1"}, nil) - // crossingBuildings ⇒ zone clears. - h.collectionStore.EXPECT().UpdateRackPlacement(inTxCtx, rackID, testOrgID, &newSite, &targetBuildingID, "").Return(nil) - h.collectionStore.EXPECT().CascadeRackDeviceSites(inTxCtx, rackID, testOrgID, &newSite).Return(int64(4), nil) - // Cross-building move with no chosen cell — explicit nil/nil - // position write confirms the new row carries no stale placement. - h.store.EXPECT().SetRackBuildingPosition(inTxCtx, testOrgID, rackID, (*int32)(nil), (*int32)(nil)).Return(nil) + // Bulk placement update — crossingBuildings zone clear runs in SQL. + h.collectionStore.EXPECT().UpdateRackPlacementBulkForBuilding(inTxCtx, testOrgID, []int64{rackID}, &newSite, &targetBuildingID).Return(nil) + // Bulk cascade fires for site-changed racks. + h.collectionStore.EXPECT().CascadeRackDeviceSitesBulk(inTxCtx, testOrgID, []int64{rackID}, &newSite).Return(int64(4), nil) + // Bulk pass-1 vacate confirms the new row carries no stale placement. + h.store.EXPECT().SetRackBuildingPositionBulkClear(inTxCtx, testOrgID, []int64{rackID}).Return(nil) out, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ OrgID: testOrgID, @@ -431,6 +442,251 @@ func TestAssignRacksToBuilding_rejectsPositionWithoutBuilding(t *testing.T) { } } +// TestAssignRacksToBuilding_emptyRejected guards the len(Racks) == 0 +// pre-check so callers learn up front instead of getting a 0-row +// response. +func TestAssignRacksToBuilding_emptyRejected(t *testing.T) { + h := newAssignHarness(t) + _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: ptrInt64(11), + Racks: nil, + }) + if err == nil { + t.Fatal("expected InvalidArgument for empty racks, got nil") + } + if h.tx.calls != 0 { + t.Fatalf("guard must reject before opening tx, got %d", h.tx.calls) + } +} + +// TestAssignRacksToBuilding_rejectsDuplicateRackIDs covers F19: bulk +// requests with the same rack id repeated must fail up-front so the +// per-entry grid-cell write doesn't silently clobber an earlier entry. +func TestAssignRacksToBuilding_rejectsDuplicateRackIDs(t *testing.T) { + h := newAssignHarness(t) + buildingID := int64(11) + + _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: &buildingID, + Racks: []models.RackPlacementParam{ + {RackID: 1, AisleIndex: ptrInt32(0), PositionInAisle: ptrInt32(0)}, + {RackID: 1, AisleIndex: ptrInt32(0), PositionInAisle: ptrInt32(1)}, + }, + }) + if err == nil { + t.Fatal("expected InvalidArgument for duplicate rack_ids, got nil") + } + if h.tx.calls != 0 { + t.Fatalf("guard must reject before opening tx, got %d", h.tx.calls) + } +} + +// TestAssignRacksToBuilding_bulkRollsBackOnLaterFailure mirrors the +// sites batch rollback case: first rack succeeds, second errors on the +// lock, the tx aborts, and the closure ran exactly once with the error +// propagating. +func TestAssignRacksToBuilding_bulkRollsBackOnLaterFailure(t *testing.T) { + h := newAssignHarness(t) + buildingID := int64(11) + siteID := int64(3) + + h.siteStore.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, buildingID).Return(nil) + h.store.EXPECT().GetBuilding(inTxCtx, testOrgID, buildingID). + Return(&models.Building{ID: buildingID, SiteID: &siteID, Aisles: 4, RacksPerAisle: 6}, nil) + // Phase A walks both rack ids in order; the second lock errors so + // the closure exits before any bulk write fires. + h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, int64(100), testOrgID). + Return(interfaces.RackPlacement{}, nil) + h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, int64(101), testOrgID). + Return(interfaces.RackPlacement{}, fleeterror.NewNotFoundErrorf("rack %d not found", 101)) + // No bulk UpdateRackPlacementBulkForBuilding / CascadeRackDeviceSitesBulk / + // SetRackBuildingPosition{Bulk}* calls — the closure aborts in Phase A. + _ = siteID + + _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: &buildingID, + Racks: []models.RackPlacementParam{ + {RackID: 100}, + {RackID: 101}, + }, + }) + if !fleeterror.IsNotFoundError(err) { + t.Fatalf("expected NotFound, got %v", err) + } + if h.tx.calls != 1 { + t.Fatalf("expected exactly 1 tx closure run, got %d", h.tx.calls) + } +} + +// TestAssignRacksToBuilding_swapsPositionsInSingleBatch covers F5: +// a single batch that swaps two racks' positions inside the same +// building must succeed in one tx. The service pre-clears every +// rack's position (pass 1) before writing any new positions (pass 2), +// so the partial unique index uk_device_set_rack_building_position +// can't see two rows trying to hold the same (building, aisle, pos) +// simultaneously. +func TestAssignRacksToBuilding_swapsPositionsInSingleBatch(t *testing.T) { + h := newAssignHarness(t) + buildingID := int64(11) + siteID := int64(3) + rackA := int64(100) + rackB := int64(101) + + // Racks are sorted by id, so rackA(100) is processed before rackB(101) + // during Phase A (lock acquisition). Phase B issues one bulk + // placement update, then one bulk pass-1 vacate covering both racks, + // then one bulk pass-2 place that writes the swapped positions in a + // single statement. + gomock.InOrder( + // Building lock + load. + h.siteStore.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, buildingID).Return(nil), + h.store.EXPECT().GetBuilding(inTxCtx, testOrgID, buildingID). + Return(&models.Building{ID: buildingID, SiteID: &siteID, Aisles: 4, RacksPerAisle: 6}, nil), + // Phase A: locks in sorted order, no writes. + h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackA, testOrgID). + Return(interfaces.RackPlacement{SiteID: &siteID, BuildingID: ptrInt64(buildingID), Zone: ""}, nil), + h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackB, testOrgID). + Return(interfaces.RackPlacement{SiteID: &siteID, BuildingID: ptrInt64(buildingID), Zone: ""}, nil), + // Phase B1: single bulk placement update across both racks. + h.collectionStore.EXPECT().UpdateRackPlacementBulkForBuilding(inTxCtx, testOrgID, []int64{rackA, rackB}, &siteID, &buildingID).Return(nil), + // Phase B2: no cascade — both racks stay in the same site. + // Phase B3: single bulk pass-1 vacate — critical for the swap. + h.store.EXPECT().SetRackBuildingPositionBulkClear(inTxCtx, testOrgID, []int64{rackA, rackB}).Return(nil), + // Phase B4: single bulk pass-2 place — both racks in one statement. + h.store.EXPECT().SetRackBuildingPositionBulkPlace( + inTxCtx, testOrgID, []int64{rackA, rackB}, []int32{0, 0}, []int32{1, 0}, + ).Return(nil), + ) + + _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: &buildingID, + Racks: []models.RackPlacementParam{ + // rackA: (0,0) -> (0,1) + {RackID: rackA, AisleIndex: ptrInt32(0), PositionInAisle: ptrInt32(1)}, + // rackB: (0,1) -> (0,0) + {RackID: rackB, AisleIndex: ptrInt32(0), PositionInAisle: ptrInt32(0)}, + }, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if h.tx.calls != 1 { + t.Fatalf("expected one tx run, got %d", h.tx.calls) + } +} + +// TestAssignRacksToBuilding_mixedClearAndPlaceInSingleBatch covers F5: +// one rack being unplaced + another rack moving into the freshly +// vacated cell must succeed in one batch. The clear write fires in +// pass 1 strictly before the place write in pass 2. +func TestAssignRacksToBuilding_mixedClearAndPlaceInSingleBatch(t *testing.T) { + h := newAssignHarness(t) + buildingID := int64(11) + siteID := int64(3) + rackClearer := int64(100) // was at (0,0), going to NULL + rackPlacer := int64(101) // was unplaced, going to (0,0) + + gomock.InOrder( + h.siteStore.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, buildingID).Return(nil), + h.store.EXPECT().GetBuilding(inTxCtx, testOrgID, buildingID). + Return(&models.Building{ID: buildingID, SiteID: &siteID, Aisles: 4, RacksPerAisle: 6}, nil), + // Phase A: locks in sorted order. + h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackClearer, testOrgID). + Return(interfaces.RackPlacement{SiteID: &siteID, BuildingID: ptrInt64(buildingID), Zone: ""}, nil), + h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackPlacer, testOrgID). + Return(interfaces.RackPlacement{SiteID: &siteID, BuildingID: ptrInt64(buildingID), Zone: ""}, nil), + // Phase B1: single bulk placement update across both racks. + h.collectionStore.EXPECT().UpdateRackPlacementBulkForBuilding(inTxCtx, testOrgID, []int64{rackClearer, rackPlacer}, &siteID, &buildingID).Return(nil), + // Phase B2: no cascade — both stay in the same site. + // Phase B3: bulk vacate covers both racks unconditionally + // (swap-safe invariant). + h.store.EXPECT().SetRackBuildingPositionBulkClear(inTxCtx, testOrgID, []int64{rackClearer, rackPlacer}).Return(nil), + // Phase B4: bulk pass-2 places only rackPlacer — rackClearer + // has no requested position and stays vacated. + h.store.EXPECT().SetRackBuildingPositionBulkPlace( + inTxCtx, testOrgID, []int64{rackPlacer}, []int32{0}, []int32{0}, + ).Return(nil), + ) + + _, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: &buildingID, + Racks: []models.RackPlacementParam{ + {RackID: rackClearer}, // clear cell + {RackID: rackPlacer, AisleIndex: ptrInt32(0), PositionInAisle: ptrInt32(0)}, + }, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if h.tx.calls != 1 { + t.Fatalf("expected one tx run, got %d", h.tx.calls) + } +} + +// TestAssignRacksToBuilding_largeBatchIssuesSingleBulkWrites guards the +// F7 bulk refactor: a 100-rack batch must produce exactly one +// UpdateRackPlacementBulkForBuilding + one CascadeRackDeviceSitesBulk +// + one SetRackBuildingPositionBulkClear + one SetRackBuildingPositionBulkPlace +// call regardless of N. +func TestAssignRacksToBuilding_largeBatchIssuesSingleBulkWrites(t *testing.T) { + h := newAssignHarness(t) + buildingID := int64(11) + siteID := int64(3) + + const N = 100 + wantRackIDs := make([]int64, N) + wantAisles := make([]int32, N) + wantPositions := make([]int32, N) + racks := make([]models.RackPlacementParam, N) + // Build distinct (aisle, position) values that fit in a 10×10 grid. + for i := 0; i < N; i++ { + id := int64(1000 + i) + wantRackIDs[i] = id + aisle := int32(i / 10) + pos := int32(i % 10) + wantAisles[i] = aisle + wantPositions[i] = pos + racks[i] = models.RackPlacementParam{ + RackID: id, AisleIndex: ptrInt32(aisle), PositionInAisle: ptrInt32(pos), + } + } + + h.siteStore.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, buildingID).Return(nil) + h.store.EXPECT().GetBuilding(inTxCtx, testOrgID, buildingID). + Return(&models.Building{ID: buildingID, SiteID: &siteID, Aisles: 10, RacksPerAisle: 10}, nil) + // Phase A: N per-rack lock acquisitions in sorted order — these are + // the only writes that fan out by N. + for _, id := range wantRackIDs { + h.collectionStore.EXPECT().LockRackPlacementForWrite(inTxCtx, id, testOrgID). + Return(interfaces.RackPlacement{}, nil) + } + // Phase B writes: exactly four bulk calls, regardless of N. + h.collectionStore.EXPECT().UpdateRackPlacementBulkForBuilding(inTxCtx, testOrgID, wantRackIDs, &siteID, &buildingID).Return(nil) + h.collectionStore.EXPECT().CascadeRackDeviceSitesBulk(inTxCtx, testOrgID, wantRackIDs, &siteID).Return(int64(200), nil) + h.store.EXPECT().SetRackBuildingPositionBulkClear(inTxCtx, testOrgID, wantRackIDs).Return(nil) + h.store.EXPECT().SetRackBuildingPositionBulkPlace(inTxCtx, testOrgID, wantRackIDs, wantAisles, wantPositions).Return(nil) + + out, err := h.svc.AssignRacksToBuilding(context.Background(), models.AssignRacksToBuildingParams{ + OrgID: testOrgID, + TargetBuildingID: &buildingID, + Racks: racks, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if out.SiteReassignedDeviceCount != 200 { + t.Fatalf("expected 200 cascaded devices, got %d", out.SiteReassignedDeviceCount) + } + if h.tx.calls != 1 { + t.Fatalf("expected one tx closure run, got %d", h.tx.calls) + } +} + // ListBuildingRacks just delegates to the store after an org-scoped // building existence check. func TestListBuildingRacks_returnsStoreResult(t *testing.T) { diff --git a/server/internal/domain/collection/service.go b/server/internal/domain/collection/service.go index 00076207b..49403f6bf 100644 --- a/server/internal/domain/collection/service.go +++ b/server/internal/domain/collection/service.go @@ -695,194 +695,150 @@ func (s *Service) ListCollections(ctx context.Context, req *pb.ListCollectionsRe return &pb.ListCollectionsResponse{Collections: collections, NextPageToken: nextPageToken, TotalCount: totalCount}, nil } -type membershipChangeResult struct { - collection *pb.DeviceCollection - count int64 - conflicts []interfaces.AddedDeviceSiteConflict - finalSiteID *int64 - cascadeCount int64 +// AddDevicesToGroupParams is the domain-layer input shape for adding +// devices to a group device set. TargetGroupID must point at a group; +// rack adds must go through AssignDevicesToRack to get atomic prior-rack +// removal + site cascade. +type AddDevicesToGroupParams struct { + TargetGroupID int64 + DeviceSelector *commonpb.DeviceSelector } -// AddDevicesToCollection adds devices to a collection. -func (s *Service) AddDevicesToCollection(ctx context.Context, req *pb.AddDevicesToCollectionRequest) (*pb.AddDevicesToCollectionResponse, error) { +// AddDevicesToGroupResult carries the added-row count for the activity +// log + handler response surface. +type AddDevicesToGroupResult struct { + AddedCount int64 +} + +// AddDevicesToGroup adds devices to a group device set. Groups are +// org-scoped (devices may span sites) so this skips the rack site +// cascade entirely. Rack targets are rejected with InvalidArgument. +func (s *Service) AddDevicesToGroup(ctx context.Context, params AddDevicesToGroupParams) (*AddDevicesToGroupResult, error) { info, err := session.GetInfo(ctx) if err != nil { return nil, err } - deviceIdentifiers, err := s.resolveDeviceIdentifiers(ctx, req.DeviceSelector, info.OrganizationID) + deviceIdentifiers, err := s.resolveDeviceIdentifiers(ctx, params.DeviceSelector, info.OrganizationID) if err != nil { return nil, err } + type txOut struct { + added int64 + label string + } result, err := s.transactor.RunInTxWithResult(ctx, func(ctx context.Context) (any, error) { - coll, err := s.collectionStore.GetCollection(ctx, info.OrganizationID, req.CollectionId) + coll, err := s.collectionStore.GetCollection(ctx, info.OrganizationID, params.TargetGroupID) if err != nil { return nil, err } - - // Lock rack FOR UPDATE so the cascade reads rack.site_id under a - // write lock that serializes against SiteService writers. Skip the - // site lock — that would invert canonical lock order and deadlock - // against concurrent site moves. Groups skip lock and cascade. - var ( - conflicts []interfaces.AddedDeviceSiteConflict - finalSiteID *int64 - cascadeCount int64 - ) - if coll.Type == pb.CollectionType_COLLECTION_TYPE_RACK { - placement, err := s.collectionStore.LockRackPlacementForWrite(ctx, req.CollectionId, info.OrganizationID) - if err != nil { - return nil, err - } - finalSiteID = placement.SiteID - if placement.SiteID != nil { - conflicts, err = s.collectionStore.GetAddedDeviceSiteConflicts(ctx, info.OrganizationID, req.CollectionId, deviceIdentifiers) - if err != nil { - return nil, err - } - } + if coll.Type != pb.CollectionType_COLLECTION_TYPE_GROUP { + return nil, fleeterror.NewInvalidArgumentErrorf("target_group_id %d is not a group", params.TargetGroupID) } - addedCount, err := s.collectionStore.AddDevicesToCollection(ctx, info.OrganizationID, req.CollectionId, deviceIdentifiers) + addedCount, err := s.collectionStore.AddDevicesToCollection(ctx, info.OrganizationID, params.TargetGroupID, deviceIdentifiers) if err != nil { return nil, err } - if coll.Type == pb.CollectionType_COLLECTION_TYPE_RACK && finalSiteID != nil { - n, err := s.collectionStore.CascadeAddedDeviceSites(ctx, info.OrganizationID, req.CollectionId, deviceIdentifiers) - if err != nil { - return nil, err - } - cascadeCount = n - } - - return &membershipChangeResult{ - collection: coll, - count: addedCount, - conflicts: conflicts, - finalSiteID: finalSiteID, - cascadeCount: cascadeCount, - }, nil + return &txOut{added: addedCount, label: coll.Label}, nil }) if err != nil { return nil, err } - - txResult, ok := result.(*membershipChangeResult) + out, ok := result.(*txOut) if !ok { return nil, fleeterror.NewInternalErrorf("unexpected result type: %T", result) } - addedCountInt := int(txResult.count) - scopeType := collectionScopeType(txResult.collection.Type) - label := txResult.collection.Label - addEvent := activitymodels.Event{ + addedCountInt := int(out.added) + scopeType := collectionScopeType(pb.CollectionType_COLLECTION_TYPE_GROUP) + s.logActivity(ctx, activitymodels.Event{ Category: activitymodels.CategoryCollection, Type: "add_devices", - Description: fmt.Sprintf("Add devices to %s: %s", scopeType, label), + Description: fmt.Sprintf("Add devices to %s: %s", scopeType, out.label), ScopeType: &scopeType, - ScopeLabel: &label, + ScopeLabel: &out.label, ScopeCount: &addedCountInt, UserID: &info.ExternalUserID, Username: &info.Username, OrganizationID: &info.OrganizationID, - SiteID: txResult.finalSiteID, - } - if len(txResult.conflicts) > 0 { - total := len(txResult.conflicts) - capacity := total - if capacity > maxCascadeAuditEntries { - capacity = maxCascadeAuditEntries - } - priors := make([]map[string]any, 0, capacity) - for i, c := range txResult.conflicts { - if i >= maxCascadeAuditEntries { - break - } - row := map[string]any{ - "device_identifier": c.DeviceIdentifier, - "target_site_id": c.TargetSiteID, - } - if c.PriorSiteID != nil { - row["prior_site_id"] = *c.PriorSiteID - } - priors = append(priors, row) - } - meta := map[string]any{ - "site_cascade": true, - "final_site_id": txResult.finalSiteID, - "site_reassigned_count": txResult.cascadeCount, - "device_site_changes": priors, - "total_affected": total, - } - if total > maxCascadeAuditEntries { - meta["truncated"] = true - } - addEvent.Metadata = meta - } - s.logActivity(ctx, addEvent) + }) - // #nosec G115 -- addedCount is bounded by request size which is limited by gRPC message size - return &pb.AddDevicesToCollectionResponse{ - CollectionId: req.CollectionId, - AddedCount: int32(txResult.count), - // #nosec G115 -- cascadeCount bounded by added member count - SiteReassignedCount: int32(txResult.cascadeCount), - }, nil + return &AddDevicesToGroupResult{AddedCount: out.added}, nil } -// RemoveDevicesFromCollection removes devices from a collection. -func (s *Service) RemoveDevicesFromCollection(ctx context.Context, req *pb.RemoveDevicesFromCollectionRequest) (*pb.RemoveDevicesFromCollectionResponse, error) { +// RemoveDevicesFromGroupParams is the domain-layer input shape for +// removing devices from a group device set. Rack targets are rejected +// with InvalidArgument; use AssignDevicesToRack (target_rack_id unset) +// to clear rack membership. +type RemoveDevicesFromGroupParams struct { + TargetGroupID int64 + DeviceSelector *commonpb.DeviceSelector +} + +// RemoveDevicesFromGroupResult carries the removed-row count for the +// activity log + handler response surface. +type RemoveDevicesFromGroupResult struct { + RemovedCount int64 +} + +// RemoveDevicesFromGroup removes devices from a group device set. +func (s *Service) RemoveDevicesFromGroup(ctx context.Context, params RemoveDevicesFromGroupParams) (*RemoveDevicesFromGroupResult, error) { info, err := session.GetInfo(ctx) if err != nil { return nil, err } - deviceIdentifiers, err := s.resolveDeviceIdentifiers(ctx, req.DeviceSelector, info.OrganizationID) + deviceIdentifiers, err := s.resolveDeviceIdentifiers(ctx, params.DeviceSelector, info.OrganizationID) if err != nil { return nil, err } + type txOut struct { + removed int64 + label string + } result, err := s.transactor.RunInTxWithResult(ctx, func(ctx context.Context) (any, error) { - coll, err := s.collectionStore.GetCollection(ctx, info.OrganizationID, req.CollectionId) + coll, err := s.collectionStore.GetCollection(ctx, info.OrganizationID, params.TargetGroupID) if err != nil { return nil, err } + if coll.Type != pb.CollectionType_COLLECTION_TYPE_GROUP { + return nil, fleeterror.NewInvalidArgumentErrorf("target_group_id %d is not a group", params.TargetGroupID) + } - removedCount, err := s.collectionStore.RemoveDevicesFromCollection(ctx, info.OrganizationID, req.CollectionId, deviceIdentifiers) + removedCount, err := s.collectionStore.RemoveDevicesFromCollection(ctx, info.OrganizationID, params.TargetGroupID, deviceIdentifiers) if err != nil { return nil, err } - return &membershipChangeResult{collection: coll, count: removedCount}, nil + return &txOut{removed: removedCount, label: coll.Label}, nil }) if err != nil { return nil, err } - - txResult, ok := result.(*membershipChangeResult) + out, ok := result.(*txOut) if !ok { return nil, fleeterror.NewInternalErrorf("unexpected result type: %T", result) } - removedCountInt := int(txResult.count) - scopeType := collectionScopeType(txResult.collection.Type) - label := txResult.collection.Label + removedCountInt := int(out.removed) + scopeType := collectionScopeType(pb.CollectionType_COLLECTION_TYPE_GROUP) s.logActivity(ctx, activitymodels.Event{ Category: activitymodels.CategoryCollection, Type: "remove_devices", - Description: fmt.Sprintf("Remove devices from %s: %s", scopeType, label), + Description: fmt.Sprintf("Remove devices from %s: %s", scopeType, out.label), ScopeType: &scopeType, - ScopeLabel: &label, + ScopeLabel: &out.label, ScopeCount: &removedCountInt, UserID: &info.ExternalUserID, Username: &info.Username, OrganizationID: &info.OrganizationID, }) - // #nosec G115 -- removedCount is bounded by request size which is limited by gRPC message size - return &pb.RemoveDevicesFromCollectionResponse{RemovedCount: int32(txResult.count)}, nil + return &RemoveDevicesFromGroupResult{RemovedCount: out.removed}, nil } // AssignDevicesToRackParams is the domain-layer input shape for the @@ -929,11 +885,39 @@ func (s *Service) AssignDevicesToRack(ctx context.Context, params AssignDevicesT targetSiteID *int64 targetLabel string ) - // Lock + verify target rack first so concurrent SaveRack / - // DeleteCollection on the target can't race us. Canonical lock - // order is rack-only here — site/building locks would invert - // against AddDevicesToCollection. + // Canonical lock order: source racks (ascending id) → target + // rack. LockSourceRacksForDevices takes FOR UPDATE on every + // rack currently holding any of the requested devices, in + // sorted order. Without this pre-pass, two concurrent + // AssignDevicesToRack calls touching overlapping device sets + // race the device_set_membership unique constraint: the loser + // trips uk_device_set_membership during the INSERT and the + // whole tx aborts. Locking the source rows first serializes + // the calls on the source rack and lets both succeed in turn. + // The query excludes excludeRackID so we don't double-lock the + // target (taken below via LockRackPlacementForWrite). Pass 0 + // in the clear-rack path where there is no target. + var excludeRackID int64 + if params.TargetRackID != nil { + excludeRackID = *params.TargetRackID + } + if _, err := s.collectionStore.LockSourceRacksForDevices(ctx, params.OrgID, params.DeviceIdentifiers, excludeRackID); err != nil { + return nil, err + } + + // Lock + verify target rack so concurrent SaveRack / + // DeleteCollection on the target can't race us. Site/building + // locks would invert against AddDevicesToCollection's cascade, + // so we stay rack-only here. + // + // Order: take the row lock BEFORE the label/type read so a + // concurrent rename can't slip a stale label into the activity + // log we emit downstream. if params.TargetRackID != nil { + placement, err := s.collectionStore.LockRackPlacementForWrite(ctx, *params.TargetRackID, params.OrgID) + if err != nil { + return nil, err + } coll, err := s.collectionStore.GetCollection(ctx, params.OrgID, *params.TargetRackID) if err != nil { return nil, err @@ -941,20 +925,19 @@ func (s *Service) AssignDevicesToRack(ctx context.Context, params AssignDevicesT if coll.Type != pb.CollectionType_COLLECTION_TYPE_RACK { return nil, fleeterror.NewInvalidArgumentErrorf("target_rack_id %d is not a rack", *params.TargetRackID) } - placement, err := s.collectionStore.LockRackPlacementForWrite(ctx, *params.TargetRackID, params.OrgID) - if err != nil { - return nil, err - } targetSiteID = placement.SiteID targetLabel = coll.Label } // Clear existing rack membership for the given devices regardless - // of which rack they sit in. This is the half of the operation - // that previously lived in the client-side - // RemoveDevicesFromDeviceSet call; bundling it into the tx is - // what closes the orphan window described in #420. - removed, err := s.collectionStore.RemoveDevicesFromAnyRack(ctx, params.OrgID, params.DeviceIdentifiers) + // of which rack they sit in EXCEPT the target rack. Excluding + // the target preserves the membership row + rack_slot child for + // devices that are already in the target rack (a same-rack + // re-add would otherwise cascade-drop the rack_slot row). This + // is the half of the operation that previously lived in the + // client-side RemoveDevicesFromDeviceSet call; bundling it into + // the tx is what closes the orphan window described in #420. + removed, err := s.collectionStore.RemoveDevicesFromAnyRack(ctx, params.OrgID, params.DeviceIdentifiers, excludeRackID) if err != nil { return nil, err } @@ -1010,7 +993,11 @@ func (s *Service) AssignDevicesToRack(ctx context.Context, params AssignDevicesT } else { eventDescription = "Cleared devices from rack" } - assignedInt := int(out.assigned + out.removed) + // ScopeCount is the number of devices the caller asked to touch, + // not the sum of per-step row counts (assigned + removed can + // double-count devices that were both removed from a prior rack + // and added to a new one). + assignedInt := len(params.DeviceIdentifiers) event := activitymodels.Event{ Category: activitymodels.CategoryCollection, Type: "assign_devices_to_rack", diff --git a/server/internal/domain/collection/service_test.go b/server/internal/domain/collection/service_test.go index f53d2994c..cd783cb1e 100644 --- a/server/internal/domain/collection/service_test.go +++ b/server/internal/domain/collection/service_test.go @@ -292,7 +292,7 @@ func TestService_DeleteCollection_NotFoundWhenZeroRows(t *testing.T) { assert.True(t, fleeterror.IsNotFoundError(err)) } -func TestService_AddDevicesToCollection_NotFoundWhenNotOwnedByOrg(t *testing.T) { +func TestService_AddDevicesToGroup_NotFoundWhenNotOwnedByOrg(t *testing.T) { ctrl := gomock.NewController(t) mockStore := mocks.NewMockCollectionStore(ctrl) mockTransactor := mocks.NewMockTransactor(ctrl) @@ -313,8 +313,8 @@ func TestService_AddDevicesToCollection_NotFoundWhenNotOwnedByOrg(t *testing.T) Return(nil, fleeterror.NewNotFoundErrorf("collection not found")) // Act - _, err := svc.AddDevicesToCollection(ctx, &pb.AddDevicesToCollectionRequest{ - CollectionId: testCollectionID, + _, err := svc.AddDevicesToGroup(ctx, AddDevicesToGroupParams{ + TargetGroupID: testCollectionID, DeviceSelector: &commonpb.DeviceSelector{ SelectionType: &commonpb.DeviceSelector_DeviceList{ DeviceList: &commonpb.DeviceIdentifierList{DeviceIdentifiers: []string{"device-1"}}, @@ -400,7 +400,7 @@ func TestService_GetRackSlots_RejectsGroupCollection(t *testing.T) { assert.True(t, fleeterror.IsInvalidArgumentError(err)) } -func TestService_AddDevicesToCollection_ResolverError(t *testing.T) { +func TestService_AddDevicesToGroup_ResolverError(t *testing.T) { ctrl := gomock.NewController(t) mockStore := mocks.NewMockCollectionStore(ctrl) mockTransactor := mocks.NewMockTransactor(ctrl) @@ -413,8 +413,8 @@ func TestService_AddDevicesToCollection_ResolverError(t *testing.T) { svc := NewService(mockStore, &mockDeviceQueryer{}, nil, nil, mockTransactor, resolver, nil, newStubActivityService(ctrl)) // Act - _, err := svc.AddDevicesToCollection(ctx, &pb.AddDevicesToCollectionRequest{ - CollectionId: testCollectionID, + _, err := svc.AddDevicesToGroup(ctx, AddDevicesToGroupParams{ + TargetGroupID: testCollectionID, DeviceSelector: &commonpb.DeviceSelector{ SelectionType: &commonpb.DeviceSelector_DeviceList{ DeviceList: &commonpb.DeviceIdentifierList{DeviceIdentifiers: []string{"device-1"}}, @@ -1785,67 +1785,11 @@ func TestService_SaveRack_OmittedPlacementPreservesZone(t *testing.T) { _ = mockSiteStore } -// TestService_AddDevicesToCollection_CascadesRackSite covers the -// AddDevicesToDeviceSet cascade flow (issue #220): when devices are -// added to a rack that has a site stamped, every paired device whose -// current site_id differs is rewritten to the rack's site_id in the -// same transaction. Group targets remain org-scoped. -func TestService_AddDevicesToCollection_CascadesRackSite(t *testing.T) { - deviceIDs := []string{"device-1", "device-2"} - resolver := func(_ context.Context, _ *commonpb.DeviceSelector, _ int64) ([]string, error) { - return deviceIDs, nil - } - svc, mockStore, _, captured := newTestServiceWithSitesRecordingActivity(t, resolver) - ctx := testCtx(t) - - collectionID := int64(42) - rackSite := int64(7) - priorSite := int64(11) - - mockStore.EXPECT().GetCollection(gomock.Any(), testOrgID, collectionID). - Return(&pb.DeviceCollection{Id: collectionID, Label: "Rack A", Type: pb.CollectionType_COLLECTION_TYPE_RACK}, nil) - mockStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), collectionID, testOrgID). - Return(interfaces.RackPlacement{SiteID: &rackSite}, nil) - mockStore.EXPECT().GetAddedDeviceSiteConflicts(gomock.Any(), testOrgID, collectionID, deviceIDs). - Return([]interfaces.AddedDeviceSiteConflict{ - {DeviceIdentifier: "device-1", PriorSiteID: &priorSite, TargetSiteID: rackSite}, - }, nil) - mockStore.EXPECT().AddDevicesToCollection(gomock.Any(), testOrgID, collectionID, deviceIDs).Return(int64(2), nil) - mockStore.EXPECT().CascadeAddedDeviceSites(gomock.Any(), testOrgID, collectionID, deviceIDs).Return(int64(1), nil) - - resp, err := svc.AddDevicesToCollection(ctx, &pb.AddDevicesToCollectionRequest{ - CollectionId: collectionID, - DeviceSelector: &commonpb.DeviceSelector{ - SelectionType: &commonpb.DeviceSelector_DeviceList{ - DeviceList: &commonpb.DeviceIdentifierList{DeviceIdentifiers: deviceIDs}, - }, - }, - }) - require.NoError(t, err) - assert.Equal(t, int32(2), resp.AddedCount) - assert.Equal(t, int32(1), resp.SiteReassignedCount, "response carries the cascade row count") - - // Assert cascade metadata on the activity event. - require.Len(t, *captured, 1) - event := (*captured)[0] - assert.Equal(t, "add_devices", event.Type) - require.NotNil(t, event.SiteID) - assert.Equal(t, rackSite, *event.SiteID) - require.NotNil(t, event.Metadata) - assert.Equal(t, true, event.Metadata["site_cascade"]) - assert.Equal(t, int64(1), event.Metadata["site_reassigned_count"]) - priors, ok := event.Metadata["device_site_changes"].([]map[string]any) - require.True(t, ok) - require.Len(t, priors, 1) - assert.Equal(t, "device-1", priors[0]["device_identifier"]) - assert.Equal(t, priorSite, priors[0]["prior_site_id"]) - assert.Equal(t, rackSite, priors[0]["target_site_id"]) -} - -// TestService_AddDevicesToCollection_GroupTargetSkipsCascade asserts -// the cascade exemption for groups: plan §"Cross-collection consistency -// rule" — groups are org-scoped and may span sites by design. -func TestService_AddDevicesToCollection_GroupTargetSkipsCascade(t *testing.T) { +// TestService_AddDevicesToGroup_HappyPath covers the group add flow: +// groups are org-scoped (cross-site allowed) so there is no rack lock, +// no LockSiteForWrite, and no cascade — just the membership insert +// plus an activity event. +func TestService_AddDevicesToGroup_HappyPath(t *testing.T) { deviceIDs := []string{"device-1"} resolver := func(_ context.Context, _ *commonpb.DeviceSelector, _ int64) ([]string, error) { return deviceIDs, nil @@ -1856,12 +1800,10 @@ func TestService_AddDevicesToCollection_GroupTargetSkipsCascade(t *testing.T) { collectionID := int64(43) mockStore.EXPECT().GetCollection(gomock.Any(), testOrgID, collectionID). Return(&pb.DeviceCollection{Id: collectionID, Label: "G1", Type: pb.CollectionType_COLLECTION_TYPE_GROUP}, nil) - // No LockRackPlacementForWrite, no LockSiteForWrite, no cascade - // expectations — groups skip the rack-site invariant entirely. mockStore.EXPECT().AddDevicesToCollection(gomock.Any(), testOrgID, collectionID, deviceIDs).Return(int64(1), nil) - resp, err := svc.AddDevicesToCollection(ctx, &pb.AddDevicesToCollectionRequest{ - CollectionId: collectionID, + resp, err := svc.AddDevicesToGroup(ctx, AddDevicesToGroupParams{ + TargetGroupID: collectionID, DeviceSelector: &commonpb.DeviceSelector{ SelectionType: &commonpb.DeviceSelector_DeviceList{ DeviceList: &commonpb.DeviceIdentifierList{DeviceIdentifiers: deviceIDs}, @@ -1869,13 +1811,14 @@ func TestService_AddDevicesToCollection_GroupTargetSkipsCascade(t *testing.T) { }, }) require.NoError(t, err) - assert.Equal(t, int32(1), resp.AddedCount) + assert.Equal(t, int64(1), resp.AddedCount) } -// TestService_AddDevicesToCollection_RackWithoutSiteSkipsCascade asserts -// that adding devices to a rack whose site_id is NULL still inserts the -// membership but does not run the cascade — there is no site to enforce. -func TestService_AddDevicesToCollection_RackWithoutSiteSkipsCascade(t *testing.T) { +// TestService_AddDevicesToGroup_RejectsRackTarget covers the type +// guard: rack targets must go through AssignDevicesToRack to get the +// atomic prior-rack removal + site cascade. The group endpoint rejects +// rack targets with InvalidArgument before any store mutation. +func TestService_AddDevicesToGroup_RejectsRackTarget(t *testing.T) { deviceIDs := []string{"device-1"} resolver := func(_ context.Context, _ *commonpb.DeviceSelector, _ int64) ([]string, error) { return deviceIDs, nil @@ -1885,22 +1828,18 @@ func TestService_AddDevicesToCollection_RackWithoutSiteSkipsCascade(t *testing.T collectionID := int64(44) mockStore.EXPECT().GetCollection(gomock.Any(), testOrgID, collectionID). - Return(&pb.DeviceCollection{Id: collectionID, Label: "Site-less Rack", Type: pb.CollectionType_COLLECTION_TYPE_RACK}, nil) - mockStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), collectionID, testOrgID). - Return(interfaces.RackPlacement{}, nil) // no site stamped - // No GetAddedDeviceSiteConflicts, no CascadeAddedDeviceSites. - mockStore.EXPECT().AddDevicesToCollection(gomock.Any(), testOrgID, collectionID, deviceIDs).Return(int64(1), nil) + Return(&pb.DeviceCollection{Id: collectionID, Label: "Rack-X", Type: pb.CollectionType_COLLECTION_TYPE_RACK}, nil) - resp, err := svc.AddDevicesToCollection(ctx, &pb.AddDevicesToCollectionRequest{ - CollectionId: collectionID, + _, err := svc.AddDevicesToGroup(ctx, AddDevicesToGroupParams{ + TargetGroupID: collectionID, DeviceSelector: &commonpb.DeviceSelector{ SelectionType: &commonpb.DeviceSelector_DeviceList{ DeviceList: &commonpb.DeviceIdentifierList{DeviceIdentifiers: deviceIDs}, }, }, }) - require.NoError(t, err) - assert.Equal(t, int32(1), resp.AddedCount) + require.Error(t, err) + assert.True(t, fleeterror.IsInvalidArgumentError(err)) } // TestService_AssignDevicesToRack_atomicReassign covers the issue @@ -1916,11 +1855,13 @@ func TestService_AssignDevicesToRack_atomicReassign(t *testing.T) { deviceIDs := []string{"d1", "d2"} gomock.InOrder( - mockStore.EXPECT().GetCollection(gomock.Any(), testOrgID, targetRackID). - Return(&pb.DeviceCollection{Id: targetRackID, Label: "Rack-B", Type: pb.CollectionType_COLLECTION_TYPE_RACK}, nil), + mockStore.EXPECT().LockSourceRacksForDevices(gomock.Any(), testOrgID, deviceIDs, targetRackID). + Return([]int64{11, 23}, nil), mockStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), targetRackID, testOrgID). Return(interfaces.RackPlacement{SiteID: &rackSite}, nil), - mockStore.EXPECT().RemoveDevicesFromAnyRack(gomock.Any(), testOrgID, deviceIDs).Return(int64(2), nil), + mockStore.EXPECT().GetCollection(gomock.Any(), testOrgID, targetRackID). + Return(&pb.DeviceCollection{Id: targetRackID, Label: "Rack-B", Type: pb.CollectionType_COLLECTION_TYPE_RACK}, nil), + mockStore.EXPECT().RemoveDevicesFromAnyRack(gomock.Any(), testOrgID, deviceIDs, targetRackID).Return(int64(2), nil), mockStore.EXPECT().AddDevicesToCollection(gomock.Any(), testOrgID, targetRackID, deviceIDs).Return(int64(2), nil), mockStore.EXPECT().CascadeAddedDeviceSites(gomock.Any(), testOrgID, targetRackID, deviceIDs).Return(int64(1), nil), ) @@ -1944,7 +1885,11 @@ func TestService_AssignDevicesToRack_unassignClearsWithoutAdd(t *testing.T) { ctx := testCtx(t) deviceIDs := []string{"d1"} - mockStore.EXPECT().RemoveDevicesFromAnyRack(gomock.Any(), testOrgID, deviceIDs).Return(int64(1), nil) + gomock.InOrder( + mockStore.EXPECT().LockSourceRacksForDevices(gomock.Any(), testOrgID, deviceIDs, int64(0)). + Return([]int64{17}, nil), + mockStore.EXPECT().RemoveDevicesFromAnyRack(gomock.Any(), testOrgID, deviceIDs, int64(0)).Return(int64(1), nil), + ) out, err := svc.AssignDevicesToRack(ctx, AssignDevicesToRackParams{ OrgID: testOrgID, @@ -1964,6 +1909,10 @@ func TestService_AssignDevicesToRack_targetMustBeRack(t *testing.T) { ctx := testCtx(t) targetID := int64(99) + mockStore.EXPECT().LockSourceRacksForDevices(gomock.Any(), testOrgID, []string{"d1"}, targetID). + Return(nil, nil) + mockStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), targetID, testOrgID). + Return(interfaces.RackPlacement{}, nil) mockStore.EXPECT().GetCollection(gomock.Any(), testOrgID, targetID). Return(&pb.DeviceCollection{Id: targetID, Label: "G1", Type: pb.CollectionType_COLLECTION_TYPE_GROUP}, nil) @@ -1975,6 +1924,63 @@ func TestService_AssignDevicesToRack_targetMustBeRack(t *testing.T) { require.Error(t, err) } +// TestService_AssignDevicesToRack_acquiresSourceRackLocksBeforeWrites +// pins F9's lock-order invariant: LockSourceRacksForDevices must run +// BEFORE LockRackPlacementForWrite and BEFORE RemoveDevicesFromAnyRack. +// Reversing this risks deadlock against a concurrent call that takes +// the locks in the opposite order, and skipping it altogether is what +// lets concurrent overlapping reparent calls race the +// device_set_membership unique constraint. +func TestService_AssignDevicesToRack_acquiresSourceRackLocksBeforeWrites(t *testing.T) { + svc, mockStore, _ := newTestServiceWithSites(t, nil) + ctx := testCtx(t) + + targetRackID := int64(42) + deviceIDs := []string{"d1", "d2"} + + gomock.InOrder( + mockStore.EXPECT().LockSourceRacksForDevices(gomock.Any(), testOrgID, deviceIDs, targetRackID). + Return([]int64{11, 23}, nil), + mockStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), targetRackID, testOrgID). + Return(interfaces.RackPlacement{}, nil), + mockStore.EXPECT().GetCollection(gomock.Any(), testOrgID, targetRackID). + Return(&pb.DeviceCollection{Id: targetRackID, Label: "Rack-B", Type: pb.CollectionType_COLLECTION_TYPE_RACK}, nil), + mockStore.EXPECT().RemoveDevicesFromAnyRack(gomock.Any(), testOrgID, deviceIDs, targetRackID).Return(int64(2), nil), + mockStore.EXPECT().AddDevicesToCollection(gomock.Any(), testOrgID, targetRackID, deviceIDs).Return(int64(2), nil), + ) + + _, err := svc.AssignDevicesToRack(ctx, AssignDevicesToRackParams{ + OrgID: testOrgID, + TargetRackID: &targetRackID, + DeviceIdentifiers: deviceIDs, + }) + require.NoError(t, err) +} + +// TestService_AssignDevicesToRack_unassignPathLocksSourceRacks pins +// that the source-rack lock pre-pass ALSO fires on the clear-rack +// path. excludeRackID is 0 so every source rack holding any of the +// requested devices is locked. +func TestService_AssignDevicesToRack_unassignPathLocksSourceRacks(t *testing.T) { + svc, mockStore, _ := newTestServiceWithSites(t, nil) + ctx := testCtx(t) + + deviceIDs := []string{"d1", "d2"} + + gomock.InOrder( + mockStore.EXPECT().LockSourceRacksForDevices(gomock.Any(), testOrgID, deviceIDs, int64(0)). + Return([]int64{5, 12}, nil), + mockStore.EXPECT().RemoveDevicesFromAnyRack(gomock.Any(), testOrgID, deviceIDs, int64(0)).Return(int64(2), nil), + ) + + _, err := svc.AssignDevicesToRack(ctx, AssignDevicesToRackParams{ + OrgID: testOrgID, + TargetRackID: nil, + DeviceIdentifiers: deviceIDs, + }) + require.NoError(t, err) +} + // TestService_AssignDevicesToRack_emptyDevicesRejected guards the // empty-input edge so callers get InvalidArgument instead of a // silent 0-row response. diff --git a/server/internal/domain/fleetimport/importer.go b/server/internal/domain/fleetimport/importer.go index cd1ab4340..c870c3c79 100644 --- a/server/internal/domain/fleetimport/importer.go +++ b/server/internal/domain/fleetimport/importer.go @@ -12,6 +12,7 @@ import ( collectionpb "github.com/block/proto-fleet/server/generated/grpc/collection/v1" commonpb "github.com/block/proto-fleet/server/generated/grpc/common/v1" poolspb "github.com/block/proto-fleet/server/generated/grpc/pools/v1" + "github.com/block/proto-fleet/server/internal/domain/collection" "github.com/block/proto-fleet/server/internal/domain/fleeterror" models "github.com/block/proto-fleet/server/internal/domain/miner/models" "github.com/block/proto-fleet/server/internal/domain/stores/interfaces" @@ -42,7 +43,8 @@ type PoolCreator interface { // collection service (with validation, transactions, and activity logging). type CollectionManager interface { CreateCollection(ctx context.Context, req *collectionpb.CreateCollectionRequest) (*collectionpb.CreateCollectionResponse, error) - AddDevicesToCollection(ctx context.Context, req *collectionpb.AddDevicesToCollectionRequest) (*collectionpb.AddDevicesToCollectionResponse, error) + AddDevicesToGroup(ctx context.Context, params collection.AddDevicesToGroupParams) (*collection.AddDevicesToGroupResult, error) + AssignDevicesToRack(ctx context.Context, params collection.AssignDevicesToRackParams) (*collection.AssignDevicesToRackResult, error) ListCollections(ctx context.Context, req *collectionpb.ListCollectionsRequest) (*collectionpb.ListCollectionsResponse, error) } @@ -77,7 +79,7 @@ func (imp *Importer) Import(ctx context.Context, orgID int64, data *ImportData) macToDevice := imp.resolveMACToDevice(ctx, orgID, data.Miners) result.PoolsCreated = imp.createPools(ctx, data.Pools) - result.GroupsCreated, result.RacksCreated, result.DevicesAssigned = imp.createGroupsAndRacks(ctx, data, macToDevice) + result.GroupsCreated, result.RacksCreated, result.DevicesAssigned = imp.createGroupsAndRacks(ctx, orgID, data, macToDevice) result.WorkerNamesSet = imp.setWorkerNames(ctx, data, macToDevice) result.MinerNamesSet = imp.setMinerNames(ctx, orgID, data, macToDevice) return result @@ -232,7 +234,7 @@ func deviceSelector(deviceIDs []string) *commonpb.DeviceSelector { } } -func (imp *Importer) createGroupsAndRacks(ctx context.Context, data *ImportData, macToDevice map[string]*interfaces.PairedDeviceInfo) (int32, int32, int32) { +func (imp *Importer) createGroupsAndRacks(ctx context.Context, orgID int64, data *ImportData, macToDevice map[string]*interfaces.PairedDeviceInfo) (int32, int32, int32) { // Pre-fetch existing collections so we can reconcile duplicates existingCollections := imp.buildExistingCollectionMap(ctx) @@ -301,14 +303,14 @@ func (imp *Importer) createGroupsAndRacks(ctx context.Context, data *ImportData, if existingID, ok := existingCollections[key]; ok { // Existing group — just add devices if len(deviceIDs) > 0 { - resp, err := imp.collectionManager.AddDevicesToCollection(ctx, &collectionpb.AddDevicesToCollectionRequest{ - CollectionId: existingID, + resp, err := imp.collectionManager.AddDevicesToGroup(ctx, collection.AddDevicesToGroupParams{ + TargetGroupID: existingID, DeviceSelector: deviceSelector(deviceIDs), }) if err != nil { slog.Warn("failed to add devices to group", "group", g.Name, "error", err) } else { - devicesAssigned += resp.AddedCount + devicesAssigned += int32(resp.AddedCount) //nolint:gosec // bounded by device count } } } else { @@ -326,13 +328,13 @@ func (imp *Importer) createGroupsAndRacks(ctx context.Context, data *ImportData, // Race: group was created after we built existingCollections — refresh and add devices. existingCollections = imp.buildExistingCollectionMap(ctx) if existingID, ok := existingCollections[key]; ok && len(deviceIDs) > 0 { - if addResp, addErr := imp.collectionManager.AddDevicesToCollection(ctx, &collectionpb.AddDevicesToCollectionRequest{ - CollectionId: existingID, + if addResp, addErr := imp.collectionManager.AddDevicesToGroup(ctx, collection.AddDevicesToGroupParams{ + TargetGroupID: existingID, DeviceSelector: deviceSelector(deviceIDs), }); addErr != nil { slog.Warn("failed to add devices to group after duplicate", "group", g.Name, "error", addErr) } else { - devicesAssigned += addResp.AddedCount + devicesAssigned += int32(addResp.AddedCount) //nolint:gosec // bounded by device count } } continue @@ -374,16 +376,20 @@ func (imp *Importer) createGroupsAndRacks(ctx context.Context, data *ImportData, } if existingID, ok := existingCollections[key]; ok { - // Existing rack — just add devices + // Existing rack — assign devices atomically. Bulk import + // targets fresh devices with no prior rack, so the atomic + // prior-rack-clear in AssignDevicesToRack is a no-op. if len(rackDeviceIDs) > 0 { - resp, err := imp.collectionManager.AddDevicesToCollection(ctx, &collectionpb.AddDevicesToCollectionRequest{ - CollectionId: existingID, - DeviceSelector: deviceSelector(rackDeviceIDs), + rackID := existingID + resp, err := imp.collectionManager.AssignDevicesToRack(ctx, collection.AssignDevicesToRackParams{ + OrgID: orgID, + TargetRackID: &rackID, + DeviceIdentifiers: rackDeviceIDs, }) if err != nil { - slog.Warn("failed to add devices to rack", "rack", r.Name, "error", err) + slog.Warn("failed to assign devices to rack", "rack", r.Name, "error", err) } else { - devicesAssigned += resp.AddedCount + devicesAssigned += int32(resp.AssignedCount) //nolint:gosec // bounded by device count } } } else { @@ -407,16 +413,18 @@ func (imp *Importer) createGroupsAndRacks(ctx context.Context, data *ImportData, slog.Warn("failed to create rack", "name", r.Name, "error", err) continue } - // Race: rack was created after we built existingCollections — refresh and add devices. + // Race: rack was created after we built existingCollections — refresh and assign devices. existingCollections = imp.buildExistingCollectionMap(ctx) if existingID, ok := existingCollections[key]; ok && len(rackDeviceIDs) > 0 { - if addResp, addErr := imp.collectionManager.AddDevicesToCollection(ctx, &collectionpb.AddDevicesToCollectionRequest{ - CollectionId: existingID, - DeviceSelector: deviceSelector(rackDeviceIDs), + rackID := existingID + if addResp, addErr := imp.collectionManager.AssignDevicesToRack(ctx, collection.AssignDevicesToRackParams{ + OrgID: orgID, + TargetRackID: &rackID, + DeviceIdentifiers: rackDeviceIDs, }); addErr != nil { - slog.Warn("failed to add devices to rack after duplicate", "rack", r.Name, "error", addErr) + slog.Warn("failed to assign devices to rack after duplicate", "rack", r.Name, "error", addErr) } else { - devicesAssigned += addResp.AddedCount + devicesAssigned += int32(addResp.AssignedCount) //nolint:gosec // bounded by device count } } continue diff --git a/server/internal/domain/foremanimport/service_test.go b/server/internal/domain/foremanimport/service_test.go index 0b3b05247..428b1d864 100644 --- a/server/internal/domain/foremanimport/service_test.go +++ b/server/internal/domain/foremanimport/service_test.go @@ -12,6 +12,7 @@ import ( collectionpb "github.com/block/proto-fleet/server/generated/grpc/collection/v1" pb "github.com/block/proto-fleet/server/generated/grpc/foremanimport/v1" poolspb "github.com/block/proto-fleet/server/generated/grpc/pools/v1" + "github.com/block/proto-fleet/server/internal/domain/collection" "github.com/block/proto-fleet/server/internal/domain/stores/interfaces" "github.com/block/proto-fleet/server/internal/domain/stores/interfaces/mocks" "github.com/block/proto-fleet/server/internal/infrastructure/foreman" @@ -64,20 +65,25 @@ func (f *fakeCollectionManager) CreateCollection(_ context.Context, _ *collectio }, nil } -func (f *fakeCollectionManager) AddDevicesToCollection(_ context.Context, req *collectionpb.AddDevicesToCollectionRequest) (*collectionpb.AddDevicesToCollectionResponse, error) { +func (f *fakeCollectionManager) AddDevicesToGroup(_ context.Context, params collection.AddDevicesToGroupParams) (*collection.AddDevicesToGroupResult, error) { f.mu.Lock() defer f.mu.Unlock() var count int32 - if sel := req.GetDeviceSelector(); sel != nil { + if sel := params.DeviceSelector; sel != nil { if dl := sel.GetDeviceList(); dl != nil { count = int32(len(dl.DeviceIdentifiers)) //nolint:gosec } } f.devicesAdded += count - return &collectionpb.AddDevicesToCollectionResponse{ - CollectionId: req.CollectionId, - AddedCount: count, - }, nil + return &collection.AddDevicesToGroupResult{AddedCount: int64(count)}, nil +} + +func (f *fakeCollectionManager) AssignDevicesToRack(_ context.Context, params collection.AssignDevicesToRackParams) (*collection.AssignDevicesToRackResult, error) { + f.mu.Lock() + defer f.mu.Unlock() + count := int32(len(params.DeviceIdentifiers)) //nolint:gosec + f.devicesAdded += count + return &collection.AssignDevicesToRackResult{AssignedCount: int64(count)}, nil } func (f *fakeCollectionManager) ListCollections(_ context.Context, _ *collectionpb.ListCollectionsRequest) (*collectionpb.ListCollectionsResponse, error) { diff --git a/server/internal/domain/sites/models/models.go b/server/internal/domain/sites/models/models.go index 9dd4b3231..6dd113908 100644 --- a/server/internal/domain/sites/models/models.go +++ b/server/internal/domain/sites/models/models.go @@ -97,10 +97,19 @@ type PerDeviceConflict struct { // AssignDevicesToSiteParams is the input shape for the bulk assign // flow. TargetSiteID == nil means "Unassigned". +// +// When ForceClearConflictingRackMembership is true the service, inside +// the same transaction as the site write, drops any existing rack +// membership for the listed devices before applying the site update. +// This closes the cross-site reparent orphan window the client-side +// remove-then-reassign loop in MinerReparentPicker had. When false +// (default), a device sitting in a rack at a different site rejects +// the whole batch with PerDeviceConflict[]. type AssignDevicesToSiteParams struct { - OrgID int64 - TargetSiteID *int64 - DeviceIdentifiers []string + OrgID int64 + TargetSiteID *int64 + DeviceIdentifiers []string + ForceClearConflictingRackMembership bool } // AssignBuildingsToSiteParams is the input shape for the bulk diff --git a/server/internal/domain/sites/service.go b/server/internal/domain/sites/service.go index 5167046b5..2ef796099 100644 --- a/server/internal/domain/sites/service.go +++ b/server/internal/domain/sites/service.go @@ -318,6 +318,36 @@ func (s *Service) AssignDevicesToSite(ctx context.Context, params models.AssignD if err != nil { return err } + // When the caller opted into the force-clear branch, treat + // DEVICE_IN_RACK_AT_OTHER_SITE conflicts as a cascade-clear + // signal instead of a rejection. DEVICE_NOT_FOUND still aborts + // — the caller can't move what doesn't exist. We filter the + // conflict list and require collectionStore to be wired before + // touching the rack rows. + if params.ForceClearConflictingRackMembership && len(conflicts) > 0 { + filtered := conflicts[:0] + hasRackConflict := false + for _, c := range conflicts { + if c.Reason == models.ReasonDeviceInRackAtOtherSite { + hasRackConflict = true + continue + } + filtered = append(filtered, c) + } + conflicts = filtered + if hasRackConflict { + if s.collectionStore == nil { + return fleeterror.NewInternalErrorf("force-clear branch requires a collection store") + } + // Pass targetRackID=0 so the helper clears every rack + // membership for these devices — there's no target rack + // here, just a site move. The F2 fix made this branch + // safe (0 means "exclude nothing"). + if _, err := s.collectionStore.RemoveDevicesFromAnyRack(txCtx, params.OrgID, identifiers, 0); err != nil { + return err + } + } + } if len(conflicts) > 0 { // Don't return a sentinel error — SQLTransactor wraps non- // FleetError errors as Internal, which would surface as a @@ -379,6 +409,12 @@ func (s *Service) AssignBuildingsToSite(ctx context.Context, params models.Assig if len(buildingIDs) == 0 { return nil, fleeterror.NewInvalidArgumentError("building_ids must not be empty") } + // Reject an explicit target_site_id == 0 so callers don't confuse + // "Unassigned" (TargetSiteID == nil) with a zero-valued site row + // they forgot to populate. nil stays the sentinel for unassign. + if params.TargetSiteID != nil && *params.TargetSiteID == 0 { + return nil, fleeterror.NewInvalidArgumentError("target_site_id must be > 0 (use nil for Unassigned)") + } // Sort for stable lock order: deadlock-safe against concurrent // AssignBuildingsToSite touching an overlapping building set. sort.Slice(buildingIDs, func(i, j int) bool { return buildingIDs[i] < buildingIDs[j] }) @@ -398,31 +434,45 @@ func (s *Service) AssignBuildingsToSite(ctx context.Context, params models.Assig return err } } + // Phase A: sequential per-building lock acquisition in sorted + // order so a concurrent DeleteSite owning the source site can't + // clear racks under any of these buildings between our reads + // and writes. Locks must be acquired one-by-one — bulk lock + // acquisition would risk a deadlock against another + // AssignBuildingsToSite touching an overlapping building set. for _, buildingID := range buildingIDs { - // Lock the building so a concurrent DeleteSite that owns the - // source site can't clear this building's racks while we - // reassign them. Same site→building lock order DeleteSite uses. if err := s.store.LockBuildingForWrite(txCtx, params.OrgID, buildingID); err != nil { return err } - rowsAffected, err := s.store.AssignBuildingToSite(txCtx, params.OrgID, buildingID, params.TargetSiteID) - if err != nil { - return err - } - if rowsAffected == 0 { - return fleeterror.NewNotFoundErrorf("building %d not found", buildingID) - } - racks, err := s.store.ReassignRacksUnderBuilding(txCtx, params.OrgID, buildingID, params.TargetSiteID) - if err != nil { - return err - } - rackCount += racks - devices, err := s.store.ReassignDevicesUnderBuilding(txCtx, params.OrgID, buildingID, params.TargetSiteID) - if err != nil { - return err - } - deviceCount += devices } + + // Phase B1: single bulk write moving every locked building to + // the target site. The row count tells us whether every + // requested building is live; mismatch surfaces as NotFound + // (matches the per-row check the old loop performed). + rowsAffected, err := s.store.AssignBuildingsToSiteBulk(txCtx, params.OrgID, buildingIDs, params.TargetSiteID) + if err != nil { + return err + } + if rowsAffected != int64(len(buildingIDs)) { + return fleeterror.NewNotFoundErrorf("one or more buildings not found (expected %d, updated %d)", len(buildingIDs), rowsAffected) + } + + // Phase B2: single bulk rack cascade across every building in + // the batch. + racks, err := s.store.ReassignRacksUnderBuildingsBulk(txCtx, params.OrgID, buildingIDs, params.TargetSiteID) + if err != nil { + return err + } + rackCount = racks + + // Phase B3: single bulk device cascade across every building + // in the batch. + devices, err := s.store.ReassignDevicesUnderBuildingsBulk(txCtx, params.OrgID, buildingIDs, params.TargetSiteID) + if err != nil { + return err + } + deviceCount = devices return nil }) if err != nil { @@ -475,6 +525,12 @@ func (s *Service) AssignRacksToSite(ctx context.Context, params models.AssignRac if len(rackIDs) == 0 { return nil, fleeterror.NewInvalidArgumentError("rack_ids must not be empty") } + // Reject an explicit target_site_id == 0 so callers don't confuse + // "Unassigned" (TargetSiteID == nil) with a zero-valued site row + // they forgot to populate. nil stays the sentinel for unassign. + if params.TargetSiteID != nil && *params.TargetSiteID == 0 { + return nil, fleeterror.NewInvalidArgumentError("target_site_id must be > 0 (use nil for Unassigned)") + } sort.Slice(rackIDs, func(i, j int) bool { return rackIDs[i] < rackIDs[j] }) var ( @@ -491,35 +547,48 @@ func (s *Service) AssignRacksToSite(ctx context.Context, params models.AssignRac return err } } + // Phase A: sequential per-rack lock acquisition in sorted order + // (deadlock-safe). Per-rack reads classify each rack into the + // "site changed" bucket (which drives both the SQL write set + // and the activity-log metadata) or the same-site no-op + // bucket. + var changedRackIDs []int64 for _, rackID := range rackIDs { current, err := s.collectionStore.LockRackPlacementForWrite(txCtx, rackID, params.OrgID) if err != nil { return err } siteChanged := !int64PtrEqual(current.SiteID, params.TargetSiteID) - // building_id is bound to a single site, so any site - // transition invalidates the rack's building membership. - // Even an unchanged building_id read can't survive a site - // move — the operator must re-pick a building in the new - // site. - newBuildingID := current.BuildingID - finalZone := current.Zone - if siteChanged && current.BuildingID != nil { - newBuildingID = nil - finalZone = "" + if !siteChanged { + continue + } + changedRackIDs = append(changedRackIDs, rackID) + cascadedRackIDs = append(cascadedRackIDs, rackID) + if current.BuildingID != nil { clearedCount++ } - if err := s.collectionStore.UpdateRackPlacement(txCtx, rackID, params.OrgID, params.TargetSiteID, newBuildingID, finalZone); err != nil { + } + + // Phase B1: single bulk update for every rack whose site + // actually changes. building_id, zone, and grid placement + // clear together because a building is bound to one site and + // the partial unique index would otherwise leave stale cells + // behind. Skip the round-trip when no rack changes site. + if len(changedRackIDs) > 0 { + if err := s.collectionStore.UpdateRackPlacementBulkForSite( + txCtx, params.OrgID, changedRackIDs, params.TargetSiteID, + ); err != nil { return err } - if siteChanged { - n, err := s.collectionStore.CascadeRackDeviceSites(txCtx, rackID, params.OrgID, params.TargetSiteID) - if err != nil { - return err - } - deviceCount += n - cascadedRackIDs = append(cascadedRackIDs, rackID) + + // Phase B2: single bulk cascade for the same set. + n, err := s.collectionStore.CascadeRackDeviceSitesBulk( + txCtx, params.OrgID, changedRackIDs, params.TargetSiteID, + ) + if err != nil { + return err } + deviceCount += n } return nil }) diff --git a/server/internal/domain/sites/service_test.go b/server/internal/domain/sites/service_test.go index 3f89e3272..91975bd6a 100644 --- a/server/internal/domain/sites/service_test.go +++ b/server/internal/domain/sites/service_test.go @@ -339,6 +339,204 @@ func TestAssignDevicesToSite_targetMatchesCurrentRackSiteIsNotAConflict(t *testi } } +// TestAssignDevicesToSite_forceClearCascadesRackMembership pins the +// cross-site reparent path: when the caller passes the force-clear +// flag and devices live in racks at other sites, the service drops +// every rack membership for those devices inside the same tx and +// then applies the site write. No conflicts surface to the caller. +func TestAssignDevicesToSite_forceClearCascadesRackMembership(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + collStore := mocks.NewMockCollectionStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, collStore, nil, nil, tx, nil) + + identifiers := []string{"d1", "d2"} + target := int64(20) + conflictingSite := int64(30) + + store.EXPECT().LockDevicesForReassign(inTxCtx, testOrgID, identifiers).Return(nil) + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, target).Return(nil) + store.EXPECT().ListExistingDeviceIdentifiers(inTxCtx, testOrgID, identifiers).Return(identifiers, nil) + store.EXPECT().FindDeviceSiteConflicts(inTxCtx, testOrgID, identifiers).Return(map[string]int64{ + "d1": conflictingSite, + }, nil) + // targetRackID=0 means "clear every rack membership for these + // devices" — there's no target rack in a site move. + collStore.EXPECT().RemoveDevicesFromAnyRack(inTxCtx, testOrgID, identifiers, int64(0)).Return(int64(1), nil) + store.EXPECT().AssignDevicesToSite(inTxCtx, testOrgID, gomock.AssignableToTypeOf(ptrInt64(0)), identifiers).Return(int64(2), nil) + + count, conflicts, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ + OrgID: testOrgID, + TargetSiteID: &target, + DeviceIdentifiers: identifiers, + ForceClearConflictingRackMembership: true, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if count != 2 { + t.Fatalf("expected 2 rows updated, got %d", count) + } + if len(conflicts) != 0 { + t.Fatalf("expected zero conflicts on cascade-clear success, got %v", conflicts) + } + if tx.calls != 1 { + t.Fatalf("expected one tx run, got %d", tx.calls) + } +} + +// TestAssignDevicesToSite_forceClearWithoutConflicts is the no-op +// branch: when the flag is true but nothing conflicts, the service +// must not call RemoveDevicesFromAnyRack — the cascade clear is only +// for the rack-at-other-site case. +func TestAssignDevicesToSite_forceClearWithoutConflicts(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + collStore := mocks.NewMockCollectionStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, collStore, nil, nil, tx, nil) + + identifiers := []string{"d1"} + target := int64(20) + + store.EXPECT().LockDevicesForReassign(inTxCtx, testOrgID, identifiers).Return(nil) + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, target).Return(nil) + store.EXPECT().ListExistingDeviceIdentifiers(inTxCtx, testOrgID, identifiers).Return(identifiers, nil) + store.EXPECT().FindDeviceSiteConflicts(inTxCtx, testOrgID, identifiers).Return(map[string]int64{}, nil) + // No RemoveDevicesFromAnyRack expectation: gomock fails the test + // if it's called. + store.EXPECT().AssignDevicesToSite(inTxCtx, testOrgID, gomock.AssignableToTypeOf(ptrInt64(0)), identifiers).Return(int64(1), nil) + + count, conflicts, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ + OrgID: testOrgID, + TargetSiteID: &target, + DeviceIdentifiers: identifiers, + ForceClearConflictingRackMembership: true, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if count != 1 { + t.Fatalf("expected 1 row updated, got %d", count) + } + if len(conflicts) != 0 { + t.Fatalf("expected zero conflicts, got %v", conflicts) + } +} + +// TestAssignDevicesToSite_forceClearMissingDeviceStillRejects covers +// the partial-conflict case: cascade-clear handles the rack-site +// mismatch, but a DEVICE_NOT_FOUND still aborts the batch (you can't +// move a device that doesn't exist). The store's AssignDevicesToSite +// must NOT be called when at least one device-not-found conflict +// remains. +func TestAssignDevicesToSite_forceClearMissingDeviceStillRejects(t *testing.T) { + for _, tf := range transactorFactories { + t.Run(tf.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + collStore := mocks.NewMockCollectionStore(ctrl) + tx := tf.make() + svc := NewService(store, nil, collStore, nil, nil, tx, nil) + + identifiers := []string{"d1", "d-missing"} + target := int64(20) + conflictingSite := int64(30) + + store.EXPECT().LockDevicesForReassign(inTxCtx, testOrgID, identifiers).Return(nil) + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, target).Return(nil) + // Only d1 exists; d-missing is reported as not found. + store.EXPECT().ListExistingDeviceIdentifiers(inTxCtx, testOrgID, identifiers).Return([]string{"d1"}, nil) + store.EXPECT().FindDeviceSiteConflicts(inTxCtx, testOrgID, identifiers).Return(map[string]int64{ + "d1": conflictingSite, + }, nil) + // Cascade clear still fires for the rack conflict; the + // remaining DEVICE_NOT_FOUND rejects the batch before any + // site write. + collStore.EXPECT().RemoveDevicesFromAnyRack(inTxCtx, testOrgID, identifiers, int64(0)).Return(int64(1), nil) + // No AssignDevicesToSite expectation: batch must reject. + + count, conflicts, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ + OrgID: testOrgID, + TargetSiteID: &target, + DeviceIdentifiers: identifiers, + ForceClearConflictingRackMembership: true, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if count != 0 { + t.Fatalf("expected zero rows on rejection, got %d", count) + } + if len(conflicts) != 1 { + t.Fatalf("expected one remaining conflict, got %d", len(conflicts)) + } + if conflicts[0].Reason != models.ReasonDeviceNotFound { + t.Fatalf("expected ReasonDeviceNotFound, got %v", conflicts[0].Reason) + } + }) + } +} + +// TestAssignDevicesToSite_forceClearRollsBackOnSiteWriteFailure pins +// the rollback contract: if AssignDevicesToSite fails after the +// cascade clear, the whole tx aborts — the cascade-clear write is +// undone alongside the failed site write. The wrappingFakeTransactor +// also pins that a non-FleetError surfaces as Internal in prod. +func TestAssignDevicesToSite_forceClearRollsBackOnSiteWriteFailure(t *testing.T) { + for _, tf := range transactorFactories { + t.Run(tf.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + collStore := mocks.NewMockCollectionStore(ctrl) + tx := tf.make() + svc := NewService(store, nil, collStore, nil, nil, tx, nil) + + identifiers := []string{"d1"} + target := int64(20) + conflictingSite := int64(30) + sentinel := errors.New("site write boom") + + store.EXPECT().LockDevicesForReassign(inTxCtx, testOrgID, identifiers).Return(nil) + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, target).Return(nil) + store.EXPECT().ListExistingDeviceIdentifiers(inTxCtx, testOrgID, identifiers).Return(identifiers, nil) + store.EXPECT().FindDeviceSiteConflicts(inTxCtx, testOrgID, identifiers).Return(map[string]int64{ + "d1": conflictingSite, + }, nil) + collStore.EXPECT().RemoveDevicesFromAnyRack(inTxCtx, testOrgID, identifiers, int64(0)).Return(int64(1), nil) + // Site write fails. The transactor returns the error, which + // rolls back the cascade clear write that just happened. + store.EXPECT().AssignDevicesToSite(inTxCtx, testOrgID, gomock.AssignableToTypeOf(ptrInt64(0)), identifiers).Return(int64(0), sentinel) + + _, _, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ + OrgID: testOrgID, + TargetSiteID: &target, + DeviceIdentifiers: identifiers, + ForceClearConflictingRackMembership: true, + }) + if err == nil { + t.Fatalf("expected error, got nil") + } + // Exactly one tx attempt — RunInTx surfaces the error so the + // outer caller sees the failure path. No retries are wired + // into either fake transactor. + switch ttx := tx.(type) { + case *fakeTransactor: + if ttx.calls != 1 { + t.Fatalf("expected one tx run, got %d", ttx.calls) + } + case *wrappingFakeTransactor: + if ttx.calls != 1 { + t.Fatalf("expected one tx run, got %d", ttx.calls) + } + default: + t.Fatalf("unexpected transactor type %T", tx) + } + }) + } +} + func TestAssignBuildingsToSite_cascadeOnSuccess(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) @@ -353,9 +551,9 @@ func TestAssignBuildingsToSite_cascadeOnSuccess(t *testing.T) { // LockBuildingsBySiteForWrite for the source-site race. store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, target).Return(nil) store.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, int64(50)).Return(nil) - store.EXPECT().AssignBuildingToSite(inTxCtx, testOrgID, int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(1), nil) - store.EXPECT().ReassignRacksUnderBuilding(inTxCtx, testOrgID, int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(3), nil) - store.EXPECT().ReassignDevicesUnderBuilding(inTxCtx, testOrgID, int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(15), nil) + store.EXPECT().AssignBuildingsToSiteBulk(inTxCtx, testOrgID, []int64{50}, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(1), nil) + store.EXPECT().ReassignRacksUnderBuildingsBulk(inTxCtx, testOrgID, []int64{50}, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(3), nil) + store.EXPECT().ReassignDevicesUnderBuildingsBulk(inTxCtx, testOrgID, []int64{50}, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(15), nil) out, err := svc.AssignBuildingsToSite(context.Background(), models.AssignBuildingsToSiteParams{ OrgID: testOrgID, @@ -404,11 +602,12 @@ func TestAssignBuildingsToSite_bulkRollsBackOnLaterFailure(t *testing.T) { svc := NewService(store, nil, nil, nil, nil, tx, nil) target := int64(20) + // All per-building locks run in Phase A in sorted order. The second + // building's lock errors so no bulk write fires (cleaner mock log + // than the pre-refactor per-building loop, where the first building + // went through update+cascade before the second's lock failed). store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, target).Return(nil) store.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, int64(50)).Return(nil) - store.EXPECT().AssignBuildingToSite(inTxCtx, testOrgID, int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(1), nil) - store.EXPECT().ReassignRacksUnderBuilding(inTxCtx, testOrgID, int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(2), nil) - store.EXPECT().ReassignDevicesUnderBuilding(inTxCtx, testOrgID, int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(10), nil) store.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, int64(51)). Return(fleeterror.NewNotFoundErrorf("building %d not found", 51)) @@ -601,6 +800,21 @@ func TestAssignRacksToSite_emptyRejected(t *testing.T) { } } +func TestAssignBuildingsToSite_emptyRejected(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, mocks.NewMockCollectionStore(ctrl), nil, nil, tx, nil) + + _, err := svc.AssignBuildingsToSite(context.Background(), models.AssignBuildingsToSiteParams{ + OrgID: testOrgID, + BuildingIDs: nil, + }) + if err == nil { + t.Fatal("expected InvalidArgument for empty building_ids, got nil") + } +} + func TestAssignRacksToSite_nilCollectionStoreRejected(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) @@ -615,3 +829,279 @@ func TestAssignRacksToSite_nilCollectionStoreRejected(t *testing.T) { t.Fatal("expected internal error when collection store is unconfigured") } } + +// TestAssignRacksToSite_clearsBuildingOnSiteChange covers the cascade +// happy path: a rack moves to a new site, so building_id + zone must +// clear, clearedCount increments, and CascadeRackDeviceSites fires. +func TestAssignRacksToSite_clearsBuildingOnSiteChange(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + collStore := mocks.NewMockCollectionStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, collStore, nil, nil, tx, nil) + + rackID := int64(100) + oldSite := int64(1) + oldBuilding := int64(50) + newSite := int64(2) + + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, newSite).Return(nil) + collStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackID, testOrgID). + Return(interfaces.RackPlacement{SiteID: &oldSite, BuildingID: &oldBuilding, Zone: "Z"}, nil) + // Bulk placement update writes site + clears building/zone in one + // statement. Bulk cascade follows for the same set. + collStore.EXPECT(). + UpdateRackPlacementBulkForSite(inTxCtx, testOrgID, []int64{rackID}, &newSite). + Return(nil) + collStore.EXPECT().CascadeRackDeviceSitesBulk(inTxCtx, testOrgID, []int64{rackID}, &newSite).Return(int64(4), nil) + _ = oldBuilding + + out, err := svc.AssignRacksToSite(context.Background(), models.AssignRacksToSiteParams{ + OrgID: testOrgID, + RackIDs: []int64{rackID}, + TargetSiteID: &newSite, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if out.ReassignedDeviceCount != 4 { + t.Fatalf("expected ReassignedDeviceCount=4, got %d", out.ReassignedDeviceCount) + } + if out.ClearedBuildingCount != 1 { + t.Fatalf("expected ClearedBuildingCount=1, got %d", out.ClearedBuildingCount) + } +} + +// TestAssignRacksToSite_sameSiteIsNoop covers the no-op branch: a rack +// already on the target site stays intact — no cascade, no clear. +func TestAssignRacksToSite_sameSiteIsNoop(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + collStore := mocks.NewMockCollectionStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, collStore, nil, nil, tx, nil) + + rackID := int64(100) + site := int64(7) + building := int64(50) + + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, site).Return(nil) + collStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackID, testOrgID). + Return(interfaces.RackPlacement{SiteID: &site, BuildingID: &building, Zone: "Z"}, nil) + // Site unchanged → rack is filtered out of the bulk write set; no + // UpdateRackPlacementBulkForSite or CascadeRackDeviceSitesBulk call + // fires. + _ = building + + out, err := svc.AssignRacksToSite(context.Background(), models.AssignRacksToSiteParams{ + OrgID: testOrgID, + RackIDs: []int64{rackID}, + TargetSiteID: &site, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if out.ReassignedDeviceCount != 0 || out.ClearedBuildingCount != 0 { + t.Fatalf("expected zero cascade counts, got %+v", out) + } +} + +// TestAssignRacksToSite_noPriorBuildingStaysIntact ensures the +// clearedCount only ticks when there *was* a building to clear. +func TestAssignRacksToSite_noPriorBuildingStaysIntact(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + collStore := mocks.NewMockCollectionStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, collStore, nil, nil, tx, nil) + + rackID := int64(100) + oldSite := int64(1) + newSite := int64(2) + + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, newSite).Return(nil) + collStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rackID, testOrgID). + Return(interfaces.RackPlacement{SiteID: &oldSite, BuildingID: nil, Zone: ""}, nil) + collStore.EXPECT(). + UpdateRackPlacementBulkForSite(inTxCtx, testOrgID, []int64{rackID}, &newSite). + Return(nil) + collStore.EXPECT().CascadeRackDeviceSitesBulk(inTxCtx, testOrgID, []int64{rackID}, &newSite).Return(int64(0), nil) + + out, err := svc.AssignRacksToSite(context.Background(), models.AssignRacksToSiteParams{ + OrgID: testOrgID, + RackIDs: []int64{rackID}, + TargetSiteID: &newSite, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if out.ClearedBuildingCount != 0 { + t.Fatalf("expected ClearedBuildingCount=0 (no prior building), got %d", out.ClearedBuildingCount) + } +} + +// TestAssignRacksToSite_bulkRollsBackOnLaterFailure mirrors the +// building-batch rollback case: first rack succeeds, second fails on +// the lock, the tx aborts, and the wrapping transactor records exactly +// one closure run with the error propagating up. +func TestAssignRacksToSite_bulkRollsBackOnLaterFailure(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + collStore := mocks.NewMockCollectionStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, collStore, nil, nil, tx, nil) + + oldSite := int64(1) + newSite := int64(2) + + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, newSite).Return(nil) + // Phase A locks both racks in sorted order. The second lock errors + // so the closure aborts before any bulk write fires. + collStore.EXPECT().LockRackPlacementForWrite(inTxCtx, int64(100), testOrgID). + Return(interfaces.RackPlacement{SiteID: &oldSite}, nil) + collStore.EXPECT().LockRackPlacementForWrite(inTxCtx, int64(101), testOrgID). + Return(interfaces.RackPlacement{}, fleeterror.NewNotFoundErrorf("rack %d not found", 101)) + + _, err := svc.AssignRacksToSite(context.Background(), models.AssignRacksToSiteParams{ + OrgID: testOrgID, + RackIDs: []int64{100, 101}, + TargetSiteID: &newSite, + }) + if !fleeterror.IsNotFoundError(err) { + t.Fatalf("expected NotFound, got %v", err) + } + if tx.calls != 1 { + t.Fatalf("expected exactly 1 tx closure run, got %d", tx.calls) + } +} + +// TestAssignBuildingsToSite_largeBatchIssuesSingleBulkWrites guards +// the F13 bulk refactor: a 100-building batch must produce exactly one +// AssignBuildingsToSiteBulk + one ReassignRacksUnderBuildingsBulk + +// one ReassignDevicesUnderBuildingsBulk call regardless of N. Per- +// building lock acquisitions stay sequential for deadlock safety. +func TestAssignBuildingsToSite_largeBatchIssuesSingleBulkWrites(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, nil, nil, nil, tx, nil) + + const N = 100 + target := int64(20) + buildingIDs := make([]int64, N) + for i := 0; i < N; i++ { + buildingIDs[i] = int64(1000 + i) + } + + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, target).Return(nil) + for _, bid := range buildingIDs { + store.EXPECT().LockBuildingForWrite(inTxCtx, testOrgID, bid).Return(nil) + } + store.EXPECT().AssignBuildingsToSiteBulk(inTxCtx, testOrgID, buildingIDs, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(N), nil) + store.EXPECT().ReassignRacksUnderBuildingsBulk(inTxCtx, testOrgID, buildingIDs, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(300), nil) + store.EXPECT().ReassignDevicesUnderBuildingsBulk(inTxCtx, testOrgID, buildingIDs, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(2000), nil) + + out, err := svc.AssignBuildingsToSite(context.Background(), models.AssignBuildingsToSiteParams{ + OrgID: testOrgID, + BuildingIDs: buildingIDs, + TargetSiteID: &target, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if out.ReassignedRackCount != 300 || out.ReassignedDeviceCount != 2000 { + t.Fatalf("unexpected cascade counts: %+v", out) + } + if tx.calls != 1 { + t.Fatalf("expected one tx closure run, got %d", tx.calls) + } +} + +// TestAssignRacksToSite_largeBatchIssuesSingleBulkWrites guards the +// F14 bulk refactor: a 100-rack site-move batch must produce at most +// one UpdateRackPlacementBulkForSite + one CascadeRackDeviceSitesBulk +// call regardless of N, after the per-rack sequential lock phase. +func TestAssignRacksToSite_largeBatchIssuesSingleBulkWrites(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + collStore := mocks.NewMockCollectionStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, collStore, nil, nil, tx, nil) + + const N = 100 + oldSite := int64(9) + newSite := int64(20) + rackIDs := make([]int64, N) + for i := 0; i < N; i++ { + rackIDs[i] = int64(1000 + i) + } + + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, newSite).Return(nil) + for _, rid := range rackIDs { + collStore.EXPECT().LockRackPlacementForWrite(inTxCtx, rid, testOrgID). + Return(interfaces.RackPlacement{SiteID: &oldSite}, nil) + } + // Exactly one bulk placement update + one bulk cascade. + collStore.EXPECT().UpdateRackPlacementBulkForSite(inTxCtx, testOrgID, rackIDs, &newSite).Return(nil) + collStore.EXPECT().CascadeRackDeviceSitesBulk(inTxCtx, testOrgID, rackIDs, &newSite).Return(int64(500), nil) + + out, err := svc.AssignRacksToSite(context.Background(), models.AssignRacksToSiteParams{ + OrgID: testOrgID, + RackIDs: rackIDs, + TargetSiteID: &newSite, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if out.ReassignedDeviceCount != 500 { + t.Fatalf("unexpected cascade count: %d", out.ReassignedDeviceCount) + } + if tx.calls != 1 { + t.Fatalf("expected one tx closure run, got %d", tx.calls) + } +} + +// TestAssignBuildingsToSite_rejectsZeroTargetSite guards F12: an +// explicit *TargetSiteID == 0 must return InvalidArgument so callers +// can't confuse "Unassigned" (nil) with a zero-valued site. +func TestAssignBuildingsToSite_rejectsZeroTargetSite(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, mocks.NewMockCollectionStore(ctrl), nil, nil, tx, nil) + + zero := int64(0) + _, err := svc.AssignBuildingsToSite(context.Background(), models.AssignBuildingsToSiteParams{ + OrgID: testOrgID, + BuildingIDs: []int64{1}, + TargetSiteID: &zero, + }) + if err == nil { + t.Fatal("expected InvalidArgument for target_site_id=0, got nil") + } + if tx.calls != 0 { + t.Fatalf("guard must reject before opening tx, got %d", tx.calls) + } +} + +// TestAssignRacksToSite_rejectsZeroTargetSite covers the matching F12 +// guard on the rack-batch RPC. +func TestAssignRacksToSite_rejectsZeroTargetSite(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, mocks.NewMockCollectionStore(ctrl), nil, nil, tx, nil) + + zero := int64(0) + _, err := svc.AssignRacksToSite(context.Background(), models.AssignRacksToSiteParams{ + OrgID: testOrgID, + RackIDs: []int64{1}, + TargetSiteID: &zero, + }) + if err == nil { + t.Fatal("expected InvalidArgument for target_site_id=0, got nil") + } + if tx.calls != 0 { + t.Fatalf("guard must reject before opening tx, got %d", tx.calls) + } +} diff --git a/server/internal/domain/stores/interfaces/building.go b/server/internal/domain/stores/interfaces/building.go index 178d7bc5f..a9a5089c0 100644 --- a/server/internal/domain/stores/interfaces/building.go +++ b/server/internal/domain/stores/interfaces/building.go @@ -72,4 +72,15 @@ type BuildingStore interface { // building_id via the collection store's UpdateRackPlacement in // the same transaction. SetRackBuildingPosition(ctx context.Context, orgID, rackID int64, aisleIndex, positionInAisle *int32) error + + // SetRackBuildingPositionBulkClear nulls (aisle_index, + // position_in_aisle) for every rack in rackIDs in one statement. + // Used by AssignRacksToBuilding's pass-1 vacate. + SetRackBuildingPositionBulkClear(ctx context.Context, orgID int64, rackIDs []int64) error + + // SetRackBuildingPositionBulkPlace writes per-rack + // (aisleIndexes[i], positionInAisles[i]) for every rack in + // rackIDs (parallel-aligned arrays). Used by + // AssignRacksToBuilding's pass-2 after pass-1 cleared cells. + SetRackBuildingPositionBulkPlace(ctx context.Context, orgID int64, rackIDs []int64, aisleIndexes, positionInAisles []int32) error } diff --git a/server/internal/domain/stores/interfaces/collection.go b/server/internal/domain/stores/interfaces/collection.go index befbb1ccf..c1e03b128 100644 --- a/server/internal/domain/stores/interfaces/collection.go +++ b/server/internal/domain/stores/interfaces/collection.go @@ -100,6 +100,24 @@ type CollectionStore interface { // atomically. UpdateRackPlacement(ctx context.Context, collectionID, orgID int64, siteID, buildingID *int64, zone string) error + // UpdateRackPlacementBulkForBuilding writes site_id, building_id, + // and zone in one statement for every rack in rackIDs. Semantics + // mirror the per-row UpdateRackPlacement with the + // AssignRacksToBuilding-specific rules in SQL: + // * targetBuildingID == nil keeps each rack's current site_id. + // * Zone clears to '' for racks transitioning to a different (or + // NULL) building; preserved otherwise. + // * Grid position clears when building_id changes. + // Caller is expected to have locked every rack via + // LockRackPlacementForWrite before invoking. + UpdateRackPlacementBulkForBuilding(ctx context.Context, orgID int64, rackIDs []int64, targetSiteID, targetBuildingID *int64) error + + // UpdateRackPlacementBulkForSite stamps every rack in rackIDs with + // the target site, clears building_id + zone + grid placement. + // Caller is expected to pass only racks whose site is actually + // changing. + UpdateRackPlacementBulkForSite(ctx context.Context, orgID int64, rackIDs []int64, targetSiteID *int64) error + // UnassignDeviceSitesByRack nulls device.site_id for paired rack // members that match the rack's stamped site. No-op when the rack // has no site or no members. @@ -109,6 +127,11 @@ type CollectionStore interface { // rack members where the value differs. Returns the affected count. CascadeRackDeviceSites(ctx context.Context, collectionID, orgID int64, targetSiteID *int64) (int64, error) + // CascadeRackDeviceSitesBulk is the multi-rack variant: rewrites + // device.site_id to targetSiteID for every paired member of every + // rack in rackIDs where the current value differs. + CascadeRackDeviceSitesBulk(ctx context.Context, orgID int64, rackIDs []int64, targetSiteID *int64) (int64, error) + // GetDeviceSiteIDsByMembership returns device_identifier + current // site_id for every rack member. GetDeviceSiteIDsByMembership(ctx context.Context, collectionID, orgID int64) (map[string]*int64, error) @@ -170,13 +193,28 @@ type CollectionStore interface { RemoveDevicesFromCollection(ctx context.Context, orgID int64, collectionID int64, deviceIdentifiers []string) (int64, error) // RemoveDevicesFromAnyRack deletes the given devices' rack - // membership rows regardless of which rack they currently sit in. - // Used by AssignDevicesToRack to clear prior rack membership inside - // the same transaction as the new-rack insert, closing the orphan - // window the client-side RemoveDevicesFromDeviceSet + - // AddDevicesToDeviceSet orchestration had. No-op for devices - // without a rack membership. - RemoveDevicesFromAnyRack(ctx context.Context, orgID int64, deviceIdentifiers []string) (int64, error) + // membership rows regardless of which rack they currently sit in, + // EXCEPT the target rack (targetRackID). Used by AssignDevicesToRack + // to clear prior rack membership inside the same transaction as the + // new-rack insert, closing the orphan window the client-side + // RemoveDevicesFromDeviceSet + AddDevicesToDeviceSet orchestration + // had. Excluding targetRackID preserves the membership row (and its + // rack_slot child) for devices already in the target rack -- a + // re-add inside the same transaction would silently drop the + // rack_slot via the FK cascade. Pass 0 to clear unconditionally + // (caller intends to unassign). + RemoveDevicesFromAnyRack(ctx context.Context, orgID int64, deviceIdentifiers []string, targetRackID int64) (int64, error) + + // LockSourceRacksForDevices takes FOR UPDATE locks on every source + // rack currently holding any of the given devices, in ascending + // device_set_id order, and returns the locked ids. AssignDevicesToRack + // calls this BEFORE RemoveDevicesFromAnyRack so concurrent reparent + // calls touching overlapping device sets serialize on the source + // rack rows instead of racing the device_set_membership unique + // constraint. excludeRackID is the target rack the caller will lock + // separately via LockRackPlacementForWrite (avoids double-locking); + // pass 0 in the clear-rack path where there is no target. + LockSourceRacksForDevices(ctx context.Context, orgID int64, deviceIdentifiers []string, excludeRackID int64) ([]int64, error) // ListCollectionMembers returns paginated members of a collection ordered by when they were added (newest first). // Returns the members and a next page token (empty if no more results). diff --git a/server/internal/domain/stores/interfaces/mocks/mock_building_store.go b/server/internal/domain/stores/interfaces/mocks/mock_building_store.go index c75ed6f84..07fa740d5 100644 --- a/server/internal/domain/stores/interfaces/mocks/mock_building_store.go +++ b/server/internal/domain/stores/interfaces/mocks/mock_building_store.go @@ -161,6 +161,34 @@ func (mr *MockBuildingStoreMockRecorder) SetRackBuildingPosition(ctx, orgID, rac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRackBuildingPosition", reflect.TypeOf((*MockBuildingStore)(nil).SetRackBuildingPosition), ctx, orgID, rackID, aisleIndex, positionInAisle) } +// SetRackBuildingPositionBulkClear mocks base method. +func (m *MockBuildingStore) SetRackBuildingPositionBulkClear(ctx context.Context, orgID int64, rackIDs []int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetRackBuildingPositionBulkClear", ctx, orgID, rackIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetRackBuildingPositionBulkClear indicates an expected call of SetRackBuildingPositionBulkClear. +func (mr *MockBuildingStoreMockRecorder) SetRackBuildingPositionBulkClear(ctx, orgID, rackIDs any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRackBuildingPositionBulkClear", reflect.TypeOf((*MockBuildingStore)(nil).SetRackBuildingPositionBulkClear), ctx, orgID, rackIDs) +} + +// SetRackBuildingPositionBulkPlace mocks base method. +func (m *MockBuildingStore) SetRackBuildingPositionBulkPlace(ctx context.Context, orgID int64, rackIDs []int64, aisleIndexes, positionInAisles []int32) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetRackBuildingPositionBulkPlace", ctx, orgID, rackIDs, aisleIndexes, positionInAisles) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetRackBuildingPositionBulkPlace indicates an expected call of SetRackBuildingPositionBulkPlace. +func (mr *MockBuildingStoreMockRecorder) SetRackBuildingPositionBulkPlace(ctx, orgID, rackIDs, aisleIndexes, positionInAisles any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRackBuildingPositionBulkPlace", reflect.TypeOf((*MockBuildingStore)(nil).SetRackBuildingPositionBulkPlace), ctx, orgID, rackIDs, aisleIndexes, positionInAisles) +} + // SoftDeleteBuilding mocks base method. func (m *MockBuildingStore) SoftDeleteBuilding(ctx context.Context, orgID, id int64) (int64, error) { m.ctrl.T.Helper() diff --git a/server/internal/domain/stores/interfaces/mocks/mock_collection_store.go b/server/internal/domain/stores/interfaces/mocks/mock_collection_store.go index 7db7f6538..8b6b6c1fd 100644 --- a/server/internal/domain/stores/interfaces/mocks/mock_collection_store.go +++ b/server/internal/domain/stores/interfaces/mocks/mock_collection_store.go @@ -87,6 +87,21 @@ func (mr *MockCollectionStoreMockRecorder) CascadeRackDeviceSites(ctx, collectio return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CascadeRackDeviceSites", reflect.TypeOf((*MockCollectionStore)(nil).CascadeRackDeviceSites), ctx, collectionID, orgID, targetSiteID) } +// CascadeRackDeviceSitesBulk mocks base method. +func (m *MockCollectionStore) CascadeRackDeviceSitesBulk(ctx context.Context, orgID int64, rackIDs []int64, targetSiteID *int64) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CascadeRackDeviceSitesBulk", ctx, orgID, rackIDs, targetSiteID) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CascadeRackDeviceSitesBulk indicates an expected call of CascadeRackDeviceSitesBulk. +func (mr *MockCollectionStoreMockRecorder) CascadeRackDeviceSitesBulk(ctx, orgID, rackIDs, targetSiteID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CascadeRackDeviceSitesBulk", reflect.TypeOf((*MockCollectionStore)(nil).CascadeRackDeviceSitesBulk), ctx, orgID, rackIDs, targetSiteID) +} + // ClearRackPlacementForSoftDelete mocks base method. func (m *MockCollectionStore) ClearRackPlacementForSoftDelete(ctx context.Context, orgID, collectionID int64) error { m.ctrl.T.Helper() @@ -447,6 +462,21 @@ func (mr *MockCollectionStoreMockRecorder) LockRackPlacementForWrite(ctx, collec return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockRackPlacementForWrite", reflect.TypeOf((*MockCollectionStore)(nil).LockRackPlacementForWrite), ctx, collectionID, orgID) } +// LockSourceRacksForDevices mocks base method. +func (m *MockCollectionStore) LockSourceRacksForDevices(ctx context.Context, orgID int64, deviceIdentifiers []string, excludeRackID int64) ([]int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LockSourceRacksForDevices", ctx, orgID, deviceIdentifiers, excludeRackID) + ret0, _ := ret[0].([]int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LockSourceRacksForDevices indicates an expected call of LockSourceRacksForDevices. +func (mr *MockCollectionStoreMockRecorder) LockSourceRacksForDevices(ctx, orgID, deviceIdentifiers, excludeRackID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockSourceRacksForDevices", reflect.TypeOf((*MockCollectionStore)(nil).LockSourceRacksForDevices), ctx, orgID, deviceIdentifiers, excludeRackID) +} + // RemoveAllDevicesFromCollection mocks base method. func (m *MockCollectionStore) RemoveAllDevicesFromCollection(ctx context.Context, orgID, collectionID int64) (int64, error) { m.ctrl.T.Helper() @@ -463,18 +493,18 @@ func (mr *MockCollectionStoreMockRecorder) RemoveAllDevicesFromCollection(ctx, o } // RemoveDevicesFromAnyRack mocks base method. -func (m *MockCollectionStore) RemoveDevicesFromAnyRack(ctx context.Context, orgID int64, deviceIdentifiers []string) (int64, error) { +func (m *MockCollectionStore) RemoveDevicesFromAnyRack(ctx context.Context, orgID int64, deviceIdentifiers []string, targetRackID int64) (int64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemoveDevicesFromAnyRack", ctx, orgID, deviceIdentifiers) + ret := m.ctrl.Call(m, "RemoveDevicesFromAnyRack", ctx, orgID, deviceIdentifiers, targetRackID) ret0, _ := ret[0].(int64) ret1, _ := ret[1].(error) return ret0, ret1 } // RemoveDevicesFromAnyRack indicates an expected call of RemoveDevicesFromAnyRack. -func (mr *MockCollectionStoreMockRecorder) RemoveDevicesFromAnyRack(ctx, orgID, deviceIdentifiers any) *gomock.Call { +func (mr *MockCollectionStoreMockRecorder) RemoveDevicesFromAnyRack(ctx, orgID, deviceIdentifiers, targetRackID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveDevicesFromAnyRack", reflect.TypeOf((*MockCollectionStore)(nil).RemoveDevicesFromAnyRack), ctx, orgID, deviceIdentifiers) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveDevicesFromAnyRack", reflect.TypeOf((*MockCollectionStore)(nil).RemoveDevicesFromAnyRack), ctx, orgID, deviceIdentifiers, targetRackID) } // RemoveDevicesFromCollection mocks base method. @@ -577,3 +607,31 @@ func (mr *MockCollectionStoreMockRecorder) UpdateRackPlacement(ctx, collectionID mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRackPlacement", reflect.TypeOf((*MockCollectionStore)(nil).UpdateRackPlacement), ctx, collectionID, orgID, siteID, buildingID, zone) } + +// UpdateRackPlacementBulkForBuilding mocks base method. +func (m *MockCollectionStore) UpdateRackPlacementBulkForBuilding(ctx context.Context, orgID int64, rackIDs []int64, targetSiteID, targetBuildingID *int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRackPlacementBulkForBuilding", ctx, orgID, rackIDs, targetSiteID, targetBuildingID) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateRackPlacementBulkForBuilding indicates an expected call of UpdateRackPlacementBulkForBuilding. +func (mr *MockCollectionStoreMockRecorder) UpdateRackPlacementBulkForBuilding(ctx, orgID, rackIDs, targetSiteID, targetBuildingID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRackPlacementBulkForBuilding", reflect.TypeOf((*MockCollectionStore)(nil).UpdateRackPlacementBulkForBuilding), ctx, orgID, rackIDs, targetSiteID, targetBuildingID) +} + +// UpdateRackPlacementBulkForSite mocks base method. +func (m *MockCollectionStore) UpdateRackPlacementBulkForSite(ctx context.Context, orgID int64, rackIDs []int64, targetSiteID *int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRackPlacementBulkForSite", ctx, orgID, rackIDs, targetSiteID) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateRackPlacementBulkForSite indicates an expected call of UpdateRackPlacementBulkForSite. +func (mr *MockCollectionStoreMockRecorder) UpdateRackPlacementBulkForSite(ctx, orgID, rackIDs, targetSiteID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRackPlacementBulkForSite", reflect.TypeOf((*MockCollectionStore)(nil).UpdateRackPlacementBulkForSite), ctx, orgID, rackIDs, targetSiteID) +} diff --git a/server/internal/domain/stores/interfaces/mocks/mock_site_store.go b/server/internal/domain/stores/interfaces/mocks/mock_site_store.go index 026322048..47b52abe9 100644 --- a/server/internal/domain/stores/interfaces/mocks/mock_site_store.go +++ b/server/internal/domain/stores/interfaces/mocks/mock_site_store.go @@ -56,6 +56,21 @@ func (mr *MockSiteStoreMockRecorder) AssignBuildingToSite(ctx, orgID, buildingID return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssignBuildingToSite", reflect.TypeOf((*MockSiteStore)(nil).AssignBuildingToSite), ctx, orgID, buildingID, targetSiteID) } +// AssignBuildingsToSiteBulk mocks base method. +func (m *MockSiteStore) AssignBuildingsToSiteBulk(ctx context.Context, orgID int64, buildingIDs []int64, targetSiteID *int64) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssignBuildingsToSiteBulk", ctx, orgID, buildingIDs, targetSiteID) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AssignBuildingsToSiteBulk indicates an expected call of AssignBuildingsToSiteBulk. +func (mr *MockSiteStoreMockRecorder) AssignBuildingsToSiteBulk(ctx, orgID, buildingIDs, targetSiteID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssignBuildingsToSiteBulk", reflect.TypeOf((*MockSiteStore)(nil).AssignBuildingsToSiteBulk), ctx, orgID, buildingIDs, targetSiteID) +} + // AssignDevicesToSite mocks base method. func (m *MockSiteStore) AssignDevicesToSite(ctx context.Context, orgID int64, targetSiteID *int64, deviceIdentifiers []string) (int64, error) { m.ctrl.T.Helper() @@ -247,6 +262,21 @@ func (mr *MockSiteStoreMockRecorder) ReassignDevicesUnderBuilding(ctx, orgID, bu return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReassignDevicesUnderBuilding", reflect.TypeOf((*MockSiteStore)(nil).ReassignDevicesUnderBuilding), ctx, orgID, buildingID, targetSiteID) } +// ReassignDevicesUnderBuildingsBulk mocks base method. +func (m *MockSiteStore) ReassignDevicesUnderBuildingsBulk(ctx context.Context, orgID int64, buildingIDs []int64, targetSiteID *int64) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReassignDevicesUnderBuildingsBulk", ctx, orgID, buildingIDs, targetSiteID) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReassignDevicesUnderBuildingsBulk indicates an expected call of ReassignDevicesUnderBuildingsBulk. +func (mr *MockSiteStoreMockRecorder) ReassignDevicesUnderBuildingsBulk(ctx, orgID, buildingIDs, targetSiteID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReassignDevicesUnderBuildingsBulk", reflect.TypeOf((*MockSiteStore)(nil).ReassignDevicesUnderBuildingsBulk), ctx, orgID, buildingIDs, targetSiteID) +} + // ReassignRacksUnderBuilding mocks base method. func (m *MockSiteStore) ReassignRacksUnderBuilding(ctx context.Context, orgID, buildingID int64, targetSiteID *int64) (int64, error) { m.ctrl.T.Helper() @@ -262,6 +292,21 @@ func (mr *MockSiteStoreMockRecorder) ReassignRacksUnderBuilding(ctx, orgID, buil return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReassignRacksUnderBuilding", reflect.TypeOf((*MockSiteStore)(nil).ReassignRacksUnderBuilding), ctx, orgID, buildingID, targetSiteID) } +// ReassignRacksUnderBuildingsBulk mocks base method. +func (m *MockSiteStore) ReassignRacksUnderBuildingsBulk(ctx context.Context, orgID int64, buildingIDs []int64, targetSiteID *int64) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReassignRacksUnderBuildingsBulk", ctx, orgID, buildingIDs, targetSiteID) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReassignRacksUnderBuildingsBulk indicates an expected call of ReassignRacksUnderBuildingsBulk. +func (mr *MockSiteStoreMockRecorder) ReassignRacksUnderBuildingsBulk(ctx, orgID, buildingIDs, targetSiteID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReassignRacksUnderBuildingsBulk", reflect.TypeOf((*MockSiteStore)(nil).ReassignRacksUnderBuildingsBulk), ctx, orgID, buildingIDs, targetSiteID) +} + // SiteBelongsToOrg mocks base method. func (m *MockSiteStore) SiteBelongsToOrg(ctx context.Context, orgID, id int64) (bool, error) { m.ctrl.T.Helper() diff --git a/server/internal/domain/stores/interfaces/site.go b/server/internal/domain/stores/interfaces/site.go index 33eb9744f..ff2d45760 100644 --- a/server/internal/domain/stores/interfaces/site.go +++ b/server/internal/domain/stores/interfaces/site.go @@ -113,13 +113,28 @@ type SiteStore interface { // number of rows affected (0 == not found). AssignBuildingToSite(ctx context.Context, orgID, buildingID int64, targetSiteID *int64) (int64, error) + // AssignBuildingsToSiteBulk is the multi-building variant of + // AssignBuildingToSite. Updates building.site_id for every building + // in buildingIDs in one statement. Returns the row count actually + // moved (skips soft-deleted rows). + AssignBuildingsToSiteBulk(ctx context.Context, orgID int64, buildingIDs []int64, targetSiteID *int64) (int64, error) + // ReassignRacksUnderBuilding cascades site_id down to every rack // under the given building. Caller wraps it in the same tx as the // building UPDATE. ReassignRacksUnderBuilding(ctx context.Context, orgID, buildingID int64, targetSiteID *int64) (int64, error) + // ReassignRacksUnderBuildingsBulk cascades site_id down to every + // rack under any building in buildingIDs, in one statement. + ReassignRacksUnderBuildingsBulk(ctx context.Context, orgID int64, buildingIDs []int64, targetSiteID *int64) (int64, error) + // ReassignDevicesUnderBuilding cascades site_id down to every // device in any rack of the given building. Caller wraps it in // the same tx as the building UPDATE. ReassignDevicesUnderBuilding(ctx context.Context, orgID, buildingID int64, targetSiteID *int64) (int64, error) + + // ReassignDevicesUnderBuildingsBulk cascades site_id down to every + // device in any rack under any building in buildingIDs, in one + // statement. + ReassignDevicesUnderBuildingsBulk(ctx context.Context, orgID int64, buildingIDs []int64, targetSiteID *int64) (int64, error) } diff --git a/server/internal/domain/stores/sqlstores/building.go b/server/internal/domain/stores/sqlstores/building.go index aeb1e6674..749da9f36 100644 --- a/server/internal/domain/stores/sqlstores/building.go +++ b/server/internal/domain/stores/sqlstores/building.go @@ -237,6 +237,37 @@ func (s *SQLBuildingStore) SetRackBuildingPosition(ctx context.Context, orgID, r return nil } +func (s *SQLBuildingStore) SetRackBuildingPositionBulkClear(ctx context.Context, orgID int64, rackIDs []int64) error { + if len(rackIDs) == 0 { + return nil + } + if err := s.GetQueries(ctx).SetRackBuildingPositionBulkClear(ctx, sqlc.SetRackBuildingPositionBulkClearParams{ + RackIds: rackIDs, + OrgID: orgID, + }); err != nil { + return fleeterror.NewInternalErrorf("failed to bulk-clear rack building positions: %v", err) + } + return nil +} + +func (s *SQLBuildingStore) SetRackBuildingPositionBulkPlace(ctx context.Context, orgID int64, rackIDs []int64, aisleIndexes, positionInAisles []int32) error { + if len(rackIDs) == 0 { + return nil + } + if len(rackIDs) != len(aisleIndexes) || len(rackIDs) != len(positionInAisles) { + return fleeterror.NewInternalErrorf("SetRackBuildingPositionBulkPlace: array length mismatch (rackIDs=%d aisles=%d positions=%d)", len(rackIDs), len(aisleIndexes), len(positionInAisles)) + } + if err := s.GetQueries(ctx).SetRackBuildingPositionBulkPlace(ctx, sqlc.SetRackBuildingPositionBulkPlaceParams{ + OrgID: orgID, + RackIds: rackIDs, + AisleIndexes: aisleIndexes, + PositionInAisles: positionInAisles, + }); err != nil { + return fleeterror.NewInternalErrorf("failed to bulk-place rack building positions: %v", err) + } + return nil +} + func buildingFromRow(row sqlc.Building) models.Building { return models.Building{ ID: row.ID, diff --git a/server/internal/domain/stores/sqlstores/collection.go b/server/internal/domain/stores/sqlstores/collection.go index 3a15240b1..26af393f7 100644 --- a/server/internal/domain/stores/sqlstores/collection.go +++ b/server/internal/domain/stores/sqlstores/collection.go @@ -237,6 +237,35 @@ func (s *SQLCollectionStore) UpdateRackPlacement(ctx context.Context, collection return nil } +func (s *SQLCollectionStore) UpdateRackPlacementBulkForBuilding(ctx context.Context, orgID int64, rackIDs []int64, targetSiteID, targetBuildingID *int64) error { + if len(rackIDs) == 0 { + return nil + } + if err := s.GetQueries(ctx).UpdateRackPlacementBulkForBuilding(ctx, sqlc.UpdateRackPlacementBulkForBuildingParams{ + TargetBuildingID: ptrToNullInt64(targetBuildingID), + TargetSiteID: ptrToNullInt64(targetSiteID), + RackIds: rackIDs, + OrgID: orgID, + }); err != nil { + return fleeterror.NewInternalErrorf("failed to bulk-update rack placement: %v", err) + } + return nil +} + +func (s *SQLCollectionStore) UpdateRackPlacementBulkForSite(ctx context.Context, orgID int64, rackIDs []int64, targetSiteID *int64) error { + if len(rackIDs) == 0 { + return nil + } + if err := s.GetQueries(ctx).UpdateRackPlacementBulkForSite(ctx, sqlc.UpdateRackPlacementBulkForSiteParams{ + TargetSiteID: ptrToNullInt64(targetSiteID), + RackIds: rackIDs, + OrgID: orgID, + }); err != nil { + return fleeterror.NewInternalErrorf("failed to bulk-update rack placement (site): %v", err) + } + return nil +} + func (s *SQLCollectionStore) UnassignDeviceSitesByRack(ctx context.Context, collectionID, orgID int64) (int64, error) { n, err := s.GetQueries(ctx).UnassignDeviceSitesByRack(ctx, sqlc.UnassignDeviceSitesByRackParams{ DeviceSetID: collectionID, @@ -260,6 +289,21 @@ func (s *SQLCollectionStore) CascadeRackDeviceSites(ctx context.Context, collect return n, nil } +func (s *SQLCollectionStore) CascadeRackDeviceSitesBulk(ctx context.Context, orgID int64, rackIDs []int64, targetSiteID *int64) (int64, error) { + if len(rackIDs) == 0 { + return 0, nil + } + n, err := s.GetQueries(ctx).CascadeRackDeviceSitesBulk(ctx, sqlc.CascadeRackDeviceSitesBulkParams{ + TargetSiteID: ptrToNullInt64(targetSiteID), + RackIds: rackIDs, + OrgID: orgID, + }) + if err != nil { + return 0, fleeterror.NewInternalErrorf("failed to bulk-cascade rack device sites: %v", err) + } + return n, nil +} + func (s *SQLCollectionStore) GetDeviceSiteIDsByMembership(ctx context.Context, collectionID, orgID int64) (map[string]*int64, error) { rows, err := s.GetQueries(ctx).GetDeviceSiteIDsByMembership(ctx, sqlc.GetDeviceSiteIDsByMembershipParams{ DeviceSetID: collectionID, @@ -523,10 +567,11 @@ func (s *SQLCollectionStore) RemoveDevicesFromCollection(ctx context.Context, or return count, nil } -func (s *SQLCollectionStore) RemoveDevicesFromAnyRack(ctx context.Context, orgID int64, deviceIdentifiers []string) (int64, error) { +func (s *SQLCollectionStore) RemoveDevicesFromAnyRack(ctx context.Context, orgID int64, deviceIdentifiers []string, targetRackID int64) (int64, error) { count, err := s.GetQueries(ctx).RemoveDevicesFromAnyRack(ctx, sqlc.RemoveDevicesFromAnyRackParams{ OrgID: orgID, DeviceIdentifiers: deviceIdentifiers, + TargetRackID: targetRackID, }) if err != nil { return 0, fleeterror.NewInternalErrorf("failed to remove devices from rack: %v", err) @@ -534,6 +579,18 @@ func (s *SQLCollectionStore) RemoveDevicesFromAnyRack(ctx context.Context, orgID return count, nil } +func (s *SQLCollectionStore) LockSourceRacksForDevices(ctx context.Context, orgID int64, deviceIdentifiers []string, excludeRackID int64) ([]int64, error) { + ids, err := s.GetQueries(ctx).LockSourceRacksForDevices(ctx, sqlc.LockSourceRacksForDevicesParams{ + OrgID: orgID, + DeviceIdentifiers: deviceIdentifiers, + ExcludeRackID: excludeRackID, + }) + if err != nil { + return nil, fleeterror.NewInternalErrorf("failed to lock source racks for devices: %v", err) + } + return ids, nil +} + func (s *SQLCollectionStore) ListCollectionMembers(ctx context.Context, orgID int64, collectionID int64, pageSize int32, pageToken string) ([]*pb.CollectionMember, string, error) { cursor, err := decodeMemberCursor(pageToken) if err != nil { diff --git a/server/internal/domain/stores/sqlstores/site.go b/server/internal/domain/stores/sqlstores/site.go index 4ef807e53..a8010cdf0 100644 --- a/server/internal/domain/stores/sqlstores/site.go +++ b/server/internal/domain/stores/sqlstores/site.go @@ -285,6 +285,24 @@ func (s *SQLSiteStore) AssignBuildingToSite(ctx context.Context, orgID, building return rowsAffected, nil } +func (s *SQLSiteStore) AssignBuildingsToSiteBulk(ctx context.Context, orgID int64, buildingIDs []int64, targetSiteID *int64) (int64, error) { + if len(buildingIDs) == 0 { + return 0, nil + } + rowsAffected, err := s.GetQueries(ctx).AssignBuildingsToSiteBulk(ctx, sqlc.AssignBuildingsToSiteBulkParams{ + SiteID: ptrToNullInt64(targetSiteID), + BuildingIds: buildingIDs, + OrgID: orgID, + }) + if err != nil { + if isUniqueViolation(err) { + return 0, fleeterror.NewPlainError("a building with this name already exists in the target site", connect.CodeAlreadyExists).WithCallerStackTrace() + } + return 0, fleeterror.NewInternalErrorf("failed to bulk-assign buildings to site: %v", err) + } + return rowsAffected, nil +} + func (s *SQLSiteStore) ReassignRacksUnderBuilding(ctx context.Context, orgID, buildingID int64, targetSiteID *int64) (int64, error) { rowsAffected, err := s.GetQueries(ctx).ReassignRacksUnderBuilding(ctx, sqlc.ReassignRacksUnderBuildingParams{ TargetSiteID: ptrToNullInt64(targetSiteID), @@ -297,6 +315,21 @@ func (s *SQLSiteStore) ReassignRacksUnderBuilding(ctx context.Context, orgID, bu return rowsAffected, nil } +func (s *SQLSiteStore) ReassignRacksUnderBuildingsBulk(ctx context.Context, orgID int64, buildingIDs []int64, targetSiteID *int64) (int64, error) { + if len(buildingIDs) == 0 { + return 0, nil + } + rowsAffected, err := s.GetQueries(ctx).ReassignRacksUnderBuildingsBulk(ctx, sqlc.ReassignRacksUnderBuildingsBulkParams{ + TargetSiteID: ptrToNullInt64(targetSiteID), + OrgID: orgID, + BuildingIds: buildingIDs, + }) + if err != nil { + return 0, fleeterror.NewInternalErrorf("failed to bulk-reassign racks under buildings: %v", err) + } + return rowsAffected, nil +} + func (s *SQLSiteStore) ReassignDevicesUnderBuilding(ctx context.Context, orgID, buildingID int64, targetSiteID *int64) (int64, error) { rowsAffected, err := s.GetQueries(ctx).ReassignDevicesUnderBuilding(ctx, sqlc.ReassignDevicesUnderBuildingParams{ TargetSiteID: ptrToNullInt64(targetSiteID), @@ -309,6 +342,21 @@ func (s *SQLSiteStore) ReassignDevicesUnderBuilding(ctx context.Context, orgID, return rowsAffected, nil } +func (s *SQLSiteStore) ReassignDevicesUnderBuildingsBulk(ctx context.Context, orgID int64, buildingIDs []int64, targetSiteID *int64) (int64, error) { + if len(buildingIDs) == 0 { + return 0, nil + } + rowsAffected, err := s.GetQueries(ctx).ReassignDevicesUnderBuildingsBulk(ctx, sqlc.ReassignDevicesUnderBuildingsBulkParams{ + TargetSiteID: ptrToNullInt64(targetSiteID), + OrgID: orgID, + BuildingIds: buildingIDs, + }) + if err != nil { + return 0, fleeterror.NewInternalErrorf("failed to bulk-reassign devices under buildings: %v", err) + } + return rowsAffected, nil +} + func (s *SQLSiteStore) ListAllSiteNetworkConfigs(ctx context.Context, orgID, excludeID int64) ([]models.SiteNetworkConfigEntry, error) { rows, err := s.GetQueries(ctx).ListSiteNetworkConfigsForOverlap(ctx, sqlc.ListSiteNetworkConfigsForOverlapParams{ OrgID: orgID, diff --git a/server/internal/handlers/buildings/handler_test.go b/server/internal/handlers/buildings/handler_test.go index cf798b1b1..354dab3cc 100644 --- a/server/internal/handlers/buildings/handler_test.go +++ b/server/internal/handlers/buildings/handler_test.go @@ -395,13 +395,20 @@ func TestHandler_AssignRacksToBuilding_happy(t *testing.T) { h.siteStore.EXPECT().LockBuildingForWrite(gomock.Any(), int64(7), buildingID).Return(nil), h.buildingStore.EXPECT().GetBuilding(gomock.Any(), int64(7), buildingID). Return(&models.Building{ID: buildingID, SiteID: &siteID, Aisles: 4, RacksPerAisle: 6}, nil), + // Phase A: per-rack lock + read. h.collectionStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), int64(99), int64(7)). Return(interfaces.RackPlacement{SiteID: nil}, nil), - h.collectionStore.EXPECT().UpdateRackPlacement(gomock.Any(), int64(99), int64(7), &siteID, &buildingID, ""). + // Phase B1: single bulk placement write. + h.collectionStore.EXPECT().UpdateRackPlacementBulkForBuilding(gomock.Any(), int64(7), []int64{99}, &siteID, &buildingID). Return(nil), - h.collectionStore.EXPECT().CascadeRackDeviceSites(gomock.Any(), int64(99), int64(7), &siteID). + // Phase B2: single bulk cascade for site-changed rack. + h.collectionStore.EXPECT().CascadeRackDeviceSitesBulk(gomock.Any(), int64(7), []int64{99}, &siteID). Return(int64(3), nil), - h.buildingStore.EXPECT().SetRackBuildingPosition(gomock.Any(), int64(7), int64(99), ptrInt32t(1), ptrInt32t(2)). + // Phase B3: bulk pass-1 vacate. + h.buildingStore.EXPECT().SetRackBuildingPositionBulkClear(gomock.Any(), int64(7), []int64{99}). + Return(nil), + // Phase B4: bulk pass-2 place. + h.buildingStore.EXPECT().SetRackBuildingPositionBulkPlace(gomock.Any(), int64(7), []int64{99}, []int32{1}, []int32{2}). Return(nil), ) diff --git a/server/internal/handlers/collection/handler.go b/server/internal/handlers/collection/handler.go index 32ad826de..57fd7ff82 100644 --- a/server/internal/handlers/collection/handler.go +++ b/server/internal/handlers/collection/handler.go @@ -85,30 +85,6 @@ func (h *Handler) ListCollections(ctx context.Context, r *connect.Request[pb.Lis return connect.NewResponse(result), nil } -// AddDevicesToCollection adds devices to a collection. -func (h *Handler) AddDevicesToCollection(ctx context.Context, r *connect.Request[pb.AddDevicesToCollectionRequest]) (*connect.Response[pb.AddDevicesToCollectionResponse], error) { - if _, err := middleware.RequirePermission(ctx, authz.PermRackManage, authz.ResourceContext{}); err != nil { - return nil, err - } - result, err := h.collectionSvc.AddDevicesToCollection(ctx, r.Msg) - if err != nil { - return nil, err - } - return connect.NewResponse(result), nil -} - -// RemoveDevicesFromCollection removes devices from a collection. -func (h *Handler) RemoveDevicesFromCollection(ctx context.Context, r *connect.Request[pb.RemoveDevicesFromCollectionRequest]) (*connect.Response[pb.RemoveDevicesFromCollectionResponse], error) { - if _, err := middleware.RequirePermission(ctx, authz.PermRackManage, authz.ResourceContext{}); err != nil { - return nil, err - } - result, err := h.collectionSvc.RemoveDevicesFromCollection(ctx, r.Msg) - if err != nil { - return nil, err - } - return connect.NewResponse(result), nil -} - // ListCollectionMembers returns all members of a collection. func (h *Handler) ListCollectionMembers(ctx context.Context, r *connect.Request[pb.ListCollectionMembersRequest]) (*connect.Response[pb.ListCollectionMembersResponse], error) { if _, err := middleware.RequirePermission(ctx, authz.PermRackRead, authz.ResourceContext{}); err != nil { diff --git a/server/internal/handlers/deviceset/handler.go b/server/internal/handlers/deviceset/handler.go index 0af9a4664..2781cd5fe 100644 --- a/server/internal/handlers/deviceset/handler.go +++ b/server/internal/handlers/deviceset/handler.go @@ -107,36 +107,42 @@ func (h *Handler) ListDeviceSets(ctx context.Context, r *connect.Request[dspb.Li }), nil } -func (h *Handler) AddDevicesToDeviceSet(ctx context.Context, r *connect.Request[dspb.AddDevicesToDeviceSetRequest]) (*connect.Response[dspb.AddDevicesToDeviceSetResponse], error) { +// AddDevicesToGroup adds devices to a group device set. The target +// must be a group; racks are rejected with InvalidArgument so callers +// can't smuggle rack mutations through the group endpoint (rack adds +// must go through AssignDevicesToRack to get the atomic prior-rack +// removal + site cascade). +func (h *Handler) AddDevicesToGroup(ctx context.Context, r *connect.Request[dspb.AddDevicesToGroupRequest]) (*connect.Response[dspb.AddDevicesToGroupResponse], error) { if _, err := middleware.RequirePermission(ctx, authz.PermRackManage, authz.ResourceContext{}); err != nil { return nil, err } - result, err := h.svc.AddDevicesToCollection(ctx, &collectionpb.AddDevicesToCollectionRequest{ - CollectionId: r.Msg.DeviceSetId, + result, err := h.svc.AddDevicesToGroup(ctx, collection.AddDevicesToGroupParams{ + TargetGroupID: r.Msg.TargetGroupId, DeviceSelector: r.Msg.DeviceSelector, }) if err != nil { return nil, err } - return connect.NewResponse(&dspb.AddDevicesToDeviceSetResponse{ - DeviceSetId: result.CollectionId, - AddedCount: result.AddedCount, - SiteReassignedCount: result.SiteReassignedCount, + return connect.NewResponse(&dspb.AddDevicesToGroupResponse{ + AddedCount: result.AddedCount, }), nil } -func (h *Handler) RemoveDevicesFromDeviceSet(ctx context.Context, r *connect.Request[dspb.RemoveDevicesFromDeviceSetRequest]) (*connect.Response[dspb.RemoveDevicesFromDeviceSetResponse], error) { +// RemoveDevicesFromGroup removes devices from a group device set. +// Racks are rejected with InvalidArgument; use AssignDevicesToRack +// (target_rack_id unset) to clear rack membership. +func (h *Handler) RemoveDevicesFromGroup(ctx context.Context, r *connect.Request[dspb.RemoveDevicesFromGroupRequest]) (*connect.Response[dspb.RemoveDevicesFromGroupResponse], error) { if _, err := middleware.RequirePermission(ctx, authz.PermRackManage, authz.ResourceContext{}); err != nil { return nil, err } - result, err := h.svc.RemoveDevicesFromCollection(ctx, &collectionpb.RemoveDevicesFromCollectionRequest{ - CollectionId: r.Msg.DeviceSetId, + result, err := h.svc.RemoveDevicesFromGroup(ctx, collection.RemoveDevicesFromGroupParams{ + TargetGroupID: r.Msg.TargetGroupId, DeviceSelector: r.Msg.DeviceSelector, }) if err != nil { return nil, err } - return connect.NewResponse(&dspb.RemoveDevicesFromDeviceSetResponse{ + return connect.NewResponse(&dspb.RemoveDevicesFromGroupResponse{ RemovedCount: result.RemovedCount, }), nil } @@ -331,16 +337,11 @@ func (h *Handler) AssignDevicesToRack(ctx context.Context, r *connect.Request[ds if err != nil { return nil, err } - var targetRackID *int64 - if r.Msg.TargetRackId != nil { - v := r.Msg.GetTargetRackId() - targetRackID = &v + params, err := toAssignDevicesToRackParams(r.Msg, info.OrganizationID) + if err != nil { + return nil, err } - result, err := h.svc.AssignDevicesToRack(ctx, collection.AssignDevicesToRackParams{ - OrgID: info.OrganizationID, - TargetRackID: targetRackID, - DeviceIdentifiers: r.Msg.GetDeviceIdentifiers(), - }) + result, err := h.svc.AssignDevicesToRack(ctx, params) if err != nil { return nil, err } diff --git a/server/internal/handlers/deviceset/handler_test.go b/server/internal/handlers/deviceset/handler_test.go index d2e0e081e..d610929da 100644 --- a/server/internal/handlers/deviceset/handler_test.go +++ b/server/internal/handlers/deviceset/handler_test.go @@ -5,6 +5,7 @@ import ( "errors" "testing" + "connectrpc.com/authn" "connectrpc.com/connect" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -14,10 +15,13 @@ import ( commonpb "github.com/block/proto-fleet/server/generated/grpc/common/v1" dspb "github.com/block/proto-fleet/server/generated/grpc/device_set/v1" "github.com/block/proto-fleet/server/internal/domain/activity" + "github.com/block/proto-fleet/server/internal/domain/authz" "github.com/block/proto-fleet/server/internal/domain/collection" "github.com/block/proto-fleet/server/internal/domain/fleeterror" + "github.com/block/proto-fleet/server/internal/domain/session" "github.com/block/proto-fleet/server/internal/domain/stores/interfaces" "github.com/block/proto-fleet/server/internal/domain/stores/interfaces/mocks" + "github.com/block/proto-fleet/server/internal/handlers/middleware" "github.com/block/proto-fleet/server/internal/testutil" ) @@ -347,3 +351,327 @@ func TestListRackZoneRefs_StoreError(t *testing.T) { _, err := h.handler.ListRackZoneRefs(testCtx(t), connect.NewRequest(&dspb.ListRackZoneRefsRequest{})) require.Error(t, err) } + +// ctxWithPerms builds an auth context with an explicit permission set +// so we can assert AssignDevicesToRack's PermRackManage gate. +func ctxWithPerms(perms ...string) context.Context { + ctx := authn.SetInfo(context.Background(), &session.Info{ + AuthMethod: session.AuthMethodSession, + OrganizationID: testOrgID, + UserID: testUserID, + }) + return middleware.WithEffectivePermissions(ctx, authz.NewEffectivePermissions([]authz.Assignment{{ + AssignmentID: 1, + ScopeType: authz.ScopeOrg, + Permissions: perms, + }})) +} + +// deviceListSelector builds a DeviceSelector that selects the given +// identifiers (the device_list variant), the only variant accepted by +// AssignDevicesToRack and the only variant the noopResolver-backed +// group endpoint tests exercise. +func deviceListSelector(ids ...string) *commonpb.DeviceSelector { + return &commonpb.DeviceSelector{ + SelectionType: &commonpb.DeviceSelector_DeviceList{ + DeviceList: &commonpb.DeviceIdentifierList{ + DeviceIdentifiers: ids, + }, + }, + } +} + +// allDevicesSelector builds the all_devices DeviceSelector variant. +// AssignDevicesToRack must reject it with InvalidArgument. +func allDevicesSelector() *commonpb.DeviceSelector { + return &commonpb.DeviceSelector{ + SelectionType: &commonpb.DeviceSelector_AllDevices{AllDevices: true}, + } +} + +// TestAssignDevicesToRack_PermissionRequired confirms the +// PermRackManage gate rejects callers without rack:manage. No store +// calls should fire. +func TestAssignDevicesToRack_PermissionRequired(t *testing.T) { + h := newTestHandler(t) + + // Caller has *some* permission but not PermRackManage. + ctx := ctxWithPerms(authz.PermSiteRead) + req := connect.NewRequest(&dspb.AssignDevicesToRackRequest{ + TargetRackId: ptrInt64Local(42), + DeviceSelector: deviceListSelector("d1"), + }) + + _, err := h.handler.AssignDevicesToRack(ctx, req) + require.Error(t, err) + var fe fleeterror.FleetError + require.ErrorAs(t, err, &fe, "expected FleetError, got %T", err) + assert.Equal(t, connect.CodePermissionDenied, fe.GRPCCode) +} + +// TestAssignDevicesToRack_RejectsAllDevicesSelector confirms that the +// all_devices selector variant is rejected with InvalidArgument at the +// handler boundary, before any store call fires. Moving every paired +// device into a single rack is never the intended operation and the +// filter-based variants are reserved for a future expansion. +func TestAssignDevicesToRack_RejectsAllDevicesSelector(t *testing.T) { + h := newTestHandler(t) + + resp, err := h.handler.AssignDevicesToRack(testCtx(t), connect.NewRequest(&dspb.AssignDevicesToRackRequest{ + TargetRackId: ptrInt64Local(42), + DeviceSelector: allDevicesSelector(), + })) + require.Error(t, err) + require.Nil(t, resp) + var fe fleeterror.FleetError + require.ErrorAs(t, err, &fe) + assert.Equal(t, connect.CodeInvalidArgument, fe.GRPCCode) +} + +// TestAssignDevicesToRack_HappyPathAssigns covers the assign branch: +// target_rack_id set, devices flow through the lock → label-read → +// remove → add → cascade chain, and the handler round-trips the +// per-step counts onto the wire response. +func TestAssignDevicesToRack_HappyPathAssigns(t *testing.T) { + h := newTestHandler(t) + + targetRackID := int64(42) + rackSite := int64(7) + deviceIDs := []string{"d1", "d2"} + + gomock.InOrder( + h.collectionStore.EXPECT(). + LockSourceRacksForDevices(gomock.Any(), testOrgID, deviceIDs, targetRackID). + Return([]int64{11, 23}, nil), + h.collectionStore.EXPECT(). + LockRackPlacementForWrite(gomock.Any(), targetRackID, testOrgID). + Return(interfaces.RackPlacement{SiteID: &rackSite}, nil), + h.collectionStore.EXPECT(). + GetCollection(gomock.Any(), testOrgID, targetRackID). + Return(&collectionpb.DeviceCollection{Id: targetRackID, Label: "Rack-B", Type: collectionpb.CollectionType_COLLECTION_TYPE_RACK}, nil), + h.collectionStore.EXPECT(). + RemoveDevicesFromAnyRack(gomock.Any(), testOrgID, deviceIDs, targetRackID). + Return(int64(2), nil), + h.collectionStore.EXPECT(). + AddDevicesToCollection(gomock.Any(), testOrgID, targetRackID, deviceIDs). + Return(int64(2), nil), + h.collectionStore.EXPECT(). + CascadeAddedDeviceSites(gomock.Any(), testOrgID, targetRackID, deviceIDs). + Return(int64(1), nil), + ) + + resp, err := h.handler.AssignDevicesToRack(testCtx(t), connect.NewRequest(&dspb.AssignDevicesToRackRequest{ + TargetRackId: &targetRackID, + DeviceSelector: deviceListSelector(deviceIDs...), + })) + require.NoError(t, err) + assert.Equal(t, int64(2), resp.Msg.AssignedCount) + assert.Equal(t, int64(2), resp.Msg.RemovedCount) + assert.Equal(t, int64(1), resp.Msg.SiteReassignedCount) +} + +// TestAssignDevicesToRack_UnassignBranch covers target_rack_id unset: +// clears prior rack membership, no Get/Lock/Add/Cascade. +// RemoveDevicesFromAnyRack is called with targetRackID = 0 (sentinel +// meaning "don't exclude any rack"). +func TestAssignDevicesToRack_UnassignBranch(t *testing.T) { + h := newTestHandler(t) + + deviceIDs := []string{"d1"} + gomock.InOrder( + h.collectionStore.EXPECT(). + LockSourceRacksForDevices(gomock.Any(), testOrgID, deviceIDs, int64(0)). + Return([]int64{17}, nil), + h.collectionStore.EXPECT(). + RemoveDevicesFromAnyRack(gomock.Any(), testOrgID, deviceIDs, int64(0)). + Return(int64(1), nil), + ) + + resp, err := h.handler.AssignDevicesToRack(testCtx(t), connect.NewRequest(&dspb.AssignDevicesToRackRequest{ + TargetRackId: nil, + DeviceSelector: deviceListSelector(deviceIDs...), + })) + require.NoError(t, err) + assert.Equal(t, int64(0), resp.Msg.AssignedCount) + assert.Equal(t, int64(1), resp.Msg.RemovedCount) + assert.Equal(t, int64(0), resp.Msg.SiteReassignedCount) +} + +// TestAddDevicesToGroup_HappyPath asserts that the handler verifies the +// target is a group, calls the underlying AddDevicesToGroup path, and +// round-trips the added_count onto the wire response. +// +// The default test harness uses a noopResolver that returns nil for +// every selector; this test wires a resolver that returns the supplied +// identifiers so the call threads through to the store mock. +func TestAddDevicesToGroup_HappyPath(t *testing.T) { + targetGroupID := int64(11) + deviceIDs := []string{"d1", "d2"} + + h := newGroupHandlerWithResolver(t, deviceIDs) + gomock.InOrder( + h.collectionStore.EXPECT(). + GetCollection(gomock.Any(), testOrgID, targetGroupID). + Return(&collectionpb.DeviceCollection{Id: targetGroupID, Label: "Group A", Type: collectionpb.CollectionType_COLLECTION_TYPE_GROUP}, nil), + h.collectionStore.EXPECT(). + AddDevicesToCollection(gomock.Any(), testOrgID, targetGroupID, deviceIDs). + Return(int64(2), nil), + ) + + resp, err := h.handler.AddDevicesToGroup(testCtx(t), connect.NewRequest(&dspb.AddDevicesToGroupRequest{ + TargetGroupId: targetGroupID, + DeviceSelector: deviceListSelector(deviceIDs...), + })) + require.NoError(t, err) + assert.Equal(t, int64(2), resp.Msg.AddedCount) +} + +// TestAddDevicesToGroup_RejectsRackTarget asserts the handler returns +// InvalidArgument when target_group_id points at a rack, so callers +// can't smuggle a rack mutation through the group endpoint and bypass +// AssignDevicesToRack's atomic prior-rack removal + site cascade. +func TestAddDevicesToGroup_RejectsRackTarget(t *testing.T) { + h := newTestHandler(t) + + targetID := int64(11) + h.collectionStore.EXPECT(). + GetCollection(gomock.Any(), testOrgID, targetID). + Return(&collectionpb.DeviceCollection{Id: targetID, Label: "Rack-X", Type: collectionpb.CollectionType_COLLECTION_TYPE_RACK}, nil) + + _, err := h.handler.AddDevicesToGroup(testCtx(t), connect.NewRequest(&dspb.AddDevicesToGroupRequest{ + TargetGroupId: targetID, + DeviceSelector: deviceListSelector("d1"), + })) + require.Error(t, err) + var fe fleeterror.FleetError + require.ErrorAs(t, err, &fe) + assert.Equal(t, connect.CodeInvalidArgument, fe.GRPCCode) +} + +// TestAddDevicesToGroup_PermissionRequired confirms the PermRackManage +// gate. No store calls fire when the caller lacks the permission. +func TestAddDevicesToGroup_PermissionRequired(t *testing.T) { + h := newTestHandler(t) + + ctx := ctxWithPerms(authz.PermSiteRead) + _, err := h.handler.AddDevicesToGroup(ctx, connect.NewRequest(&dspb.AddDevicesToGroupRequest{ + TargetGroupId: 42, + DeviceSelector: deviceListSelector("d1"), + })) + require.Error(t, err) + var fe fleeterror.FleetError + require.ErrorAs(t, err, &fe) + assert.Equal(t, connect.CodePermissionDenied, fe.GRPCCode) +} + +// TestRemoveDevicesFromGroup_HappyPath asserts the handler verifies the +// target is a group and forwards to the group remove path. +func TestRemoveDevicesFromGroup_HappyPath(t *testing.T) { + targetGroupID := int64(11) + deviceIDs := []string{"d1"} + + h := newGroupHandlerWithResolver(t, deviceIDs) + gomock.InOrder( + h.collectionStore.EXPECT(). + GetCollection(gomock.Any(), testOrgID, targetGroupID). + Return(&collectionpb.DeviceCollection{Id: targetGroupID, Label: "Group A", Type: collectionpb.CollectionType_COLLECTION_TYPE_GROUP}, nil), + h.collectionStore.EXPECT(). + RemoveDevicesFromCollection(gomock.Any(), testOrgID, targetGroupID, deviceIDs). + Return(int64(1), nil), + ) + + resp, err := h.handler.RemoveDevicesFromGroup(testCtx(t), connect.NewRequest(&dspb.RemoveDevicesFromGroupRequest{ + TargetGroupId: targetGroupID, + DeviceSelector: deviceListSelector(deviceIDs...), + })) + require.NoError(t, err) + assert.Equal(t, int64(1), resp.Msg.RemovedCount) +} + +// TestRemoveDevicesFromGroup_RejectsRackTarget asserts a rack target +// returns InvalidArgument with no follow-up store mutation. +func TestRemoveDevicesFromGroup_RejectsRackTarget(t *testing.T) { + h := newTestHandler(t) + + targetID := int64(11) + h.collectionStore.EXPECT(). + GetCollection(gomock.Any(), testOrgID, targetID). + Return(&collectionpb.DeviceCollection{Id: targetID, Label: "Rack-X", Type: collectionpb.CollectionType_COLLECTION_TYPE_RACK}, nil) + + _, err := h.handler.RemoveDevicesFromGroup(testCtx(t), connect.NewRequest(&dspb.RemoveDevicesFromGroupRequest{ + TargetGroupId: targetID, + DeviceSelector: deviceListSelector("d1"), + })) + require.Error(t, err) + var fe fleeterror.FleetError + require.ErrorAs(t, err, &fe) + assert.Equal(t, connect.CodeInvalidArgument, fe.GRPCCode) +} + +// TestRemoveDevicesFromGroup_PermissionRequired confirms the +// PermRackManage gate. +func TestRemoveDevicesFromGroup_PermissionRequired(t *testing.T) { + h := newTestHandler(t) + + ctx := ctxWithPerms(authz.PermSiteRead) + _, err := h.handler.RemoveDevicesFromGroup(ctx, connect.NewRequest(&dspb.RemoveDevicesFromGroupRequest{ + TargetGroupId: 42, + DeviceSelector: deviceListSelector("d1"), + })) + require.Error(t, err) + var fe fleeterror.FleetError + require.ErrorAs(t, err, &fe) + assert.Equal(t, connect.CodePermissionDenied, fe.GRPCCode) +} + +// newGroupHandlerWithResolver builds a harness like newTestHandler but +// wires a resolver that returns the supplied identifiers for any +// selector. The default harness uses a noopResolver that returns nil, +// which is fine for handler-level rejection tests but not for the +// group happy-path tests that need to thread a non-empty identifier +// list through to AddDevicesToGroup / RemoveDevicesFromGroup. +func newGroupHandlerWithResolver(t *testing.T, ids []string) *testHarness { + t.Helper() + ctrl := gomock.NewController(t) + + collectionStore := mocks.NewMockCollectionStore(ctrl) + buildingStore := mocks.NewMockBuildingStore(ctrl) + tx := mocks.NewMockTransactor(ctrl) + tx.EXPECT().RunInTx(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( + func(ctx context.Context, fn func(context.Context) error) error { + return fn(ctx) + }, + ) + tx.EXPECT().RunInTxWithResult(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( + func(ctx context.Context, fn func(context.Context) (any, error)) (any, error) { + return fn(ctx) + }, + ) + + activityStore := mocks.NewMockActivityStore(ctrl) + activityStore.EXPECT().Insert(gomock.Any(), gomock.Any()).AnyTimes().Return(nil) + activitySvc := activity.NewService(activityStore) + + resolver := func(_ context.Context, _ *commonpb.DeviceSelector, _ int64) ([]string, error) { + return ids, nil + } + + svc := collection.NewService( + collectionStore, + nil, + nil, + buildingStore, + tx, + resolver, + nil, + activitySvc, + ) + return &testHarness{ + handler: NewHandler(svc), + collectionStore: collectionStore, + buildingStore: buildingStore, + ctrl: ctrl, + } +} + +func ptrInt64Local(v int64) *int64 { return &v } diff --git a/server/internal/handlers/deviceset/translate.go b/server/internal/handlers/deviceset/translate.go new file mode 100644 index 000000000..4dff4ee21 --- /dev/null +++ b/server/internal/handlers/deviceset/translate.go @@ -0,0 +1,55 @@ +package deviceset + +import ( + commonpb "github.com/block/proto-fleet/server/generated/grpc/common/v1" + dspb "github.com/block/proto-fleet/server/generated/grpc/device_set/v1" + "github.com/block/proto-fleet/server/internal/domain/collection" + "github.com/block/proto-fleet/server/internal/domain/fleeterror" +) + +// toAssignDevicesToRackParams translates the proto request into the +// domain-layer input shape. Mirrors the proto→domain helpers under +// server/internal/handlers/sites/translate.go so handler bodies stay +// focused on auth + wiring. +// +// Only the device_list selector variant is accepted. all_devices is +// rejected with InvalidArgument because moving every paired device +// into a single rack is never the intended operation; filter-based +// selectors are reserved for a future expansion. +func toAssignDevicesToRackParams(req *dspb.AssignDevicesToRackRequest, orgID int64) (collection.AssignDevicesToRackParams, error) { + var targetRackID *int64 + if req.TargetRackId != nil { + v := req.GetTargetRackId() + targetRackID = &v + } + identifiers, err := identifiersFromAssignSelector(req.GetDeviceSelector()) + if err != nil { + return collection.AssignDevicesToRackParams{}, err + } + return collection.AssignDevicesToRackParams{ + OrgID: orgID, + TargetRackID: targetRackID, + DeviceIdentifiers: identifiers, + }, nil +} + +// identifiersFromAssignSelector enforces the device_list-only contract +// for AssignDevicesToRack. The all_devices variant and the unset +// oneof are both rejected with InvalidArgument so callers learn +// up-front instead of getting a 0-row response or a panic downstream. +func identifiersFromAssignSelector(sel *commonpb.DeviceSelector) ([]string, error) { + if sel == nil { + return nil, fleeterror.NewInvalidArgumentError("device_selector is required") + } + switch v := sel.GetSelectionType().(type) { + case *commonpb.DeviceSelector_DeviceList: + if v.DeviceList == nil { + return nil, fleeterror.NewInvalidArgumentError("device_selector.device_list is required") + } + return v.DeviceList.GetDeviceIdentifiers(), nil + case *commonpb.DeviceSelector_AllDevices: + return nil, fleeterror.NewInvalidArgumentError("device_selector.all_devices is not supported for AssignDevicesToRack; pass an explicit device_list") + default: + return nil, fleeterror.NewInvalidArgumentError("device_selector must set device_list") + } +} diff --git a/server/internal/handlers/middleware/rpc_permissions.go b/server/internal/handlers/middleware/rpc_permissions.go index df4cbe636..ff8f93d42 100644 --- a/server/internal/handlers/middleware/rpc_permissions.go +++ b/server/internal/handlers/middleware/rpc_permissions.go @@ -142,43 +142,41 @@ var ProcedurePermissions = map[string]string{ // Collections are the legacy name for racks; the wire surface still // carries Collection-prefixed names while the domain has been // renamed. - collectionv1connect.DeviceCollectionServiceGetCollectionProcedure: authz.PermRackRead, - collectionv1connect.DeviceCollectionServiceGetCollectionStatsProcedure: authz.PermRackRead, - collectionv1connect.DeviceCollectionServiceListCollectionsProcedure: authz.PermRackRead, - collectionv1connect.DeviceCollectionServiceListCollectionMembersProcedure: authz.PermRackRead, - collectionv1connect.DeviceCollectionServiceGetDeviceCollectionsProcedure: authz.PermRackRead, - collectionv1connect.DeviceCollectionServiceListRackTypesProcedure: authz.PermRackRead, - collectionv1connect.DeviceCollectionServiceListRackZonesProcedure: authz.PermRackRead, - collectionv1connect.DeviceCollectionServiceGetRackSlotsProcedure: authz.PermRackRead, - collectionv1connect.DeviceCollectionServiceCreateCollectionProcedure: authz.PermRackManage, - collectionv1connect.DeviceCollectionServiceUpdateCollectionProcedure: authz.PermRackManage, - collectionv1connect.DeviceCollectionServiceDeleteCollectionProcedure: authz.PermRackManage, - collectionv1connect.DeviceCollectionServiceAddDevicesToCollectionProcedure: authz.PermRackManage, - collectionv1connect.DeviceCollectionServiceRemoveDevicesFromCollectionProcedure: authz.PermRackManage, - collectionv1connect.DeviceCollectionServiceSaveRackProcedure: authz.PermRackManage, - collectionv1connect.DeviceCollectionServiceSetRackSlotPositionProcedure: authz.PermRackManage, - collectionv1connect.DeviceCollectionServiceClearRackSlotPositionProcedure: authz.PermRackManage, + collectionv1connect.DeviceCollectionServiceGetCollectionProcedure: authz.PermRackRead, + collectionv1connect.DeviceCollectionServiceGetCollectionStatsProcedure: authz.PermRackRead, + collectionv1connect.DeviceCollectionServiceListCollectionsProcedure: authz.PermRackRead, + collectionv1connect.DeviceCollectionServiceListCollectionMembersProcedure: authz.PermRackRead, + collectionv1connect.DeviceCollectionServiceGetDeviceCollectionsProcedure: authz.PermRackRead, + collectionv1connect.DeviceCollectionServiceListRackTypesProcedure: authz.PermRackRead, + collectionv1connect.DeviceCollectionServiceListRackZonesProcedure: authz.PermRackRead, + collectionv1connect.DeviceCollectionServiceGetRackSlotsProcedure: authz.PermRackRead, + collectionv1connect.DeviceCollectionServiceCreateCollectionProcedure: authz.PermRackManage, + collectionv1connect.DeviceCollectionServiceUpdateCollectionProcedure: authz.PermRackManage, + collectionv1connect.DeviceCollectionServiceDeleteCollectionProcedure: authz.PermRackManage, + collectionv1connect.DeviceCollectionServiceSaveRackProcedure: authz.PermRackManage, + collectionv1connect.DeviceCollectionServiceSetRackSlotPositionProcedure: authz.PermRackManage, + collectionv1connect.DeviceCollectionServiceClearRackSlotPositionProcedure: authz.PermRackManage, // DeviceSetService (racks via the new wire surface) — same mapping // as DeviceCollectionService; the handler is a proto-adapter shim. - device_setv1connect.DeviceSetServiceGetDeviceSetProcedure: authz.PermRackRead, - device_setv1connect.DeviceSetServiceGetDeviceSetStatsProcedure: authz.PermRackRead, - device_setv1connect.DeviceSetServiceListDeviceSetsProcedure: authz.PermRackRead, - device_setv1connect.DeviceSetServiceListDeviceSetMembersProcedure: authz.PermRackRead, - device_setv1connect.DeviceSetServiceGetDeviceDeviceSetsProcedure: authz.PermRackRead, - device_setv1connect.DeviceSetServiceListRackTypesProcedure: authz.PermRackRead, - device_setv1connect.DeviceSetServiceListRackZonesProcedure: authz.PermRackRead, - device_setv1connect.DeviceSetServiceListRackZoneRefsProcedure: authz.PermRackRead, - device_setv1connect.DeviceSetServiceGetRackSlotsProcedure: authz.PermRackRead, - device_setv1connect.DeviceSetServiceCreateDeviceSetProcedure: authz.PermRackManage, - device_setv1connect.DeviceSetServiceUpdateDeviceSetProcedure: authz.PermRackManage, - device_setv1connect.DeviceSetServiceDeleteDeviceSetProcedure: authz.PermRackManage, - device_setv1connect.DeviceSetServiceAddDevicesToDeviceSetProcedure: authz.PermRackManage, - device_setv1connect.DeviceSetServiceRemoveDevicesFromDeviceSetProcedure: authz.PermRackManage, - device_setv1connect.DeviceSetServiceSaveRackProcedure: authz.PermRackManage, - device_setv1connect.DeviceSetServiceAssignDevicesToRackProcedure: authz.PermRackManage, - device_setv1connect.DeviceSetServiceSetRackSlotPositionProcedure: authz.PermRackManage, - device_setv1connect.DeviceSetServiceClearRackSlotPositionProcedure: authz.PermRackManage, + device_setv1connect.DeviceSetServiceGetDeviceSetProcedure: authz.PermRackRead, + device_setv1connect.DeviceSetServiceGetDeviceSetStatsProcedure: authz.PermRackRead, + device_setv1connect.DeviceSetServiceListDeviceSetsProcedure: authz.PermRackRead, + device_setv1connect.DeviceSetServiceListDeviceSetMembersProcedure: authz.PermRackRead, + device_setv1connect.DeviceSetServiceGetDeviceDeviceSetsProcedure: authz.PermRackRead, + device_setv1connect.DeviceSetServiceListRackTypesProcedure: authz.PermRackRead, + device_setv1connect.DeviceSetServiceListRackZonesProcedure: authz.PermRackRead, + device_setv1connect.DeviceSetServiceListRackZoneRefsProcedure: authz.PermRackRead, + device_setv1connect.DeviceSetServiceGetRackSlotsProcedure: authz.PermRackRead, + device_setv1connect.DeviceSetServiceCreateDeviceSetProcedure: authz.PermRackManage, + device_setv1connect.DeviceSetServiceUpdateDeviceSetProcedure: authz.PermRackManage, + device_setv1connect.DeviceSetServiceDeleteDeviceSetProcedure: authz.PermRackManage, + device_setv1connect.DeviceSetServiceAddDevicesToGroupProcedure: authz.PermRackManage, + device_setv1connect.DeviceSetServiceRemoveDevicesFromGroupProcedure: authz.PermRackManage, + device_setv1connect.DeviceSetServiceSaveRackProcedure: authz.PermRackManage, + device_setv1connect.DeviceSetServiceAssignDevicesToRackProcedure: authz.PermRackManage, + device_setv1connect.DeviceSetServiceSetRackSlotPositionProcedure: authz.PermRackManage, + device_setv1connect.DeviceSetServiceClearRackSlotPositionProcedure: authz.PermRackManage, // ErrorQueryService — fleet:read; diagnostics are scoped to the org // and live alongside the fleet dashboard. diff --git a/server/internal/handlers/sites/handler.go b/server/internal/handlers/sites/handler.go index f3b8a251a..f02bf0e85 100644 --- a/server/internal/handlers/sites/handler.go +++ b/server/internal/handlers/sites/handler.go @@ -12,7 +12,6 @@ import ( "github.com/block/proto-fleet/server/generated/grpc/sites/v1/sitesv1connect" "github.com/block/proto-fleet/server/internal/domain/authz" "github.com/block/proto-fleet/server/internal/domain/sites" - "github.com/block/proto-fleet/server/internal/domain/sites/models" "github.com/block/proto-fleet/server/internal/handlers/middleware" ) @@ -122,16 +121,7 @@ func (h *Handler) AssignRacksToSite(ctx context.Context, req *connect.Request[pb if err != nil { return nil, err } - var targetSiteID *int64 - if req.Msg.TargetSiteId != nil { - v := req.Msg.GetTargetSiteId() - targetSiteID = &v - } - out, err := h.service.AssignRacksToSite(ctx, models.AssignRacksToSiteParams{ - OrgID: info.OrganizationID, - RackIDs: req.Msg.GetRackIds(), - TargetSiteID: targetSiteID, - }) + out, err := h.service.AssignRacksToSite(ctx, toAssignRacksToSiteParams(req.Msg, info.OrganizationID)) if err != nil { return nil, err } diff --git a/server/internal/handlers/sites/handler_test.go b/server/internal/handlers/sites/handler_test.go index 614b0354a..00ec4df76 100644 --- a/server/internal/handlers/sites/handler_test.go +++ b/server/internal/handlers/sites/handler_test.go @@ -286,9 +286,9 @@ func TestHandler_AssignBuildingsToSite_surfacesCascadeCounts(t *testing.T) { h.siteStore.EXPECT().LockSiteForWrite(gomock.Any(), int64(7), target).Return(nil) h.siteStore.EXPECT().LockBuildingForWrite(gomock.Any(), int64(7), int64(50)).Return(nil) - h.siteStore.EXPECT().AssignBuildingToSite(gomock.Any(), int64(7), int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(1), nil) - h.siteStore.EXPECT().ReassignRacksUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(3), nil) - h.siteStore.EXPECT().ReassignDevicesUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(15), nil) + h.siteStore.EXPECT().AssignBuildingsToSiteBulk(gomock.Any(), int64(7), []int64{50}, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(1), nil) + h.siteStore.EXPECT().ReassignRacksUnderBuildingsBulk(gomock.Any(), int64(7), []int64{50}, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(3), nil) + h.siteStore.EXPECT().ReassignDevicesUnderBuildingsBulk(gomock.Any(), int64(7), []int64{50}, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(15), nil) resp, err := h.handler.AssignBuildingsToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignBuildingsToSiteRequest{ BuildingIds: []int64{50}, @@ -304,12 +304,12 @@ func TestHandler_AssignBuildingsToSite_targetUnsetCascadesToUnassigned(t *testin h := newTestHandler(t) // target_site_id unset → service skips LockSiteForWrite (no target - // site to lock) but still locks the building before the cascade, - // then passes a nil targetSiteID through. + // site to lock) but still locks the building before the bulk cascade + // writes, then passes a nil targetSiteID through. h.siteStore.EXPECT().LockBuildingForWrite(gomock.Any(), int64(7), int64(50)).Return(nil) - h.siteStore.EXPECT().AssignBuildingToSite(gomock.Any(), int64(7), int64(50), gomock.Nil()).Return(int64(1), nil) - h.siteStore.EXPECT().ReassignRacksUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.Nil()).Return(int64(0), nil) - h.siteStore.EXPECT().ReassignDevicesUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.Nil()).Return(int64(0), nil) + h.siteStore.EXPECT().AssignBuildingsToSiteBulk(gomock.Any(), int64(7), []int64{50}, gomock.Nil()).Return(int64(1), nil) + h.siteStore.EXPECT().ReassignRacksUnderBuildingsBulk(gomock.Any(), int64(7), []int64{50}, gomock.Nil()).Return(int64(0), nil) + h.siteStore.EXPECT().ReassignDevicesUnderBuildingsBulk(gomock.Any(), int64(7), []int64{50}, gomock.Nil()).Return(int64(0), nil) resp, err := h.handler.AssignBuildingsToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignBuildingsToSiteRequest{ BuildingIds: []int64{50}, @@ -325,16 +325,14 @@ func TestHandler_AssignBuildingsToSite_bulkAggregatesCascadeCounts(t *testing.T) target := int64(20) - // Two buildings, processed in sorted ID order. + // Phase A locks both buildings in sorted ID order; Phase B issues + // one bulk write per kind across both buildings. h.siteStore.EXPECT().LockSiteForWrite(gomock.Any(), int64(7), target).Return(nil) h.siteStore.EXPECT().LockBuildingForWrite(gomock.Any(), int64(7), int64(50)).Return(nil) - h.siteStore.EXPECT().AssignBuildingToSite(gomock.Any(), int64(7), int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(1), nil) - h.siteStore.EXPECT().ReassignRacksUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(2), nil) - h.siteStore.EXPECT().ReassignDevicesUnderBuilding(gomock.Any(), int64(7), int64(50), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(10), nil) h.siteStore.EXPECT().LockBuildingForWrite(gomock.Any(), int64(7), int64(51)).Return(nil) - h.siteStore.EXPECT().AssignBuildingToSite(gomock.Any(), int64(7), int64(51), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(1), nil) - h.siteStore.EXPECT().ReassignRacksUnderBuilding(gomock.Any(), int64(7), int64(51), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(4), nil) - h.siteStore.EXPECT().ReassignDevicesUnderBuilding(gomock.Any(), int64(7), int64(51), gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(20), nil) + h.siteStore.EXPECT().AssignBuildingsToSiteBulk(gomock.Any(), int64(7), []int64{50, 51}, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(2), nil) + h.siteStore.EXPECT().ReassignRacksUnderBuildingsBulk(gomock.Any(), int64(7), []int64{50, 51}, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(6), nil) + h.siteStore.EXPECT().ReassignDevicesUnderBuildingsBulk(gomock.Any(), int64(7), []int64{50, 51}, gomock.AssignableToTypeOf(ptrInt64(0))).Return(int64(30), nil) // Pass IDs out of order to verify deterministic locking via sort. resp, err := h.handler.AssignBuildingsToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignBuildingsToSiteRequest{ @@ -358,9 +356,10 @@ func TestHandler_AssignRacksToSite_partialUpdateCascadesAndClearsBuilding(t *tes h.siteStore.EXPECT().LockSiteForWrite(gomock.Any(), int64(7), target).Return(nil) h.collectionStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), rackID, int64(7)). Return(interfaces.RackPlacement{SiteID: &priorSite, BuildingID: &priorBuilding, Zone: "Z1"}, nil) - // site changes & rack has a building → building clears, zone clears. - h.collectionStore.EXPECT().UpdateRackPlacement(gomock.Any(), rackID, int64(7), &target, (*int64)(nil), "").Return(nil) - h.collectionStore.EXPECT().CascadeRackDeviceSites(gomock.Any(), rackID, int64(7), &target).Return(int64(8), nil) + // Bulk placement update clears building + zone in SQL; bulk cascade + // follows for the same rack set. + h.collectionStore.EXPECT().UpdateRackPlacementBulkForSite(gomock.Any(), int64(7), []int64{rackID}, &target).Return(nil) + h.collectionStore.EXPECT().CascadeRackDeviceSitesBulk(gomock.Any(), int64(7), []int64{rackID}, &target).Return(int64(8), nil) resp, err := h.handler.AssignRacksToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignRacksToSiteRequest{ RackIds: []int64{rackID}, @@ -382,8 +381,8 @@ func TestHandler_AssignRacksToSite_targetUnsetUnassigns(t *testing.T) { h.collectionStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), rackID, int64(7)). Return(interfaces.RackPlacement{SiteID: &priorSite}, nil) // no building set // site changes (priorSite → nil) but no building to clear. - h.collectionStore.EXPECT().UpdateRackPlacement(gomock.Any(), rackID, int64(7), gomock.Nil(), gomock.Nil(), "").Return(nil) - h.collectionStore.EXPECT().CascadeRackDeviceSites(gomock.Any(), rackID, int64(7), gomock.Nil()).Return(int64(3), nil) + h.collectionStore.EXPECT().UpdateRackPlacementBulkForSite(gomock.Any(), int64(7), []int64{rackID}, gomock.Nil()).Return(nil) + h.collectionStore.EXPECT().CascadeRackDeviceSitesBulk(gomock.Any(), int64(7), []int64{rackID}, gomock.Nil()).Return(int64(3), nil) resp, err := h.handler.AssignRacksToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignRacksToSiteRequest{ RackIds: []int64{rackID}, @@ -401,11 +400,10 @@ func TestHandler_AssignRacksToSite_sameSiteIsNoOp(t *testing.T) { rackID := int64(50) h.siteStore.EXPECT().LockSiteForWrite(gomock.Any(), int64(7), target).Return(nil) - // rack already at target site, has a building — no clear, no cascade. + // rack already at target site — filtered out of the bulk write set; + // no UpdateRackPlacementBulkForSite or CascadeRackDeviceSitesBulk call. h.collectionStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), rackID, int64(7)). Return(interfaces.RackPlacement{SiteID: &target, BuildingID: ptrInt64(11), Zone: "Z1"}, nil) - h.collectionStore.EXPECT().UpdateRackPlacement(gomock.Any(), rackID, int64(7), &target, ptrInt64(11), "Z1").Return(nil) - // No CascadeRackDeviceSites — siteChanged is false. resp, err := h.handler.AssignRacksToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignRacksToSiteRequest{ RackIds: []int64{rackID}, @@ -424,15 +422,14 @@ func TestHandler_AssignRacksToSite_bulkAggregates(t *testing.T) { priorSite := int64(9) h.siteStore.EXPECT().LockSiteForWrite(gomock.Any(), int64(7), target).Return(nil) - // Two racks, processed in sorted id order. + // Phase A locks both racks in sorted id order; Phase B issues one + // bulk placement update and one bulk cascade across both racks. h.collectionStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), int64(50), int64(7)). Return(interfaces.RackPlacement{SiteID: &priorSite, BuildingID: ptrInt64(11)}, nil) - h.collectionStore.EXPECT().UpdateRackPlacement(gomock.Any(), int64(50), int64(7), &target, (*int64)(nil), "").Return(nil) - h.collectionStore.EXPECT().CascadeRackDeviceSites(gomock.Any(), int64(50), int64(7), &target).Return(int64(4), nil) h.collectionStore.EXPECT().LockRackPlacementForWrite(gomock.Any(), int64(51), int64(7)). Return(interfaces.RackPlacement{SiteID: &priorSite}, nil) // no building - h.collectionStore.EXPECT().UpdateRackPlacement(gomock.Any(), int64(51), int64(7), &target, gomock.Nil(), "").Return(nil) - h.collectionStore.EXPECT().CascadeRackDeviceSites(gomock.Any(), int64(51), int64(7), &target).Return(int64(2), nil) + h.collectionStore.EXPECT().UpdateRackPlacementBulkForSite(gomock.Any(), int64(7), []int64{50, 51}, &target).Return(nil) + h.collectionStore.EXPECT().CascadeRackDeviceSitesBulk(gomock.Any(), int64(7), []int64{50, 51}, &target).Return(int64(6), nil) // IDs passed out-of-order to verify the sort happens. resp, err := h.handler.AssignRacksToSite(sitePermsCtx(t, 7), connect.NewRequest(&pb.AssignRacksToSiteRequest{ diff --git a/server/internal/handlers/sites/translate.go b/server/internal/handlers/sites/translate.go index eca57fc39..725dd0ecd 100644 --- a/server/internal/handlers/sites/translate.go +++ b/server/internal/handlers/sites/translate.go @@ -48,9 +48,10 @@ func toAssignDevicesParams(req *pb.AssignDevicesToSiteRequest, orgID int64) mode targetSiteID = &v } return models.AssignDevicesToSiteParams{ - OrgID: orgID, - TargetSiteID: targetSiteID, - DeviceIdentifiers: req.GetDeviceIdentifiers(), + OrgID: orgID, + TargetSiteID: targetSiteID, + DeviceIdentifiers: req.GetDeviceIdentifiers(), + ForceClearConflictingRackMembership: req.GetForceClearConflictingRackMembership(), } } @@ -67,6 +68,19 @@ func toAssignBuildingsParams(req *pb.AssignBuildingsToSiteRequest, orgID int64) } } +func toAssignRacksToSiteParams(req *pb.AssignRacksToSiteRequest, orgID int64) models.AssignRacksToSiteParams { + var targetSiteID *int64 + if req.TargetSiteId != nil { + v := req.GetTargetSiteId() + targetSiteID = &v + } + return models.AssignRacksToSiteParams{ + OrgID: orgID, + RackIDs: req.GetRackIds(), + TargetSiteID: targetSiteID, + } +} + // resolveTimezone returns the operator-stored timezone when set, // falling back to (country, location_state) inference. The override // lets operators correct sub-state edge cases (N Idaho, El Paso TX, diff --git a/server/sqlc/queries/building.sql b/server/sqlc/queries/building.sql index 9ba52bf3d..5210d0dc8 100644 --- a/server/sqlc/queries/building.sql +++ b/server/sqlc/queries/building.sql @@ -195,6 +195,58 @@ SET aisle_index = sqlc.narg('aisle_index')::int, WHERE device_set_id = sqlc.arg('rack_id') AND org_id = sqlc.arg('org_id'); +-- name: SetRackBuildingPositionBulkClear :exec +-- Bulk variant of SetRackBuildingPosition that nulls (aisle_index, +-- position_in_aisle) for every rack in @rack_ids. Used by +-- AssignRacksToBuilding's pass-1 vacate so every rack in the batch +-- holds NULL position before pass-2 reclaims cells. Mirrors the +-- single-row query's intentional no-touch on building_id — +-- UpdateRackPlacement already settled that. +UPDATE device_set_rack +SET aisle_index = NULL, + position_in_aisle = NULL +WHERE device_set_id = ANY(sqlc.arg('rack_ids')::bigint[]) + AND org_id = sqlc.arg('org_id'); + +-- name: SetRackBuildingPositionBulkPlace :exec +-- Bulk variant of SetRackBuildingPosition that writes per-rack +-- (aisle_index, position_in_aisle) via parallel arrays joined on +-- ordinal position. Used by AssignRacksToBuilding's pass-2 after +-- pass-1 has vacated every cell touched by the batch. Arrays must be +-- the same length and parallel-aligned with @rack_ids; callers that +-- have a "place nothing" pass-2 should skip the query entirely. +UPDATE device_set_rack dsr +SET aisle_index = u.aisle_index, + position_in_aisle = u.position_in_aisle +FROM ( + SELECT + rack_ids[i] AS rack_id, + aisle_indexes[i] AS aisle_index, + position_in_aisles[i] AS position_in_aisle + FROM ( + SELECT + sqlc.arg('rack_ids')::bigint[] AS rack_ids, + sqlc.arg('aisle_indexes')::int[] AS aisle_indexes, + sqlc.arg('position_in_aisles')::int[] AS position_in_aisles + ) arrs + CROSS JOIN generate_subscripts(arrs.rack_ids, 1) AS i +) AS u +WHERE dsr.device_set_id = u.rack_id + AND dsr.org_id = sqlc.arg('org_id'); + +-- name: AssignBuildingsToSiteBulk :execrows +-- Bulk variant of AssignBuildingToSite. Updates building.site_id for +-- every building in @building_ids in one statement. Caller is +-- expected to have row-locked each building first (canonical lock +-- order: site → buildings). Returns the row count of buildings +-- actually moved (skips soft-deleted rows). +UPDATE building +SET site_id = sqlc.narg('site_id'), + updated_at = CURRENT_TIMESTAMP +WHERE id = ANY(sqlc.arg('building_ids')::bigint[]) + AND org_id = sqlc.arg('org_id') + AND deleted_at IS NULL; + -- name: BuildingBelongsToOrg :one SELECT EXISTS( SELECT 1 FROM building diff --git a/server/sqlc/queries/device_set.sql b/server/sqlc/queries/device_set.sql index 15c587604..e4c3ad605 100644 --- a/server/sqlc/queries/device_set.sql +++ b/server/sqlc/queries/device_set.sql @@ -133,6 +133,90 @@ WHERE device_set_id = sqlc.arg('device_set_id') AND ds.deleted_at IS NULL ); +-- name: UpdateRackPlacementBulkForBuilding :exec +-- Bulk variant of UpdateRackPlacement scoped to AssignRacksToBuilding. +-- Sets site_id, building_id, and zone for every rack in @rack_ids in a +-- single update. +-- +-- Semantics match the per-row UpdateRackPlacement + service-layer +-- zone/site rules: +-- +-- * When @target_building_id IS NULL (unassign branch), each rack +-- keeps its current site_id (no cascade fires later). Otherwise +-- every rack is stamped with @target_site_id. +-- * Zone clears to '' for any rack that had a building and is +-- transitioning to a different (or NULL) building. Racks staying in +-- the same building preserve their zone. +-- * aisle_index / position_in_aisle clear when building_id changes, +-- matching the single-row CASE. +UPDATE device_set_rack dsr +SET site_id = CASE + WHEN sqlc.narg('target_building_id')::bigint IS NULL THEN dsr.site_id + ELSE sqlc.narg('target_site_id')::bigint + END, + building_id = sqlc.narg('target_building_id')::bigint, + zone = CASE + WHEN dsr.building_id IS NOT NULL + AND dsr.building_id IS DISTINCT FROM sqlc.narg('target_building_id')::bigint + THEN '' + ELSE dsr.zone + END, + aisle_index = CASE + WHEN sqlc.narg('target_building_id')::bigint IS DISTINCT FROM dsr.building_id THEN NULL + ELSE dsr.aisle_index + END, + position_in_aisle = CASE + WHEN sqlc.narg('target_building_id')::bigint IS DISTINCT FROM dsr.building_id THEN NULL + ELSE dsr.position_in_aisle + END +WHERE dsr.device_set_id = ANY(sqlc.arg('rack_ids')::bigint[]) + AND dsr.org_id = sqlc.arg('org_id') + AND EXISTS ( + SELECT 1 FROM device_set ds + WHERE ds.id = dsr.device_set_id + AND ds.org_id = sqlc.arg('org_id') + AND ds.deleted_at IS NULL + ); + +-- name: UpdateRackPlacementBulkForSite :exec +-- Bulk variant used by AssignRacksToSite. Stamps every rack in +-- @rack_ids with the target site, clears building_id and zone (because +-- a building belongs to one site, the rack's building membership is +-- invalidated by any site transition), and clears the grid placement +-- as a downstream effect of building_id changing. Caller is expected +-- to only pass racks whose current site differs from the target so the +-- response counts stay accurate. +UPDATE device_set_rack dsr +SET site_id = sqlc.narg('target_site_id')::bigint, + building_id = NULL, + zone = '', + aisle_index = NULL, + position_in_aisle = NULL +WHERE dsr.device_set_id = ANY(sqlc.arg('rack_ids')::bigint[]) + AND dsr.org_id = sqlc.arg('org_id') + AND EXISTS ( + SELECT 1 FROM device_set ds + WHERE ds.id = dsr.device_set_id + AND ds.org_id = sqlc.arg('org_id') + AND ds.deleted_at IS NULL + ); + +-- name: CascadeRackDeviceSitesBulk :execrows +-- Bulk variant of CascadeRackDeviceSites. Rewrites device.site_id to +-- target_site_id for every paired member of every rack in @rack_ids. +-- NULL target unassigns. IS DISTINCT FROM skips no-op rows. Returns +-- the total affected row count across all racks. +UPDATE device d +SET site_id = sqlc.narg('target_site_id')::bigint, + updated_at = CURRENT_TIMESTAMP +FROM device_set_membership dsm +WHERE dsm.device_set_id = ANY(sqlc.arg('rack_ids')::bigint[]) + AND dsm.org_id = sqlc.arg('org_id') + AND dsm.device_set_type = 'rack' + AND dsm.device_id = d.id + AND d.deleted_at IS NULL + AND d.site_id IS DISTINCT FROM sqlc.narg('target_site_id')::bigint; + -- name: SoftDeleteDeviceSet :execrows UPDATE device_set SET deleted_at = CURRENT_TIMESTAMP @@ -211,15 +295,43 @@ DELETE FROM device_set_membership WHERE device_set_id = $1 AND org_id = $2; +-- name: LockSourceRacksForDevices :many +-- Returns the distinct source rack ids currently holding any of the +-- provided device identifiers, locked FOR UPDATE in ascending id order. +-- AssignDevicesToRack calls this BEFORE the DELETE-from-source + +-- INSERT-into-target cascade so concurrent reparent calls touching +-- overlapping device sets serialize on the source rack rows instead of +-- racing the device_set_membership unique constraint. Excludes the +-- target rack (@exclude_rack_id) so the caller can take the target +-- lock once via LockRackPlacementForWrite without re-locking here. +-- Pass 0 for @exclude_rack_id to lock every source rack (clear-rack +-- path where there is no target). +SELECT DISTINCT dsm.device_set_id +FROM device_set_membership dsm +JOIN device_set ds ON ds.id = dsm.device_set_id +WHERE ds.org_id = @org_id + AND dsm.device_set_type = 'rack' + AND ds.deleted_at IS NULL + AND dsm.device_identifier = ANY(@device_identifiers::text[]) + AND dsm.device_set_id != @exclude_rack_id::bigint +ORDER BY dsm.device_set_id ASC +FOR UPDATE OF ds; + -- name: RemoveDevicesFromAnyRack :execrows --- Removes the given devices from whatever rack they're currently in. --- AssignDevicesToRack uses this to clear prior rack membership inside --- the same transaction as the new-rack insert, closing the orphan --- window the client-side remove + add orchestration had. +-- Removes the given devices from whatever rack they're currently in, +-- EXCEPT the target rack (@target_rack_id). AssignDevicesToRack uses +-- this to clear prior rack membership inside the same transaction as +-- the new-rack insert, closing the orphan window the client-side +-- remove + add orchestration had. Skipping the target rack preserves +-- the existing membership row (and its rack_slot child) when a device +-- is reassigned to the same rack it's already in -- otherwise the +-- DELETE would cascade rack_slot rows that we'd silently lose. Pass +-- 0 for an unconditional clear (caller intends to unassign). DELETE FROM device_set_membership WHERE org_id = $1 AND device_identifier = ANY(@device_identifiers::text[]) - AND device_set_type = 'rack'; + AND device_set_type = 'rack' + AND device_set_id != @target_rack_id::bigint; -- name: RemoveDevicesFromDeviceSet :execrows DELETE FROM device_set_membership diff --git a/server/sqlc/queries/site.sql b/server/sqlc/queries/site.sql index 8376d1781..bf2893fa2 100644 --- a/server/sqlc/queries/site.sql +++ b/server/sqlc/queries/site.sql @@ -295,6 +295,42 @@ WHERE dsr.org_id = sqlc.arg('org_id') AND ds.deleted_at IS NULL ); +-- name: ReassignRacksUnderBuildingsBulk :execrows +-- Bulk variant of ReassignRacksUnderBuilding. Sets rack.site_id = +-- $target for every live rack pointing at any of @building_ids in one +-- statement. Caller wraps in the same tx as the building UPDATE so +-- the building/rack/device site_ids stay in lockstep. +UPDATE device_set_rack dsr +SET site_id = sqlc.narg('target_site_id') +WHERE dsr.org_id = sqlc.arg('org_id') + AND dsr.building_id = ANY(sqlc.arg('building_ids')::bigint[]) + AND EXISTS ( + SELECT 1 FROM device_set ds + WHERE ds.id = dsr.device_set_id + AND ds.deleted_at IS NULL + ); + +-- name: ReassignDevicesUnderBuildingsBulk :execrows +-- Bulk variant of ReassignDevicesUnderBuilding. Sets device.site_id = +-- $target for every device in any live rack of any building in +-- @building_ids. Caller wraps in the same tx as the building UPDATE. +UPDATE device d +SET site_id = sqlc.narg('target_site_id'), + updated_at = CURRENT_TIMESTAMP +FROM device_set_membership dsm +JOIN device_set ds + ON ds.id = dsm.device_set_id + AND ds.deleted_at IS NULL +JOIN device_set_rack dsr + ON dsr.device_set_id = dsm.device_set_id + AND dsr.org_id = dsm.org_id +WHERE d.id = dsm.device_id + AND d.org_id = dsm.org_id + AND dsm.device_set_type = 'rack' + AND d.org_id = sqlc.arg('org_id') + AND dsr.building_id = ANY(sqlc.arg('building_ids')::bigint[]) + AND d.deleted_at IS NULL; + -- name: ReassignDevicesUnderBuilding :execrows -- Sets device.site_id = $target for every device in any live rack of -- the given building. Caller wraps this in the same tx as the building From 2ced5b821134a345d72ed1f0504020c7366d004d Mon Sep 17 00:00:00 2001 From: flesher Date: Mon, 15 Jun 2026 12:16:41 -0700 Subject: [PATCH 8/9] chore: regen sqlc after rebase onto main Picks up main's curtailment automation query registrations alongside the branch's new bulk SQL queries. Co-Authored-By: Claude Opus 4.7 --- server/generated/sqlc/db.go | 1736 ++++++++++++++++++++--------------- 1 file changed, 988 insertions(+), 748 deletions(-) diff --git a/server/generated/sqlc/db.go b/server/generated/sqlc/db.go index f43278f36..7b3117162 100644 --- a/server/generated/sqlc/db.go +++ b/server/generated/sqlc/db.go @@ -90,6 +90,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.claimMessageForProcessingStmt, err = db.PrepareContext(ctx, claimMessageForProcessing); err != nil { return nil, fmt.Errorf("error preparing query ClaimMessageForProcessing: %w", err) } + if q.clearCurtailmentAutomationActiveEventStmt, err = db.PrepareContext(ctx, clearCurtailmentAutomationActiveEvent); err != nil { + return nil, fmt.Errorf("error preparing query ClearCurtailmentAutomationActiveEvent: %w", err) + } if q.clearRackPlacementForSoftDeleteStmt, err = db.PrepareContext(ctx, clearRackPlacementForSoftDelete); err != nil { return nil, fmt.Errorf("error preparing query ClearRackPlacementForSoftDelete: %w", err) } @@ -120,6 +123,12 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.countComponentsWithErrorsStmt, err = db.PrepareContext(ctx, countComponentsWithErrors); err != nil { return nil, fmt.Errorf("error preparing query CountComponentsWithErrors: %w", err) } + if q.countCurtailmentAutomationRulesByMQTTSourceStmt, err = db.PrepareContext(ctx, countCurtailmentAutomationRulesByMQTTSource); err != nil { + return nil, fmt.Errorf("error preparing query CountCurtailmentAutomationRulesByMQTTSource: %w", err) + } + if q.countCurtailmentAutomationRulesByResponseProfileStmt, err = db.PrepareContext(ctx, countCurtailmentAutomationRulesByResponseProfile); err != nil { + return nil, fmt.Errorf("error preparing query CountCurtailmentAutomationRulesByResponseProfile: %w", err) + } if q.countDevicesWithErrorsStmt, err = db.PrepareContext(ctx, countDevicesWithErrors); err != nil { return nil, fmt.Errorf("error preparing query CountDevicesWithErrors: %w", err) } @@ -189,6 +198,15 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.curtailmentEventHasInFlightTargetsStmt, err = db.PrepareContext(ctx, curtailmentEventHasInFlightTargets); err != nil { return nil, fmt.Errorf("error preparing query CurtailmentEventHasInFlightTargets: %w", err) } + if q.deleteCurtailmentAutomationRuleByOrgStmt, err = db.PrepareContext(ctx, deleteCurtailmentAutomationRuleByOrg); err != nil { + return nil, fmt.Errorf("error preparing query DeleteCurtailmentAutomationRuleByOrg: %w", err) + } + if q.deleteCurtailmentResponseProfileByOrgStmt, err = db.PrepareContext(ctx, deleteCurtailmentResponseProfileByOrg); err != nil { + return nil, fmt.Errorf("error preparing query DeleteCurtailmentResponseProfileByOrg: %w", err) + } + if q.deleteCurtailmentResponseProfilesBySiteStmt, err = db.PrepareContext(ctx, deleteCurtailmentResponseProfilesBySite); err != nil { + return nil, fmt.Errorf("error preparing query DeleteCurtailmentResponseProfilesBySite: %w", err) + } if q.deleteDisabledMQTTSourceConfigByOrgStmt, err = db.PrepareContext(ctx, deleteDisabledMQTTSourceConfigByOrg); err != nil { return nil, fmt.Errorf("error preparing query DeleteDisabledMQTTSourceConfigByOrg: %w", err) } @@ -210,6 +228,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.deviceHasActiveCloudPairingStmt, err = db.PrepareContext(ctx, deviceHasActiveCloudPairing); err != nil { return nil, fmt.Errorf("error preparing query DeviceHasActiveCloudPairing: %w", err) } + if q.deviceHasActivePairingStmt, err = db.PrepareContext(ctx, deviceHasActivePairing); err != nil { + return nil, fmt.Errorf("error preparing query DeviceHasActivePairing: %w", err) + } if q.deviceSetBelongsToOrgStmt, err = db.PrepareContext(ctx, deviceSetBelongsToOrg); err != nil { return nil, fmt.Errorf("error preparing query DeviceSetBelongsToOrg: %w", err) } @@ -282,6 +303,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.getBuiltinRoleForOrgStmt, err = db.PrepareContext(ctx, getBuiltinRoleForOrg); err != nil { return nil, fmt.Errorf("error preparing query GetBuiltinRoleForOrg: %w", err) } + if q.getCurtailmentAutomationRuleByOrgStmt, err = db.PrepareContext(ctx, getCurtailmentAutomationRuleByOrg); err != nil { + return nil, fmt.Errorf("error preparing query GetCurtailmentAutomationRuleByOrg: %w", err) + } if q.getCurtailmentEventByExternalReferenceStmt, err = db.PrepareContext(ctx, getCurtailmentEventByExternalReference); err != nil { return nil, fmt.Errorf("error preparing query GetCurtailmentEventByExternalReference: %w", err) } @@ -300,6 +324,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.getCurtailmentReconcilerHeartbeatStmt, err = db.PrepareContext(ctx, getCurtailmentReconcilerHeartbeat); err != nil { return nil, fmt.Errorf("error preparing query GetCurtailmentReconcilerHeartbeat: %w", err) } + if q.getCurtailmentResponseProfileByOrgStmt, err = db.PrepareContext(ctx, getCurtailmentResponseProfileByOrg); err != nil { + return nil, fmt.Errorf("error preparing query GetCurtailmentResponseProfileByOrg: %w", err) + } if q.getCurtailmentTargetRollupByEventStmt, err = db.PrepareContext(ctx, getCurtailmentTargetRollupByEvent); err != nil { return nil, fmt.Errorf("error preparing query GetCurtailmentTargetRollupByEvent: %w", err) } @@ -594,9 +621,15 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.insertActivityLogStmt, err = db.PrepareContext(ctx, insertActivityLog); err != nil { return nil, fmt.Errorf("error preparing query InsertActivityLog: %w", err) } + if q.insertCurtailmentAutomationRuleStmt, err = db.PrepareContext(ctx, insertCurtailmentAutomationRule); err != nil { + return nil, fmt.Errorf("error preparing query InsertCurtailmentAutomationRule: %w", err) + } if q.insertCurtailmentEventStmt, err = db.PrepareContext(ctx, insertCurtailmentEvent); err != nil { return nil, fmt.Errorf("error preparing query InsertCurtailmentEvent: %w", err) } + if q.insertCurtailmentResponseProfileStmt, err = db.PrepareContext(ctx, insertCurtailmentResponseProfile); err != nil { + return nil, fmt.Errorf("error preparing query InsertCurtailmentResponseProfile: %w", err) + } if q.insertDeviceStmt, err = db.PrepareContext(ctx, insertDevice); err != nil { return nil, fmt.Errorf("error preparing query InsertDevice: %w", err) } @@ -612,6 +645,12 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.insertMinerStateSnapshotStmt, err = db.PrepareContext(ctx, insertMinerStateSnapshot); err != nil { return nil, fmt.Errorf("error preparing query InsertMinerStateSnapshot: %w", err) } + if q.insertNotificationHistoryStmt, err = db.PrepareContext(ctx, insertNotificationHistory); err != nil { + return nil, fmt.Errorf("error preparing query InsertNotificationHistory: %w", err) + } + if q.insertNotificationMetricSamplesStmt, err = db.PrepareContext(ctx, insertNotificationMetricSamples); err != nil { + return nil, fmt.Errorf("error preparing query InsertNotificationMetricSamples: %w", err) + } if q.isBatchFinishedStmt, err = db.PrepareContext(ctx, isBatchFinished); err != nil { return nil, fmt.Errorf("error preparing query IsBatchFinished: %w", err) } @@ -651,12 +690,18 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.listBuiltinRolesForOrgStmt, err = db.PrepareContext(ctx, listBuiltinRolesForOrg); err != nil { return nil, fmt.Errorf("error preparing query ListBuiltinRolesForOrg: %w", err) } + if q.listCurtailmentAutomationRulesByOrgStmt, err = db.PrepareContext(ctx, listCurtailmentAutomationRulesByOrg); err != nil { + return nil, fmt.Errorf("error preparing query ListCurtailmentAutomationRulesByOrg: %w", err) + } if q.listCurtailmentCandidatesByOrgStmt, err = db.PrepareContext(ctx, listCurtailmentCandidatesByOrg); err != nil { return nil, fmt.Errorf("error preparing query ListCurtailmentCandidatesByOrg: %w", err) } if q.listCurtailmentEventsForOrgStmt, err = db.PrepareContext(ctx, listCurtailmentEventsForOrg); err != nil { return nil, fmt.Errorf("error preparing query ListCurtailmentEventsForOrg: %w", err) } + if q.listCurtailmentResponseProfilesByOrgStmt, err = db.PrepareContext(ctx, listCurtailmentResponseProfilesByOrg); err != nil { + return nil, fmt.Errorf("error preparing query ListCurtailmentResponseProfilesByOrg: %w", err) + } if q.listCurtailmentTargetsByEventStmt, err = db.PrepareContext(ctx, listCurtailmentTargetsByEvent); err != nil { return nil, fmt.Errorf("error preparing query ListCurtailmentTargetsByEvent: %w", err) } @@ -678,6 +723,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.listEffectivePermissionsForUserForUpdateStmt, err = db.PrepareContext(ctx, listEffectivePermissionsForUserForUpdate); err != nil { return nil, fmt.Errorf("error preparing query ListEffectivePermissionsForUserForUpdate: %w", err) } + if q.listEnabledCurtailmentAutomationRulesByMQTTSourceStmt, err = db.PrepareContext(ctx, listEnabledCurtailmentAutomationRulesByMQTTSource); err != nil { + return nil, fmt.Errorf("error preparing query ListEnabledCurtailmentAutomationRulesByMQTTSource: %w", err) + } if q.listEnabledMQTTSourcesStmt, err = db.PrepareContext(ctx, listEnabledMQTTSources); err != nil { return nil, fmt.Errorf("error preparing query ListEnabledMQTTSources: %w", err) } @@ -870,6 +918,21 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.revokeSessionStmt, err = db.PrepareContext(ctx, revokeSession); err != nil { return nil, fmt.Errorf("error preparing query RevokeSession: %w", err) } + if q.setCurtailmentAutomationActiveEventStmt, err = db.PrepareContext(ctx, setCurtailmentAutomationActiveEvent); err != nil { + return nil, fmt.Errorf("error preparing query SetCurtailmentAutomationActiveEvent: %w", err) + } + if q.setCurtailmentAutomationExecutionErrorStmt, err = db.PrepareContext(ctx, setCurtailmentAutomationExecutionError); err != nil { + return nil, fmt.Errorf("error preparing query SetCurtailmentAutomationExecutionError: %w", err) + } + if q.setCurtailmentAutomationRestoreStartedStmt, err = db.PrepareContext(ctx, setCurtailmentAutomationRestoreStarted); err != nil { + return nil, fmt.Errorf("error preparing query SetCurtailmentAutomationRestoreStarted: %w", err) + } + if q.setCurtailmentAutomationRuleEnabledStmt, err = db.PrepareContext(ctx, setCurtailmentAutomationRuleEnabled); err != nil { + return nil, fmt.Errorf("error preparing query SetCurtailmentAutomationRuleEnabled: %w", err) + } + if q.setDevicePairingAuthNeededIfNotPairedStmt, err = db.PrepareContext(ctx, setDevicePairingAuthNeededIfNotPaired); err != nil { + return nil, fmt.Errorf("error preparing query SetDevicePairingAuthNeededIfNotPaired: %w", err) + } if q.setFleetNodeEnrollmentStatusStmt, err = db.PrepareContext(ctx, setFleetNodeEnrollmentStatus); err != nil { return nil, fmt.Errorf("error preparing query SetFleetNodeEnrollmentStatus: %w", err) } @@ -993,12 +1056,18 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.updateBuildingStmt, err = db.PrepareContext(ctx, updateBuilding); err != nil { return nil, fmt.Errorf("error preparing query UpdateBuilding: %w", err) } + if q.updateCurtailmentAutomationRuleStmt, err = db.PrepareContext(ctx, updateCurtailmentAutomationRule); err != nil { + return nil, fmt.Errorf("error preparing query UpdateCurtailmentAutomationRule: %w", err) + } if q.updateCurtailmentEventOperatorFieldsStmt, err = db.PrepareContext(ctx, updateCurtailmentEventOperatorFields); err != nil { return nil, fmt.Errorf("error preparing query UpdateCurtailmentEventOperatorFields: %w", err) } if q.updateCurtailmentEventStateStmt, err = db.PrepareContext(ctx, updateCurtailmentEventState); err != nil { return nil, fmt.Errorf("error preparing query UpdateCurtailmentEventState: %w", err) } + if q.updateCurtailmentResponseProfileStmt, err = db.PrepareContext(ctx, updateCurtailmentResponseProfile); err != nil { + return nil, fmt.Errorf("error preparing query UpdateCurtailmentResponseProfile: %w", err) + } if q.updateCurtailmentTargetStateStmt, err = db.PrepareContext(ctx, updateCurtailmentTargetState); err != nil { return nil, fmt.Errorf("error preparing query UpdateCurtailmentTargetState: %w", err) } @@ -1107,6 +1176,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.upsertCommandOnDeviceLogStmt, err = db.PrepareContext(ctx, upsertCommandOnDeviceLog); err != nil { return nil, fmt.Errorf("error preparing query UpsertCommandOnDeviceLog: %w", err) } + if q.upsertCurtailmentAutomationSignalStateStmt, err = db.PrepareContext(ctx, upsertCurtailmentAutomationSignalState); err != nil { + return nil, fmt.Errorf("error preparing query UpsertCurtailmentAutomationSignalState: %w", err) + } if q.upsertCurtailmentReconcilerHeartbeatStmt, err = db.PrepareContext(ctx, upsertCurtailmentReconcilerHeartbeat); err != nil { return nil, fmt.Errorf("error preparing query UpsertCurtailmentReconcilerHeartbeat: %w", err) } @@ -1255,6 +1327,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing claimMessageForProcessingStmt: %w", cerr) } } + if q.clearCurtailmentAutomationActiveEventStmt != nil { + if cerr := q.clearCurtailmentAutomationActiveEventStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing clearCurtailmentAutomationActiveEventStmt: %w", cerr) + } + } if q.clearRackPlacementForSoftDeleteStmt != nil { if cerr := q.clearRackPlacementForSoftDeleteStmt.Close(); cerr != nil { err = fmt.Errorf("error closing clearRackPlacementForSoftDeleteStmt: %w", cerr) @@ -1305,6 +1382,16 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing countComponentsWithErrorsStmt: %w", cerr) } } + if q.countCurtailmentAutomationRulesByMQTTSourceStmt != nil { + if cerr := q.countCurtailmentAutomationRulesByMQTTSourceStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing countCurtailmentAutomationRulesByMQTTSourceStmt: %w", cerr) + } + } + if q.countCurtailmentAutomationRulesByResponseProfileStmt != nil { + if cerr := q.countCurtailmentAutomationRulesByResponseProfileStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing countCurtailmentAutomationRulesByResponseProfileStmt: %w", cerr) + } + } if q.countDevicesWithErrorsStmt != nil { if cerr := q.countDevicesWithErrorsStmt.Close(); cerr != nil { err = fmt.Errorf("error closing countDevicesWithErrorsStmt: %w", cerr) @@ -1420,6 +1507,21 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing curtailmentEventHasInFlightTargetsStmt: %w", cerr) } } + if q.deleteCurtailmentAutomationRuleByOrgStmt != nil { + if cerr := q.deleteCurtailmentAutomationRuleByOrgStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing deleteCurtailmentAutomationRuleByOrgStmt: %w", cerr) + } + } + if q.deleteCurtailmentResponseProfileByOrgStmt != nil { + if cerr := q.deleteCurtailmentResponseProfileByOrgStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing deleteCurtailmentResponseProfileByOrgStmt: %w", cerr) + } + } + if q.deleteCurtailmentResponseProfilesBySiteStmt != nil { + if cerr := q.deleteCurtailmentResponseProfilesBySiteStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing deleteCurtailmentResponseProfilesBySiteStmt: %w", cerr) + } + } if q.deleteDisabledMQTTSourceConfigByOrgStmt != nil { if cerr := q.deleteDisabledMQTTSourceConfigByOrgStmt.Close(); cerr != nil { err = fmt.Errorf("error closing deleteDisabledMQTTSourceConfigByOrgStmt: %w", cerr) @@ -1455,6 +1557,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing deviceHasActiveCloudPairingStmt: %w", cerr) } } + if q.deviceHasActivePairingStmt != nil { + if cerr := q.deviceHasActivePairingStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing deviceHasActivePairingStmt: %w", cerr) + } + } if q.deviceSetBelongsToOrgStmt != nil { if cerr := q.deviceSetBelongsToOrgStmt.Close(); cerr != nil { err = fmt.Errorf("error closing deviceSetBelongsToOrgStmt: %w", cerr) @@ -1575,6 +1682,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getBuiltinRoleForOrgStmt: %w", cerr) } } + if q.getCurtailmentAutomationRuleByOrgStmt != nil { + if cerr := q.getCurtailmentAutomationRuleByOrgStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getCurtailmentAutomationRuleByOrgStmt: %w", cerr) + } + } if q.getCurtailmentEventByExternalReferenceStmt != nil { if cerr := q.getCurtailmentEventByExternalReferenceStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getCurtailmentEventByExternalReferenceStmt: %w", cerr) @@ -1605,6 +1717,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getCurtailmentReconcilerHeartbeatStmt: %w", cerr) } } + if q.getCurtailmentResponseProfileByOrgStmt != nil { + if cerr := q.getCurtailmentResponseProfileByOrgStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getCurtailmentResponseProfileByOrgStmt: %w", cerr) + } + } if q.getCurtailmentTargetRollupByEventStmt != nil { if cerr := q.getCurtailmentTargetRollupByEventStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getCurtailmentTargetRollupByEventStmt: %w", cerr) @@ -2095,11 +2212,21 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing insertActivityLogStmt: %w", cerr) } } + if q.insertCurtailmentAutomationRuleStmt != nil { + if cerr := q.insertCurtailmentAutomationRuleStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing insertCurtailmentAutomationRuleStmt: %w", cerr) + } + } if q.insertCurtailmentEventStmt != nil { if cerr := q.insertCurtailmentEventStmt.Close(); cerr != nil { err = fmt.Errorf("error closing insertCurtailmentEventStmt: %w", cerr) } } + if q.insertCurtailmentResponseProfileStmt != nil { + if cerr := q.insertCurtailmentResponseProfileStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing insertCurtailmentResponseProfileStmt: %w", cerr) + } + } if q.insertDeviceStmt != nil { if cerr := q.insertDeviceStmt.Close(); cerr != nil { err = fmt.Errorf("error closing insertDeviceStmt: %w", cerr) @@ -2125,6 +2252,16 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing insertMinerStateSnapshotStmt: %w", cerr) } } + if q.insertNotificationHistoryStmt != nil { + if cerr := q.insertNotificationHistoryStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing insertNotificationHistoryStmt: %w", cerr) + } + } + if q.insertNotificationMetricSamplesStmt != nil { + if cerr := q.insertNotificationMetricSamplesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing insertNotificationMetricSamplesStmt: %w", cerr) + } + } if q.isBatchFinishedStmt != nil { if cerr := q.isBatchFinishedStmt.Close(); cerr != nil { err = fmt.Errorf("error closing isBatchFinishedStmt: %w", cerr) @@ -2190,6 +2327,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing listBuiltinRolesForOrgStmt: %w", cerr) } } + if q.listCurtailmentAutomationRulesByOrgStmt != nil { + if cerr := q.listCurtailmentAutomationRulesByOrgStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listCurtailmentAutomationRulesByOrgStmt: %w", cerr) + } + } if q.listCurtailmentCandidatesByOrgStmt != nil { if cerr := q.listCurtailmentCandidatesByOrgStmt.Close(); cerr != nil { err = fmt.Errorf("error closing listCurtailmentCandidatesByOrgStmt: %w", cerr) @@ -2200,6 +2342,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing listCurtailmentEventsForOrgStmt: %w", cerr) } } + if q.listCurtailmentResponseProfilesByOrgStmt != nil { + if cerr := q.listCurtailmentResponseProfilesByOrgStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listCurtailmentResponseProfilesByOrgStmt: %w", cerr) + } + } if q.listCurtailmentTargetsByEventStmt != nil { if cerr := q.listCurtailmentTargetsByEventStmt.Close(); cerr != nil { err = fmt.Errorf("error closing listCurtailmentTargetsByEventStmt: %w", cerr) @@ -2235,6 +2382,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing listEffectivePermissionsForUserForUpdateStmt: %w", cerr) } } + if q.listEnabledCurtailmentAutomationRulesByMQTTSourceStmt != nil { + if cerr := q.listEnabledCurtailmentAutomationRulesByMQTTSourceStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listEnabledCurtailmentAutomationRulesByMQTTSourceStmt: %w", cerr) + } + } if q.listEnabledMQTTSourcesStmt != nil { if cerr := q.listEnabledMQTTSourcesStmt.Close(); cerr != nil { err = fmt.Errorf("error closing listEnabledMQTTSourcesStmt: %w", cerr) @@ -2555,6 +2707,31 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing revokeSessionStmt: %w", cerr) } } + if q.setCurtailmentAutomationActiveEventStmt != nil { + if cerr := q.setCurtailmentAutomationActiveEventStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing setCurtailmentAutomationActiveEventStmt: %w", cerr) + } + } + if q.setCurtailmentAutomationExecutionErrorStmt != nil { + if cerr := q.setCurtailmentAutomationExecutionErrorStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing setCurtailmentAutomationExecutionErrorStmt: %w", cerr) + } + } + if q.setCurtailmentAutomationRestoreStartedStmt != nil { + if cerr := q.setCurtailmentAutomationRestoreStartedStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing setCurtailmentAutomationRestoreStartedStmt: %w", cerr) + } + } + if q.setCurtailmentAutomationRuleEnabledStmt != nil { + if cerr := q.setCurtailmentAutomationRuleEnabledStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing setCurtailmentAutomationRuleEnabledStmt: %w", cerr) + } + } + if q.setDevicePairingAuthNeededIfNotPairedStmt != nil { + if cerr := q.setDevicePairingAuthNeededIfNotPairedStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing setDevicePairingAuthNeededIfNotPairedStmt: %w", cerr) + } + } if q.setFleetNodeEnrollmentStatusStmt != nil { if cerr := q.setFleetNodeEnrollmentStatusStmt.Close(); cerr != nil { err = fmt.Errorf("error closing setFleetNodeEnrollmentStatusStmt: %w", cerr) @@ -2760,6 +2937,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing updateBuildingStmt: %w", cerr) } } + if q.updateCurtailmentAutomationRuleStmt != nil { + if cerr := q.updateCurtailmentAutomationRuleStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateCurtailmentAutomationRuleStmt: %w", cerr) + } + } if q.updateCurtailmentEventOperatorFieldsStmt != nil { if cerr := q.updateCurtailmentEventOperatorFieldsStmt.Close(); cerr != nil { err = fmt.Errorf("error closing updateCurtailmentEventOperatorFieldsStmt: %w", cerr) @@ -2770,6 +2952,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing updateCurtailmentEventStateStmt: %w", cerr) } } + if q.updateCurtailmentResponseProfileStmt != nil { + if cerr := q.updateCurtailmentResponseProfileStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateCurtailmentResponseProfileStmt: %w", cerr) + } + } if q.updateCurtailmentTargetStateStmt != nil { if cerr := q.updateCurtailmentTargetStateStmt.Close(); cerr != nil { err = fmt.Errorf("error closing updateCurtailmentTargetStateStmt: %w", cerr) @@ -2950,6 +3137,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing upsertCommandOnDeviceLogStmt: %w", cerr) } } + if q.upsertCurtailmentAutomationSignalStateStmt != nil { + if cerr := q.upsertCurtailmentAutomationSignalStateStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing upsertCurtailmentAutomationSignalStateStmt: %w", cerr) + } + } if q.upsertCurtailmentReconcilerHeartbeatStmt != nil { if cerr := q.upsertCurtailmentReconcilerHeartbeatStmt.Close(); cerr != nil { err = fmt.Errorf("error closing upsertCurtailmentReconcilerHeartbeatStmt: %w", cerr) @@ -3042,757 +3234,805 @@ func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, ar } type Queries struct { - db DBTX - tx *sql.Tx - acquireReconcileLockStmt *sql.Stmt - addDevicesToDeviceSetStmt *sql.Stmt - adminResetUserPasswordStmt *sql.Stmt - adminTerminateCurtailmentEventStmt *sql.Stmt - allDevicesBelongToOrgStmt *sql.Stmt - assignBuildingToSiteStmt *sql.Stmt - assignBuildingsToSiteBulkStmt *sql.Stmt - assignDevicesToSiteStmt *sql.Stmt - assignPermissionToRoleStmt *sql.Stmt - assignRoleStmt *sql.Stmt - beginCurtailmentRestorationStmt *sql.Stmt - bindEnrollmentToFleetNodeStmt *sql.Stmt - buildingBelongsToOrgStmt *sql.Stmt - buildingsByIDsStmt *sql.Stmt - bulkInsertCurtailmentTargetsStmt *sql.Stmt - bumpCurtailmentTargetRetryStmt *sql.Stmt - cancelEnrollmentForFleetNodeStmt *sql.Stmt - cancelPendingEnrollmentStmt *sql.Stmt - cascadeAddedDeviceSitesStmt *sql.Stmt - cascadeRackDeviceSitesStmt *sql.Stmt - cascadeRackDeviceSitesBulkStmt *sql.Stmt - claimMessageForProcessingStmt *sql.Stmt - clearRackPlacementForSoftDeleteStmt *sql.Stmt - clearRackSlotPositionStmt *sql.Stmt - clearRolePermissionsStmt *sql.Stmt - closeStaleErrorsStmt *sql.Stmt - confirmEnrollmentStmt *sql.Stmt - consumeFleetNodeAuthChallengeStmt *sql.Stmt - countActiveAssignmentsForRoleStmt *sql.Stmt - countActiveUnpairedDiscoveredDevicesStmt *sql.Stmt - countActivityLogsStmt *sql.Stmt - countComponentsWithErrorsStmt *sql.Stmt - countDevicesWithErrorsStmt *sql.Stmt - countErrorsStmt *sql.Stmt - countMinersByStateStmt *sql.Stmt - countOrgScopeSuperAdminsExcludingUserStmt *sql.Stmt - createApiKeyStmt *sql.Stmt - createBuildingStmt *sql.Stmt - createCommandBatchLogStmt *sql.Stmt - createCustomRoleStmt *sql.Stmt - createDeviceSetStmt *sql.Stmt - createFleetNodeStmt *sql.Stmt - createFleetNodeApiKeyStmt *sql.Stmt - createOrganizationStmt *sql.Stmt - createPendingEnrollmentStmt *sql.Stmt - createPoolStmt *sql.Stmt - createQueueMessageStmt *sql.Stmt - createRackExtensionStmt *sql.Stmt - createScheduleStmt *sql.Stmt - createScheduleTargetStmt *sql.Stmt - createSessionStmt *sql.Stmt - createSiteStmt *sql.Stmt - createUserStmt *sql.Stmt - createUserOrganizationStmt *sql.Stmt - curtailmentEventHasInFlightTargetsStmt *sql.Stmt - deleteDisabledMQTTSourceConfigByOrgStmt *sql.Stmt - deleteExpiredSessionsStmt *sql.Stmt - deleteOrganizationStmt *sql.Stmt - deletePairingsForFleetNodeStmt *sql.Stmt - deletePoolStmt *sql.Stmt - deleteScheduleTargetsStmt *sql.Stmt - deviceHasActiveCloudPairingStmt *sql.Stmt - deviceSetBelongsToOrgStmt *sql.Stmt - ensureCurtailmentOrgConfigStmt *sql.Stmt - findDeviceSiteConflictsStmt *sql.Stmt - getActiveCurtailmentEventStmt *sql.Stmt - getActiveSchedulesStmt *sql.Stmt - getActiveUnpairedDiscoveredDevicesStmt *sql.Stmt - getAddedDeviceSiteConflictsStmt *sql.Stmt - getAllDeviceInfoForCapabilityCheckStmt *sql.Stmt - getAllDeviceMetricsDailyAggregatesStmt *sql.Stmt - getAllDeviceMetricsHourlyAggregatesStmt *sql.Stmt - getAllDeviceMetricsTimeSeriesStmt *sql.Stmt - getAllDeviceStatusDailyAggregatesStmt *sql.Stmt - getAllDeviceStatusHourlyAggregatesStmt *sql.Stmt - getAllPairedDeviceIdentifiersStmt *sql.Stmt - getApiKeyByHashStmt *sql.Stmt - getAssignmentByIDStmt *sql.Stmt - getAvailableFirmwareVersionsStmt *sql.Stmt - getAvailableModelsStmt *sql.Stmt - getBatchHeaderForOrgStmt *sql.Stmt - getBatchLogStmt *sql.Stmt - getBatchStatusAndDeviceCountsStmt *sql.Stmt - getBuildingStmt *sql.Stmt - getBuildingSiteStmt *sql.Stmt - getBuiltinRoleForOrgStmt *sql.Stmt - getCurtailmentEventByExternalReferenceStmt *sql.Stmt - getCurtailmentEventByIdempotencyKeyStmt *sql.Stmt - getCurtailmentEventByUUIDStmt *sql.Stmt - getCurtailmentEventDetailByUUIDStmt *sql.Stmt - getCurtailmentOrgConfigStmt *sql.Stmt - getCurtailmentReconcilerHeartbeatStmt *sql.Stmt - getCurtailmentTargetRollupByEventStmt *sql.Stmt - getDeviceByDeviceIdentifierStmt *sql.Stmt - getDeviceByIDStmt *sql.Stmt - getDeviceDeviceSetsStmt *sql.Stmt - getDeviceDeviceSetsByTypeStmt *sql.Stmt - getDeviceIDByDeviceIdentifierStmt *sql.Stmt - getDeviceIDByIdentifierStmt *sql.Stmt - getDeviceIDsByDeviceIdentifiersStmt *sql.Stmt - getDeviceIDsWithIdentifiersStmt *sql.Stmt - getDeviceIdentifierByIDStmt *sql.Stmt - getDeviceIdentifiersByDeviceSetIDStmt *sql.Stmt - getDeviceInfoForCapabilityCheckStmt *sql.Stmt - getDeviceMetricsDailyAggregatesStmt *sql.Stmt - getDeviceMetricsHourlyAggregatesStmt *sql.Stmt - getDeviceMetricsTimeSeriesStmt *sql.Stmt - getDevicePairingStatusByDeviceDatabaseIDStmt *sql.Stmt - getDevicePropertiesForRenameStmt *sql.Stmt - getDevicePropertiesForRenameWithoutTelemetryStmt *sql.Stmt - getDeviceSetStmt *sql.Stmt - getDeviceSetTypeStmt *sql.Stmt - getDeviceSetTypesBatchStmt *sql.Stmt - getDeviceSiteIDsByMembershipStmt *sql.Stmt - getDeviceStatusStmt *sql.Stmt - getDeviceStatusByDeviceIdentifierStmt *sql.Stmt - getDeviceStatusDailyAggregatesStmt *sql.Stmt - getDeviceStatusForDeviceIdentifiersStmt *sql.Stmt - getDeviceStatusHourlyAggregatesStmt *sql.Stmt - getDeviceWithCredentialsAndIPByDeviceIdentifierStmt *sql.Stmt - getDeviceWithCredentialsAndIPByIDStmt *sql.Stmt - getDiscoveredDeviceByDeviceIdentifierStmt *sql.Stmt - getDiscoveredDeviceByIDStmt *sql.Stmt - getDiscoveredDeviceByIPAndPortStmt *sql.Stmt - getDistinctActivityUsersStmt *sql.Stmt - getDistinctEventTypesStmt *sql.Stmt - getDistinctScopeTypesStmt *sql.Stmt - getErrorByErrorIDStmt *sql.Stmt - getErrorByIDStmt *sql.Stmt - getFilteredDeviceIdentifiersStmt *sql.Stmt - getFilteredDeviceIdsStmt *sql.Stmt - getFleetNodeByIDStmt *sql.Stmt - getFleetNodeByIDUnscopedStmt *sql.Stmt - getFleetNodeSessionByTokenHashStmt *sql.Stmt - getGroupLabelsForDevicesStmt *sql.Stmt - getKnownSubnetsStmt *sql.Stmt - getLatestAllDeviceMetricsStmt *sql.Stmt - getLatestDeviceMetricsStmt *sql.Stmt - getMQTTSourceConfigByOrgStmt *sql.Stmt - getMQTTSourceStateByIDStmt *sql.Stmt - getMaxPriorityStmt *sql.Stmt - getMessagesToProcessStmt *sql.Stmt - getMinerCredentialsByDeviceIDStmt *sql.Stmt - getMinerModelGroupsStmt *sql.Stmt - getMinerStateCountsByDeviceIDsStmt *sql.Stmt - getMinerStateSnapshotsStmt *sql.Stmt - getOfflineDevicesStmt *sql.Stmt - getOpenErrorByDedupKeyStmt *sql.Stmt - getOrgScopeAssignmentForUserStmt *sql.Stmt - getOrganizationByIDStmt *sql.Stmt - getOrganizationByNameStmt *sql.Stmt - getOrganizationByOrgIDStmt *sql.Stmt - getOrganizationPrivateKeyStmt *sql.Stmt - getOrganizationsForUserStmt *sql.Stmt - getPairedDeviceByMACAddressStmt *sql.Stmt - getPairedDeviceBySerialNumberStmt *sql.Stmt - getPairedDevicesByMACAddressesStmt *sql.Stmt - getPairedDevicesIdsStmt *sql.Stmt - getPendingEnrollmentByCodeHashStmt *sql.Stmt - getPendingEnrollmentByFleetNodeStmt *sql.Stmt - getPermissionByKeyStmt *sql.Stmt - getPermissionsByKeysStmt *sql.Stmt - getPoolStmt *sql.Stmt - getRackDetailsForDevicesStmt *sql.Stmt - getRackInfoStmt *sql.Stmt - getRackInfoBatchStmt *sql.Stmt - getRackSlotsStmt *sql.Stmt - getRoleByIDStmt *sql.Stmt - getRoleByIDForUpdateStmt *sql.Stmt - getRunningPowerTargetScheduleOverlapsStmt *sql.Stmt - getScheduleStmt *sql.Stmt - getScheduleByIDForProcessorStmt *sql.Stmt - getScheduleForUpdateStmt *sql.Stmt - getScheduleTargetsStmt *sql.Stmt - getScheduleTargetsByScheduleIDsStmt *sql.Stmt - getSessionByIDStmt *sql.Stmt - getSiteStmt *sql.Stmt - getTotalDevicesPendingAuthStmt *sql.Stmt - getTotalMinerStateSnapshotsStmt *sql.Stmt - getTotalPairedDevicesStmt *sql.Stmt - getTotalPoolsStmt *sql.Stmt - getUserByExternalIdStmt *sql.Stmt - getUserByIdStmt *sql.Stmt - getUserByIdForUpdateStmt *sql.Stmt - getUserByUsernameStmt *sql.Stmt - getUserRoleInOrganizationStmt *sql.Stmt - getUserRoleNameStmt *sql.Stmt - getUsersForOrganizationStmt *sql.Stmt - hasUserStmt *sql.Stmt - insertActivityLogStmt *sql.Stmt - insertCurtailmentEventStmt *sql.Stmt - insertDeviceStmt *sql.Stmt - insertDeviceMetricsStmt *sql.Stmt - insertErrorStmt *sql.Stmt - insertMQTTSourceConfigStmt *sql.Stmt - insertMinerStateSnapshotStmt *sql.Stmt - isBatchFinishedStmt *sql.Stmt - isBatchProcessingStmt *sql.Stmt - listActiveCurtailedDevicesByOrgStmt *sql.Stmt - listActiveCurtailmentEventsStmt *sql.Stmt - listActiveOrganizationIDsStmt *sql.Stmt - listActivityLogsStmt *sql.Stmt - listApiKeysByOrganizationStmt *sql.Stmt - listAssignmentsForRoleStmt *sql.Stmt - listAssignmentsForUserStmt *sql.Stmt - listBatchDeviceResultsStmt *sql.Stmt - listBuildingRacksStmt *sql.Stmt - listBuildingsByOrgStmt *sql.Stmt - listBuiltinRolesForOrgStmt *sql.Stmt - listCurtailmentCandidatesByOrgStmt *sql.Stmt - listCurtailmentEventsForOrgStmt *sql.Stmt - listCurtailmentTargetsByEventStmt *sql.Stmt - listCurtailmentTargetsByEventPageStmt *sql.Stmt - listCustomRolesForOrgStmt *sql.Stmt - listDeviceSetMembersPaginatedStmt *sql.Stmt - listDeviceSetMembersPaginatedAfterStmt *sql.Stmt - listEffectivePermissionsForUserStmt *sql.Stmt - listEffectivePermissionsForUserForUpdateStmt *sql.Stmt - listEnabledMQTTSourcesStmt *sql.Stmt - listExistingDeviceIdentifiersStmt *sql.Stmt - listFleetNodeDevicesStmt *sql.Stmt - listFleetNodeDiscoveredDevicesStmt *sql.Stmt - listFleetNodesForOrganizationStmt *sql.Stmt - listMQTTSourceConfigsByOrgStmt *sql.Stmt - listMQTTSourceStatesByOrgStmt *sql.Stmt - listMinerStateSnapshotsStmt *sql.Stmt - listNonTerminalCurtailmentEventsStmt *sql.Stmt - listOrganizationsStmt *sql.Stmt - listPermissionsStmt *sql.Stmt - listPoolsStmt *sql.Stmt - listRackTypesStmt *sql.Stmt - listRackZoneRefsStmt *sql.Stmt - listRackZonesStmt *sql.Stmt - listRacksOutsideBuildingBoundsStmt *sql.Stmt - listRecentlyResolvedCurtailedDevicesByOrgStmt *sql.Stmt - listRolePermissionKeysStmt *sql.Stmt - listRolesStmt *sql.Stmt - listRolesWithDetailsForOrgStmt *sql.Stmt - listScheduleIDStatusesStmt *sql.Stmt - listSchedulesStmt *sql.Stmt - listSiteNetworkConfigsForOverlapStmt *sql.Stmt - listSitesStmt *sql.Stmt - listUsersForOrganizationStmt *sql.Stmt - lockAndCountOrgScopeSuperAdminsStmt *sql.Stmt - lockBuildingForWriteStmt *sql.Stmt - lockBuildingsBySiteForWriteStmt *sql.Stmt - lockDevicesForReassignStmt *sql.Stmt - lockFleetNodeByIDStmt *sql.Stmt - lockRackPlacementForWriteStmt *sql.Stmt - lockSchedulePriorityStmt *sql.Stmt - lockSiteForWriteStmt *sql.Stmt - lockSourceRacksForDevicesStmt *sql.Stmt - markCommandBatchFinishedStmt *sql.Stmt - markCommandBatchFinishedWithStartedAtStmt *sql.Stmt - markCommandBatchProcessingStmt *sql.Stmt - negateSchedulePrioritiesStmt *sql.Stmt - pairDeviceToFleetNodeStmt *sql.Stmt - passwordUpdatedAtStmt *sql.Stmt - pauseActiveScheduleStmt *sql.Stmt - prunePermissionsOutsideKeysStmt *sql.Stmt - queryComponentKeysWithErrorsStmt *sql.Stmt - queryDeviceIDsWithErrorsStmt *sql.Stmt - queryErrorsStmt *sql.Stmt - reapStuckFirmwareUpdateMessagesStmt *sql.Stmt - reapStuckProcessingMessagesStmt *sql.Stmt - reassignDevicesUnderBuildingStmt *sql.Stmt - reassignDevicesUnderBuildingsBulkStmt *sql.Stmt - reassignRacksUnderBuildingStmt *sql.Stmt - reassignRacksUnderBuildingsBulkStmt *sql.Stmt - removeAllDevicesFromDeviceSetStmt *sql.Stmt - removeDevicesFromAnyRackStmt *sql.Stmt - removeDevicesFromDeviceSetStmt *sql.Stmt - resetCurtailmentTargetsForRecurtailStmt *sql.Stmt - resetCurtailmentTargetsForRestoreStmt *sql.Stmt - resumeCurtailmentFromRestoringStmt *sql.Stmt - resumePausedScheduleStmt *sql.Stmt - revertScheduleToActiveStmt *sql.Stmt - revokeAllSessionsByUserIDStmt *sql.Stmt - revokeApiKeyStmt *sql.Stmt - revokeApiKeysByFleetNodeIDStmt *sql.Stmt - revokePermissionFromRoleStmt *sql.Stmt - revokeSessionStmt *sql.Stmt - setFleetNodeEnrollmentStatusStmt *sql.Stmt - setMQTTSourceConfigEnabledStmt *sql.Stmt - setRackBuildingPositionStmt *sql.Stmt - setRackBuildingPositionBulkClearStmt *sql.Stmt - setRackBuildingPositionBulkPlaceStmt *sql.Stmt - setRackSlotPositionStmt *sql.Stmt - setSchedulePrioritiesStmt *sql.Stmt - setScheduleRunningStmt *sql.Stmt - siteBelongsToOrgStmt *sql.Stmt - softDeleteBuildingStmt *sql.Stmt - softDeleteBuildingsBySiteStmt *sql.Stmt - softDeleteCustomRoleStmt *sql.Stmt - softDeleteDeviceSetStmt *sql.Stmt - softDeleteDevicesStmt *sql.Stmt - softDeleteDiscoveredDeviceByIdentifierStmt *sql.Stmt - softDeleteDiscoveredDevicesForDeletedDevicesStmt *sql.Stmt - softDeleteFleetNodeStmt *sql.Stmt - softDeleteFleetNodesForExpiredEnrollmentsStmt *sql.Stmt - softDeleteOrganizationStmt *sql.Stmt - softDeletePoolStmt *sql.Stmt - softDeleteRoleStmt *sql.Stmt - softDeleteScheduleStmt *sql.Stmt - softDeleteSiteStmt *sql.Stmt - softDeleteUserStmt *sql.Stmt - softDeleteUserFromOrganizationStmt *sql.Stmt - sweepCurtailmentTargetsToRestoreFailedStmt *sql.Stmt - sweepExpiredEnrollmentsStmt *sql.Stmt - sweepExpiredFleetNodeAuthChallengesStmt *sql.Stmt - sweepExpiredFleetNodeSessionsStmt *sql.Stmt - transferDiscoveredDeviceAttributionStmt *sql.Stmt - unassignDeviceSitesByRackStmt *sql.Stmt - unassignDevicesFromSiteStmt *sql.Stmt - unassignRacksFromBuildingStmt *sql.Stmt - unassignRacksFromBuildingsBySiteStmt *sql.Stmt - unassignRacksFromSiteStmt *sql.Stmt - unassignRoleStmt *sql.Stmt - undeleteOrganizationStmt *sql.Stmt - undeleteRoleStmt *sql.Stmt - unpairDeviceStmt *sql.Stmt - updateApiKeyLastUsedStmt *sql.Stmt - updateBuildingStmt *sql.Stmt - updateCurtailmentEventOperatorFieldsStmt *sql.Stmt - updateCurtailmentEventStateStmt *sql.Stmt - updateCurtailmentTargetStateStmt *sql.Stmt - updateCustomRoleNameStmt *sql.Stmt - updateDeviceIPAssignmentStmt *sql.Stmt - updateDeviceInfoStmt *sql.Stmt - updateDevicePairingStatusByIdentifierStmt *sql.Stmt - updateDeviceSetDescriptionStmt *sql.Stmt - updateDeviceSetLabelStmt *sql.Stmt - updateDeviceSetLabelAndDescriptionStmt *sql.Stmt - updateDeviceWorkerNameStmt *sql.Stmt - updateDeviceWorkerNamePoolSyncStatusByIDStmt *sql.Stmt - updateDiscoveredDeviceFirmwareVersionStmt *sql.Stmt - updateFleetNodeLastSeenAtStmt *sql.Stmt - updateLastLoginStmt *sql.Stmt - updateMQTTSourceConfigStmt *sql.Stmt - updateMessageAfterFailureStmt *sql.Stmt - updateMessagePermanentlyFailedStmt *sql.Stmt - updateMessageStatusStmt *sql.Stmt - updateMinerPasswordStmt *sql.Stmt - updateOpenErrorStmt *sql.Stmt - updateOrganizationStmt *sql.Stmt - updatePoolStmt *sql.Stmt - updateRackInfoStmt *sql.Stmt - updateRackPlacementStmt *sql.Stmt - updateRackPlacementBulkForBuildingStmt *sql.Stmt - updateRackPlacementBulkForSiteStmt *sql.Stmt - updateRoleStmt *sql.Stmt - updateScheduleStmt *sql.Stmt - updateScheduleAfterRunStmt *sql.Stmt - updateSessionActivityStmt *sql.Stmt - updateSiteStmt *sql.Stmt - updateUserPasswordStmt *sql.Stmt - updateUserPasswordAndFlagStmt *sql.Stmt - updateUserRoleStmt *sql.Stmt - updateUserUsernameStmt *sql.Stmt - upsertBuiltinRoleForOrgStmt *sql.Stmt - upsertCommandOnDeviceLogStmt *sql.Stmt - upsertCurtailmentReconcilerHeartbeatStmt *sql.Stmt - upsertCustomRoleForOrgStmt *sql.Stmt - upsertDevicePairingStmt *sql.Stmt - upsertDeviceStatusStmt *sql.Stmt - upsertDiscoveredDeviceStmt *sql.Stmt - upsertDiscoveredDeviceFromFleetNodeStmt *sql.Stmt - upsertFleetNodeAuthChallengeStmt *sql.Stmt - upsertFleetNodeSessionStmt *sql.Stmt - upsertMQTTSourceStateStmt *sql.Stmt - upsertMinerCredentialsStmt *sql.Stmt - upsertPermissionStmt *sql.Stmt + db DBTX + tx *sql.Tx + acquireReconcileLockStmt *sql.Stmt + addDevicesToDeviceSetStmt *sql.Stmt + adminResetUserPasswordStmt *sql.Stmt + adminTerminateCurtailmentEventStmt *sql.Stmt + allDevicesBelongToOrgStmt *sql.Stmt + assignBuildingToSiteStmt *sql.Stmt + assignBuildingsToSiteBulkStmt *sql.Stmt + assignDevicesToSiteStmt *sql.Stmt + assignPermissionToRoleStmt *sql.Stmt + assignRoleStmt *sql.Stmt + beginCurtailmentRestorationStmt *sql.Stmt + bindEnrollmentToFleetNodeStmt *sql.Stmt + buildingBelongsToOrgStmt *sql.Stmt + buildingsByIDsStmt *sql.Stmt + bulkInsertCurtailmentTargetsStmt *sql.Stmt + bumpCurtailmentTargetRetryStmt *sql.Stmt + cancelEnrollmentForFleetNodeStmt *sql.Stmt + cancelPendingEnrollmentStmt *sql.Stmt + cascadeAddedDeviceSitesStmt *sql.Stmt + cascadeRackDeviceSitesStmt *sql.Stmt + cascadeRackDeviceSitesBulkStmt *sql.Stmt + claimMessageForProcessingStmt *sql.Stmt + clearCurtailmentAutomationActiveEventStmt *sql.Stmt + clearRackPlacementForSoftDeleteStmt *sql.Stmt + clearRackSlotPositionStmt *sql.Stmt + clearRolePermissionsStmt *sql.Stmt + closeStaleErrorsStmt *sql.Stmt + confirmEnrollmentStmt *sql.Stmt + consumeFleetNodeAuthChallengeStmt *sql.Stmt + countActiveAssignmentsForRoleStmt *sql.Stmt + countActiveUnpairedDiscoveredDevicesStmt *sql.Stmt + countActivityLogsStmt *sql.Stmt + countComponentsWithErrorsStmt *sql.Stmt + countCurtailmentAutomationRulesByMQTTSourceStmt *sql.Stmt + countCurtailmentAutomationRulesByResponseProfileStmt *sql.Stmt + countDevicesWithErrorsStmt *sql.Stmt + countErrorsStmt *sql.Stmt + countMinersByStateStmt *sql.Stmt + countOrgScopeSuperAdminsExcludingUserStmt *sql.Stmt + createApiKeyStmt *sql.Stmt + createBuildingStmt *sql.Stmt + createCommandBatchLogStmt *sql.Stmt + createCustomRoleStmt *sql.Stmt + createDeviceSetStmt *sql.Stmt + createFleetNodeStmt *sql.Stmt + createFleetNodeApiKeyStmt *sql.Stmt + createOrganizationStmt *sql.Stmt + createPendingEnrollmentStmt *sql.Stmt + createPoolStmt *sql.Stmt + createQueueMessageStmt *sql.Stmt + createRackExtensionStmt *sql.Stmt + createScheduleStmt *sql.Stmt + createScheduleTargetStmt *sql.Stmt + createSessionStmt *sql.Stmt + createSiteStmt *sql.Stmt + createUserStmt *sql.Stmt + createUserOrganizationStmt *sql.Stmt + curtailmentEventHasInFlightTargetsStmt *sql.Stmt + deleteCurtailmentAutomationRuleByOrgStmt *sql.Stmt + deleteCurtailmentResponseProfileByOrgStmt *sql.Stmt + deleteCurtailmentResponseProfilesBySiteStmt *sql.Stmt + deleteDisabledMQTTSourceConfigByOrgStmt *sql.Stmt + deleteExpiredSessionsStmt *sql.Stmt + deleteOrganizationStmt *sql.Stmt + deletePairingsForFleetNodeStmt *sql.Stmt + deletePoolStmt *sql.Stmt + deleteScheduleTargetsStmt *sql.Stmt + deviceHasActiveCloudPairingStmt *sql.Stmt + deviceHasActivePairingStmt *sql.Stmt + deviceSetBelongsToOrgStmt *sql.Stmt + ensureCurtailmentOrgConfigStmt *sql.Stmt + findDeviceSiteConflictsStmt *sql.Stmt + getActiveCurtailmentEventStmt *sql.Stmt + getActiveSchedulesStmt *sql.Stmt + getActiveUnpairedDiscoveredDevicesStmt *sql.Stmt + getAddedDeviceSiteConflictsStmt *sql.Stmt + getAllDeviceInfoForCapabilityCheckStmt *sql.Stmt + getAllDeviceMetricsDailyAggregatesStmt *sql.Stmt + getAllDeviceMetricsHourlyAggregatesStmt *sql.Stmt + getAllDeviceMetricsTimeSeriesStmt *sql.Stmt + getAllDeviceStatusDailyAggregatesStmt *sql.Stmt + getAllDeviceStatusHourlyAggregatesStmt *sql.Stmt + getAllPairedDeviceIdentifiersStmt *sql.Stmt + getApiKeyByHashStmt *sql.Stmt + getAssignmentByIDStmt *sql.Stmt + getAvailableFirmwareVersionsStmt *sql.Stmt + getAvailableModelsStmt *sql.Stmt + getBatchHeaderForOrgStmt *sql.Stmt + getBatchLogStmt *sql.Stmt + getBatchStatusAndDeviceCountsStmt *sql.Stmt + getBuildingStmt *sql.Stmt + getBuildingSiteStmt *sql.Stmt + getBuiltinRoleForOrgStmt *sql.Stmt + getCurtailmentAutomationRuleByOrgStmt *sql.Stmt + getCurtailmentEventByExternalReferenceStmt *sql.Stmt + getCurtailmentEventByIdempotencyKeyStmt *sql.Stmt + getCurtailmentEventByUUIDStmt *sql.Stmt + getCurtailmentEventDetailByUUIDStmt *sql.Stmt + getCurtailmentOrgConfigStmt *sql.Stmt + getCurtailmentReconcilerHeartbeatStmt *sql.Stmt + getCurtailmentResponseProfileByOrgStmt *sql.Stmt + getCurtailmentTargetRollupByEventStmt *sql.Stmt + getDeviceByDeviceIdentifierStmt *sql.Stmt + getDeviceByIDStmt *sql.Stmt + getDeviceDeviceSetsStmt *sql.Stmt + getDeviceDeviceSetsByTypeStmt *sql.Stmt + getDeviceIDByDeviceIdentifierStmt *sql.Stmt + getDeviceIDByIdentifierStmt *sql.Stmt + getDeviceIDsByDeviceIdentifiersStmt *sql.Stmt + getDeviceIDsWithIdentifiersStmt *sql.Stmt + getDeviceIdentifierByIDStmt *sql.Stmt + getDeviceIdentifiersByDeviceSetIDStmt *sql.Stmt + getDeviceInfoForCapabilityCheckStmt *sql.Stmt + getDeviceMetricsDailyAggregatesStmt *sql.Stmt + getDeviceMetricsHourlyAggregatesStmt *sql.Stmt + getDeviceMetricsTimeSeriesStmt *sql.Stmt + getDevicePairingStatusByDeviceDatabaseIDStmt *sql.Stmt + getDevicePropertiesForRenameStmt *sql.Stmt + getDevicePropertiesForRenameWithoutTelemetryStmt *sql.Stmt + getDeviceSetStmt *sql.Stmt + getDeviceSetTypeStmt *sql.Stmt + getDeviceSetTypesBatchStmt *sql.Stmt + getDeviceSiteIDsByMembershipStmt *sql.Stmt + getDeviceStatusStmt *sql.Stmt + getDeviceStatusByDeviceIdentifierStmt *sql.Stmt + getDeviceStatusDailyAggregatesStmt *sql.Stmt + getDeviceStatusForDeviceIdentifiersStmt *sql.Stmt + getDeviceStatusHourlyAggregatesStmt *sql.Stmt + getDeviceWithCredentialsAndIPByDeviceIdentifierStmt *sql.Stmt + getDeviceWithCredentialsAndIPByIDStmt *sql.Stmt + getDiscoveredDeviceByDeviceIdentifierStmt *sql.Stmt + getDiscoveredDeviceByIDStmt *sql.Stmt + getDiscoveredDeviceByIPAndPortStmt *sql.Stmt + getDistinctActivityUsersStmt *sql.Stmt + getDistinctEventTypesStmt *sql.Stmt + getDistinctScopeTypesStmt *sql.Stmt + getErrorByErrorIDStmt *sql.Stmt + getErrorByIDStmt *sql.Stmt + getFilteredDeviceIdentifiersStmt *sql.Stmt + getFilteredDeviceIdsStmt *sql.Stmt + getFleetNodeByIDStmt *sql.Stmt + getFleetNodeByIDUnscopedStmt *sql.Stmt + getFleetNodeSessionByTokenHashStmt *sql.Stmt + getGroupLabelsForDevicesStmt *sql.Stmt + getKnownSubnetsStmt *sql.Stmt + getLatestAllDeviceMetricsStmt *sql.Stmt + getLatestDeviceMetricsStmt *sql.Stmt + getMQTTSourceConfigByOrgStmt *sql.Stmt + getMQTTSourceStateByIDStmt *sql.Stmt + getMaxPriorityStmt *sql.Stmt + getMessagesToProcessStmt *sql.Stmt + getMinerCredentialsByDeviceIDStmt *sql.Stmt + getMinerModelGroupsStmt *sql.Stmt + getMinerStateCountsByDeviceIDsStmt *sql.Stmt + getMinerStateSnapshotsStmt *sql.Stmt + getOfflineDevicesStmt *sql.Stmt + getOpenErrorByDedupKeyStmt *sql.Stmt + getOrgScopeAssignmentForUserStmt *sql.Stmt + getOrganizationByIDStmt *sql.Stmt + getOrganizationByNameStmt *sql.Stmt + getOrganizationByOrgIDStmt *sql.Stmt + getOrganizationPrivateKeyStmt *sql.Stmt + getOrganizationsForUserStmt *sql.Stmt + getPairedDeviceByMACAddressStmt *sql.Stmt + getPairedDeviceBySerialNumberStmt *sql.Stmt + getPairedDevicesByMACAddressesStmt *sql.Stmt + getPairedDevicesIdsStmt *sql.Stmt + getPendingEnrollmentByCodeHashStmt *sql.Stmt + getPendingEnrollmentByFleetNodeStmt *sql.Stmt + getPermissionByKeyStmt *sql.Stmt + getPermissionsByKeysStmt *sql.Stmt + getPoolStmt *sql.Stmt + getRackDetailsForDevicesStmt *sql.Stmt + getRackInfoStmt *sql.Stmt + getRackInfoBatchStmt *sql.Stmt + getRackSlotsStmt *sql.Stmt + getRoleByIDStmt *sql.Stmt + getRoleByIDForUpdateStmt *sql.Stmt + getRunningPowerTargetScheduleOverlapsStmt *sql.Stmt + getScheduleStmt *sql.Stmt + getScheduleByIDForProcessorStmt *sql.Stmt + getScheduleForUpdateStmt *sql.Stmt + getScheduleTargetsStmt *sql.Stmt + getScheduleTargetsByScheduleIDsStmt *sql.Stmt + getSessionByIDStmt *sql.Stmt + getSiteStmt *sql.Stmt + getTotalDevicesPendingAuthStmt *sql.Stmt + getTotalMinerStateSnapshotsStmt *sql.Stmt + getTotalPairedDevicesStmt *sql.Stmt + getTotalPoolsStmt *sql.Stmt + getUserByExternalIdStmt *sql.Stmt + getUserByIdStmt *sql.Stmt + getUserByIdForUpdateStmt *sql.Stmt + getUserByUsernameStmt *sql.Stmt + getUserRoleInOrganizationStmt *sql.Stmt + getUserRoleNameStmt *sql.Stmt + getUsersForOrganizationStmt *sql.Stmt + hasUserStmt *sql.Stmt + insertActivityLogStmt *sql.Stmt + insertCurtailmentAutomationRuleStmt *sql.Stmt + insertCurtailmentEventStmt *sql.Stmt + insertCurtailmentResponseProfileStmt *sql.Stmt + insertDeviceStmt *sql.Stmt + insertDeviceMetricsStmt *sql.Stmt + insertErrorStmt *sql.Stmt + insertMQTTSourceConfigStmt *sql.Stmt + insertMinerStateSnapshotStmt *sql.Stmt + insertNotificationHistoryStmt *sql.Stmt + insertNotificationMetricSamplesStmt *sql.Stmt + isBatchFinishedStmt *sql.Stmt + isBatchProcessingStmt *sql.Stmt + listActiveCurtailedDevicesByOrgStmt *sql.Stmt + listActiveCurtailmentEventsStmt *sql.Stmt + listActiveOrganizationIDsStmt *sql.Stmt + listActivityLogsStmt *sql.Stmt + listApiKeysByOrganizationStmt *sql.Stmt + listAssignmentsForRoleStmt *sql.Stmt + listAssignmentsForUserStmt *sql.Stmt + listBatchDeviceResultsStmt *sql.Stmt + listBuildingRacksStmt *sql.Stmt + listBuildingsByOrgStmt *sql.Stmt + listBuiltinRolesForOrgStmt *sql.Stmt + listCurtailmentAutomationRulesByOrgStmt *sql.Stmt + listCurtailmentCandidatesByOrgStmt *sql.Stmt + listCurtailmentEventsForOrgStmt *sql.Stmt + listCurtailmentResponseProfilesByOrgStmt *sql.Stmt + listCurtailmentTargetsByEventStmt *sql.Stmt + listCurtailmentTargetsByEventPageStmt *sql.Stmt + listCustomRolesForOrgStmt *sql.Stmt + listDeviceSetMembersPaginatedStmt *sql.Stmt + listDeviceSetMembersPaginatedAfterStmt *sql.Stmt + listEffectivePermissionsForUserStmt *sql.Stmt + listEffectivePermissionsForUserForUpdateStmt *sql.Stmt + listEnabledCurtailmentAutomationRulesByMQTTSourceStmt *sql.Stmt + listEnabledMQTTSourcesStmt *sql.Stmt + listExistingDeviceIdentifiersStmt *sql.Stmt + listFleetNodeDevicesStmt *sql.Stmt + listFleetNodeDiscoveredDevicesStmt *sql.Stmt + listFleetNodesForOrganizationStmt *sql.Stmt + listMQTTSourceConfigsByOrgStmt *sql.Stmt + listMQTTSourceStatesByOrgStmt *sql.Stmt + listMinerStateSnapshotsStmt *sql.Stmt + listNonTerminalCurtailmentEventsStmt *sql.Stmt + listOrganizationsStmt *sql.Stmt + listPermissionsStmt *sql.Stmt + listPoolsStmt *sql.Stmt + listRackTypesStmt *sql.Stmt + listRackZoneRefsStmt *sql.Stmt + listRackZonesStmt *sql.Stmt + listRacksOutsideBuildingBoundsStmt *sql.Stmt + listRecentlyResolvedCurtailedDevicesByOrgStmt *sql.Stmt + listRolePermissionKeysStmt *sql.Stmt + listRolesStmt *sql.Stmt + listRolesWithDetailsForOrgStmt *sql.Stmt + listScheduleIDStatusesStmt *sql.Stmt + listSchedulesStmt *sql.Stmt + listSiteNetworkConfigsForOverlapStmt *sql.Stmt + listSitesStmt *sql.Stmt + listUsersForOrganizationStmt *sql.Stmt + lockAndCountOrgScopeSuperAdminsStmt *sql.Stmt + lockBuildingForWriteStmt *sql.Stmt + lockBuildingsBySiteForWriteStmt *sql.Stmt + lockDevicesForReassignStmt *sql.Stmt + lockFleetNodeByIDStmt *sql.Stmt + lockRackPlacementForWriteStmt *sql.Stmt + lockSchedulePriorityStmt *sql.Stmt + lockSiteForWriteStmt *sql.Stmt + lockSourceRacksForDevicesStmt *sql.Stmt + markCommandBatchFinishedStmt *sql.Stmt + markCommandBatchFinishedWithStartedAtStmt *sql.Stmt + markCommandBatchProcessingStmt *sql.Stmt + negateSchedulePrioritiesStmt *sql.Stmt + pairDeviceToFleetNodeStmt *sql.Stmt + passwordUpdatedAtStmt *sql.Stmt + pauseActiveScheduleStmt *sql.Stmt + prunePermissionsOutsideKeysStmt *sql.Stmt + queryComponentKeysWithErrorsStmt *sql.Stmt + queryDeviceIDsWithErrorsStmt *sql.Stmt + queryErrorsStmt *sql.Stmt + reapStuckFirmwareUpdateMessagesStmt *sql.Stmt + reapStuckProcessingMessagesStmt *sql.Stmt + reassignDevicesUnderBuildingStmt *sql.Stmt + reassignDevicesUnderBuildingsBulkStmt *sql.Stmt + reassignRacksUnderBuildingStmt *sql.Stmt + reassignRacksUnderBuildingsBulkStmt *sql.Stmt + removeAllDevicesFromDeviceSetStmt *sql.Stmt + removeDevicesFromAnyRackStmt *sql.Stmt + removeDevicesFromDeviceSetStmt *sql.Stmt + resetCurtailmentTargetsForRecurtailStmt *sql.Stmt + resetCurtailmentTargetsForRestoreStmt *sql.Stmt + resumeCurtailmentFromRestoringStmt *sql.Stmt + resumePausedScheduleStmt *sql.Stmt + revertScheduleToActiveStmt *sql.Stmt + revokeAllSessionsByUserIDStmt *sql.Stmt + revokeApiKeyStmt *sql.Stmt + revokeApiKeysByFleetNodeIDStmt *sql.Stmt + revokePermissionFromRoleStmt *sql.Stmt + revokeSessionStmt *sql.Stmt + setCurtailmentAutomationActiveEventStmt *sql.Stmt + setCurtailmentAutomationExecutionErrorStmt *sql.Stmt + setCurtailmentAutomationRestoreStartedStmt *sql.Stmt + setCurtailmentAutomationRuleEnabledStmt *sql.Stmt + setDevicePairingAuthNeededIfNotPairedStmt *sql.Stmt + setFleetNodeEnrollmentStatusStmt *sql.Stmt + setMQTTSourceConfigEnabledStmt *sql.Stmt + setRackBuildingPositionStmt *sql.Stmt + setRackBuildingPositionBulkClearStmt *sql.Stmt + setRackBuildingPositionBulkPlaceStmt *sql.Stmt + setRackSlotPositionStmt *sql.Stmt + setSchedulePrioritiesStmt *sql.Stmt + setScheduleRunningStmt *sql.Stmt + siteBelongsToOrgStmt *sql.Stmt + softDeleteBuildingStmt *sql.Stmt + softDeleteBuildingsBySiteStmt *sql.Stmt + softDeleteCustomRoleStmt *sql.Stmt + softDeleteDeviceSetStmt *sql.Stmt + softDeleteDevicesStmt *sql.Stmt + softDeleteDiscoveredDeviceByIdentifierStmt *sql.Stmt + softDeleteDiscoveredDevicesForDeletedDevicesStmt *sql.Stmt + softDeleteFleetNodeStmt *sql.Stmt + softDeleteFleetNodesForExpiredEnrollmentsStmt *sql.Stmt + softDeleteOrganizationStmt *sql.Stmt + softDeletePoolStmt *sql.Stmt + softDeleteRoleStmt *sql.Stmt + softDeleteScheduleStmt *sql.Stmt + softDeleteSiteStmt *sql.Stmt + softDeleteUserStmt *sql.Stmt + softDeleteUserFromOrganizationStmt *sql.Stmt + sweepCurtailmentTargetsToRestoreFailedStmt *sql.Stmt + sweepExpiredEnrollmentsStmt *sql.Stmt + sweepExpiredFleetNodeAuthChallengesStmt *sql.Stmt + sweepExpiredFleetNodeSessionsStmt *sql.Stmt + transferDiscoveredDeviceAttributionStmt *sql.Stmt + unassignDeviceSitesByRackStmt *sql.Stmt + unassignDevicesFromSiteStmt *sql.Stmt + unassignRacksFromBuildingStmt *sql.Stmt + unassignRacksFromBuildingsBySiteStmt *sql.Stmt + unassignRacksFromSiteStmt *sql.Stmt + unassignRoleStmt *sql.Stmt + undeleteOrganizationStmt *sql.Stmt + undeleteRoleStmt *sql.Stmt + unpairDeviceStmt *sql.Stmt + updateApiKeyLastUsedStmt *sql.Stmt + updateBuildingStmt *sql.Stmt + updateCurtailmentAutomationRuleStmt *sql.Stmt + updateCurtailmentEventOperatorFieldsStmt *sql.Stmt + updateCurtailmentEventStateStmt *sql.Stmt + updateCurtailmentResponseProfileStmt *sql.Stmt + updateCurtailmentTargetStateStmt *sql.Stmt + updateCustomRoleNameStmt *sql.Stmt + updateDeviceIPAssignmentStmt *sql.Stmt + updateDeviceInfoStmt *sql.Stmt + updateDevicePairingStatusByIdentifierStmt *sql.Stmt + updateDeviceSetDescriptionStmt *sql.Stmt + updateDeviceSetLabelStmt *sql.Stmt + updateDeviceSetLabelAndDescriptionStmt *sql.Stmt + updateDeviceWorkerNameStmt *sql.Stmt + updateDeviceWorkerNamePoolSyncStatusByIDStmt *sql.Stmt + updateDiscoveredDeviceFirmwareVersionStmt *sql.Stmt + updateFleetNodeLastSeenAtStmt *sql.Stmt + updateLastLoginStmt *sql.Stmt + updateMQTTSourceConfigStmt *sql.Stmt + updateMessageAfterFailureStmt *sql.Stmt + updateMessagePermanentlyFailedStmt *sql.Stmt + updateMessageStatusStmt *sql.Stmt + updateMinerPasswordStmt *sql.Stmt + updateOpenErrorStmt *sql.Stmt + updateOrganizationStmt *sql.Stmt + updatePoolStmt *sql.Stmt + updateRackInfoStmt *sql.Stmt + updateRackPlacementStmt *sql.Stmt + updateRackPlacementBulkForBuildingStmt *sql.Stmt + updateRackPlacementBulkForSiteStmt *sql.Stmt + updateRoleStmt *sql.Stmt + updateScheduleStmt *sql.Stmt + updateScheduleAfterRunStmt *sql.Stmt + updateSessionActivityStmt *sql.Stmt + updateSiteStmt *sql.Stmt + updateUserPasswordStmt *sql.Stmt + updateUserPasswordAndFlagStmt *sql.Stmt + updateUserRoleStmt *sql.Stmt + updateUserUsernameStmt *sql.Stmt + upsertBuiltinRoleForOrgStmt *sql.Stmt + upsertCommandOnDeviceLogStmt *sql.Stmt + upsertCurtailmentAutomationSignalStateStmt *sql.Stmt + upsertCurtailmentReconcilerHeartbeatStmt *sql.Stmt + upsertCustomRoleForOrgStmt *sql.Stmt + upsertDevicePairingStmt *sql.Stmt + upsertDeviceStatusStmt *sql.Stmt + upsertDiscoveredDeviceStmt *sql.Stmt + upsertDiscoveredDeviceFromFleetNodeStmt *sql.Stmt + upsertFleetNodeAuthChallengeStmt *sql.Stmt + upsertFleetNodeSessionStmt *sql.Stmt + upsertMQTTSourceStateStmt *sql.Stmt + upsertMinerCredentialsStmt *sql.Stmt + upsertPermissionStmt *sql.Stmt } func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ - db: tx, - tx: tx, - acquireReconcileLockStmt: q.acquireReconcileLockStmt, - addDevicesToDeviceSetStmt: q.addDevicesToDeviceSetStmt, - adminResetUserPasswordStmt: q.adminResetUserPasswordStmt, - adminTerminateCurtailmentEventStmt: q.adminTerminateCurtailmentEventStmt, - allDevicesBelongToOrgStmt: q.allDevicesBelongToOrgStmt, - assignBuildingToSiteStmt: q.assignBuildingToSiteStmt, - assignBuildingsToSiteBulkStmt: q.assignBuildingsToSiteBulkStmt, - assignDevicesToSiteStmt: q.assignDevicesToSiteStmt, - assignPermissionToRoleStmt: q.assignPermissionToRoleStmt, - assignRoleStmt: q.assignRoleStmt, - beginCurtailmentRestorationStmt: q.beginCurtailmentRestorationStmt, - bindEnrollmentToFleetNodeStmt: q.bindEnrollmentToFleetNodeStmt, - buildingBelongsToOrgStmt: q.buildingBelongsToOrgStmt, - buildingsByIDsStmt: q.buildingsByIDsStmt, - bulkInsertCurtailmentTargetsStmt: q.bulkInsertCurtailmentTargetsStmt, - bumpCurtailmentTargetRetryStmt: q.bumpCurtailmentTargetRetryStmt, - cancelEnrollmentForFleetNodeStmt: q.cancelEnrollmentForFleetNodeStmt, - cancelPendingEnrollmentStmt: q.cancelPendingEnrollmentStmt, - cascadeAddedDeviceSitesStmt: q.cascadeAddedDeviceSitesStmt, - cascadeRackDeviceSitesStmt: q.cascadeRackDeviceSitesStmt, - cascadeRackDeviceSitesBulkStmt: q.cascadeRackDeviceSitesBulkStmt, - claimMessageForProcessingStmt: q.claimMessageForProcessingStmt, - clearRackPlacementForSoftDeleteStmt: q.clearRackPlacementForSoftDeleteStmt, - clearRackSlotPositionStmt: q.clearRackSlotPositionStmt, - clearRolePermissionsStmt: q.clearRolePermissionsStmt, - closeStaleErrorsStmt: q.closeStaleErrorsStmt, - confirmEnrollmentStmt: q.confirmEnrollmentStmt, - consumeFleetNodeAuthChallengeStmt: q.consumeFleetNodeAuthChallengeStmt, - countActiveAssignmentsForRoleStmt: q.countActiveAssignmentsForRoleStmt, - countActiveUnpairedDiscoveredDevicesStmt: q.countActiveUnpairedDiscoveredDevicesStmt, - countActivityLogsStmt: q.countActivityLogsStmt, - countComponentsWithErrorsStmt: q.countComponentsWithErrorsStmt, - countDevicesWithErrorsStmt: q.countDevicesWithErrorsStmt, - countErrorsStmt: q.countErrorsStmt, - countMinersByStateStmt: q.countMinersByStateStmt, - countOrgScopeSuperAdminsExcludingUserStmt: q.countOrgScopeSuperAdminsExcludingUserStmt, - createApiKeyStmt: q.createApiKeyStmt, - createBuildingStmt: q.createBuildingStmt, - createCommandBatchLogStmt: q.createCommandBatchLogStmt, - createCustomRoleStmt: q.createCustomRoleStmt, - createDeviceSetStmt: q.createDeviceSetStmt, - createFleetNodeStmt: q.createFleetNodeStmt, - createFleetNodeApiKeyStmt: q.createFleetNodeApiKeyStmt, - createOrganizationStmt: q.createOrganizationStmt, - createPendingEnrollmentStmt: q.createPendingEnrollmentStmt, - createPoolStmt: q.createPoolStmt, - createQueueMessageStmt: q.createQueueMessageStmt, - createRackExtensionStmt: q.createRackExtensionStmt, - createScheduleStmt: q.createScheduleStmt, - createScheduleTargetStmt: q.createScheduleTargetStmt, - createSessionStmt: q.createSessionStmt, - createSiteStmt: q.createSiteStmt, - createUserStmt: q.createUserStmt, - createUserOrganizationStmt: q.createUserOrganizationStmt, - curtailmentEventHasInFlightTargetsStmt: q.curtailmentEventHasInFlightTargetsStmt, - deleteDisabledMQTTSourceConfigByOrgStmt: q.deleteDisabledMQTTSourceConfigByOrgStmt, - deleteExpiredSessionsStmt: q.deleteExpiredSessionsStmt, - deleteOrganizationStmt: q.deleteOrganizationStmt, - deletePairingsForFleetNodeStmt: q.deletePairingsForFleetNodeStmt, - deletePoolStmt: q.deletePoolStmt, - deleteScheduleTargetsStmt: q.deleteScheduleTargetsStmt, - deviceHasActiveCloudPairingStmt: q.deviceHasActiveCloudPairingStmt, - deviceSetBelongsToOrgStmt: q.deviceSetBelongsToOrgStmt, - ensureCurtailmentOrgConfigStmt: q.ensureCurtailmentOrgConfigStmt, - findDeviceSiteConflictsStmt: q.findDeviceSiteConflictsStmt, - getActiveCurtailmentEventStmt: q.getActiveCurtailmentEventStmt, - getActiveSchedulesStmt: q.getActiveSchedulesStmt, - getActiveUnpairedDiscoveredDevicesStmt: q.getActiveUnpairedDiscoveredDevicesStmt, - getAddedDeviceSiteConflictsStmt: q.getAddedDeviceSiteConflictsStmt, - getAllDeviceInfoForCapabilityCheckStmt: q.getAllDeviceInfoForCapabilityCheckStmt, - getAllDeviceMetricsDailyAggregatesStmt: q.getAllDeviceMetricsDailyAggregatesStmt, - getAllDeviceMetricsHourlyAggregatesStmt: q.getAllDeviceMetricsHourlyAggregatesStmt, - getAllDeviceMetricsTimeSeriesStmt: q.getAllDeviceMetricsTimeSeriesStmt, - getAllDeviceStatusDailyAggregatesStmt: q.getAllDeviceStatusDailyAggregatesStmt, - getAllDeviceStatusHourlyAggregatesStmt: q.getAllDeviceStatusHourlyAggregatesStmt, - getAllPairedDeviceIdentifiersStmt: q.getAllPairedDeviceIdentifiersStmt, - getApiKeyByHashStmt: q.getApiKeyByHashStmt, - getAssignmentByIDStmt: q.getAssignmentByIDStmt, - getAvailableFirmwareVersionsStmt: q.getAvailableFirmwareVersionsStmt, - getAvailableModelsStmt: q.getAvailableModelsStmt, - getBatchHeaderForOrgStmt: q.getBatchHeaderForOrgStmt, - getBatchLogStmt: q.getBatchLogStmt, - getBatchStatusAndDeviceCountsStmt: q.getBatchStatusAndDeviceCountsStmt, - getBuildingStmt: q.getBuildingStmt, - getBuildingSiteStmt: q.getBuildingSiteStmt, - getBuiltinRoleForOrgStmt: q.getBuiltinRoleForOrgStmt, - getCurtailmentEventByExternalReferenceStmt: q.getCurtailmentEventByExternalReferenceStmt, - getCurtailmentEventByIdempotencyKeyStmt: q.getCurtailmentEventByIdempotencyKeyStmt, - getCurtailmentEventByUUIDStmt: q.getCurtailmentEventByUUIDStmt, - getCurtailmentEventDetailByUUIDStmt: q.getCurtailmentEventDetailByUUIDStmt, - getCurtailmentOrgConfigStmt: q.getCurtailmentOrgConfigStmt, - getCurtailmentReconcilerHeartbeatStmt: q.getCurtailmentReconcilerHeartbeatStmt, - getCurtailmentTargetRollupByEventStmt: q.getCurtailmentTargetRollupByEventStmt, - getDeviceByDeviceIdentifierStmt: q.getDeviceByDeviceIdentifierStmt, - getDeviceByIDStmt: q.getDeviceByIDStmt, - getDeviceDeviceSetsStmt: q.getDeviceDeviceSetsStmt, - getDeviceDeviceSetsByTypeStmt: q.getDeviceDeviceSetsByTypeStmt, - getDeviceIDByDeviceIdentifierStmt: q.getDeviceIDByDeviceIdentifierStmt, - getDeviceIDByIdentifierStmt: q.getDeviceIDByIdentifierStmt, - getDeviceIDsByDeviceIdentifiersStmt: q.getDeviceIDsByDeviceIdentifiersStmt, - getDeviceIDsWithIdentifiersStmt: q.getDeviceIDsWithIdentifiersStmt, - getDeviceIdentifierByIDStmt: q.getDeviceIdentifierByIDStmt, - getDeviceIdentifiersByDeviceSetIDStmt: q.getDeviceIdentifiersByDeviceSetIDStmt, - getDeviceInfoForCapabilityCheckStmt: q.getDeviceInfoForCapabilityCheckStmt, - getDeviceMetricsDailyAggregatesStmt: q.getDeviceMetricsDailyAggregatesStmt, - getDeviceMetricsHourlyAggregatesStmt: q.getDeviceMetricsHourlyAggregatesStmt, - getDeviceMetricsTimeSeriesStmt: q.getDeviceMetricsTimeSeriesStmt, - getDevicePairingStatusByDeviceDatabaseIDStmt: q.getDevicePairingStatusByDeviceDatabaseIDStmt, - getDevicePropertiesForRenameStmt: q.getDevicePropertiesForRenameStmt, - getDevicePropertiesForRenameWithoutTelemetryStmt: q.getDevicePropertiesForRenameWithoutTelemetryStmt, - getDeviceSetStmt: q.getDeviceSetStmt, - getDeviceSetTypeStmt: q.getDeviceSetTypeStmt, - getDeviceSetTypesBatchStmt: q.getDeviceSetTypesBatchStmt, - getDeviceSiteIDsByMembershipStmt: q.getDeviceSiteIDsByMembershipStmt, - getDeviceStatusStmt: q.getDeviceStatusStmt, - getDeviceStatusByDeviceIdentifierStmt: q.getDeviceStatusByDeviceIdentifierStmt, - getDeviceStatusDailyAggregatesStmt: q.getDeviceStatusDailyAggregatesStmt, - getDeviceStatusForDeviceIdentifiersStmt: q.getDeviceStatusForDeviceIdentifiersStmt, - getDeviceStatusHourlyAggregatesStmt: q.getDeviceStatusHourlyAggregatesStmt, - getDeviceWithCredentialsAndIPByDeviceIdentifierStmt: q.getDeviceWithCredentialsAndIPByDeviceIdentifierStmt, - getDeviceWithCredentialsAndIPByIDStmt: q.getDeviceWithCredentialsAndIPByIDStmt, - getDiscoveredDeviceByDeviceIdentifierStmt: q.getDiscoveredDeviceByDeviceIdentifierStmt, - getDiscoveredDeviceByIDStmt: q.getDiscoveredDeviceByIDStmt, - getDiscoveredDeviceByIPAndPortStmt: q.getDiscoveredDeviceByIPAndPortStmt, - getDistinctActivityUsersStmt: q.getDistinctActivityUsersStmt, - getDistinctEventTypesStmt: q.getDistinctEventTypesStmt, - getDistinctScopeTypesStmt: q.getDistinctScopeTypesStmt, - getErrorByErrorIDStmt: q.getErrorByErrorIDStmt, - getErrorByIDStmt: q.getErrorByIDStmt, - getFilteredDeviceIdentifiersStmt: q.getFilteredDeviceIdentifiersStmt, - getFilteredDeviceIdsStmt: q.getFilteredDeviceIdsStmt, - getFleetNodeByIDStmt: q.getFleetNodeByIDStmt, - getFleetNodeByIDUnscopedStmt: q.getFleetNodeByIDUnscopedStmt, - getFleetNodeSessionByTokenHashStmt: q.getFleetNodeSessionByTokenHashStmt, - getGroupLabelsForDevicesStmt: q.getGroupLabelsForDevicesStmt, - getKnownSubnetsStmt: q.getKnownSubnetsStmt, - getLatestAllDeviceMetricsStmt: q.getLatestAllDeviceMetricsStmt, - getLatestDeviceMetricsStmt: q.getLatestDeviceMetricsStmt, - getMQTTSourceConfigByOrgStmt: q.getMQTTSourceConfigByOrgStmt, - getMQTTSourceStateByIDStmt: q.getMQTTSourceStateByIDStmt, - getMaxPriorityStmt: q.getMaxPriorityStmt, - getMessagesToProcessStmt: q.getMessagesToProcessStmt, - getMinerCredentialsByDeviceIDStmt: q.getMinerCredentialsByDeviceIDStmt, - getMinerModelGroupsStmt: q.getMinerModelGroupsStmt, - getMinerStateCountsByDeviceIDsStmt: q.getMinerStateCountsByDeviceIDsStmt, - getMinerStateSnapshotsStmt: q.getMinerStateSnapshotsStmt, - getOfflineDevicesStmt: q.getOfflineDevicesStmt, - getOpenErrorByDedupKeyStmt: q.getOpenErrorByDedupKeyStmt, - getOrgScopeAssignmentForUserStmt: q.getOrgScopeAssignmentForUserStmt, - getOrganizationByIDStmt: q.getOrganizationByIDStmt, - getOrganizationByNameStmt: q.getOrganizationByNameStmt, - getOrganizationByOrgIDStmt: q.getOrganizationByOrgIDStmt, - getOrganizationPrivateKeyStmt: q.getOrganizationPrivateKeyStmt, - getOrganizationsForUserStmt: q.getOrganizationsForUserStmt, - getPairedDeviceByMACAddressStmt: q.getPairedDeviceByMACAddressStmt, - getPairedDeviceBySerialNumberStmt: q.getPairedDeviceBySerialNumberStmt, - getPairedDevicesByMACAddressesStmt: q.getPairedDevicesByMACAddressesStmt, - getPairedDevicesIdsStmt: q.getPairedDevicesIdsStmt, - getPendingEnrollmentByCodeHashStmt: q.getPendingEnrollmentByCodeHashStmt, - getPendingEnrollmentByFleetNodeStmt: q.getPendingEnrollmentByFleetNodeStmt, - getPermissionByKeyStmt: q.getPermissionByKeyStmt, - getPermissionsByKeysStmt: q.getPermissionsByKeysStmt, - getPoolStmt: q.getPoolStmt, - getRackDetailsForDevicesStmt: q.getRackDetailsForDevicesStmt, - getRackInfoStmt: q.getRackInfoStmt, - getRackInfoBatchStmt: q.getRackInfoBatchStmt, - getRackSlotsStmt: q.getRackSlotsStmt, - getRoleByIDStmt: q.getRoleByIDStmt, - getRoleByIDForUpdateStmt: q.getRoleByIDForUpdateStmt, - getRunningPowerTargetScheduleOverlapsStmt: q.getRunningPowerTargetScheduleOverlapsStmt, - getScheduleStmt: q.getScheduleStmt, - getScheduleByIDForProcessorStmt: q.getScheduleByIDForProcessorStmt, - getScheduleForUpdateStmt: q.getScheduleForUpdateStmt, - getScheduleTargetsStmt: q.getScheduleTargetsStmt, - getScheduleTargetsByScheduleIDsStmt: q.getScheduleTargetsByScheduleIDsStmt, - getSessionByIDStmt: q.getSessionByIDStmt, - getSiteStmt: q.getSiteStmt, - getTotalDevicesPendingAuthStmt: q.getTotalDevicesPendingAuthStmt, - getTotalMinerStateSnapshotsStmt: q.getTotalMinerStateSnapshotsStmt, - getTotalPairedDevicesStmt: q.getTotalPairedDevicesStmt, - getTotalPoolsStmt: q.getTotalPoolsStmt, - getUserByExternalIdStmt: q.getUserByExternalIdStmt, - getUserByIdStmt: q.getUserByIdStmt, - getUserByIdForUpdateStmt: q.getUserByIdForUpdateStmt, - getUserByUsernameStmt: q.getUserByUsernameStmt, - getUserRoleInOrganizationStmt: q.getUserRoleInOrganizationStmt, - getUserRoleNameStmt: q.getUserRoleNameStmt, - getUsersForOrganizationStmt: q.getUsersForOrganizationStmt, - hasUserStmt: q.hasUserStmt, - insertActivityLogStmt: q.insertActivityLogStmt, - insertCurtailmentEventStmt: q.insertCurtailmentEventStmt, - insertDeviceStmt: q.insertDeviceStmt, - insertDeviceMetricsStmt: q.insertDeviceMetricsStmt, - insertErrorStmt: q.insertErrorStmt, - insertMQTTSourceConfigStmt: q.insertMQTTSourceConfigStmt, - insertMinerStateSnapshotStmt: q.insertMinerStateSnapshotStmt, - isBatchFinishedStmt: q.isBatchFinishedStmt, - isBatchProcessingStmt: q.isBatchProcessingStmt, - listActiveCurtailedDevicesByOrgStmt: q.listActiveCurtailedDevicesByOrgStmt, - listActiveCurtailmentEventsStmt: q.listActiveCurtailmentEventsStmt, - listActiveOrganizationIDsStmt: q.listActiveOrganizationIDsStmt, - listActivityLogsStmt: q.listActivityLogsStmt, - listApiKeysByOrganizationStmt: q.listApiKeysByOrganizationStmt, - listAssignmentsForRoleStmt: q.listAssignmentsForRoleStmt, - listAssignmentsForUserStmt: q.listAssignmentsForUserStmt, - listBatchDeviceResultsStmt: q.listBatchDeviceResultsStmt, - listBuildingRacksStmt: q.listBuildingRacksStmt, - listBuildingsByOrgStmt: q.listBuildingsByOrgStmt, - listBuiltinRolesForOrgStmt: q.listBuiltinRolesForOrgStmt, - listCurtailmentCandidatesByOrgStmt: q.listCurtailmentCandidatesByOrgStmt, - listCurtailmentEventsForOrgStmt: q.listCurtailmentEventsForOrgStmt, - listCurtailmentTargetsByEventStmt: q.listCurtailmentTargetsByEventStmt, - listCurtailmentTargetsByEventPageStmt: q.listCurtailmentTargetsByEventPageStmt, - listCustomRolesForOrgStmt: q.listCustomRolesForOrgStmt, - listDeviceSetMembersPaginatedStmt: q.listDeviceSetMembersPaginatedStmt, - listDeviceSetMembersPaginatedAfterStmt: q.listDeviceSetMembersPaginatedAfterStmt, - listEffectivePermissionsForUserStmt: q.listEffectivePermissionsForUserStmt, - listEffectivePermissionsForUserForUpdateStmt: q.listEffectivePermissionsForUserForUpdateStmt, - listEnabledMQTTSourcesStmt: q.listEnabledMQTTSourcesStmt, - listExistingDeviceIdentifiersStmt: q.listExistingDeviceIdentifiersStmt, - listFleetNodeDevicesStmt: q.listFleetNodeDevicesStmt, - listFleetNodeDiscoveredDevicesStmt: q.listFleetNodeDiscoveredDevicesStmt, - listFleetNodesForOrganizationStmt: q.listFleetNodesForOrganizationStmt, - listMQTTSourceConfigsByOrgStmt: q.listMQTTSourceConfigsByOrgStmt, - listMQTTSourceStatesByOrgStmt: q.listMQTTSourceStatesByOrgStmt, - listMinerStateSnapshotsStmt: q.listMinerStateSnapshotsStmt, - listNonTerminalCurtailmentEventsStmt: q.listNonTerminalCurtailmentEventsStmt, - listOrganizationsStmt: q.listOrganizationsStmt, - listPermissionsStmt: q.listPermissionsStmt, - listPoolsStmt: q.listPoolsStmt, - listRackTypesStmt: q.listRackTypesStmt, - listRackZoneRefsStmt: q.listRackZoneRefsStmt, - listRackZonesStmt: q.listRackZonesStmt, - listRacksOutsideBuildingBoundsStmt: q.listRacksOutsideBuildingBoundsStmt, - listRecentlyResolvedCurtailedDevicesByOrgStmt: q.listRecentlyResolvedCurtailedDevicesByOrgStmt, - listRolePermissionKeysStmt: q.listRolePermissionKeysStmt, - listRolesStmt: q.listRolesStmt, - listRolesWithDetailsForOrgStmt: q.listRolesWithDetailsForOrgStmt, - listScheduleIDStatusesStmt: q.listScheduleIDStatusesStmt, - listSchedulesStmt: q.listSchedulesStmt, - listSiteNetworkConfigsForOverlapStmt: q.listSiteNetworkConfigsForOverlapStmt, - listSitesStmt: q.listSitesStmt, - listUsersForOrganizationStmt: q.listUsersForOrganizationStmt, - lockAndCountOrgScopeSuperAdminsStmt: q.lockAndCountOrgScopeSuperAdminsStmt, - lockBuildingForWriteStmt: q.lockBuildingForWriteStmt, - lockBuildingsBySiteForWriteStmt: q.lockBuildingsBySiteForWriteStmt, - lockDevicesForReassignStmt: q.lockDevicesForReassignStmt, - lockFleetNodeByIDStmt: q.lockFleetNodeByIDStmt, - lockRackPlacementForWriteStmt: q.lockRackPlacementForWriteStmt, - lockSchedulePriorityStmt: q.lockSchedulePriorityStmt, - lockSiteForWriteStmt: q.lockSiteForWriteStmt, - lockSourceRacksForDevicesStmt: q.lockSourceRacksForDevicesStmt, - markCommandBatchFinishedStmt: q.markCommandBatchFinishedStmt, - markCommandBatchFinishedWithStartedAtStmt: q.markCommandBatchFinishedWithStartedAtStmt, - markCommandBatchProcessingStmt: q.markCommandBatchProcessingStmt, - negateSchedulePrioritiesStmt: q.negateSchedulePrioritiesStmt, - pairDeviceToFleetNodeStmt: q.pairDeviceToFleetNodeStmt, - passwordUpdatedAtStmt: q.passwordUpdatedAtStmt, - pauseActiveScheduleStmt: q.pauseActiveScheduleStmt, - prunePermissionsOutsideKeysStmt: q.prunePermissionsOutsideKeysStmt, - queryComponentKeysWithErrorsStmt: q.queryComponentKeysWithErrorsStmt, - queryDeviceIDsWithErrorsStmt: q.queryDeviceIDsWithErrorsStmt, - queryErrorsStmt: q.queryErrorsStmt, - reapStuckFirmwareUpdateMessagesStmt: q.reapStuckFirmwareUpdateMessagesStmt, - reapStuckProcessingMessagesStmt: q.reapStuckProcessingMessagesStmt, - reassignDevicesUnderBuildingStmt: q.reassignDevicesUnderBuildingStmt, - reassignDevicesUnderBuildingsBulkStmt: q.reassignDevicesUnderBuildingsBulkStmt, - reassignRacksUnderBuildingStmt: q.reassignRacksUnderBuildingStmt, - reassignRacksUnderBuildingsBulkStmt: q.reassignRacksUnderBuildingsBulkStmt, - removeAllDevicesFromDeviceSetStmt: q.removeAllDevicesFromDeviceSetStmt, - removeDevicesFromAnyRackStmt: q.removeDevicesFromAnyRackStmt, - removeDevicesFromDeviceSetStmt: q.removeDevicesFromDeviceSetStmt, - resetCurtailmentTargetsForRecurtailStmt: q.resetCurtailmentTargetsForRecurtailStmt, - resetCurtailmentTargetsForRestoreStmt: q.resetCurtailmentTargetsForRestoreStmt, - resumeCurtailmentFromRestoringStmt: q.resumeCurtailmentFromRestoringStmt, - resumePausedScheduleStmt: q.resumePausedScheduleStmt, - revertScheduleToActiveStmt: q.revertScheduleToActiveStmt, - revokeAllSessionsByUserIDStmt: q.revokeAllSessionsByUserIDStmt, - revokeApiKeyStmt: q.revokeApiKeyStmt, - revokeApiKeysByFleetNodeIDStmt: q.revokeApiKeysByFleetNodeIDStmt, - revokePermissionFromRoleStmt: q.revokePermissionFromRoleStmt, - revokeSessionStmt: q.revokeSessionStmt, - setFleetNodeEnrollmentStatusStmt: q.setFleetNodeEnrollmentStatusStmt, - setMQTTSourceConfigEnabledStmt: q.setMQTTSourceConfigEnabledStmt, - setRackBuildingPositionStmt: q.setRackBuildingPositionStmt, - setRackBuildingPositionBulkClearStmt: q.setRackBuildingPositionBulkClearStmt, - setRackBuildingPositionBulkPlaceStmt: q.setRackBuildingPositionBulkPlaceStmt, - setRackSlotPositionStmt: q.setRackSlotPositionStmt, - setSchedulePrioritiesStmt: q.setSchedulePrioritiesStmt, - setScheduleRunningStmt: q.setScheduleRunningStmt, - siteBelongsToOrgStmt: q.siteBelongsToOrgStmt, - softDeleteBuildingStmt: q.softDeleteBuildingStmt, - softDeleteBuildingsBySiteStmt: q.softDeleteBuildingsBySiteStmt, - softDeleteCustomRoleStmt: q.softDeleteCustomRoleStmt, - softDeleteDeviceSetStmt: q.softDeleteDeviceSetStmt, - softDeleteDevicesStmt: q.softDeleteDevicesStmt, - softDeleteDiscoveredDeviceByIdentifierStmt: q.softDeleteDiscoveredDeviceByIdentifierStmt, - softDeleteDiscoveredDevicesForDeletedDevicesStmt: q.softDeleteDiscoveredDevicesForDeletedDevicesStmt, - softDeleteFleetNodeStmt: q.softDeleteFleetNodeStmt, - softDeleteFleetNodesForExpiredEnrollmentsStmt: q.softDeleteFleetNodesForExpiredEnrollmentsStmt, - softDeleteOrganizationStmt: q.softDeleteOrganizationStmt, - softDeletePoolStmt: q.softDeletePoolStmt, - softDeleteRoleStmt: q.softDeleteRoleStmt, - softDeleteScheduleStmt: q.softDeleteScheduleStmt, - softDeleteSiteStmt: q.softDeleteSiteStmt, - softDeleteUserStmt: q.softDeleteUserStmt, - softDeleteUserFromOrganizationStmt: q.softDeleteUserFromOrganizationStmt, - sweepCurtailmentTargetsToRestoreFailedStmt: q.sweepCurtailmentTargetsToRestoreFailedStmt, - sweepExpiredEnrollmentsStmt: q.sweepExpiredEnrollmentsStmt, - sweepExpiredFleetNodeAuthChallengesStmt: q.sweepExpiredFleetNodeAuthChallengesStmt, - sweepExpiredFleetNodeSessionsStmt: q.sweepExpiredFleetNodeSessionsStmt, - transferDiscoveredDeviceAttributionStmt: q.transferDiscoveredDeviceAttributionStmt, - unassignDeviceSitesByRackStmt: q.unassignDeviceSitesByRackStmt, - unassignDevicesFromSiteStmt: q.unassignDevicesFromSiteStmt, - unassignRacksFromBuildingStmt: q.unassignRacksFromBuildingStmt, - unassignRacksFromBuildingsBySiteStmt: q.unassignRacksFromBuildingsBySiteStmt, - unassignRacksFromSiteStmt: q.unassignRacksFromSiteStmt, - unassignRoleStmt: q.unassignRoleStmt, - undeleteOrganizationStmt: q.undeleteOrganizationStmt, - undeleteRoleStmt: q.undeleteRoleStmt, - unpairDeviceStmt: q.unpairDeviceStmt, - updateApiKeyLastUsedStmt: q.updateApiKeyLastUsedStmt, - updateBuildingStmt: q.updateBuildingStmt, - updateCurtailmentEventOperatorFieldsStmt: q.updateCurtailmentEventOperatorFieldsStmt, - updateCurtailmentEventStateStmt: q.updateCurtailmentEventStateStmt, - updateCurtailmentTargetStateStmt: q.updateCurtailmentTargetStateStmt, - updateCustomRoleNameStmt: q.updateCustomRoleNameStmt, - updateDeviceIPAssignmentStmt: q.updateDeviceIPAssignmentStmt, - updateDeviceInfoStmt: q.updateDeviceInfoStmt, - updateDevicePairingStatusByIdentifierStmt: q.updateDevicePairingStatusByIdentifierStmt, - updateDeviceSetDescriptionStmt: q.updateDeviceSetDescriptionStmt, - updateDeviceSetLabelStmt: q.updateDeviceSetLabelStmt, - updateDeviceSetLabelAndDescriptionStmt: q.updateDeviceSetLabelAndDescriptionStmt, - updateDeviceWorkerNameStmt: q.updateDeviceWorkerNameStmt, - updateDeviceWorkerNamePoolSyncStatusByIDStmt: q.updateDeviceWorkerNamePoolSyncStatusByIDStmt, - updateDiscoveredDeviceFirmwareVersionStmt: q.updateDiscoveredDeviceFirmwareVersionStmt, - updateFleetNodeLastSeenAtStmt: q.updateFleetNodeLastSeenAtStmt, - updateLastLoginStmt: q.updateLastLoginStmt, - updateMQTTSourceConfigStmt: q.updateMQTTSourceConfigStmt, - updateMessageAfterFailureStmt: q.updateMessageAfterFailureStmt, - updateMessagePermanentlyFailedStmt: q.updateMessagePermanentlyFailedStmt, - updateMessageStatusStmt: q.updateMessageStatusStmt, - updateMinerPasswordStmt: q.updateMinerPasswordStmt, - updateOpenErrorStmt: q.updateOpenErrorStmt, - updateOrganizationStmt: q.updateOrganizationStmt, - updatePoolStmt: q.updatePoolStmt, - updateRackInfoStmt: q.updateRackInfoStmt, - updateRackPlacementStmt: q.updateRackPlacementStmt, - updateRackPlacementBulkForBuildingStmt: q.updateRackPlacementBulkForBuildingStmt, - updateRackPlacementBulkForSiteStmt: q.updateRackPlacementBulkForSiteStmt, - updateRoleStmt: q.updateRoleStmt, - updateScheduleStmt: q.updateScheduleStmt, - updateScheduleAfterRunStmt: q.updateScheduleAfterRunStmt, - updateSessionActivityStmt: q.updateSessionActivityStmt, - updateSiteStmt: q.updateSiteStmt, - updateUserPasswordStmt: q.updateUserPasswordStmt, - updateUserPasswordAndFlagStmt: q.updateUserPasswordAndFlagStmt, - updateUserRoleStmt: q.updateUserRoleStmt, - updateUserUsernameStmt: q.updateUserUsernameStmt, - upsertBuiltinRoleForOrgStmt: q.upsertBuiltinRoleForOrgStmt, - upsertCommandOnDeviceLogStmt: q.upsertCommandOnDeviceLogStmt, - upsertCurtailmentReconcilerHeartbeatStmt: q.upsertCurtailmentReconcilerHeartbeatStmt, - upsertCustomRoleForOrgStmt: q.upsertCustomRoleForOrgStmt, - upsertDevicePairingStmt: q.upsertDevicePairingStmt, - upsertDeviceStatusStmt: q.upsertDeviceStatusStmt, - upsertDiscoveredDeviceStmt: q.upsertDiscoveredDeviceStmt, - upsertDiscoveredDeviceFromFleetNodeStmt: q.upsertDiscoveredDeviceFromFleetNodeStmt, - upsertFleetNodeAuthChallengeStmt: q.upsertFleetNodeAuthChallengeStmt, - upsertFleetNodeSessionStmt: q.upsertFleetNodeSessionStmt, - upsertMQTTSourceStateStmt: q.upsertMQTTSourceStateStmt, - upsertMinerCredentialsStmt: q.upsertMinerCredentialsStmt, - upsertPermissionStmt: q.upsertPermissionStmt, + db: tx, + tx: tx, + acquireReconcileLockStmt: q.acquireReconcileLockStmt, + addDevicesToDeviceSetStmt: q.addDevicesToDeviceSetStmt, + adminResetUserPasswordStmt: q.adminResetUserPasswordStmt, + adminTerminateCurtailmentEventStmt: q.adminTerminateCurtailmentEventStmt, + allDevicesBelongToOrgStmt: q.allDevicesBelongToOrgStmt, + assignBuildingToSiteStmt: q.assignBuildingToSiteStmt, + assignBuildingsToSiteBulkStmt: q.assignBuildingsToSiteBulkStmt, + assignDevicesToSiteStmt: q.assignDevicesToSiteStmt, + assignPermissionToRoleStmt: q.assignPermissionToRoleStmt, + assignRoleStmt: q.assignRoleStmt, + beginCurtailmentRestorationStmt: q.beginCurtailmentRestorationStmt, + bindEnrollmentToFleetNodeStmt: q.bindEnrollmentToFleetNodeStmt, + buildingBelongsToOrgStmt: q.buildingBelongsToOrgStmt, + buildingsByIDsStmt: q.buildingsByIDsStmt, + bulkInsertCurtailmentTargetsStmt: q.bulkInsertCurtailmentTargetsStmt, + bumpCurtailmentTargetRetryStmt: q.bumpCurtailmentTargetRetryStmt, + cancelEnrollmentForFleetNodeStmt: q.cancelEnrollmentForFleetNodeStmt, + cancelPendingEnrollmentStmt: q.cancelPendingEnrollmentStmt, + cascadeAddedDeviceSitesStmt: q.cascadeAddedDeviceSitesStmt, + cascadeRackDeviceSitesStmt: q.cascadeRackDeviceSitesStmt, + cascadeRackDeviceSitesBulkStmt: q.cascadeRackDeviceSitesBulkStmt, + claimMessageForProcessingStmt: q.claimMessageForProcessingStmt, + clearCurtailmentAutomationActiveEventStmt: q.clearCurtailmentAutomationActiveEventStmt, + clearRackPlacementForSoftDeleteStmt: q.clearRackPlacementForSoftDeleteStmt, + clearRackSlotPositionStmt: q.clearRackSlotPositionStmt, + clearRolePermissionsStmt: q.clearRolePermissionsStmt, + closeStaleErrorsStmt: q.closeStaleErrorsStmt, + confirmEnrollmentStmt: q.confirmEnrollmentStmt, + consumeFleetNodeAuthChallengeStmt: q.consumeFleetNodeAuthChallengeStmt, + countActiveAssignmentsForRoleStmt: q.countActiveAssignmentsForRoleStmt, + countActiveUnpairedDiscoveredDevicesStmt: q.countActiveUnpairedDiscoveredDevicesStmt, + countActivityLogsStmt: q.countActivityLogsStmt, + countComponentsWithErrorsStmt: q.countComponentsWithErrorsStmt, + countCurtailmentAutomationRulesByMQTTSourceStmt: q.countCurtailmentAutomationRulesByMQTTSourceStmt, + countCurtailmentAutomationRulesByResponseProfileStmt: q.countCurtailmentAutomationRulesByResponseProfileStmt, + countDevicesWithErrorsStmt: q.countDevicesWithErrorsStmt, + countErrorsStmt: q.countErrorsStmt, + countMinersByStateStmt: q.countMinersByStateStmt, + countOrgScopeSuperAdminsExcludingUserStmt: q.countOrgScopeSuperAdminsExcludingUserStmt, + createApiKeyStmt: q.createApiKeyStmt, + createBuildingStmt: q.createBuildingStmt, + createCommandBatchLogStmt: q.createCommandBatchLogStmt, + createCustomRoleStmt: q.createCustomRoleStmt, + createDeviceSetStmt: q.createDeviceSetStmt, + createFleetNodeStmt: q.createFleetNodeStmt, + createFleetNodeApiKeyStmt: q.createFleetNodeApiKeyStmt, + createOrganizationStmt: q.createOrganizationStmt, + createPendingEnrollmentStmt: q.createPendingEnrollmentStmt, + createPoolStmt: q.createPoolStmt, + createQueueMessageStmt: q.createQueueMessageStmt, + createRackExtensionStmt: q.createRackExtensionStmt, + createScheduleStmt: q.createScheduleStmt, + createScheduleTargetStmt: q.createScheduleTargetStmt, + createSessionStmt: q.createSessionStmt, + createSiteStmt: q.createSiteStmt, + createUserStmt: q.createUserStmt, + createUserOrganizationStmt: q.createUserOrganizationStmt, + curtailmentEventHasInFlightTargetsStmt: q.curtailmentEventHasInFlightTargetsStmt, + deleteCurtailmentAutomationRuleByOrgStmt: q.deleteCurtailmentAutomationRuleByOrgStmt, + deleteCurtailmentResponseProfileByOrgStmt: q.deleteCurtailmentResponseProfileByOrgStmt, + deleteCurtailmentResponseProfilesBySiteStmt: q.deleteCurtailmentResponseProfilesBySiteStmt, + deleteDisabledMQTTSourceConfigByOrgStmt: q.deleteDisabledMQTTSourceConfigByOrgStmt, + deleteExpiredSessionsStmt: q.deleteExpiredSessionsStmt, + deleteOrganizationStmt: q.deleteOrganizationStmt, + deletePairingsForFleetNodeStmt: q.deletePairingsForFleetNodeStmt, + deletePoolStmt: q.deletePoolStmt, + deleteScheduleTargetsStmt: q.deleteScheduleTargetsStmt, + deviceHasActiveCloudPairingStmt: q.deviceHasActiveCloudPairingStmt, + deviceHasActivePairingStmt: q.deviceHasActivePairingStmt, + deviceSetBelongsToOrgStmt: q.deviceSetBelongsToOrgStmt, + ensureCurtailmentOrgConfigStmt: q.ensureCurtailmentOrgConfigStmt, + findDeviceSiteConflictsStmt: q.findDeviceSiteConflictsStmt, + getActiveCurtailmentEventStmt: q.getActiveCurtailmentEventStmt, + getActiveSchedulesStmt: q.getActiveSchedulesStmt, + getActiveUnpairedDiscoveredDevicesStmt: q.getActiveUnpairedDiscoveredDevicesStmt, + getAddedDeviceSiteConflictsStmt: q.getAddedDeviceSiteConflictsStmt, + getAllDeviceInfoForCapabilityCheckStmt: q.getAllDeviceInfoForCapabilityCheckStmt, + getAllDeviceMetricsDailyAggregatesStmt: q.getAllDeviceMetricsDailyAggregatesStmt, + getAllDeviceMetricsHourlyAggregatesStmt: q.getAllDeviceMetricsHourlyAggregatesStmt, + getAllDeviceMetricsTimeSeriesStmt: q.getAllDeviceMetricsTimeSeriesStmt, + getAllDeviceStatusDailyAggregatesStmt: q.getAllDeviceStatusDailyAggregatesStmt, + getAllDeviceStatusHourlyAggregatesStmt: q.getAllDeviceStatusHourlyAggregatesStmt, + getAllPairedDeviceIdentifiersStmt: q.getAllPairedDeviceIdentifiersStmt, + getApiKeyByHashStmt: q.getApiKeyByHashStmt, + getAssignmentByIDStmt: q.getAssignmentByIDStmt, + getAvailableFirmwareVersionsStmt: q.getAvailableFirmwareVersionsStmt, + getAvailableModelsStmt: q.getAvailableModelsStmt, + getBatchHeaderForOrgStmt: q.getBatchHeaderForOrgStmt, + getBatchLogStmt: q.getBatchLogStmt, + getBatchStatusAndDeviceCountsStmt: q.getBatchStatusAndDeviceCountsStmt, + getBuildingStmt: q.getBuildingStmt, + getBuildingSiteStmt: q.getBuildingSiteStmt, + getBuiltinRoleForOrgStmt: q.getBuiltinRoleForOrgStmt, + getCurtailmentAutomationRuleByOrgStmt: q.getCurtailmentAutomationRuleByOrgStmt, + getCurtailmentEventByExternalReferenceStmt: q.getCurtailmentEventByExternalReferenceStmt, + getCurtailmentEventByIdempotencyKeyStmt: q.getCurtailmentEventByIdempotencyKeyStmt, + getCurtailmentEventByUUIDStmt: q.getCurtailmentEventByUUIDStmt, + getCurtailmentEventDetailByUUIDStmt: q.getCurtailmentEventDetailByUUIDStmt, + getCurtailmentOrgConfigStmt: q.getCurtailmentOrgConfigStmt, + getCurtailmentReconcilerHeartbeatStmt: q.getCurtailmentReconcilerHeartbeatStmt, + getCurtailmentResponseProfileByOrgStmt: q.getCurtailmentResponseProfileByOrgStmt, + getCurtailmentTargetRollupByEventStmt: q.getCurtailmentTargetRollupByEventStmt, + getDeviceByDeviceIdentifierStmt: q.getDeviceByDeviceIdentifierStmt, + getDeviceByIDStmt: q.getDeviceByIDStmt, + getDeviceDeviceSetsStmt: q.getDeviceDeviceSetsStmt, + getDeviceDeviceSetsByTypeStmt: q.getDeviceDeviceSetsByTypeStmt, + getDeviceIDByDeviceIdentifierStmt: q.getDeviceIDByDeviceIdentifierStmt, + getDeviceIDByIdentifierStmt: q.getDeviceIDByIdentifierStmt, + getDeviceIDsByDeviceIdentifiersStmt: q.getDeviceIDsByDeviceIdentifiersStmt, + getDeviceIDsWithIdentifiersStmt: q.getDeviceIDsWithIdentifiersStmt, + getDeviceIdentifierByIDStmt: q.getDeviceIdentifierByIDStmt, + getDeviceIdentifiersByDeviceSetIDStmt: q.getDeviceIdentifiersByDeviceSetIDStmt, + getDeviceInfoForCapabilityCheckStmt: q.getDeviceInfoForCapabilityCheckStmt, + getDeviceMetricsDailyAggregatesStmt: q.getDeviceMetricsDailyAggregatesStmt, + getDeviceMetricsHourlyAggregatesStmt: q.getDeviceMetricsHourlyAggregatesStmt, + getDeviceMetricsTimeSeriesStmt: q.getDeviceMetricsTimeSeriesStmt, + getDevicePairingStatusByDeviceDatabaseIDStmt: q.getDevicePairingStatusByDeviceDatabaseIDStmt, + getDevicePropertiesForRenameStmt: q.getDevicePropertiesForRenameStmt, + getDevicePropertiesForRenameWithoutTelemetryStmt: q.getDevicePropertiesForRenameWithoutTelemetryStmt, + getDeviceSetStmt: q.getDeviceSetStmt, + getDeviceSetTypeStmt: q.getDeviceSetTypeStmt, + getDeviceSetTypesBatchStmt: q.getDeviceSetTypesBatchStmt, + getDeviceSiteIDsByMembershipStmt: q.getDeviceSiteIDsByMembershipStmt, + getDeviceStatusStmt: q.getDeviceStatusStmt, + getDeviceStatusByDeviceIdentifierStmt: q.getDeviceStatusByDeviceIdentifierStmt, + getDeviceStatusDailyAggregatesStmt: q.getDeviceStatusDailyAggregatesStmt, + getDeviceStatusForDeviceIdentifiersStmt: q.getDeviceStatusForDeviceIdentifiersStmt, + getDeviceStatusHourlyAggregatesStmt: q.getDeviceStatusHourlyAggregatesStmt, + getDeviceWithCredentialsAndIPByDeviceIdentifierStmt: q.getDeviceWithCredentialsAndIPByDeviceIdentifierStmt, + getDeviceWithCredentialsAndIPByIDStmt: q.getDeviceWithCredentialsAndIPByIDStmt, + getDiscoveredDeviceByDeviceIdentifierStmt: q.getDiscoveredDeviceByDeviceIdentifierStmt, + getDiscoveredDeviceByIDStmt: q.getDiscoveredDeviceByIDStmt, + getDiscoveredDeviceByIPAndPortStmt: q.getDiscoveredDeviceByIPAndPortStmt, + getDistinctActivityUsersStmt: q.getDistinctActivityUsersStmt, + getDistinctEventTypesStmt: q.getDistinctEventTypesStmt, + getDistinctScopeTypesStmt: q.getDistinctScopeTypesStmt, + getErrorByErrorIDStmt: q.getErrorByErrorIDStmt, + getErrorByIDStmt: q.getErrorByIDStmt, + getFilteredDeviceIdentifiersStmt: q.getFilteredDeviceIdentifiersStmt, + getFilteredDeviceIdsStmt: q.getFilteredDeviceIdsStmt, + getFleetNodeByIDStmt: q.getFleetNodeByIDStmt, + getFleetNodeByIDUnscopedStmt: q.getFleetNodeByIDUnscopedStmt, + getFleetNodeSessionByTokenHashStmt: q.getFleetNodeSessionByTokenHashStmt, + getGroupLabelsForDevicesStmt: q.getGroupLabelsForDevicesStmt, + getKnownSubnetsStmt: q.getKnownSubnetsStmt, + getLatestAllDeviceMetricsStmt: q.getLatestAllDeviceMetricsStmt, + getLatestDeviceMetricsStmt: q.getLatestDeviceMetricsStmt, + getMQTTSourceConfigByOrgStmt: q.getMQTTSourceConfigByOrgStmt, + getMQTTSourceStateByIDStmt: q.getMQTTSourceStateByIDStmt, + getMaxPriorityStmt: q.getMaxPriorityStmt, + getMessagesToProcessStmt: q.getMessagesToProcessStmt, + getMinerCredentialsByDeviceIDStmt: q.getMinerCredentialsByDeviceIDStmt, + getMinerModelGroupsStmt: q.getMinerModelGroupsStmt, + getMinerStateCountsByDeviceIDsStmt: q.getMinerStateCountsByDeviceIDsStmt, + getMinerStateSnapshotsStmt: q.getMinerStateSnapshotsStmt, + getOfflineDevicesStmt: q.getOfflineDevicesStmt, + getOpenErrorByDedupKeyStmt: q.getOpenErrorByDedupKeyStmt, + getOrgScopeAssignmentForUserStmt: q.getOrgScopeAssignmentForUserStmt, + getOrganizationByIDStmt: q.getOrganizationByIDStmt, + getOrganizationByNameStmt: q.getOrganizationByNameStmt, + getOrganizationByOrgIDStmt: q.getOrganizationByOrgIDStmt, + getOrganizationPrivateKeyStmt: q.getOrganizationPrivateKeyStmt, + getOrganizationsForUserStmt: q.getOrganizationsForUserStmt, + getPairedDeviceByMACAddressStmt: q.getPairedDeviceByMACAddressStmt, + getPairedDeviceBySerialNumberStmt: q.getPairedDeviceBySerialNumberStmt, + getPairedDevicesByMACAddressesStmt: q.getPairedDevicesByMACAddressesStmt, + getPairedDevicesIdsStmt: q.getPairedDevicesIdsStmt, + getPendingEnrollmentByCodeHashStmt: q.getPendingEnrollmentByCodeHashStmt, + getPendingEnrollmentByFleetNodeStmt: q.getPendingEnrollmentByFleetNodeStmt, + getPermissionByKeyStmt: q.getPermissionByKeyStmt, + getPermissionsByKeysStmt: q.getPermissionsByKeysStmt, + getPoolStmt: q.getPoolStmt, + getRackDetailsForDevicesStmt: q.getRackDetailsForDevicesStmt, + getRackInfoStmt: q.getRackInfoStmt, + getRackInfoBatchStmt: q.getRackInfoBatchStmt, + getRackSlotsStmt: q.getRackSlotsStmt, + getRoleByIDStmt: q.getRoleByIDStmt, + getRoleByIDForUpdateStmt: q.getRoleByIDForUpdateStmt, + getRunningPowerTargetScheduleOverlapsStmt: q.getRunningPowerTargetScheduleOverlapsStmt, + getScheduleStmt: q.getScheduleStmt, + getScheduleByIDForProcessorStmt: q.getScheduleByIDForProcessorStmt, + getScheduleForUpdateStmt: q.getScheduleForUpdateStmt, + getScheduleTargetsStmt: q.getScheduleTargetsStmt, + getScheduleTargetsByScheduleIDsStmt: q.getScheduleTargetsByScheduleIDsStmt, + getSessionByIDStmt: q.getSessionByIDStmt, + getSiteStmt: q.getSiteStmt, + getTotalDevicesPendingAuthStmt: q.getTotalDevicesPendingAuthStmt, + getTotalMinerStateSnapshotsStmt: q.getTotalMinerStateSnapshotsStmt, + getTotalPairedDevicesStmt: q.getTotalPairedDevicesStmt, + getTotalPoolsStmt: q.getTotalPoolsStmt, + getUserByExternalIdStmt: q.getUserByExternalIdStmt, + getUserByIdStmt: q.getUserByIdStmt, + getUserByIdForUpdateStmt: q.getUserByIdForUpdateStmt, + getUserByUsernameStmt: q.getUserByUsernameStmt, + getUserRoleInOrganizationStmt: q.getUserRoleInOrganizationStmt, + getUserRoleNameStmt: q.getUserRoleNameStmt, + getUsersForOrganizationStmt: q.getUsersForOrganizationStmt, + hasUserStmt: q.hasUserStmt, + insertActivityLogStmt: q.insertActivityLogStmt, + insertCurtailmentAutomationRuleStmt: q.insertCurtailmentAutomationRuleStmt, + insertCurtailmentEventStmt: q.insertCurtailmentEventStmt, + insertCurtailmentResponseProfileStmt: q.insertCurtailmentResponseProfileStmt, + insertDeviceStmt: q.insertDeviceStmt, + insertDeviceMetricsStmt: q.insertDeviceMetricsStmt, + insertErrorStmt: q.insertErrorStmt, + insertMQTTSourceConfigStmt: q.insertMQTTSourceConfigStmt, + insertMinerStateSnapshotStmt: q.insertMinerStateSnapshotStmt, + insertNotificationHistoryStmt: q.insertNotificationHistoryStmt, + insertNotificationMetricSamplesStmt: q.insertNotificationMetricSamplesStmt, + isBatchFinishedStmt: q.isBatchFinishedStmt, + isBatchProcessingStmt: q.isBatchProcessingStmt, + listActiveCurtailedDevicesByOrgStmt: q.listActiveCurtailedDevicesByOrgStmt, + listActiveCurtailmentEventsStmt: q.listActiveCurtailmentEventsStmt, + listActiveOrganizationIDsStmt: q.listActiveOrganizationIDsStmt, + listActivityLogsStmt: q.listActivityLogsStmt, + listApiKeysByOrganizationStmt: q.listApiKeysByOrganizationStmt, + listAssignmentsForRoleStmt: q.listAssignmentsForRoleStmt, + listAssignmentsForUserStmt: q.listAssignmentsForUserStmt, + listBatchDeviceResultsStmt: q.listBatchDeviceResultsStmt, + listBuildingRacksStmt: q.listBuildingRacksStmt, + listBuildingsByOrgStmt: q.listBuildingsByOrgStmt, + listBuiltinRolesForOrgStmt: q.listBuiltinRolesForOrgStmt, + listCurtailmentAutomationRulesByOrgStmt: q.listCurtailmentAutomationRulesByOrgStmt, + listCurtailmentCandidatesByOrgStmt: q.listCurtailmentCandidatesByOrgStmt, + listCurtailmentEventsForOrgStmt: q.listCurtailmentEventsForOrgStmt, + listCurtailmentResponseProfilesByOrgStmt: q.listCurtailmentResponseProfilesByOrgStmt, + listCurtailmentTargetsByEventStmt: q.listCurtailmentTargetsByEventStmt, + listCurtailmentTargetsByEventPageStmt: q.listCurtailmentTargetsByEventPageStmt, + listCustomRolesForOrgStmt: q.listCustomRolesForOrgStmt, + listDeviceSetMembersPaginatedStmt: q.listDeviceSetMembersPaginatedStmt, + listDeviceSetMembersPaginatedAfterStmt: q.listDeviceSetMembersPaginatedAfterStmt, + listEffectivePermissionsForUserStmt: q.listEffectivePermissionsForUserStmt, + listEffectivePermissionsForUserForUpdateStmt: q.listEffectivePermissionsForUserForUpdateStmt, + listEnabledCurtailmentAutomationRulesByMQTTSourceStmt: q.listEnabledCurtailmentAutomationRulesByMQTTSourceStmt, + listEnabledMQTTSourcesStmt: q.listEnabledMQTTSourcesStmt, + listExistingDeviceIdentifiersStmt: q.listExistingDeviceIdentifiersStmt, + listFleetNodeDevicesStmt: q.listFleetNodeDevicesStmt, + listFleetNodeDiscoveredDevicesStmt: q.listFleetNodeDiscoveredDevicesStmt, + listFleetNodesForOrganizationStmt: q.listFleetNodesForOrganizationStmt, + listMQTTSourceConfigsByOrgStmt: q.listMQTTSourceConfigsByOrgStmt, + listMQTTSourceStatesByOrgStmt: q.listMQTTSourceStatesByOrgStmt, + listMinerStateSnapshotsStmt: q.listMinerStateSnapshotsStmt, + listNonTerminalCurtailmentEventsStmt: q.listNonTerminalCurtailmentEventsStmt, + listOrganizationsStmt: q.listOrganizationsStmt, + listPermissionsStmt: q.listPermissionsStmt, + listPoolsStmt: q.listPoolsStmt, + listRackTypesStmt: q.listRackTypesStmt, + listRackZoneRefsStmt: q.listRackZoneRefsStmt, + listRackZonesStmt: q.listRackZonesStmt, + listRacksOutsideBuildingBoundsStmt: q.listRacksOutsideBuildingBoundsStmt, + listRecentlyResolvedCurtailedDevicesByOrgStmt: q.listRecentlyResolvedCurtailedDevicesByOrgStmt, + listRolePermissionKeysStmt: q.listRolePermissionKeysStmt, + listRolesStmt: q.listRolesStmt, + listRolesWithDetailsForOrgStmt: q.listRolesWithDetailsForOrgStmt, + listScheduleIDStatusesStmt: q.listScheduleIDStatusesStmt, + listSchedulesStmt: q.listSchedulesStmt, + listSiteNetworkConfigsForOverlapStmt: q.listSiteNetworkConfigsForOverlapStmt, + listSitesStmt: q.listSitesStmt, + listUsersForOrganizationStmt: q.listUsersForOrganizationStmt, + lockAndCountOrgScopeSuperAdminsStmt: q.lockAndCountOrgScopeSuperAdminsStmt, + lockBuildingForWriteStmt: q.lockBuildingForWriteStmt, + lockBuildingsBySiteForWriteStmt: q.lockBuildingsBySiteForWriteStmt, + lockDevicesForReassignStmt: q.lockDevicesForReassignStmt, + lockFleetNodeByIDStmt: q.lockFleetNodeByIDStmt, + lockRackPlacementForWriteStmt: q.lockRackPlacementForWriteStmt, + lockSchedulePriorityStmt: q.lockSchedulePriorityStmt, + lockSiteForWriteStmt: q.lockSiteForWriteStmt, + lockSourceRacksForDevicesStmt: q.lockSourceRacksForDevicesStmt, + markCommandBatchFinishedStmt: q.markCommandBatchFinishedStmt, + markCommandBatchFinishedWithStartedAtStmt: q.markCommandBatchFinishedWithStartedAtStmt, + markCommandBatchProcessingStmt: q.markCommandBatchProcessingStmt, + negateSchedulePrioritiesStmt: q.negateSchedulePrioritiesStmt, + pairDeviceToFleetNodeStmt: q.pairDeviceToFleetNodeStmt, + passwordUpdatedAtStmt: q.passwordUpdatedAtStmt, + pauseActiveScheduleStmt: q.pauseActiveScheduleStmt, + prunePermissionsOutsideKeysStmt: q.prunePermissionsOutsideKeysStmt, + queryComponentKeysWithErrorsStmt: q.queryComponentKeysWithErrorsStmt, + queryDeviceIDsWithErrorsStmt: q.queryDeviceIDsWithErrorsStmt, + queryErrorsStmt: q.queryErrorsStmt, + reapStuckFirmwareUpdateMessagesStmt: q.reapStuckFirmwareUpdateMessagesStmt, + reapStuckProcessingMessagesStmt: q.reapStuckProcessingMessagesStmt, + reassignDevicesUnderBuildingStmt: q.reassignDevicesUnderBuildingStmt, + reassignDevicesUnderBuildingsBulkStmt: q.reassignDevicesUnderBuildingsBulkStmt, + reassignRacksUnderBuildingStmt: q.reassignRacksUnderBuildingStmt, + reassignRacksUnderBuildingsBulkStmt: q.reassignRacksUnderBuildingsBulkStmt, + removeAllDevicesFromDeviceSetStmt: q.removeAllDevicesFromDeviceSetStmt, + removeDevicesFromAnyRackStmt: q.removeDevicesFromAnyRackStmt, + removeDevicesFromDeviceSetStmt: q.removeDevicesFromDeviceSetStmt, + resetCurtailmentTargetsForRecurtailStmt: q.resetCurtailmentTargetsForRecurtailStmt, + resetCurtailmentTargetsForRestoreStmt: q.resetCurtailmentTargetsForRestoreStmt, + resumeCurtailmentFromRestoringStmt: q.resumeCurtailmentFromRestoringStmt, + resumePausedScheduleStmt: q.resumePausedScheduleStmt, + revertScheduleToActiveStmt: q.revertScheduleToActiveStmt, + revokeAllSessionsByUserIDStmt: q.revokeAllSessionsByUserIDStmt, + revokeApiKeyStmt: q.revokeApiKeyStmt, + revokeApiKeysByFleetNodeIDStmt: q.revokeApiKeysByFleetNodeIDStmt, + revokePermissionFromRoleStmt: q.revokePermissionFromRoleStmt, + revokeSessionStmt: q.revokeSessionStmt, + setCurtailmentAutomationActiveEventStmt: q.setCurtailmentAutomationActiveEventStmt, + setCurtailmentAutomationExecutionErrorStmt: q.setCurtailmentAutomationExecutionErrorStmt, + setCurtailmentAutomationRestoreStartedStmt: q.setCurtailmentAutomationRestoreStartedStmt, + setCurtailmentAutomationRuleEnabledStmt: q.setCurtailmentAutomationRuleEnabledStmt, + setDevicePairingAuthNeededIfNotPairedStmt: q.setDevicePairingAuthNeededIfNotPairedStmt, + setFleetNodeEnrollmentStatusStmt: q.setFleetNodeEnrollmentStatusStmt, + setMQTTSourceConfigEnabledStmt: q.setMQTTSourceConfigEnabledStmt, + setRackBuildingPositionStmt: q.setRackBuildingPositionStmt, + setRackBuildingPositionBulkClearStmt: q.setRackBuildingPositionBulkClearStmt, + setRackBuildingPositionBulkPlaceStmt: q.setRackBuildingPositionBulkPlaceStmt, + setRackSlotPositionStmt: q.setRackSlotPositionStmt, + setSchedulePrioritiesStmt: q.setSchedulePrioritiesStmt, + setScheduleRunningStmt: q.setScheduleRunningStmt, + siteBelongsToOrgStmt: q.siteBelongsToOrgStmt, + softDeleteBuildingStmt: q.softDeleteBuildingStmt, + softDeleteBuildingsBySiteStmt: q.softDeleteBuildingsBySiteStmt, + softDeleteCustomRoleStmt: q.softDeleteCustomRoleStmt, + softDeleteDeviceSetStmt: q.softDeleteDeviceSetStmt, + softDeleteDevicesStmt: q.softDeleteDevicesStmt, + softDeleteDiscoveredDeviceByIdentifierStmt: q.softDeleteDiscoveredDeviceByIdentifierStmt, + softDeleteDiscoveredDevicesForDeletedDevicesStmt: q.softDeleteDiscoveredDevicesForDeletedDevicesStmt, + softDeleteFleetNodeStmt: q.softDeleteFleetNodeStmt, + softDeleteFleetNodesForExpiredEnrollmentsStmt: q.softDeleteFleetNodesForExpiredEnrollmentsStmt, + softDeleteOrganizationStmt: q.softDeleteOrganizationStmt, + softDeletePoolStmt: q.softDeletePoolStmt, + softDeleteRoleStmt: q.softDeleteRoleStmt, + softDeleteScheduleStmt: q.softDeleteScheduleStmt, + softDeleteSiteStmt: q.softDeleteSiteStmt, + softDeleteUserStmt: q.softDeleteUserStmt, + softDeleteUserFromOrganizationStmt: q.softDeleteUserFromOrganizationStmt, + sweepCurtailmentTargetsToRestoreFailedStmt: q.sweepCurtailmentTargetsToRestoreFailedStmt, + sweepExpiredEnrollmentsStmt: q.sweepExpiredEnrollmentsStmt, + sweepExpiredFleetNodeAuthChallengesStmt: q.sweepExpiredFleetNodeAuthChallengesStmt, + sweepExpiredFleetNodeSessionsStmt: q.sweepExpiredFleetNodeSessionsStmt, + transferDiscoveredDeviceAttributionStmt: q.transferDiscoveredDeviceAttributionStmt, + unassignDeviceSitesByRackStmt: q.unassignDeviceSitesByRackStmt, + unassignDevicesFromSiteStmt: q.unassignDevicesFromSiteStmt, + unassignRacksFromBuildingStmt: q.unassignRacksFromBuildingStmt, + unassignRacksFromBuildingsBySiteStmt: q.unassignRacksFromBuildingsBySiteStmt, + unassignRacksFromSiteStmt: q.unassignRacksFromSiteStmt, + unassignRoleStmt: q.unassignRoleStmt, + undeleteOrganizationStmt: q.undeleteOrganizationStmt, + undeleteRoleStmt: q.undeleteRoleStmt, + unpairDeviceStmt: q.unpairDeviceStmt, + updateApiKeyLastUsedStmt: q.updateApiKeyLastUsedStmt, + updateBuildingStmt: q.updateBuildingStmt, + updateCurtailmentAutomationRuleStmt: q.updateCurtailmentAutomationRuleStmt, + updateCurtailmentEventOperatorFieldsStmt: q.updateCurtailmentEventOperatorFieldsStmt, + updateCurtailmentEventStateStmt: q.updateCurtailmentEventStateStmt, + updateCurtailmentResponseProfileStmt: q.updateCurtailmentResponseProfileStmt, + updateCurtailmentTargetStateStmt: q.updateCurtailmentTargetStateStmt, + updateCustomRoleNameStmt: q.updateCustomRoleNameStmt, + updateDeviceIPAssignmentStmt: q.updateDeviceIPAssignmentStmt, + updateDeviceInfoStmt: q.updateDeviceInfoStmt, + updateDevicePairingStatusByIdentifierStmt: q.updateDevicePairingStatusByIdentifierStmt, + updateDeviceSetDescriptionStmt: q.updateDeviceSetDescriptionStmt, + updateDeviceSetLabelStmt: q.updateDeviceSetLabelStmt, + updateDeviceSetLabelAndDescriptionStmt: q.updateDeviceSetLabelAndDescriptionStmt, + updateDeviceWorkerNameStmt: q.updateDeviceWorkerNameStmt, + updateDeviceWorkerNamePoolSyncStatusByIDStmt: q.updateDeviceWorkerNamePoolSyncStatusByIDStmt, + updateDiscoveredDeviceFirmwareVersionStmt: q.updateDiscoveredDeviceFirmwareVersionStmt, + updateFleetNodeLastSeenAtStmt: q.updateFleetNodeLastSeenAtStmt, + updateLastLoginStmt: q.updateLastLoginStmt, + updateMQTTSourceConfigStmt: q.updateMQTTSourceConfigStmt, + updateMessageAfterFailureStmt: q.updateMessageAfterFailureStmt, + updateMessagePermanentlyFailedStmt: q.updateMessagePermanentlyFailedStmt, + updateMessageStatusStmt: q.updateMessageStatusStmt, + updateMinerPasswordStmt: q.updateMinerPasswordStmt, + updateOpenErrorStmt: q.updateOpenErrorStmt, + updateOrganizationStmt: q.updateOrganizationStmt, + updatePoolStmt: q.updatePoolStmt, + updateRackInfoStmt: q.updateRackInfoStmt, + updateRackPlacementStmt: q.updateRackPlacementStmt, + updateRackPlacementBulkForBuildingStmt: q.updateRackPlacementBulkForBuildingStmt, + updateRackPlacementBulkForSiteStmt: q.updateRackPlacementBulkForSiteStmt, + updateRoleStmt: q.updateRoleStmt, + updateScheduleStmt: q.updateScheduleStmt, + updateScheduleAfterRunStmt: q.updateScheduleAfterRunStmt, + updateSessionActivityStmt: q.updateSessionActivityStmt, + updateSiteStmt: q.updateSiteStmt, + updateUserPasswordStmt: q.updateUserPasswordStmt, + updateUserPasswordAndFlagStmt: q.updateUserPasswordAndFlagStmt, + updateUserRoleStmt: q.updateUserRoleStmt, + updateUserUsernameStmt: q.updateUserUsernameStmt, + upsertBuiltinRoleForOrgStmt: q.upsertBuiltinRoleForOrgStmt, + upsertCommandOnDeviceLogStmt: q.upsertCommandOnDeviceLogStmt, + upsertCurtailmentAutomationSignalStateStmt: q.upsertCurtailmentAutomationSignalStateStmt, + upsertCurtailmentReconcilerHeartbeatStmt: q.upsertCurtailmentReconcilerHeartbeatStmt, + upsertCustomRoleForOrgStmt: q.upsertCustomRoleForOrgStmt, + upsertDevicePairingStmt: q.upsertDevicePairingStmt, + upsertDeviceStatusStmt: q.upsertDeviceStatusStmt, + upsertDiscoveredDeviceStmt: q.upsertDiscoveredDeviceStmt, + upsertDiscoveredDeviceFromFleetNodeStmt: q.upsertDiscoveredDeviceFromFleetNodeStmt, + upsertFleetNodeAuthChallengeStmt: q.upsertFleetNodeAuthChallengeStmt, + upsertFleetNodeSessionStmt: q.upsertFleetNodeSessionStmt, + upsertMQTTSourceStateStmt: q.upsertMQTTSourceStateStmt, + upsertMinerCredentialsStmt: q.upsertMinerCredentialsStmt, + upsertPermissionStmt: q.upsertPermissionStmt, } } From 211beb85b49bc9650c1f53a7fdfb6b0926641578 Mon Sep 17 00:00:00 2001 From: flesher Date: Mon, 15 Jun 2026 12:30:02 -0700 Subject: [PATCH 9/9] fix: codex P1/P2 review bugs from rebased issue-420 LockSourceRacksForDevices: Postgres rejects DISTINCT + FOR UPDATE. Rewrite with subquery-derived distinct ids and outer FOR UPDATE SELECT keyed on device_set.id. Every AssignDevicesToRack call was failing at runtime in prod; mock-only tests didn't catch it. AssignDevicesToSite force-clear: partition conflicts into clearable (DEVICE_IN_RACK_AT_OTHER_SITE) vs residual. Abort BEFORE any RemoveDevicesFromAnyRack deletion when residual non-clearable conflicts (e.g. DEVICE_NOT_FOUND) remain. When clearable: pass only the conflicting identifiers so devices already at the target site keep their rack/rack_slot rows. ManageBuildingModal.handleSave: serialize unassign before in- building dispatch. F5's two-pass clear-then-place ordering only applies within one AssignRacksToBuilding call. With concurrent calls, the in-building call could hit the partial unique index before the unassign committed. Co-Authored-By: Claude Opus 4.7 --- .../ManageBuildingModal.tsx | 10 ++- server/generated/sqlc/device_set.sql.go | 42 ++++++----- server/internal/domain/sites/service.go | 41 +++++++---- server/internal/domain/sites/service_test.go | 70 +++++++++++++++++-- server/sqlc/queries/device_set.sql | 42 ++++++----- 5 files changed, 145 insertions(+), 60 deletions(-) diff --git a/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx b/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx index 75611c856..0d3fed39d 100644 --- a/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx +++ b/client/src/protoFleet/features/buildings/components/ManageBuildingModal/ManageBuildingModal.tsx @@ -423,7 +423,15 @@ const ManageBuildingModal = ({ }); try { - await Promise.all([dispatch(inBuilding, building.id), dispatch(unassign, undefined)]); + // Serialize: unassign (vacate) before in-building (claim). + // Each AssignRacksToBuilding call has internal clear-before- + // place ordering inside its tx, but that doesn't protect + // across two separate calls. If rack A is being removed and + // rack B is being placed at A's former cell, the in-building + // call can hit the partial unique index before the unassign + // commits. dispatch short-circuits when the list is empty. + await dispatch(unassign, undefined); + await dispatch(inBuilding, building.id); } catch (err) { setErrorMsg( err instanceof Error ? `Failed to save rack positions: ${err.message}.` : "Failed to save rack positions.", diff --git a/server/generated/sqlc/device_set.sql.go b/server/generated/sqlc/device_set.sql.go index 48f568a02..45746709c 100644 --- a/server/generated/sqlc/device_set.sql.go +++ b/server/generated/sqlc/device_set.sql.go @@ -1177,16 +1177,21 @@ func (q *Queries) LockRackPlacementForWrite(ctx context.Context, arg LockRackPla } const lockSourceRacksForDevices = `-- name: LockSourceRacksForDevices :many -SELECT DISTINCT dsm.device_set_id -FROM device_set_membership dsm -JOIN device_set ds ON ds.id = dsm.device_set_id -WHERE ds.org_id = $1 - AND dsm.device_set_type = 'rack' +SELECT ds.id AS device_set_id +FROM device_set ds +WHERE ds.id IN ( + SELECT dsm.device_set_id + FROM device_set_membership dsm + WHERE dsm.org_id = $1 + AND dsm.device_set_type = 'rack' + AND dsm.device_identifier = ANY($2::text[]) + AND dsm.device_set_id != $3::bigint + ) + AND ds.org_id = $1 + AND ds.type = 'rack' AND ds.deleted_at IS NULL - AND dsm.device_identifier = ANY($2::text[]) - AND dsm.device_set_id != $3::bigint -ORDER BY dsm.device_set_id ASC -FOR UPDATE OF ds +ORDER BY ds.id ASC +FOR UPDATE ` type LockSourceRacksForDevicesParams struct { @@ -1195,16 +1200,15 @@ type LockSourceRacksForDevicesParams struct { ExcludeRackID int64 } -// Returns the distinct source rack ids currently holding any of the -// provided device identifiers, locked FOR UPDATE in ascending id order. -// AssignDevicesToRack calls this BEFORE the DELETE-from-source + -// INSERT-into-target cascade so concurrent reparent calls touching -// overlapping device sets serialize on the source rack rows instead of -// racing the device_set_membership unique constraint. Excludes the -// target rack (@exclude_rack_id) so the caller can take the target -// lock once via LockRackPlacementForWrite without re-locking here. -// Pass 0 for @exclude_rack_id to lock every source rack (clear-rack -// path where there is no target). +// Returns the source rack ids currently holding any of the provided +// device identifiers, locked FOR UPDATE in ascending id order. Used +// by AssignDevicesToRack to serialize concurrent reparent calls that +// touch overlapping device sets. Excludes the target rack so the +// caller can take the target lock once via LockRackPlacementForWrite +// without re-locking here. Pass 0 for @exclude_rack_id to lock every +// source rack (clear-rack path where there is no target). Distinct +// ids are derived in a subquery so the outer locking SELECT can use +// FOR UPDATE (Postgres rejects DISTINCT + FOR UPDATE at runtime). func (q *Queries) LockSourceRacksForDevices(ctx context.Context, arg LockSourceRacksForDevicesParams) ([]int64, error) { rows, err := q.query(ctx, q.lockSourceRacksForDevicesStmt, lockSourceRacksForDevices, arg.OrgID, pq.Array(arg.DeviceIdentifiers), arg.ExcludeRackID) if err != nil { diff --git a/server/internal/domain/sites/service.go b/server/internal/domain/sites/service.go index 2ef796099..286fb6418 100644 --- a/server/internal/domain/sites/service.go +++ b/server/internal/domain/sites/service.go @@ -321,31 +321,44 @@ func (s *Service) AssignDevicesToSite(ctx context.Context, params models.AssignD // When the caller opted into the force-clear branch, treat // DEVICE_IN_RACK_AT_OTHER_SITE conflicts as a cascade-clear // signal instead of a rejection. DEVICE_NOT_FOUND still aborts - // — the caller can't move what doesn't exist. We filter the - // conflict list and require collectionStore to be wired before - // touching the rack rows. + // — the caller can't move what doesn't exist. Partition + // conflicts: only identifiers whose blocker is rack-at-other- + // site get their rack memberships cleared. Devices already at + // the target site (no conflict) keep their rack rows, so we + // don't cascade-delete unrelated rack_slot rows. if params.ForceClearConflictingRackMembership && len(conflicts) > 0 { - filtered := conflicts[:0] - hasRackConflict := false + var ( + clearableIDs []string + residual []models.PerDeviceConflict + ) for _, c := range conflicts { if c.Reason == models.ReasonDeviceInRackAtOtherSite { - hasRackConflict = true + clearableIDs = append(clearableIDs, c.DeviceIdentifier) continue } - filtered = append(filtered, c) + residual = append(residual, c) + } + // Abort BEFORE any deletion when residual non-clearable + // conflicts remain. Otherwise the tx would commit the + // rack-membership delete for clearable devices and then + // return without the site move, leaving rack-stripped + // devices on their old site. + if len(residual) > 0 { + txConflicts = residual + return nil } - conflicts = filtered - if hasRackConflict { + if len(clearableIDs) > 0 { if s.collectionStore == nil { return fleeterror.NewInternalErrorf("force-clear branch requires a collection store") } - // Pass targetRackID=0 so the helper clears every rack - // membership for these devices — there's no target rack - // here, just a site move. The F2 fix made this branch - // safe (0 means "exclude nothing"). - if _, err := s.collectionStore.RemoveDevicesFromAnyRack(txCtx, params.OrgID, identifiers, 0); err != nil { + // Clear only the rack memberships for devices that + // actually had the cross-site conflict. targetRackID=0 + // means "exclude nothing", i.e. drop every rack row + // for the listed devices. + if _, err := s.collectionStore.RemoveDevicesFromAnyRack(txCtx, params.OrgID, clearableIDs, 0); err != nil { return err } + conflicts = nil } } if len(conflicts) > 0 { diff --git a/server/internal/domain/sites/service_test.go b/server/internal/domain/sites/service_test.go index 91975bd6a..583429ea6 100644 --- a/server/internal/domain/sites/service_test.go +++ b/server/internal/domain/sites/service_test.go @@ -361,9 +361,12 @@ func TestAssignDevicesToSite_forceClearCascadesRackMembership(t *testing.T) { store.EXPECT().FindDeviceSiteConflicts(inTxCtx, testOrgID, identifiers).Return(map[string]int64{ "d1": conflictingSite, }, nil) - // targetRackID=0 means "clear every rack membership for these - // devices" — there's no target rack in a site move. - collStore.EXPECT().RemoveDevicesFromAnyRack(inTxCtx, testOrgID, identifiers, int64(0)).Return(int64(1), nil) + // Only d1 had a rack-at-other-site conflict, so only d1's rack + // memberships get cleared. d2 (no conflict, already at target) + // keeps its rack row so its rack_slot child isn't cascade-dropped. + // targetRackID=0 means "exclude nothing" — drop every rack row + // for the listed devices. + collStore.EXPECT().RemoveDevicesFromAnyRack(inTxCtx, testOrgID, []string{"d1"}, int64(0)).Return(int64(1), nil) store.EXPECT().AssignDevicesToSite(inTxCtx, testOrgID, gomock.AssignableToTypeOf(ptrInt64(0)), identifiers).Return(int64(2), nil) count, conflicts, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ @@ -451,10 +454,12 @@ func TestAssignDevicesToSite_forceClearMissingDeviceStillRejects(t *testing.T) { store.EXPECT().FindDeviceSiteConflicts(inTxCtx, testOrgID, identifiers).Return(map[string]int64{ "d1": conflictingSite, }, nil) - // Cascade clear still fires for the rack conflict; the - // remaining DEVICE_NOT_FOUND rejects the batch before any - // site write. - collStore.EXPECT().RemoveDevicesFromAnyRack(inTxCtx, testOrgID, identifiers, int64(0)).Return(int64(1), nil) + // Residual DEVICE_NOT_FOUND aborts the tx BEFORE any + // deletion runs. Otherwise the cascade-clear delete would + // commit and the tx would still return without the site + // move, leaving d1 rack-stripped on its old site. + // No RemoveDevicesFromAnyRack expectation: gomock fails + // the test if it's called. // No AssignDevicesToSite expectation: batch must reject. count, conflicts, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ @@ -537,6 +542,57 @@ func TestAssignDevicesToSite_forceClearRollsBackOnSiteWriteFailure(t *testing.T) } } +// TestAssignDevicesToSite_forceClearOnlyConflictingDevices pins the +// scoped-clear contract: when only a subset of devices have a +// rack-at-other-site conflict, RemoveDevicesFromAnyRack is called +// only with the conflicting identifiers. Devices already at the +// target site (no conflict) keep their rack rows so their rack_slot +// children aren't cascade-dropped. Regression test for codex PR +// review (issue-420): the original implementation passed the full +// identifier list, over-deleting unrelated rack memberships. +func TestAssignDevicesToSite_forceClearOnlyConflictingDevices(t *testing.T) { + ctrl := gomock.NewController(t) + store := mocks.NewMockSiteStore(ctrl) + collStore := mocks.NewMockCollectionStore(ctrl) + tx := &fakeTransactor{} + svc := NewService(store, nil, collStore, nil, nil, tx, nil) + + identifiers := []string{"d-conflict", "d-already-here"} + target := int64(20) + conflictingSite := int64(30) + + store.EXPECT().LockDevicesForReassign(inTxCtx, testOrgID, identifiers).Return(nil) + store.EXPECT().LockSiteForWrite(inTxCtx, testOrgID, target).Return(nil) + store.EXPECT().ListExistingDeviceIdentifiers(inTxCtx, testOrgID, identifiers).Return(identifiers, nil) + // Only d-conflict is at a different site; d-already-here returns + // no rack-site row (already at target, would be filtered by the + // target==site equality check anyway). + store.EXPECT().FindDeviceSiteConflicts(inTxCtx, testOrgID, identifiers).Return(map[string]int64{ + "d-conflict": conflictingSite, + }, nil) + // Critical: only d-conflict's rack rows get dropped. Passing + // the full identifier list here would delete d-already-here's + // rack membership and cascade its rack_slot row. + collStore.EXPECT().RemoveDevicesFromAnyRack(inTxCtx, testOrgID, []string{"d-conflict"}, int64(0)).Return(int64(1), nil) + store.EXPECT().AssignDevicesToSite(inTxCtx, testOrgID, gomock.AssignableToTypeOf(ptrInt64(0)), identifiers).Return(int64(2), nil) + + count, conflicts, err := svc.AssignDevicesToSite(context.Background(), models.AssignDevicesToSiteParams{ + OrgID: testOrgID, + TargetSiteID: &target, + DeviceIdentifiers: identifiers, + ForceClearConflictingRackMembership: true, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if count != 2 { + t.Fatalf("expected 2 rows updated, got %d", count) + } + if len(conflicts) != 0 { + t.Fatalf("expected zero conflicts after scoped clear, got %v", conflicts) + } +} + func TestAssignBuildingsToSite_cascadeOnSuccess(t *testing.T) { ctrl := gomock.NewController(t) store := mocks.NewMockSiteStore(ctrl) diff --git a/server/sqlc/queries/device_set.sql b/server/sqlc/queries/device_set.sql index e4c3ad605..5aeabb013 100644 --- a/server/sqlc/queries/device_set.sql +++ b/server/sqlc/queries/device_set.sql @@ -296,26 +296,30 @@ WHERE device_set_id = $1 AND org_id = $2; -- name: LockSourceRacksForDevices :many --- Returns the distinct source rack ids currently holding any of the --- provided device identifiers, locked FOR UPDATE in ascending id order. --- AssignDevicesToRack calls this BEFORE the DELETE-from-source + --- INSERT-into-target cascade so concurrent reparent calls touching --- overlapping device sets serialize on the source rack rows instead of --- racing the device_set_membership unique constraint. Excludes the --- target rack (@exclude_rack_id) so the caller can take the target --- lock once via LockRackPlacementForWrite without re-locking here. --- Pass 0 for @exclude_rack_id to lock every source rack (clear-rack --- path where there is no target). -SELECT DISTINCT dsm.device_set_id -FROM device_set_membership dsm -JOIN device_set ds ON ds.id = dsm.device_set_id -WHERE ds.org_id = @org_id - AND dsm.device_set_type = 'rack' +-- Returns the source rack ids currently holding any of the provided +-- device identifiers, locked FOR UPDATE in ascending id order. Used +-- by AssignDevicesToRack to serialize concurrent reparent calls that +-- touch overlapping device sets. Excludes the target rack so the +-- caller can take the target lock once via LockRackPlacementForWrite +-- without re-locking here. Pass 0 for @exclude_rack_id to lock every +-- source rack (clear-rack path where there is no target). Distinct +-- ids are derived in a subquery so the outer locking SELECT can use +-- FOR UPDATE (Postgres rejects DISTINCT + FOR UPDATE at runtime). +SELECT ds.id AS device_set_id +FROM device_set ds +WHERE ds.id IN ( + SELECT dsm.device_set_id + FROM device_set_membership dsm + WHERE dsm.org_id = @org_id + AND dsm.device_set_type = 'rack' + AND dsm.device_identifier = ANY(@device_identifiers::text[]) + AND dsm.device_set_id != @exclude_rack_id::bigint + ) + AND ds.org_id = @org_id + AND ds.type = 'rack' AND ds.deleted_at IS NULL - AND dsm.device_identifier = ANY(@device_identifiers::text[]) - AND dsm.device_set_id != @exclude_rack_id::bigint -ORDER BY dsm.device_set_id ASC -FOR UPDATE OF ds; +ORDER BY ds.id ASC +FOR UPDATE; -- name: RemoveDevicesFromAnyRack :execrows -- Removes the given devices from whatever rack they're currently in,