Approval Gates
An approval gate pauses a workflow run until a human approves or rejects it. This enables human-in-the-loop workflows like deploy pipelines where production deploys require sign-off.
How it works
- The handler calls
ctx.approval()with a prompt message - The run transitions to
AwaitingApproval - The worker releases the run and moves on to other work
- A human calls
POST /api/v1/runs/:id/approveorPOST /api/v1/runs/:id/reject - On approval, the run is requeued. A worker picks it up, replays completed steps from cache, skips the approved gate, and continues execution
- On rejection, the run transitions to
Failed
Example
//! Deploy workflow with human approval gate before production.
use ironflow_engine::config::{ApprovalConfig, ShellConfig};
use ironflow_engine::context::WorkflowContext;
use ironflow_engine::handler::{HandlerFuture, WorkflowHandler};
/// Deploy pipeline that requires human approval before shipping to production.
///
/// 1. **build** -- compile the project
/// 2. **test** -- run the test suite
/// 3. **deploy-staging** -- deploy to staging environment
/// 4. **approval gate** -- pause and wait for human approval
/// 5. **deploy-production** -- resumes after approval via step replay
///
/// After approval (POST /api/v1/runs/:id/approve), the engine
/// re-executes the handler: completed steps return cached output,
/// the approved gate is skipped, and execution continues with
/// deploy-production.
///
/// If the approval is rejected, the run transitions to `Failed`.
/// If cancelled, the run transitions to `Cancelled`.
pub struct DeployApproval;
impl WorkflowHandler for DeployApproval {
fn name(&self) -> &str {
"deploy-approval"
}
fn description(&self) -> &str {
"Deploy pipeline with human approval gate before production. \
Demonstrates ctx.approval() for human-in-the-loop workflows."
}
fn source_code(&self) -> Option<&str> {
Some(include_str!("deploy_approval.rs"))
}
fn execute<'a>(&'a self, ctx: &'a mut WorkflowContext) -> HandlerFuture<'a> {
Box::pin(async move {
// Step 1: Build
ctx.shell(
"build",
ShellConfig::new("echo 'Compiling...' && sleep 0.2 && echo 'Build OK'"),
)
.await?;
// Step 2: Test
ctx.shell(
"test",
ShellConfig::new("echo 'Running tests...' && sleep 0.3 && echo '87 tests passed'"),
)
.await?;
// Step 3: Deploy to staging
ctx.shell(
"deploy-staging",
ShellConfig::new(
"echo 'Deploying to staging...' && sleep 0.2 && echo 'Staging live'",
),
)
.await?;
// Step 4: Human approval gate
// The run pauses here and transitions to AwaitingApproval.
// A human must call POST /api/v1/runs/:id/approve to continue,
// or POST /api/v1/runs/:id/reject to fail the run.
ctx.approval(
"prod-approval",
ApprovalConfig::new("Staging looks good. Deploy to production?")
.with_timeout_seconds(3600),
)
.await?;
// Step 5: Deploy to production (only reached after approval)
ctx.shell(
"deploy-production",
ShellConfig::new(
"echo 'Deploying to production...' && sleep 0.3 && echo 'Production live'",
),
)
.await?;
Ok(())
})
}
}
Configuration
ApprovalConfig::new("Deploy to production?")
.with_timeout_seconds(3600) // Auto-reject after 1 hour
The timeout is optional. Without it, the run waits indefinitely.
Step replay
After an approval, the engine re-executes the handler from the beginning. Completed steps return their cached output immediately – they do not re-run. The approved gate is skipped, and execution resumes with the next step.