Approval Workflow
This example shows how to pause a durable execution and wait for a human decision before proceeding. The workflow validates a resource-access request, parks itself waiting for manager approval, then either provisions the resource or records the rejection — depending on what the manager decides.
Features demonstrated: #[zart_durable], #[zart_step], zart::wait_for_event(), durable.offer_event().
Data types
Section titled “Data types”#[derive(Debug, Clone, Serialize, Deserialize)]struct ApprovalRequest { requester_name: String, resource: String, reason: String,}
#[derive(Debug, Clone, Serialize, Deserialize)]struct ApprovalDecision { approved: bool, reviewer: String, comment: String,}
#[derive(Debug, Clone, Serialize, Deserialize)]struct ApprovalOutput { decision: String, // "approved" | "rejected" requester: String, resource: String, reviewer: String, comment: String,}Step definitions
Section titled “Step definitions”use zart::prelude::*;use zart::zart_step;
#[zart_step("validate-request")]async fn validate_request(request: &ApprovalRequest) -> Result<String, StepError> { if request.requester_name.is_empty() { return Err(StepError::Failed { step: "validate-request".into(), reason: "requester name is required".into(), }); } Ok(format!("Validated request from {} for {}", request.requester_name, request.resource))}
#[zart_step("process-approved")]async fn process_approved(resource: &str, requester: &str) -> Result<String, StepError> { // In a real system, this would provision the resource Ok(format!("Provisioned {} for {}", resource, requester))}The workflow
Section titled “The workflow”The durable handler composes steps and uses zart::wait_for_event to pause for the manager’s decision.
#[zart_durable("approval-task", timeout = "5m")]async fn approval_task(data: ApprovalRequest) -> Result<ApprovalOutput, TaskError> { // Step 1: validate the request validate_request(&data).await?;
// Step 2: park until a manager delivers the approval event // The task is stored in the DB with a far-future execution time. // offer_event() wakes it up with the typed payload. let decision: ApprovalDecision = zart::wait_for_event("manager-approval", Some(Duration::from_secs(120))).await?;
// Step 3: act on the decision if decision.approved { process_approved(&data.resource, &data.requester_name).await?; }
Ok(ApprovalOutput { decision: if decision.approved { "approved" } else { "rejected" }.into(), requester: data.requester_name, resource: data.resource, reviewer: decision.reviewer, comment: decision.comment, })}Starting the workflow and delivering the event
Section titled “Starting the workflow and delivering the event”let mut registry = TaskRegistry::new();registry.register("approval-task", ApprovalTask);let registry = Arc::new(registry);
let durable = DurableScheduler::new(sched.clone());let execution_id = "approval-demo-1";
// Start the workflow — it will run step 1, then park at wait_for_eventdurable .start_for::<ApprovalTask>(execution_id, "approval-task", &ApprovalRequest { requester_name: "Bob".into(), resource: "staging-environment".into(), reason: "Need to test the new deployment pipeline".into(), }) .await?;
// ... later, when the manager clicks "Approve" in your UI or API ...let decision = ApprovalDecision { approved: true, reviewer: "Manager Carol".into(), comment: "Looks good — proceed!".into(),};durable .offer_event(execution_id, "manager-approval", serde_json::to_value(&decision)?) .await?;
// The workflow resumes automatically and runs step 3let output: ApprovalOutput = durable .wait_completion(execution_id, Duration::from_secs(60), None) .await?;println!("Decision: {} by {}", output.decision, output.reviewer);What you’ll see
Section titled “What you’ll see”=== Zart Approval Workflow Example ===
Starting execution 'approval-demo-...'... Requester: Bob Resource: staging-environment Reason: Need to test the new deployment pipeline
Execution is waiting for manager approval...(Simulating manager delivering the event after 2 seconds)
Manager decision: ApprovalDecision { approved: true, reviewer: "Alice (Manager)", ... }
Execution completed! Decision: approved Requester: Bob Resource: staging-environment Reviewer: Alice (Manager) Comment: Approved for testing purposesKey concepts
Section titled “Key concepts”zart::wait_for_event(name, timeout) — suspends the durable execution. The task record is written back to the database with a far-future scheduled_at time so workers ignore it. The execution does not consume a worker thread while it waits.
durable.offer_event(execution_id, event_name, payload) — delivers the event. Zart writes the payload into the task’s step results under the event name, resets scheduled_at to now, and the next worker poll picks it up.
Resumption is durable — if the worker crashes between receiving the event and running step 3, the workflow resumes from the event receipt on restart. The wait_for_event step result is already persisted.
Use cases — manager approval flows, interactive onboarding, integration with external review systems, human-in-the-loop decision workflows.