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    GcpAgentPlatform(GcpAgentPlatformSandboxHeartbeatData),
1601    KubernetesPods(KubernetesSandboxHeartbeatData),
1602    Local(LocalSandboxHeartbeatData),
1603}
1604
1605#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1606#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1607#[serde(rename_all = "camelCase")]
1608pub struct SandboxHeartbeatStatus {
1609    pub health: ObservedHealth,
1610    pub lifecycle: ProviderLifecycleState,
1611    pub message: Option<String>,
1612    pub stale: bool,
1613    pub partial: bool,
1614    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1615}
1616
1617impl Default for SandboxHeartbeatStatus {
1618    fn default() -> Self {
1619        Self {
1620            health: ObservedHealth::Healthy,
1621            lifecycle: ProviderLifecycleState::Running,
1622            message: None,
1623            stale: false,
1624            partial: false,
1625            collection_issues: Vec::new(),
1626        }
1627    }
1628}
1629
1630/// AWS: the image a sandbox runs from, and the lifecycle state AWS reports for it.
1631#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1632#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1633#[serde(rename_all = "camelCase")]
1634pub struct AwsMicrovmSandboxHeartbeatData {
1635    pub status: SandboxHeartbeatStatus,
1636    /// Image the sessions belong to.
1637    pub image_identifier: String,
1638    /// The image's own lifecycle state, which is where AWS surfaces base-image deprecation.
1639    ///
1640    /// No session count sits beside it: counting means `lambda:ListMicrovms`, which authorizes
1641    /// against no resource type and so cannot be granted without an account-wide reach the
1642    /// permission sets refuse. A field only an over-broad grant could fill is a field whose
1643    /// implementer ships AccessDenied into a customer's account.
1644    pub image_state: Option<String>,
1645}
1646
1647/// Azure: the sandbox group's ARM state. The data plane has no list operation, so a session count
1648/// is not available here.
1649#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1650#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1651#[serde(rename_all = "camelCase")]
1652pub struct AzureSandboxGroupHeartbeatData {
1653    pub status: SandboxHeartbeatStatus,
1654    pub sandbox_group: String,
1655    pub provisioning_state: Option<String>,
1656}
1657
1658/// GCP: the Agent Platform template sessions are cut from, and the engine it hangs under.
1659///
1660/// No session count: that needs `aiplatform.sandboxEnvironments.list`, which only the management
1661/// permission set holds. No template state either — emission is gated on reading it `ACTIVE`,
1662/// which is what `status` already says.
1663#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1664#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1665#[serde(rename_all = "camelCase")]
1666pub struct GcpAgentPlatformSandboxHeartbeatData {
1667    pub status: SandboxHeartbeatStatus,
1668    /// Reasoning engine the template hangs under, without which the template id names nothing.
1669    pub engine: String,
1670    /// The template sessions are currently cut from.
1671    pub template_id: String,
1672}
1673
1674/// Kubernetes: pods carrying the sandbox label, in the deployment's namespace.
1675#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1676#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1677#[serde(rename_all = "camelCase")]
1678pub struct KubernetesSandboxHeartbeatData {
1679    pub status: SandboxHeartbeatStatus,
1680    pub namespace: String,
1681    pub active_sessions: u32,
1682    /// Claimed but unused pods waiting in the pool.
1683    pub idle_pods: u32,
1684}
1685
1686/// Local: containers Docker still holds for this sandbox.
1687#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1688#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1689#[serde(rename_all = "camelCase")]
1690pub struct LocalSandboxHeartbeatData {
1691    pub status: SandboxHeartbeatStatus,
1692    pub active_sessions: u32,
1693    /// Whether the loopback route is serving in this process. False after a manager restart until
1694    /// the next tick rebinds it.
1695    pub route_serving: bool,
1696}
1697
1698#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1699#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1700#[serde(tag = "backend", rename_all = "camelCase")]
1701pub enum AiHeartbeatData {
1702    AwsBedrock(AwsBedrockAiHeartbeatData),
1703    GcpVertex(GcpVertexAiHeartbeatData),
1704    AzureFoundry(AzureFoundryAiHeartbeatData),
1705    External(ExternalAiHeartbeatData),
1706}
1707
1708#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1709#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1710#[serde(rename_all = "camelCase")]
1711pub struct AiHeartbeatStatus {
1712    pub health: ObservedHealth,
1713    pub lifecycle: ProviderLifecycleState,
1714    pub message: Option<String>,
1715    pub stale: bool,
1716    pub partial: bool,
1717    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1718}
1719
1720impl Default for AiHeartbeatStatus {
1721    fn default() -> Self {
1722        // A configured AI binding is not proof that its models are usable.
1723        Self {
1724            health: ObservedHealth::Unknown,
1725            lifecycle: ProviderLifecycleState::Unknown,
1726            message: None,
1727            stale: false,
1728            partial: false,
1729            collection_issues: vec![],
1730        }
1731    }
1732}
1733
1734/// Provider control plane used to observe model availability without invoking
1735/// a model, spending customer quota, or accepting provider terms.
1736#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1737#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1738#[serde(rename_all = "kebab-case")]
1739pub enum AiAvailabilitySource {
1740    AwsBedrock,
1741    GcpVertex,
1742    AzureFoundry,
1743    Anthropic,
1744}
1745
1746#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1747#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1748#[serde(rename_all = "kebab-case")]
1749pub enum AiModelAvailability {
1750    Available,
1751    Blocked,
1752    Unknown,
1753}
1754
1755#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1756#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1757#[serde(rename_all = "kebab-case")]
1758pub enum AiAvailabilityBlocker {
1759    AgreementRequired,
1760    EntitlementRequired,
1761    ModelActivationRequired,
1762    DeploymentRequired,
1763    QuotaConfigurationRequired,
1764    RegionUnavailable,
1765    AccessDenied,
1766    ObservationFailed,
1767}
1768
1769#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1770#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1771#[serde(rename_all = "kebab-case")]
1772pub enum AiAccessTest {
1773    Verified,
1774    Failed,
1775    NotChecked,
1776}
1777
1778#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1779#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1780#[serde(rename_all = "camelCase")]
1781pub struct AiModelAvailabilityObservation {
1782    pub public_model_id: String,
1783    pub client_apis: Vec<ClientApi>,
1784    pub availability: AiModelAvailability,
1785    pub blockers: Vec<AiAvailabilityBlocker>,
1786    pub access_test: AiAccessTest,
1787    #[serde(skip_serializing_if = "Option::is_none")]
1788    pub tested_at: Option<DateTime<Utc>>,
1789    #[serde(skip_serializing_if = "Option::is_none")]
1790    pub error_code: Option<String>,
1791}
1792
1793#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1794#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1795#[serde(rename_all = "camelCase")]
1796pub struct AiAvailabilityObservation {
1797    pub source: AiAvailabilitySource,
1798    pub catalog_revision: String,
1799    #[serde(skip_serializing_if = "Option::is_none")]
1800    pub location: Option<String>,
1801    pub models: Vec<AiModelAvailabilityObservation>,
1802}
1803
1804impl AiAvailabilityObservation {
1805    /// Conservative observation used until a provider control-plane read has
1806    /// completed. It never claims that a configured model is usable.
1807    pub fn unobserved(
1808        source: AiAvailabilitySource,
1809        platform: Platform,
1810        location: Option<String>,
1811    ) -> Self {
1812        Self {
1813            source,
1814            catalog_revision: crate::ai_catalog::AI_CATALOG_REVISION.to_string(),
1815            location,
1816            models: crate::ai_catalog::models_for(platform)
1817                .into_iter()
1818                .map(|model| AiModelAvailabilityObservation {
1819                    public_model_id: model.public_id.to_string(),
1820                    client_apis: ClientApi::ALL.to_vec(),
1821                    availability: AiModelAvailability::Unknown,
1822                    blockers: vec![AiAvailabilityBlocker::ObservationFailed],
1823                    access_test: AiAccessTest::NotChecked,
1824                    tested_at: None,
1825                    error_code: None,
1826                })
1827                .collect(),
1828        }
1829    }
1830}
1831
1832#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1833#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1834#[serde(rename_all = "camelCase")]
1835pub struct LocalPostgresHeartbeatData {
1836    pub status: PostgresHeartbeatStatus,
1837    pub name: String,
1838    pub port: Option<u16>,
1839    pub version: String,
1840    pub process_running: bool,
1841}
1842
1843#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1844#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1845#[serde(rename_all = "camelCase")]
1846pub struct AuroraPostgresHeartbeatData {
1847    pub status: PostgresHeartbeatStatus,
1848    pub cluster_identifier: String,
1849    #[serde(skip_serializing_if = "Option::is_none")]
1850    pub engine_version: Option<String>,
1851    #[serde(skip_serializing_if = "Option::is_none")]
1852    pub endpoint: Option<String>,
1853    /// Latest sampled `ServerlessDatabaseCapacity` (ACU).
1854    #[serde(skip_serializing_if = "Option::is_none")]
1855    pub serverless_capacity: Option<f64>,
1856    /// True when a `minCapacity: 0` instance has not reached 0 ACU over the observation
1857    /// window — it is silently paying always-on prices (auto-pause verification).
1858    pub never_pauses: bool,
1859}
1860
1861#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1862#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1863#[serde(rename_all = "camelCase")]
1864pub struct GcpCloudSqlPostgresHeartbeatData {
1865    pub status: PostgresHeartbeatStatus,
1866    pub instance_name: String,
1867    #[serde(skip_serializing_if = "Option::is_none")]
1868    pub state: Option<String>,
1869    #[serde(skip_serializing_if = "Option::is_none")]
1870    pub database_version: Option<String>,
1871}
1872
1873#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1874#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1875#[serde(rename_all = "camelCase")]
1876pub struct AzureFlexibleServerPostgresHeartbeatData {
1877    pub status: PostgresHeartbeatStatus,
1878    pub server_name: String,
1879    #[serde(skip_serializing_if = "Option::is_none")]
1880    pub state: Option<String>,
1881    #[serde(skip_serializing_if = "Option::is_none")]
1882    pub version: Option<String>,
1883}
1884
1885#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1886#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1887#[serde(rename_all = "camelCase")]
1888pub struct AwsBedrockAiHeartbeatData {
1889    pub status: AiHeartbeatStatus,
1890    pub region: String,
1891    pub availability: AiAvailabilityObservation,
1892}
1893
1894#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1895#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1896#[serde(rename_all = "camelCase")]
1897pub struct GcpVertexAiHeartbeatData {
1898    pub status: AiHeartbeatStatus,
1899    pub project: String,
1900    pub location: String,
1901    pub availability: AiAvailabilityObservation,
1902}
1903
1904#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1905#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1906#[serde(rename_all = "camelCase")]
1907pub struct AzureFoundryAiHeartbeatData {
1908    pub status: AiHeartbeatStatus,
1909    pub account_name: String,
1910    pub endpoint: Option<String>,
1911    pub resource_group: Option<String>,
1912    pub location: Option<String>,
1913    pub availability: AiAvailabilityObservation,
1914}
1915
1916#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1917#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1918#[serde(rename_all = "camelCase")]
1919pub struct ExternalAiHeartbeatData {
1920    pub status: AiHeartbeatStatus,
1921    /// The BYO-key provider serving this binding (e.g. "openai"). Used on the Local
1922    /// platform, where the app brings its own provider key instead of an ambient cloud.
1923    pub provider: String,
1924    #[serde(skip_serializing_if = "Option::is_none")]
1925    pub availability: Option<AiAvailabilityObservation>,
1926}
1927
1928#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1929#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1930#[serde(tag = "backend", rename_all = "camelCase")]
1931pub enum VaultHeartbeatData {
1932    AwsParameterStore(AwsParameterStoreVaultHeartbeatData),
1933    GcpSecretManager(GcpSecretManagerVaultHeartbeatData),
1934    AzureKeyVault(AzureKeyVaultHeartbeatData),
1935    KubernetesSecret(KubernetesSecretVaultHeartbeatData),
1936    Local(LocalVaultHeartbeatData),
1937}
1938
1939#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1940#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1941#[serde(rename_all = "camelCase")]
1942pub struct VaultHeartbeatStatus {
1943    pub health: ObservedHealth,
1944    pub lifecycle: ProviderLifecycleState,
1945    pub message: Option<String>,
1946    pub stale: bool,
1947    pub partial: bool,
1948    pub collection_issues: Vec<HeartbeatCollectionIssue>,
1949}
1950
1951impl Default for VaultHeartbeatStatus {
1952    fn default() -> Self {
1953        Self {
1954            health: ObservedHealth::Healthy,
1955            lifecycle: ProviderLifecycleState::Running,
1956            message: None,
1957            stale: false,
1958            partial: false,
1959            collection_issues: vec![],
1960        }
1961    }
1962}
1963
1964#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1965#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1966#[serde(rename_all = "camelCase")]
1967pub struct AwsParameterStoreVaultHeartbeatData {
1968    pub status: VaultHeartbeatStatus,
1969    pub account_id: String,
1970    pub region: String,
1971    pub prefix: String,
1972    pub parameter_metadata_sampled: bool,
1973    pub sampled_parameter_count: Option<u32>,
1974    pub sampled_secure_string_count: Option<u32>,
1975    pub sampled_string_count: Option<u32>,
1976    pub sampled_string_list_count: Option<u32>,
1977    pub sampled_advanced_tier_count: Option<u32>,
1978    pub sampled_kms_key_metadata_present_count: Option<u32>,
1979    pub latest_modified_at: Option<DateTime<Utc>>,
1980    pub has_more_parameters: Option<bool>,
1981}
1982
1983#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1984#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1985#[serde(rename_all = "camelCase")]
1986pub struct GcpSecretManagerVaultHeartbeatData {
1987    pub status: VaultHeartbeatStatus,
1988    pub project_id: String,
1989    pub location: String,
1990    pub prefix: String,
1991    pub secret_metadata_listed: bool,
1992}
1993
1994#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1995#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1996#[serde(rename_all = "camelCase")]
1997pub struct AzureKeyVaultHeartbeatData {
1998    pub status: VaultHeartbeatStatus,
1999    pub name: String,
2000    pub resource_group: Option<String>,
2001    pub resource_id: Option<String>,
2002    pub location: Option<String>,
2003    pub vault_uri: Option<String>,
2004    pub provisioning_state: Option<String>,
2005    pub sku_family: Option<String>,
2006    pub sku_name: Option<String>,
2007    pub soft_delete_enabled: bool,
2008    pub soft_delete_retention_days: i32,
2009    pub purge_protection_enabled: Option<bool>,
2010    pub rbac_authorization_enabled: bool,
2011    pub public_network_access: String,
2012    pub access_policy_count: u32,
2013    pub private_endpoint_connection_count: u32,
2014    pub secret_metadata_listed: bool,
2015}
2016
2017#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2018#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2019#[serde(rename_all = "camelCase")]
2020pub struct KubernetesSecretVaultHeartbeatData {
2021    pub status: VaultHeartbeatStatus,
2022    pub namespace: String,
2023    pub prefix: String,
2024    pub secret_metadata_listed: bool,
2025}
2026
2027#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2028#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2029#[serde(rename_all = "camelCase")]
2030pub struct LocalVaultHeartbeatData {
2031    pub status: VaultHeartbeatStatus,
2032    pub path: String,
2033    pub path_exists: bool,
2034    pub is_directory: Option<bool>,
2035    pub readonly: Option<bool>,
2036    pub modified_at: Option<DateTime<Utc>>,
2037    pub secret_metadata_listed: bool,
2038}
2039
2040#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2041#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2042#[serde(tag = "backend", rename_all = "camelCase")]
2043pub enum ServiceAccountHeartbeatData {
2044    AwsIamRole(AwsIamRoleServiceAccountHeartbeatData),
2045    GcpServiceAccount(GcpServiceAccountHeartbeatData),
2046    AzureManagedIdentity(AzureManagedIdentityServiceAccountHeartbeatData),
2047    Local(LocalServiceAccountHeartbeatData),
2048}
2049
2050#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2051#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2052#[serde(rename_all = "camelCase")]
2053pub struct ServiceAccountHeartbeatStatus {
2054    pub health: ObservedHealth,
2055    pub lifecycle: ProviderLifecycleState,
2056    pub message: Option<String>,
2057    pub stale: bool,
2058    pub partial: bool,
2059    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2060}
2061
2062#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2063#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2064#[serde(rename_all = "camelCase")]
2065pub struct AwsIamRoleServiceAccountHeartbeatData {
2066    pub status: ServiceAccountHeartbeatStatus,
2067    pub role_name: String,
2068    pub role_arn: String,
2069    pub role_id: String,
2070    pub path: String,
2071    pub create_date: String,
2072    pub description: Option<String>,
2073    pub max_session_duration: Option<i32>,
2074    pub assume_role_policy_present: bool,
2075    pub permissions_boundary_type: Option<String>,
2076    pub permissions_boundary_arn: Option<String>,
2077    pub tag_count: u32,
2078    pub managed_tag_count: u32,
2079    pub attached_policy_count: u32,
2080    pub attached_policy_names: Vec<String>,
2081    pub inline_policy_count: u32,
2082    pub inline_policy_names: Vec<String>,
2083    pub stack_permissions_applied: bool,
2084    pub last_used_date: Option<String>,
2085    pub last_used_region: Option<String>,
2086}
2087
2088#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2089#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2090#[serde(rename_all = "camelCase")]
2091pub struct GcpServiceAccountHeartbeatData {
2092    pub status: ServiceAccountHeartbeatStatus,
2093    pub name: Option<String>,
2094    pub project_id: Option<String>,
2095    pub unique_id: Option<String>,
2096    pub email: String,
2097    pub display_name: Option<String>,
2098    pub description: Option<String>,
2099    pub oauth2_client_id: Option<String>,
2100    pub disabled: Option<bool>,
2101    pub etag: Option<String>,
2102    pub project_binding_count: u32,
2103    pub project_roles: Vec<String>,
2104    pub service_account_binding_count: u32,
2105    pub service_account_roles: Vec<String>,
2106}
2107
2108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2109#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2110#[serde(rename_all = "camelCase")]
2111pub struct AzureManagedIdentityServiceAccountHeartbeatData {
2112    pub status: ServiceAccountHeartbeatStatus,
2113    pub name: String,
2114    pub resource_id: String,
2115    pub resource_group: String,
2116    pub location: String,
2117    pub type_: Option<String>,
2118    pub client_id: Option<String>,
2119    pub principal_id: Option<String>,
2120    pub tenant_id: Option<String>,
2121    pub isolation_scope: Option<String>,
2122    pub managed_tag_count: u32,
2123    pub role_assignment_count: u32,
2124    pub role_assignment_ids: Vec<String>,
2125    pub custom_role_definition_count: u32,
2126    pub custom_role_definition_ids: Vec<String>,
2127    pub stack_permissions_applied: bool,
2128}
2129
2130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2131#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2132#[serde(rename_all = "camelCase")]
2133pub struct LocalServiceAccountHeartbeatData {
2134    pub status: ServiceAccountHeartbeatStatus,
2135    pub identity: String,
2136    pub configured: bool,
2137}
2138
2139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2140#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2141#[serde(tag = "backend", rename_all = "camelCase")]
2142pub enum NetworkHeartbeatData {
2143    AwsVpc(AwsVpcNetworkHeartbeatData),
2144    GcpVpc(GcpVpcNetworkHeartbeatData),
2145    AzureVnet(AzureVnetNetworkHeartbeatData),
2146}
2147
2148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2149#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2150#[serde(rename_all = "camelCase")]
2151pub struct NetworkHeartbeatStatus {
2152    pub health: ObservedHealth,
2153    pub lifecycle: ProviderLifecycleState,
2154    pub message: Option<String>,
2155    pub stale: bool,
2156    pub partial: bool,
2157    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2158}
2159
2160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2161#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2162#[serde(rename_all = "camelCase")]
2163pub struct AwsVpcNetworkHeartbeatData {
2164    pub status: NetworkHeartbeatStatus,
2165    pub vpc_id: Option<String>,
2166    pub vpc_state: Option<String>,
2167    pub cidr_block: Option<String>,
2168    pub public_subnet_ids: Vec<String>,
2169    pub private_subnet_ids: Vec<String>,
2170    pub availability_zones: Vec<String>,
2171    pub internet_gateway_id: Option<String>,
2172    pub nat_gateway_id: Option<String>,
2173    pub route_table_count: u32,
2174    pub security_group_id: 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 GcpVpcNetworkHeartbeatData {
2182    pub status: NetworkHeartbeatStatus,
2183    pub network_name: Option<String>,
2184    pub network_self_link: Option<String>,
2185    pub subnetwork_name: Option<String>,
2186    pub subnetwork_self_link: Option<String>,
2187    pub region: Option<String>,
2188    pub cidr_block: Option<String>,
2189    pub router_name: Option<String>,
2190    pub cloud_nat_name: Option<String>,
2191    pub firewall_name: Option<String>,
2192    pub is_byo_vpc: bool,
2193}
2194
2195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2196#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2197#[serde(rename_all = "camelCase")]
2198pub struct AzureVnetNetworkHeartbeatData {
2199    pub status: NetworkHeartbeatStatus,
2200    pub vnet_name: Option<String>,
2201    pub vnet_resource_id: Option<String>,
2202    pub resource_group: Option<String>,
2203    pub location: Option<String>,
2204    pub cidr_block: Option<String>,
2205    pub public_subnet_name: Option<String>,
2206    pub private_subnet_name: Option<String>,
2207    pub application_gateway_subnet_name: Option<String>,
2208    pub private_endpoint_subnet_name: Option<String>,
2209    pub nat_gateway_id: Option<String>,
2210    pub public_ip_id: Option<String>,
2211    pub nsg_id: Option<String>,
2212    pub is_byo_vnet: bool,
2213    pub last_byo_vnet_verification_error_code: Option<String>,
2214}
2215
2216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2217#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2218#[serde(tag = "backend", rename_all = "camelCase")]
2219pub enum RemoteStackManagementHeartbeatData {
2220    AwsIamRole(AwsRemoteStackManagementHeartbeatData),
2221    GcpServiceAccount(GcpRemoteStackManagementHeartbeatData),
2222    AzureManagedIdentity(AzureRemoteStackManagementHeartbeatData),
2223}
2224
2225#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2226#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2227#[serde(rename_all = "camelCase")]
2228pub struct RemoteStackManagementHeartbeatStatus {
2229    pub health: ObservedHealth,
2230    pub lifecycle: ProviderLifecycleState,
2231    pub message: Option<String>,
2232    pub stale: bool,
2233    pub partial: bool,
2234    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2235}
2236
2237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2238#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2239#[serde(rename_all = "camelCase")]
2240pub struct AwsRemoteStackManagementHeartbeatData {
2241    pub status: RemoteStackManagementHeartbeatStatus,
2242    pub role_name: Option<String>,
2243    pub role_arn: Option<String>,
2244    pub management_permissions_applied: bool,
2245}
2246
2247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2248#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2249#[serde(rename_all = "camelCase")]
2250pub struct GcpRemoteStackManagementHeartbeatData {
2251    pub status: RemoteStackManagementHeartbeatStatus,
2252    pub service_account_email: Option<String>,
2253    pub service_account_unique_id: Option<String>,
2254    pub role_bound: bool,
2255    pub impersonation_granted: bool,
2256}
2257
2258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2259#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2260#[serde(rename_all = "camelCase")]
2261pub struct AzureRemoteStackManagementHeartbeatData {
2262    pub status: RemoteStackManagementHeartbeatStatus,
2263    pub uami_resource_id: Option<String>,
2264    pub uami_client_id: Option<String>,
2265    pub uami_principal_id: Option<String>,
2266    pub tenant_id: Option<String>,
2267    pub fic_name: Option<String>,
2268    pub role_definition_id: Option<String>,
2269    pub role_assignment_ids: Vec<String>,
2270}
2271
2272#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2273#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2274#[serde(tag = "backend", rename_all = "camelCase")]
2275pub enum ArtifactRegistryHeartbeatData {
2276    AwsEcr(AwsEcrArtifactRegistryHeartbeatData),
2277    GcpArtifactRegistry(GcpArtifactRegistryHeartbeatData),
2278    AzureContainerRegistry(AzureContainerRegistryHeartbeatData),
2279    Local(LocalArtifactRegistryHeartbeatData),
2280}
2281
2282#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2283#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2284#[serde(rename_all = "camelCase")]
2285pub struct ArtifactRegistryHeartbeatStatus {
2286    pub health: ObservedHealth,
2287    pub lifecycle: ProviderLifecycleState,
2288    pub message: Option<String>,
2289    pub stale: bool,
2290    pub partial: bool,
2291    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2292}
2293
2294#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2295#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2296#[serde(rename_all = "camelCase")]
2297pub struct AwsEcrArtifactRegistryHeartbeatData {
2298    pub status: ArtifactRegistryHeartbeatStatus,
2299    pub registry_id: String,
2300    pub region: String,
2301    pub registry_uri: String,
2302    pub repository_prefix: String,
2303    pub pull_role_arn: Option<String>,
2304    pub push_role_arn: Option<String>,
2305    pub repository_count: u32,
2306    pub repositories_truncated: bool,
2307    pub repositories: Vec<AwsEcrRepositoryHeartbeatData>,
2308}
2309
2310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2311#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2312#[serde(rename_all = "camelCase")]
2313pub struct AwsEcrRepositoryHeartbeatData {
2314    pub repository_arn: String,
2315    pub registry_id: String,
2316    pub repository_name: String,
2317    pub repository_uri: String,
2318    pub created_at: f64,
2319    pub image_tag_mutability: Option<String>,
2320    pub scan_on_push: Option<bool>,
2321    pub encryption_type: Option<String>,
2322    pub kms_key_present: bool,
2323}
2324
2325#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2326#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2327#[serde(rename_all = "camelCase")]
2328pub struct GcpArtifactRegistryHeartbeatData {
2329    pub status: ArtifactRegistryHeartbeatStatus,
2330    pub project_id: String,
2331    pub location: String,
2332    pub repository_id: String,
2333    pub name: Option<String>,
2334    pub format: Option<String>,
2335    pub mode: Option<String>,
2336    pub description: Option<String>,
2337    pub label_count: u32,
2338    pub cleanup_policy_count: u32,
2339    pub cleanup_policy_dry_run: Option<bool>,
2340    pub kms_key_name_present: bool,
2341    pub size_bytes: Option<String>,
2342    pub satisfies_pzs: Option<bool>,
2343    pub create_time: Option<String>,
2344    pub update_time: Option<String>,
2345    pub iam_policy_etag_present: bool,
2346    pub iam_binding_count: u32,
2347    pub iam_roles: Vec<String>,
2348    pub pull_service_account_email: Option<String>,
2349    pub push_service_account_email: Option<String>,
2350}
2351
2352#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2353#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2354#[serde(rename_all = "camelCase")]
2355pub struct AzureContainerRegistryHeartbeatData {
2356    pub status: ArtifactRegistryHeartbeatStatus,
2357    pub name: String,
2358    pub resource_id: Option<String>,
2359    pub resource_group: String,
2360    pub location: String,
2361    pub type_: Option<String>,
2362    pub login_server: Option<String>,
2363    pub sku_name: String,
2364    pub sku_tier: Option<String>,
2365    pub provisioning_state: Option<String>,
2366    pub admin_user_enabled: bool,
2367    pub anonymous_pull_enabled: bool,
2368    pub public_network_access: String,
2369    pub network_rule_bypass_options: String,
2370    pub network_rule_default_action: Option<String>,
2371    pub ip_rule_count: u32,
2372    pub encryption_status: Option<String>,
2373    pub encryption_key_vault_uri_present: bool,
2374    pub encryption_key_identifier_present: bool,
2375    pub policies_present: bool,
2376    pub policy_count: u32,
2377    pub private_endpoint_connection_count: u32,
2378    pub data_endpoint_enabled: Option<bool>,
2379    pub data_endpoint_host_names: Vec<String>,
2380    pub zone_redundancy: String,
2381    pub creation_date: Option<String>,
2382    pub managed_tag_count: u32,
2383}
2384
2385#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2386#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2387#[serde(rename_all = "camelCase")]
2388pub struct LocalArtifactRegistryHeartbeatData {
2389    pub status: ArtifactRegistryHeartbeatStatus,
2390    pub registry_url: String,
2391    pub reachable: bool,
2392}
2393
2394#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2395#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2396#[serde(tag = "backend", rename_all = "camelCase")]
2397pub enum BuildHeartbeatData {
2398    AwsCodeBuild(AwsCodeBuildHeartbeatData),
2399    GcpCloudBuild(GcpCloudBuildHeartbeatData),
2400    AzureContainerApps(AzureContainerAppsBuildHeartbeatData),
2401    KubernetesJob(KubernetesBuildHeartbeatData),
2402}
2403
2404#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2405#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2406#[serde(rename_all = "camelCase")]
2407pub struct BuildHeartbeatStatus {
2408    pub health: ObservedHealth,
2409    pub lifecycle: ProviderLifecycleState,
2410    pub message: Option<String>,
2411    pub stale: bool,
2412    pub partial: bool,
2413    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2414}
2415
2416#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2417#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2418#[serde(rename_all = "camelCase")]
2419pub struct AwsCodeBuildHeartbeatData {
2420    pub status: BuildHeartbeatStatus,
2421    pub project_name: String,
2422    pub project_arn: Option<String>,
2423    pub description: Option<String>,
2424    pub source_type: Option<String>,
2425    pub artifacts_type: Option<String>,
2426    pub artifacts_encryption_disabled: Option<bool>,
2427    pub environment_type: Option<String>,
2428    pub environment_image: Option<String>,
2429    pub compute_type: Option<String>,
2430    pub image_pull_credentials_type: Option<String>,
2431    pub privileged_mode: Option<bool>,
2432    pub environment_variable_count: u32,
2433    pub service_role_present: bool,
2434    pub encryption_key_present: bool,
2435    pub cloud_watch_logs_status: Option<String>,
2436    pub s3_logs_status: Option<String>,
2437    pub timeout_in_minutes: Option<i32>,
2438    pub queued_timeout_in_minutes: Option<i32>,
2439    pub created: Option<f64>,
2440    pub last_modified: Option<f64>,
2441}
2442
2443#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2444#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2445#[serde(rename_all = "camelCase")]
2446pub struct GcpCloudBuildHeartbeatData {
2447    pub status: BuildHeartbeatStatus,
2448    pub project_id: String,
2449    pub location: String,
2450    pub build_config_id: String,
2451    pub service_account: Option<String>,
2452    pub environment_variable_count: u32,
2453}
2454
2455#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2456#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2457#[serde(rename_all = "camelCase")]
2458pub struct AzureContainerAppsBuildHeartbeatData {
2459    pub status: BuildHeartbeatStatus,
2460    pub managed_environment_id: String,
2461    pub resource_group_name: String,
2462    pub managed_identity_id: Option<String>,
2463    pub resource_prefix: Option<String>,
2464    pub environment_variable_count: u32,
2465}
2466
2467#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2468#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2469#[serde(rename_all = "camelCase")]
2470pub struct KubernetesBuildHeartbeatData {
2471    pub status: BuildHeartbeatStatus,
2472    pub job_name: String,
2473    pub namespace: String,
2474    pub active: Option<i32>,
2475    pub succeeded: Option<i32>,
2476    pub failed: Option<i32>,
2477    pub start_time: Option<DateTime<Utc>>,
2478    pub completion_time: Option<DateTime<Utc>>,
2479    pub condition_count: u32,
2480    pub image_digest: Option<String>,
2481    pub events: Vec<KubernetesEventSnapshot>,
2482}
2483
2484#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2485#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2486#[serde(tag = "backend", rename_all = "camelCase")]
2487pub enum ServiceActivationHeartbeatData {
2488    GcpServiceUsage(GcpServiceUsageActivationHeartbeatData),
2489    AzureResourceProvider(AzureResourceProviderActivationHeartbeatData),
2490}
2491
2492#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2493#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2494#[serde(rename_all = "camelCase")]
2495pub struct ServiceActivationHeartbeatStatus {
2496    pub health: ObservedHealth,
2497    pub lifecycle: ProviderLifecycleState,
2498    pub message: Option<String>,
2499    pub stale: bool,
2500    pub partial: bool,
2501    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2502}
2503
2504impl Default for ServiceActivationHeartbeatStatus {
2505    fn default() -> Self {
2506        Self {
2507            health: ObservedHealth::Healthy,
2508            lifecycle: ProviderLifecycleState::Running,
2509            message: None,
2510            stale: false,
2511            partial: false,
2512            collection_issues: vec![],
2513        }
2514    }
2515}
2516
2517#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2518#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2519#[serde(rename_all = "camelCase")]
2520pub struct GcpServiceUsageActivationHeartbeatData {
2521    pub status: ServiceActivationHeartbeatStatus,
2522    pub project_id: String,
2523    pub service_name: String,
2524    pub service_resource_name: Option<String>,
2525    pub title: Option<String>,
2526    pub state: Option<String>,
2527    pub enabled: bool,
2528    pub last_operation_name: Option<String>,
2529}
2530
2531#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2532#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2533#[serde(rename_all = "camelCase")]
2534pub struct AzureResourceProviderActivationHeartbeatData {
2535    pub status: ServiceActivationHeartbeatStatus,
2536    pub namespace: String,
2537    pub provider_id: Option<String>,
2538    pub registration_state: Option<String>,
2539    pub registration_policy: Option<String>,
2540    pub resource_type_count: u32,
2541    pub registered: bool,
2542}
2543
2544#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2545#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2546#[serde(rename_all = "camelCase")]
2547pub struct AzureResourceGroupHeartbeatData {
2548    pub status: AzureResourceGroupHeartbeatStatus,
2549    pub name: String,
2550    pub resource_id: Option<String>,
2551    pub location: Option<String>,
2552    pub provisioning_state: Option<String>,
2553    pub managed_tags: BTreeMap<String, String>,
2554}
2555
2556#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2557#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2558#[serde(rename_all = "camelCase")]
2559pub struct AzureResourceGroupHeartbeatStatus {
2560    pub health: ObservedHealth,
2561    pub lifecycle: ProviderLifecycleState,
2562    pub message: Option<String>,
2563    pub stale: bool,
2564    pub partial: bool,
2565    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2566}
2567
2568impl Default for AzureResourceGroupHeartbeatStatus {
2569    fn default() -> Self {
2570        Self {
2571            health: ObservedHealth::Healthy,
2572            lifecycle: ProviderLifecycleState::Running,
2573            message: None,
2574            stale: false,
2575            partial: false,
2576            collection_issues: vec![],
2577        }
2578    }
2579}
2580
2581#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2582#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2583#[serde(rename_all = "camelCase")]
2584pub struct AzureStorageAccountHeartbeatData {
2585    pub status: StorageHeartbeatStatus,
2586    pub name: String,
2587    pub resource_id: Option<String>,
2588    pub resource_group: Option<String>,
2589    pub location: Option<String>,
2590    pub kind: Option<String>,
2591    pub sku_name: Option<String>,
2592    pub sku_tier: Option<String>,
2593    pub provisioning_state: Option<String>,
2594    pub primary_endpoints: AzureStorageAccountEndpoints,
2595    pub secondary_endpoints: AzureStorageAccountEndpoints,
2596    pub public_network_access: Option<String>,
2597    pub allow_blob_public_access: Option<bool>,
2598    pub allow_shared_key_access: Option<bool>,
2599    pub minimum_tls_version: Option<String>,
2600    pub supports_https_traffic_only: Option<bool>,
2601    pub encryption_key_source: Option<String>,
2602    pub require_infrastructure_encryption: Option<bool>,
2603    pub network_default_action: Option<String>,
2604    pub network_bypass: Option<String>,
2605    pub network_ip_rule_count: Option<u32>,
2606    pub network_virtual_network_rule_count: Option<u32>,
2607    pub network_resource_access_rule_count: Option<u32>,
2608}
2609
2610#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
2611#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2612#[serde(rename_all = "camelCase")]
2613pub struct AzureStorageAccountEndpoints {
2614    pub blob: Option<String>,
2615    pub dfs: Option<String>,
2616    pub file: Option<String>,
2617    pub queue: Option<String>,
2618    pub table: Option<String>,
2619    pub web: Option<String>,
2620}
2621
2622#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2623#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2624#[serde(rename_all = "camelCase")]
2625pub struct AzureContainerAppsEnvironmentHeartbeatData {
2626    pub status: AzureContainerAppsEnvironmentHeartbeatStatus,
2627    pub name: String,
2628    pub resource_id: Option<String>,
2629    pub resource_group: Option<String>,
2630    pub location: Option<String>,
2631    pub kind: Option<String>,
2632    pub provisioning_state: Option<String>,
2633    pub default_domain: Option<String>,
2634    pub static_ip: Option<String>,
2635    pub custom_domain_verification_id: Option<String>,
2636    pub infrastructure_resource_group: Option<String>,
2637    pub event_stream_endpoint: Option<String>,
2638    pub zone_redundant: Option<bool>,
2639    pub workload_profile_count: u32,
2640    pub workload_profiles: Vec<AzureContainerAppsEnvironmentWorkloadProfile>,
2641}
2642
2643#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2644#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2645#[serde(rename_all = "camelCase")]
2646pub struct AzureContainerAppsEnvironmentHeartbeatStatus {
2647    pub health: ObservedHealth,
2648    pub lifecycle: ProviderLifecycleState,
2649    pub message: Option<String>,
2650    pub stale: bool,
2651    pub partial: bool,
2652    pub collection_issues: Vec<HeartbeatCollectionIssue>,
2653}
2654
2655#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2656#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2657#[serde(rename_all = "camelCase")]
2658pub struct AzureContainerAppsEnvironmentWorkloadProfile {
2659    pub name: String,
2660    pub workload_profile_type: String,
2661    pub minimum_count: Option<i32>,
2662    pub maximum_count: Option<i32>,
2663}
2664
2665#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2666#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2667#[serde(rename_all = "camelCase")]
2668pub struct AzureServiceBusNamespaceHeartbeatData {
2669    pub status: QueueHeartbeatStatus,
2670    pub name: String,
2671    pub resource_id: Option<String>,
2672    pub resource_group: Option<String>,
2673    pub location: Option<String>,
2674    pub sku_name: Option<String>,
2675    pub sku_tier: Option<String>,
2676    pub sku_capacity: Option<i32>,
2677    pub namespace_status: Option<String>,
2678    pub provisioning_state: Option<String>,
2679    pub service_bus_endpoint: Option<String>,
2680    pub metric_id: Option<String>,
2681    pub public_network_access: Option<String>,
2682    pub disable_local_auth: Option<bool>,
2683    pub minimum_tls_version: Option<String>,
2684    pub premium_messaging_partitions: Option<i32>,
2685    pub private_endpoint_connection_count: u32,
2686    pub zone_redundant: Option<bool>,
2687    pub created_at: Option<String>,
2688    pub updated_at: Option<String>,
2689}
2690
2691#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2692#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
2693#[serde(rename_all = "camelCase")]
2694pub struct NamedResourceDetail {
2695    pub name: String,
2696    pub region: Option<String>,
2697}
2698
2699#[cfg(test)]
2700mod tests {
2701    use super::*;
2702    use chrono::TimeZone as _;
2703    use serde_json::json;
2704
2705    fn observed_at() -> DateTime<Utc> {
2706        Utc.with_ymd_and_hms(2026, 5, 28, 10, 30, 0).unwrap()
2707    }
2708
2709    fn workload_status() -> WorkloadHeartbeatStatus {
2710        WorkloadHeartbeatStatus {
2711            health: ObservedHealth::Healthy,
2712            lifecycle: ProviderLifecycleState::Running,
2713            message: None,
2714            stale: false,
2715            partial: false,
2716            collection_issues: vec![],
2717        }
2718    }
2719
2720    fn workload_replicas() -> WorkloadReplicaStatus {
2721        WorkloadReplicaStatus {
2722            desired: Some(2),
2723            current: Some(2),
2724            ready: Some(1),
2725            available: Some(1),
2726            updated: Some(2),
2727            misscheduled: None,
2728        }
2729    }
2730
2731    fn heartbeat(data: ResourceHeartbeatData, resource_type: &str) -> ResourceHeartbeat {
2732        ResourceHeartbeat {
2733            deployment_id: Some("dep_123".to_string()),
2734            resource_id: "api".to_string(),
2735            resource_type: ResourceType::from(resource_type),
2736            controller_platform: Platform::Kubernetes,
2737            backend: HeartbeatBackend::Kubernetes,
2738            observed_at: observed_at(),
2739            data,
2740            raw: vec![RawHeartbeatSnippet {
2741                source: "kubernetes/apps/v1/deployments/api".to_string(),
2742                format: RawHeartbeatSnippetFormat::Json,
2743                collected_at: observed_at(),
2744                body: r#"{"readyReplicas":1}"#.to_string(),
2745                truncated: false,
2746            }],
2747        }
2748    }
2749
2750    #[test]
2751    fn resource_heartbeat_roundtrips_managed_resource_data() {
2752        let heartbeat_json = serde_json::to_value(heartbeat(
2753            ResourceHeartbeatData::Container(ContainerHeartbeatData::Kubernetes(
2754                KubernetesContainerHeartbeatData {
2755                    status: workload_status(),
2756                    namespace: "default".to_string(),
2757                    name: "api".to_string(),
2758                    workload_kind: KubernetesWorkloadKind::Deployment,
2759                    replicas: workload_replicas(),
2760                    restarts: None,
2761                    cpu: None,
2762                    memory: None,
2763                    workload: None,
2764                    pods: vec![],
2765                    events: vec![],
2766                },
2767            )),
2768            "container",
2769        ))
2770        .unwrap();
2771
2772        let parsed: ResourceHeartbeat = serde_json::from_value(heartbeat_json).unwrap();
2773        assert_eq!(parsed.resource_id, "api");
2774        assert!(matches!(
2775            parsed.data,
2776            ResourceHeartbeatData::Container(ContainerHeartbeatData::Kubernetes(_))
2777        ));
2778    }
2779
2780    #[test]
2781    fn local_daemon_heartbeat_defaults_missing_daemon_name() {
2782        let mut value =
2783            serde_json::to_value(DaemonHeartbeatData::Local(LocalDaemonHeartbeatData {
2784                status: workload_status(),
2785                daemon_name: "agent".to_string(),
2786                runtime_id: "local-agent".to_string(),
2787                pid: None,
2788                command_supported: false,
2789                image_path_present: true,
2790                restart_count: None,
2791                exit_reason: None,
2792                daemon_instance: None,
2793                events: vec![],
2794            }))
2795            .unwrap();
2796
2797        value.as_object_mut().unwrap().remove("daemonName");
2798
2799        let parsed: DaemonHeartbeatData = serde_json::from_value(value).unwrap();
2800        match parsed {
2801            DaemonHeartbeatData::Local(data) => assert_eq!(data.daemon_name, ""),
2802            other => panic!("expected local daemon heartbeat, got {other:?}"),
2803        }
2804    }
2805
2806    #[test]
2807    fn container_heartbeat_serializes_resource_first_data() {
2808        let heartbeat = heartbeat(
2809            ResourceHeartbeatData::Container(ContainerHeartbeatData::Kubernetes(
2810                KubernetesContainerHeartbeatData {
2811                    status: workload_status(),
2812                    namespace: "default".to_string(),
2813                    name: "api".to_string(),
2814                    workload_kind: KubernetesWorkloadKind::Deployment,
2815                    replicas: workload_replicas(),
2816                    restarts: Some(1),
2817                    cpu: Some(MetricSample {
2818                        value: 0.5,
2819                        unit: MetricUnit::Cores,
2820                    }),
2821                    memory: None,
2822                    workload: None,
2823                    pods: vec![],
2824                    events: vec![],
2825                },
2826            )),
2827            "container",
2828        );
2829
2830        let value = serde_json::to_value(&heartbeat).unwrap();
2831
2832        assert_eq!(value["resourceType"], "container");
2833        assert_eq!(value["data"]["resourceType"], "container");
2834        assert_eq!(value["data"]["data"]["backend"], "kubernetes");
2835        assert_eq!(value["raw"][0]["body"], r#"{"readyReplicas":1}"#);
2836        assert!(value.get("collection").is_none());
2837        assert!(value["data"]["data"].get("summary").is_none());
2838        assert!(value["data"]["data"].get("detail").is_none());
2839    }
2840
2841    #[test]
2842    fn representative_workload_data_has_stable_tags() {
2843        let daemon = serde_json::to_value(ResourceHeartbeatData::Daemon(
2844            DaemonHeartbeatData::Kubernetes(KubernetesDaemonHeartbeatData {
2845                status: workload_status(),
2846                namespace: "default".to_string(),
2847                name: "agent".to_string(),
2848                replicas: workload_replicas(),
2849                restarts: Some(0),
2850                command_supported: true,
2851                cpu: None,
2852                memory: None,
2853                workload: None,
2854                pods: vec![],
2855                events: vec![],
2856            }),
2857        ))
2858        .unwrap();
2859        let worker = serde_json::to_value(ResourceHeartbeatData::Worker(
2860            WorkerHeartbeatData::AwsLambda(AwsLambdaWorkerHeartbeatData {
2861                status: workload_status(),
2862                function_name: "handler".to_string(),
2863                runtime: Some("nodejs22.x".to_string()),
2864                package_type: None,
2865                memory_size_mb: None,
2866                timeout_seconds: None,
2867                version: None,
2868                revision_id: None,
2869                last_modified: None,
2870                state: None,
2871                state_reason: None,
2872                state_reason_code: None,
2873                last_update_status: None,
2874                last_update_status_reason: None,
2875                last_update_status_reason_code: None,
2876                code_sha256: None,
2877                layer_count: 0,
2878                function_url_auth_type: None,
2879                function_url_cors_present: false,
2880                trigger_count: 0,
2881            }),
2882        ))
2883        .unwrap();
2884
2885        assert_eq!(daemon["resourceType"], "daemon");
2886        assert_eq!(daemon["data"]["backend"], "kubernetes");
2887        assert_eq!(worker["resourceType"], "worker");
2888        assert_eq!(worker["data"]["backend"], "awsLambda");
2889    }
2890
2891    /// A variant that compiles can still be unreadable on the wire: the transport round-trips every
2892    /// heartbeat through the generated manager type, which matches on the tag string alone.
2893    #[test]
2894    fn gcp_sandbox_heartbeat_keeps_its_wire_tag_and_camel_case_names() {
2895        let data = ResourceHeartbeatData::Sandbox(SandboxHeartbeatData::GcpAgentPlatform(
2896            GcpAgentPlatformSandboxHeartbeatData {
2897                status: SandboxHeartbeatStatus::default(),
2898                engine: "eng".to_string(),
2899                template_id: "tpl1".to_string(),
2900            },
2901        ));
2902
2903        let value = serde_json::to_value(&data).unwrap();
2904        assert_eq!(
2905            value,
2906            json!({
2907                "resourceType": "sandbox",
2908                "data": {
2909                    "backend": "gcpAgentPlatform",
2910                    "status": {
2911                        "health": "healthy",
2912                        "lifecycle": "running",
2913                        "message": null,
2914                        "stale": false,
2915                        "partial": false,
2916                        "collectionIssues": []
2917                    },
2918                    "engine": "eng",
2919                    "templateId": "tpl1"
2920                }
2921            })
2922        );
2923
2924        let parsed: ResourceHeartbeatData = serde_json::from_value(value).unwrap();
2925        assert_eq!(parsed, data);
2926    }
2927
2928    #[test]
2929    fn representative_cluster_and_data_variants_have_optional_counts() {
2930        let cluster = serde_json::to_value(ResourceHeartbeatData::KubernetesCluster(
2931            KubernetesClusterHeartbeatData {
2932                status: WorkloadHeartbeatStatus {
2933                    health: ObservedHealth::Healthy,
2934                    lifecycle: ProviderLifecycleState::Running,
2935                    message: None,
2936                    stale: false,
2937                    partial: false,
2938                    collection_issues: vec![],
2939                },
2940                node_counts: ObservedCounts {
2941                    desired: Some(3),
2942                    current: Some(3),
2943                    ready: None,
2944                },
2945                pod_counts: ObservedCounts {
2946                    desired: None,
2947                    current: Some(12),
2948                    ready: Some(11),
2949                },
2950                cpu: None,
2951                memory: None,
2952                name: "prod".to_string(),
2953                region: Some("us-east-1".to_string()),
2954                namespace: None,
2955                version: Some("1.33".to_string()),
2956                node_statuses: vec![],
2957                events: vec![],
2958            },
2959        ))
2960        .unwrap();
2961        let queue = serde_json::to_value(ResourceHeartbeatData::Queue(QueueHeartbeatData::AwsSqs(
2962            AwsSqsQueueHeartbeatData {
2963                status: QueueHeartbeatStatus {
2964                    health: ObservedHealth::Healthy,
2965                    lifecycle: ProviderLifecycleState::Running,
2966                    message: None,
2967                    stale: false,
2968                    partial: false,
2969                    collection_issues: vec![],
2970                },
2971                name: "jobs".to_string(),
2972                region: Some("us-east-1".to_string()),
2973                queue_url: Some("https://sqs.us-east-1.amazonaws.com/123/jobs".to_string()),
2974                queue_arn: Some("arn:aws:sqs:us-east-1:123:jobs".to_string()),
2975                visibility_timeout_seconds: Some(30),
2976                message_retention_period_seconds: Some(345600),
2977                delay_seconds: Some(0),
2978                receive_message_wait_time_seconds: Some(0),
2979                maximum_message_size: Some(262144),
2980                redrive_policy: None,
2981                redrive_allow_policy: None,
2982                fifo_queue: Some(false),
2983                content_based_deduplication: None,
2984                deduplication_scope: None,
2985                fifo_throughput_limit: None,
2986                sse_enabled: Some(false),
2987                kms_master_key_id: None,
2988                kms_data_key_reuse_period_seconds: None,
2989                sqs_managed_sse_enabled: Some(false),
2990                approximate_visible_messages: Some(42),
2991                approximate_in_flight_messages: Some(1),
2992                approximate_delayed_messages: Some(0),
2993                approximate_counts: true,
2994            },
2995        )))
2996        .unwrap();
2997        let storage = serde_json::to_value(ResourceHeartbeatData::Storage(
2998            StorageHeartbeatData::AwsS3(AwsS3StorageHeartbeatData {
2999                status: StorageHeartbeatStatus {
3000                    health: ObservedHealth::Healthy,
3001                    lifecycle: ProviderLifecycleState::Running,
3002                    message: None,
3003                    stale: false,
3004                    partial: false,
3005                    collection_issues: vec![],
3006                },
3007                name: "assets".to_string(),
3008                region: Some("us-east-1".to_string()),
3009                bucket_location: Some("us-east-1".to_string()),
3010                versioning_status: Some("Enabled".to_string()),
3011                versioning_enabled: Some(true),
3012                lifecycle_present: false,
3013                lifecycle_rule_count: Some(0),
3014                encryption_config_present: true,
3015                encryption_enabled: Some(true),
3016                public_access_block_present: true,
3017                block_public_acls: Some(true),
3018                ignore_public_acls: Some(true),
3019                block_public_policy: Some(true),
3020                restrict_public_buckets: Some(true),
3021                bucket_policy_present: Some(false),
3022                bucket_acl_present: Some(true),
3023            }),
3024        ))
3025        .unwrap();
3026        let gcp_storage = serde_json::to_value(ResourceHeartbeatData::Storage(
3027            StorageHeartbeatData::GcpCloudStorage(GcpCloudStorageHeartbeatData {
3028                status: StorageHeartbeatStatus {
3029                    health: ObservedHealth::Healthy,
3030                    lifecycle: ProviderLifecycleState::Running,
3031                    message: None,
3032                    stale: false,
3033                    partial: false,
3034                    collection_issues: vec![],
3035                },
3036                name: "assets".to_string(),
3037                bucket_id: Some("project/assets".to_string()),
3038                location: Some("US".to_string()),
3039                location_type: Some("multi-region".to_string()),
3040                storage_class: Some("STANDARD".to_string()),
3041                versioning_enabled: Some(true),
3042                lifecycle_present: false,
3043                lifecycle_rule_count: Some(0),
3044                retention_policy_effective_time: None,
3045                retention_policy_is_locked: None,
3046                retention_period: None,
3047                soft_delete_retention_duration_seconds: None,
3048                soft_delete_effective_time: None,
3049                uniform_bucket_level_access_enabled: Some(true),
3050                uniform_bucket_level_access_locked_time: None,
3051                public_access_prevention: Some("enforced".to_string()),
3052                encryption_config_present: true,
3053                default_kms_key_name: Some(
3054                    "projects/p/locations/l/keyRings/r/cryptoKeys/k".to_string(),
3055                ),
3056            }),
3057        ))
3058        .unwrap();
3059        let kv = serde_json::to_value(ResourceHeartbeatData::Kv(KvHeartbeatData::AwsDynamoDb(
3060            AwsDynamoDbKvHeartbeatData {
3061                status: KvHeartbeatStatus {
3062                    health: ObservedHealth::Healthy,
3063                    lifecycle: ProviderLifecycleState::Running,
3064                    message: None,
3065                    stale: false,
3066                    partial: false,
3067                    collection_issues: vec![],
3068                },
3069                name: "state".to_string(),
3070                region: Some("us-east-1".to_string()),
3071                table_arn: None,
3072                table_status: Some("ACTIVE".to_string()),
3073                billing_mode: Some("PAY_PER_REQUEST".to_string()),
3074                key_schema: vec![AwsDynamoDbKeySchemaElement {
3075                    attribute_name: "pk".to_string(),
3076                    key_type: "HASH".to_string(),
3077                }],
3078                global_secondary_index_count: Some(0),
3079                local_secondary_index_count: Some(0),
3080                item_count: None,
3081                table_size_bytes: None,
3082                stream_enabled: Some(false),
3083                stream_view_type: None,
3084                ttl_status: Some("ENABLED".to_string()),
3085                ttl_attribute_name: Some("ttl".to_string()),
3086                deletion_protection_enabled: Some(false),
3087                sse_status: Some("ENABLED".to_string()),
3088                sse_type: Some("KMS".to_string()),
3089                table_class: None,
3090                replica_count: Some(0),
3091                restore_in_progress: None,
3092            },
3093        )))
3094        .unwrap();
3095
3096        assert_eq!(cluster["resourceType"], "kubernetes-cluster");
3097        assert!(cluster["data"].get("summary").is_none());
3098        assert!(cluster["data"].get("detail").is_none());
3099        assert_eq!(cluster["data"]["name"], "prod");
3100        assert_eq!(queue["data"]["backend"], "awsSqs");
3101        assert_eq!(queue["data"]["approximateVisibleMessages"], 42);
3102        assert!(queue["data"].get("summary").is_none());
3103        assert_eq!(storage["data"]["backend"], "awsS3");
3104        assert!(storage["data"].get("summary").is_none());
3105        assert_eq!(gcp_storage["data"]["backend"], "gcpCloudStorage");
3106        assert_eq!(gcp_storage["data"]["publicAccessPrevention"], "enforced");
3107        assert_eq!(kv["data"]["backend"], "awsDynamoDb");
3108        assert!(kv["data"].get("summary").is_none());
3109        assert_eq!(kv["data"]["itemCount"], json!(null));
3110    }
3111
3112    #[test]
3113    fn ai_heartbeat_serializes_stable_tags() {
3114        let aws = serde_json::to_value(ResourceHeartbeatData::Ai(AiHeartbeatData::AwsBedrock(
3115            AwsBedrockAiHeartbeatData {
3116                status: AiHeartbeatStatus::default(),
3117                region: "us-east-1".to_string(),
3118                availability: AiAvailabilityObservation::unobserved(
3119                    AiAvailabilitySource::AwsBedrock,
3120                    Platform::Aws,
3121                    Some("us-east-1".to_string()),
3122                ),
3123            },
3124        )))
3125        .unwrap();
3126        let gcp = serde_json::to_value(ResourceHeartbeatData::Ai(AiHeartbeatData::GcpVertex(
3127            GcpVertexAiHeartbeatData {
3128                status: AiHeartbeatStatus::default(),
3129                project: "my-project".to_string(),
3130                location: "us-central1".to_string(),
3131                availability: AiAvailabilityObservation::unobserved(
3132                    AiAvailabilitySource::GcpVertex,
3133                    Platform::Gcp,
3134                    Some("us-central1".to_string()),
3135                ),
3136            },
3137        )))
3138        .unwrap();
3139        let azure = serde_json::to_value(ResourceHeartbeatData::Ai(AiHeartbeatData::AzureFoundry(
3140            AzureFoundryAiHeartbeatData {
3141                status: AiHeartbeatStatus::default(),
3142                account_name: "my-ai-account".to_string(),
3143                endpoint: Some("https://my-ai-account.openai.azure.com/".to_string()),
3144                resource_group: Some("my-rg".to_string()),
3145                location: Some("eastus".to_string()),
3146                availability: AiAvailabilityObservation::unobserved(
3147                    AiAvailabilitySource::AzureFoundry,
3148                    Platform::Azure,
3149                    Some("eastus".to_string()),
3150                ),
3151            },
3152        )))
3153        .unwrap();
3154
3155        assert_eq!(aws["resourceType"], "ai");
3156        assert_eq!(aws["data"]["backend"], "awsBedrock");
3157        assert_eq!(aws["data"]["region"], "us-east-1");
3158        assert!(aws["data"].get("summary").is_none());
3159
3160        assert_eq!(gcp["resourceType"], "ai");
3161        assert_eq!(gcp["data"]["backend"], "gcpVertex");
3162        assert_eq!(gcp["data"]["project"], "my-project");
3163        assert_eq!(gcp["data"]["location"], "us-central1");
3164
3165        assert_eq!(azure["resourceType"], "ai");
3166        assert_eq!(azure["data"]["backend"], "azureFoundry");
3167        assert_eq!(azure["data"]["accountName"], "my-ai-account");
3168        assert_eq!(
3169            azure["data"]["endpoint"],
3170            "https://my-ai-account.openai.azure.com/"
3171        );
3172    }
3173}