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    /// Image this replica runs.
888    #[serde(default)]
889    pub image: Option<String>,
890}
891
892#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
893#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
894#[serde(rename_all = "camelCase")]
895pub struct KubernetesDaemonHeartbeatData {
896    pub status: WorkloadHeartbeatStatus,
897    pub namespace: String,
898    pub name: String,
899    pub replicas: WorkloadReplicaStatus,
900    pub restarts: Option<u32>,
901    pub command_supported: bool,
902    pub cpu: Option<MetricSample>,
903    pub memory: Option<MetricSample>,
904    pub workload: Option<KubernetesWorkloadStatus>,
905    pub pods: Vec<KubernetesPodRuntimeUnitStatus>,
906    pub events: Vec<KubernetesEventSnapshot>,
907}
908
909#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
910#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
911#[serde(rename_all = "camelCase")]
912pub struct LocalDaemonHeartbeatData {
913    pub status: WorkloadHeartbeatStatus,
914    #[serde(default)]
915    pub daemon_name: String,
916    pub runtime_id: String,
917    pub pid: Option<u32>,
918    pub command_supported: bool,
919    pub image_path_present: bool,
920    pub restart_count: Option<u32>,
921    pub exit_reason: Option<String>,
922    pub daemon_instance: Option<LocalRuntimeUnitStatus>,
923    pub events: Vec<LocalRuntimeEventSnapshot>,
924}
925
926#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
927#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
928#[serde(rename_all = "camelCase")]
929pub struct LocalRuntimeUnitStatus {
930    pub unit_id: String,
931    pub name: String,
932    pub kind: LocalRuntimeUnitKind,
933    pub ready: bool,
934    pub phase: Option<String>,
935    pub pid: Option<u32>,
936    pub restart_count: Option<u32>,
937    pub cpu: Option<MetricSample>,
938    pub memory: Option<MetricSample>,
939}
940
941#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
942#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
943#[serde(rename_all = "kebab-case")]
944pub enum LocalRuntimeUnitKind {
945    Container,
946    Process,
947    Daemon,
948}
949
950#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
951#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
952#[serde(rename_all = "camelCase")]
953pub enum KubernetesWorkloadKind {
954    Deployment,
955    StatefulSet,
956    DaemonSet,
957    ReplicaSet,
958    Pod,
959}
960
961#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
962#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
963#[serde(rename_all = "camelCase")]
964pub struct KubernetesWorkloadStatus {
965    pub desired_replicas: Option<u32>,
966    pub ready_replicas: Option<u32>,
967    pub available_replicas: Option<u32>,
968    pub updated_replicas: Option<u32>,
969    pub observed_generation: Option<i64>,
970    pub desired_generation: Option<i64>,
971    pub rollout_reason: Option<String>,
972    pub conditions: Vec<KubernetesWorkloadCondition>,
973}
974
975#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
976#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
977#[serde(rename_all = "camelCase")]
978pub struct KubernetesWorkloadCondition {
979    #[serde(rename = "type")]
980    pub condition_type: String,
981    pub status: String,
982    pub reason: Option<String>,
983    pub message: Option<String>,
984    pub last_transition_time: Option<DateTime<Utc>>,
985}
986
987#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
988#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
989#[serde(rename_all = "camelCase")]
990pub struct KubernetesPodRuntimeUnitStatus {
991    pub name: String,
992    pub uid: Option<String>,
993    pub phase: Option<String>,
994    pub ready: bool,
995    pub restart_count: u32,
996    pub waiting_reason: Option<String>,
997    pub terminated_reason: Option<String>,
998    pub node_name: Option<String>,
999    pub pod_ip: Option<String>,
1000    pub owner_references: Vec<KubernetesOwnerReference>,
1001    pub cpu: Option<MetricSample>,
1002    pub memory: Option<MetricSample>,
1003    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1004    pub containers: Vec<ContainerImageIdentity>,
1005}
1006
1007#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1008#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1009#[serde(rename_all = "camelCase")]
1010pub struct KubernetesOwnerReference {
1011    pub kind: String,
1012    pub name: String,
1013    pub uid: String,
1014    pub controller: bool,
1015}
1016
1017#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1018#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1019#[serde(tag = "backend", rename_all = "camelCase")]
1020pub enum ComputeClusterHeartbeatData {
1021    Aws(AwsComputeClusterHeartbeatData),
1022    Gcp(GcpComputeClusterHeartbeatData),
1023    Azure(AzureComputeClusterHeartbeatData),
1024    Machines(MachinesComputeClusterHeartbeatData),
1025    Local(LocalComputeClusterHeartbeatData),
1026}
1027
1028#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1029#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1030#[serde(rename_all = "camelCase")]
1031pub struct ComputeClusterHeartbeatStatus {
1032    pub health: ObservedHealth,
1033    pub lifecycle: ProviderLifecycleState,
1034    pub message: Option<String>,
1035    pub stale: bool,
1036    pub partial: bool,
1037    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1038}
1039
1040#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1041#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1042#[serde(rename_all = "camelCase")]
1043pub struct LocalComputeClusterHeartbeatData {
1044    pub status: ComputeClusterHeartbeatStatus,
1045    pub nodes: ObservedCounts,
1046    pub name: String,
1047    pub host_identifier: Option<String>,
1048    pub docker_available: bool,
1049    pub docker_version: Option<String>,
1050    pub docker_api_version: Option<String>,
1051    pub docker_os: Option<String>,
1052    pub docker_arch: Option<String>,
1053    pub network_name: Option<String>,
1054    pub network_available: bool,
1055    pub tracked_containers: Option<u32>,
1056    pub running_containers: Option<u32>,
1057}
1058
1059#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1060#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1061#[serde(rename_all = "camelCase")]
1062pub struct AwsComputeClusterHeartbeatData {
1063    pub status: ComputeClusterHeartbeatStatus,
1064    pub nodes: ObservedCounts,
1065    pub cpu: Option<MetricSample>,
1066    pub memory: Option<MetricSample>,
1067    pub name: String,
1068    pub region: Option<String>,
1069    pub backend_cluster_id: Option<String>,
1070    pub capacity_groups: Vec<ComputeCapacityGroupStatus>,
1071    pub provider_fleets: Vec<ProviderFleetStatus>,
1072}
1073
1074#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1075#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1076#[serde(rename_all = "camelCase")]
1077pub struct GcpComputeClusterHeartbeatData {
1078    pub status: ComputeClusterHeartbeatStatus,
1079    pub nodes: ObservedCounts,
1080    pub cpu: Option<MetricSample>,
1081    pub memory: Option<MetricSample>,
1082    pub name: String,
1083    pub region: Option<String>,
1084    pub backend_cluster_id: Option<String>,
1085    pub capacity_groups: Vec<ComputeCapacityGroupStatus>,
1086    pub provider_fleets: Vec<ProviderFleetStatus>,
1087}
1088
1089#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1090#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1091#[serde(rename_all = "camelCase")]
1092pub struct AzureComputeClusterHeartbeatData {
1093    pub status: ComputeClusterHeartbeatStatus,
1094    pub nodes: ObservedCounts,
1095    pub cpu: Option<MetricSample>,
1096    pub memory: Option<MetricSample>,
1097    pub name: String,
1098    pub region: Option<String>,
1099    pub backend_cluster_id: Option<String>,
1100    pub capacity_groups: Vec<ComputeCapacityGroupStatus>,
1101    pub provider_fleets: Vec<ProviderFleetStatus>,
1102}
1103
1104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1105#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1106#[serde(rename_all = "camelCase")]
1107pub struct MachinesComputeClusterHeartbeatData {
1108    pub status: ComputeClusterHeartbeatStatus,
1109    pub nodes: ObservedCounts,
1110    pub cpu: Option<MetricSample>,
1111    pub memory: Option<MetricSample>,
1112    pub name: String,
1113    pub backend_cluster_id: Option<String>,
1114    pub capacity_groups: Vec<ComputeCapacityGroupStatus>,
1115    pub machines: Vec<MachinesComputeMachineStatus>,
1116}
1117
1118#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1119#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1120#[serde(rename_all = "camelCase")]
1121pub struct MachinesComputeMachineStatus {
1122    pub machine_id: String,
1123    pub status: String,
1124    pub capacity_group: String,
1125    pub zone: String,
1126    pub public_ip: Option<String>,
1127    pub overlay_ip: Option<String>,
1128    pub last_heartbeat: String,
1129    pub horizond_version: Option<String>,
1130    pub replica_count: i64,
1131    pub cpu_cores: Option<f64>,
1132    pub memory_bytes: Option<i64>,
1133    pub drain_force: bool,
1134    pub drain_requested_at: Option<String>,
1135    pub drain_deadline_at: Option<String>,
1136    pub drained_at: Option<String>,
1137    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1138    pub drain_blockers: Vec<ComputeDrainBlocker>,
1139}
1140
1141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1142#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1143#[serde(rename_all = "camelCase")]
1144pub struct ComputeCapacityGroupStatus {
1145    pub group_id: String,
1146    pub current_machines: u32,
1147    pub desired_machines: u32,
1148    pub min_machines: Option<u32>,
1149    pub max_machines: Option<u32>,
1150    pub instance_type: Option<String>,
1151    pub recommendation: Option<ComputeCapacityRecommendation>,
1152    pub capacity_blocker: Option<ComputeCapacityBlocker>,
1153    #[serde(default, skip_serializing_if = "Option::is_none")]
1154    pub drain_progress: Option<ComputeDrainProgress>,
1155}
1156
1157#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1158#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1159#[serde(rename_all = "camelCase")]
1160pub enum ComputeCapacityBlockerCategory {
1161    Quota,
1162    Capacity,
1163    Allocation,
1164    Other,
1165}
1166
1167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1168#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1169#[serde(rename_all = "camelCase")]
1170pub struct ComputeCapacityBlocker {
1171    pub category: ComputeCapacityBlockerCategory,
1172    pub provider_code: Option<String>,
1173    pub message: String,
1174    pub provider_reference: Option<String>,
1175    pub observed_at: DateTime<Utc>,
1176}
1177
1178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1179#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1180#[serde(rename_all = "camelCase")]
1181pub struct ComputeCapacityRecommendation {
1182    pub desired_machines: u32,
1183    pub reason: Option<String>,
1184    pub utilization: Option<MetricSample>,
1185    pub unschedulable_replicas: Option<u32>,
1186}
1187
1188#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1189#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1190#[serde(rename_all = "camelCase")]
1191pub enum ComputeDrainProgressStatus {
1192    Draining,
1193    Drained,
1194    Terminating,
1195}
1196
1197#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1198#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1199#[serde(rename_all = "camelCase")]
1200pub struct ComputeDrainBlocker {
1201    pub workload_name: String,
1202    pub replica_id: String,
1203    pub scheduling_mode: String,
1204    pub state: String,
1205    pub reason: String,
1206}
1207
1208#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1209#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1210#[serde(rename_all = "camelCase")]
1211pub struct ComputeDrainProgress {
1212    pub machine_id: String,
1213    pub status: ComputeDrainProgressStatus,
1214    pub replica_count: i64,
1215    pub force: bool,
1216    pub stalled: bool,
1217    pub drain_requested_at: Option<String>,
1218    pub drain_deadline_at: Option<String>,
1219    pub drained_at: Option<String>,
1220    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1221    pub blockers: Vec<ComputeDrainBlocker>,
1222}
1223
1224#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1225#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1226#[serde(rename_all = "camelCase")]
1227pub struct ProviderFleetStatus {
1228    pub group_id: String,
1229    pub provider_id: String,
1230    pub location: Option<String>,
1231    pub current_machines: u32,
1232    pub desired_machines: u32,
1233}
1234
1235#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1236#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1237#[serde(rename_all = "camelCase")]
1238pub struct KubernetesClusterHeartbeatData {
1239    pub status: WorkloadHeartbeatStatus,
1240    pub node_counts: ObservedCounts,
1241    pub pod_counts: ObservedCounts,
1242    pub cpu: Option<MetricSample>,
1243    pub memory: Option<MetricSample>,
1244    pub name: String,
1245    pub region: Option<String>,
1246    pub namespace: Option<String>,
1247    pub version: Option<String>,
1248    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1249    pub node_statuses: Vec<KubernetesClusterNodeStatus>,
1250    pub events: Vec<KubernetesEventSnapshot>,
1251}
1252
1253#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1254#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1255#[serde(rename_all = "camelCase")]
1256pub struct KubernetesClusterNodeStatus {
1257    pub name: String,
1258    pub uid: Option<String>,
1259    pub ready: bool,
1260    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1261    pub conditions: Vec<KubernetesNodeConditionStatus>,
1262    pub roles: Vec<String>,
1263    pub labels: BTreeMap<String, String>,
1264    pub allocatable: KubernetesNodeResources,
1265    pub capacity: KubernetesNodeResources,
1266    pub usage: Option<KubernetesNodeUsage>,
1267    pub kubelet_version: Option<String>,
1268    pub container_runtime_version: Option<String>,
1269}
1270
1271#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1272#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1273#[serde(rename_all = "camelCase")]
1274pub struct KubernetesNodeConditionStatus {
1275    pub type_: String,
1276    pub status: String,
1277    pub reason: Option<String>,
1278    pub message: Option<String>,
1279}
1280
1281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1282#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1283#[serde(rename_all = "camelCase")]
1284pub struct KubernetesNodeResources {
1285    pub cpu: Option<MetricSample>,
1286    pub memory: Option<MetricSample>,
1287    pub pods: Option<u64>,
1288}
1289
1290#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1291#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1292#[serde(rename_all = "camelCase")]
1293pub struct KubernetesNodeUsage {
1294    pub cpu: Option<MetricSample>,
1295    pub memory: Option<MetricSample>,
1296}
1297
1298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1299#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1300#[serde(tag = "backend", rename_all = "camelCase")]
1301pub enum QueueHeartbeatData {
1302    AwsSqs(AwsSqsQueueHeartbeatData),
1303    GcpPubSub(GcpPubSubQueueHeartbeatData),
1304    AzureServiceBus(AzureServiceBusQueueHeartbeatData),
1305    Local(LocalQueueHeartbeatData),
1306}
1307
1308#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1309#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1310#[serde(rename_all = "camelCase")]
1311pub struct QueueHeartbeatStatus {
1312    pub health: ObservedHealth,
1313    pub lifecycle: ProviderLifecycleState,
1314    pub message: Option<String>,
1315    pub stale: bool,
1316    pub partial: bool,
1317    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1318}
1319
1320impl Default for QueueHeartbeatStatus {
1321    fn default() -> Self {
1322        Self {
1323            health: ObservedHealth::Unknown,
1324            lifecycle: ProviderLifecycleState::Unknown,
1325            message: None,
1326            stale: false,
1327            partial: false,
1328            collection_issues: vec![],
1329        }
1330    }
1331}
1332
1333#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1334#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1335#[serde(rename_all = "camelCase")]
1336pub struct LocalQueueHeartbeatData {
1337    pub status: QueueHeartbeatStatus,
1338    pub name: String,
1339    pub path: Option<String>,
1340    pub service_status: Option<String>,
1341}
1342
1343#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1344#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1345#[serde(rename_all = "camelCase")]
1346pub struct GcpPubSubQueueHeartbeatData {
1347    pub status: QueueHeartbeatStatus,
1348    pub topic_name: String,
1349    pub subscription_name: Option<String>,
1350    pub project_id: Option<String>,
1351    pub topic_full_name: Option<String>,
1352    pub subscription_full_name: Option<String>,
1353    pub endpoint: Option<String>,
1354    pub topic_labels: BTreeMap<String, String>,
1355    pub subscription_labels: BTreeMap<String, String>,
1356    pub message_storage_allowed_persistence_regions: Vec<String>,
1357    pub message_storage_enforce_in_transit: Option<bool>,
1358    pub kms_key_name: Option<String>,
1359    pub schema_name: Option<String>,
1360    pub schema_encoding: Option<String>,
1361    pub schema_first_revision_id: Option<String>,
1362    pub schema_last_revision_id: Option<String>,
1363    pub topic_message_retention_duration: Option<String>,
1364    pub topic_state: Option<String>,
1365    pub subscription_ack_deadline_seconds: Option<u32>,
1366    pub subscription_message_retention_duration: Option<String>,
1367    pub subscription_retain_acked_messages: Option<bool>,
1368    pub subscription_enable_message_ordering: Option<bool>,
1369    pub subscription_filter: Option<String>,
1370    pub subscription_detached: Option<bool>,
1371    pub subscription_state: Option<String>,
1372    pub subscription_push_config_present: Option<bool>,
1373    pub subscription_push_endpoint: Option<String>,
1374    pub subscription_push_attributes: BTreeMap<String, String>,
1375    pub subscription_push_oidc_service_account_email: Option<String>,
1376    pub subscription_push_oidc_audience: Option<String>,
1377    pub subscription_push_pubsub_wrapper_write_metadata: Option<bool>,
1378    pub subscription_push_no_wrapper_write_metadata: Option<bool>,
1379    pub subscription_dead_letter_topic: Option<String>,
1380    pub subscription_dead_letter_max_delivery_attempts: Option<u32>,
1381}
1382
1383#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1384#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1385#[serde(rename_all = "camelCase")]
1386pub struct AzureServiceBusQueueHeartbeatData {
1387    pub status: QueueHeartbeatStatus,
1388    pub name: String,
1389    pub namespace_name: String,
1390    pub resource_group: Option<String>,
1391    pub resource_id: Option<String>,
1392    pub endpoint: Option<String>,
1393    pub queue_status: Option<String>,
1394    pub lock_duration: Option<String>,
1395    pub max_delivery_count: Option<u32>,
1396    pub requires_duplicate_detection: Option<bool>,
1397    pub duplicate_detection_history_time_window: Option<String>,
1398    pub requires_session: Option<bool>,
1399    pub dead_lettering_on_message_expiration: Option<bool>,
1400    pub forward_dead_lettered_messages_to: Option<String>,
1401    pub forward_to: Option<String>,
1402    pub default_message_time_to_live: Option<String>,
1403    pub auto_delete_on_idle: Option<String>,
1404    pub enable_batched_operations: Option<bool>,
1405    pub enable_express: Option<bool>,
1406    pub enable_partitioning: Option<bool>,
1407    pub max_message_size_in_kilobytes: Option<u64>,
1408    pub max_size_in_megabytes: Option<u32>,
1409    pub message_count: Option<u64>,
1410    pub active_message_count: Option<u64>,
1411    pub dead_letter_message_count: Option<u64>,
1412    pub scheduled_message_count: Option<u64>,
1413    pub transfer_message_count: Option<u64>,
1414    pub transfer_dead_letter_message_count: Option<u64>,
1415    pub size_in_bytes: Option<u64>,
1416    pub accessed_at: Option<String>,
1417    pub created_at: Option<String>,
1418    pub updated_at: Option<String>,
1419}
1420
1421#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1422#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1423#[serde(rename_all = "camelCase")]
1424pub struct AwsSqsQueueHeartbeatData {
1425    pub status: QueueHeartbeatStatus,
1426    pub name: String,
1427    pub region: Option<String>,
1428    pub queue_url: Option<String>,
1429    pub queue_arn: Option<String>,
1430    pub visibility_timeout_seconds: Option<u32>,
1431    pub message_retention_period_seconds: Option<u32>,
1432    pub delay_seconds: Option<u32>,
1433    pub receive_message_wait_time_seconds: Option<u32>,
1434    pub maximum_message_size: Option<u32>,
1435    pub redrive_policy: Option<String>,
1436    pub redrive_allow_policy: Option<String>,
1437    pub fifo_queue: Option<bool>,
1438    pub content_based_deduplication: Option<bool>,
1439    pub deduplication_scope: Option<String>,
1440    pub fifo_throughput_limit: Option<String>,
1441    pub sse_enabled: Option<bool>,
1442    pub kms_master_key_id: Option<String>,
1443    pub kms_data_key_reuse_period_seconds: Option<u32>,
1444    pub sqs_managed_sse_enabled: Option<bool>,
1445    pub approximate_visible_messages: Option<u64>,
1446    pub approximate_in_flight_messages: Option<u64>,
1447    pub approximate_delayed_messages: Option<u64>,
1448    pub approximate_counts: bool,
1449}
1450
1451#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1452#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1453#[serde(tag = "backend", rename_all = "camelCase")]
1454pub enum KvHeartbeatData {
1455    AwsDynamoDb(AwsDynamoDbKvHeartbeatData),
1456    GcpFirestore(GcpFirestoreKvHeartbeatData),
1457    AzureTable(AzureTableKvHeartbeatData),
1458    Local(LocalKvHeartbeatData),
1459}
1460
1461#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1462#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1463#[serde(rename_all = "camelCase")]
1464pub struct KvHeartbeatStatus {
1465    pub health: ObservedHealth,
1466    pub lifecycle: ProviderLifecycleState,
1467    pub message: Option<String>,
1468    pub stale: bool,
1469    pub partial: bool,
1470    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1471}
1472
1473impl Default for KvHeartbeatStatus {
1474    fn default() -> Self {
1475        Self {
1476            health: ObservedHealth::Unknown,
1477            lifecycle: ProviderLifecycleState::Unknown,
1478            message: None,
1479            stale: false,
1480            partial: false,
1481            collection_issues: vec![],
1482        }
1483    }
1484}
1485
1486#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1487#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1488#[serde(rename_all = "camelCase")]
1489pub struct AwsDynamoDbKvHeartbeatData {
1490    pub status: KvHeartbeatStatus,
1491    pub name: String,
1492    pub region: Option<String>,
1493    pub table_arn: Option<String>,
1494    pub table_status: Option<String>,
1495    pub billing_mode: Option<String>,
1496    pub key_schema: Vec<AwsDynamoDbKeySchemaElement>,
1497    pub global_secondary_index_count: Option<u32>,
1498    pub local_secondary_index_count: Option<u32>,
1499    pub item_count: Option<u64>,
1500    pub table_size_bytes: Option<u64>,
1501    pub stream_enabled: Option<bool>,
1502    pub stream_view_type: Option<String>,
1503    pub ttl_status: Option<String>,
1504    pub ttl_attribute_name: Option<String>,
1505    pub deletion_protection_enabled: Option<bool>,
1506    pub sse_status: Option<String>,
1507    pub sse_type: Option<String>,
1508    pub table_class: Option<String>,
1509    pub replica_count: Option<u32>,
1510    pub restore_in_progress: Option<bool>,
1511}
1512
1513#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1514#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1515#[serde(rename_all = "camelCase")]
1516pub struct AwsDynamoDbKeySchemaElement {
1517    pub attribute_name: String,
1518    pub key_type: String,
1519}
1520
1521#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1522#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1523#[serde(rename_all = "camelCase")]
1524pub struct GcpFirestoreKvHeartbeatData {
1525    pub status: KvHeartbeatStatus,
1526    pub database_name: String,
1527    pub project_id: Option<String>,
1528    pub endpoint: Option<String>,
1529    pub location_id: Option<String>,
1530    pub database_type: Option<String>,
1531    pub concurrency_mode: Option<String>,
1532    pub app_engine_integration_mode: Option<String>,
1533    pub delete_protection_state: Option<String>,
1534    pub point_in_time_recovery_enablement: Option<String>,
1535    pub version_retention_period: Option<String>,
1536    pub earliest_version_time: Option<String>,
1537    pub create_time: Option<String>,
1538    pub update_time: Option<String>,
1539    pub delete_time: Option<String>,
1540    pub database_edition: Option<String>,
1541    pub cmek_enabled: bool,
1542    pub source_info_present: bool,
1543}
1544
1545#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1546#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1547#[serde(rename_all = "camelCase")]
1548pub struct AzureTableKvHeartbeatData {
1549    pub status: KvHeartbeatStatus,
1550    pub table_name: String,
1551    pub storage_account_name: String,
1552    pub resource_group: Option<String>,
1553    pub endpoint: Option<String>,
1554    pub storage_account_resource_id: Option<String>,
1555    pub storage_account_location: Option<String>,
1556    pub storage_account_kind: Option<String>,
1557    pub storage_account_provisioning_state: Option<String>,
1558    pub storage_account_primary_status: Option<String>,
1559    pub table_exists: bool,
1560    pub signed_identifier_count: Option<u32>,
1561}
1562
1563#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1564#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1565#[serde(rename_all = "camelCase")]
1566pub struct LocalKvHeartbeatData {
1567    pub status: KvHeartbeatStatus,
1568    pub name: String,
1569    pub path: String,
1570    pub path_exists: bool,
1571    pub is_directory: Option<bool>,
1572    pub cloud_metadata_supported: bool,
1573}
1574
1575#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1576#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1577#[serde(tag = "backend", rename_all = "camelCase")]
1578pub enum PostgresHeartbeatData {
1579    /// AWS Aurora Serverless v2 backend.
1580    Aurora(AuroraPostgresHeartbeatData),
1581    /// GCP Cloud SQL backend.
1582    CloudSql(GcpCloudSqlPostgresHeartbeatData),
1583    /// Azure Flexible Server backend.
1584    FlexibleServer(AzureFlexibleServerPostgresHeartbeatData),
1585    /// Local embedded Postgres backend.
1586    Local(LocalPostgresHeartbeatData),
1587}
1588
1589#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1590#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1591#[serde(rename_all = "camelCase")]
1592pub struct PostgresHeartbeatStatus {
1593    pub health: ObservedHealth,
1594    pub lifecycle: ProviderLifecycleState,
1595    pub message: Option<String>,
1596    pub stale: bool,
1597    pub partial: bool,
1598    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1599}
1600
1601impl Default for PostgresHeartbeatStatus {
1602    fn default() -> Self {
1603        Self {
1604            health: ObservedHealth::Unknown,
1605            lifecycle: ProviderLifecycleState::Unknown,
1606            message: None,
1607            stale: false,
1608            partial: false,
1609            collection_issues: vec![],
1610        }
1611    }
1612}
1613
1614/// Content-free telemetry about what a sandbox resource is running.
1615///
1616/// Never anything from inside a sandbox. A controller reaches only the cloud's management APIs,
1617/// and the whole point of the resource is that the control plane cannot see what runs in it.
1618#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1619#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1620#[serde(tag = "backend", rename_all = "camelCase")]
1621pub enum SandboxHeartbeatData {
1622    AwsMicrovm(AwsMicrovmSandboxHeartbeatData),
1623    AzureSandboxGroup(AzureSandboxGroupHeartbeatData),
1624    GcpAgentPlatform(GcpAgentPlatformSandboxHeartbeatData),
1625    KubernetesPods(KubernetesSandboxHeartbeatData),
1626    Local(LocalSandboxHeartbeatData),
1627}
1628
1629#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1630#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1631#[serde(rename_all = "camelCase")]
1632pub struct SandboxHeartbeatStatus {
1633    pub health: ObservedHealth,
1634    pub lifecycle: ProviderLifecycleState,
1635    pub message: Option<String>,
1636    pub stale: bool,
1637    pub partial: bool,
1638    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1639}
1640
1641impl Default for SandboxHeartbeatStatus {
1642    fn default() -> Self {
1643        Self {
1644            health: ObservedHealth::Healthy,
1645            lifecycle: ProviderLifecycleState::Running,
1646            message: None,
1647            stale: false,
1648            partial: false,
1649            collection_issues: Vec::new(),
1650        }
1651    }
1652}
1653
1654/// AWS: the image a sandbox runs from, and the lifecycle state AWS reports for it.
1655#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1656#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1657#[serde(rename_all = "camelCase")]
1658pub struct AwsMicrovmSandboxHeartbeatData {
1659    pub status: SandboxHeartbeatStatus,
1660    /// Image the sandboxes belong to.
1661    pub image_identifier: String,
1662    /// The image's own lifecycle state, which is where AWS surfaces base-image deprecation.
1663    ///
1664    /// No sandbox count sits beside it: counting means `lambda:ListMicrovms`, which authorizes
1665    /// against no resource type and so cannot be granted without an account-wide reach the
1666    /// permission sets refuse. A field only an over-broad grant could fill is a field whose
1667    /// implementer ships AccessDenied into a customer's account.
1668    pub image_state: Option<String>,
1669}
1670
1671/// Azure: the sandbox group's ARM state. The data plane has no list operation, so a sandbox count
1672/// is not available here.
1673#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1674#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1675#[serde(rename_all = "camelCase")]
1676pub struct AzureSandboxGroupHeartbeatData {
1677    pub status: SandboxHeartbeatStatus,
1678    pub sandbox_group: String,
1679    pub provisioning_state: Option<String>,
1680}
1681
1682/// GCP: the Agent Platform template sandboxes are cut from, and the engine it hangs under.
1683///
1684/// No sandbox count: that needs `aiplatform.sandboxEnvironments.list`, which only the management
1685/// permission set holds. No template state either — emission is gated on reading it `ACTIVE`,
1686/// which is what `status` already says.
1687#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1688#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1689#[serde(rename_all = "camelCase")]
1690pub struct GcpAgentPlatformSandboxHeartbeatData {
1691    pub status: SandboxHeartbeatStatus,
1692    /// Reasoning engine the template hangs under, without which the template id names nothing.
1693    pub engine: String,
1694    /// The template sandboxes are currently cut from.
1695    pub template_id: String,
1696}
1697
1698/// Kubernetes: pods carrying the sandbox label, in the deployment's namespace.
1699#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1700#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1701#[serde(rename_all = "camelCase")]
1702pub struct KubernetesSandboxHeartbeatData {
1703    pub status: SandboxHeartbeatStatus,
1704    pub namespace: String,
1705    pub active_sessions: u32,
1706    /// Claimed but unused pods waiting in the pool.
1707    pub idle_pods: u32,
1708}
1709
1710/// Local: containers Docker still holds for this sandbox.
1711#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1712#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1713#[serde(rename_all = "camelCase")]
1714pub struct LocalSandboxHeartbeatData {
1715    pub status: SandboxHeartbeatStatus,
1716    pub active_sessions: u32,
1717    /// Whether the loopback route is serving in this process. False after a manager restart until
1718    /// the next tick rebinds it.
1719    pub route_serving: bool,
1720}
1721
1722#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1723#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1724#[serde(tag = "backend", rename_all = "camelCase")]
1725pub enum AiHeartbeatData {
1726    AwsBedrock(AwsBedrockAiHeartbeatData),
1727    GcpVertex(GcpVertexAiHeartbeatData),
1728    AzureFoundry(AzureFoundryAiHeartbeatData),
1729    External(ExternalAiHeartbeatData),
1730}
1731
1732#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1733#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1734#[serde(rename_all = "camelCase")]
1735pub struct AiHeartbeatStatus {
1736    pub health: ObservedHealth,
1737    pub lifecycle: ProviderLifecycleState,
1738    pub message: Option<String>,
1739    pub stale: bool,
1740    pub partial: bool,
1741    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1742}
1743
1744impl Default for AiHeartbeatStatus {
1745    fn default() -> Self {
1746        // A configured AI binding is not proof that its models are usable.
1747        Self {
1748            health: ObservedHealth::Unknown,
1749            lifecycle: ProviderLifecycleState::Unknown,
1750            message: None,
1751            stale: false,
1752            partial: false,
1753            collection_issues: vec![],
1754        }
1755    }
1756}
1757
1758/// Provider control plane used to observe model availability without invoking
1759/// a model, spending customer quota, or accepting provider terms.
1760#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1761#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1762#[serde(rename_all = "kebab-case")]
1763pub enum AiAvailabilitySource {
1764    AwsBedrock,
1765    GcpVertex,
1766    AzureFoundry,
1767    Anthropic,
1768}
1769
1770#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1771#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1772#[serde(rename_all = "kebab-case")]
1773pub enum AiModelAvailability {
1774    Available,
1775    Blocked,
1776    Unknown,
1777}
1778
1779#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1780#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1781#[serde(rename_all = "kebab-case")]
1782pub enum AiAvailabilityBlocker {
1783    AgreementRequired,
1784    EntitlementRequired,
1785    ModelActivationRequired,
1786    DeploymentRequired,
1787    QuotaConfigurationRequired,
1788    RegionUnavailable,
1789    AccessDenied,
1790    ObservationFailed,
1791}
1792
1793#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1794#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1795#[serde(rename_all = "kebab-case")]
1796pub enum AiAccessTest {
1797    Verified,
1798    Failed,
1799    NotChecked,
1800}
1801
1802#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1803#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1804#[serde(rename_all = "camelCase")]
1805pub struct AiModelAvailabilityObservation {
1806    pub public_model_id: String,
1807    pub client_apis: Vec<ClientApi>,
1808    pub availability: AiModelAvailability,
1809    pub blockers: Vec<AiAvailabilityBlocker>,
1810    pub access_test: AiAccessTest,
1811    #[serde(skip_serializing_if = "Option::is_none")]
1812    pub tested_at: Option<DateTime<Utc>>,
1813    #[serde(skip_serializing_if = "Option::is_none")]
1814    pub error_code: Option<String>,
1815}
1816
1817#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1818#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1819#[serde(rename_all = "camelCase")]
1820pub struct AiAvailabilityObservation {
1821    pub source: AiAvailabilitySource,
1822    pub catalog_revision: String,
1823    #[serde(skip_serializing_if = "Option::is_none")]
1824    pub location: Option<String>,
1825    pub models: Vec<AiModelAvailabilityObservation>,
1826}
1827
1828impl AiAvailabilityObservation {
1829    /// Conservative observation used until a provider control-plane read has
1830    /// completed. It never claims that a configured model is usable.
1831    pub fn unobserved(
1832        source: AiAvailabilitySource,
1833        platform: Platform,
1834        location: Option<String>,
1835    ) -> Self {
1836        Self {
1837            source,
1838            catalog_revision: crate::ai_catalog::AI_CATALOG_REVISION.to_string(),
1839            location,
1840            models: crate::ai_catalog::models_for(platform)
1841                .into_iter()
1842                .map(|model| AiModelAvailabilityObservation {
1843                    public_model_id: model.public_id.to_string(),
1844                    client_apis: ClientApi::ALL.to_vec(),
1845                    availability: AiModelAvailability::Unknown,
1846                    blockers: vec![AiAvailabilityBlocker::ObservationFailed],
1847                    access_test: AiAccessTest::NotChecked,
1848                    tested_at: None,
1849                    error_code: None,
1850                })
1851                .collect(),
1852        }
1853    }
1854}
1855
1856#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1857#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1858#[serde(rename_all = "camelCase")]
1859pub struct LocalPostgresHeartbeatData {
1860    pub status: PostgresHeartbeatStatus,
1861    pub name: String,
1862    pub port: Option<u16>,
1863    pub version: String,
1864    pub process_running: bool,
1865}
1866
1867#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1868#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1869#[serde(rename_all = "camelCase")]
1870pub struct AuroraPostgresHeartbeatData {
1871    pub status: PostgresHeartbeatStatus,
1872    pub cluster_identifier: String,
1873    #[serde(skip_serializing_if = "Option::is_none")]
1874    pub engine_version: Option<String>,
1875    #[serde(skip_serializing_if = "Option::is_none")]
1876    pub endpoint: Option<String>,
1877    /// Latest sampled `ServerlessDatabaseCapacity` (ACU).
1878    #[serde(skip_serializing_if = "Option::is_none")]
1879    pub serverless_capacity: Option<f64>,
1880    /// True when a `minCapacity: 0` instance has not reached 0 ACU over the observation
1881    /// window — it is silently paying always-on prices (auto-pause verification).
1882    pub never_pauses: bool,
1883}
1884
1885#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1886#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1887#[serde(rename_all = "camelCase")]
1888pub struct GcpCloudSqlPostgresHeartbeatData {
1889    pub status: PostgresHeartbeatStatus,
1890    pub instance_name: String,
1891    #[serde(skip_serializing_if = "Option::is_none")]
1892    pub state: Option<String>,
1893    #[serde(skip_serializing_if = "Option::is_none")]
1894    pub database_version: Option<String>,
1895}
1896
1897#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1898#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1899#[serde(rename_all = "camelCase")]
1900pub struct AzureFlexibleServerPostgresHeartbeatData {
1901    pub status: PostgresHeartbeatStatus,
1902    pub server_name: String,
1903    #[serde(skip_serializing_if = "Option::is_none")]
1904    pub state: Option<String>,
1905    #[serde(skip_serializing_if = "Option::is_none")]
1906    pub version: Option<String>,
1907}
1908
1909#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1910#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1911#[serde(rename_all = "camelCase")]
1912pub struct AwsBedrockAiHeartbeatData {
1913    pub status: AiHeartbeatStatus,
1914    pub region: String,
1915    pub availability: AiAvailabilityObservation,
1916}
1917
1918#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1919#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1920#[serde(rename_all = "camelCase")]
1921pub struct GcpVertexAiHeartbeatData {
1922    pub status: AiHeartbeatStatus,
1923    pub project: String,
1924    pub location: String,
1925    pub availability: AiAvailabilityObservation,
1926}
1927
1928#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1929#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1930#[serde(rename_all = "camelCase")]
1931pub struct AzureFoundryAiHeartbeatData {
1932    pub status: AiHeartbeatStatus,
1933    pub account_name: String,
1934    pub endpoint: Option<String>,
1935    pub resource_group: Option<String>,
1936    pub location: Option<String>,
1937    pub availability: AiAvailabilityObservation,
1938}
1939
1940#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1941#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1942#[serde(rename_all = "camelCase")]
1943pub struct ExternalAiHeartbeatData {
1944    pub status: AiHeartbeatStatus,
1945    /// The BYO-key provider serving this binding (e.g. "openai"). Used on the Local
1946    /// platform, where the app brings its own provider key instead of an ambient cloud.
1947    pub provider: String,
1948    #[serde(skip_serializing_if = "Option::is_none")]
1949    pub availability: Option<AiAvailabilityObservation>,
1950}
1951
1952#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1953#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1954#[serde(tag = "backend", rename_all = "camelCase")]
1955pub enum VaultHeartbeatData {
1956    AwsParameterStore(AwsParameterStoreVaultHeartbeatData),
1957    GcpSecretManager(GcpSecretManagerVaultHeartbeatData),
1958    AzureKeyVault(AzureKeyVaultHeartbeatData),
1959    KubernetesSecret(KubernetesSecretVaultHeartbeatData),
1960    Local(LocalVaultHeartbeatData),
1961}
1962
1963#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1964#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1965#[serde(rename_all = "camelCase")]
1966pub struct VaultHeartbeatStatus {
1967    pub health: ObservedHealth,
1968    pub lifecycle: ProviderLifecycleState,
1969    pub message: Option<String>,
1970    pub stale: bool,
1971    pub partial: bool,
1972    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1973}
1974
1975impl Default for VaultHeartbeatStatus {
1976    fn default() -> Self {
1977        Self {
1978            health: ObservedHealth::Healthy,
1979            lifecycle: ProviderLifecycleState::Running,
1980            message: None,
1981            stale: false,
1982            partial: false,
1983            collection_issues: vec![],
1984        }
1985    }
1986}
1987
1988#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1989#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1990#[serde(rename_all = "camelCase")]
1991pub struct AwsParameterStoreVaultHeartbeatData {
1992    pub status: VaultHeartbeatStatus,
1993    pub account_id: String,
1994    pub region: String,
1995    pub prefix: String,
1996    pub parameter_metadata_sampled: bool,
1997    pub sampled_parameter_count: Option<u32>,
1998    pub sampled_secure_string_count: Option<u32>,
1999    pub sampled_string_count: Option<u32>,
2000    pub sampled_string_list_count: Option<u32>,
2001    pub sampled_advanced_tier_count: Option<u32>,
2002    pub sampled_kms_key_metadata_present_count: Option<u32>,
2003    pub latest_modified_at: Option<DateTime<Utc>>,
2004    pub has_more_parameters: Option<bool>,
2005}
2006
2007#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2008#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2009#[serde(rename_all = "camelCase")]
2010pub struct GcpSecretManagerVaultHeartbeatData {
2011    pub status: VaultHeartbeatStatus,
2012    pub project_id: String,
2013    pub location: String,
2014    pub prefix: String,
2015    pub secret_metadata_listed: bool,
2016}
2017
2018#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2019#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2020#[serde(rename_all = "camelCase")]
2021pub struct AzureKeyVaultHeartbeatData {
2022    pub status: VaultHeartbeatStatus,
2023    pub name: String,
2024    pub resource_group: Option<String>,
2025    pub resource_id: Option<String>,
2026    pub location: Option<String>,
2027    pub vault_uri: Option<String>,
2028    pub provisioning_state: Option<String>,
2029    pub sku_family: Option<String>,
2030    pub sku_name: Option<String>,
2031    pub soft_delete_enabled: bool,
2032    pub soft_delete_retention_days: i32,
2033    pub purge_protection_enabled: Option<bool>,
2034    pub rbac_authorization_enabled: bool,
2035    pub public_network_access: String,
2036    pub access_policy_count: u32,
2037    pub private_endpoint_connection_count: u32,
2038    pub secret_metadata_listed: bool,
2039}
2040
2041#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2042#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2043#[serde(rename_all = "camelCase")]
2044pub struct KubernetesSecretVaultHeartbeatData {
2045    pub status: VaultHeartbeatStatus,
2046    pub namespace: String,
2047    pub prefix: String,
2048    pub secret_metadata_listed: bool,
2049}
2050
2051#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2052#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2053#[serde(rename_all = "camelCase")]
2054pub struct LocalVaultHeartbeatData {
2055    pub status: VaultHeartbeatStatus,
2056    pub path: String,
2057    pub path_exists: bool,
2058    pub is_directory: Option<bool>,
2059    pub readonly: Option<bool>,
2060    pub modified_at: Option<DateTime<Utc>>,
2061    pub secret_metadata_listed: bool,
2062}
2063
2064#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2065#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2066#[serde(tag = "backend", rename_all = "camelCase")]
2067pub enum ServiceAccountHeartbeatData {
2068    AwsIamRole(AwsIamRoleServiceAccountHeartbeatData),
2069    GcpServiceAccount(GcpServiceAccountHeartbeatData),
2070    AzureManagedIdentity(AzureManagedIdentityServiceAccountHeartbeatData),
2071    Local(LocalServiceAccountHeartbeatData),
2072}
2073
2074#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2075#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2076#[serde(rename_all = "camelCase")]
2077pub struct ServiceAccountHeartbeatStatus {
2078    pub health: ObservedHealth,
2079    pub lifecycle: ProviderLifecycleState,
2080    pub message: Option<String>,
2081    pub stale: bool,
2082    pub partial: bool,
2083    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2084}
2085
2086#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2087#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2088#[serde(rename_all = "camelCase")]
2089pub struct AwsIamRoleServiceAccountHeartbeatData {
2090    pub status: ServiceAccountHeartbeatStatus,
2091    pub role_name: String,
2092    pub role_arn: String,
2093    pub role_id: String,
2094    pub path: String,
2095    pub create_date: String,
2096    pub description: Option<String>,
2097    pub max_session_duration: Option<i32>,
2098    pub assume_role_policy_present: bool,
2099    pub permissions_boundary_type: Option<String>,
2100    pub permissions_boundary_arn: Option<String>,
2101    pub tag_count: u32,
2102    pub managed_tag_count: u32,
2103    pub attached_policy_count: u32,
2104    pub attached_policy_names: Vec<String>,
2105    pub inline_policy_count: u32,
2106    pub inline_policy_names: Vec<String>,
2107    pub stack_permissions_applied: bool,
2108    pub last_used_date: Option<String>,
2109    pub last_used_region: Option<String>,
2110}
2111
2112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2113#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2114#[serde(rename_all = "camelCase")]
2115pub struct GcpServiceAccountHeartbeatData {
2116    pub status: ServiceAccountHeartbeatStatus,
2117    pub name: Option<String>,
2118    pub project_id: Option<String>,
2119    pub unique_id: Option<String>,
2120    pub email: String,
2121    pub display_name: Option<String>,
2122    pub description: Option<String>,
2123    pub oauth2_client_id: Option<String>,
2124    pub disabled: Option<bool>,
2125    pub etag: Option<String>,
2126    pub project_binding_count: u32,
2127    pub project_roles: Vec<String>,
2128    pub service_account_binding_count: u32,
2129    pub service_account_roles: Vec<String>,
2130}
2131
2132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2133#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2134#[serde(rename_all = "camelCase")]
2135pub struct AzureManagedIdentityServiceAccountHeartbeatData {
2136    pub status: ServiceAccountHeartbeatStatus,
2137    pub name: String,
2138    pub resource_id: String,
2139    pub resource_group: String,
2140    pub location: String,
2141    pub type_: Option<String>,
2142    pub client_id: Option<String>,
2143    pub principal_id: Option<String>,
2144    pub tenant_id: Option<String>,
2145    pub isolation_scope: Option<String>,
2146    pub managed_tag_count: u32,
2147    pub role_assignment_count: u32,
2148    pub role_assignment_ids: Vec<String>,
2149    pub custom_role_definition_count: u32,
2150    pub custom_role_definition_ids: Vec<String>,
2151    pub stack_permissions_applied: bool,
2152}
2153
2154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2155#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2156#[serde(rename_all = "camelCase")]
2157pub struct LocalServiceAccountHeartbeatData {
2158    pub status: ServiceAccountHeartbeatStatus,
2159    pub identity: String,
2160    pub configured: bool,
2161}
2162
2163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2164#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2165#[serde(tag = "backend", rename_all = "camelCase")]
2166pub enum NetworkHeartbeatData {
2167    AwsVpc(AwsVpcNetworkHeartbeatData),
2168    GcpVpc(GcpVpcNetworkHeartbeatData),
2169    AzureVnet(AzureVnetNetworkHeartbeatData),
2170}
2171
2172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2173#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2174#[serde(rename_all = "camelCase")]
2175pub struct NetworkHeartbeatStatus {
2176    pub health: ObservedHealth,
2177    pub lifecycle: ProviderLifecycleState,
2178    pub message: Option<String>,
2179    pub stale: bool,
2180    pub partial: bool,
2181    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2182}
2183
2184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2185#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2186#[serde(rename_all = "camelCase")]
2187pub struct AwsVpcNetworkHeartbeatData {
2188    pub status: NetworkHeartbeatStatus,
2189    pub vpc_id: Option<String>,
2190    pub vpc_state: Option<String>,
2191    pub cidr_block: Option<String>,
2192    pub public_subnet_ids: Vec<String>,
2193    pub private_subnet_ids: Vec<String>,
2194    pub availability_zones: Vec<String>,
2195    pub internet_gateway_id: Option<String>,
2196    pub nat_gateway_id: Option<String>,
2197    pub route_table_count: u32,
2198    pub security_group_id: Option<String>,
2199    pub is_byo_vpc: bool,
2200}
2201
2202#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2203#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2204#[serde(rename_all = "camelCase")]
2205pub struct GcpVpcNetworkHeartbeatData {
2206    pub status: NetworkHeartbeatStatus,
2207    pub network_name: Option<String>,
2208    pub network_self_link: Option<String>,
2209    pub subnetwork_name: Option<String>,
2210    pub subnetwork_self_link: Option<String>,
2211    pub region: Option<String>,
2212    pub cidr_block: Option<String>,
2213    pub router_name: Option<String>,
2214    pub cloud_nat_name: Option<String>,
2215    pub firewall_name: Option<String>,
2216    pub is_byo_vpc: bool,
2217}
2218
2219#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2220#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2221#[serde(rename_all = "camelCase")]
2222pub struct AzureVnetNetworkHeartbeatData {
2223    pub status: NetworkHeartbeatStatus,
2224    pub vnet_name: Option<String>,
2225    pub vnet_resource_id: Option<String>,
2226    pub resource_group: Option<String>,
2227    pub location: Option<String>,
2228    pub cidr_block: Option<String>,
2229    pub public_subnet_name: Option<String>,
2230    pub private_subnet_name: Option<String>,
2231    pub application_gateway_subnet_name: Option<String>,
2232    pub private_endpoint_subnet_name: Option<String>,
2233    pub nat_gateway_id: Option<String>,
2234    pub public_ip_id: Option<String>,
2235    pub nsg_id: Option<String>,
2236    pub is_byo_vnet: bool,
2237    pub last_byo_vnet_verification_error_code: Option<String>,
2238}
2239
2240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2241#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2242#[serde(tag = "backend", rename_all = "camelCase")]
2243pub enum RemoteStackManagementHeartbeatData {
2244    AwsIamRole(AwsRemoteStackManagementHeartbeatData),
2245    GcpServiceAccount(GcpRemoteStackManagementHeartbeatData),
2246    AzureManagedIdentity(AzureRemoteStackManagementHeartbeatData),
2247}
2248
2249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2250#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2251#[serde(rename_all = "camelCase")]
2252pub struct RemoteStackManagementHeartbeatStatus {
2253    pub health: ObservedHealth,
2254    pub lifecycle: ProviderLifecycleState,
2255    pub message: Option<String>,
2256    pub stale: bool,
2257    pub partial: bool,
2258    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2259}
2260
2261#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2262#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2263#[serde(rename_all = "camelCase")]
2264pub struct AwsRemoteStackManagementHeartbeatData {
2265    pub status: RemoteStackManagementHeartbeatStatus,
2266    pub role_name: Option<String>,
2267    pub role_arn: Option<String>,
2268    pub management_permissions_applied: bool,
2269}
2270
2271#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2272#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2273#[serde(rename_all = "camelCase")]
2274pub struct GcpRemoteStackManagementHeartbeatData {
2275    pub status: RemoteStackManagementHeartbeatStatus,
2276    pub service_account_email: Option<String>,
2277    pub service_account_unique_id: Option<String>,
2278    pub role_bound: bool,
2279    pub impersonation_granted: bool,
2280}
2281
2282#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2283#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2284#[serde(rename_all = "camelCase")]
2285pub struct AzureRemoteStackManagementHeartbeatData {
2286    pub status: RemoteStackManagementHeartbeatStatus,
2287    pub uami_resource_id: Option<String>,
2288    pub uami_client_id: Option<String>,
2289    pub uami_principal_id: Option<String>,
2290    pub tenant_id: Option<String>,
2291    pub fic_name: Option<String>,
2292    pub role_definition_id: Option<String>,
2293    pub role_assignment_ids: Vec<String>,
2294}
2295
2296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2297#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2298#[serde(tag = "backend", rename_all = "camelCase")]
2299pub enum ArtifactRegistryHeartbeatData {
2300    AwsEcr(AwsEcrArtifactRegistryHeartbeatData),
2301    GcpArtifactRegistry(GcpArtifactRegistryHeartbeatData),
2302    AzureContainerRegistry(AzureContainerRegistryHeartbeatData),
2303    Local(LocalArtifactRegistryHeartbeatData),
2304}
2305
2306#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2307#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2308#[serde(rename_all = "camelCase")]
2309pub struct ArtifactRegistryHeartbeatStatus {
2310    pub health: ObservedHealth,
2311    pub lifecycle: ProviderLifecycleState,
2312    pub message: Option<String>,
2313    pub stale: bool,
2314    pub partial: bool,
2315    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2316}
2317
2318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2319#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2320#[serde(rename_all = "camelCase")]
2321pub struct AwsEcrArtifactRegistryHeartbeatData {
2322    pub status: ArtifactRegistryHeartbeatStatus,
2323    pub registry_id: String,
2324    pub region: String,
2325    pub registry_uri: String,
2326    pub repository_prefix: String,
2327    pub pull_role_arn: Option<String>,
2328    pub push_role_arn: Option<String>,
2329    pub repository_count: u32,
2330    pub repositories_truncated: bool,
2331    pub repositories: Vec<AwsEcrRepositoryHeartbeatData>,
2332}
2333
2334#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2335#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2336#[serde(rename_all = "camelCase")]
2337pub struct AwsEcrRepositoryHeartbeatData {
2338    pub repository_arn: String,
2339    pub registry_id: String,
2340    pub repository_name: String,
2341    pub repository_uri: String,
2342    pub created_at: f64,
2343    pub image_tag_mutability: Option<String>,
2344    pub scan_on_push: Option<bool>,
2345    pub encryption_type: Option<String>,
2346    pub kms_key_present: bool,
2347}
2348
2349#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2350#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2351#[serde(rename_all = "camelCase")]
2352pub struct GcpArtifactRegistryHeartbeatData {
2353    pub status: ArtifactRegistryHeartbeatStatus,
2354    pub project_id: String,
2355    pub location: String,
2356    pub repository_id: String,
2357    pub name: Option<String>,
2358    pub format: Option<String>,
2359    pub mode: Option<String>,
2360    pub description: Option<String>,
2361    pub label_count: u32,
2362    pub cleanup_policy_count: u32,
2363    pub cleanup_policy_dry_run: Option<bool>,
2364    pub kms_key_name_present: bool,
2365    pub size_bytes: Option<String>,
2366    pub satisfies_pzs: Option<bool>,
2367    pub create_time: Option<String>,
2368    pub update_time: Option<String>,
2369    pub iam_policy_etag_present: bool,
2370    pub iam_binding_count: u32,
2371    pub iam_roles: Vec<String>,
2372    pub pull_service_account_email: Option<String>,
2373    pub push_service_account_email: Option<String>,
2374}
2375
2376#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2377#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2378#[serde(rename_all = "camelCase")]
2379pub struct AzureContainerRegistryHeartbeatData {
2380    pub status: ArtifactRegistryHeartbeatStatus,
2381    pub name: String,
2382    pub resource_id: Option<String>,
2383    pub resource_group: String,
2384    pub location: String,
2385    pub type_: Option<String>,
2386    pub login_server: Option<String>,
2387    pub sku_name: String,
2388    pub sku_tier: Option<String>,
2389    pub provisioning_state: Option<String>,
2390    pub admin_user_enabled: bool,
2391    pub anonymous_pull_enabled: bool,
2392    pub public_network_access: String,
2393    pub network_rule_bypass_options: String,
2394    pub network_rule_default_action: Option<String>,
2395    pub ip_rule_count: u32,
2396    pub encryption_status: Option<String>,
2397    pub encryption_key_vault_uri_present: bool,
2398    pub encryption_key_identifier_present: bool,
2399    pub policies_present: bool,
2400    pub policy_count: u32,
2401    pub private_endpoint_connection_count: u32,
2402    pub data_endpoint_enabled: Option<bool>,
2403    pub data_endpoint_host_names: Vec<String>,
2404    pub zone_redundancy: String,
2405    pub creation_date: Option<String>,
2406    pub managed_tag_count: u32,
2407}
2408
2409#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2410#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2411#[serde(rename_all = "camelCase")]
2412pub struct LocalArtifactRegistryHeartbeatData {
2413    pub status: ArtifactRegistryHeartbeatStatus,
2414    pub registry_url: String,
2415    pub reachable: bool,
2416}
2417
2418#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2419#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2420#[serde(tag = "backend", rename_all = "camelCase")]
2421pub enum BuildHeartbeatData {
2422    AwsCodeBuild(AwsCodeBuildHeartbeatData),
2423    GcpCloudBuild(GcpCloudBuildHeartbeatData),
2424    AzureContainerApps(AzureContainerAppsBuildHeartbeatData),
2425    KubernetesJob(KubernetesBuildHeartbeatData),
2426}
2427
2428#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2429#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2430#[serde(rename_all = "camelCase")]
2431pub struct BuildHeartbeatStatus {
2432    pub health: ObservedHealth,
2433    pub lifecycle: ProviderLifecycleState,
2434    pub message: Option<String>,
2435    pub stale: bool,
2436    pub partial: bool,
2437    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2438}
2439
2440#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2441#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2442#[serde(rename_all = "camelCase")]
2443pub struct AwsCodeBuildHeartbeatData {
2444    pub status: BuildHeartbeatStatus,
2445    pub project_name: String,
2446    pub project_arn: Option<String>,
2447    pub description: Option<String>,
2448    pub source_type: Option<String>,
2449    pub artifacts_type: Option<String>,
2450    pub artifacts_encryption_disabled: Option<bool>,
2451    pub environment_type: Option<String>,
2452    pub environment_image: Option<String>,
2453    pub compute_type: Option<String>,
2454    pub image_pull_credentials_type: Option<String>,
2455    pub privileged_mode: Option<bool>,
2456    pub environment_variable_count: u32,
2457    pub service_role_present: bool,
2458    pub encryption_key_present: bool,
2459    pub cloud_watch_logs_status: Option<String>,
2460    pub s3_logs_status: Option<String>,
2461    pub timeout_in_minutes: Option<i32>,
2462    pub queued_timeout_in_minutes: Option<i32>,
2463    pub created: Option<f64>,
2464    pub last_modified: Option<f64>,
2465}
2466
2467#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2468#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2469#[serde(rename_all = "camelCase")]
2470pub struct GcpCloudBuildHeartbeatData {
2471    pub status: BuildHeartbeatStatus,
2472    pub project_id: String,
2473    pub location: String,
2474    pub build_config_id: String,
2475    pub service_account: Option<String>,
2476    pub environment_variable_count: u32,
2477}
2478
2479#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2480#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2481#[serde(rename_all = "camelCase")]
2482pub struct AzureContainerAppsBuildHeartbeatData {
2483    pub status: BuildHeartbeatStatus,
2484    pub managed_environment_id: String,
2485    pub resource_group_name: String,
2486    pub managed_identity_id: Option<String>,
2487    pub resource_prefix: Option<String>,
2488    pub environment_variable_count: u32,
2489}
2490
2491#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2492#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2493#[serde(rename_all = "camelCase")]
2494pub struct KubernetesBuildHeartbeatData {
2495    pub status: BuildHeartbeatStatus,
2496    pub job_name: String,
2497    pub namespace: String,
2498    pub active: Option<i32>,
2499    pub succeeded: Option<i32>,
2500    pub failed: Option<i32>,
2501    pub start_time: Option<DateTime<Utc>>,
2502    pub completion_time: Option<DateTime<Utc>>,
2503    pub condition_count: u32,
2504    pub image_digest: Option<String>,
2505    pub events: Vec<KubernetesEventSnapshot>,
2506}
2507
2508#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2509#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2510#[serde(tag = "backend", rename_all = "camelCase")]
2511pub enum ServiceActivationHeartbeatData {
2512    GcpServiceUsage(GcpServiceUsageActivationHeartbeatData),
2513    AzureResourceProvider(AzureResourceProviderActivationHeartbeatData),
2514}
2515
2516#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2517#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2518#[serde(rename_all = "camelCase")]
2519pub struct ServiceActivationHeartbeatStatus {
2520    pub health: ObservedHealth,
2521    pub lifecycle: ProviderLifecycleState,
2522    pub message: Option<String>,
2523    pub stale: bool,
2524    pub partial: bool,
2525    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2526}
2527
2528impl Default for ServiceActivationHeartbeatStatus {
2529    fn default() -> Self {
2530        Self {
2531            health: ObservedHealth::Healthy,
2532            lifecycle: ProviderLifecycleState::Running,
2533            message: None,
2534            stale: false,
2535            partial: false,
2536            collection_issues: vec![],
2537        }
2538    }
2539}
2540
2541#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2542#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2543#[serde(rename_all = "camelCase")]
2544pub struct GcpServiceUsageActivationHeartbeatData {
2545    pub status: ServiceActivationHeartbeatStatus,
2546    pub project_id: String,
2547    pub service_name: String,
2548    pub service_resource_name: Option<String>,
2549    pub title: Option<String>,
2550    pub state: Option<String>,
2551    pub enabled: bool,
2552    pub last_operation_name: Option<String>,
2553}
2554
2555#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2556#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2557#[serde(rename_all = "camelCase")]
2558pub struct AzureResourceProviderActivationHeartbeatData {
2559    pub status: ServiceActivationHeartbeatStatus,
2560    pub namespace: String,
2561    pub provider_id: Option<String>,
2562    pub registration_state: Option<String>,
2563    pub registration_policy: Option<String>,
2564    pub resource_type_count: u32,
2565    pub registered: bool,
2566}
2567
2568#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2569#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2570#[serde(rename_all = "camelCase")]
2571pub struct AzureResourceGroupHeartbeatData {
2572    pub status: AzureResourceGroupHeartbeatStatus,
2573    pub name: String,
2574    pub resource_id: Option<String>,
2575    pub location: Option<String>,
2576    pub provisioning_state: Option<String>,
2577    pub managed_tags: BTreeMap<String, String>,
2578}
2579
2580#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2581#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2582#[serde(rename_all = "camelCase")]
2583pub struct AzureResourceGroupHeartbeatStatus {
2584    pub health: ObservedHealth,
2585    pub lifecycle: ProviderLifecycleState,
2586    pub message: Option<String>,
2587    pub stale: bool,
2588    pub partial: bool,
2589    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2590}
2591
2592impl Default for AzureResourceGroupHeartbeatStatus {
2593    fn default() -> Self {
2594        Self {
2595            health: ObservedHealth::Healthy,
2596            lifecycle: ProviderLifecycleState::Running,
2597            message: None,
2598            stale: false,
2599            partial: false,
2600            collection_issues: vec![],
2601        }
2602    }
2603}
2604
2605#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2606#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2607#[serde(rename_all = "camelCase")]
2608pub struct AzureStorageAccountHeartbeatData {
2609    pub status: StorageHeartbeatStatus,
2610    pub name: String,
2611    pub resource_id: Option<String>,
2612    pub resource_group: Option<String>,
2613    pub location: Option<String>,
2614    pub kind: Option<String>,
2615    pub sku_name: Option<String>,
2616    pub sku_tier: Option<String>,
2617    pub provisioning_state: Option<String>,
2618    pub primary_endpoints: AzureStorageAccountEndpoints,
2619    pub secondary_endpoints: AzureStorageAccountEndpoints,
2620    pub public_network_access: Option<String>,
2621    pub allow_blob_public_access: Option<bool>,
2622    pub allow_shared_key_access: Option<bool>,
2623    pub minimum_tls_version: Option<String>,
2624    pub supports_https_traffic_only: Option<bool>,
2625    pub encryption_key_source: Option<String>,
2626    pub require_infrastructure_encryption: Option<bool>,
2627    pub network_default_action: Option<String>,
2628    pub network_bypass: Option<String>,
2629    pub network_ip_rule_count: Option<u32>,
2630    pub network_virtual_network_rule_count: Option<u32>,
2631    pub network_resource_access_rule_count: Option<u32>,
2632}
2633
2634#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
2635#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2636#[serde(rename_all = "camelCase")]
2637pub struct AzureStorageAccountEndpoints {
2638    pub blob: Option<String>,
2639    pub dfs: Option<String>,
2640    pub file: Option<String>,
2641    pub queue: Option<String>,
2642    pub table: Option<String>,
2643    pub web: Option<String>,
2644}
2645
2646#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2647#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2648#[serde(rename_all = "camelCase")]
2649pub struct AzureContainerAppsEnvironmentHeartbeatData {
2650    pub status: AzureContainerAppsEnvironmentHeartbeatStatus,
2651    pub name: String,
2652    pub resource_id: Option<String>,
2653    pub resource_group: Option<String>,
2654    pub location: Option<String>,
2655    pub kind: Option<String>,
2656    pub provisioning_state: Option<String>,
2657    pub default_domain: Option<String>,
2658    pub static_ip: Option<String>,
2659    pub custom_domain_verification_id: Option<String>,
2660    pub infrastructure_resource_group: Option<String>,
2661    pub event_stream_endpoint: Option<String>,
2662    pub zone_redundant: Option<bool>,
2663    pub workload_profile_count: u32,
2664    pub workload_profiles: Vec<AzureContainerAppsEnvironmentWorkloadProfile>,
2665}
2666
2667#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2668#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2669#[serde(rename_all = "camelCase")]
2670pub struct AzureContainerAppsEnvironmentHeartbeatStatus {
2671    pub health: ObservedHealth,
2672    pub lifecycle: ProviderLifecycleState,
2673    pub message: Option<String>,
2674    pub stale: bool,
2675    pub partial: bool,
2676    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2677}
2678
2679#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2680#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2681#[serde(rename_all = "camelCase")]
2682pub struct AzureContainerAppsEnvironmentWorkloadProfile {
2683    pub name: String,
2684    pub workload_profile_type: String,
2685    pub minimum_count: Option<i32>,
2686    pub maximum_count: Option<i32>,
2687}
2688
2689#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2690#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2691#[serde(rename_all = "camelCase")]
2692pub struct AzureServiceBusNamespaceHeartbeatData {
2693    pub status: QueueHeartbeatStatus,
2694    pub name: String,
2695    pub resource_id: Option<String>,
2696    pub resource_group: Option<String>,
2697    pub location: Option<String>,
2698    pub sku_name: Option<String>,
2699    pub sku_tier: Option<String>,
2700    pub sku_capacity: Option<i32>,
2701    pub namespace_status: Option<String>,
2702    pub provisioning_state: Option<String>,
2703    pub service_bus_endpoint: Option<String>,
2704    pub metric_id: Option<String>,
2705    pub public_network_access: Option<String>,
2706    pub disable_local_auth: Option<bool>,
2707    pub minimum_tls_version: Option<String>,
2708    pub premium_messaging_partitions: Option<i32>,
2709    pub private_endpoint_connection_count: u32,
2710    pub zone_redundant: Option<bool>,
2711    pub created_at: Option<String>,
2712    pub updated_at: Option<String>,
2713}
2714
2715#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2716#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2717#[serde(rename_all = "camelCase")]
2718pub struct NamedResourceDetail {
2719    pub name: String,
2720    pub region: Option<String>,
2721}
2722
2723#[cfg(test)]
2724mod tests {
2725    use super::*;
2726    use chrono::TimeZone as _;
2727    use serde_json::json;
2728
2729    fn observed_at() -> DateTime<Utc> {
2730        Utc.with_ymd_and_hms(2026, 5, 28, 10, 30, 0).unwrap()
2731    }
2732
2733    fn workload_status() -> WorkloadHeartbeatStatus {
2734        WorkloadHeartbeatStatus {
2735            health: ObservedHealth::Healthy,
2736            lifecycle: ProviderLifecycleState::Running,
2737            message: None,
2738            stale: false,
2739            partial: false,
2740            collection_issues: vec![],
2741        }
2742    }
2743
2744    fn workload_replicas() -> WorkloadReplicaStatus {
2745        WorkloadReplicaStatus {
2746            desired: Some(2),
2747            current: Some(2),
2748            ready: Some(1),
2749            available: Some(1),
2750            updated: Some(2),
2751            misscheduled: None,
2752        }
2753    }
2754
2755    fn heartbeat(data: ResourceHeartbeatData, resource_type: &str) -> ResourceHeartbeat {
2756        ResourceHeartbeat {
2757            deployment_id: Some("dep_123".to_string()),
2758            resource_id: "api".to_string(),
2759            resource_type: ResourceType::from(resource_type),
2760            controller_platform: Platform::Kubernetes,
2761            backend: HeartbeatBackend::Kubernetes,
2762            observed_at: observed_at(),
2763            data,
2764            raw: vec![RawHeartbeatSnippet {
2765                source: "kubernetes/apps/v1/deployments/api".to_string(),
2766                format: RawHeartbeatSnippetFormat::Json,
2767                collected_at: observed_at(),
2768                body: r#"{"readyReplicas":1}"#.to_string(),
2769                truncated: false,
2770            }],
2771        }
2772    }
2773
2774    #[test]
2775    fn resource_heartbeat_roundtrips_managed_resource_data() {
2776        let heartbeat_json = serde_json::to_value(heartbeat(
2777            ResourceHeartbeatData::Container(ContainerHeartbeatData::Kubernetes(
2778                KubernetesContainerHeartbeatData {
2779                    status: workload_status(),
2780                    namespace: "default".to_string(),
2781                    name: "api".to_string(),
2782                    workload_kind: KubernetesWorkloadKind::Deployment,
2783                    replicas: workload_replicas(),
2784                    restarts: None,
2785                    cpu: None,
2786                    memory: None,
2787                    workload: None,
2788                    pods: vec![],
2789                    events: vec![],
2790                },
2791            )),
2792            "container",
2793        ))
2794        .unwrap();
2795
2796        let parsed: ResourceHeartbeat = serde_json::from_value(heartbeat_json).unwrap();
2797        assert_eq!(parsed.resource_id, "api");
2798        assert!(matches!(
2799            parsed.data,
2800            ResourceHeartbeatData::Container(ContainerHeartbeatData::Kubernetes(_))
2801        ));
2802    }
2803
2804    #[test]
2805    fn local_daemon_heartbeat_defaults_missing_daemon_name() {
2806        let mut value =
2807            serde_json::to_value(DaemonHeartbeatData::Local(LocalDaemonHeartbeatData {
2808                status: workload_status(),
2809                daemon_name: "agent".to_string(),
2810                runtime_id: "local-agent".to_string(),
2811                pid: None,
2812                command_supported: false,
2813                image_path_present: true,
2814                restart_count: None,
2815                exit_reason: None,
2816                daemon_instance: None,
2817                events: vec![],
2818            }))
2819            .unwrap();
2820
2821        value.as_object_mut().unwrap().remove("daemonName");
2822
2823        let parsed: DaemonHeartbeatData = serde_json::from_value(value).unwrap();
2824        match parsed {
2825            DaemonHeartbeatData::Local(data) => assert_eq!(data.daemon_name, ""),
2826            other => panic!("expected local daemon heartbeat, got {other:?}"),
2827        }
2828    }
2829
2830    #[test]
2831    fn container_heartbeat_serializes_resource_first_data() {
2832        let heartbeat = heartbeat(
2833            ResourceHeartbeatData::Container(ContainerHeartbeatData::Kubernetes(
2834                KubernetesContainerHeartbeatData {
2835                    status: workload_status(),
2836                    namespace: "default".to_string(),
2837                    name: "api".to_string(),
2838                    workload_kind: KubernetesWorkloadKind::Deployment,
2839                    replicas: workload_replicas(),
2840                    restarts: Some(1),
2841                    cpu: Some(MetricSample {
2842                        value: 0.5,
2843                        unit: MetricUnit::Cores,
2844                    }),
2845                    memory: None,
2846                    workload: None,
2847                    pods: vec![],
2848                    events: vec![],
2849                },
2850            )),
2851            "container",
2852        );
2853
2854        let value = serde_json::to_value(&heartbeat).unwrap();
2855
2856        assert_eq!(value["resourceType"], "container");
2857        assert_eq!(value["data"]["resourceType"], "container");
2858        assert_eq!(value["data"]["data"]["backend"], "kubernetes");
2859        assert_eq!(value["raw"][0]["body"], r#"{"readyReplicas":1}"#);
2860        assert!(value.get("collection").is_none());
2861        assert!(value["data"]["data"].get("summary").is_none());
2862        assert!(value["data"]["data"].get("detail").is_none());
2863    }
2864
2865    #[test]
2866    fn representative_workload_data_has_stable_tags() {
2867        let daemon = serde_json::to_value(ResourceHeartbeatData::Daemon(
2868            DaemonHeartbeatData::Kubernetes(KubernetesDaemonHeartbeatData {
2869                status: workload_status(),
2870                namespace: "default".to_string(),
2871                name: "agent".to_string(),
2872                replicas: workload_replicas(),
2873                restarts: Some(0),
2874                command_supported: true,
2875                cpu: None,
2876                memory: None,
2877                workload: None,
2878                pods: vec![],
2879                events: vec![],
2880            }),
2881        ))
2882        .unwrap();
2883        let worker = serde_json::to_value(ResourceHeartbeatData::Worker(
2884            WorkerHeartbeatData::AwsLambda(AwsLambdaWorkerHeartbeatData {
2885                status: workload_status(),
2886                function_name: "handler".to_string(),
2887                runtime: Some("nodejs22.x".to_string()),
2888                package_type: None,
2889                memory_size_mb: None,
2890                timeout_seconds: None,
2891                version: None,
2892                revision_id: None,
2893                last_modified: None,
2894                state: None,
2895                state_reason: None,
2896                state_reason_code: None,
2897                last_update_status: None,
2898                last_update_status_reason: None,
2899                last_update_status_reason_code: None,
2900                code_sha256: None,
2901                layer_count: 0,
2902                function_url_auth_type: None,
2903                function_url_cors_present: false,
2904                trigger_count: 0,
2905            }),
2906        ))
2907        .unwrap();
2908
2909        assert_eq!(daemon["resourceType"], "daemon");
2910        assert_eq!(daemon["data"]["backend"], "kubernetes");
2911        assert_eq!(worker["resourceType"], "worker");
2912        assert_eq!(worker["data"]["backend"], "awsLambda");
2913    }
2914
2915    /// A variant that compiles can still be unreadable on the wire: the transport round-trips every
2916    /// heartbeat through the generated manager type, which matches on the tag string alone.
2917    #[test]
2918    fn gcp_sandbox_heartbeat_keeps_its_wire_tag_and_camel_case_names() {
2919        let data = ResourceHeartbeatData::Sandbox(SandboxHeartbeatData::GcpAgentPlatform(
2920            GcpAgentPlatformSandboxHeartbeatData {
2921                status: SandboxHeartbeatStatus::default(),
2922                engine: "eng".to_string(),
2923                template_id: "tpl1".to_string(),
2924            },
2925        ));
2926
2927        let value = serde_json::to_value(&data).unwrap();
2928        assert_eq!(
2929            value,
2930            json!({
2931                "resourceType": "sandbox",
2932                "data": {
2933                    "backend": "gcpAgentPlatform",
2934                    "status": {
2935                        "health": "healthy",
2936                        "lifecycle": "running",
2937                        "message": null,
2938                        "stale": false,
2939                        "partial": false,
2940                        "collectionIssues": []
2941                    },
2942                    "engine": "eng",
2943                    "templateId": "tpl1"
2944                }
2945            })
2946        );
2947
2948        let parsed: ResourceHeartbeatData = serde_json::from_value(value).unwrap();
2949        assert_eq!(parsed, data);
2950    }
2951
2952    #[test]
2953    fn representative_cluster_and_data_variants_have_optional_counts() {
2954        let cluster = serde_json::to_value(ResourceHeartbeatData::KubernetesCluster(
2955            KubernetesClusterHeartbeatData {
2956                status: WorkloadHeartbeatStatus {
2957                    health: ObservedHealth::Healthy,
2958                    lifecycle: ProviderLifecycleState::Running,
2959                    message: None,
2960                    stale: false,
2961                    partial: false,
2962                    collection_issues: vec![],
2963                },
2964                node_counts: ObservedCounts {
2965                    desired: Some(3),
2966                    current: Some(3),
2967                    ready: None,
2968                },
2969                pod_counts: ObservedCounts {
2970                    desired: None,
2971                    current: Some(12),
2972                    ready: Some(11),
2973                },
2974                cpu: None,
2975                memory: None,
2976                name: "prod".to_string(),
2977                region: Some("us-east-1".to_string()),
2978                namespace: None,
2979                version: Some("1.33".to_string()),
2980                node_statuses: vec![],
2981                events: vec![],
2982            },
2983        ))
2984        .unwrap();
2985        let queue = serde_json::to_value(ResourceHeartbeatData::Queue(QueueHeartbeatData::AwsSqs(
2986            AwsSqsQueueHeartbeatData {
2987                status: QueueHeartbeatStatus {
2988                    health: ObservedHealth::Healthy,
2989                    lifecycle: ProviderLifecycleState::Running,
2990                    message: None,
2991                    stale: false,
2992                    partial: false,
2993                    collection_issues: vec![],
2994                },
2995                name: "jobs".to_string(),
2996                region: Some("us-east-1".to_string()),
2997                queue_url: Some("https://sqs.us-east-1.amazonaws.com/123/jobs".to_string()),
2998                queue_arn: Some("arn:aws:sqs:us-east-1:123:jobs".to_string()),
2999                visibility_timeout_seconds: Some(30),
3000                message_retention_period_seconds: Some(345600),
3001                delay_seconds: Some(0),
3002                receive_message_wait_time_seconds: Some(0),
3003                maximum_message_size: Some(262144),
3004                redrive_policy: None,
3005                redrive_allow_policy: None,
3006                fifo_queue: Some(false),
3007                content_based_deduplication: None,
3008                deduplication_scope: None,
3009                fifo_throughput_limit: None,
3010                sse_enabled: Some(false),
3011                kms_master_key_id: None,
3012                kms_data_key_reuse_period_seconds: None,
3013                sqs_managed_sse_enabled: Some(false),
3014                approximate_visible_messages: Some(42),
3015                approximate_in_flight_messages: Some(1),
3016                approximate_delayed_messages: Some(0),
3017                approximate_counts: true,
3018            },
3019        )))
3020        .unwrap();
3021        let storage = serde_json::to_value(ResourceHeartbeatData::Storage(
3022            StorageHeartbeatData::AwsS3(AwsS3StorageHeartbeatData {
3023                status: StorageHeartbeatStatus {
3024                    health: ObservedHealth::Healthy,
3025                    lifecycle: ProviderLifecycleState::Running,
3026                    message: None,
3027                    stale: false,
3028                    partial: false,
3029                    collection_issues: vec![],
3030                },
3031                name: "assets".to_string(),
3032                region: Some("us-east-1".to_string()),
3033                bucket_location: Some("us-east-1".to_string()),
3034                versioning_status: Some("Enabled".to_string()),
3035                versioning_enabled: Some(true),
3036                lifecycle_present: false,
3037                lifecycle_rule_count: Some(0),
3038                encryption_config_present: true,
3039                encryption_enabled: Some(true),
3040                public_access_block_present: true,
3041                block_public_acls: Some(true),
3042                ignore_public_acls: Some(true),
3043                block_public_policy: Some(true),
3044                restrict_public_buckets: Some(true),
3045                bucket_policy_present: Some(false),
3046                bucket_acl_present: Some(true),
3047            }),
3048        ))
3049        .unwrap();
3050        let gcp_storage = serde_json::to_value(ResourceHeartbeatData::Storage(
3051            StorageHeartbeatData::GcpCloudStorage(GcpCloudStorageHeartbeatData {
3052                status: StorageHeartbeatStatus {
3053                    health: ObservedHealth::Healthy,
3054                    lifecycle: ProviderLifecycleState::Running,
3055                    message: None,
3056                    stale: false,
3057                    partial: false,
3058                    collection_issues: vec![],
3059                },
3060                name: "assets".to_string(),
3061                bucket_id: Some("project/assets".to_string()),
3062                location: Some("US".to_string()),
3063                location_type: Some("multi-region".to_string()),
3064                storage_class: Some("STANDARD".to_string()),
3065                versioning_enabled: Some(true),
3066                lifecycle_present: false,
3067                lifecycle_rule_count: Some(0),
3068                retention_policy_effective_time: None,
3069                retention_policy_is_locked: None,
3070                retention_period: None,
3071                soft_delete_retention_duration_seconds: None,
3072                soft_delete_effective_time: None,
3073                uniform_bucket_level_access_enabled: Some(true),
3074                uniform_bucket_level_access_locked_time: None,
3075                public_access_prevention: Some("enforced".to_string()),
3076                encryption_config_present: true,
3077                default_kms_key_name: Some(
3078                    "projects/p/locations/l/keyRings/r/cryptoKeys/k".to_string(),
3079                ),
3080            }),
3081        ))
3082        .unwrap();
3083        let kv = serde_json::to_value(ResourceHeartbeatData::Kv(KvHeartbeatData::AwsDynamoDb(
3084            AwsDynamoDbKvHeartbeatData {
3085                status: KvHeartbeatStatus {
3086                    health: ObservedHealth::Healthy,
3087                    lifecycle: ProviderLifecycleState::Running,
3088                    message: None,
3089                    stale: false,
3090                    partial: false,
3091                    collection_issues: vec![],
3092                },
3093                name: "state".to_string(),
3094                region: Some("us-east-1".to_string()),
3095                table_arn: None,
3096                table_status: Some("ACTIVE".to_string()),
3097                billing_mode: Some("PAY_PER_REQUEST".to_string()),
3098                key_schema: vec![AwsDynamoDbKeySchemaElement {
3099                    attribute_name: "pk".to_string(),
3100                    key_type: "HASH".to_string(),
3101                }],
3102                global_secondary_index_count: Some(0),
3103                local_secondary_index_count: Some(0),
3104                item_count: None,
3105                table_size_bytes: None,
3106                stream_enabled: Some(false),
3107                stream_view_type: None,
3108                ttl_status: Some("ENABLED".to_string()),
3109                ttl_attribute_name: Some("ttl".to_string()),
3110                deletion_protection_enabled: Some(false),
3111                sse_status: Some("ENABLED".to_string()),
3112                sse_type: Some("KMS".to_string()),
3113                table_class: None,
3114                replica_count: Some(0),
3115                restore_in_progress: None,
3116            },
3117        )))
3118        .unwrap();
3119
3120        assert_eq!(cluster["resourceType"], "kubernetes-cluster");
3121        assert!(cluster["data"].get("summary").is_none());
3122        assert!(cluster["data"].get("detail").is_none());
3123        assert_eq!(cluster["data"]["name"], "prod");
3124        assert_eq!(queue["data"]["backend"], "awsSqs");
3125        assert_eq!(queue["data"]["approximateVisibleMessages"], 42);
3126        assert!(queue["data"].get("summary").is_none());
3127        assert_eq!(storage["data"]["backend"], "awsS3");
3128        assert!(storage["data"].get("summary").is_none());
3129        assert_eq!(gcp_storage["data"]["backend"], "gcpCloudStorage");
3130        assert_eq!(gcp_storage["data"]["publicAccessPrevention"], "enforced");
3131        assert_eq!(kv["data"]["backend"], "awsDynamoDb");
3132        assert!(kv["data"].get("summary").is_none());
3133        assert_eq!(kv["data"]["itemCount"], json!(null));
3134    }
3135
3136    #[test]
3137    fn ai_heartbeat_serializes_stable_tags() {
3138        let aws = serde_json::to_value(ResourceHeartbeatData::Ai(AiHeartbeatData::AwsBedrock(
3139            AwsBedrockAiHeartbeatData {
3140                status: AiHeartbeatStatus::default(),
3141                region: "us-east-1".to_string(),
3142                availability: AiAvailabilityObservation::unobserved(
3143                    AiAvailabilitySource::AwsBedrock,
3144                    Platform::Aws,
3145                    Some("us-east-1".to_string()),
3146                ),
3147            },
3148        )))
3149        .unwrap();
3150        let gcp = serde_json::to_value(ResourceHeartbeatData::Ai(AiHeartbeatData::GcpVertex(
3151            GcpVertexAiHeartbeatData {
3152                status: AiHeartbeatStatus::default(),
3153                project: "my-project".to_string(),
3154                location: "us-central1".to_string(),
3155                availability: AiAvailabilityObservation::unobserved(
3156                    AiAvailabilitySource::GcpVertex,
3157                    Platform::Gcp,
3158                    Some("us-central1".to_string()),
3159                ),
3160            },
3161        )))
3162        .unwrap();
3163        let azure = serde_json::to_value(ResourceHeartbeatData::Ai(AiHeartbeatData::AzureFoundry(
3164            AzureFoundryAiHeartbeatData {
3165                status: AiHeartbeatStatus::default(),
3166                account_name: "my-ai-account".to_string(),
3167                endpoint: Some("https://my-ai-account.openai.azure.com/".to_string()),
3168                resource_group: Some("my-rg".to_string()),
3169                location: Some("eastus".to_string()),
3170                availability: AiAvailabilityObservation::unobserved(
3171                    AiAvailabilitySource::AzureFoundry,
3172                    Platform::Azure,
3173                    Some("eastus".to_string()),
3174                ),
3175            },
3176        )))
3177        .unwrap();
3178
3179        assert_eq!(aws["resourceType"], "ai");
3180        assert_eq!(aws["data"]["backend"], "awsBedrock");
3181        assert_eq!(aws["data"]["region"], "us-east-1");
3182        assert!(aws["data"].get("summary").is_none());
3183
3184        assert_eq!(gcp["resourceType"], "ai");
3185        assert_eq!(gcp["data"]["backend"], "gcpVertex");
3186        assert_eq!(gcp["data"]["project"], "my-project");
3187        assert_eq!(gcp["data"]["location"], "us-central1");
3188
3189        assert_eq!(azure["resourceType"], "ai");
3190        assert_eq!(azure["data"]["backend"], "azureFoundry");
3191        assert_eq!(azure["data"]["accountName"], "my-ai-account");
3192        assert_eq!(
3193            azure["data"]["endpoint"],
3194            "https://my-ai-account.openai.azure.com/"
3195        );
3196    }
3197}