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.
A list of all active recipes is retrieved from Remote Settings, which has likely been syncing them in the background.
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.
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.
Next a recipe is checked for compatibility using capabilities.
Capabilities are simple strings, such as
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
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.
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,
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.
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.
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.