Most teams write approval logic in code first and then try to describe it in a document. Do the reverse. A policy-as-code approach treats organizational rules as versioned, testable code that a policy engine evaluates at runtime, which gives you a single, authoritative source of truth instead of prose that drifts from behavior (Wiz). The practical benefit for approvals is that the wait limit and the fallback become data you can review in a pull request, not a constant buried in a service.
The key design decision is that the model never decides whether the action is allowed; it only proposes. A policy decision point decides whether the specific actor, agent, action, resource and approval state allow execution (Thomas Thelliez). Your approval timeout is part of that decision: an unapproved request past its deadline is a policy input, and the default action is the policy output.
A minimal policy file can stay readable. This example gives each tier a maximum wait and a named default:
version: 3
defaults:
clock: "business_hours"
on_timeout: "auto_deny"
notify_before: "50%" # reminder at half the tier limit
tiers:
low:
max_wait: "15m"
on_timeout: "auto_deny"
approvers: ["team_lead"]
medium:
max_wait: "4h"
on_timeout: "escalate"
escalate_to: ["security_reviewer"]
high:
max_wait: "1_business_day"
on_timeout: "park"
park_until: "manual_review"
require_reason: true
Three things are worth noticing. First, notify_before is relative to the tier limit, so a 15 minute tier gets a nudge at 7.5 minutes without a per-tier reminder rule. Second, clock distinguishes wall-clock hours from business hours, which matters because a 4 hour wait that starts at 6 PM on Friday becomes four minutes of real human availability. Third, require_reason is set on the tier that parks, because a parked request without a stated reason is just a stalled one with better branding.



