Execution Model

This document describes the execution model of the Normandy Client.

The basic unit of instruction from the server is a recipe, which contains instructions for filtering, and arguments for a given action. See below for details.

One iteration through all of these steps is called a Normandy session. This happens at least once every 6 hours, and possibly more often if Remote Settings syncs new changes.

1. Fetching

A list of all active recipes is retrieved from Remote Settings, which has likely been syncing them in the background.

2. Suitability

Once recipes have been retrieved, they go through several checks to determine their suitability for this client. Recipes contain information about which clients should execute the recipe. All recipes are processed by all clients, and all filtering happens in the client.

For more information, see the suitabilities docs.

Signature

First, recipes are validated using a signature generated by Autograph that is included with the recipe. This signature validates both the contents of the recipe as well as its source.

This signature is separate and distinct from the signing that happens on the Remote Settings collection. This provides additional assurance that this recipe is legitimate and intended to run on this client.

Capabilities

Next a recipe is checked for compatibility using capabilities. Capabilities are simple strings, such as "action:show-heartbeat". A recipe contains a list of required capabilities, and the Normandy Client has a list of capabilities that it supports. If any of the capabilities required by the recipe are not compatible with the client, then the recipe does not execute.

Capabilities are used to avoid running recipes on a client that are so incompatible as to be harmful. For example, some changes to filter expression handling cannot be detected by filter expressions, and so older clients that receive filters using these new features would break.

Note

Capabilities were first introduced in Firefox 70. Clients prior to this do not check capabilities, and run all recipes provided. To accommodate this, the server splits recipes into two Remote Settings collections, normandy-recipes, and normandy-recipes-capabilities. Clients prior to Firefox 70 use the former, whereas Firefox 70 and above use the latter. Recipes that only require “baseline” capabilities are published to both, and those that require advanced capabilities are only published to the capabilities aware collection.

Filter Expressions

Finally the recipe’s filter expression is checked. Filter expressions are written in an expression language named JEXL that is similar to JavaScript, but far simpler. It is intended to be as safe to evaluate as possible.

Filters are evaluated in a context that contains details about the client including browser versions, installed add-ons, and Telemetry data. Filters have access to “transforms” which are simple functions that can do things like check preference values or parse strings into Date objects. Filters don’t have access to change any state in the browser, and are generally idempotent. However, filters are not considered to be “pure functions”, because they have access to state that may change, such as time and location.

3. Execution

After a recipe’s suitability is determined, that recipe is executed. The recipe specifies an action by name, as well as arguments to pass to that action. The arguments are validated against an expected schema.

All action have a pre- and post-step that runs once each Normandy session. The pre-step is run before any recipes are executed, and once the post-step is executed, no more recipes will be executed on that action in this session.

Each recipe is passed to the action, along with its suitability. Individual actions have their own semantics about what to do with recipes. Many actions maintain their own life cycle of events for new recipes, existing recipes, and recipes that stop applying to this client.