Fix Galaxy orchestration denial and controls

This commit is contained in:
2026-08-15 08:37:57 -05:00
parent 642cb6adc1
commit 2730712179
14 changed files with 348 additions and 386 deletions
@@ -695,6 +695,7 @@ fn prepare_request_for_execution(
return Some(reason);
}
normalize_request_for_local_execution(request);
let status = resolve_request_from_approved_config(request, parent_conversation_id, ctx);
populate_default_auth_secret_for_execution(request, ctx);
if let Some(reason) =
@@ -885,6 +886,18 @@ fn populate_default_auth_secret_for_execution(
default_auth_secret_name_for_harness(&request.harness_type, ctx);
}
fn normalize_request_for_local_execution(request: &mut RunAgentsRequest) {
let edit_state = OrchestrationEditState::from_run_agents_fields(
&request.model_id,
&request.harness_type,
&request.execution_mode,
);
request.model_id = edit_state.model_id;
request.harness_type = edit_state.harness_type;
request.execution_mode = RunAgentsExecutionMode::Local;
request.harness_auth_secret_name = None;
}
/// Unconditionally overrides run-wide fields on a `RunAgentsRequest`
/// from the approved orchestration config, delegating to
/// `OrchestrationEditState::override_from_approved_config`.
@@ -908,6 +921,9 @@ fn validate_request(request: &RunAgentsRequest) -> Result<(), String> {
if request.agent_run_configs.is_empty() {
return Err("orchestrate: empty agent_run_configs".to_string());
}
if request.execution_mode.is_remote() {
return Err("Galaxy only supports local child-agent orchestration.".to_string());
}
let mut normalized_names = HashSet::new();
for config in &request.agent_run_configs {
@@ -292,6 +292,7 @@ fn validate_request_rejects_blank_and_duplicate_agent_names() {
let AIAgentActionType::RunAgents(mut request) = remote_run_agents_action("oz").action else {
panic!("expected run_agents action");
};
normalize_request_for_local_execution(&mut request);
request.agent_run_configs[0].name = " ".to_string();
assert_eq!(
validate_request(&request),
@@ -315,6 +316,7 @@ fn validate_request_allows_unique_sibling_names() {
let AIAgentActionType::RunAgents(mut request) = remote_run_agents_action("oz").action else {
panic!("expected run_agents action");
};
normalize_request_for_local_execution(&mut request);
request.agent_run_configs.push(RunAgentsAgentRunConfig {
name: "second-child".to_string(),
prompt: "Do separate work".to_string(),
@@ -324,6 +326,38 @@ fn validate_request_allows_unique_sibling_names() {
assert_eq!(validate_request(&request), Ok(()));
}
#[test]
fn local_normalization_clears_remote_only_fields_and_disabled_harness() {
let AIAgentActionType::RunAgents(mut request) = remote_run_agents_action("codex").action else {
panic!("expected run_agents action");
};
request.model_id = "gpt-5".to_string();
request.harness_auth_secret_name = Some("remote-secret".to_string());
normalize_request_for_local_execution(&mut request);
assert!(matches!(
request.execution_mode,
RunAgentsExecutionMode::Local
));
assert_eq!(request.harness_type, "oz");
assert_eq!(request.model_id, "");
assert_eq!(request.harness_auth_secret_name, None);
assert_eq!(validate_request(&request), Ok(()));
}
#[test]
fn validate_request_rejects_remote_dispatch() {
let AIAgentActionType::RunAgents(request) = remote_run_agents_action("oz").action else {
panic!("expected run_agents action");
};
assert_eq!(
validate_request(&request),
Err("Galaxy only supports local child-agent orchestration.".to_string())
);
}
fn initialize_run_agents_test(app: &mut App, mode: ExecutionMode) -> RunAgentsTestState {
initialize_settings_for_tests_with_mode(app, mode, false);
let global_resource_handles = GlobalResourceHandles::mock(app);
@@ -486,7 +520,7 @@ fn should_autoexecute_when_plan_has_approved_orchestration_config() {
}
#[test]
fn should_not_autoexecute_approved_remote_non_warp_plan_without_default_auth_secret() {
fn approved_remote_plan_is_normalized_and_can_autoexecute_locally() {
App::test((), |mut app| async move {
let state = initialize_run_agents_test(&mut app, ExecutionMode::App);
persist_plan_config_with_harness(
@@ -508,7 +542,7 @@ fn should_not_autoexecute_approved_remote_non_warp_plan_without_default_auth_sec
)
});
assert!(!should_autoexecute);
assert!(should_autoexecute);
});
}
@@ -782,7 +816,7 @@ fn should_not_autoexecute_without_approved_plan_or_always_allow_profile() {
}
#[test]
fn execute_denies_remote_non_warp_harness_without_default_auth_secret() {
fn execute_normalizes_remote_non_oz_harness_without_requiring_remote_auth() {
App::test((), |mut app| async move {
let state = initialize_run_agents_test(&mut app, ExecutionMode::App);
let action = remote_run_agents_action("codex");
@@ -799,21 +833,12 @@ fn execute_denies_remote_non_warp_harness_without_default_auth_secret() {
.into()
});
let AnyActionExecution::Sync(AIAgentActionResultType::RunAgents(RunAgentsResult::Denied {
reason,
})) = execution
else {
panic!("expected synchronous run_agents denial");
};
assert_eq!(
reason,
"Cloud child agents using this harness require an API key before they can run."
);
assert!(matches!(execution, AnyActionExecution::Async { .. }));
});
}
#[test]
fn should_autoexecute_remote_non_warp_harness_with_always_allow_even_without_default_auth_secret() {
fn normalized_remote_non_oz_harness_autoexecutes_with_always_allow() {
App::test((), |mut app| async move {
let state = initialize_run_agents_test(&mut app, ExecutionMode::App);
set_run_agents_permission(&mut app, RunAgentsPermission::AlwaysAllow);
@@ -834,7 +859,7 @@ fn should_autoexecute_remote_non_warp_harness_with_always_allow_even_without_def
}
#[test]
fn should_autoexecute_remote_non_warp_harness_with_default_auth_secret() {
fn normalized_remote_non_oz_harness_ignores_default_auth_secret() {
App::test((), |mut app| async move {
let state = initialize_run_agents_test(&mut app, ExecutionMode::App);
set_run_agents_permission(&mut app, RunAgentsPermission::AlwaysAllow);
@@ -856,7 +881,7 @@ fn should_autoexecute_remote_non_warp_harness_with_default_auth_secret() {
}
#[test]
fn should_autoexecute_remote_warp_harness_without_default_auth_secret() {
fn normalized_remote_oz_harness_autoexecutes_without_default_auth_secret() {
App::test((), |mut app| async move {
let state = initialize_run_agents_test(&mut app, ExecutionMode::App);
set_run_agents_permission(&mut app, RunAgentsPermission::AlwaysAllow);