From bae381a6ef5b3c051175e5a8e69f32308e4e8b3d Mon Sep 17 00:00:00 2001 From: Alon Gubkin Date: Mon, 21 Sep 2026 08:45:48 -0700 Subject: [PATCH 1/2] fix: reject incompatible worker architectures --- crates/alien-build/src/plan.rs | 95 +++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 12 deletions(-) diff --git a/crates/alien-build/src/plan.rs b/crates/alien-build/src/plan.rs index 0e941f4cbe..03b0f546c1 100644 --- a/crates/alien-build/src/plan.rs +++ b/crates/alien-build/src/plan.rs @@ -268,7 +268,7 @@ fn resolved_compute_architecture( stack: &Stack, platform: Platform, ) -> crate::error::Result { - let architectures = stack + let mut architectures = stack .resources() .filter_map(|(_, entry)| entry.config.downcast_ref::()) .flat_map(|cluster| cluster.capacity_groups.iter()) @@ -294,24 +294,40 @@ fn resolved_compute_architecture( }) }) .collect::>>()?; - if architectures.is_empty() { - return instance_catalog::default_architecture(platform).ok_or_else(|| { - AlienError::new(crate::error::ErrorData::BuildConfigInvalid { - message: format!("managed cloud {platform} has no default compute architecture"), - }) - }); - } - let mut architectures = architectures.into_iter().collect::>(); architectures.sort_by_key(|architecture| match architecture { Architecture::Arm64 => 0, Architecture::X86_64 => 1, }); architectures.dedup(); - match architectures.as_slice() { - [architecture] => Ok(*architecture), - _ => Err(AlienError::new(crate::error::ErrorData::BuildConfigInvalid { + + let compute_architecture = match architectures.as_slice() { + [] => None, + [architecture] => Some(*architecture), + _ => return Err(AlienError::new(crate::error::ErrorData::BuildConfigInvalid { message: "compute pools require mixed CPU architectures; one platform image cannot satisfy both".to_string(), })), + }; + let worker_architecture = (platform == Platform::Aws + && stack + .resources() + .any(|(_, entry)| entry.config.downcast_ref::().is_some())) + .then_some(Architecture::Arm64); + + match (compute_architecture, worker_architecture) { + (Some(compute), Some(worker)) if compute != worker => Err(AlienError::new( + crate::error::ErrorData::BuildConfigInvalid { + message: format!( + "AWS Worker requires {worker:?}, but compute pools require {compute:?}; one platform image cannot satisfy both" + ), + }, + )), + (Some(compute), _) => Ok(compute), + (None, Some(worker)) => Ok(worker), + (None, None) => instance_catalog::default_architecture(platform).ok_or_else(|| { + AlienError::new(crate::error::ErrorData::BuildConfigInvalid { + message: format!("managed cloud {platform} has no default compute architecture"), + }) + }), } } @@ -446,6 +462,30 @@ mod tests { stack_with(cluster.build()) } + fn stack_with_worker_and_architecture(architecture: Architecture) -> Stack { + let cluster = ComputeCluster::new("runtime".to_string()) + .capacity_group(CapacityGroup { + group_id: "pool".to_string(), + instance_type: None, + profile: Some(MachineProfile { + cpu: "1".to_string(), + memory_bytes: 2 * 1024 * 1024 * 1024, + ephemeral_storage_bytes: 20 * 1024 * 1024 * 1024, + architecture: Some(architecture), + gpu: None, + }), + min_size: 1, + max_size: 1, + scale_policy: None, + nested_virtualization: None, + }) + .build(); + Stack::new("plan-test".to_string()) + .add(worker_with(rust_source()), ResourceLifecycle::Live) + .add(cluster, ResourceLifecycle::Live) + .build() + } + #[test] fn aws_explicit_x86_uses_x64_runner() { let stack = stack_with_architectures(&[Architecture::X86_64]); @@ -464,6 +504,37 @@ mod tests { ); } + #[test] + fn aws_worker_without_compute_constraint_requires_arm64() { + let stack = stack_with(worker_with(rust_source())); + + assert_eq!( + resolve_targets_for_stack_platform(&stack, Aws, None).expect("target should resolve"), + vec![BinaryTarget::LinuxArm64] + ); + } + + #[test] + fn aws_worker_and_arm64_compute_share_one_target() { + let stack = stack_with_worker_and_architecture(Architecture::Arm64); + + assert_eq!( + resolve_targets_for_stack_platform(&stack, Aws, None).expect("target should resolve"), + vec![BinaryTarget::LinuxArm64] + ); + } + + #[test] + fn aws_worker_and_x86_compute_are_rejected_before_build() { + let stack = stack_with_worker_and_architecture(Architecture::X86_64); + + let error = resolve_targets_for_stack_platform(&stack, Aws, None) + .expect_err("incompatible fixed architectures should fail"); + let message = error.to_string(); + assert!(message.contains("AWS Worker requires Arm64")); + assert!(message.contains("compute pools require X86_64")); + } + #[test] fn gcp_and_azure_without_compute_constraint_keep_x64_default() { let stack = Stack::new("unconstrained".to_string()).build(); From 46e43a40d2eed09fb08ae04c5b8ce7c059565265 Mon Sep 17 00:00:00 2001 From: Alon Gubkin Date: Mon, 21 Sep 2026 08:52:45 -0700 Subject: [PATCH 2/2] fix: validate architecture in shared build path --- crates/alien-build/src/lib.rs | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/alien-build/src/lib.rs b/crates/alien-build/src/lib.rs index d2b8f92f32..20ac30b3ec 100644 --- a/crates/alien-build/src/lib.rs +++ b/crates/alien-build/src/lib.rs @@ -135,6 +135,15 @@ pub async fn build_stack(mut stack: Stack, settings: &BuildSettings) -> Result