Work

Approvals

Vigilo's approval system turns "who needs to sign off on this change?" from a Slack thread into a deterministic, auditable workflow. Each ChangeRequest…

Last updated

Overview

Vigilo's approval system turns "who needs to sign off on this change?" from a Slack thread into a deterministic, auditable workflow. Each ChangeRequest that enters the review state produces one or more ApprovalStep rows. The change can only advance to approved once every step has been decided. Policies, SLAs, segregation of duties, and CAB-as-gate are all driven from a single workspace-scoped configuration.

The approval queue lives at /ws/{slug}/approvals. Approvers see their pending decisions, the SLA countdown chip, the requesting engineer, the affected assets, and one-click Approve / Reject actions. Bulk approve and bulk reject ship on the toolbar for batched CAB-day decisions.

Why it exists

Auditors want two things: who signed off, and when. A free-form Slack approval gives them neither. The ApprovalStep model writes both, plus the SLA window the decision was made within, plus the policy that produced the step, plus the comment the approver left. This is the artifact that survives an audit.

Key concepts

  • ApprovalPolicy — Workspace-scoped rule. Has priority (lower wins, use 10/20/30 so you can insert between later), is_active, a conditions JSON blob, and a stages JSON array. Stored uniquely per (workspace, name).
  • ApprovalPolicy.conditions — Any subset of change_type[], priority[], risk_level[], min_affected_assets, min_risk_score, tags_any[]. Missing keys mean "don't check that dimension". A policy matches(change) only when every declared condition is satisfied.
  • ApprovalPolicy.stages — Ordered list of {order, kind, role|cab, sla_hours}. kind="role" picks the first eligible workspace member with that role (respecting OOO delegation and SoD). kind="cab" produces a step that waits for a CAB meeting decision.
  • ApprovalStep — One gate on one change. Status is pending, approved, rejected, or skipped. Carries approver, order, decision_at, comments, stage_type, cab_meeting, sla_due_at, warned_at, breached_at, and a back-reference to the originating policy.
  • SLA windowsla_due_at is set from the policy's sla_hours at step creation. The Celery sweep vigilo.approvals.sla_breach_sweep warns 4 hours before breach (setting warned_at) and emits a breach event after the deadline (setting breached_at). Both fields are checked first so each step warns and breaches exactly once (T1.1).
  • Segregation of duties (SoD) — When Workspace.enforce_sod is True, a step where the picked approver matches change.requested_by is silently dropped at creation. If every candidate step is dropped this way, the change still lands in review so an admin can intervene out-of-band rather than the submit hard-failing.

Common workflows

Approving a pending change

  1. Open Approvals in the sidebar. Your pending steps are at the top, sorted by sla_due_at ascending so anything close to breach surfaces first. Each row shows the SLA countdown chip (T2.3) — green > 24h, amber 24h to 4h, red < 4h or already breached.
  2. Click a row to expand the change preview: title, description, affected assets, risk score, similar past changes, and the full approval chain so far.
  3. Click Approve (with an optional comment) or Reject (comment required). The decision stamps decision_at, sets status, and either advances the chain to the next step or — if this was the last step — calls change.approve() on the FSM. Rejection terminates the chain and calls change.reject().

Bulk approving

  1. From the approvals list, tick the checkboxes on multiple rows.
  2. Click Approve selected. A single dialog lets you attach the same comment to all decisions. Each step is approved individually so SoD and per-step audit entries still apply. Rows you don't have permission for are skipped with a banner explaining why.

Configuring an approval policy

  1. Navigate to Settings → Approvals.
  2. Click New policy. Give it a name (unique per workspace) and a priority. Use 10 for catch-all most-restrictive, 90 for fallback most-permissive.
  3. In the conditions section, pick the dimensions you want to gate on. Leave others blank for "don't care".
  4. In the stages section, add an ordered list of gates. For role gates, pick the role and SLA hours. For CAB gates, pick cab and the SLA hours — the actual CABMeeting is bound when the change is added to a meeting agenda.
  5. Save. New submit() calls will route through the policy. Existing in-flight changes keep the steps they already have.

Watching SLA escalation

The vigilo.approvals.sla_breach_sweep Celery task runs every 5 minutes. For each pending step with a non-null sla_due_at:

  • If now is between (sla_due_at - 4h) and sla_due_at and warned_at is null, dispatch an approval.sla_warning event and stamp warned_at.
  • If now is past sla_due_at and breached_at is null, dispatch approval.sla_breached and stamp breached_at.

Both events flow through dispatch_event, so webhooks, Slack, and Playbooks fire on the same trigger.

Permissions

  • Viewers can read the approvals list but see no action buttons.
  • Approvers (workspace role='approver' or changes.approve permission) can act on steps assigned to them or to a role they cover via OOO delegation (UserProfile.effective_approver()).
  • Admins / owners can re-route a stuck step to a different approver and edit policies.
  • SoD is enforced for everyone — an approver cannot approve a change they requested, regardless of role.

Troubleshooting

The change shows "blocked by SLA" — The first pending step is past sla_due_at. The change is still in review; the SLA breach is informational and surfaces in dashboards and webhooks. Approve the step or reassign it.

My step doesn't appear in my queue — Either it's not yours (the picker chose a different eligible member), or you're the requester (SoD dropped it). Ask an admin to reassign via the step's row menu.

The policy I configured isn't firing — A higher-precedence policy is matching first. Check Settings → Approvals sorted by priority and verify the conditions on each policy above yours against the change.

Bulk approve skipped some rows — You don't have permission on those steps, or they were CAB steps awaiting a meeting decision (CAB steps cannot be approved individually).

Related