Approval
Approval protocol schemas
Source: packages/spec/src/automation/approval.zod.ts
import { ApprovalDecision, ApprovalEscalationSchema, ApprovalNodeApproverSchema, ApprovalNodeConfigSchema, ApproverType, DecisionOutputDefSchema } from '@objectstack/spec/automation';
import type { ApprovalDecision, ApprovalEscalation, ApprovalNodeApprover, ApprovalNodeConfig, ApproverType, DecisionOutputDef } from '@objectstack/spec/automation';
// Validate data
const result = ApprovalDecision.parse(data);
| Property | Type | Required | Description |
|---|
| enabled | boolean | ✅ | Enable SLA-based escalation for this node |
| timeoutHours | number | ✅ | Hours before escalation triggers |
| action | Enum<'reassign' | 'auto_approve' | 'auto_reject' | 'notify'> | ✅ | Action on escalation timeout |
| escalateTo | string | optional | User id or position machine name to escalate to |
| notifySubmitter | boolean | ✅ | Notify the original submitter on escalation |
| Property | Type | Required | Description |
|---|
| type | Enum<'manager' | 'position' | 'department' | 'team' | 'field' | 'expression' | 'org_membership_level' | 'role' | 'user' | 'queue'> | ✅ | |
| value | string | optional | User id / membership tier / position / team / department / field — per type; for expression, a CEL expression over current.* / trigger.* / vars.* |
| resolveAs | Enum<'user' | 'department' | 'position' | 'team'> | optional | How an expression result is expanded into approvers (default 'user') |
| group | string | optional | Group label for per_group sign-off (e.g. "legal", "finance") |
| organization | string | optional | ADR-0105 D9 — organization whose directory resolves this approver: $root (group org), $parent (one level up), or an organization slug. Omitted = the request's own organization. |
| Property | Type | Required | Description |
|---|
| approvers | { type: Enum<'manager' | 'position' | 'department' | 'team' | 'field' | 'expression' | … +4 more>; value?: string; resolveAs?: Enum<'user' | 'department' | 'position' | 'team'>; group?: string; … }[] | ✅ | Allowed approvers for this node |
| behavior | Enum<'first_response' | 'unanimous' | 'quorum' | 'per_group'> | ✅ | How to combine multiple approvers |
| minApprovals | integer | optional | Approvals required — total (quorum) or per group (per_group). Default 1 |
| lockRecord | boolean | ✅ | Lock the record from editing while pending |
| approvalStatusField | string | optional | Business-object field to mirror request status onto |
| onEmptyApprovers | Enum<'admin_rescue' | 'fail' | 'auto_approve'> | ✅ | Behavior when no concrete approver resolves at node entry |
| decisionOutputs | (string | { key: string; label?: string; type?: Enum<'text' | 'user' | 'department' | 'position' | 'team'>; multiple?: boolean; … })[] | optional | Author-declared decision outputs — bare keys or typed { key, type, multiple } declarations |
| escalation | { enabled: boolean; timeoutHours: number; action: Enum<'reassign' | 'auto_approve' | 'auto_reject' | 'notify'>; escalateTo?: string; … } | optional | Per-node SLA escalation |
| maxRevisions | integer | ✅ | Max send-backs for revision before auto-reject (0 = send-back disabled) |
manager
position
department
team
field
expression
org_membership_level
role
user
queue
| Property | Type | Required | Description |
|---|
| key | string | ✅ | Output key (the flow variable name under the node id) |
| label | string | optional | Field label in the decision dialog |
| type | Enum<'text' | 'user' | 'department' | 'position' | 'team'> | optional | Decision-dialog input widget (default 'text') |
| multiple | boolean | optional | Collect multiple values (id array) |
| required | boolean | optional | Approver must supply this output to approve |