Skip to main content

alien_core/
heartbeat.rs

1use std::collections::BTreeMap;
2
3use crate::{Platform, ResourceType};
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use serde_json::Value as JsonValue;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
10#[serde(rename_all = "camelCase")]
11pub struct ResourceHeartbeat {
12    pub deployment_id: Option<String>,
13    /// Alien resource id, such as the `alien.Container` or `alien.Storage`
14    /// resource id from the stack.
15    pub resource_id: String,
16    pub resource_type: ResourceType,
17    pub controller_platform: Platform,
18    pub backend: HeartbeatBackend,
19    pub observed_at: DateTime<Utc>,
20    pub data: ResourceHeartbeatData,
21    pub raw: Vec<RawHeartbeatSnippet>,
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
26#[serde(rename_all = "camelCase")]
27pub struct ObservedInventoryBatch {
28    /// Writer/source for this inventory pass, such as `operator` or
29    /// `manager-observer`.
30    pub source_kind: String,
31    /// Stable scope for the provider list operation that produced this batch.
32    pub inventory_scope: String,
33    /// Platform whose observer produced this snapshot.
34    pub controller_platform: Platform,
35    /// Backend whose observer produced this snapshot.
36    pub backend: HeartbeatBackend,
37    /// Time the inventory scope was observed.
38    pub observed_at: DateTime<Utc>,
39    /// Whether this batch is a complete replacement for the scope. Complete
40    /// batches tombstone previously observed rows in the same scope when they
41    /// are absent from `resources`.
42    pub complete: bool,
43    pub resources: Vec<ObservedResourceSample>,
44}
45
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
48#[serde(rename_all = "camelCase")]
49pub struct ObservedResourceSample {
50    pub deployment_id: Option<String>,
51    /// Provider-native stable identity: Kubernetes object identity, cloud ARN,
52    /// GCP full resource name, Azure resource id, etc.
53    pub raw_identity: String,
54    /// Provider-native kind, such as `apps/v1/Deployment`,
55    /// `AWS::S3::Bucket`, `storage.googleapis.com/Bucket`, or an Azure
56    /// resource type.
57    pub provider_kind: String,
58    pub display_name: String,
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub namespace: Option<String>,
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub region: Option<String>,
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub scope: Option<String>,
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub resource_type_hint: Option<ResourceType>,
67    /// Release/version identity observed from the provider resource, when available.
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub version: Option<String>,
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub alien_resource_id: Option<String>,
72    pub health: ObservedHealth,
73    pub lifecycle: ProviderLifecycleState,
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub message: Option<String>,
76    pub partial: bool,
77    pub provider_stale: bool,
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub counts: Option<ObservedCounts>,
80    #[serde(default, skip_serializing_if = "Vec::is_empty")]
81    pub collection_issues: Vec<HeartbeatCollectionIssue>,
82    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
83    pub labels: BTreeMap<String, String>,
84    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
85    pub attributes: BTreeMap<String, JsonValue>,
86    #[serde(default, skip_serializing_if = "Vec::is_empty")]
87    pub raw: Vec<RawHeartbeatSnippet>,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
92#[serde(rename_all = "camelCase")]
93pub enum HeartbeatBackend {
94    Aws,
95    Gcp,
96    Azure,
97    Kubernetes,
98    Local,
99    Managed,
100    External,
101    Test,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
105#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
106#[serde(rename_all = "kebab-case")]
107pub enum HeartbeatCollectionIssueReason {
108    Forbidden,
109    NotInstalled,
110    ApiUnavailable,
111    CollectionFailed,
112    TimedOut,
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
117#[serde(tag = "resourceType", content = "data")]
118pub enum ResourceHeartbeatData {
119    #[serde(rename = "storage")]
120    Storage(StorageHeartbeatData),
121    #[serde(rename = "worker")]
122    Worker(WorkerHeartbeatData),
123    #[serde(rename = "container")]
124    Container(ContainerHeartbeatData),
125    #[serde(rename = "daemon")]
126    Daemon(DaemonHeartbeatData),
127    #[serde(rename = "compute-cluster")]
128    ComputeCluster(ComputeClusterHeartbeatData),
129    #[serde(rename = "kubernetes-cluster")]
130    KubernetesCluster(KubernetesClusterHeartbeatData),
131    #[serde(rename = "queue")]
132    Queue(QueueHeartbeatData),
133    #[serde(rename = "kv")]
134    Kv(KvHeartbeatData),
135    #[serde(rename = "postgres")]
136    Postgres(PostgresHeartbeatData),
137    #[serde(rename = "vault")]
138    Vault(VaultHeartbeatData),
139    #[serde(rename = "service-account")]
140    ServiceAccount(ServiceAccountHeartbeatData),
141    #[serde(rename = "network")]
142    Network(NetworkHeartbeatData),
143    #[serde(rename = "remote-stack-management")]
144    RemoteStackManagement(RemoteStackManagementHeartbeatData),
145    #[serde(rename = "artifact-registry")]
146    ArtifactRegistry(ArtifactRegistryHeartbeatData),
147    #[serde(rename = "build")]
148    Build(BuildHeartbeatData),
149    #[serde(rename = "service_activation")]
150    ServiceActivation(ServiceActivationHeartbeatData),
151    #[serde(rename = "azure_resource_group")]
152    AzureResourceGroup(AzureResourceGroupHeartbeatData),
153    #[serde(rename = "azure_storage_account")]
154    AzureStorageAccount(AzureStorageAccountHeartbeatData),
155    #[serde(rename = "azure_container_apps_environment")]
156    AzureContainerAppsEnvironment(AzureContainerAppsEnvironmentHeartbeatData),
157    #[serde(rename = "azure_service_bus_namespace")]
158    AzureServiceBusNamespace(AzureServiceBusNamespaceHeartbeatData),
159    #[serde(rename = "ai")]
160    Ai(AiHeartbeatData),
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
164#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
165#[serde(rename_all = "kebab-case")]
166pub enum ObservedHealth {
167    Unknown,
168    Healthy,
169    Degraded,
170    Unhealthy,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
174#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
175#[serde(rename_all = "kebab-case")]
176pub enum ProviderLifecycleState {
177    Unknown,
178    Creating,
179    Updating,
180    Running,
181    Scaling,
182    Stopping,
183    Stopped,
184    Deleting,
185    Deleted,
186    Failed,
187}
188
189#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
191#[serde(rename_all = "camelCase")]
192pub struct HeartbeatCollectionIssue {
193    pub source: String,
194    pub reason: HeartbeatCollectionIssueReason,
195    pub severity: HeartbeatIssueSeverity,
196    pub message: String,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
200#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
201#[serde(rename_all = "kebab-case")]
202pub enum HeartbeatIssueSeverity {
203    Info,
204    Warning,
205    Error,
206}
207
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
209#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
210#[serde(rename_all = "camelCase")]
211pub struct KubernetesEventSnapshot {
212    pub reason: String,
213    #[serde(rename = "type")]
214    pub type_: Option<String>,
215    pub message: String,
216    pub count: Option<i32>,
217    pub first_timestamp: Option<DateTime<Utc>>,
218    pub last_timestamp: Option<DateTime<Utc>>,
219    pub event_time: Option<DateTime<Utc>>,
220    pub source: Option<KubernetesEventSource>,
221    pub involved_object: Option<KubernetesEventInvolvedObject>,
222    pub raw: Option<JsonValue>,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
226#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
227#[serde(rename_all = "camelCase")]
228pub struct KubernetesEventSource {
229    pub component: Option<String>,
230    pub host: Option<String>,
231}
232
233#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
234#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
235#[serde(rename_all = "camelCase")]
236pub struct KubernetesEventInvolvedObject {
237    pub kind: Option<String>,
238    pub namespace: Option<String>,
239    pub name: Option<String>,
240    pub uid: Option<String>,
241    pub api_version: Option<String>,
242    pub resource_version: Option<String>,
243    pub field_path: Option<String>,
244}
245
246#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
247#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
248#[serde(rename_all = "camelCase")]
249pub struct ManagedRuntimeEventSnapshot {
250    pub event_id: Option<String>,
251    pub reason: String,
252    #[serde(rename = "type")]
253    pub type_: Option<String>,
254    pub message: String,
255    pub count: Option<i32>,
256    pub first_timestamp: Option<DateTime<Utc>>,
257    pub last_timestamp: Option<DateTime<Utc>>,
258    pub event_time: Option<DateTime<Utc>>,
259    pub source: Option<ManagedRuntimeEventSource>,
260    pub involved_object: Option<ManagedRuntimeEventInvolvedObject>,
261    pub details: Option<JsonValue>,
262    pub raw: Option<JsonValue>,
263}
264
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
266#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
267#[serde(rename_all = "camelCase")]
268pub struct ManagedRuntimeEventSource {
269    pub component: Option<String>,
270    pub host: Option<String>,
271}
272
273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
274#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
275#[serde(rename_all = "camelCase")]
276pub struct ManagedRuntimeEventInvolvedObject {
277    pub kind: Option<String>,
278    pub id: Option<String>,
279    pub name: Option<String>,
280    pub replica_id: Option<String>,
281    pub machine_id: Option<String>,
282    pub details: Option<JsonValue>,
283}
284
285#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
286#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
287#[serde(rename_all = "camelCase")]
288pub struct LocalRuntimeEventSnapshot {
289    pub timestamp: DateTime<Utc>,
290    pub severity: HeartbeatIssueSeverity,
291    pub kind: String,
292    pub message: String,
293    pub subject: Option<LocalRuntimeEventSubject>,
294    pub raw: Option<JsonValue>,
295}
296
297#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
298#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
299#[serde(rename_all = "camelCase")]
300pub struct LocalRuntimeEventSubject {
301    pub kind: String,
302    pub id: Option<String>,
303    pub name: Option<String>,
304}
305
306#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
308#[serde(rename_all = "camelCase")]
309pub struct RawHeartbeatSnippet {
310    pub source: String,
311    pub format: RawHeartbeatSnippetFormat,
312    pub collected_at: DateTime<Utc>,
313    pub body: String,
314    pub truncated: bool,
315}
316
317#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
318#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
319#[serde(rename_all = "kebab-case")]
320pub enum RawHeartbeatSnippetFormat {
321    Json,
322    Yaml,
323    Text,
324}
325
326#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
327#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
328#[serde(rename_all = "camelCase")]
329pub struct ObservedCounts {
330    pub desired: Option<u32>,
331    pub current: Option<u32>,
332    pub ready: Option<u32>,
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
336#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
337#[serde(rename_all = "camelCase")]
338pub struct MetricSample {
339    pub value: f64,
340    pub unit: MetricUnit,
341}
342
343#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
344#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
345#[serde(rename_all = "kebab-case")]
346pub enum MetricUnit {
347    Count,
348    Percent,
349    Bytes,
350    Cores,
351    Milliseconds,
352    RequestsPerSecond,
353}
354
355#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
356#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
357#[serde(tag = "backend", rename_all = "camelCase")]
358pub enum StorageHeartbeatData {
359    AwsS3(AwsS3StorageHeartbeatData),
360    GcpCloudStorage(GcpCloudStorageHeartbeatData),
361    AzureBlob(AzureBlobStorageHeartbeatData),
362    Local(LocalStorageHeartbeatData),
363}
364
365#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
366#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
367#[serde(rename_all = "camelCase")]
368pub struct StorageHeartbeatStatus {
369    pub health: ObservedHealth,
370    pub lifecycle: ProviderLifecycleState,
371    pub message: Option<String>,
372    pub stale: bool,
373    pub partial: bool,
374    pub collection_issues: Vec<HeartbeatCollectionIssue>,
375}
376
377#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
378#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
379#[serde(rename_all = "camelCase")]
380pub struct AwsS3StorageHeartbeatData {
381    pub status: StorageHeartbeatStatus,
382    pub name: String,
383    pub region: Option<String>,
384    pub bucket_location: Option<String>,
385    pub versioning_status: Option<String>,
386    pub versioning_enabled: Option<bool>,
387    pub lifecycle_present: bool,
388    pub lifecycle_rule_count: Option<u64>,
389    pub encryption_config_present: bool,
390    pub encryption_enabled: Option<bool>,
391    pub public_access_block_present: bool,
392    pub block_public_acls: Option<bool>,
393    pub ignore_public_acls: Option<bool>,
394    pub block_public_policy: Option<bool>,
395    pub restrict_public_buckets: Option<bool>,
396    pub bucket_policy_present: Option<bool>,
397    pub bucket_acl_present: Option<bool>,
398}
399
400#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
401#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
402#[serde(rename_all = "camelCase")]
403pub struct AzureBlobStorageHeartbeatData {
404    pub status: StorageHeartbeatStatus,
405    pub name: String,
406    pub storage_account_name: Option<String>,
407    pub resource_group: Option<String>,
408    pub location: Option<String>,
409    pub account_kind: Option<String>,
410    pub sku_name: Option<String>,
411    pub sku_tier: Option<String>,
412    pub access_tier: Option<String>,
413    pub provisioning_state: Option<String>,
414    pub primary_location: Option<String>,
415    pub secondary_location: Option<String>,
416    pub status_of_primary: Option<String>,
417    pub status_of_secondary: Option<String>,
418    pub public_network_access: Option<String>,
419    pub allow_blob_public_access: Option<bool>,
420    pub encryption_key_source: Option<String>,
421    pub blob_encryption_enabled: Option<bool>,
422    pub file_encryption_enabled: Option<bool>,
423    pub queue_encryption_enabled: Option<bool>,
424    pub table_encryption_enabled: Option<bool>,
425    pub blob_versioning_enabled: Option<bool>,
426    pub blob_delete_retention_enabled: Option<bool>,
427    pub blob_delete_retention_days: Option<u64>,
428    pub container_delete_retention_enabled: Option<bool>,
429    pub container_delete_retention_days: Option<u64>,
430    pub change_feed_enabled: Option<bool>,
431    pub change_feed_retention_days: Option<u64>,
432    pub container_public_access: Option<String>,
433}
434
435#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
436#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
437#[serde(rename_all = "camelCase")]
438pub struct GcpCloudStorageHeartbeatData {
439    pub status: StorageHeartbeatStatus,
440    pub name: String,
441    pub bucket_id: Option<String>,
442    pub location: Option<String>,
443    pub location_type: Option<String>,
444    pub storage_class: Option<String>,
445    pub versioning_enabled: Option<bool>,
446    pub lifecycle_present: bool,
447    pub lifecycle_rule_count: Option<u64>,
448    pub retention_policy_effective_time: Option<String>,
449    pub retention_policy_is_locked: Option<bool>,
450    pub retention_period: Option<String>,
451    pub soft_delete_retention_duration_seconds: Option<String>,
452    pub soft_delete_effective_time: Option<String>,
453    pub uniform_bucket_level_access_enabled: Option<bool>,
454    pub uniform_bucket_level_access_locked_time: Option<String>,
455    pub public_access_prevention: Option<String>,
456    pub encryption_config_present: bool,
457    pub default_kms_key_name: Option<String>,
458}
459
460#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
461#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
462#[serde(rename_all = "camelCase")]
463pub struct LocalStorageHeartbeatData {
464    pub status: StorageHeartbeatStatus,
465    pub path: String,
466    pub path_exists: bool,
467    pub is_directory: Option<bool>,
468    pub readonly: Option<bool>,
469    pub modified_at: Option<DateTime<Utc>>,
470}
471
472#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
473#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
474#[serde(tag = "backend", rename_all = "camelCase")]
475pub enum WorkerHeartbeatData {
476    AwsLambda(AwsLambdaWorkerHeartbeatData),
477    GcpCloudRun(GcpCloudRunWorkerHeartbeatData),
478    AzureContainerApps(AzureContainerAppsWorkerHeartbeatData),
479    Kubernetes(KubernetesWorkerHeartbeatData),
480    Local(LocalWorkerHeartbeatData),
481}
482
483#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
484#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
485#[serde(tag = "backend", rename_all = "camelCase")]
486pub enum ContainerHeartbeatData {
487    HorizonPlatform(HorizonContainerHeartbeatData),
488    Kubernetes(KubernetesContainerHeartbeatData),
489    Local(LocalContainerHeartbeatData),
490}
491
492#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
493#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
494#[serde(tag = "backend", rename_all = "camelCase")]
495pub enum DaemonHeartbeatData {
496    Aws(AwsDaemonHeartbeatData),
497    Gcp(GcpDaemonHeartbeatData),
498    Azure(AzureDaemonHeartbeatData),
499    Machines(MachinesDaemonHeartbeatData),
500    Kubernetes(KubernetesDaemonHeartbeatData),
501    Local(LocalDaemonHeartbeatData),
502}
503
504#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
505#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
506#[serde(rename_all = "camelCase")]
507pub struct WorkloadHeartbeatStatus {
508    pub health: ObservedHealth,
509    pub lifecycle: ProviderLifecycleState,
510    pub message: Option<String>,
511    pub stale: bool,
512    pub partial: bool,
513    pub collection_issues: Vec<HeartbeatCollectionIssue>,
514}
515
516#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
517#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
518#[serde(rename_all = "camelCase")]
519pub struct WorkloadReplicaStatus {
520    pub desired: Option<u32>,
521    pub current: Option<u32>,
522    pub ready: Option<u32>,
523    pub available: Option<u32>,
524    pub updated: Option<u32>,
525    pub misscheduled: Option<u32>,
526}
527
528#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
529#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
530#[serde(rename_all = "camelCase")]
531pub struct AwsLambdaWorkerHeartbeatData {
532    pub status: WorkloadHeartbeatStatus,
533    pub function_name: String,
534    pub runtime: Option<String>,
535    pub package_type: Option<String>,
536    pub memory_size_mb: Option<i64>,
537    pub timeout_seconds: Option<i64>,
538    pub version: Option<String>,
539    pub revision_id: Option<String>,
540    pub last_modified: Option<String>,
541    pub state: Option<String>,
542    pub state_reason: Option<String>,
543    pub state_reason_code: Option<String>,
544    pub last_update_status: Option<String>,
545    pub last_update_status_reason: Option<String>,
546    pub last_update_status_reason_code: Option<String>,
547    pub code_sha256: Option<String>,
548    pub layer_count: u32,
549    pub function_url_auth_type: Option<String>,
550    pub function_url_cors_present: bool,
551    pub trigger_count: u32,
552}
553
554#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
555#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
556#[serde(rename_all = "camelCase")]
557pub struct GcpCloudRunWorkerHeartbeatData {
558    pub status: WorkloadHeartbeatStatus,
559    pub service: String,
560    pub region: Option<String>,
561    pub uri: Option<String>,
562    pub urls: Vec<String>,
563    pub latest_created_revision: Option<String>,
564    pub latest_ready_revision: Option<String>,
565    pub generation: Option<i64>,
566    pub observed_generation: Option<i64>,
567    pub traffic_count: u32,
568    pub min_instance_count: Option<i32>,
569    pub max_instance_count: Option<i32>,
570    pub container_image: Option<String>,
571    pub cpu_limit: Option<String>,
572    pub memory_limit: Option<String>,
573}
574
575#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
576#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
577#[serde(rename_all = "camelCase")]
578pub struct AzureContainerAppsWorkerHeartbeatData {
579    pub status: WorkloadHeartbeatStatus,
580    pub app_name: String,
581    pub revision: Option<String>,
582    pub environment_name: Option<String>,
583    pub provisioning_state: Option<String>,
584    pub running_status: Option<String>,
585    pub ingress_fqdn: Option<String>,
586    pub min_replicas: Option<i32>,
587    pub max_replicas: Option<i32>,
588    pub cpu: Option<f64>,
589    pub memory: Option<String>,
590}
591
592#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
593#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
594#[serde(rename_all = "camelCase")]
595pub struct KubernetesWorkerHeartbeatData {
596    pub status: WorkloadHeartbeatStatus,
597    pub namespace: String,
598    pub name: String,
599    pub workload_kind: KubernetesWorkloadKind,
600    pub replicas: WorkloadReplicaStatus,
601    pub restarts: Option<u32>,
602    pub cpu: Option<MetricSample>,
603    pub memory: Option<MetricSample>,
604    pub workload: Option<KubernetesWorkloadStatus>,
605    pub pods: Vec<KubernetesPodRuntimeUnitStatus>,
606    pub trigger_count: u32,
607    pub events: Vec<KubernetesEventSnapshot>,
608}
609
610#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
611#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
612#[serde(rename_all = "camelCase")]
613pub struct LocalWorkerHeartbeatData {
614    pub status: WorkloadHeartbeatStatus,
615    pub pid: Option<u32>,
616    pub command_supported: bool,
617    pub image_path_present: bool,
618    pub readiness_probe_ok: Option<bool>,
619    pub trigger_count: u32,
620    pub cpu: Option<MetricSample>,
621    pub memory: Option<MetricSample>,
622    pub process: Option<LocalRuntimeUnitStatus>,
623    pub events: Vec<LocalRuntimeEventSnapshot>,
624}
625
626#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
627#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
628#[serde(rename_all = "camelCase")]
629pub struct HorizonContainerHeartbeatData {
630    pub status: WorkloadHeartbeatStatus,
631    pub container_id: String,
632    pub image: Option<String>,
633    #[serde(default)]
634    pub observed_image: Option<String>,
635    #[serde(default)]
636    pub latest_update_timestamp: Option<String>,
637    pub scheduling_mode: HorizonWorkloadSchedulingMode,
638    pub replicas: WorkloadReplicaStatus,
639    pub cpu: Option<MetricSample>,
640    pub memory: Option<MetricSample>,
641    pub attention_count: u32,
642    pub replica_units: Vec<ManagedRuntimeUnitStatus>,
643    pub events: Vec<ManagedRuntimeEventSnapshot>,
644}
645
646#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
647#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
648#[serde(rename_all = "camelCase")]
649pub enum HorizonWorkloadSchedulingMode {
650    Replicated,
651    Stateful,
652    Daemon,
653}
654
655#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
656#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
657#[serde(rename_all = "camelCase")]
658pub struct KubernetesContainerHeartbeatData {
659    pub status: WorkloadHeartbeatStatus,
660    pub namespace: String,
661    pub name: String,
662    pub workload_kind: KubernetesWorkloadKind,
663    pub replicas: WorkloadReplicaStatus,
664    pub restarts: Option<u32>,
665    pub cpu: Option<MetricSample>,
666    pub memory: Option<MetricSample>,
667    pub workload: Option<KubernetesWorkloadStatus>,
668    pub pods: Vec<KubernetesPodRuntimeUnitStatus>,
669    pub events: Vec<KubernetesEventSnapshot>,
670}
671
672#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
673#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
674#[serde(rename_all = "camelCase")]
675pub struct LocalContainerHeartbeatData {
676    pub status: WorkloadHeartbeatStatus,
677    pub container_id: Option<String>,
678    pub name: Option<String>,
679    pub image: Option<String>,
680    pub runtime_status: Option<String>,
681    pub restart_count: Option<u32>,
682    pub port_count: u32,
683    pub bind_mount_count: u32,
684    pub local_url: Option<String>,
685    pub runtime_reachable: bool,
686    pub cpu: Option<MetricSample>,
687    pub memory: Option<MetricSample>,
688    pub container_unit: Option<LocalRuntimeUnitStatus>,
689    pub events: Vec<LocalRuntimeEventSnapshot>,
690}
691
692#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
693#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
694#[serde(rename_all = "camelCase")]
695pub struct AwsDaemonHeartbeatData {
696    pub status: WorkloadHeartbeatStatus,
697    pub horizon_cluster_id: String,
698    #[serde(default)]
699    pub daemon_name: String,
700    pub horizon_status: String,
701    pub horizon_status_reason: Option<String>,
702    pub horizon_status_message: Option<String>,
703    pub capacity_group: String,
704    pub desired_machines: u32,
705    pub assigned_machines: u32,
706    pub healthy_instances: u32,
707    pub unavailable_instances: u32,
708    pub command_supported: bool,
709    #[serde(default)]
710    pub observed_image: Option<String>,
711    pub latest_update_timestamp: String,
712    pub daemon_instances: Vec<ManagedRuntimeUnitStatus>,
713    pub events: Vec<ManagedRuntimeEventSnapshot>,
714}
715
716#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
717#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
718#[serde(rename_all = "camelCase")]
719pub struct GcpDaemonHeartbeatData {
720    pub status: WorkloadHeartbeatStatus,
721    pub horizon_cluster_id: String,
722    #[serde(default)]
723    pub daemon_name: String,
724    pub horizon_status: String,
725    pub horizon_status_reason: Option<String>,
726    pub horizon_status_message: Option<String>,
727    pub capacity_group: String,
728    pub desired_machines: u32,
729    pub assigned_machines: u32,
730    pub healthy_instances: u32,
731    pub unavailable_instances: u32,
732    pub command_supported: bool,
733    #[serde(default)]
734    pub observed_image: Option<String>,
735    pub latest_update_timestamp: String,
736    pub daemon_instances: Vec<ManagedRuntimeUnitStatus>,
737    pub events: Vec<ManagedRuntimeEventSnapshot>,
738}
739
740#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
741#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
742#[serde(rename_all = "camelCase")]
743pub struct AzureDaemonHeartbeatData {
744    pub status: WorkloadHeartbeatStatus,
745    pub horizon_cluster_id: String,
746    #[serde(default)]
747    pub daemon_name: String,
748    pub horizon_status: String,
749    pub horizon_status_reason: Option<String>,
750    pub horizon_status_message: Option<String>,
751    pub capacity_group: String,
752    pub desired_machines: u32,
753    pub assigned_machines: u32,
754    pub healthy_instances: u32,
755    pub unavailable_instances: u32,
756    pub command_supported: bool,
757    #[serde(default)]
758    pub observed_image: Option<String>,
759    pub latest_update_timestamp: String,
760    pub daemon_instances: Vec<ManagedRuntimeUnitStatus>,
761    pub events: Vec<ManagedRuntimeEventSnapshot>,
762}
763
764#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
765#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
766#[serde(rename_all = "camelCase")]
767pub struct MachinesDaemonHeartbeatData {
768    pub status: WorkloadHeartbeatStatus,
769    pub horizon_cluster_id: String,
770    #[serde(default)]
771    pub daemon_name: String,
772    pub horizon_status: String,
773    pub horizon_status_reason: Option<String>,
774    pub horizon_status_message: Option<String>,
775    pub capacity_group: String,
776    pub desired_machines: u32,
777    pub assigned_machines: u32,
778    pub healthy_instances: u32,
779    pub unavailable_instances: u32,
780    pub command_supported: bool,
781    #[serde(default)]
782    pub observed_image: Option<String>,
783    pub latest_update_timestamp: String,
784    pub daemon_instances: Vec<ManagedRuntimeUnitStatus>,
785    pub events: Vec<ManagedRuntimeEventSnapshot>,
786}
787
788#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
789#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
790#[serde(rename_all = "camelCase")]
791pub struct ManagedRuntimeUnitStatus {
792    pub replica_id: String,
793    pub name: String,
794    pub machine_id: Option<String>,
795    pub node_name: Option<String>,
796    pub ip: Option<String>,
797    pub ready: bool,
798    pub phase: Option<String>,
799    pub status: Option<String>,
800    pub reason: Option<String>,
801    pub message: Option<String>,
802    pub restart_count: Option<u32>,
803    pub waiting_reason: Option<String>,
804    pub terminated_reason: Option<String>,
805    pub metrics_healthy: Option<bool>,
806    pub metrics_status: Option<String>,
807    pub metrics_last_updated: Option<String>,
808    pub cpu: Option<MetricSample>,
809    pub memory: Option<MetricSample>,
810}
811
812#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
813#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
814#[serde(rename_all = "camelCase")]
815pub struct KubernetesDaemonHeartbeatData {
816    pub status: WorkloadHeartbeatStatus,
817    pub namespace: String,
818    pub name: String,
819    pub replicas: WorkloadReplicaStatus,
820    pub restarts: Option<u32>,
821    pub command_supported: bool,
822    pub cpu: Option<MetricSample>,
823    pub memory: Option<MetricSample>,
824    pub workload: Option<KubernetesWorkloadStatus>,
825    pub pods: Vec<KubernetesPodRuntimeUnitStatus>,
826    pub events: Vec<KubernetesEventSnapshot>,
827}
828
829#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
830#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
831#[serde(rename_all = "camelCase")]
832pub struct LocalDaemonHeartbeatData {
833    pub status: WorkloadHeartbeatStatus,
834    #[serde(default)]
835    pub daemon_name: String,
836    pub runtime_id: String,
837    pub pid: Option<u32>,
838    pub command_supported: bool,
839    pub image_path_present: bool,
840    pub restart_count: Option<u32>,
841    pub exit_reason: Option<String>,
842    pub daemon_instance: Option<LocalRuntimeUnitStatus>,
843    pub events: Vec<LocalRuntimeEventSnapshot>,
844}
845
846#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
847#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
848#[serde(rename_all = "camelCase")]
849pub struct LocalRuntimeUnitStatus {
850    pub unit_id: String,
851    pub name: String,
852    pub kind: LocalRuntimeUnitKind,
853    pub ready: bool,
854    pub phase: Option<String>,
855    pub pid: Option<u32>,
856    pub restart_count: Option<u32>,
857    pub cpu: Option<MetricSample>,
858    pub memory: Option<MetricSample>,
859}
860
861#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
862#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
863#[serde(rename_all = "kebab-case")]
864pub enum LocalRuntimeUnitKind {
865    Container,
866    Process,
867    Daemon,
868}
869
870#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
871#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
872#[serde(rename_all = "camelCase")]
873pub enum KubernetesWorkloadKind {
874    Deployment,
875    StatefulSet,
876    DaemonSet,
877    ReplicaSet,
878    Pod,
879}
880
881#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
882#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
883#[serde(rename_all = "camelCase")]
884pub struct KubernetesWorkloadStatus {
885    pub desired_replicas: Option<u32>,
886    pub ready_replicas: Option<u32>,
887    pub available_replicas: Option<u32>,
888    pub updated_replicas: Option<u32>,
889    pub observed_generation: Option<i64>,
890    pub desired_generation: Option<i64>,
891    pub rollout_reason: Option<String>,
892    pub conditions: Vec<KubernetesWorkloadCondition>,
893}
894
895#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
896#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
897#[serde(rename_all = "camelCase")]
898pub struct KubernetesWorkloadCondition {
899    #[serde(rename = "type")]
900    pub condition_type: String,
901    pub status: String,
902    pub reason: Option<String>,
903    pub message: Option<String>,
904    pub last_transition_time: Option<DateTime<Utc>>,
905}
906
907#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
908#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
909#[serde(rename_all = "camelCase")]
910pub struct KubernetesPodRuntimeUnitStatus {
911    pub name: String,
912    pub uid: Option<String>,
913    pub phase: Option<String>,
914    pub ready: bool,
915    pub restart_count: u32,
916    pub waiting_reason: Option<String>,
917    pub terminated_reason: Option<String>,
918    pub node_name: Option<String>,
919    pub pod_ip: Option<String>,
920    pub owner_references: Vec<KubernetesOwnerReference>,
921    pub cpu: Option<MetricSample>,
922    pub memory: Option<MetricSample>,
923}
924
925#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
926#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
927#[serde(rename_all = "camelCase")]
928pub struct KubernetesOwnerReference {
929    pub kind: String,
930    pub name: String,
931    pub uid: String,
932    pub controller: bool,
933}
934
935#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
936#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
937#[serde(tag = "backend", rename_all = "camelCase")]
938pub enum ComputeClusterHeartbeatData {
939    Aws(AwsComputeClusterHeartbeatData),
940    Gcp(GcpComputeClusterHeartbeatData),
941    Azure(AzureComputeClusterHeartbeatData),
942    Machines(MachinesComputeClusterHeartbeatData),
943    Local(LocalComputeClusterHeartbeatData),
944}
945
946#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
947#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
948#[serde(rename_all = "camelCase")]
949pub struct ComputeClusterHeartbeatStatus {
950    pub health: ObservedHealth,
951    pub lifecycle: ProviderLifecycleState,
952    pub message: Option<String>,
953    pub stale: bool,
954    pub partial: bool,
955    pub collection_issues: Vec<HeartbeatCollectionIssue>,
956}
957
958#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
959#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
960#[serde(rename_all = "camelCase")]
961pub struct LocalComputeClusterHeartbeatData {
962    pub status: ComputeClusterHeartbeatStatus,
963    pub nodes: ObservedCounts,
964    pub name: String,
965    pub host_identifier: Option<String>,
966    pub docker_available: bool,
967    pub docker_version: Option<String>,
968    pub docker_api_version: Option<String>,
969    pub docker_os: Option<String>,
970    pub docker_arch: Option<String>,
971    pub network_name: Option<String>,
972    pub network_available: bool,
973    pub tracked_containers: Option<u32>,
974    pub running_containers: Option<u32>,
975}
976
977#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
978#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
979#[serde(rename_all = "camelCase")]
980pub struct AwsComputeClusterHeartbeatData {
981    pub status: ComputeClusterHeartbeatStatus,
982    pub nodes: ObservedCounts,
983    pub cpu: Option<MetricSample>,
984    pub memory: Option<MetricSample>,
985    pub name: String,
986    pub region: Option<String>,
987    pub backend_cluster_id: Option<String>,
988    pub capacity_groups: Vec<ComputeCapacityGroupStatus>,
989    pub provider_fleets: Vec<ProviderFleetStatus>,
990}
991
992#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
993#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
994#[serde(rename_all = "camelCase")]
995pub struct GcpComputeClusterHeartbeatData {
996    pub status: ComputeClusterHeartbeatStatus,
997    pub nodes: ObservedCounts,
998    pub cpu: Option<MetricSample>,
999    pub memory: Option<MetricSample>,
1000    pub name: String,
1001    pub region: Option<String>,
1002    pub backend_cluster_id: Option<String>,
1003    pub capacity_groups: Vec<ComputeCapacityGroupStatus>,
1004    pub provider_fleets: Vec<ProviderFleetStatus>,
1005}
1006
1007#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1008#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1009#[serde(rename_all = "camelCase")]
1010pub struct AzureComputeClusterHeartbeatData {
1011    pub status: ComputeClusterHeartbeatStatus,
1012    pub nodes: ObservedCounts,
1013    pub cpu: Option<MetricSample>,
1014    pub memory: Option<MetricSample>,
1015    pub name: String,
1016    pub region: Option<String>,
1017    pub backend_cluster_id: Option<String>,
1018    pub capacity_groups: Vec<ComputeCapacityGroupStatus>,
1019    pub provider_fleets: Vec<ProviderFleetStatus>,
1020}
1021
1022#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1023#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1024#[serde(rename_all = "camelCase")]
1025pub struct MachinesComputeClusterHeartbeatData {
1026    pub status: ComputeClusterHeartbeatStatus,
1027    pub nodes: ObservedCounts,
1028    pub cpu: Option<MetricSample>,
1029    pub memory: Option<MetricSample>,
1030    pub name: String,
1031    pub backend_cluster_id: Option<String>,
1032    pub capacity_groups: Vec<ComputeCapacityGroupStatus>,
1033    pub machines: Vec<MachinesComputeMachineStatus>,
1034}
1035
1036#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1037#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1038#[serde(rename_all = "camelCase")]
1039pub struct MachinesComputeMachineStatus {
1040    pub machine_id: String,
1041    pub status: String,
1042    pub capacity_group: String,
1043    pub zone: String,
1044    pub public_ip: Option<String>,
1045    pub overlay_ip: Option<String>,
1046    pub last_heartbeat: String,
1047    pub horizond_version: Option<String>,
1048    pub replica_count: i64,
1049    pub cpu_cores: Option<f64>,
1050    pub memory_bytes: Option<i64>,
1051    pub drain_force: bool,
1052    pub drain_requested_at: Option<String>,
1053    pub drain_deadline_at: Option<String>,
1054    pub drained_at: Option<String>,
1055    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1056    pub drain_blockers: Vec<ComputeDrainBlocker>,
1057}
1058
1059#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1060#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1061#[serde(rename_all = "camelCase")]
1062pub struct ComputeCapacityGroupStatus {
1063    pub group_id: String,
1064    pub current_machines: u32,
1065    pub desired_machines: u32,
1066    pub min_machines: Option<u32>,
1067    pub max_machines: Option<u32>,
1068    pub instance_type: Option<String>,
1069    pub recommendation: Option<ComputeCapacityRecommendation>,
1070    pub capacity_blocker: Option<ComputeCapacityBlocker>,
1071    #[serde(default, skip_serializing_if = "Option::is_none")]
1072    pub drain_progress: Option<ComputeDrainProgress>,
1073}
1074
1075#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1076#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1077#[serde(rename_all = "camelCase")]
1078pub enum ComputeCapacityBlockerCategory {
1079    Quota,
1080    Capacity,
1081    Allocation,
1082    Other,
1083}
1084
1085#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1086#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1087#[serde(rename_all = "camelCase")]
1088pub struct ComputeCapacityBlocker {
1089    pub category: ComputeCapacityBlockerCategory,
1090    pub provider_code: Option<String>,
1091    pub message: String,
1092    pub provider_reference: Option<String>,
1093    pub observed_at: DateTime<Utc>,
1094}
1095
1096#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1097#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1098#[serde(rename_all = "camelCase")]
1099pub struct ComputeCapacityRecommendation {
1100    pub desired_machines: u32,
1101    pub reason: Option<String>,
1102    pub utilization: Option<MetricSample>,
1103    pub unschedulable_replicas: Option<u32>,
1104}
1105
1106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1107#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1108#[serde(rename_all = "camelCase")]
1109pub enum ComputeDrainProgressStatus {
1110    Draining,
1111    Drained,
1112    Terminating,
1113}
1114
1115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1116#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1117#[serde(rename_all = "camelCase")]
1118pub struct ComputeDrainBlocker {
1119    pub workload_name: String,
1120    pub replica_id: String,
1121    pub scheduling_mode: String,
1122    pub state: String,
1123    pub reason: String,
1124}
1125
1126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1127#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1128#[serde(rename_all = "camelCase")]
1129pub struct ComputeDrainProgress {
1130    pub machine_id: String,
1131    pub status: ComputeDrainProgressStatus,
1132    pub replica_count: i64,
1133    pub force: bool,
1134    pub stalled: bool,
1135    pub drain_requested_at: Option<String>,
1136    pub drain_deadline_at: Option<String>,
1137    pub drained_at: Option<String>,
1138    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1139    pub blockers: Vec<ComputeDrainBlocker>,
1140}
1141
1142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1143#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1144#[serde(rename_all = "camelCase")]
1145pub struct ProviderFleetStatus {
1146    pub group_id: String,
1147    pub provider_id: String,
1148    pub location: Option<String>,
1149    pub current_machines: u32,
1150    pub desired_machines: u32,
1151}
1152
1153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1154#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1155#[serde(rename_all = "camelCase")]
1156pub struct KubernetesClusterHeartbeatData {
1157    pub status: WorkloadHeartbeatStatus,
1158    pub node_counts: ObservedCounts,
1159    pub pod_counts: ObservedCounts,
1160    pub cpu: Option<MetricSample>,
1161    pub memory: Option<MetricSample>,
1162    pub name: String,
1163    pub region: Option<String>,
1164    pub namespace: Option<String>,
1165    pub version: Option<String>,
1166    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1167    pub node_statuses: Vec<KubernetesClusterNodeStatus>,
1168    pub events: Vec<KubernetesEventSnapshot>,
1169}
1170
1171#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1172#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1173#[serde(rename_all = "camelCase")]
1174pub struct KubernetesClusterNodeStatus {
1175    pub name: String,
1176    pub uid: Option<String>,
1177    pub ready: bool,
1178    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1179    pub conditions: Vec<KubernetesNodeConditionStatus>,
1180    pub roles: Vec<String>,
1181    pub labels: BTreeMap<String, String>,
1182    pub allocatable: KubernetesNodeResources,
1183    pub capacity: KubernetesNodeResources,
1184    pub usage: Option<KubernetesNodeUsage>,
1185    pub kubelet_version: Option<String>,
1186    pub container_runtime_version: Option<String>,
1187}
1188
1189#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1190#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1191#[serde(rename_all = "camelCase")]
1192pub struct KubernetesNodeConditionStatus {
1193    pub type_: String,
1194    pub status: String,
1195    pub reason: Option<String>,
1196    pub message: Option<String>,
1197}
1198
1199#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1200#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1201#[serde(rename_all = "camelCase")]
1202pub struct KubernetesNodeResources {
1203    pub cpu: Option<MetricSample>,
1204    pub memory: Option<MetricSample>,
1205    pub pods: Option<u64>,
1206}
1207
1208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1209#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1210#[serde(rename_all = "camelCase")]
1211pub struct KubernetesNodeUsage {
1212    pub cpu: Option<MetricSample>,
1213    pub memory: Option<MetricSample>,
1214}
1215
1216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1217#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1218#[serde(tag = "backend", rename_all = "camelCase")]
1219pub enum QueueHeartbeatData {
1220    AwsSqs(AwsSqsQueueHeartbeatData),
1221    GcpPubSub(GcpPubSubQueueHeartbeatData),
1222    AzureServiceBus(AzureServiceBusQueueHeartbeatData),
1223    Local(LocalQueueHeartbeatData),
1224}
1225
1226#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1227#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1228#[serde(rename_all = "camelCase")]
1229pub struct QueueHeartbeatStatus {
1230    pub health: ObservedHealth,
1231    pub lifecycle: ProviderLifecycleState,
1232    pub message: Option<String>,
1233    pub stale: bool,
1234    pub partial: bool,
1235    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1236}
1237
1238impl Default for QueueHeartbeatStatus {
1239    fn default() -> Self {
1240        Self {
1241            health: ObservedHealth::Unknown,
1242            lifecycle: ProviderLifecycleState::Unknown,
1243            message: None,
1244            stale: false,
1245            partial: false,
1246            collection_issues: vec![],
1247        }
1248    }
1249}
1250
1251#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1252#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1253#[serde(rename_all = "camelCase")]
1254pub struct LocalQueueHeartbeatData {
1255    pub status: QueueHeartbeatStatus,
1256    pub name: String,
1257    pub path: Option<String>,
1258    pub service_status: Option<String>,
1259}
1260
1261#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1262#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1263#[serde(rename_all = "camelCase")]
1264pub struct GcpPubSubQueueHeartbeatData {
1265    pub status: QueueHeartbeatStatus,
1266    pub topic_name: String,
1267    pub subscription_name: Option<String>,
1268    pub project_id: Option<String>,
1269    pub topic_full_name: Option<String>,
1270    pub subscription_full_name: Option<String>,
1271    pub endpoint: Option<String>,
1272    pub topic_labels: BTreeMap<String, String>,
1273    pub subscription_labels: BTreeMap<String, String>,
1274    pub message_storage_allowed_persistence_regions: Vec<String>,
1275    pub message_storage_enforce_in_transit: Option<bool>,
1276    pub kms_key_name: Option<String>,
1277    pub schema_name: Option<String>,
1278    pub schema_encoding: Option<String>,
1279    pub schema_first_revision_id: Option<String>,
1280    pub schema_last_revision_id: Option<String>,
1281    pub topic_message_retention_duration: Option<String>,
1282    pub topic_state: Option<String>,
1283    pub subscription_ack_deadline_seconds: Option<u32>,
1284    pub subscription_message_retention_duration: Option<String>,
1285    pub subscription_retain_acked_messages: Option<bool>,
1286    pub subscription_enable_message_ordering: Option<bool>,
1287    pub subscription_filter: Option<String>,
1288    pub subscription_detached: Option<bool>,
1289    pub subscription_state: Option<String>,
1290    pub subscription_push_config_present: Option<bool>,
1291    pub subscription_push_endpoint: Option<String>,
1292    pub subscription_push_attributes: BTreeMap<String, String>,
1293    pub subscription_push_oidc_service_account_email: Option<String>,
1294    pub subscription_push_oidc_audience: Option<String>,
1295    pub subscription_push_pubsub_wrapper_write_metadata: Option<bool>,
1296    pub subscription_push_no_wrapper_write_metadata: Option<bool>,
1297    pub subscription_dead_letter_topic: Option<String>,
1298    pub subscription_dead_letter_max_delivery_attempts: Option<u32>,
1299}
1300
1301#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1302#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1303#[serde(rename_all = "camelCase")]
1304pub struct AzureServiceBusQueueHeartbeatData {
1305    pub status: QueueHeartbeatStatus,
1306    pub name: String,
1307    pub namespace_name: String,
1308    pub resource_group: Option<String>,
1309    pub resource_id: Option<String>,
1310    pub endpoint: Option<String>,
1311    pub queue_status: Option<String>,
1312    pub lock_duration: Option<String>,
1313    pub max_delivery_count: Option<u32>,
1314    pub requires_duplicate_detection: Option<bool>,
1315    pub duplicate_detection_history_time_window: Option<String>,
1316    pub requires_session: Option<bool>,
1317    pub dead_lettering_on_message_expiration: Option<bool>,
1318    pub forward_dead_lettered_messages_to: Option<String>,
1319    pub forward_to: Option<String>,
1320    pub default_message_time_to_live: Option<String>,
1321    pub auto_delete_on_idle: Option<String>,
1322    pub enable_batched_operations: Option<bool>,
1323    pub enable_express: Option<bool>,
1324    pub enable_partitioning: Option<bool>,
1325    pub max_message_size_in_kilobytes: Option<u64>,
1326    pub max_size_in_megabytes: Option<u32>,
1327    pub message_count: Option<u64>,
1328    pub active_message_count: Option<u64>,
1329    pub dead_letter_message_count: Option<u64>,
1330    pub scheduled_message_count: Option<u64>,
1331    pub transfer_message_count: Option<u64>,
1332    pub transfer_dead_letter_message_count: Option<u64>,
1333    pub size_in_bytes: Option<u64>,
1334    pub accessed_at: Option<String>,
1335    pub created_at: Option<String>,
1336    pub updated_at: Option<String>,
1337}
1338
1339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1340#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1341#[serde(rename_all = "camelCase")]
1342pub struct AwsSqsQueueHeartbeatData {
1343    pub status: QueueHeartbeatStatus,
1344    pub name: String,
1345    pub region: Option<String>,
1346    pub queue_url: Option<String>,
1347    pub queue_arn: Option<String>,
1348    pub visibility_timeout_seconds: Option<u32>,
1349    pub message_retention_period_seconds: Option<u32>,
1350    pub delay_seconds: Option<u32>,
1351    pub receive_message_wait_time_seconds: Option<u32>,
1352    pub maximum_message_size: Option<u32>,
1353    pub redrive_policy: Option<String>,
1354    pub redrive_allow_policy: Option<String>,
1355    pub fifo_queue: Option<bool>,
1356    pub content_based_deduplication: Option<bool>,
1357    pub deduplication_scope: Option<String>,
1358    pub fifo_throughput_limit: Option<String>,
1359    pub sse_enabled: Option<bool>,
1360    pub kms_master_key_id: Option<String>,
1361    pub kms_data_key_reuse_period_seconds: Option<u32>,
1362    pub sqs_managed_sse_enabled: Option<bool>,
1363    pub approximate_visible_messages: Option<u64>,
1364    pub approximate_in_flight_messages: Option<u64>,
1365    pub approximate_delayed_messages: Option<u64>,
1366    pub approximate_counts: bool,
1367}
1368
1369#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1370#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1371#[serde(tag = "backend", rename_all = "camelCase")]
1372pub enum KvHeartbeatData {
1373    AwsDynamoDb(AwsDynamoDbKvHeartbeatData),
1374    GcpFirestore(GcpFirestoreKvHeartbeatData),
1375    AzureTable(AzureTableKvHeartbeatData),
1376    Local(LocalKvHeartbeatData),
1377}
1378
1379#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1380#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1381#[serde(rename_all = "camelCase")]
1382pub struct KvHeartbeatStatus {
1383    pub health: ObservedHealth,
1384    pub lifecycle: ProviderLifecycleState,
1385    pub message: Option<String>,
1386    pub stale: bool,
1387    pub partial: bool,
1388    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1389}
1390
1391impl Default for KvHeartbeatStatus {
1392    fn default() -> Self {
1393        Self {
1394            health: ObservedHealth::Unknown,
1395            lifecycle: ProviderLifecycleState::Unknown,
1396            message: None,
1397            stale: false,
1398            partial: false,
1399            collection_issues: vec![],
1400        }
1401    }
1402}
1403
1404#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1405#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1406#[serde(rename_all = "camelCase")]
1407pub struct AwsDynamoDbKvHeartbeatData {
1408    pub status: KvHeartbeatStatus,
1409    pub name: String,
1410    pub region: Option<String>,
1411    pub table_arn: Option<String>,
1412    pub table_status: Option<String>,
1413    pub billing_mode: Option<String>,
1414    pub key_schema: Vec<AwsDynamoDbKeySchemaElement>,
1415    pub global_secondary_index_count: Option<u32>,
1416    pub local_secondary_index_count: Option<u32>,
1417    pub item_count: Option<u64>,
1418    pub table_size_bytes: Option<u64>,
1419    pub stream_enabled: Option<bool>,
1420    pub stream_view_type: Option<String>,
1421    pub ttl_status: Option<String>,
1422    pub ttl_attribute_name: Option<String>,
1423    pub deletion_protection_enabled: Option<bool>,
1424    pub sse_status: Option<String>,
1425    pub sse_type: Option<String>,
1426    pub table_class: Option<String>,
1427    pub replica_count: Option<u32>,
1428    pub restore_in_progress: Option<bool>,
1429}
1430
1431#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1432#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1433#[serde(rename_all = "camelCase")]
1434pub struct AwsDynamoDbKeySchemaElement {
1435    pub attribute_name: String,
1436    pub key_type: String,
1437}
1438
1439#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1440#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1441#[serde(rename_all = "camelCase")]
1442pub struct GcpFirestoreKvHeartbeatData {
1443    pub status: KvHeartbeatStatus,
1444    pub database_name: String,
1445    pub project_id: Option<String>,
1446    pub endpoint: Option<String>,
1447    pub location_id: Option<String>,
1448    pub database_type: Option<String>,
1449    pub concurrency_mode: Option<String>,
1450    pub app_engine_integration_mode: Option<String>,
1451    pub delete_protection_state: Option<String>,
1452    pub point_in_time_recovery_enablement: Option<String>,
1453    pub version_retention_period: Option<String>,
1454    pub earliest_version_time: Option<String>,
1455    pub create_time: Option<String>,
1456    pub update_time: Option<String>,
1457    pub delete_time: Option<String>,
1458    pub database_edition: Option<String>,
1459    pub cmek_enabled: bool,
1460    pub source_info_present: bool,
1461}
1462
1463#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1464#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1465#[serde(rename_all = "camelCase")]
1466pub struct AzureTableKvHeartbeatData {
1467    pub status: KvHeartbeatStatus,
1468    pub table_name: String,
1469    pub storage_account_name: String,
1470    pub resource_group: Option<String>,
1471    pub endpoint: Option<String>,
1472    pub storage_account_resource_id: Option<String>,
1473    pub storage_account_location: Option<String>,
1474    pub storage_account_kind: Option<String>,
1475    pub storage_account_provisioning_state: Option<String>,
1476    pub storage_account_primary_status: Option<String>,
1477    pub table_exists: bool,
1478    pub signed_identifier_count: Option<u32>,
1479}
1480
1481#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1482#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1483#[serde(rename_all = "camelCase")]
1484pub struct LocalKvHeartbeatData {
1485    pub status: KvHeartbeatStatus,
1486    pub name: String,
1487    pub path: String,
1488    pub path_exists: bool,
1489    pub is_directory: Option<bool>,
1490    pub cloud_metadata_supported: bool,
1491}
1492
1493#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1494#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1495#[serde(tag = "backend", rename_all = "camelCase")]
1496pub enum PostgresHeartbeatData {
1497    /// AWS Aurora Serverless v2 backend.
1498    Aurora(AuroraPostgresHeartbeatData),
1499    /// GCP Cloud SQL backend.
1500    CloudSql(GcpCloudSqlPostgresHeartbeatData),
1501    /// Azure Flexible Server backend.
1502    FlexibleServer(AzureFlexibleServerPostgresHeartbeatData),
1503    /// Local embedded Postgres backend.
1504    Local(LocalPostgresHeartbeatData),
1505}
1506
1507#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1508#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1509#[serde(rename_all = "camelCase")]
1510pub struct PostgresHeartbeatStatus {
1511    pub health: ObservedHealth,
1512    pub lifecycle: ProviderLifecycleState,
1513    pub message: Option<String>,
1514    pub stale: bool,
1515    pub partial: bool,
1516    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1517}
1518
1519impl Default for PostgresHeartbeatStatus {
1520    fn default() -> Self {
1521        Self {
1522            health: ObservedHealth::Unknown,
1523            lifecycle: ProviderLifecycleState::Unknown,
1524            message: None,
1525            stale: false,
1526            partial: false,
1527            collection_issues: vec![],
1528        }
1529    }
1530}
1531
1532#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1533#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1534#[serde(tag = "backend", rename_all = "camelCase")]
1535pub enum AiHeartbeatData {
1536    AwsBedrock(AwsBedrockAiHeartbeatData),
1537    GcpVertex(GcpVertexAiHeartbeatData),
1538    AzureFoundry(AzureFoundryAiHeartbeatData),
1539    External(ExternalAiHeartbeatData),
1540}
1541
1542#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1543#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1544#[serde(rename_all = "camelCase")]
1545pub struct AiHeartbeatStatus {
1546    pub health: ObservedHealth,
1547    pub lifecycle: ProviderLifecycleState,
1548    pub message: Option<String>,
1549    pub stale: bool,
1550    pub partial: bool,
1551    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1552}
1553
1554impl Default for AiHeartbeatStatus {
1555    fn default() -> Self {
1556        // No per-stack resource to poll, so unobserved = healthy/running;
1557        // errors surface at inference time via the SDK.
1558        Self {
1559            health: ObservedHealth::Healthy,
1560            lifecycle: ProviderLifecycleState::Running,
1561            message: None,
1562            stale: false,
1563            partial: false,
1564            collection_issues: vec![],
1565        }
1566    }
1567}
1568
1569#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1570#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1571#[serde(rename_all = "camelCase")]
1572pub struct LocalPostgresHeartbeatData {
1573    pub status: PostgresHeartbeatStatus,
1574    pub name: String,
1575    pub port: Option<u16>,
1576    pub version: String,
1577    pub process_running: bool,
1578}
1579
1580#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1581#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1582#[serde(rename_all = "camelCase")]
1583pub struct AuroraPostgresHeartbeatData {
1584    pub status: PostgresHeartbeatStatus,
1585    pub cluster_identifier: String,
1586    #[serde(skip_serializing_if = "Option::is_none")]
1587    pub engine_version: Option<String>,
1588    #[serde(skip_serializing_if = "Option::is_none")]
1589    pub endpoint: Option<String>,
1590    /// Latest sampled `ServerlessDatabaseCapacity` (ACU).
1591    #[serde(skip_serializing_if = "Option::is_none")]
1592    pub serverless_capacity: Option<f64>,
1593    /// True when a `minCapacity: 0` instance has not reached 0 ACU over the observation
1594    /// window — it is silently paying always-on prices (auto-pause verification).
1595    pub never_pauses: bool,
1596}
1597
1598#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1599#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1600#[serde(rename_all = "camelCase")]
1601pub struct GcpCloudSqlPostgresHeartbeatData {
1602    pub status: PostgresHeartbeatStatus,
1603    pub instance_name: String,
1604    #[serde(skip_serializing_if = "Option::is_none")]
1605    pub state: Option<String>,
1606    #[serde(skip_serializing_if = "Option::is_none")]
1607    pub database_version: Option<String>,
1608}
1609
1610#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1611#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1612#[serde(rename_all = "camelCase")]
1613pub struct AzureFlexibleServerPostgresHeartbeatData {
1614    pub status: PostgresHeartbeatStatus,
1615    pub server_name: String,
1616    #[serde(skip_serializing_if = "Option::is_none")]
1617    pub state: Option<String>,
1618    #[serde(skip_serializing_if = "Option::is_none")]
1619    pub version: Option<String>,
1620}
1621
1622#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1623#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1624#[serde(rename_all = "camelCase")]
1625pub struct AwsBedrockAiHeartbeatData {
1626    pub status: AiHeartbeatStatus,
1627    pub region: String,
1628}
1629
1630#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1631#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1632#[serde(rename_all = "camelCase")]
1633pub struct GcpVertexAiHeartbeatData {
1634    pub status: AiHeartbeatStatus,
1635    pub project: String,
1636    pub location: String,
1637}
1638
1639#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1640#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1641#[serde(rename_all = "camelCase")]
1642pub struct AzureFoundryAiHeartbeatData {
1643    pub status: AiHeartbeatStatus,
1644    pub account_name: String,
1645    pub endpoint: Option<String>,
1646    pub resource_group: Option<String>,
1647    pub location: Option<String>,
1648}
1649
1650#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1651#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1652#[serde(rename_all = "camelCase")]
1653pub struct ExternalAiHeartbeatData {
1654    pub status: AiHeartbeatStatus,
1655    /// The BYO-key provider serving this binding (e.g. "openai"). Used on the Local
1656    /// platform, where the app brings its own provider key instead of an ambient cloud.
1657    pub provider: String,
1658}
1659
1660#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1661#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1662#[serde(tag = "backend", rename_all = "camelCase")]
1663pub enum VaultHeartbeatData {
1664    AwsParameterStore(AwsParameterStoreVaultHeartbeatData),
1665    GcpSecretManager(GcpSecretManagerVaultHeartbeatData),
1666    AzureKeyVault(AzureKeyVaultHeartbeatData),
1667    KubernetesSecret(KubernetesSecretVaultHeartbeatData),
1668    Local(LocalVaultHeartbeatData),
1669}
1670
1671#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1672#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1673#[serde(rename_all = "camelCase")]
1674pub struct VaultHeartbeatStatus {
1675    pub health: ObservedHealth,
1676    pub lifecycle: ProviderLifecycleState,
1677    pub message: Option<String>,
1678    pub stale: bool,
1679    pub partial: bool,
1680    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1681}
1682
1683impl Default for VaultHeartbeatStatus {
1684    fn default() -> Self {
1685        Self {
1686            health: ObservedHealth::Healthy,
1687            lifecycle: ProviderLifecycleState::Running,
1688            message: None,
1689            stale: false,
1690            partial: false,
1691            collection_issues: vec![],
1692        }
1693    }
1694}
1695
1696#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1697#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1698#[serde(rename_all = "camelCase")]
1699pub struct AwsParameterStoreVaultHeartbeatData {
1700    pub status: VaultHeartbeatStatus,
1701    pub account_id: String,
1702    pub region: String,
1703    pub prefix: String,
1704    pub parameter_metadata_sampled: bool,
1705    pub sampled_parameter_count: Option<u32>,
1706    pub sampled_secure_string_count: Option<u32>,
1707    pub sampled_string_count: Option<u32>,
1708    pub sampled_string_list_count: Option<u32>,
1709    pub sampled_advanced_tier_count: Option<u32>,
1710    pub sampled_kms_key_metadata_present_count: Option<u32>,
1711    pub latest_modified_at: Option<DateTime<Utc>>,
1712    pub has_more_parameters: Option<bool>,
1713}
1714
1715#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1716#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1717#[serde(rename_all = "camelCase")]
1718pub struct GcpSecretManagerVaultHeartbeatData {
1719    pub status: VaultHeartbeatStatus,
1720    pub project_id: String,
1721    pub location: String,
1722    pub prefix: String,
1723    pub secret_metadata_listed: bool,
1724}
1725
1726#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1727#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1728#[serde(rename_all = "camelCase")]
1729pub struct AzureKeyVaultHeartbeatData {
1730    pub status: VaultHeartbeatStatus,
1731    pub name: String,
1732    pub resource_group: Option<String>,
1733    pub resource_id: Option<String>,
1734    pub location: Option<String>,
1735    pub vault_uri: Option<String>,
1736    pub provisioning_state: Option<String>,
1737    pub sku_family: Option<String>,
1738    pub sku_name: Option<String>,
1739    pub soft_delete_enabled: bool,
1740    pub soft_delete_retention_days: i32,
1741    pub purge_protection_enabled: Option<bool>,
1742    pub rbac_authorization_enabled: bool,
1743    pub public_network_access: String,
1744    pub access_policy_count: u32,
1745    pub private_endpoint_connection_count: u32,
1746    pub secret_metadata_listed: bool,
1747}
1748
1749#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1750#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1751#[serde(rename_all = "camelCase")]
1752pub struct KubernetesSecretVaultHeartbeatData {
1753    pub status: VaultHeartbeatStatus,
1754    pub namespace: String,
1755    pub prefix: String,
1756    pub secret_metadata_listed: bool,
1757}
1758
1759#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1760#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1761#[serde(rename_all = "camelCase")]
1762pub struct LocalVaultHeartbeatData {
1763    pub status: VaultHeartbeatStatus,
1764    pub path: String,
1765    pub path_exists: bool,
1766    pub is_directory: Option<bool>,
1767    pub readonly: Option<bool>,
1768    pub modified_at: Option<DateTime<Utc>>,
1769    pub secret_metadata_listed: bool,
1770}
1771
1772#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1773#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1774#[serde(tag = "backend", rename_all = "camelCase")]
1775pub enum ServiceAccountHeartbeatData {
1776    AwsIamRole(AwsIamRoleServiceAccountHeartbeatData),
1777    GcpServiceAccount(GcpServiceAccountHeartbeatData),
1778    AzureManagedIdentity(AzureManagedIdentityServiceAccountHeartbeatData),
1779    Local(LocalServiceAccountHeartbeatData),
1780}
1781
1782#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1783#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1784#[serde(rename_all = "camelCase")]
1785pub struct ServiceAccountHeartbeatStatus {
1786    pub health: ObservedHealth,
1787    pub lifecycle: ProviderLifecycleState,
1788    pub message: Option<String>,
1789    pub stale: bool,
1790    pub partial: bool,
1791    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1792}
1793
1794#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1795#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1796#[serde(rename_all = "camelCase")]
1797pub struct AwsIamRoleServiceAccountHeartbeatData {
1798    pub status: ServiceAccountHeartbeatStatus,
1799    pub role_name: String,
1800    pub role_arn: String,
1801    pub role_id: String,
1802    pub path: String,
1803    pub create_date: String,
1804    pub description: Option<String>,
1805    pub max_session_duration: Option<i32>,
1806    pub assume_role_policy_present: bool,
1807    pub permissions_boundary_type: Option<String>,
1808    pub permissions_boundary_arn: Option<String>,
1809    pub tag_count: u32,
1810    pub managed_tag_count: u32,
1811    pub attached_policy_count: u32,
1812    pub attached_policy_names: Vec<String>,
1813    pub inline_policy_count: u32,
1814    pub inline_policy_names: Vec<String>,
1815    pub stack_permissions_applied: bool,
1816    pub last_used_date: Option<String>,
1817    pub last_used_region: Option<String>,
1818}
1819
1820#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1821#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1822#[serde(rename_all = "camelCase")]
1823pub struct GcpServiceAccountHeartbeatData {
1824    pub status: ServiceAccountHeartbeatStatus,
1825    pub name: Option<String>,
1826    pub project_id: Option<String>,
1827    pub unique_id: Option<String>,
1828    pub email: String,
1829    pub display_name: Option<String>,
1830    pub description: Option<String>,
1831    pub oauth2_client_id: Option<String>,
1832    pub disabled: Option<bool>,
1833    pub etag: Option<String>,
1834    pub project_binding_count: u32,
1835    pub project_roles: Vec<String>,
1836    pub service_account_binding_count: u32,
1837    pub service_account_roles: Vec<String>,
1838}
1839
1840#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1841#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1842#[serde(rename_all = "camelCase")]
1843pub struct AzureManagedIdentityServiceAccountHeartbeatData {
1844    pub status: ServiceAccountHeartbeatStatus,
1845    pub name: String,
1846    pub resource_id: String,
1847    pub resource_group: String,
1848    pub location: String,
1849    pub type_: Option<String>,
1850    pub client_id: Option<String>,
1851    pub principal_id: Option<String>,
1852    pub tenant_id: Option<String>,
1853    pub isolation_scope: Option<String>,
1854    pub managed_tag_count: u32,
1855    pub role_assignment_count: u32,
1856    pub role_assignment_ids: Vec<String>,
1857    pub custom_role_definition_count: u32,
1858    pub custom_role_definition_ids: Vec<String>,
1859    pub stack_permissions_applied: bool,
1860}
1861
1862#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1863#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1864#[serde(rename_all = "camelCase")]
1865pub struct LocalServiceAccountHeartbeatData {
1866    pub status: ServiceAccountHeartbeatStatus,
1867    pub identity: String,
1868    pub configured: bool,
1869}
1870
1871#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1872#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1873#[serde(tag = "backend", rename_all = "camelCase")]
1874pub enum NetworkHeartbeatData {
1875    AwsVpc(AwsVpcNetworkHeartbeatData),
1876    GcpVpc(GcpVpcNetworkHeartbeatData),
1877    AzureVnet(AzureVnetNetworkHeartbeatData),
1878}
1879
1880#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1881#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1882#[serde(rename_all = "camelCase")]
1883pub struct NetworkHeartbeatStatus {
1884    pub health: ObservedHealth,
1885    pub lifecycle: ProviderLifecycleState,
1886    pub message: Option<String>,
1887    pub stale: bool,
1888    pub partial: bool,
1889    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1890}
1891
1892#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1893#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1894#[serde(rename_all = "camelCase")]
1895pub struct AwsVpcNetworkHeartbeatData {
1896    pub status: NetworkHeartbeatStatus,
1897    pub vpc_id: Option<String>,
1898    pub vpc_state: Option<String>,
1899    pub cidr_block: Option<String>,
1900    pub public_subnet_ids: Vec<String>,
1901    pub private_subnet_ids: Vec<String>,
1902    pub availability_zones: Vec<String>,
1903    pub internet_gateway_id: Option<String>,
1904    pub nat_gateway_id: Option<String>,
1905    pub route_table_count: u32,
1906    pub security_group_id: Option<String>,
1907    pub is_byo_vpc: bool,
1908}
1909
1910#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1911#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1912#[serde(rename_all = "camelCase")]
1913pub struct GcpVpcNetworkHeartbeatData {
1914    pub status: NetworkHeartbeatStatus,
1915    pub network_name: Option<String>,
1916    pub network_self_link: Option<String>,
1917    pub subnetwork_name: Option<String>,
1918    pub subnetwork_self_link: Option<String>,
1919    pub region: Option<String>,
1920    pub cidr_block: Option<String>,
1921    pub router_name: Option<String>,
1922    pub cloud_nat_name: Option<String>,
1923    pub firewall_name: Option<String>,
1924    pub is_byo_vpc: bool,
1925}
1926
1927#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1928#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1929#[serde(rename_all = "camelCase")]
1930pub struct AzureVnetNetworkHeartbeatData {
1931    pub status: NetworkHeartbeatStatus,
1932    pub vnet_name: Option<String>,
1933    pub vnet_resource_id: Option<String>,
1934    pub resource_group: Option<String>,
1935    pub location: Option<String>,
1936    pub cidr_block: Option<String>,
1937    pub public_subnet_name: Option<String>,
1938    pub private_subnet_name: Option<String>,
1939    pub application_gateway_subnet_name: Option<String>,
1940    pub private_endpoint_subnet_name: Option<String>,
1941    pub nat_gateway_id: Option<String>,
1942    pub public_ip_id: Option<String>,
1943    pub nsg_id: Option<String>,
1944    pub is_byo_vnet: bool,
1945    pub last_byo_vnet_verification_error_code: Option<String>,
1946}
1947
1948#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1949#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1950#[serde(tag = "backend", rename_all = "camelCase")]
1951pub enum RemoteStackManagementHeartbeatData {
1952    AwsIamRole(AwsRemoteStackManagementHeartbeatData),
1953    GcpServiceAccount(GcpRemoteStackManagementHeartbeatData),
1954    AzureManagedIdentity(AzureRemoteStackManagementHeartbeatData),
1955}
1956
1957#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1958#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1959#[serde(rename_all = "camelCase")]
1960pub struct RemoteStackManagementHeartbeatStatus {
1961    pub health: ObservedHealth,
1962    pub lifecycle: ProviderLifecycleState,
1963    pub message: Option<String>,
1964    pub stale: bool,
1965    pub partial: bool,
1966    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1967}
1968
1969#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1970#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1971#[serde(rename_all = "camelCase")]
1972pub struct AwsRemoteStackManagementHeartbeatData {
1973    pub status: RemoteStackManagementHeartbeatStatus,
1974    pub role_name: Option<String>,
1975    pub role_arn: Option<String>,
1976    pub management_permissions_applied: bool,
1977}
1978
1979#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1980#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1981#[serde(rename_all = "camelCase")]
1982pub struct GcpRemoteStackManagementHeartbeatData {
1983    pub status: RemoteStackManagementHeartbeatStatus,
1984    pub service_account_email: Option<String>,
1985    pub service_account_unique_id: Option<String>,
1986    pub role_bound: bool,
1987    pub impersonation_granted: bool,
1988}
1989
1990#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1991#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1992#[serde(rename_all = "camelCase")]
1993pub struct AzureRemoteStackManagementHeartbeatData {
1994    pub status: RemoteStackManagementHeartbeatStatus,
1995    pub uami_resource_id: Option<String>,
1996    pub uami_client_id: Option<String>,
1997    pub uami_principal_id: Option<String>,
1998    pub tenant_id: Option<String>,
1999    pub fic_name: Option<String>,
2000    pub role_definition_id: Option<String>,
2001    pub role_assignment_ids: Vec<String>,
2002}
2003
2004#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2005#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2006#[serde(tag = "backend", rename_all = "camelCase")]
2007pub enum ArtifactRegistryHeartbeatData {
2008    AwsEcr(AwsEcrArtifactRegistryHeartbeatData),
2009    GcpArtifactRegistry(GcpArtifactRegistryHeartbeatData),
2010    AzureContainerRegistry(AzureContainerRegistryHeartbeatData),
2011    Local(LocalArtifactRegistryHeartbeatData),
2012}
2013
2014#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2015#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2016#[serde(rename_all = "camelCase")]
2017pub struct ArtifactRegistryHeartbeatStatus {
2018    pub health: ObservedHealth,
2019    pub lifecycle: ProviderLifecycleState,
2020    pub message: Option<String>,
2021    pub stale: bool,
2022    pub partial: bool,
2023    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2024}
2025
2026#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2027#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2028#[serde(rename_all = "camelCase")]
2029pub struct AwsEcrArtifactRegistryHeartbeatData {
2030    pub status: ArtifactRegistryHeartbeatStatus,
2031    pub registry_id: String,
2032    pub region: String,
2033    pub registry_uri: String,
2034    pub repository_prefix: String,
2035    pub pull_role_arn: Option<String>,
2036    pub push_role_arn: Option<String>,
2037    pub repository_count: u32,
2038    pub repositories_truncated: bool,
2039    pub repositories: Vec<AwsEcrRepositoryHeartbeatData>,
2040}
2041
2042#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2043#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2044#[serde(rename_all = "camelCase")]
2045pub struct AwsEcrRepositoryHeartbeatData {
2046    pub repository_arn: String,
2047    pub registry_id: String,
2048    pub repository_name: String,
2049    pub repository_uri: String,
2050    pub created_at: f64,
2051    pub image_tag_mutability: Option<String>,
2052    pub scan_on_push: Option<bool>,
2053    pub encryption_type: Option<String>,
2054    pub kms_key_present: bool,
2055}
2056
2057#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2058#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2059#[serde(rename_all = "camelCase")]
2060pub struct GcpArtifactRegistryHeartbeatData {
2061    pub status: ArtifactRegistryHeartbeatStatus,
2062    pub project_id: String,
2063    pub location: String,
2064    pub repository_id: String,
2065    pub name: Option<String>,
2066    pub format: Option<String>,
2067    pub mode: Option<String>,
2068    pub description: Option<String>,
2069    pub label_count: u32,
2070    pub cleanup_policy_count: u32,
2071    pub cleanup_policy_dry_run: Option<bool>,
2072    pub kms_key_name_present: bool,
2073    pub size_bytes: Option<String>,
2074    pub satisfies_pzs: Option<bool>,
2075    pub create_time: Option<String>,
2076    pub update_time: Option<String>,
2077    pub iam_policy_etag_present: bool,
2078    pub iam_binding_count: u32,
2079    pub iam_roles: Vec<String>,
2080    pub pull_service_account_email: Option<String>,
2081    pub push_service_account_email: Option<String>,
2082}
2083
2084#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2085#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2086#[serde(rename_all = "camelCase")]
2087pub struct AzureContainerRegistryHeartbeatData {
2088    pub status: ArtifactRegistryHeartbeatStatus,
2089    pub name: String,
2090    pub resource_id: Option<String>,
2091    pub resource_group: String,
2092    pub location: String,
2093    pub type_: Option<String>,
2094    pub login_server: Option<String>,
2095    pub sku_name: String,
2096    pub sku_tier: Option<String>,
2097    pub provisioning_state: Option<String>,
2098    pub admin_user_enabled: bool,
2099    pub anonymous_pull_enabled: bool,
2100    pub public_network_access: String,
2101    pub network_rule_bypass_options: String,
2102    pub network_rule_default_action: Option<String>,
2103    pub ip_rule_count: u32,
2104    pub encryption_status: Option<String>,
2105    pub encryption_key_vault_uri_present: bool,
2106    pub encryption_key_identifier_present: bool,
2107    pub policies_present: bool,
2108    pub policy_count: u32,
2109    pub private_endpoint_connection_count: u32,
2110    pub data_endpoint_enabled: Option<bool>,
2111    pub data_endpoint_host_names: Vec<String>,
2112    pub zone_redundancy: String,
2113    pub creation_date: Option<String>,
2114    pub managed_tag_count: u32,
2115}
2116
2117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2118#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2119#[serde(rename_all = "camelCase")]
2120pub struct LocalArtifactRegistryHeartbeatData {
2121    pub status: ArtifactRegistryHeartbeatStatus,
2122    pub registry_url: String,
2123    pub reachable: bool,
2124}
2125
2126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2127#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2128#[serde(tag = "backend", rename_all = "camelCase")]
2129pub enum BuildHeartbeatData {
2130    AwsCodeBuild(AwsCodeBuildHeartbeatData),
2131    GcpCloudBuild(GcpCloudBuildHeartbeatData),
2132    AzureContainerApps(AzureContainerAppsBuildHeartbeatData),
2133    KubernetesJob(KubernetesBuildHeartbeatData),
2134}
2135
2136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2137#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2138#[serde(rename_all = "camelCase")]
2139pub struct BuildHeartbeatStatus {
2140    pub health: ObservedHealth,
2141    pub lifecycle: ProviderLifecycleState,
2142    pub message: Option<String>,
2143    pub stale: bool,
2144    pub partial: bool,
2145    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2146}
2147
2148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2149#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2150#[serde(rename_all = "camelCase")]
2151pub struct AwsCodeBuildHeartbeatData {
2152    pub status: BuildHeartbeatStatus,
2153    pub project_name: String,
2154    pub project_arn: Option<String>,
2155    pub description: Option<String>,
2156    pub source_type: Option<String>,
2157    pub artifacts_type: Option<String>,
2158    pub artifacts_encryption_disabled: Option<bool>,
2159    pub environment_type: Option<String>,
2160    pub environment_image: Option<String>,
2161    pub compute_type: Option<String>,
2162    pub image_pull_credentials_type: Option<String>,
2163    pub privileged_mode: Option<bool>,
2164    pub environment_variable_count: u32,
2165    pub service_role_present: bool,
2166    pub encryption_key_present: bool,
2167    pub cloud_watch_logs_status: Option<String>,
2168    pub s3_logs_status: Option<String>,
2169    pub timeout_in_minutes: Option<i32>,
2170    pub queued_timeout_in_minutes: Option<i32>,
2171    pub created: Option<f64>,
2172    pub last_modified: Option<f64>,
2173}
2174
2175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2176#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2177#[serde(rename_all = "camelCase")]
2178pub struct GcpCloudBuildHeartbeatData {
2179    pub status: BuildHeartbeatStatus,
2180    pub project_id: String,
2181    pub location: String,
2182    pub build_config_id: String,
2183    pub service_account: Option<String>,
2184    pub environment_variable_count: u32,
2185}
2186
2187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2188#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2189#[serde(rename_all = "camelCase")]
2190pub struct AzureContainerAppsBuildHeartbeatData {
2191    pub status: BuildHeartbeatStatus,
2192    pub managed_environment_id: String,
2193    pub resource_group_name: String,
2194    pub managed_identity_id: Option<String>,
2195    pub resource_prefix: Option<String>,
2196    pub environment_variable_count: u32,
2197}
2198
2199#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2200#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2201#[serde(rename_all = "camelCase")]
2202pub struct KubernetesBuildHeartbeatData {
2203    pub status: BuildHeartbeatStatus,
2204    pub job_name: String,
2205    pub namespace: String,
2206    pub active: Option<i32>,
2207    pub succeeded: Option<i32>,
2208    pub failed: Option<i32>,
2209    pub start_time: Option<DateTime<Utc>>,
2210    pub completion_time: Option<DateTime<Utc>>,
2211    pub condition_count: u32,
2212    pub image_digest: Option<String>,
2213    pub events: Vec<KubernetesEventSnapshot>,
2214}
2215
2216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2217#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2218#[serde(tag = "backend", rename_all = "camelCase")]
2219pub enum ServiceActivationHeartbeatData {
2220    GcpServiceUsage(GcpServiceUsageActivationHeartbeatData),
2221    AzureResourceProvider(AzureResourceProviderActivationHeartbeatData),
2222}
2223
2224#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2225#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2226#[serde(rename_all = "camelCase")]
2227pub struct ServiceActivationHeartbeatStatus {
2228    pub health: ObservedHealth,
2229    pub lifecycle: ProviderLifecycleState,
2230    pub message: Option<String>,
2231    pub stale: bool,
2232    pub partial: bool,
2233    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2234}
2235
2236impl Default for ServiceActivationHeartbeatStatus {
2237    fn default() -> Self {
2238        Self {
2239            health: ObservedHealth::Healthy,
2240            lifecycle: ProviderLifecycleState::Running,
2241            message: None,
2242            stale: false,
2243            partial: false,
2244            collection_issues: vec![],
2245        }
2246    }
2247}
2248
2249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2250#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2251#[serde(rename_all = "camelCase")]
2252pub struct GcpServiceUsageActivationHeartbeatData {
2253    pub status: ServiceActivationHeartbeatStatus,
2254    pub project_id: String,
2255    pub service_name: String,
2256    pub service_resource_name: Option<String>,
2257    pub title: Option<String>,
2258    pub state: Option<String>,
2259    pub enabled: bool,
2260    pub last_operation_name: Option<String>,
2261}
2262
2263#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2264#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2265#[serde(rename_all = "camelCase")]
2266pub struct AzureResourceProviderActivationHeartbeatData {
2267    pub status: ServiceActivationHeartbeatStatus,
2268    pub namespace: String,
2269    pub provider_id: Option<String>,
2270    pub registration_state: Option<String>,
2271    pub registration_policy: Option<String>,
2272    pub resource_type_count: u32,
2273    pub registered: bool,
2274}
2275
2276#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2277#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2278#[serde(rename_all = "camelCase")]
2279pub struct AzureResourceGroupHeartbeatData {
2280    pub status: AzureResourceGroupHeartbeatStatus,
2281    pub name: String,
2282    pub resource_id: Option<String>,
2283    pub location: Option<String>,
2284    pub provisioning_state: Option<String>,
2285    pub managed_tags: BTreeMap<String, String>,
2286}
2287
2288#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2289#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2290#[serde(rename_all = "camelCase")]
2291pub struct AzureResourceGroupHeartbeatStatus {
2292    pub health: ObservedHealth,
2293    pub lifecycle: ProviderLifecycleState,
2294    pub message: Option<String>,
2295    pub stale: bool,
2296    pub partial: bool,
2297    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2298}
2299
2300impl Default for AzureResourceGroupHeartbeatStatus {
2301    fn default() -> Self {
2302        Self {
2303            health: ObservedHealth::Healthy,
2304            lifecycle: ProviderLifecycleState::Running,
2305            message: None,
2306            stale: false,
2307            partial: false,
2308            collection_issues: vec![],
2309        }
2310    }
2311}
2312
2313#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2314#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2315#[serde(rename_all = "camelCase")]
2316pub struct AzureStorageAccountHeartbeatData {
2317    pub status: StorageHeartbeatStatus,
2318    pub name: String,
2319    pub resource_id: Option<String>,
2320    pub resource_group: Option<String>,
2321    pub location: Option<String>,
2322    pub kind: Option<String>,
2323    pub sku_name: Option<String>,
2324    pub sku_tier: Option<String>,
2325    pub provisioning_state: Option<String>,
2326    pub primary_endpoints: AzureStorageAccountEndpoints,
2327    pub secondary_endpoints: AzureStorageAccountEndpoints,
2328    pub public_network_access: Option<String>,
2329    pub allow_blob_public_access: Option<bool>,
2330    pub allow_shared_key_access: Option<bool>,
2331    pub minimum_tls_version: Option<String>,
2332    pub supports_https_traffic_only: Option<bool>,
2333    pub encryption_key_source: Option<String>,
2334    pub require_infrastructure_encryption: Option<bool>,
2335    pub network_default_action: Option<String>,
2336    pub network_bypass: Option<String>,
2337    pub network_ip_rule_count: Option<u32>,
2338    pub network_virtual_network_rule_count: Option<u32>,
2339    pub network_resource_access_rule_count: Option<u32>,
2340}
2341
2342#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
2343#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2344#[serde(rename_all = "camelCase")]
2345pub struct AzureStorageAccountEndpoints {
2346    pub blob: Option<String>,
2347    pub dfs: Option<String>,
2348    pub file: Option<String>,
2349    pub queue: Option<String>,
2350    pub table: Option<String>,
2351    pub web: Option<String>,
2352}
2353
2354#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2355#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2356#[serde(rename_all = "camelCase")]
2357pub struct AzureContainerAppsEnvironmentHeartbeatData {
2358    pub status: AzureContainerAppsEnvironmentHeartbeatStatus,
2359    pub name: String,
2360    pub resource_id: Option<String>,
2361    pub resource_group: Option<String>,
2362    pub location: Option<String>,
2363    pub kind: Option<String>,
2364    pub provisioning_state: Option<String>,
2365    pub default_domain: Option<String>,
2366    pub static_ip: Option<String>,
2367    pub custom_domain_verification_id: Option<String>,
2368    pub infrastructure_resource_group: Option<String>,
2369    pub event_stream_endpoint: Option<String>,
2370    pub zone_redundant: Option<bool>,
2371    pub workload_profile_count: u32,
2372    pub workload_profiles: Vec<AzureContainerAppsEnvironmentWorkloadProfile>,
2373}
2374
2375#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2376#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2377#[serde(rename_all = "camelCase")]
2378pub struct AzureContainerAppsEnvironmentHeartbeatStatus {
2379    pub health: ObservedHealth,
2380    pub lifecycle: ProviderLifecycleState,
2381    pub message: Option<String>,
2382    pub stale: bool,
2383    pub partial: bool,
2384    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2385}
2386
2387#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2388#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2389#[serde(rename_all = "camelCase")]
2390pub struct AzureContainerAppsEnvironmentWorkloadProfile {
2391    pub name: String,
2392    pub workload_profile_type: String,
2393    pub minimum_count: Option<i32>,
2394    pub maximum_count: Option<i32>,
2395}
2396
2397#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2398#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2399#[serde(rename_all = "camelCase")]
2400pub struct AzureServiceBusNamespaceHeartbeatData {
2401    pub status: QueueHeartbeatStatus,
2402    pub name: String,
2403    pub resource_id: Option<String>,
2404    pub resource_group: Option<String>,
2405    pub location: Option<String>,
2406    pub sku_name: Option<String>,
2407    pub sku_tier: Option<String>,
2408    pub sku_capacity: Option<i32>,
2409    pub namespace_status: Option<String>,
2410    pub provisioning_state: Option<String>,
2411    pub service_bus_endpoint: Option<String>,
2412    pub metric_id: Option<String>,
2413    pub public_network_access: Option<String>,
2414    pub disable_local_auth: Option<bool>,
2415    pub minimum_tls_version: Option<String>,
2416    pub premium_messaging_partitions: Option<i32>,
2417    pub private_endpoint_connection_count: u32,
2418    pub zone_redundant: Option<bool>,
2419    pub created_at: Option<String>,
2420    pub updated_at: Option<String>,
2421}
2422
2423#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2424#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2425#[serde(rename_all = "camelCase")]
2426pub struct NamedResourceDetail {
2427    pub name: String,
2428    pub region: Option<String>,
2429}
2430
2431#[cfg(test)]
2432mod tests {
2433    use super::*;
2434    use chrono::TimeZone as _;
2435    use serde_json::json;
2436
2437    fn observed_at() -> DateTime<Utc> {
2438        Utc.with_ymd_and_hms(2026, 5, 28, 10, 30, 0).unwrap()
2439    }
2440
2441    fn workload_status() -> WorkloadHeartbeatStatus {
2442        WorkloadHeartbeatStatus {
2443            health: ObservedHealth::Healthy,
2444            lifecycle: ProviderLifecycleState::Running,
2445            message: None,
2446            stale: false,
2447            partial: false,
2448            collection_issues: vec![],
2449        }
2450    }
2451
2452    fn workload_replicas() -> WorkloadReplicaStatus {
2453        WorkloadReplicaStatus {
2454            desired: Some(2),
2455            current: Some(2),
2456            ready: Some(1),
2457            available: Some(1),
2458            updated: Some(2),
2459            misscheduled: None,
2460        }
2461    }
2462
2463    fn heartbeat(data: ResourceHeartbeatData, resource_type: &str) -> ResourceHeartbeat {
2464        ResourceHeartbeat {
2465            deployment_id: Some("dep_123".to_string()),
2466            resource_id: "api".to_string(),
2467            resource_type: ResourceType::from(resource_type),
2468            controller_platform: Platform::Kubernetes,
2469            backend: HeartbeatBackend::Kubernetes,
2470            observed_at: observed_at(),
2471            data,
2472            raw: vec![RawHeartbeatSnippet {
2473                source: "kubernetes/apps/v1/deployments/api".to_string(),
2474                format: RawHeartbeatSnippetFormat::Json,
2475                collected_at: observed_at(),
2476                body: r#"{"readyReplicas":1}"#.to_string(),
2477                truncated: false,
2478            }],
2479        }
2480    }
2481
2482    #[test]
2483    fn resource_heartbeat_roundtrips_managed_resource_data() {
2484        let heartbeat_json = serde_json::to_value(heartbeat(
2485            ResourceHeartbeatData::Container(ContainerHeartbeatData::Kubernetes(
2486                KubernetesContainerHeartbeatData {
2487                    status: workload_status(),
2488                    namespace: "default".to_string(),
2489                    name: "api".to_string(),
2490                    workload_kind: KubernetesWorkloadKind::Deployment,
2491                    replicas: workload_replicas(),
2492                    restarts: None,
2493                    cpu: None,
2494                    memory: None,
2495                    workload: None,
2496                    pods: vec![],
2497                    events: vec![],
2498                },
2499            )),
2500            "container",
2501        ))
2502        .unwrap();
2503
2504        let parsed: ResourceHeartbeat = serde_json::from_value(heartbeat_json).unwrap();
2505        assert_eq!(parsed.resource_id, "api");
2506        assert!(matches!(
2507            parsed.data,
2508            ResourceHeartbeatData::Container(ContainerHeartbeatData::Kubernetes(_))
2509        ));
2510    }
2511
2512    #[test]
2513    fn local_daemon_heartbeat_defaults_missing_daemon_name() {
2514        let mut value =
2515            serde_json::to_value(DaemonHeartbeatData::Local(LocalDaemonHeartbeatData {
2516                status: workload_status(),
2517                daemon_name: "agent".to_string(),
2518                runtime_id: "local-agent".to_string(),
2519                pid: None,
2520                command_supported: false,
2521                image_path_present: true,
2522                restart_count: None,
2523                exit_reason: None,
2524                daemon_instance: None,
2525                events: vec![],
2526            }))
2527            .unwrap();
2528
2529        value.as_object_mut().unwrap().remove("daemonName");
2530
2531        let parsed: DaemonHeartbeatData = serde_json::from_value(value).unwrap();
2532        match parsed {
2533            DaemonHeartbeatData::Local(data) => assert_eq!(data.daemon_name, ""),
2534            other => panic!("expected local daemon heartbeat, got {other:?}"),
2535        }
2536    }
2537
2538    #[test]
2539    fn container_heartbeat_serializes_resource_first_data() {
2540        let heartbeat = heartbeat(
2541            ResourceHeartbeatData::Container(ContainerHeartbeatData::Kubernetes(
2542                KubernetesContainerHeartbeatData {
2543                    status: workload_status(),
2544                    namespace: "default".to_string(),
2545                    name: "api".to_string(),
2546                    workload_kind: KubernetesWorkloadKind::Deployment,
2547                    replicas: workload_replicas(),
2548                    restarts: Some(1),
2549                    cpu: Some(MetricSample {
2550                        value: 0.5,
2551                        unit: MetricUnit::Cores,
2552                    }),
2553                    memory: None,
2554                    workload: None,
2555                    pods: vec![],
2556                    events: vec![],
2557                },
2558            )),
2559            "container",
2560        );
2561
2562        let value = serde_json::to_value(&heartbeat).unwrap();
2563
2564        assert_eq!(value["resourceType"], "container");
2565        assert_eq!(value["data"]["resourceType"], "container");
2566        assert_eq!(value["data"]["data"]["backend"], "kubernetes");
2567        assert_eq!(value["raw"][0]["body"], r#"{"readyReplicas":1}"#);
2568        assert!(value.get("collection").is_none());
2569        assert!(value["data"]["data"].get("summary").is_none());
2570        assert!(value["data"]["data"].get("detail").is_none());
2571    }
2572
2573    #[test]
2574    fn representative_workload_data_has_stable_tags() {
2575        let daemon = serde_json::to_value(ResourceHeartbeatData::Daemon(
2576            DaemonHeartbeatData::Kubernetes(KubernetesDaemonHeartbeatData {
2577                status: workload_status(),
2578                namespace: "default".to_string(),
2579                name: "agent".to_string(),
2580                replicas: workload_replicas(),
2581                restarts: Some(0),
2582                command_supported: true,
2583                cpu: None,
2584                memory: None,
2585                workload: None,
2586                pods: vec![],
2587                events: vec![],
2588            }),
2589        ))
2590        .unwrap();
2591        let worker = serde_json::to_value(ResourceHeartbeatData::Worker(
2592            WorkerHeartbeatData::AwsLambda(AwsLambdaWorkerHeartbeatData {
2593                status: workload_status(),
2594                function_name: "handler".to_string(),
2595                runtime: Some("nodejs22.x".to_string()),
2596                package_type: None,
2597                memory_size_mb: None,
2598                timeout_seconds: None,
2599                version: None,
2600                revision_id: None,
2601                last_modified: None,
2602                state: None,
2603                state_reason: None,
2604                state_reason_code: None,
2605                last_update_status: None,
2606                last_update_status_reason: None,
2607                last_update_status_reason_code: None,
2608                code_sha256: None,
2609                layer_count: 0,
2610                function_url_auth_type: None,
2611                function_url_cors_present: false,
2612                trigger_count: 0,
2613            }),
2614        ))
2615        .unwrap();
2616
2617        assert_eq!(daemon["resourceType"], "daemon");
2618        assert_eq!(daemon["data"]["backend"], "kubernetes");
2619        assert_eq!(worker["resourceType"], "worker");
2620        assert_eq!(worker["data"]["backend"], "awsLambda");
2621    }
2622
2623    #[test]
2624    fn representative_cluster_and_data_variants_have_optional_counts() {
2625        let cluster = serde_json::to_value(ResourceHeartbeatData::KubernetesCluster(
2626            KubernetesClusterHeartbeatData {
2627                status: WorkloadHeartbeatStatus {
2628                    health: ObservedHealth::Healthy,
2629                    lifecycle: ProviderLifecycleState::Running,
2630                    message: None,
2631                    stale: false,
2632                    partial: false,
2633                    collection_issues: vec![],
2634                },
2635                node_counts: ObservedCounts {
2636                    desired: Some(3),
2637                    current: Some(3),
2638                    ready: None,
2639                },
2640                pod_counts: ObservedCounts {
2641                    desired: None,
2642                    current: Some(12),
2643                    ready: Some(11),
2644                },
2645                cpu: None,
2646                memory: None,
2647                name: "prod".to_string(),
2648                region: Some("us-east-1".to_string()),
2649                namespace: None,
2650                version: Some("1.33".to_string()),
2651                node_statuses: vec![],
2652                events: vec![],
2653            },
2654        ))
2655        .unwrap();
2656        let queue = serde_json::to_value(ResourceHeartbeatData::Queue(QueueHeartbeatData::AwsSqs(
2657            AwsSqsQueueHeartbeatData {
2658                status: QueueHeartbeatStatus {
2659                    health: ObservedHealth::Healthy,
2660                    lifecycle: ProviderLifecycleState::Running,
2661                    message: None,
2662                    stale: false,
2663                    partial: false,
2664                    collection_issues: vec![],
2665                },
2666                name: "jobs".to_string(),
2667                region: Some("us-east-1".to_string()),
2668                queue_url: Some("https://sqs.us-east-1.amazonaws.com/123/jobs".to_string()),
2669                queue_arn: Some("arn:aws:sqs:us-east-1:123:jobs".to_string()),
2670                visibility_timeout_seconds: Some(30),
2671                message_retention_period_seconds: Some(345600),
2672                delay_seconds: Some(0),
2673                receive_message_wait_time_seconds: Some(0),
2674                maximum_message_size: Some(262144),
2675                redrive_policy: None,
2676                redrive_allow_policy: None,
2677                fifo_queue: Some(false),
2678                content_based_deduplication: None,
2679                deduplication_scope: None,
2680                fifo_throughput_limit: None,
2681                sse_enabled: Some(false),
2682                kms_master_key_id: None,
2683                kms_data_key_reuse_period_seconds: None,
2684                sqs_managed_sse_enabled: Some(false),
2685                approximate_visible_messages: Some(42),
2686                approximate_in_flight_messages: Some(1),
2687                approximate_delayed_messages: Some(0),
2688                approximate_counts: true,
2689            },
2690        )))
2691        .unwrap();
2692        let storage = serde_json::to_value(ResourceHeartbeatData::Storage(
2693            StorageHeartbeatData::AwsS3(AwsS3StorageHeartbeatData {
2694                status: StorageHeartbeatStatus {
2695                    health: ObservedHealth::Healthy,
2696                    lifecycle: ProviderLifecycleState::Running,
2697                    message: None,
2698                    stale: false,
2699                    partial: false,
2700                    collection_issues: vec![],
2701                },
2702                name: "assets".to_string(),
2703                region: Some("us-east-1".to_string()),
2704                bucket_location: Some("us-east-1".to_string()),
2705                versioning_status: Some("Enabled".to_string()),
2706                versioning_enabled: Some(true),
2707                lifecycle_present: false,
2708                lifecycle_rule_count: Some(0),
2709                encryption_config_present: true,
2710                encryption_enabled: Some(true),
2711                public_access_block_present: true,
2712                block_public_acls: Some(true),
2713                ignore_public_acls: Some(true),
2714                block_public_policy: Some(true),
2715                restrict_public_buckets: Some(true),
2716                bucket_policy_present: Some(false),
2717                bucket_acl_present: Some(true),
2718            }),
2719        ))
2720        .unwrap();
2721        let gcp_storage = serde_json::to_value(ResourceHeartbeatData::Storage(
2722            StorageHeartbeatData::GcpCloudStorage(GcpCloudStorageHeartbeatData {
2723                status: StorageHeartbeatStatus {
2724                    health: ObservedHealth::Healthy,
2725                    lifecycle: ProviderLifecycleState::Running,
2726                    message: None,
2727                    stale: false,
2728                    partial: false,
2729                    collection_issues: vec![],
2730                },
2731                name: "assets".to_string(),
2732                bucket_id: Some("project/assets".to_string()),
2733                location: Some("US".to_string()),
2734                location_type: Some("multi-region".to_string()),
2735                storage_class: Some("STANDARD".to_string()),
2736                versioning_enabled: Some(true),
2737                lifecycle_present: false,
2738                lifecycle_rule_count: Some(0),
2739                retention_policy_effective_time: None,
2740                retention_policy_is_locked: None,
2741                retention_period: None,
2742                soft_delete_retention_duration_seconds: None,
2743                soft_delete_effective_time: None,
2744                uniform_bucket_level_access_enabled: Some(true),
2745                uniform_bucket_level_access_locked_time: None,
2746                public_access_prevention: Some("enforced".to_string()),
2747                encryption_config_present: true,
2748                default_kms_key_name: Some(
2749                    "projects/p/locations/l/keyRings/r/cryptoKeys/k".to_string(),
2750                ),
2751            }),
2752        ))
2753        .unwrap();
2754        let kv = serde_json::to_value(ResourceHeartbeatData::Kv(KvHeartbeatData::AwsDynamoDb(
2755            AwsDynamoDbKvHeartbeatData {
2756                status: KvHeartbeatStatus {
2757                    health: ObservedHealth::Healthy,
2758                    lifecycle: ProviderLifecycleState::Running,
2759                    message: None,
2760                    stale: false,
2761                    partial: false,
2762                    collection_issues: vec![],
2763                },
2764                name: "state".to_string(),
2765                region: Some("us-east-1".to_string()),
2766                table_arn: None,
2767                table_status: Some("ACTIVE".to_string()),
2768                billing_mode: Some("PAY_PER_REQUEST".to_string()),
2769                key_schema: vec![AwsDynamoDbKeySchemaElement {
2770                    attribute_name: "pk".to_string(),
2771                    key_type: "HASH".to_string(),
2772                }],
2773                global_secondary_index_count: Some(0),
2774                local_secondary_index_count: Some(0),
2775                item_count: None,
2776                table_size_bytes: None,
2777                stream_enabled: Some(false),
2778                stream_view_type: None,
2779                ttl_status: Some("ENABLED".to_string()),
2780                ttl_attribute_name: Some("ttl".to_string()),
2781                deletion_protection_enabled: Some(false),
2782                sse_status: Some("ENABLED".to_string()),
2783                sse_type: Some("KMS".to_string()),
2784                table_class: None,
2785                replica_count: Some(0),
2786                restore_in_progress: None,
2787            },
2788        )))
2789        .unwrap();
2790
2791        assert_eq!(cluster["resourceType"], "kubernetes-cluster");
2792        assert!(cluster["data"].get("summary").is_none());
2793        assert!(cluster["data"].get("detail").is_none());
2794        assert_eq!(cluster["data"]["name"], "prod");
2795        assert_eq!(queue["data"]["backend"], "awsSqs");
2796        assert_eq!(queue["data"]["approximateVisibleMessages"], 42);
2797        assert!(queue["data"].get("summary").is_none());
2798        assert_eq!(storage["data"]["backend"], "awsS3");
2799        assert!(storage["data"].get("summary").is_none());
2800        assert_eq!(gcp_storage["data"]["backend"], "gcpCloudStorage");
2801        assert_eq!(gcp_storage["data"]["publicAccessPrevention"], "enforced");
2802        assert_eq!(kv["data"]["backend"], "awsDynamoDb");
2803        assert!(kv["data"].get("summary").is_none());
2804        assert_eq!(kv["data"]["itemCount"], json!(null));
2805    }
2806
2807    #[test]
2808    fn ai_heartbeat_serializes_stable_tags() {
2809        let aws = serde_json::to_value(ResourceHeartbeatData::Ai(AiHeartbeatData::AwsBedrock(
2810            AwsBedrockAiHeartbeatData {
2811                status: AiHeartbeatStatus::default(),
2812                region: "us-east-1".to_string(),
2813            },
2814        )))
2815        .unwrap();
2816        let gcp =
2817            serde_json::to_value(ResourceHeartbeatData::Ai(AiHeartbeatData::GcpVertex(
2818                GcpVertexAiHeartbeatData {
2819                    status: AiHeartbeatStatus::default(),
2820                    project: "my-project".to_string(),
2821                    location: "us-central1".to_string(),
2822                },
2823            )))
2824            .unwrap();
2825        let azure =
2826            serde_json::to_value(ResourceHeartbeatData::Ai(AiHeartbeatData::AzureFoundry(
2827                AzureFoundryAiHeartbeatData {
2828                    status: AiHeartbeatStatus::default(),
2829                    account_name: "my-ai-account".to_string(),
2830                    endpoint: Some("https://my-ai-account.openai.azure.com/".to_string()),
2831                    resource_group: Some("my-rg".to_string()),
2832                    location: Some("eastus".to_string()),
2833                },
2834            )))
2835            .unwrap();
2836
2837        assert_eq!(aws["resourceType"], "ai");
2838        assert_eq!(aws["data"]["backend"], "awsBedrock");
2839        assert_eq!(aws["data"]["region"], "us-east-1");
2840        assert!(aws["data"].get("summary").is_none());
2841
2842        assert_eq!(gcp["resourceType"], "ai");
2843        assert_eq!(gcp["data"]["backend"], "gcpVertex");
2844        assert_eq!(gcp["data"]["project"], "my-project");
2845        assert_eq!(gcp["data"]["location"], "us-central1");
2846
2847        assert_eq!(azure["resourceType"], "ai");
2848        assert_eq!(azure["data"]["backend"], "azureFoundry");
2849        assert_eq!(azure["data"]["accountName"], "my-ai-account");
2850        assert_eq!(
2851            azure["data"]["endpoint"],
2852            "https://my-ai-account.openai.azure.com/"
2853        );
2854    }
2855}