Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions crates/alien-deployment/src/updating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use crate::{
DeploymentConfig, DeploymentState, DeploymentStatus, DeploymentStepResult, ErrorData, Result,
};
use alien_core::{
ComputeClusterOutputs, Platform, ResourceLifecycle, Stack, StackState, StackStatus,
ComputeClusterOutputs, Platform, ResourceLifecycle, ResourceStatus, Stack, StackState,
StackStatus,
};
use alien_error::{AlienError, Context};
use alien_infra::StackExecutor;
Expand All @@ -19,6 +20,28 @@ fn machines_deployment_has_zero_machines(platform: Platform, stack_state: &Stack
})
}

fn compute_update_status(stack_state: &StackState, target_stack: &Stack) -> Result<StackStatus> {
let statuses = stack_state
.resources
.iter()
.filter_map(|(resource_id, resource)| {
(target_stack.resources.contains_key(resource_id)
|| resource.status != ResourceStatus::Deleted)
.then_some(resource.status)
})
.collect::<Vec<_>>();

if statuses.is_empty() {
return Ok(StackStatus::Running);
}
Comment on lines +34 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Empty Target Becomes Running

When an update has an empty target stack and its last resource reaches Deleted, the filter removes every status and this branch reports Running. The previous aggregation reported an all-deleted state as Deleted, so a whole-stack removal submitted through the update path can promote an empty deployment as running instead of preserving deletion semantics.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/alien-deployment/src/updating.rs
Line: 34-36

Comment:
**Empty Target Becomes Running**

When an update has an empty target stack and its last resource reaches `Deleted`, the filter removes every status and this branch reports `Running`. The previous aggregation reported an all-deleted state as `Deleted`, so a whole-stack removal submitted through the update path can promote an empty deployment as running instead of preserving deletion semantics.

How can I resolve this? If you propose a fix, please make it concise.


StackState::compute_stack_status_from_resources(&statuses).context(
ErrorData::StackExecutionFailed {
message: "Failed to compute update status".to_string(),
},
)
}

/// Handle UpdatePending → Updating transition
///
/// This step:
Expand Down Expand Up @@ -218,13 +241,7 @@ pub async fn handle_updating(
})?;

// Compute the stack status from the resulting state
let stack_status =
step_result
.next_state
.compute_stack_status()
.context(ErrorData::StackExecutionFailed {
message: "Failed to compute stack status".to_string(),
})?;
let stack_status = compute_update_status(&step_result.next_state, &target_stack)?;

// Check if update is complete
let waiting_for_machines =
Expand Down
54 changes: 54 additions & 0 deletions crates/alien-deployment/tests/test_platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,60 @@ async fn setup_authorized_update_clears_authority_only_on_success() {
);
}

#[tokio::test]
async fn update_completes_after_removed_resource_is_deleted() {
let config = create_test_config("hash_v1", false);
let mut stack_v1 = create_test_stack("test-stack", "function-a");
let function_b = Worker::new("function-b".to_string())
.code(WorkerCode::Image {
image: "test:latest".to_string(),
})
.permissions("default".to_string())
.build();
stack_v1.resources.insert(
"function-b".to_string(),
ResourceEntry {
config: alien_core::Resource::new(function_b),
lifecycle: ResourceLifecycle::Live,
dependencies: Vec::new(),
remote_access: false,
},
);

let mut state = run_to_completion(create_initial_state(stack_v1), config.clone()).await;
let stack_v2 = create_test_stack("test-stack", "function-a");
start_update(
&mut state,
ReleaseInfo {
release_id: Some("rel_v2".to_string()),
version: Some("2.0.0".to_string()),
description: None,
stack: stack_v2,
},
);

let completed = run_to_completion(state, config).await;

assert_eq!(completed.status, DeploymentStatus::Running);
assert_eq!(
completed
.stack_state
.as_ref()
.unwrap()
.resources
.get("function-b")
.unwrap()
.status,
alien_core::ResourceStatus::Deleted,
"the executor's deletion tombstone should be preserved"
);
assert_eq!(
completed.current_release.as_ref().unwrap().release_id,
Some("rel_v2".to_string())
);
assert!(completed.target_release.is_none());
}

#[tokio::test]
async fn stale_waiting_for_machines_returns_to_updating() {
let stack_v1 = create_test_stack("test-stack", "test-function");
Expand Down
Loading