Skip to main content

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