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.