Skip to content

Zart is in active development — breaking API changes may occur despite our best efforts to keep contracts stable.

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().

#[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,
}
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 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_event
durable
.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 3
let output: ApprovalOutput = durable
.wait_completion(execution_id, Duration::from_secs(60), None)
.await?;
println!("Decision: {} by {}", output.decision, output.reviewer);
=== 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 purposes

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.