1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
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 <./suitabilities.html>`_.
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.
.. _Autograph: https://github.com/mozilla-services/autograph
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.
.. _JEXL: https://github.com/mozilla/mozjexl
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.
|