alien_core/deployment/state.rs
1//! Deployment state, step results, and runtime metadata.
2
3use crate::{Platform, ResourceHeartbeat, StackState};
4use alien_error::AlienError;
5use bon::Builder;
6use serde::{Deserialize, Serialize};
7
8use super::{DeploymentStatus, EnvironmentInfo, ReleaseInfo};
9
10/// Runtime metadata for deployment
11///
12/// Stores deployment state that needs to persist across step calls.
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
14#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
15#[serde(rename_all = "camelCase")]
16pub struct RuntimeMetadata {
17 /// Hash of the environment variables snapshot that was last synced to the vault
18 /// Used to avoid redundant sync operations during incremental deployment
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub last_synced_env_vars_hash: Option<String>,
21
22 /// The prepared (mutated) stack from the last successful deployment phase
23 /// This is the stack AFTER mutations have been applied (with service accounts, vault, etc.)
24 /// Used for compatibility checks during updates to compare mutated stacks
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub prepared_stack: Option<crate::Stack>,
27
28 /// Whether cross-account registry access has been successfully granted.
29 /// Set to true after the manager successfully sets the ECR/GAR repo policy
30 /// for this deployment's target account. Prevents redundant API calls on
31 /// every reconcile tick.
32 #[serde(default, skip_serializing_if = "is_false")]
33 pub registry_access_granted: bool,
34}
35
36/// Deployment state
37///
38/// Represents the current state of deployed infrastructure, including release tracking.
39/// This is platform-agnostic - no backend IDs or database relationships.
40///
41/// The deployment engine manages releases internally: when a deployment succeeds,
42/// it promotes `target_release` to `current_release` and clears `target_release`.
43#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
44#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
45#[serde(rename_all = "camelCase")]
46pub struct DeploymentState {
47 /// Current lifecycle phase
48 pub status: DeploymentStatus,
49 /// Target cloud platform (AWS, GCP, Azure, Kubernetes)
50 pub platform: Platform,
51 /// Currently deployed release (None for first deployment)
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub current_release: Option<ReleaseInfo>,
54 /// Target release to deploy (None when synced with current)
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub target_release: Option<ReleaseInfo>,
57 /// Infrastructure resource tracking (which resources exist, their status, outputs)
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub stack_state: Option<StackState>,
60 /// Deployment-level error for failures not owned by a specific resource.
61 ///
62 /// Resource controller failures belong in `stack_state.resources[*].error`.
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub error: Option<AlienError>,
65 /// Cloud account details (account ID, project number, region)
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub environment_info: Option<EnvironmentInfo>,
68 /// Deployment-specific data (prepared stacks, phase tracking, etc.)
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub runtime_metadata: Option<RuntimeMetadata>,
71 /// Whether a retry has been requested for a failed deployment
72 /// When true and status is a failed state, the deployment system will retry failed resources
73 #[serde(default, skip_serializing_if = "is_false")]
74 pub retry_requested: bool,
75 /// Protocol version for cross-actor compatibility.
76 /// All actors (manager, push client, agent) check this before stepping.
77 /// Mismatched versions produce a clear error instead of silent corruption.
78 /// See docs/02-manager/10-deployment-protocol.md.
79 pub protocol_version: u32,
80}
81
82/// Result of a deployment step
83///
84/// Contains the complete next deployment state along with hints for the platform.
85/// This replaces the old delta-based `DeploymentStateUpdate` approach.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
88#[serde(rename_all = "camelCase")]
89pub struct DeploymentStepResult {
90 /// The complete next deployment state
91 pub state: DeploymentState,
92
93 /// Suggested delay before next step (optimization hint)
94 /// - `None`: No suggested delay, can poll immediately
95 /// - `Some(ms)`: Wait this many milliseconds before next step
96 #[serde(skip_serializing_if = "Option::is_none")]
97 pub suggested_delay_ms: Option<u64>,
98
99 /// Whether to update heartbeat timestamp (monitoring signal)
100 /// - `false`: Don't update heartbeat (default for most steps)
101 /// - `true`: Update lastHeartbeatAt (for successful health checks in Running state)
102 #[serde(default, skip_serializing_if = "is_false")]
103 pub update_heartbeat: bool,
104
105 /// Typed resource heartbeats emitted by controllers during this step.
106 #[serde(default, skip_serializing_if = "Vec::is_empty")]
107 pub heartbeats: Vec<ResourceHeartbeat>,
108}
109
110pub(crate) fn is_false(b: &bool) -> bool {
111 !*b
112}
113
114/// Oldest deployment protocol version this binary can read.
115pub const MIN_SUPPORTED_DEPLOYMENT_PROTOCOL_VERSION: u32 = 1;
116
117/// Deployment protocol version this binary writes.
118/// Bump when making incompatible changes to DeploymentState semantics.
119pub const CURRENT_DEPLOYMENT_PROTOCOL_VERSION: u32 = 1;
120
121/// Backwards-compatible alias for older call sites.
122pub const DEPLOYMENT_PROTOCOL_VERSION: u32 = CURRENT_DEPLOYMENT_PROTOCOL_VERSION;