03 · Money · money lens

Approvals inbox: decide, unblock, and retry work

Work open approval steps, recover blocked stages, retry failed applies, and publish the policy versions that govern each flow.

5 min read · updated 28/05/2026
Who can do this
RoleViewGenerateFinalizeRecord paymentVoidCounter sale
OwnerAllowedAllowedAllowedAllowedAllowedAllowed
ManagerAllowedAllowedAllowedAllowedNot allowedNot allowed

Overview

Approval work has two halves: a human decision and the domain change that follows it. The Approvals inbox shows the open stage assigned to you. A separate blocked-work inbox shows later stages that cannot resolve a responsible holder. A failed apply is neither a lost decision nor a silent success: it records why the domain change stopped and can be retried when you are allowed to do so.

Where to find it

Open Approvals at /approvals on the web dashboard, or /m/admin/approvals in the mobile admin app. Your personal inbox and the blocked-work exception list are separate reads, so an exception does not look like a task assigned to you. Policy publishing is available through the Approvals policy controls/API (approvals.policy.publish) for the admin integration.

Key concepts

An open step is an approval stage in state open, on a pending request, with an active assignment to your user. The inbox orders steps oldest first and includes the responsibility key, org unit, age, due time, and overdue flag. Use those fields to decide what needs attention first.

Blocked work is a request in blocked_unresolvable. A later stage found zero or more than one holder for its responsibility key and org unit, so the engine stops before making an assignment. Only a user who can reassign that stage and holds its responsibility key in scope can see the row. There is no Owner fallback or automatic approval.

Apply failure means the decision was recorded but the handler could not make the domain change. The current failure reasons are context_drift, insufficient_stock, credit_exceeded, and budget_line_exhausted. A request in apply_failed remains retryable until it is superseded or cancelled.

A policy version is an immutable, tenant-scoped definition for a subject type and action. A request keeps the version it was submitted against; publishing a new version does not rewrite work already in flight.

Common workflows

1
Review your inbox
Open Approvals and start with the oldest step. Check the subject, responsibility key, org unit, age, and due time before opening the detail.
2
Record a decision
For the open stage, choose Approve, Approve with note, or Reject. Bypass is a separate decision and requires a reason code. Self-approval is refused unless the published policy and stage explicitly allow it with a stated reason; every decision is audited.
3
Recover blocked work
Open the blocked-work list and read the failed key, org unit, and resolved-holder context. If the stage has no seat, use resolveBlocked with an eligible same-tenant recipient and a reason. If a seat already exists, use reassign instead. Both operations restore the request to pending and write an audit entry.
4
Retry a failed apply
Read the recorded failure reason first. With approval.request.apply-retry, run retryApply for the request. This re-arms its apply job; the worker rechecks the current context, so retrying does not bypass stock, credit, budget, or revision checks.
5
Publish a policy version
Define the subject type, action, context fields, matching bands, and stages, then publish through approvals.policy.publish. v1 requires a catch-all band, resolves the first matching band, and opens sequential single-seat stages; quorum, multiple required approvals, and fork/join stages are refused. A successful publish creates the next immutable version and records who published it.

Role notes

Owner is the documented tenant administrator and the default operator for policy configuration. The engine still checks the responsibility key and org-unit scope for every assignment, recovery, and decision; ownership never creates a fallback seat.

Manager can work approval steps assigned to them. To see and recover a blocked row, the Manager must hold both approval.stage.reassign and the stage's responsibility key at an ancestor unit. Retrying an apply additionally requires approval.request.apply-retry; neither permission is implied by being a Manager.

Tips & time-savers

Tip
Treat the responsibility key and org unit as part of the approval, not as labels. They tell you which authority is expected and make a blocked row actionable without guessing.

Decide first, then watch the request state. decisioned means the human step is complete; applying and applied describe the domain effect that follows.

Gotchas

Warning
A blocked request is not an empty personal inbox row. Do not approve around it or assign a global Owner as a shortcut. Use resolveBlocked only when the stage has no seat; when a seat exists, resolveBlocked refuses and reassign is the correct operation.

An old policy version is never edited in place. A retry also does not force an apply through changed context: a new failure is the safe result when the subject, stock, credit, or budget no longer matches.

Related