Skip to main content

soma_palette/
execute.rs

1//! Product launcher execution and auth policy for
2//! `POST /v1/palette/execute`.
3//!
4//! Dispatch itself (`SomaApplication::execute_action`) does not know about
5//! Palette surface exposure — that policy is `ToolSpec.palette` overlay data,
6//! which only the surface layers interpret. This module is the Palette
7//! surface's enforcement point: an id that isn't (or is no longer)
8//! palette-exposed is rejected here as `launcher_not_found`, before it ever
9//! reaches the application layer.
10
11use soma_application::{ApplicationError, ExecuteActionRequest, ExecutionContext};
12use soma_domain::Confirmation;
13
14use crate::{
15    dto::{LauncherExecuteRequest, LauncherExecuteResponse},
16    schema::find_schema,
17    state::PaletteState,
18};
19
20pub enum ExecuteOutcome {
21    Ok(LauncherExecuteResponse),
22    NotFound,
23    Failed(ApplicationError),
24}
25
26/// Resolve and dispatch a Palette launcher execute request. Thin by design:
27/// verify the id is still a palette-exposed action, set destructive
28/// confirmation from the request, and delegate to
29/// `SomaApplication::execute_action` for everything else (scope, admin,
30/// destructive, and capability enforcement all live there).
31pub async fn execute_launcher(
32    state: &PaletteState,
33    request: LauncherExecuteRequest,
34    mut context: ExecutionContext,
35) -> ExecuteOutcome {
36    let snapshot = match state.application().refresh_providers() {
37        Ok(snapshot) => snapshot,
38        Err(error) => return ExecuteOutcome::Failed(error),
39    };
40    if find_schema(&snapshot, &request.id).is_none() {
41        return ExecuteOutcome::NotFound;
42    }
43
44    context.destructive_confirmation = confirmation_for(request.confirm_destructive);
45
46    let action_request = ExecuteActionRequest {
47        action: request.id,
48        params: request.params,
49    };
50
51    match state
52        .application()
53        .execute_action(action_request, context)
54        .await
55    {
56        Ok(response) => ExecuteOutcome::Ok(LauncherExecuteResponse {
57            output: response.output,
58            request_id: response.request_id,
59        }),
60        Err(error) => ExecuteOutcome::Failed(error),
61    }
62}
63
64/// Pure translation of the request's `confirmDestructive` flag into domain
65/// `Confirmation`. Split out from [`execute_launcher`] (which additionally
66/// needs a live `SomaApplication` to resolve/dispatch) so the policy is
67/// unit-testable on its own.
68#[must_use]
69fn confirmation_for(confirm_destructive: bool) -> Confirmation {
70    if confirm_destructive {
71        Confirmation::Confirmed
72    } else {
73        Confirmation::Missing
74    }
75}
76
77#[cfg(test)]
78#[path = "execute_tests.rs"]
79mod tests;