Skip to main content

alien_core/
heartbeat.rs

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