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