//! `WorkflowServiceImpl` — backs the Rhai `workflow::start(name, input)` SDK //! call (v1.2 Workflows M5). //! //! Resolves a workflow **by name in the caller's app** (`cx.app_id` — never a //! script-passed arg, the isolation boundary), seeds a durable run, and returns //! its id. The dedicated orchestrator advances the run from there. Same-app //! only, mirroring `InvokeServiceImpl`. //! //! Like `invoke()`, an authenticated caller is gated on `AppInvoke`; an //! anonymous caller skips the check under the script-as-gate convention (the //! public script IS the authorization boundary). use std::sync::Arc; use async_trait::async_trait; use picloud_shared::{SdkCallCx, WorkflowError, WorkflowRunId, WorkflowService}; use sqlx::PgPool; use crate::authz::{self, AuthzRepo, Capability}; use crate::workflow_repo::{get_workflow_by_name, start_run, NewWorkflowRun}; pub struct WorkflowServiceImpl { pool: PgPool, /// Optional. When set, `start` requires `AppInvoke` for authenticated /// callers; anonymous callers skip the check (script-as-gate). authz: Option>, } impl WorkflowServiceImpl { #[must_use] pub fn new(pool: PgPool) -> Self { Self { pool, authz: None } } /// Attach the authz repo so authenticated callers are gated on `AppInvoke`. #[must_use] pub fn with_authz(mut self, authz: Arc) -> Self { self.authz = Some(authz); self } async fn check(&self, cx: &SdkCallCx) -> Result<(), WorkflowError> { let Some(authz_repo) = self.authz.as_ref() else { return Ok(()); }; authz::script_gate( authz_repo.as_ref(), cx, Capability::AppInvoke(cx.app_id), || WorkflowError::Forbidden, WorkflowError::Backend, ) .await } } #[async_trait] impl WorkflowService for WorkflowServiceImpl { async fn start( &self, cx: &SdkCallCx, name: &str, input: serde_json::Value, ) -> Result { self.check(cx).await?; let wf = get_workflow_by_name(&self.pool, cx.app_id, name) .await .map_err(|e| WorkflowError::Backend(e.to_string()))? .ok_or_else(|| WorkflowError::NotFound(name.to_string()))?; if !wf.enabled { return Err(WorkflowError::Disabled(name.to_string())); } let new = NewWorkflowRun { workflow_id: wf.id, app_id: cx.app_id, input, // Correlate the run's step logs under the caller's execution chain. root_execution_id: cx.root_execution_id.into_inner(), workflow_depth: 0, parent_run_id: None, parent_step_id: None, }; start_run(&self.pool, new, &wf.definition) .await .map_err(|e| WorkflowError::Backend(e.to_string())) } }