Request and approval pattern
Modules NTF plus the requesting module (ATT, LVE, CLM, EXT, TSM, PAY, EMP, IAM) · Release R1 · Spec Notifications & Workflow
Context
The reference product uses one pattern everywhere: Submitted → Approved or Rejected, rejection needs a reason, the requester edits or deletes while Submitted and cancels approved future items, and manager on-behalf requests are auto-approved [Observed] (see Spryple analysis §3.3, SP-X-03 and SP-X-04). Multi-level approval, delegation and escalation are not documented [Inferred, §20]. Spyrl implements the pattern once, in the NTF engine, and every module plugs in.
Diagram
Steps
| # | Actor | Action | System response | Module | Notifications |
|---|---|---|---|---|---|
| 1 | Requester (employee) or initiator (manager on behalf) | Submits a request | Module validates (balance, limits, dates); saves as Submitted; reserves balance where relevant | Requesting module | — |
| 2 | System | Starts the workflow | Instance pinned to the active definition version; context holds amounts, days, type | NTF | — |
| 3 | System | Evaluates the next step's condition | Skips steps whose condition is false (e.g. HR step only when days > 5) | NTF | — |
| 4 | System | Resolves approvers | Reporting manager, skip-level, role in scope, department head, project manager or counterpart; removes the requester; applies active delegations; falls back to hr_manager at the requester's location when empty | NTF, IAM, EMP | — |
| 5 | System | Checks on-behalf coverage | In UP_TO_ACTOR_LEVEL mode, a step whose approvers include the initiator is auto-approved and recorded as such | NTF | Requester informed that a manager applied for them |
| 6 | System | Creates tasks | One task per approver, due by the step's SLA | NTF | Push and in-app to approvers; email digest optional |
| 7 | Approver or delegate | Approves, rejects (reason required) or sends back (comment required); single or bulk | Action recorded; ANY mode cancels sibling tasks; ALL mode waits for all | NTF | Requester informed of each decision |
| 8 | System | Advances or finishes | Next step, or final Approved; owning module handler runs and emits <mod>.<entity>.approved | NTF, module | Final outcome to requester; FYI to HR when configured |
| 9 | Requester | Withdraws before final decision | Instance Withdrawn; open tasks cancelled; reservations released | NTF, module | Approvers informed |
| 10 | Requester | Asks to cancel an approved future item | New cancellation instance using the type's cancellation chain (or self-cancel when policy allows) | Module, NTF | Approvers informed |
Delegation
| Rule | Detail |
|---|---|
| Who can delegate | Any approver for themselves; hr_admin for anyone |
| What | All request types or a list, for a date range up to delegation.max_days |
| Effect | New tasks go to the delegate; existing open tasks are re-pointed when move_existing is chosen |
| Visibility | Task shows "on behalf of" the original approver; both appear in history |
| Limits | A delegate cannot approve their own request; chained delegation resolves one hop only |
| Automatic suggestion | When an approver has approved leave, the system prompts them to set a delegate |
Escalation
| Stage | Default | Effect |
|---|---|---|
| Reminder | 24 h after task creation | Push and email to the assignee |
| Escalation level 1 | 48 h | Task added for the next manager up (or a role); the original stays open |
| Further levels | Every 48 h, up to the configured maximum | Next level |
| Timeout action | Off | Optional auto-approve or auto-reject after the last level; always recorded as automatic |
On-behalf modes
| Mode | Behaviour | Use when |
|---|---|---|
NONE | The request runs the full chain even when a manager raised it | Strict control |
UP_TO_ACTOR_LEVEL (default) | Steps the initiator could approve are auto-approved; later steps (e.g. HR) still run | Normal operations |
FULL | The whole chain is auto-approved | Parity with the reference product; small tenants |
Exception paths
| Situation | Handling |
|---|---|
| Approver has left the company | Resolver skips separated employees; fallback approver applies |
| Requester's manager changes mid-flow | Open tasks stay with the original approver unless HR reassigns (ntf.task.reassign) |
| Definition changes mid-flow | In-flight instances keep their pinned version |
| Approver is also the requester | Step skipped to the next resolver; never self-approved |
| Two approvers act at once in ANY mode | First action wins; the second receives "already decided" |
| Bulk approve with mixed validity | Per-item results; failures are listed with reasons |
| Request affects a locked period | Module decides: block, or allow with an adjustment to the next pay run |
| Notification delivery fails | Retries with backoff; in-app inbox always holds the task |