Skip to main content

alien_core/
heartbeat.rs

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