Configuring Backup Approvers

Sym gives you the ability to define an arbitrary number of backup destinations for a request, and configure timeouts between each.

Overview

When making access requests, it is possible for you to specify that you would like the initial request destination to have one or more backups. This is useful in the case of:

  • Message delivery failure (e.g. a destination channel has been deleted)
  • Failure to respond to a request (e.g. your first intended responder is on PTO)

Terminology

TermDescription
message delivery failureA message cannot be sent to a request destination for whatever reason (e.g a Slack channel does not exist).
timeoutA request is not approved or denied within the configured timeout (in seconds) window.
request forwardA timeout or message delivery failure has occurred.
request expireWe have exhausted all request forward destinations and the last one has either failed or timed out; the request terminates without a response.

Implementation

In order to use the backup approvers feature, your get_approvers() reducer must return a RequestDestinationFallback object. The values passed in to parameters continue_on_delivery_failure and continue_on_timeout will determine whether the system will try the next approver based on the failure type.

Failure typeBehavior when trueBehavior when false or None
continue_on_delivery_failureThe next destination will be tried immediately after a failed delivery to the current destination.If false, after a delivery failure, the next approver will not be tried.
continue_on_timeoutThe next RequestDestination will be tried after the timeout (in seconds) specified on the current `RequestDestination.If None, the next approver will not be tried in the case of a timeout.

Example

In this example, we will return a RequestDestinationFallback object from get_approvers() to walk through what the SDK will do in various scenarios.

from sym.sdk.annotations import reducer
from sym.sdk.integrations import slack


@reducer
def get_approvers(evt):
    return slack.fallback(
        slack.channel("#sym-requests", allow_self=True, timeout=600),
        slack.channel("#sym-backup", allow_self=True, timeout=1200),
        slack.user("[email protected]", timeout=None),
        continue_on_timeout=True)

In this example, assuming no delivery failures:

  • The request will be sent to the #sym-requests Slack channel via a request event.
  • If there is no response within 600 seconds (10 mins), the request will be sent to the #sym-backup Slack channel via a request_forward event.
  • If there is no response within 1200 seconds (20 mins), the request will be sent via DM to the Slack user with the email[email protected] via a request_forward event . Since there is no timeout specified on that RequestDestination, it will hang there indefinitely until [email protected] responds.

Adding in a timeout

However, if we modify the example slightly to add a timeout on the slack.user object, like so:

from sym.sdk.annotations import reducer
from sym.sdk.integrations import slack


@reducer
def get_approvers(evt):
    return slack.fallback(
        slack.channel("#sym-requests", allow_self=True, timeout=600),
        slack.channel("#sym-backup", allow_self=True, timeout=1200),
        slack.user("[email protected]", timeout=1800),
        continue_on_timeout=True)

In this modified example, if there is no response from [email protected] within 1800 seconds (30 mins), the SDK will fire a request_expire event, and the message in Slack will be updated to show that the request has timed out.

Advanced: Events

As with all transitions in the Sym SDK, Backup Approvers exposes hooks to the SDK.

It is important to note that the Events that occur in the SDK will be slightly different depending on the behavior specified by the RequestDestinationFallback returned by get_approvers().

In general:

  • When a continue_on_timeout occurs, Sym will fire a request_forward Event.
  • When a continue_on_delivery failure occurs, no new event will be fired. The logic will simply try the next destination without firing any events.
  • When we have reached the end of our list of destinations and the last one times out, Sym will fire a request_expire Event.

Just like other Events, you can write a hook in your impl.py with custom Python logic that will run on or after request_forward and request_expire.

Note:request_forward hooks take an additional param request_forward_context (RequestForwardContext), which contains information about the full forwarding context of a Request, including both the full list of potential destinations, and its current index in moving through that list.

For example, you might want to DM a requestor each time their request went to a new, valid destination:

from sym.sdk.annotations import hook
from sym.sdk.integrations import slack

@hook
def on_request_forward(evt, request_forward_context):
    index = request_forward_context.current_destination_index
    current_destination = request_forward_context.all_request_destinations[index]
    if current_destination:
      slack.send_message(
        evt.get_actor("request"),
        f"Your request was forwarded to {current_destination}"
      )
    
   

📘

Further reading

For more information about RequestForwardContext, see our SDK docs here.