Documentation home

Launch & ops

Approval timeouts

Time-boxed human gates: policy windows, auto-reject or escalate to the next stage, cron with CRON_SECRET, audit and webhooks.

Time-boxed approval windows

Human approval queues are a common bottleneck. Time-boxed windows (with automatic reject or escalation to the next routing stage) keep workloads moving and match operational patterns recommended by vendors such as Strata.io for identity and access governance SLAs.

Ruleset fields

On each routing stage (same objects as stage_index, role, tier), you may set:

FieldTypeRangeDescription
approval_timeout_secondsinteger60–2 592 000When the stage holds the active approval_token, decide by this wall-clock window.
on_approval_timeoutstringoptionalreject (default if omitted) or escalate_next. Requires approval_timeout_seconds on the same stage when set.

Omit approval_timeout_seconds for stages with no deadline (indefinite pending until a human acts).

What the runtime stores

When evaluations create approval_stages, each row copies:

  • timeout_seconds_snapshot — from approval_timeout_seconds (or null).
  • timeout_action_snapshot — from on_approval_timeout when a timeout is configured (or null; treated as reject at enforcement time).

Only the stage that currently has an approval_token gets approval_deadline_at set (now + timeout in UTC). When a stage completes and the next pending stage receives a token, notifyApproversForEvaluation sets that row’s deadline from its snapshot.

Enforcement cron

Timeouts are applied by a server cron, not inline on every page view:

  • POST /api/cron/approval-timeouts
  • Header: Authorization: Bearer <CRON_SECRET> (same secret as replay-webhook automation).
  • Optional JSON body: { "limit": 40 } (default 40, capped at 200 per invocation).

Configure your host (Vercel Cron, GitHub Actions, etc.) to call this on a short interval (for example every 1–5 minutes). If the job never runs, deadlines never fire.

Set CRON_SECRET in .env / host secrets (see .env.example).

Behavior summary

on_approval_timeout: "reject" (or omitted)

  1. Current stage → rejected; other pending stages → skipped.
  2. Evaluation → rejected.
  3. Integrator webhook (if webhook_url was set): terminal_outcome with rejectionReason: "approval_timeout".
  4. Audit: approval_timeout with payload action: "reject".
  5. Run bundle: append human decision receipt as rejected for that stage.

on_approval_timeout: "escalate_next"

  1. Current stage → skipped (timed out without a human decision).
  2. Next pending stage with higher stage_index receives a new token and approval_deadline_at from its snapshot.
  3. Approvers are notified (email / Slack / Teams) like a normal handoff; integrator webhook approval_required / pending-human flow as today.
  4. Audit: approval_timeout with action: "escalate_next".

If there is no next pending stage, behavior matches reject: evaluation rejected, audit approval_timeout_escalation_exhausted, webhook rejectionReason: "approval_timeout_escalation_exhausted".

Approver UI

The hosted /approve/[token] page shows Decision window (local time) when approval_deadline_at is set.

Dashboard templates

Built-in examples:

  • Single-stage — 48h then auto-reject
  • Multi-stage — escalate after 24h

Related docs