Repository: julien6387/supvisors (language: python)

Issue:
double master
simple scenario:
start node1, node2, node3 => node1 is the master, as seen by nodes 2 and 3
stop node1 => node2 becomes master, as seen by node3
restart node1 => node1 declares itself as a master. node2 still the master, as seen by node3
restart node3 => node1 master, node2 master, node3 sees node1 as master

when a new node becomes active, it is needed to come back to INITIALIZATION state.


Discussion / hints:

Fix patch:
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 00000000..7c2f1800
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+omit = */tests/*,*/client/*
+
diff --git a/.travis.yml b/.travis.yml
index 9e3b0440..c234444f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,15 +3,15 @@ language: python
 matrix:
     include:
         - python: 3.6
-          env: TOXENV=cover
-        - python: 3.6
-          env:
-            - TOXENV=py36
+          env: TOXENV=py36
+          after_success:
+            - pip install coveralls
+            - coveralls
         - python: 3.6
           env: TOXENV=docs
 
 install:
-    - travis_retry pip install tox
+    - pip install tox tox-travis
 
 script:
-    - travis_retry tox
+    - tox
diff --git a/CHANGES.txt b/CHANGES.txt
index 655e4a20..ae487051 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,9 +1,45 @@
+0.5 (2021-03-01)
+----------------
+
+* New option 'force_synchro_if' to force the end of the synchronization phase when a subset of nodes are active
+
+* New starting strategy LOCAL added to command the starting of an application on the local node only
+
+* Fixed https://github.com/julien6387/supvisors/issues/87
+  Under particular circumstances, Supvisors could have multiple master nodes
+
+* Fixed https://github.com/julien6387/supvisors/issues/86
+  The starting and stopping sequences may fail and block when a sub-sequence includes only unstartable programs
+
+* Fixed https://github.com/julien6387/supvisors/issues/85
+  When using '#' in the Supvisors rules file, a subset of nodes can optionally be defined
+
+* Fixed https://github.com/julien6387/supvisors/issues/84
+  In the Supvisors rules file, program rules can be defined using both model reference and attributes
+
+* The Web UI uses the default starting strategy of the configuration file
+
+* The layout of Web UI statistics sections has been rearranged
+
+* Fixed CSS style missing for CHECKING node state in tables
+
+* Star added to the node box of the Master instance on the main page
+
+* Type annotations are added progressively in source code
+
+* Start switching from unittest to pytest
+
+* Logs (especially Debug and Trace) updated to avoid objects to be printed, which is quite unreadable
+
+* Documentation updated
+
+
 0.4 (2021-02-14)
 ----------------
 
 * Auto-refresh button added to all pages
 
-* Main page reworked by adding a subdivision of application in node boxes
+* Web UI Main page reworked by adding a subdivision of application in node boxes
 
 * Fixed exception when exiting using Ctrl+c from shell
 
diff --git a/README.md b/README.md
index 79b67f64..4e427853 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
 # **Supvisors**
+[![PyPI version][pypi-image]][pypi-url] [![Build Status][ci-image]][ci-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Documentation Status][docs-image]][docs-url]
+
 
 **Supvisors** is a Control System for Distributed Applications, based on
 multiple instances of Supervisor.
@@ -16,11 +18,12 @@ The main features are:
 
 ## Supervisor Enhancements
 
-By design, **Supvisors** proposes a fix to the following Supervisor issues:
+**Supvisors** proposes a fix to the following Supervisor issues:
    * [#122 - supervisord Starts All Processes at the Same Time](https://github.com/Supervisor/supervisor/issues/122)
    * [#456 - Add the ability to set different "restart policies" on process workers](https://github.com/Supervisor/supervisor/issues/456)
    * [#520 - allow a program to wait for another to stop before being stopped?](https://github.com/Supervisor/supervisor/issues/520)
    * [#1023 - Pass arguments to program when starting a job?](https://github.com/Supervisor/supervisor/issues/1023)
+   * [#1150 - Why do event listeners not report the process exit status when stopped/crashed?](https://github.com/Supervisor/supervisor/issues/1150)
 
 ## Supported Platforms
 
@@ -34,21 +37,18 @@ It will likely work fine on most UNIX systems.
 **Supvisors** 0.1 (available on PyPi) works with Python 2.7 (and former versions of Supervisor, i.e. 3.3.0)
 but is not maintained anymore.
 
-[![Build Status](https://travis-ci.org/julien6387/supvisors.svg?branch=master)](https://travis-ci.org/julien6387/supvisors)
-
 ## Dependencies
 
 **Supvisors** has dependencies on:
 
-Package    | Release    | Optional
------------|------------|---------
-Supervisor | 4.2.1      |
-PyZMQ      | 20.0.0     |
-psutil     | 5.7.3      |     X
-netifaces  | 0.10.9     |     X
-matplotlib | 3.3.3      |     X
-lxml       | 4.6.2      |     X
-
+Package                                             | Release    | Optional
+----------------------------------------------------|------------|---------
+[Supervisor](http://supervisord.org)                | 4.2.1      |
+[PyZMQ](http://pyzmq.readthedocs.io)                | 20.0.0     |
+[psutil](https://pypi.python.org/pypi/psutil)       | 5.7.3      |     X
+[netifaces](https://pypi.python.org/pypi/netifaces) | 0.10.9     |     X
+[matplotlib](http://matplotlib.org)                 | 3.3.3      |     X
+[lxml](http://lxml.de)                              | 4.6.2      |     X
 
 Please note that some of these dependencies may have their own dependencies.
 
@@ -74,8 +74,6 @@ Supvisors can be installed with `pip install`:
 
 You can view the current **Supvisors** documentation [here](http://supvisors.readthedocs.io).
 
-[![Documentation Status](https://readthedocs.org/projects/supvisors/badge/?version=master)](https://supvisors.readthedocs.io/en/master/?badge=master)
-
 You will find detailed installation and configuration documentation.
 
 ## Reporting Bugs and Viewing the Source Repository
@@ -87,3 +85,15 @@ You can view the [source repository](https://github.com/julien6387/supvisors) fo
 ## Contributing
 
 Not opened yet.
+
+[pypi-image]: https://badge.fury.io/py/supvisors.svg
+[pypi-url]: https://badge.fury.io/py/supvisors
+
+[ci-image]: https://travis-ci.org/julien6387/supvisors.svg?branch=master
+[ci-url]: https://travis-ci.org/julien6387/supvisors?branch=master
+
+[coveralls-image]: https://coveralls.io/repos/github/julien6387/supvisors/badge.svg?branch=master
+[coveralls-url]: https://coveralls.io/github/julien6387/supvisors?branch=master
+
+[docs-image]: https://readthedocs.org/projects/supvisors/badge/?version=master
+[docs-url]: https://supvisors.readthedocs.io/en/master
diff --git a/docs/configuration.rst b/docs/configuration.rst
index efdb0477..5e74e16c 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -39,7 +39,7 @@ The parameters of **Supvisors** are set through an additional section ``[supviso
 
     The list of node names where **Supvisors** will be running, separated by commas.
 
-    *Default*:  None.
+    *Default*:  local host name.
 
     *Required*:  Yes.
 
@@ -96,16 +96,26 @@ The parameters of **Supvisors** are set through an additional section ``[supviso
 ``synchro_timeout``
 
     The time in seconds that **Supvisors** waits for all expected **Supvisors** instances to publish.
+    Value in [``15`` ; ``1200``].
     This use of this option is detailed in :ref:`synchronizing`.
 
     *Default*:  ``15``.
 
     *Required*:  No.
 
+``force_synchro_if``
+
+    The subset of ``address_list`` that will force the end of the synchronization phase in **Supvisors**, separated by commas.
+    If not set, **Supvisors** waits for all expected **Supvisors** instances to publish until ``synchro_timeout``.
+
+    *Default*:  None.
+
+    *Required*:  No.
+
 ``starting_strategy``
 
     The strategy used to start applications on nodes.
-    Possible values are in { ``CONFIG``, ``LESS_LOADED``, ``MOST_LOADED`` }.
+    Possible values are in { ``CONFIG``, ``LESS_LOADED``, ``MOST_LOADED``, ``LOCAL`` }.
     The use of this option is detailed in :ref:`starting_strategy`.
 
     *Default*:  ``CONFIG``.
@@ -271,26 +281,18 @@ the XML rules file before it is used.
 ``program`` Rules
 ~~~~~~~~~~~~~~~~~
 
-The ``program`` rules must be included in ``application`` rules.
-Here follows the definition of the rules applicable to a program.
+The ``program`` element defines rules applicable to a program. This element must be included in an ``application`` element.
+Here follows the definition of the attributes and rules applicable to a ``program`` element.
 
 ``name``
 
-    This attribute gives the name of the program. A Supervisor program name is expected.
+    This attribute gives the name of the program.
+    It MUST correspond to a `Supervisor program name <http://supervisord.org/configuration.html#program-x-section-settings>`_.
 
     *Default*:  None.
 
     *Required*:  Yes.
 
-``reference``
-
-    This element gives the name of the applicable ``model``, defined in `model Rules`_.
-    This use of the ``reference`` element is exclusive to the use of the following elements.
-
-    *Default*:  None.
-
-    *Required*:  Only if none of the following elements is used.
-
 ``addresses``
 
     This element gives the list of nodes where the process can be started, separated by commas. Applicable values are:
@@ -306,7 +308,7 @@ Here follows the definition of the rules applicable to a program.
 ``required``
 
     This element gives the importance of the program for the application.
-    If true (resp. false), a failure of the program is considered major (resp. minor).
+    If ``true`` (resp. ``false``), a failure of the program is considered major (resp. minor).
     This is quite informative and is mainly used to give the operational status of the application.
 
     *Default*:  ``false``.
@@ -335,7 +337,7 @@ Here follows the definition of the rules applicable to a program.
 
 ``wait_exit``
 
-    If the value of this element is set to true, Supvisors waits for the process to exit before starting the next sequence.
+    If the value of this element is set to ``true``, **Supvisors** waits for the process to exit before starting the next sequence.
     This is particularly useful for scripts used to load a database, to mount disks, to prepare the application working directory, etc.
 
     *Default*:  ``false``.
@@ -351,19 +353,19 @@ Here follows the definition of the rules applicable to a program.
     When multiple nodes are available, the ``loading`` value helps to distribute processes over the available nodes,
     so that the system remains safe.
 
-    .. note:: *About the choice of a user estimation.*
+    *Default*:  ``1``.
+
+    *Required*:  No.
+
+    .. note:: *About the choice of an user estimation*
 
-        Although **Supvisors** is taking measurements on each system where it is running, it has
+        Although **Supvisors** may be taking measurements on each system where it is running, it has
         been chosen not to use these figures for the loading purpose. Indeed, the resources consumption
         of a process may be very variable in time and is not foreseeable.
 
-        It is recommended to give a value based on a average usage of the resources in worst case
+        It is recommended to give a value based on an average usage of the resources in the worst case
         configuration and to add a margin corresponding to the standard deviation.
 
-    *Default*:  ``1``.
-
-    *Required*:  No.
-
 ``running_failure_strategy``
 
     This element gives the strategy applied when the required process is unexpectedly stopped in a running application.
@@ -375,20 +377,39 @@ Here follows the definition of the rules applicable to a program.
 
     *Required*:  No.
 
-.. attention:: *About the running failure strategy*.
+    .. attention:: *About the Running Failure Strategy*
+
+        This functionality is NOT compatible with the ``autostart`` parameter of the program configuration in Supervisor.
+        It is undesirable that Supervisor and **Supvisors** trigger a different behaviour for the same event.
+        So, unless the value of the running failure strategy is set to ``CONTINUE`` (default value), **Supvisors** forces
+        ``autostart=False`` in Supervisor internal model.
+
+        ``RESTART_PROCESS`` is almost equivalent to ``autorestart=unexpected``, except that **Supvisors** may restart
+        the crashed program somewhere else, in accordance with the starting rules defined, instead of just restarting it
+        on the same node.
+
+        There is no equivalent in **Supvisors** for ``autorestart=True``. Although there are workarounds for that,
+        it might be a future improvement.
+
+``reference``
+
+    This element gives the name of an applicable ``model``, as defined in `model Rules`_.
+
+    *Default*:  None.
 
-    This functionality is NOT compatible with the ``autostart`` parameter of the program configuration in Supervisor.
-    It is undesirable that Supervisor and **Supvisors** trigger a different behaviour for the same event.
-    So, unless the value of the running failure strategy is set to ``CONTINUE`` (default value), **Supvisors** forces
-    ``autostart=False`` in Supervisor internal model.
+    *Required*:  No.
+
+    .. note:: *About referencing models*
 
-    ``RESTART_PROCESS`` is almost equivalent to ``autorestart=unexpected``, except that **Supvisors** may restart
-    the crashed program somewhere else, in accordance with the starting rules defined, instead of just restarting it
-    at the same location.
+        The ``reference`` element can be combined with all the other elements described above.
+        The rules got from the referenced model are loaded first and then eventually superseded by any other rule
+        defined in the same program section.
 
-    There is no equivalent in **Supvisors** for ``autorestart=True``. Although there are workarounds for that,
-    it might be a future improvement.
+        A model can reference another model. In order to prevent infinite loops and to keep a reasonable complexity,
+        the maximum chain starting from the ``program`` section has been set to 3.
+        As a consequence, any rule may be superseded twice at a maximum.
 
+Here follows an example of a ``program`` definition:
 
 .. code-block:: xml
 
@@ -406,14 +427,14 @@ Here follows the definition of the rules applicable to a program.
 ``pattern`` Rules
 ~~~~~~~~~~~~~~~~~
 
-It may be quite tedious to give all this information to each program, especially if multiple programs use common rules.
+It may be quite tedious to give all this information to each program, especially if multiple programs use quite common rules.
 So two mechanisms were put in place to help.
 
-The first is the ``pattern``. It can be used to configure a set of programs in a more flexible way than just
+The first one is the ``pattern`` element. It can be used to configure a set of programs in a more flexible way than just
 considering homogeneous programs, like Supervisor does.
 
-Like the ``program`` element, the ``pattern`` must be included in ``application`` rules. The same options are applicable.
-The difference is in the ``name`` usage. For a pattern definition, a substring of any Supervisor program name is expected.
+Like the ``program`` element, the ``pattern`` element must be included in an ``application`` element. The same options are applicable.
+The difference is in the ``name`` usage. For a pattern definition, a substring of a Supervisor program name is expected.
 
 .. code-block:: xml
 
@@ -429,18 +450,18 @@ The difference is in the ``name`` usage. For a pattern definition, a substring o
     In the previous example, the rules are applicable to every program names containing the ``"prg_"`` substring,
     so that it matches ``prg_00``, ``prg_dummy``, but also ``dummy_prg_2``.
 
-    As a general rule, when considering a program name, **Supvisors** applies a ``program`` definition, if found,
-    before trying to associate a ``pattern`` definition.
+    As a general rule when looking for program rules, **Supvisors** always searches for a ``program`` definition with
+    the exact program name, and if not found only, **Supvisors** tries to find a corresponding ``pattern`` definition.
 
     It also may happen that several patterns match the same program name. In this case, **Supvisors** chooses the pattern
-    with the greatest matching, or arbitrarily the first of them if such a rule does not discriminate enough. So given two pattern
-    names ``prg`` and ``prg_``, **Supvisors** applies the rules associated to ``prg_`` when considering the program
-    ``prg_00``.
+    with the greatest matching, or arbitrarily the first of them if such a rule does not discriminate enough.
+    So considering the program ``prg_00`` and the two matching pattern names ``prg`` and ``prg_``, **Supvisors** will
+    apply the rules related to ``prg_``.
 
-.. note:: *About the use of* ``#`` *in* ``addresses``.
+.. hint:: *About the use of* ``#`` *in* ``addresses``.
 
-    The intention is for a program that is meant to be started on each node in the address list.
-    As an example, consider an extract of the following Supervisor configuration:
+    This is designed for a program that is meant to be started on every nodes of the address list.
+    As an example, based on the following simplified Supervisor configuration:
 
     .. code-block:: ini
 
@@ -492,10 +513,10 @@ The difference is in the ``name`` usage. For a pattern definition, a substring o
 ~~~~~~~~~~~~~~~
 
 The second mechanism is the ``model`` definition.
-The ``program`` definition is extended to a generic model, that can be defined outside the application scope,
-so that the same definition can be applied to multiple programs, in any application.
+The ``program`` rules definition is extended to a generic model, that can be defined outside of the application scope,
+so that the same rules definition can be applied to multiple programs, in any application.
 
-The same options are applicable, **excepting** the ``reference`` option, which doesn't make sense here.
+The same options are applicable, **including** the ``reference`` option.
 There is no particular expectation for the name attribute of a ``model``.
 
 Here follows an example of model:
@@ -504,6 +525,7 @@ Here follows an example of model:
 
     <model name="X11_model">
         <addresses>cliche01,cliche02,cliche03</addresses>
+        <start_sequence>1</start_sequence>
         <required>false</required>
         <wait_exit>false</wait_exit>
     </model>
@@ -518,17 +540,20 @@ Here follows examples of program and pattern definitions referencing a model:
 
     <pattern name="prg">
         <reference>X11_model</reference>
+        <!-- prg-like programs have the same rules as X11_model, but with required=true-->
+        <required>true</required>
     </pattern>
 
 
 ``application`` Rules
 ~~~~~~~~~~~~~~~~~~~~~
 
-Here follows the definition of the rules applicable to an application.
+Here follows the definition of the attributes and rules applicable to an ``application`` element.
 
 ``name``
 
-    This attribute gives the name of the application. A Supervisor group name is expected.
+    This attribute gives the name of the application.
+    It MUST correspond to a `Supervisor group name <http://supervisord.org/configuration.html#group-x-section-settings>`_.
 
     *Default*:  None.
 
@@ -561,7 +586,7 @@ Here follows the definition of the rules applicable to an application.
             * when calling Supervisor's ``restart`` or ``shutdown`` XML-RPC,
             * when stopping the :command:`supervisord` daemon.
 
-        It only works when calling **Supvisors**' ``restart`` or ``shutdown``.
+        It only works when calling **Supvisors**' ``restart`` or ``shutdown`` XML-RPC.
 
 ``starting_failure_strategy``
 
@@ -578,8 +603,8 @@ Here follows the definition of the rules applicable to an application.
 
 ``running_failure_strategy``
 
-    This element gives the strategy applied when any process of the application is unexpectedly stopped when the application is running.
-    This value can be superseded by the value set at program level.
+    This element gives the strategy applied when any process of the application is unexpectedly stopped when
+    the application is running. This value can be superseded by the value set at program level.
     The possible values are { ``CONTINUE``, ``RESTART_PROCESS``, ``STOP_APPLICATION``, ``RESTART_APPLICATION`` }
     and are detailed in :ref:`running_failure_strategy`.
 
@@ -589,7 +614,10 @@ Here follows the definition of the rules applicable to an application.
 
 ``program``
 
-    This element defines the program rules that are applicable to the unique program whose name correspond to the name attribute of the ``program`` element.
+    This element defines the program rules that are applicable to the program whose name correspond to the name
+    attribute of the ``program`` element. The program MUST be defined in the program list of
+    the `Supervisor group definition <http://supervisord.org/configuration.html#group-x-section-settings>`_
+    of the application considered here.
     Obviously, the definition of an application can include multiple ``program`` elements.
 
     *Default*:  None.
@@ -598,7 +626,8 @@ Here follows the definition of the rules applicable to an application.
 
 ``pattern``
 
-    This element defines the program rules that are applicable to all programs whose name matches the name attribute of the ``pattern`` element.
+    This element defines the program rules that are applicable to all programs whose name matches the name attribute
+    of the ``pattern`` element.
     Obviously, the definition of an application can include multiple ``program`` elements.
 
     *Default*:  None.
@@ -609,7 +638,7 @@ Here follows the definition of the rules applicable to an application.
 Rules File Example
 ~~~~~~~~~~~~~~~~~~
 
-Here follows a complete example of rules files. It is used in **Supvisors** tests.
+Here follows a complete example of rules files. It is used in **Supvisors** self tests.
 
 .. code-block:: xml
 
diff --git a/docs/event_interface.rst b/docs/event_interface.rst
index 56864781..7a56c051 100644
--- a/docs/event_interface.rst
+++ b/docs/event_interface.rst
@@ -93,12 +93,17 @@ Key	               Value
 'process_name'     The Process name.
 'statecode'        The Process state, in {0, 10, 20, 30, 40, 100, 200, 1000}.
 'statename'        The Process state as string, among { ``'STOPPED'``, ``'STARTING'``, ``'RUNNING'``, ``'BACKOFF'``, ``'STOPPING'``, ``'EXITED'``, ``'FATAL'``, ``'UNKNOWN'`` }.
-'expected_exit'    True if the exit status is expected (only when state is ``EXITED``).
+'expected_exit'    True if the exit status is expected (only when state is ``'EXITED'``).
 'last_event_time'  The date of the last process event received for this process, regardless of the originating **Supvisors** instance.
 'addresses'        The list of nodes where the process is running.
 'extra_args'       The additional arguments passed to the command line of the process.
 ================== ==================
 
+.. hint::
+
+    The ``expected_exit`` information of this event provides an answer to the following Supervisor request:
+
+        * `#1150 - Why do event listeners not report the process exit status when stopped/crashed? <https://github.com/Supervisor/supervisor/issues/1150>`_
 
 Process event
 ~~~~~~~~~~~~~
diff --git a/docs/images/supvisors_address_host_section.png b/docs/images/supvisors_address_host_section.png
index 791e2757..130f4982 100644
Binary files a/docs/images/supvisors_address_host_section.png and b/docs/images/supvisors_address_host_section.png differ
diff --git a/docs/images/supvisors_address_process_section.png b/docs/images/supvisors_address_process_section.png
index 1de2b332..148cb0ca 100644
Binary files a/docs/images/supvisors_address_process_section.png and b/docs/images/supvisors_address_process_section.png differ
diff --git a/docs/images/supvisors_application_page.png b/docs/images/supvisors_application_page.png
index cd4eb357..12e3df98 100644
Binary files a/docs/images/supvisors_application_page.png and b/docs/images/supvisors_application_page.png differ
diff --git a/docs/images/supvisors_conciliation_page.png b/docs/images/supvisors_conciliation_page.png
index 358e4eb3..a9478834 100644
Binary files a/docs/images/supvisors_conciliation_page.png and b/docs/images/supvisors_conciliation_page.png differ
diff --git a/docs/images/supvisors_main_page.png b/docs/images/supvisors_main_page.png
index baf7cdef..1662c52d 100644
Binary files a/docs/images/supvisors_main_page.png and b/docs/images/supvisors_main_page.png differ
diff --git a/docs/images/supvisors_menu.png b/docs/images/supvisors_menu.png
index 34b6ffce..d81da081 100644
Binary files a/docs/images/supvisors_menu.png and b/docs/images/supvisors_menu.png differ
diff --git a/docs/special.rst b/docs/special.rst
index b8d6c20b..b6d087e7 100644
--- a/docs/special.rst
+++ b/docs/special.rst
@@ -170,12 +170,13 @@ and in accordance with the rules defined for this program, i.e. the ``address_li
 
 
 Choosing a node
-~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~
 
 The following rules are applicable whatever the chosen strategy:
 
+    * the process must not be already in a *running* state in a broad sense, i.e. ``RUNNING``, ``STARTING`` or ``BACKOFF``,
     * the chosen node must be ``RUNNING``,
-    * the *loading* of the chosen address must not exceed 100% when adding the ``loading`` of the process to be started.
+    * the *loading* of the chosen node must not exceed 100% when adding the ``loading`` of the process to be started.
 
 The *loading* of the chosen node is defined as the sum of the ``loading`` of each process running on this address.
 
@@ -189,6 +190,15 @@ the ``address_list`` having the greatest expected *loading*.
 The aim is to maximize the loading of a node before starting to load another node.
 This strategy is more interesting when the resources are limited.
 
+When applying the ``LOCAL`` strategy, **Supvisors** chooses the local node provided that it is compliant with the ``address_list``.
+A typical use case is to start an HCI application on a given workstation, while other applications / services may be distributed
+over other nodes.
+
+.. attention::
+
+    A consequence of choosing the ``LOCAL`` strategy as the default ``starting_strategy`` in the :ref:`supvisors_section`
+    is that no process will be started on other node than the Master node.
+
 
 Starting a process
 ~~~~~~~~~~~~~~~~~~
diff --git a/docs/supervisorctl.rst b/docs/supervisorctl.rst
index 764551b1..632d85c3 100644
--- a/docs/supervisorctl.rst
+++ b/docs/supervisorctl.rst
@@ -153,7 +153,7 @@ Application Control
 -------------------
 
 From this part, a starting strategy may be required in the command lines.
-It can take values among { ``CONFIG``, ``LESS_LOADED``, ``MOST_LOADED`` }.
+It can take values among { ``CONFIG``, ``LESS_LOADED``, ``MOST_LOADED``, ``LOCAL`` }.
 
 ``start_application strategy``
 
diff --git a/supvisors/addressmapper.py b/supvisors/addressmapper.py
index 12306b22..1338088f 100644
--- a/supvisors/addressmapper.py
+++ b/supvisors/addressmapper.py
@@ -28,8 +28,7 @@ class AddressMapper(object):
         - logger: a reference to the common logger,
         - addresses: the list of addresses defined in the Supvisors configuration file,
         - local_addresses: the list of known aliases of the current host, i.e. the host name and the IPv4 addresses,
-        - local_address: the usage name of the current host, i.e. the name in the known aliases corresponding
-        to an address of the Supvisors list. """
+        - local_address: the usage name of the current host, i.e. the name in the known aliases corresponding to an address of the Supvisors list. """
 
     def __init__(self, logger):
         """ Initialization of the attributes. """
@@ -52,7 +51,7 @@ def addresses(self, addr):
         self.logger.info('Expected addresses: {}'.format(addr))
         # store IP list as found in config file
         self._addresses = addr
-        # get IP list for local board
+        # get IP list for local node
         self.local_address = self.expected(self.local_addresses)
         self.logger.info('Local addresses: {} - Local address: {}'.format(self.local_addresses, self.local_address))
 
@@ -61,8 +60,7 @@ def valid(self, address):
         return address in self._addresses
 
     def filter(self, address_list):
-        """ Returns a list of expected addresses from a list of names or ip addresses identifying
-        different locations. """
+        """ Returns a list of expected nodes from a list of names or ip addresses identifying different locations. """
         # filter unknown addresses
         addresses = [address for address in address_list if self.valid(address)]
         # remove duplicates keeping the same ordering
diff --git a/supvisors/application.py b/supvisors/application.py
index bdd2879b..6ce19d8c 100644
--- a/supvisors/application.py
+++ b/supvisors/application.py
@@ -17,9 +17,13 @@
 # limitations under the License.
 # ======================================================================
 
+from typing import Mapping, Sequence
+
+from supervisor.loggers import Logger
 from supervisor.states import *
 
-from supvisors.ttypes import (ApplicationStates,
+from supvisors.process import ProcessStatus
+from supvisors.ttypes import (ApplicationStates, Payload,
                               StartingFailureStrategies,
                               RunningFailureStrategies)
 
@@ -28,38 +32,36 @@ class ApplicationRules(object):
     """ Definition of the rules for starting an application, iaw rules file.
 
     Attributes are:
-        - start_sequence: defines the order of this application when starting
-        all the applications in the DEPLOYMENT state,
-        0 means: no automatic start,
-        - stop_sequence: defines the order of this application when stopping
-        all the applications,
-        0 means: immediate stop,
-        - starting_failure_strategy: defines the strategy (in
-        StartingFailureStrategies) to apply when a required process cannot be
-        started during the starting of the application,
-        - running_failure_strategy: defines the default strategy (in
-        RunningFailureStrategies) to apply when a required process crashes when
-        the application is running.
+        - start_sequence: defines the order of this application when starting all the applications in the DEPLOYMENT state (0 means: no automatic start),
+        - stop_sequence: defines the order of this application when stopping all the applications (0 means: immediate stop),
+        - starting_failure_strategy: defines the strategy (in StartingFailureStrategies) to apply when a required process cannot be started during the starting of the application,
+        - running_failure_strategy: defines the default strategy (in RunningFailureStrategies) to apply when a required process crashes when the application is running.
     """
 
     def __init__(self):
-        """ Initialization of the attributes. """
+        """ Initialization of the attributes.
+        """
         self.start_sequence = 0
         self.stop_sequence = 0
         self.starting_failure_strategy = StartingFailureStrategies.ABORT
         self.running_failure_strategy = RunningFailureStrategies.CONTINUE
 
-    def __str__(self):
-        """ Contents as string. """
-        return 'start_sequence={} stop_sequence={} ' \
-               'starting_failure_strategy={} running_failure_strategy={}' \
+    def __str__(self) -> str:
+        """ Get the application rules as string.
+
+        :return: the printable application rules
+        """
+        return 'start_sequence={} stop_sequence={} starting_failure_strategy={} running_failure_strategy={}' \
             .format(self.start_sequence, self.stop_sequence,
                     StartingFailureStrategies.to_string(self.starting_failure_strategy),
                     RunningFailureStrategies.to_string(self.running_failure_strategy))
 
     # serialization
-    def serial(self):
-        """ Return a serializable form of the ApplicationRules. """
+    def serial(self) -> Payload:
+        """ Get a serializable form of the application rules.
+
+        :return: the application rules in a dictionary
+        """
         return {'start_sequence': self.start_sequence,
                 'stop_sequence': self.stop_sequence,
                 'starting_failure_strategy': StartingFailureStrategies.to_string(self.starting_failure_strategy),
@@ -71,28 +73,29 @@ class ApplicationStatus(object):
     """ Class defining the status of an application in Supvisors.
 
     Attributes are:
-        - application_name: the name of the application, corresponding to the
-        Supervisor group name,
+        - application_name: the name of the application, corresponding to the Supervisor group name,
         - state: the state of the application in ApplicationStates,
-        - major_failure: a status telling if a required process is stopped
-        while the application is running,
-        - minor_failure: a status telling if an optional process has crashed
-        while the application is running,
-        - processes: the map (key is process name) of the ProcessStatus
-        belonging to the application,
+        - major_failure: a status telling if a required process is stopped while the application is running,
+        - minor_failure: a status telling if an optional process has crashed while the application is running,
+        - processes: the map (key is process name) of the ProcessStatus belonging to the application,
         - rules: the ApplicationRules instance applicable to the application,
-        - start_sequence: the sequencing to start the processes belonging to
-        the application, as a dictionary.
-        The value corresponds to a list of processes having the same sequence
-        order, used as key.
-        - stop_sequence: the sequencing to stop the processes belonging to the
-        application, as a dictionary.
-        The value corresponds to a list of processes having the same sequence
-        order, used as key.
+        - start_sequence: the sequencing to start the processes belonging to the application, as a dictionary.
+        - stop_sequence: the sequencing to stop the processes belonging to the application, as a dictionary.
+
+    For start and stop sequences, each entry is a list of processes having the same sequence order, used as key.
     """
 
-    def __init__(self, application_name, logger):
-        """ Initialization of the attributes. """
+    # types for annotations
+    ApplicationSequence = Mapping[int, Sequence[ProcessStatus]]
+    PrintableApplicationSequence = Mapping[int, Sequence[str]]
+    ProcessMap = Mapping[str, Sequence[ProcessStatus]]
+
+    def __init__(self, application_name: str, logger: Logger) -> None:
+        """ Initialization of the attributes.
+
+        :param application_name: the name of the application
+        :param logger: the common logger used throughout Supvisors
+        """
         # keep reference to common logger
         self.logger = logger
         # information part
@@ -101,37 +104,51 @@ def __init__(self, application_name, logger):
         self.major_failure = False
         self.minor_failure = False
         # process part
-        self.processes = {}  # {process_name: [process]}
+        self.processes: ApplicationStatus.ProcessMap = {}
         self.rules = ApplicationRules()
-        self.start_sequence = {}  # {sequence: [process]}
-        self.stop_sequence = {}  # {sequence: [process]}
+        self.start_sequence: ApplicationStatus.ApplicationSequence = {}
+        self.stop_sequence: ApplicationStatus.ApplicationSequence = {}
 
     # access
-    def running(self):
-        """ Return True if application is running. """
-        return self.state in [ApplicationStates.STARTING,
-                              ApplicationStates.RUNNING]
+    def running(self) -> bool:
+        """ Return True if the application is running.
+
+        :return: the running status of the process
+        """
+        return self.state in [ApplicationStates.STARTING, ApplicationStates.RUNNING]
 
-    def stopped(self):
-        """ Return True if application is stopped. """
+    def stopped(self) -> bool:
+        """ Return True if the application is stopped.
+
+        :return: the running status of the process
+        """
         return self.state == ApplicationStates.STOPPED
 
     @property
     def state(self):
-        """ Property for the 'state' attribute. """
+        """ Getter of state attribute.
+
+         :return: the application state
+         """
         return self._state
 
     @state.setter
-    def state(self, newState):
-        if self._state != newState:
-            self._state = newState
-            self.logger.info('Application {} is {}'
-                             .format(self.application_name,
-                                     self.state_string()))
+    def state(self, new_state: ApplicationStates) -> None:
+        """ Setter of state attribute.
+
+        :param new_state: the new application state
+        :return: None
+        """
+        if self._state != new_state:
+            self._state = new_state
+            self.logger.info('Application {} is {}'.format(self.application_name, self.state_string()))
 
     # serialization
-    def serial(self):
-        """ Return a serializable form of the ApplicationStatus. """
+    def serial(self) -> Payload:
+        """ Get a serializable form of the application status.
+
+        :return: the application status in a dictionary
+        """
         return {'application_name': self.application_name,
                 'statecode': self.state,
                 'statename': self.state_string(),
@@ -139,32 +156,52 @@ def serial(self):
                 'minor_failure': self.minor_failure}
 
     # methods
-    def state_string(self):
-        """ Return the application state as a string. """
+    def state_string(self) -> str:
+        """ Get the application state as a string.
+
+        :return: the application state as a string
+        """
         return ApplicationStates.to_string(self.state)
 
-    def add_process(self, process):
-        """ Add a new process to the process list. """
+    def add_process(self, process: ProcessStatus) -> None:
+        """ Add a new process to the process list.
+
+        :param process: the process status to be added to the application
+        :return:
+        """
         self.processes[process.process_name] = process
 
-    def update_sequences(self):
-        """ Evaluate the sequencing of the starting / stopping application
-        from its list of processes. """
+    @staticmethod
+    def printable_sequence(application_sequence: ApplicationSequence) -> PrintableApplicationSequence:
+        """ Get printable application sequence for log.
+        Only the name of the process is kept.
+
+        :return: the simplified sequence
+        """
+        return {seq: [proc.process_name for proc in proc_list]
+                for seq, proc_list in application_sequence.items()}
+
+    def update_sequences(self) -> None:
+        """ Evaluate the sequencing of the starting / stopping application from its list of processes.
+
+        :return: None
+        """
         # fill ordering iaw process rules
         self.start_sequence.clear()
         self.stop_sequence.clear()
         for process in self.processes.values():
-            self.start_sequence.setdefault(process.rules.start_sequence,
-                                           []).append(process)
-            self.stop_sequence.setdefault(process.rules.stop_sequence,
-                                          []).append(process)
+            self.start_sequence.setdefault(process.rules.start_sequence, []).append(process)
+            self.stop_sequence.setdefault(process.rules.stop_sequence, []).append(process)
         self.logger.debug('Application {}: start_sequence={} stop_sequence={}'
                           .format(self.application_name,
-                                  self.start_sequence,
-                                  self.stop_sequence))
+                                  self.printable_sequence(self.start_sequence),
+                                  self.printable_sequence(self.stop_sequence)))
+
+    def update_status(self) -> None:
+        """ Update the state of the application iaw the state of its processes.
 
-    def update_status(self):
-        """ Update the state of the application iaw the state of its processes. """
+        :return: None
+        """
         starting, running, stopping, major_failure, minor_failure = (False,) * 5
         for process in self.processes.values():
             self.logger.trace('Process {}: state={} required={} exit_expected={}'
@@ -174,8 +211,7 @@ def update_status(self):
                                       process.expected_exit))
             if process.state == ProcessStates.RUNNING:
                 running = True
-            elif process.state in [ProcessStates.STARTING,
-                                   ProcessStates.BACKOFF]:
+            elif process.state in [ProcessStates.STARTING, ProcessStates.BACKOFF]:
                 starting = True
             # STOPPING is not in STOPPED_STATES
             elif process.state == ProcessStates.STOPPING:
@@ -186,8 +222,7 @@ def update_status(self):
                     # running application
                     # exception is made for an EXITED process with an expected
                     # exit code
-                    if process.state != ProcessStates.EXITED or \
-                            not process.expected_exit:
+                    if process.state != ProcessStates.EXITED or not process.expected_exit:
                         major_failure = True
                 else:
                     # an optional process is a minor failure for a running
@@ -196,10 +231,8 @@ def update_status(self):
                             (process.state == ProcessStates.EXITED and not process.expected_exit)):
                         minor_failure = True
             # all other STOPPED-like states are considered normal
-        self.logger.trace('Application {}: starting={} running={} stopping={} '
-                          'major_failure={} minor_failure={}'
-                          .format(self.application_name, starting, running, stopping,
-                                  major_failure, minor_failure))
+        self.logger.trace('Application {}: starting={} running={} stopping={} major_failure={} minor_failure={}'
+                          .format(self.application_name, starting, running, stopping, major_failure, minor_failure))
         # apply rules for state
         if starting:
             self.state = ApplicationStates.STARTING
@@ -209,7 +242,6 @@ def update_status(self):
             self.state = ApplicationStates.RUNNING
         else:
             self.state = ApplicationStates.STOPPED
-        # update major_failure and minor_failure status
-        # (only for running applications)
+        # update major_failure and minor_failure status (only for running applications)
         self.major_failure = major_failure and self.running()
         self.minor_failure = minor_failure and self.running()
diff --git a/supvisors/client/java/org/supvisors/common/DataConversion.java b/supvisors/client/java/org/supvisors/common/DataConversion.java
index 69d5f78e..136c8891 100644
--- a/supvisors/client/java/org/supvisors/common/DataConversion.java
+++ b/supvisors/client/java/org/supvisors/common/DataConversion.java
@@ -49,7 +49,7 @@ public static <T extends SupvisorsAnyInfo> HashMap<String, T> arrayToMap(
                 final T info = klass.getDeclaredConstructor(map.getClass()).newInstance(map);
                 infoMap.put(info.getName(), info);
             } catch (Exception exc) {
-                System.err.println("DataConversion: " + exc);
+                System.err.println("DataConversion: " + exc.getCause());
             }
         }
         return infoMap;
diff --git a/supvisors/client/java/org/supvisors/common/StartingStrategy.java b/supvisors/client/java/org/supvisors/common/StartingStrategy.java
index 57c491fd..1e898e2d 100644
--- a/supvisors/client/java/org/supvisors/common/StartingStrategy.java
+++ b/supvisors/client/java/org/supvisors/common/StartingStrategy.java
@@ -23,7 +23,8 @@
 public enum StartingStrategy {
     CONFIG(0),
     LESS_LOADED(1),
-    MOST_LOADED(2);
+    MOST_LOADED(2),
+    LOCAL(3);
 
     /** The strategy code. */
     private int strategyCode;
diff --git a/supvisors/client/java/org/supvisors/rpc/SupvisorsXmlRpc.java b/supvisors/client/java/org/supvisors/rpc/SupvisorsXmlRpc.java
index 22644e7d..dcf8dbdd 100644
--- a/supvisors/client/java/org/supvisors/rpc/SupvisorsXmlRpc.java
+++ b/supvisors/client/java/org/supvisors/rpc/SupvisorsXmlRpc.java
@@ -166,10 +166,8 @@ public HashMap<String, SupvisorsProcessInfo> getProcessInfo(final String namespe
      * @return HashMap<String, SupvisorsProcessEvent>: Information about the
      * processes, sorted by namespec.
      */
-    public HashMap<String, SupvisorsProcessEvent> getAllLocalProcessInfo()
-            throws XmlRpcException {
-        Object[] objectsArray = client.rpcCall(Namespace + "get_all_local_process_info",
-            null, Object[].class);
+    public HashMap<String, SupvisorsProcessEvent> getAllLocalProcessInfo() throws XmlRpcException {
+        Object[] objectsArray = client.rpcCall(Namespace + "get_all_local_process_info", null, Object[].class);
         return DataConversion.arrayToMap(objectsArray, SupvisorsProcessEvent.class);
     }
 
@@ -182,11 +180,9 @@ public HashMap<String, SupvisorsProcessEvent> getAllLocalProcessInfo()
      * @return SupvisorsProcessEvent: Information about the process.
      * @throws XmlRpcException: with code BAD_NAME if namespec is unknown to Supvisors.
      */
-    public SupvisorsProcessEvent getLocalProcessInfo(final String namespec)
-            throws XmlRpcException {
+    public SupvisorsProcessEvent getLocalProcessInfo(final String namespec) throws XmlRpcException {
         Object[] params = new Object[]{namespec};
-        HashMap result = client.rpcCall(Namespace + "get_local_process_info",
-            params, HashMap.class);
+        HashMap result = client.rpcCall(Namespace + "get_local_process_info", params, HashMap.class);
         return new SupvisorsProcessEvent(result);
     }
 
diff --git a/supvisors/commander.py b/supvisors/commander.py
index a9c2d0b3..df8c1694 100644
--- a/supvisors/commander.py
+++ b/supvisors/commander.py
@@ -19,11 +19,16 @@
 
 import time
 
+from typing import Any, Mapping, Sequence
+
 from supervisor.childutils import get_asctime
-from supervisor.states import ProcessStates
 
+from supvisors.application import ApplicationStatus
+from supvisors.infosource import SupervisordSource
+from supvisors.listener import SupervisorListener
+from supvisors.process import ProcessStatus
 from supvisors.strategy import get_address
-from supvisors.ttypes import StartingStrategies, StartingFailureStrategies
+from supvisors.ttypes import ProcessStates, StartingStrategies, StartingFailureStrategies
 from supvisors.utils import supvisors_shortcuts
 
 
@@ -32,125 +37,174 @@ class ProcessCommand(object):
 
     Attributes are:
         - process: the process wrapped,
+        - strategy: the strategy used to start the process if applicable,
         - request_time: the date when the command is requested,
         - ignore_wait_exit: used to command a process out of its application,
         - extra_args: additional arguments to the command line.
     """
+    TIMEOUT = 10
+
+    def __init__(self, process: ProcessStatus, strategy: StartingStrategies = None) -> None:
+        """ Initialization of the attributes.
 
-    def __init__(self, process):
-        """ Initialization of the attributes. """
+        :param supvisors: the global Supvisors structure.
+        """
         self.process = process
+        self.strategy = strategy
         self.request_time = 0
         self.ignore_wait_exit = False
         self.extra_args = ''
 
-    def __str__(self):
-        """ Contents as string. """
-        return 'process={} state={} last_event_time={} ' \
-               'request_time={} ignore_wait_exit={} extra_args="{}"' \
-            .format(self.process.namespec(), self.process.state,
-                    self.process.last_event_time, self.request_time,
-                    self.ignore_wait_exit, self.extra_args)
+    def __str__(self) -> str:
+        """ Get the process command as string.
 
-    def timeout(self, now):
-        """ Return True if there is still no event 5 seconds past the request. """
-        return max(self.process.last_event_time, self.request_time) + 5 < now
+        :return: the printable process command
+        """
+        return 'process={} state={} last_event_time={} strategy={} request_time={} ' \
+               'ignore_wait_exit={} extra_args="{}"' \
+            .format(self.process.namespec(), self.process.state, self.process.last_event_time,
+                    self.strategy, self.request_time,  self.ignore_wait_exit, self.extra_args)
+
+    def timed_out(self, now: int) -> bool:
+        """ Return True if there is still no event TIMEOUT seconds past the request.
+
+        :param now: the current time
+        :param timeout: the timeout duration
+        :return: the timeout status
+        """
+        return max(self.process.last_event_time, self.request_time) + ProcessCommand.TIMEOUT < now
 
 
 class Commander(object):
-    """ Base class handling the starting / stopping of processes and
-    applications.
+    """ Base class handling the starting / stopping of processes and applications.
 
     Attributes are:
-        - planned_sequence: the applications to be commanded, as a dictionary
-        of process commands, grouped by application sequence order,
-        application name and process sequence order,
-        - planned_jobs: the current sequence of applications to be commanded,
-        as a dictionary of process commands, grouped by application name and
-        process sequence order,
-        - current_jobs: a dictionary of process commands, grouped by
-        application name.
+        - planned_sequence: the applications to be commanded, as a dictionary of process commands,
+        grouped by application sequence order, application name and process sequence order,
+        - planned_jobs: the current sequence of applications to be commanded, as a dictionary of process commands,
+        grouped by application name and process sequence order,
+        - current_jobs: a dictionary of process commands, grouped by application name.
     """
 
-    def __init__(self, supvisors):
-        """ Initialization of the attributes. """
+    # Annotation types
+    CommandList = Sequence[ProcessCommand]
+    CurrentJobs = Mapping[str, CommandList]  # {app_name: [proc_cmd]}
+    PlannedJobs = Mapping[str, Mapping[int, CommandList]]  # {app_name: {proc_seq: [proc_cmd]}}
+    PlannedSequence = Mapping[int, PlannedJobs]  # {app_seq: {app_name: {proc_seq: [proc_cmd]}}}
+
+    # Annotation types for printing facilities
+    PrintableCommandList = Sequence[str]
+    PrintableCurrentJobs = Mapping[str, PrintableCommandList]
+    PrintablePlannedJobs = Mapping[str, Mapping[int, PrintableCommandList]]
+    PrintablePlannedSequence = Mapping[int, PrintablePlannedJobs]
+
+    def __init__(self, supvisors: Any) -> None:
+        """ Initialization of the attributes.
+
+        :param supvisors: the global Supvisors structure.
+        """
         # keep a reference of the Supvisors data
         self.supvisors = supvisors
         # shortcuts for readability
         supvisors_shortcuts(self, ['logger'])
         # attributes
-        self.planned_sequence = {}  # {app_seq: {app_name: {proc_seq: [proc_cmd]}}}
-        self.planned_jobs = {}  # {app_name: {proc_seq: [proc_cmd]}}
-        self.current_jobs = {}  # {app_name: [proc_cmd]}
-
-    def in_progress(self):
-        """ Return True if there are jobs planned or in progress. """
-        self.logger.debug('progress: planned_sequence={} planned_jobs={} '
-                          'current_jobs={}'
+        self.planned_sequence: Commander.PlannedSequence = {}
+        self.planned_jobs: Commander.PlannedJobs = {}
+        self.current_jobs: Commander.CurrentJobs = {}
+
+    def in_progress(self) -> bool:
+        """ Return True if there are jobs planned or in progress.
+
+        :return: the progress status
+        """
+        self.logger.trace('Commander.in_progress: planned_sequence={} planned_jobs={} current_jobs={}'
                           .format(self.printable_planned_sequence(),
                                   self.printable_planned_jobs(),
                                   self.printable_current_jobs()))
         return len(self.planned_sequence) or len(self.planned_jobs) or len(self.current_jobs)
 
-    def has_application(self, application_name):
-        """ Return True if application is in jobs. """
+    def has_application(self, application_name: str) -> bool:
+        """ Return True if application is part of the jobs currently considered.
+
+        :param application_name: the application name
+        :return: the presence status of the application
+        """
         # get all planned applications
-        planned_applications = [app_name for jobs
-                                in self.planned_sequence.values()
-                                for app_name in jobs]
+        planned_applications = [app_name for jobs in self.planned_sequence.values() for app_name in jobs]
         # search for application name in internal structures
         return (application_name in planned_applications
                 or application_name in self.planned_jobs
                 or application_name in self.current_jobs)
 
     # log facilities
-    def printable_planned_sequence(self):
-        """ Simple form of planned_sequence, so that it can be printed. """
+    def printable_planned_sequence(self) -> PrintablePlannedSequence:
+        """ Simple form of planned_sequence, so that it can be printed.
+
+        :return: the planned sequence without objects
+        """
         return {application_sequence: {application_name: {sequence: Commander.printable_command_list(commands)
                                                           for sequence, commands in sequences.items()}
                                        for application_name, sequences in applications.items()}
                 for application_sequence, applications
                 in self.planned_sequence.items()}
 
-    def printable_planned_jobs(self):
-        """ Simple form of planned_jobs, so that it can be printed. """
+    def printable_planned_jobs(self) -> PrintablePlannedJobs:
+        """ Simple form of planned_jobs, so that it can be printed.
+
+        :return: the planned jobs without objects
+        """
         return {application_name: {sequence: Commander.printable_command_list(commands)
                                    for sequence, commands in sequences.items()}
                 for application_name, sequences in self.planned_jobs.items()}
 
-    def printable_current_jobs(self):
-        """ Simple form of current_jobs, so that it can be printed. """
+    def printable_current_jobs(self) -> PrintableCurrentJobs:
+        """ Simple form of current_jobs, so that it can be printed.
+
+        :return: the current jobs without objects
+        """
         return {application_name: Commander.printable_command_list(commands)
                 for application_name, commands in self.current_jobs.items()}
 
     @staticmethod
-    def printable_command_list(commands):
-        """ Simple form of process_list, so that it can be printed. """
+    def printable_command_list(commands: CommandList) -> PrintableCommandList:
+        """ Simple form of process_list, so that it can be printed.
+
+        :param commands: the process command list
+        :return: the process command list without objects
+        """
         return [command.process.namespec() for command in commands]
 
-    def get_process_command(self, process, jobs):
-        """ Get the process wrapper from list. """
-        return next((command for command in jobs
-                     if command.process == process), None)
-
-    def initial_jobs(self):
-        """ Initializes the planning of the jobs (start or stop). """
-        self.logger.debug('planned_sequence={}'
-                          .format(self.printable_planned_sequence()))
-        # pop lower application group from planned_sequence
-        if self.planned_sequence:
-            self.planned_jobs = self.planned_sequence.pop(
-                min(self.planned_sequence.keys()))
-            self.logger.debug('planned_jobs={}'
-                              .format(self.printable_planned_jobs()))
-            # iterate on copy to avoid problems with deletions
+    @staticmethod
+    def get_process_command(process: ProcessStatus, jobs: CommandList) -> ProcessCommand:
+        """ Get the process wrapper from list.
+
+        :param process: the process status to search
+        :param jobs: the process command list
+        :return: the process command found
+        """
+        return next((command for command in jobs if command.process is process), None)
+
+    def trigger_jobs(self) -> None:
+        """ Triggers a sequence of the jobs planned (start or stop).
+
+        :return: None
+        """
+        self.logger.debug('Commander.trigger_jobs: planned_sequence={}'.format(self.printable_planned_sequence()))
+        # pop lower sequence from planned_sequence
+        while self.planned_sequence and not self.planned_jobs and not self.current_jobs:
+            self.planned_jobs = self.planned_sequence.pop(min(self.planned_sequence.keys()))
+            self.logger.debug('Commander.trigger_jobs: planned_jobs={}'.format(self.printable_planned_jobs()))
+            # iterate on copy to avoid problems with key deletions
             for application_name in list(self.planned_jobs.keys()):
                 self.process_application_jobs(application_name)
-        else:
-            self.logger.debug('command completed')
+        self.logger.trace('Commander.trigger_jobs: waiting for events to progress')
+
+    def process_application_jobs(self, application_name: str) -> None:
+        """ Triggers the next sequenced group of the application.
 
-    def process_application_jobs(self, application_name):
-        """ Triggers the starting of a subset of the application. """
+        :param application_name: the application name
+        :return: None
+        """
         if application_name in self.planned_jobs:
             sequence = self.planned_jobs[application_name]
             self.current_jobs[application_name] = jobs = []
@@ -158,62 +212,138 @@ def process_application_jobs(self, application_name):
             while sequence and not jobs and application_name in self.planned_jobs:
                 # pop lower group from sequence
                 group = sequence.pop(min(sequence.keys()))
-                self.logger.debug('application {} - next group: {}'
-                                  .format(application_name,
-                                          self.printable_command_list(group)))
+                self.logger.debug('Commander.process_application_jobs: application {} - next group: {}'
+                                  .format(application_name, self.printable_command_list(group)))
                 for command in group:
-                    self.logger.trace('{} - state={}'
-                                      .format(command.process.namespec(),
-                                              command.process.state_string()))
+                    self.logger.trace('Commander.process_application_jobs: {} - state={}'
+                                      .format(command.process.namespec(), command.process.state_string()))
                     self.process_job(command, jobs)
-            self.logger.debug('current_jobs={}'
+            self.logger.debug('Commander.process_application_jobs: current_jobs={}'
                               .format(self.printable_current_jobs()))
-            # if nothing in progress when exiting the loop,
-            # delete application entry in current_jobs
+            # if nothing in progress when exiting the loop, delete application entry in current_jobs
             if not jobs:
-                self.logger.debug('no more jobs for application {}'
+                self.logger.debug('Commander.process_application_jobs: no more jobs for application {}'
                                   .format(application_name))
                 self.current_jobs.pop(application_name, None)
             # clean application job if its sequence is empty
             if not sequence:
-                self.logger.debug('all jobs planned for application {}'
+                self.logger.debug('Commander.process_application_jobs: all jobs planned for application {}'
                                   .format(application_name))
                 self.planned_jobs.pop(application_name, None)
         else:
-            self.logger.warn('application {} not found in jobs'
+            self.logger.warn('Commander.process_application_jobs: application {} not found in jobs'
                              .format(application_name))
 
-    def process_job(self, command, jobs):
+    def process_job(self, command: ProcessCommand, jobs: CommandList) -> bool:
         """ Perform the action on process and push progress in jobs list.
-        Method must be implemented in subclasses. """
+        Method must be implemented in subclasses Starter and Stopper.
+
+        :param command: the process command
+        :param jobs: the reference process command list the process command belongs to
+        :return: the job status
+        """
         raise NotImplementedError
 
+    def check_progress(self, condition: str, process_state: int) -> bool:
+        """ Check the progress of the application starting or stopping.
 
-class Starter(Commander):
-    """ Class handling the starting of processes and applications.
+        :param condition: the ProcessStatus method name to be used
+        :param process_state: the process state to force upon failure (among ProcessStates)
+        :return: True when starting or stopping is completed
+        """
+        """  """
+        in_progress = self.in_progress()
+        self.logger.debug('Starter.check_progress: in_progress={}'.format(in_progress))
+        if in_progress:
+            commands = [command for command_list in self.current_jobs.values()
+                        for command in command_list]
+            if commands:
+                # once the start_process (resp. stop_process) has been called, a STARTING (resp. STOPPING) event
+                # is expected quite immediately from the Supervisor instance
+                # if it doesn't, the whole sequencing would block waiting for an event that may never happen
+                # the following instructions have therefore been added for safekeeping, but it has never been reported
+                # to occur so far
+                now = time.time()
+                self.logger.trace('Starter.check_progress: now={} checking commands={}'
+                                  .format(now, [str(command) for command in commands]))
+                for command in commands:
+                    # get the ProcessStatus method corresponding to condition and call it
+                    class_method = getattr(ProcessStatus, condition)
+                    if class_method(command.process):
+                        if command.timed_out(now):
+                            self.logger.error('Starter.check_progress: {} still {} after {} seconds so abort'
+                                              .format(command.process.namespec(), condition, ProcessCommand.TIMEOUT))
+                            # generate a process event for this process to inform all Supvisors instances
+                            self.force_process_state(command.process.namespec(), process_state,
+                                                     'Still {} {} seconds after request'
+                                                     .format(condition, ProcessCommand.TIMEOUT))
+                            # don't wait for event, just abort the job
+                            jobs = self.current_jobs[command.process.application_name]
+                            jobs.remove(command)
+                            # check if there are remaining jobs in progress for this application
+                            if not jobs:
+                                self.after_event(command.process.application_name)
+            else:
+                # no commands in the pipe
+                # this can happen when nothing had to be stopped inside the planned_jobs
+                self.logger.warn('Starter.check_progress: no commands in progress but planned sequence still ongoing')
+                # check if there are planned jobs
+                if not self.planned_jobs:
+                    # trigger next sequence of applications
+                    self.trigger_jobs()
+                else:
+                    self.logger.error('Starter.check_progress: UNEXPECTED')
+        # return True when starting is completed
+        return not self.in_progress()
 
-    Attributes are:
-        - strategy: the starting strategy applied, defaulted to the value
-        set in the Supervisor configuration file.
-    """
+    def after_event(self, application_name: str) -> None:
+        """ This method is called when the last event has been received for the current jobs linked to the application.
+        Trigger the next application sub-sequence.
 
-    def __init__(self, supvisors):
-        """ Initialization of the attributes. """
-        Commander.__init__(self, supvisors)
-        self._strategy = self.get_default_strategy()
+        :param application_name: the name of the application
+        :return: None
+        """
+        # remove application entry from current_jobs
+        del self.current_jobs[application_name]
+        # trigger next application sub-sequence if any
+        if application_name in self.planned_jobs:
+            self.process_application_jobs(application_name)
+        else:
+            # nothing left for this application
+            self.logger.info('Commander.after_event: planned jobs completed for application {}'
+                             .format(application_name))
+            # check if there are planned jobs
+            if not self.planned_jobs:
+                self.logger.info('Commander.after_event: planned jobs completed for all applications')
+                # trigger next sequence of applications
+                self.trigger_jobs()
+
+    def force_process_state(self, namespec: str, state: int, reason: str) -> None:
+        """ Publish the process state requested to all Supvisors instances.
+
+        :param namespec: the process namespec
+        :param state: the process state to force (among ProcessStates)
+        :param reason: the reason declared
+        :return: None
+        """
+        """  """
+        # publish the process state to all Supvisors instances
+        state_string = ProcessStates.to_string(state)
+        self.logger.warn('Commander.force_process_state: force {} state to {}'.format(namespec, state_string))
+        try:
+            class_method = getattr(SupervisordSource, 'force_process_' + state_string.lower())
+            class_method(self.supvisors.info_source, namespec, reason)
+        except KeyError:
+            self.logger.error('Commander.force_process_state: process unknown to this Supervisor'.format(namespec))
+            # the Supvisors user is not forced to use the same process configuration on all machines,
+            # although it is strongly recommended to avoid troubles.
+            # so, publish directly a fake process event to all instances
+            class_method = getattr(SupervisorListener, 'force_process_' + state_string.lower())
+            class_method(self.supvisors.listener, namespec)
 
-    @property
-    def strategy(self):
-        """ Property for the 'strategy' attribute.
-        The setter is used to overload the default strategy (used in
-        rpcinterface and web page). """
-        return self._strategy
 
-    @strategy.setter
-    def strategy(self, strategy):
-        self.logger.info('start processes using strategy {}'.format(
-            StartingStrategies.to_string(strategy)))
-        self._strategy = strategy
+class Starter(Commander):
+    """ Class handling the starting of processes and applications. """
 
     def get_default_strategy(self):
         """ Return the default starting strategy from options. """
@@ -228,16 +358,17 @@ def abort(self):
     def start_applications(self):
         """ Plan and start the necessary jobs to start all the applications having a start_sequence.
         It uses the default strategy, as defined in the Supvisors section of the Supervisor configuration file. """
-        self.logger.info('start all applications')
+        strategy = self.get_default_strategy()
+        self.logger.info('Starter.start_applications: start all applications using strategy {}'
+                         .format(StartingStrategies.to_string(strategy)))
         # internal call: default strategy always used
-        self.strategy = self.get_default_strategy()
-        # starting initialization: push program list in todo list
+        # starting initialization: plan the whole sequence
         for application in self.supvisors.context.applications.values():
             # do not start an application that is not properly STOPPED
             if application.stopped() and application.rules.start_sequence > 0:
-                self.store_application_start_sequence(application)
+                self.store_application_start_sequence(application, strategy)
         # start work
-        self.initial_jobs()
+        self.trigger_jobs()
 
     def default_start_application(self, application):
         """ Plan and start the necessary jobs to start the application in parameter, with the default strategy. """
@@ -245,13 +376,13 @@ def default_start_application(self, application):
 
     def start_application(self, strategy, application):
         """ Plan and start the necessary jobs to start the application in parameter, with the strategy requested. """
-        self.logger.info('start application {}'.format(application.application_name))
-        # called from rpcinterface: strategy is a user choice
-        self.strategy = strategy
-        # push program list in todo list and start work
+        self.logger.info('Starter.start_application: start application {} using strategy {}'
+                         .format(application.application_name, StartingStrategies.to_string(strategy)))
+        # push program list in job list and start work
         if application.stopped():
-            self.store_application_start_sequence(application)
-            self.logger.debug('planned_sequence={}'.format(self.printable_planned_sequence()))
+            self.store_application_start_sequence(application, strategy)
+            self.logger.debug('Starter.start_application: planned_sequence={}'
+                              .format(self.printable_planned_sequence()))
             if self.planned_sequence:
                 # add application immediately to planned jobs if something in list
                 self.planned_jobs.update(self.planned_sequence.pop(min(self.planned_sequence.keys())))
@@ -260,25 +391,22 @@ def start_application(self, strategy, application):
         return not self.in_progress()
 
     def default_start_process(self, process):
-        """ Plan and start the necessary job to start the process in parameter,
-        with the default strategy.
+        """ Plan and start the necessary job to start the process in parameter, with the default strategy.
         Return False when starting not completed. """
         return self.start_process(self.get_default_strategy(), process)
 
     def start_process(self, strategy, process, extra_args=''):
-        """ Plan and start the necessary job to start the process in parameter,
-        with the strategy requested.
+        """ Plan and start the necessary job to start the process in parameter, with the strategy requested.
         Return False when starting not completed. """
-        self.logger.info('start process {}'.format(process.namespec()))
-        # called from rpcinterface: strategy is a user choice
-        self.strategy = strategy
+        self.logger.info('Starter.start_process: start process {} using strategy {}'
+                         .format(process.namespec(), StartingStrategies.to_string(strategy)))
         # store extra arguments to be passed to the command line
-        command = ProcessCommand(process)
+        command = ProcessCommand(process, strategy)
         command.extra_args = extra_args
         # WARN: when starting a single process (outside the scope of an application starting),
         # do NOT consider the 'wait_exit' rule
         command.ignore_wait_exit = True
-        # push program list in todo list and start work
+        # push program list in job list and start work
         job = self.current_jobs.setdefault(process.application_name, [])
         starting = self.process_job(command, job)
         # upon failure, remove inProgress entry if empty
@@ -287,36 +415,17 @@ def start_process(self, strategy, process, extra_args=''):
         # return True when started
         return not starting
 
-    def check_starting(self):
-        """ Check the progress of the application starting. """
-        self.logger.debug('starting progress: planned_sequence={} planned_jobs={} current_jobs={}'
-                          .format(self.printable_planned_sequence(),
-                                  self.printable_planned_jobs(),
-                                  self.printable_current_jobs()))
-        # once the start_process has been called, a STARTING event is expected
-        # in less than 5 seconds
-        now = time.time()
-        commands = [command
-                    for command_list in self.current_jobs.values()
-                    for command in command_list]
-        self.logger.trace('now={} checking commands={}'.format(now, [str(command) for command in commands]))
-        for command in commands:
-            # depending on ini file, it may take a while before the process
-            # enters in RUNNING state so just test that is in not in a
-            # STOPPED-like state 5 seconds after request_time
-            if command.process.stopped() and command.timeout(now):
-                # generate a FATAL event for this process
-                self.force_process_fatal(command.process.namespec(),
-                                         'Still stopped 5 seconds after start request')
-        # return True when starting is completed
-        return not self.in_progress()
+    def check_starting(self) -> bool:
+        """ Check the progress of the application starting.
+
+        :return: True when starting is completed
+        """
+        return self.check_progress('stopped', ProcessStates.FATAL)
 
     def on_event(self, process):
-        """ Triggers the following of the start sequencing, depending on the
-        new process status. """
+        """ Triggers the following of the start sequencing, depending on the new process status. """
         try:
-            # first check if event is in the sequence logic,
-            # i.e. it corresponds to a process in current jobs
+            # first check if event is in the sequence logic, i.e. it corresponds to a process in current jobs
             jobs = self.current_jobs[process.application_name]
             command = self.get_process_command(process, jobs)
             assert command
@@ -345,13 +454,13 @@ def on_event_in_sequence(self, command, jobs):
                 jobs.remove(command)
         elif process.state == ProcessStates.BACKOFF:
             # something wrong happened, just wait
-            self.logger.warn('problems detected with {}'.format(process.namespec()))
+            self.logger.warn('Starter.on_event_in_sequence: problems detected with {}'.format(process.namespec()))
         elif process.state == ProcessStates.EXITED:
             # remove from inProgress
             jobs.remove(command)
             # an EXITED process is accepted if wait_exit is set
             if process.rules.wait_exit and process.expected_exit:
-                self.logger.info('expected exit for {}'.format(process.namespec()))
+                self.logger.info('Starter.on_event_in_sequence: expected exit for {}'.format(process.namespec()))
             else:
                 self.process_failure(process)
         elif process.state == ProcessStates.FATAL:
@@ -361,19 +470,9 @@ def on_event_in_sequence(self, command, jobs):
             self.process_failure(process)
         # check if there are remaining jobs in progress for this application
         if not jobs:
-            # remove application entry from current_jobs
-            del self.current_jobs[process.application_name]
-            # trigger next job for aplication
-            if process.application_name in self.planned_jobs:
-                self.process_application_jobs(process.application_name)
-            else:
-                self.logger.info('starting completed for application {}'.format(process.application_name))
-                # check if there are planned jobs
-                if not self.planned_jobs:
-                    # trigger next sequence of applications
-                    self.initial_jobs()
+            self.after_event(process.application_name)
 
-    def on_event_out_of_sequence(self, process):
+    def on_event_out_of_sequence(self, process: ProcessStatus) -> None:
         """ Manages the impact of a crash event that is out of the starting sequence.
 
         Note: Keeping in mind the possible origins of the event:
@@ -384,12 +483,11 @@ def on_event_out_of_sequence(self, process):
             1) The application is in the planned sequence, or process is in the planned jobs.
                => do nothing, give a chance to this Starter.
             2) The process is NOT in the application planned jobs.
-               The process was likely started previously in the sequence of
-               this Starter, and it crashed after its RUNNING state but before
-               the application is fully started.
+               The process was likely started previously in the sequence of this Starter,
+               and it crashed after its RUNNING state but before the application is fully started.
                => apply starting failure strategy through basic process_failure
             3) The application is NOT handled in this Starter
-               => running failure strategy could be applied outside of here
+               => running failure strategy to be applied outside of here
         """
         # find the conditions of case 2
         if process.crashed() and process.application_name in self.planned_jobs:
@@ -400,41 +498,42 @@ def on_event_out_of_sequence(self, process):
             if process not in planned_process_jobs:
                 self.process_failure(process)
 
-    def store_application_start_sequence(self, application):
-        """ Copy the start sequence and remove programs that are not meant
-        to be started automatically, i.e. their start_sequence is 0. """
-        application_sequence = {seq: [ProcessCommand(process) for process in processes]
+    def store_application_start_sequence(self, application, strategy):
+        """ Copy the start sequence and remove programs that are not meant to be started automatically,
+        i.e. their start_sequence is 0. """
+        application_sequence = {seq: [ProcessCommand(process, strategy) for process in processes]
                                 for seq, processes in application.start_sequence.items()
                                 if seq > 0}
         if len(application_sequence) > 0:
             sequence = self.planned_sequence.setdefault(application.rules.start_sequence, {})
             sequence[application.application_name] = application_sequence
 
-    def process_job(self, command, jobs):
+    def process_job(self, command: ProcessCommand, jobs: Commander.CommandList) -> bool:
         """ Start the process on the relevant address.
-        Return True if process is starting. """
+        Return True if the job is started. """
         starting = False
-        # process must be stopped
+        # process must be in stopped-like state
         process = command.process
         if process.stopped():
+            # whatever a node is found or not to start the process, the job is considered started
+            # in every cases, a notification is expected from Supervisor
+            command.request_time = time.time()
+            jobs.append(command)
+            starting = True
+            # find node iaw strategy
             namespec = process.namespec()
-            address = get_address(self.supvisors, self.strategy,
-                                  process.rules.addresses,
+            address = get_address(self.supvisors, command.strategy, process.rules.addresses,
                                   process.rules.expected_loading)
             if address:
-                self.logger.info('try to start {} at address={}'.format(namespec, address))
+                self.logger.info('Starter.process_job: request starting of {} at address={}'.format(namespec, address))
                 # use asynchronous xml rpc to start program
                 self.supvisors.zmq.pusher.send_start_process(address, namespec, command.extra_args)
-                # push to jobs and timestamp process
-                command.request_time = time.time()
-                self.logger.debug('{} requested to start at {}'
-                                  .format(namespec, get_asctime(command.request_time)))
-                jobs.append(command)
-                starting = True
+                self.logger.debug('Starter.process_job: {} requested to start on {} at {}'
+                                  .format(namespec, address, get_asctime(command.request_time)))
             else:
-                self.logger.warn('no resource available to start {}'.format(namespec))
-                self.force_process_fatal(namespec, 'no resource available')
-        # return True when process is starting
+                self.logger.warn('Starter.process_job: no resource available to start {}'.format(namespec))
+                self.force_process_state(namespec, ProcessStates.FATAL, 'no resource available')
+        # return True when the job is started
         return starting
 
     def process_failure(self, process):
@@ -442,77 +541,51 @@ def process_failure(self, process):
         application_name = process.application_name
         # impact of failure on application starting
         if process.rules.required:
-            self.logger.warn('starting failed for required {}'
-                             .format(process.process_name))
+            self.logger.warn('Starter.process_failure: starting failed for required {}'.format(process.process_name))
             # get starting failure strategy of related application
             application = self.supvisors.context.applications[application_name]
             failure_strategy = application.rules.starting_failure_strategy
             # apply strategy
             if failure_strategy == StartingFailureStrategies.ABORT:
-                self.logger.error('abort starting of application {}'
-                                  .format(application_name))
+                self.logger.error('Starter.process_failure: abort starting of application {}'.format(application_name))
                 # remove failed application from starting
-                # do not remove application from current_jobs as requests
-                # have already been sent
+                # do not remove application from current_jobs as requests have already been sent
                 self.planned_jobs.pop(application_name, None)
             elif failure_strategy == StartingFailureStrategies.STOP:
-                self.logger.error('stop application {}'
-                                  .format(application_name))
+                self.logger.error('Starter.process_failure: stop application {}'.format(application_name))
                 self.planned_jobs.pop(application_name, None)
                 self.supvisors.stopper.stop_application(application)
             else:
-                self.logger.warn('continue starting of application {}'
+                self.logger.warn('Starter.process_failure: continue starting of application {}'
                                  .format(application_name))
         else:
-            self.logger.warn('starting failed for optional {}'
-                             .format(process.process_name))
-            self.logger.warn('continue starting of application {}'
-                             .format(application_name))
-
-    def force_process_fatal(self, namespec, reason):
-        """ Publish the process state as FATAL to all Supvisors instances. """
-        self.logger.warn('force {} state to FATAL'.format(namespec))
-        try:
-            # this call updates the Supervisor data model
-            self.supvisors.info_source.force_process_fatal(namespec, reason)
-        except KeyError:
-            self.logger.error('process {} unknown to this Supervisor.'
-                              .format(namespec))
-            # the Supvisors user is not forced to use the same process
-            # configuration on all machines,
-            # although it is strongly recommended to avoid troubles.
-            # => publish directly a fake process event to all Supvisors instances
-            self.supvisors.listener.force_process_fatal(namespec)
+            self.logger.warn('Starter.process_failure: starting failed for optional {}'.format(process.process_name))
+            self.logger.warn('Starter.process_failure: continue starting application {}'.format(application_name))
 
 
 class Stopper(Commander):
     """ Class handling the stopping of processes and applications. """
 
     def stop_applications(self):
-        """ Plan and start the necessary jobs to stop all the applications
-        having a stop_sequence. """
-        self.logger.info('stop all applications')
+        """ Plan and start the necessary jobs to stop all the applications having a stop_sequence. """
+        self.logger.info('Stopper.stop_applications: stop all applications')
         # stopping initialization: push program list in todo list
         for application in self.supvisors.context.applications.values():
             # do not stop an application that is not running
             if application.running() and application.rules.stop_sequence >= 0:
                 self.store_application_stop_sequence(application)
         # start work
-        self.initial_jobs()
+        self.trigger_jobs()
 
-    def stop_application(self, application):
-        """ Plan and start the necessary jobs to stop the application in
-        parameter. """
-        self.logger.info('stop application {}'
-                         .format(application.application_name))
+    def stop_application(self, application: ApplicationStatus):
+        """ Plan and start the necessary jobs to stop the application in parameter. """
+        self.logger.info('Stopper.stop_application: stop application {}'.format(application.application_name))
         # push program list in todo list and start work
         if application.running():
             self.store_application_stop_sequence(application)
-            self.logger.debug('planned_sequence={}'
-                              .format(self.printable_planned_sequence()))
+            self.logger.debug('Stopper.stop_application: planned_sequence={}'.format(self.printable_planned_sequence()))
             # add application immediately to planned jobs
-            self.planned_jobs.update(self.planned_sequence.pop(
-                min(self.planned_sequence.keys())))
+            self.planned_jobs.update(self.planned_sequence.pop(min(self.planned_sequence.keys())))
             self.process_application_jobs(application.application_name)
         # return True when stopped
         return not self.in_progress()
@@ -520,8 +593,8 @@ def stop_application(self, application):
     def stop_process(self, process):
         """ Plan and start the necessary job to stop the process in parameter.
         Return False when stopping not completed. """
-        self.logger.info('stop process {}'.format(process.namespec()))
-        # push program list in todo list and start work
+        self.logger.info('Stopper.stop_process: stop process {}'.format(process.namespec()))
+        # push program list in current jobs list and start work
         job = self.current_jobs.setdefault(process.application_name, [])
         command = ProcessCommand(process)
         stopping = self.process_job(command, job)
@@ -533,105 +606,48 @@ def stop_process(self, process):
 
     def store_application_stop_sequence(self, application):
         """ Schedules the application processes to stop. """
-        application_sequence = {
-            seq: [ProcessCommand(process) for process in processes]
-            for seq, processes in application.stop_sequence.items()}
-        sequence = self.planned_sequence.setdefault(
-            application.rules.stop_sequence, {})
+        application_sequence = {seq: [ProcessCommand(process) for process in processes]
+                                for seq, processes in application.stop_sequence.items()}
+        sequence = self.planned_sequence.setdefault(application.rules.stop_sequence, {})
         sequence[application.application_name] = application_sequence
 
-    def process_job(self, command, jobs):
+    def process_job(self, command: ProcessCommand, jobs):
         """ Stops the process where it is running.
         Return True if process is stopping. """
         process = command.process
         if process.running():
             # use asynchronous xml rpc to stop program
             for address in process.addresses:
-                self.logger.info('stopping process {} on {}'
-                                 .format(process.namespec(), address))
-                self.supvisors.zmq.pusher.send_stop_process(
-                    address, process.namespec())
+                self.logger.info('Stopper.process_job: stopping process {} on {}'.format(process.namespec(), address))
+                self.supvisors.zmq.pusher.send_stop_process(address, process.namespec())
             # push to jobs and timestamp process
             command.request_time = time.time()
-            self.logger.debug('{} requested to stop at {}'
-                              .format(process.namespec(),
-                                      get_asctime(command.request_time)))
+            self.logger.debug('Stopper.process_job: {} requested to stop at {}'
+                              .format(process.namespec(), get_asctime(command.request_time)))
             jobs.append(command)
             return True
 
-    def check_stopping(self):
+    def check_stopping(self) -> bool:
         """ Check the progress of the application stopping. """
-        self.logger.debug('stopping progress: planned_sequence={} '
-                          'planned_jobs={} current_jobs={}'
-                          .format(self.printable_planned_sequence(),
-                                  self.printable_planned_jobs(),
-                                  self.printable_current_jobs()))
-        # once the stop_process has been called, a STOPPING event is expected
-        # in less than 5 seconds
-        now = time.time()
-        commands = [command
-                    for command_list in self.current_jobs.values()
-                    for command in command_list]
-        self.logger.trace('now={} checking processes={}'
-                          .format(now, [command for command in commands]))
-        for command in commands:
-            # depending on ini file, it may take a while before the process
-            # enters in STOPPED state
-            # so just test that is in not in a RUNNING-like state 5 seconds
-            # after request_time
-            if command.process.running() and command.timeout(now):
-                self.force_process_unknown(command.process.namespec(),
-                                           'Still running 5 seconds after stop request')
-        # return True when starting is completed
-        return not self.in_progress()
+        return self.check_progress('running', ProcessStates.UNKNOWN)
 
-    def on_event(self, process):
-        """ Triggers the following of the stop sequencing, depending on
-        the new process status. """
+    def on_event(self, process: ProcessStatus) -> None:
+        """ Triggers the following of the stop sequencing, depending on the new process status. """
         # check if process event has an impact on stopping in progress
         if process.application_name in self.current_jobs:
             jobs = self.current_jobs[process.application_name]
-            self.logger.debug('jobs={}'
-                              .format(self.printable_command_list(jobs)))
+            self.logger.debug('Stopper.on_event: jobs={}'.format(self.printable_command_list(jobs)))
             command = self.get_process_command(process, jobs)
             if command:
                 if process.running():
                     # several cases:
                     # 1) expected upon conciliation of a conflicting process
                     # 2) concurrent stopping / starting
-                    self.logger.warn('{} still running when stopping'
-                                     .format(process.namespec()))
+                    self.logger.warn('Stopper.on_event: {} still running when stopping'.format(process.namespec()))
                 elif process.stopped():
                     # goal reached, whatever the state
                     jobs.remove(command)
                 # else STOPPING, on the way
-                # check if there are remaining jobs in progress for this
-                # application
+                # check if there are remaining jobs in progress for this application
                 if not jobs:
-                    # remove application entry from current_jobs
-                    del self.current_jobs[process.application_name]
-                    # trigger next job for application
-                    if process.application_name in self.planned_jobs:
-                        self.process_application_jobs(process.application_name)
-                    else:
-                        self.logger.info('application {} stopped'
-                                         .format(process.application_name))
-                        # check if there are planned jobs
-                        if not self.planned_jobs:
-                            # trigger next sequence of applications
-                            self.initial_jobs()
-
-    def force_process_unknown(self, namespec, reason):
-        """ Updates the stop sequencing when a process could not be stopped. """
-        # publish the process state as UNKNOWN to all Supvisors instances
-        self.logger.warn('force {} state to UNKNOWN'.format(namespec))
-        try:
-            self.supvisors.info_source.force_process_unknown(namespec, reason)
-        except KeyError:
-            self.logger.error('impossible to force {} state to UNKNOWN. '
-                              'process unknown in this Supervisor'.format(namespec))
-            # the Supvisors user is not forced to use the same process
-            # configuration on all machines,
-            # although it is strongly recommended to avoid troubles.
-            # so, publish directly a fake process event to all instances
-            self.supvisors.listener.force_process_unknown(namespec)
+                    self.after_event(process.application_name)
diff --git a/supvisors/context.py b/supvisors/context.py
index 03f2d257..e848e2a7 100644
--- a/supvisors/context.py
+++ b/supvisors/context.py
@@ -17,6 +17,8 @@
 # limitations under the License.
 # ======================================================================
 
+from typing import Sequence
+
 from supvisors.address import *
 from supvisors.application import ApplicationStatus
 from supvisors.process import *
@@ -27,21 +29,26 @@
 class Context(object):
     """ The Context class holds the main data of Supvisors:
     - addresses: the dictionary of all AddressStatus (key is address),
-    - applications: the dictionary of all ApplicationStatus
-    (key is application name),
+    - forced_addresses: the dictionary of the minimal set of AddressStatus (key is address),
+    - applications: the dictionary of all ApplicationStatus (key is application name),
     - processes: the dictionary of all ProcessStatus (key is process namespec),
     - master_address: the address of the Supvisors master,
-    - master: a boolean telling if the local address is the master address. """
+    - master: a boolean telling if the local address is the master address.
+    - new: a boolean telling if this context has just been started.
+    """
 
     def __init__(self, supvisors):
         """ Initialization of the attributes. """
         # keep a reference of the Supvisors data
         self.supvisors = supvisors
         # shortcuts for readability
-        supvisors_shortcuts(self, ['address_mapper', 'info_source', 'logger'])
+        supvisors_shortcuts(self, ['address_mapper', 'info_source', 'logger', 'options'])
         # attributes
-        self.addresses = {address: AddressStatus(address, self.logger)
-                          for address in self.address_mapper.addresses}
+        self.addresses = {address_name: AddressStatus(address_name, self.logger)
+                          for address_name in self.address_mapper.addresses}
+        self.forced_addresses = {address_name: status
+                                 for address_name, status in self.addresses.items()
+                                 if address_name in self.options.force_synchro_if}
         self.applications = {}
         self.processes = {}
         self._master_address = ''
@@ -50,12 +57,12 @@ def __init__(self, supvisors):
     @property
     def master_address(self):
         """ Property for the 'master_address' attribute.
-        The setter sets the 'master' attribute to True if the master address
-        is the local address. """
+        The setter sets the 'master' attribute to True if the master address is the local address. """
         return self._master_address
 
     @master_address.setter
     def master_address(self, address):
+        self.logger.info('Context.master_address: {}'.format(address))
         self._master_address = address
         self.master = address == self.address_mapper.local_address
 
@@ -64,6 +71,12 @@ def unknown_addresses(self):
         """ Return the AddressStatus instances in UNKNOWN state. """
         return self.addresses_by_states([AddressStates.UNKNOWN])
 
+    def unknown_forced_addresses(self):
+        """ Return the AddressStatus instances in UNKNOWN state. """
+        return [status.address_name
+                for status in self.forced_addresses.values()
+                if status.state == AddressStates.UNKNOWN]
+
     def running_addresses(self):
         """ Return the AddressStatus instances in RUNNING state. """
         return self.addresses_by_states([AddressStates.RUNNING])
@@ -75,8 +88,7 @@ def isolating_addresses(self):
     def isolation_addresses(self):
         """ Return the AddressStatus instances in ISOLATING or ISOLATED
         state. """
-        return self.addresses_by_states([AddressStates.ISOLATING,
-                                         AddressStates.ISOLATED])
+        return self.addresses_by_states([AddressStates.ISOLATING, AddressStates.ISOLATED])
 
     def addresses_by_states(self, states):
         """ Return the AddressStatus instances sorted by state. """
@@ -85,24 +97,23 @@ def addresses_by_states(self, states):
                 if status.state in states]
 
     def invalid(self, status):
-        """ Declare SILENT or ISOLATING the AddressStatus in parameter,
-        according to the auto_fence option.
+        """ Declare SILENT or ISOLATING the AddressStatus in parameter, according to the auto_fence option.
         A local address is never ISOLATING, whatever the option is set or not.
         Give it a chance to restart. """
-        if self.supvisors.options.auto_fence and \
-                status.address_name != self.address_mapper.local_address:
+        if self.supvisors.options.auto_fence and status.address_name != self.address_mapper.local_address:
             status.state = AddressStates.ISOLATING
         else:
             status.state = AddressStates.SILENT
         # invalidate address in concerned processes
-        # if local Supvisors is master, failure handler will be notified
-        # for processes running on this address
+        # if local Supvisors is master, failure handler will be notified for processes running on this address
         for process in status.running_processes():
             process.invalidate_address(status.address_name, self.master)
 
-    def end_synchro(self):
-        """ Declare as SILENT the AddressStatus that are still not responsive
-        at the end of the INITIALIZATION state of Supvisors. """
+    def end_synchro(self) -> None:
+        """ Declare as SILENT the nodes that are still not responsive at the end of the INITIALIZATION state.
+
+        :return: None
+        """
         # consider problem if no tick received at the end of synchro time
         for address in self.addresses.values():
             if address.state == AddressStates.UNKNOWN:
@@ -160,8 +171,7 @@ def setdefault_process(self, info):
         return process
 
     def load_processes(self, address, all_info):
-        """ Load application dictionary from process info got from Supervisor
-        on address. """
+        """ Load application dictionary from process info got from Supervisor on address. """
         # get AddressStatus corresponding to address
         status = self.addresses[address]
         # store processes into their application entry
@@ -174,76 +184,73 @@ def load_processes(self, address, all_info):
             status.add_process(process)
 
     # methods on events
-    def on_authorization(self, address_name, authorized):
-        """ Method called upon reception of an authorization event telling
-        if the remote Supvisors instance
-        authorizes the local Supvisors instance to process its events. """
+    def on_authorization(self, address_name: str, authorized: bool) -> Optional[bool]:
+        """ Method called upon reception of an authorization event telling if the remote Supvisors instance
+        authorizes the local Supvisors instance to process its events.
+
+        :param address_name: the node name from which comes the event
+        :param authorized: the node authorization status
+        :return: True if authorized both ways
+        """
         if self.address_mapper.valid(address_name):
             status = self.addresses[address_name]
             # ISOLATED address is not updated anymore
             if not status.in_isolation():
                 if authorized:
-                    self.logger.info('local is authorized to deal with {}'.format(address_name))
+                    self.logger.info('Context.on_authorization: local is authorized to deal with {}'
+                                     .format(address_name))
                     status.state = AddressStates.RUNNING
-                else:
-                    self.logger.warn('local is not authorized to deal with {}'.format(address_name))
-                    self.invalid(status)
+                    return True
+                self.logger.warn('Context.on_authorization: local is not authorized to deal with {}'
+                                 .format(address_name))
+                self.invalid(status)
         else:
-            self.logger.warn('got authorization from unexpected location={}'.format(address_name))
+            self.logger.warn('Context.on_authorization: got authorization from unexpected location={}'
+                             .format(address_name))
 
     def on_tick_event(self, address_name, event):
-        """ Method called upon reception of a tick event from the remote
-        Supvisors instance, telling that it is active.
-        Supvisors checks that the handling of the event is valid in case of
-        auto fencing.
-        The method also updates the times of the corresponding AddressStatus
-        and the ProcessStatus depending on it.
+        """ Method called upon reception of a tick event from the remote Supvisors instance, telling that it is active.
+        Supvisors checks that the handling of the event is valid in case of auto fencing.
+        The method also updates the times of the corresponding AddressStatus and the ProcessStatus depending on it.
         Finally, the updated AddressStatus is published. """
         if self.address_mapper.valid(address_name):
             status = self.addresses[address_name]
             # ISOLATED address is not updated anymore
             if not status.in_isolation():
-                self.logger.debug('got tick {} from location={}'
-                                  .format(event, address_name))
-                # asynchronous port-knocking used to check if remote Supvisors
-                # instance considers local instance as isolated
+                self.logger.debug('Context.on_tick_event: got tick {} from location={}'.format(event, address_name))
+                # asynchronous port-knocking used to check if remote Supvisors instance considers
+                # the local instance as isolated
                 if status.state in [AddressStates.UNKNOWN, AddressStates.SILENT]:
                     status.state = AddressStates.CHECKING
                     self.supvisors.zmq.pusher.send_check_address(address_name)
                 # update internal times
                 status.update_times(event['when'], int(time()))
                 # publish AddressStatus event
-                self.supvisors.zmq.publisher.send_address_status(status)
+                self.supvisors.zmq.publisher.send_address_status(status.serial())
         else:
-            self.logger.warn('got tick from unexpected location={}'
-                             .format(address_name))
+            self.logger.warn('Context.on_tick_event: got tick from unexpected location={}'.format(address_name))
 
     def on_process_event(self, address_name, event):
-        """ Method called upon reception of a process event from the remote
-        Supvisors instance.
-        Supvisors checks that the handling of the event is valid in case of
-        auto fencing.
-        The method updates the ProcessStatus corresponding to the event, and
-        thus the wrapping ApplicationStatus.
+        """ Method called upon reception of a process event from the remote Supvisors instance.
+        Supvisors checks that the handling of the event is valid in case of auto fencing.
+        The method updates the ProcessStatus corresponding to the event, and thus the wrapping ApplicationStatus.
         Finally, the updated ProcessStatus and ApplicationStatus are published.
         """
         if self.address_mapper.valid(address_name):
             status = self.addresses[address_name]
             # ISOLATED address is not updated anymore
             if not status.in_isolation():
-                self.logger.debug('got event {} from location={}'
-                                  .format(event, address_name))
+                self.logger.debug('Context.on_process_event: got event {} from location={}'.format(event, address_name))
                 try:
                     # get internal data
                     application = self.applications[event['group']]
                     process = application.processes[event['name']]
                     # update command line
-                    self.info_source.update_extra_args(process.namespec(),
-                                                       event['extra_args'])
+                    self.info_source.update_extra_args(process.namespec(), event['extra_args'])
                 except KeyError:
                     # process not found. normal when no tick yet received
                     # from this address
-                    self.logger.debug('reject event {} from location={}'
+                    self.logger.debug('Context.on_process_event: reject event {} from location={}'
                                       .format(event, address_name))
                 else:
                     # refresh process info from process event
@@ -254,30 +261,28 @@ def on_process_event(self, address_name, event):
                     # publish process event, status and application status
                     publisher = self.supvisors.zmq.publisher
                     publisher.send_process_event(address_name, event)
-                    publisher.send_process_status(process)
-                    publisher.send_application_status(application)
+                    publisher.send_process_status(process.serial())
+                    publisher.send_application_status(application.serial())
                     return process
         else:
-            self.logger.error('got process event from unexpected location={}'
-                              .format(address_name))
+            self.logger.error('Context.on_process_event: got process event from unexpected location={}'
+                .format(address_name))
 
     def on_timer_event(self):
         """ Check that all Supvisors instances are still publishing.
-        Supvisors considers that there a Supvisors instance is not active
-        if no tick received in last 10s. """
+        Supvisors considers that there a Supvisors instance is not active if no tick received in last 10s. """
         for status in self.addresses.values():
-            if status.state == AddressStates.RUNNING and \
-                    (time() - status.local_time) > 10:
+            if status.state == AddressStates.RUNNING and (time() - status.local_time) > 10:
                 self.invalid(status)
                 # publish AddressStatus event
-                self.supvisors.zmq.publisher.send_address_status(status)
+                self.supvisors.zmq.publisher.send_address_status(status.serial())
 
-    def handle_isolation(self):
+    def handle_isolation(self) -> Sequence[str]:
         """ Move ISOLATING addresses to ISOLATED and publish related events. """
         addresses = self.isolating_addresses()
         for address in addresses:
             status = self.addresses[address]
             status.state = AddressStates.ISOLATED
             # publish AddressStatus event
-            self.supvisors.zmq.publisher.send_address_status(status)
+            self.supvisors.zmq.publisher.send_address_status(status.serial())
         return addresses
diff --git a/supvisors/initializer.py b/supvisors/initializer.py
index a69a3bb9..05a4c1da 100644
--- a/supvisors/initializer.py
+++ b/supvisors/initializer.py
@@ -36,13 +36,15 @@
 class Supvisors(object):
     """ The Supvisors class. """
 
-    # logger output
-    LOGGER_FORMAT = '%(asctime)s %(levelname)s %(message)s\n'
+    # logger output (use ';' as separator as easier to cut)
+    LOGGER_FORMAT = '%(asctime)s;%(levelname)s;%(message)s\n'
 
     def __init__(self, supervisord):
         """ Initialization of the attributes. """
         # store this instance in supervisord to ensure persistence
         supervisord.supvisors = self
+        # declare zmq context (will be created in listener)
+        self.zmq = None
         # get options from config file
         server_options = SupvisorsServerOptions()
         server_options.realize()
@@ -83,8 +85,11 @@ def create_logger(self, supervisord):
         Else Supvisors will log in the file defined in option.
         """
         if self.options.logfile is Automatic:
-            # use Supervisord logger
-            return supervisord.options.logger
+            # use Supervisord logger but patch format anyway
+            logger = supervisord.options.logger
+            for handler in logger.handlers:
+                handler.setFormat(Supvisors.LOGGER_FORMAT)
+            return logger
         # else create own Logger using Supervisor functions
         nodaemon = supervisord.options.nodaemon
         silent = supervisord.options.silent
diff --git a/supvisors/listener.py b/supvisors/listener.py
index f9dd833b..582eccc2 100644
--- a/supvisors/listener.py
+++ b/supvisors/listener.py
@@ -26,9 +26,7 @@
 
 from supvisors.mainloop import SupvisorsMainLoop
 from supvisors.ttypes import ProcessStates
-from supvisors.utils import (supvisors_shortcuts,
-                             InternalEventHeaders,
-                             RemoteCommEvents)
+from supvisors.utils import supvisors_shortcuts, InternalEventHeaders, RemoteCommEvents
 from supvisors.supvisorszmq import SupervisorZmq
 
 
@@ -56,8 +54,8 @@ def __init__(self, supvisors):
             from supvisors.statscollector import instant_statistics
             self.collector = instant_statistics
         except ImportError:
-            self.logger.warn('psutil not installed')
-            self.logger.warn('this Supvisors will not publish statistics')
+            self.logger.warn('SupervisorListener.__init__: psutil not installed')
+            self.logger.warn('SupervisorListener.__init__: this Supvisors will not publish statistics')
             self.collector = None
         # other attributes
         self.address = self.supvisors.address_mapper.local_address
@@ -73,7 +71,7 @@ def __init__(self, supvisors):
     def on_running(self, _):
         """ Called when Supervisor is RUNNING.
         This method start the Supvisors main loop. """
-        self.logger.info('local supervisord is RUNNING')
+        self.logger.info('SupervisorListener.on_running: local supervisord is RUNNING')
         # replace the default handler for web ui
         self.info_source.replace_default_handler()
         # update Supervisor internal data for extra_args
@@ -95,9 +93,9 @@ def on_stopping(self, _):
         # this will prevent any pending XML-RPC request to block the main loop
         self.info_source.close_httpservers()
         # stop the main loop
-        self.logger.info('request to stop main loop')
+        self.logger.info('SupervisorListener.on_stopping: request to stop main loop')
         self.main_loop.stop()
-        self.logger.info('end of main loop')
+        self.logger.info('SupervisorListener.on_stopping: end of main loop')
         # close zmq sockets
         self.supvisors.zmq.close()
         # unsubscribe from events
@@ -107,12 +105,11 @@ def on_stopping(self, _):
         if hasattr(self.logger, 'SUPVISORS'):
             self.logger.close()
 
-    def on_process(self, event):
+    def on_process(self, event: events.ProcessStateEvent) -> None:
         """ Called when a ProcessEvent is sent by the local Supervisor.
         The event is published to all Supvisors instances. """
         event_name = events.getEventNameByType(event.__class__)
-        self.logger.debug('got Process event from supervisord: {} {}'
-                          .format(event_name, event))
+        self.logger.debug('SupervisorListener.on_process: got Process event from supervisord: {}'.format(event_name))
         # create payload from event
         payload = {'name': event.process.config.name,
                    'group': event.process.group.config.name,
@@ -122,14 +119,14 @@ def on_process(self, event):
                    'pid': event.process.pid,
                    'expected': event.expected,
                    'spawnerr': event.process.spawnerr}
-        self.logger.debug('payload={}'.format(payload))
+        self.logger.debug('SupervisorListener.on_process: payload={}'.format(payload))
         self.publisher.send_process_event(payload)
 
-    def on_tick(self, event):
+    def on_tick(self, event: events.TickEvent) -> None:
         """ Called when a TickEvent is notified.
         The event is published to all Supvisors instances.
         Then statistics are published and periodic task is triggered. """
-        self.logger.debug('got Tick event from supervisord: {}'.format(event))
+        self.logger.debug('SupervisorListener.on_tick: got Tick event from supervisord: {}'.format(event.when))
         payload = {'when': event.when}
         self.publisher.send_tick_event(payload)
         # get and publish statistics at tick time (optional)
@@ -142,11 +139,12 @@ def on_tick(self, event):
         # pushes isolated addresses to main loop
         self.supvisors.zmq.pusher.send_isolate_addresses(addresses)
 
-    def on_remote_event(self, event):
+    def on_remote_event(self, event: events.RemoteCommunicationEvent) -> None:
         """ Called when a RemoteCommunicationEvent is notified.
         This is used to sequence the events received from the Supvisors thread
         with the other events handled by the local Supervisor. """
-        self.logger.debug('got Remote event from supervisord: {}'.format(event))
+        self.logger.debug('SupervisorListener.on_remote_event: got Remote event from supervisord: {} / {}'
+                          .format(event.type, event.data))
         if event.type == RemoteCommEvents.SUPVISORS_AUTH:
             self.authorization(event.data)
         elif event.type == RemoteCommEvents.SUPVISORS_EVENT:
@@ -154,44 +152,47 @@ def on_remote_event(self, event):
         elif event.type == RemoteCommEvents.SUPVISORS_INFO:
             self.unstack_info(event.data)
 
-    def unstack_event(self, message):
+    def unstack_event(self, message: str):
         """ Unstack and process one event from the event queue. """
         event_type, event_address, event_data = json.loads(message)
         if event_type == InternalEventHeaders.TICK:
-            self.logger.trace('got tick event from {}: {}'.format(event_address, event_data))
+            self.logger.trace('SupervisorListener.unstack_event: got tick event from {}: {}'
+                              .format(event_address, event_data))
             self.fsm.on_tick_event(event_address, event_data)
         elif event_type == InternalEventHeaders.PROCESS:
-            self.logger.trace('got process event from {}: {}'.format(event_address, event_data))
+            self.logger.trace('SupervisorListener.unstack_event: got process event from {}: {}'
+                              .format(event_address, event_data))
             self.fsm.on_process_event(event_address, event_data)
         elif event_type == InternalEventHeaders.STATISTICS:
             # this Supvisors could handle statistics
             # even if psutil is not installed
-            self.logger.trace('got statistics event from {}: {}'.format(event_address, event_data))
+            self.logger.trace('SupervisorListener.unstack_event: got statistics event from {}: {}'
+                              .format(event_address, event_data))
             self.statistician.push_statistics(event_address, event_data)
 
-    def unstack_info(self, message):
+    def unstack_info(self, message: str):
         """ Unstack the process info received. """
         # unstack the queue for process info
         address_name, info = json.loads(message)
-        self.logger.trace('got process info event from {}'.format(address_name))
+        self.logger.trace('SupervisorListener.unstack_info: got process info event from {}'.format(address_name))
         self.fsm.on_process_info(address_name, info)
 
     def authorization(self, data):
         """ Extract authorization and address from data and process event. """
-        self.logger.trace('got authorization event: {}'.format(data))
+        self.logger.trace('SupervisorListener.authorization: got authorization event: {}'.format(data))
         # split the line received
-        address_name, authorized = tuple(x.split(':')[1] for x in data.split())
-        self.fsm.on_authorization(address_name, boolean(authorized))
+        address_name, authorized, master_address = tuple(x.split(':')[1] for x in data.split())
+        self.fsm.on_authorization(address_name, boolean(authorized), master_address)
 
-    def force_process_fatal(self, namespec):
+    def force_process_fatal(self, namespec: str):
         """ Publishes a fake process event showing a FATAL state for the process. """
         self.force_process_state(namespec, ProcessStates.FATAL)
 
-    def force_process_unknown(self, namespec):
+    def force_process_unknown(self, namespec: str):
         """ Publishes a fake process event showing an UNKNOWN state for the process. """
         self.force_process_state(namespec, ProcessStates.UNKNOWN)
 
-    def force_process_state(self, namespec, state):
+    def force_process_state(self, namespec: str, state: int):
         """ Publishes a fake process event showing a state for the process. """
         application_name, process_name = split_namespec(namespec)
         # create payload from event
@@ -201,5 +202,5 @@ def force_process_state(self, namespec, state):
                    'now': int(time.time()),
                    'pid': 0,
                    'expected': False}
-        self.logger.debug('payload={}'.format(payload))
+        self.logger.debug('SupervisorListener.force_process_state: payload={}'.format(payload))
         self.publisher.send_process_event(payload)
diff --git a/supvisors/mainloop.py b/supvisors/mainloop.py
index 8dbce0e6..2f66a96b 100644
--- a/supvisors/mainloop.py
+++ b/supvisors/mainloop.py
@@ -21,13 +21,13 @@
 import zmq
 
 from threading import Event, Thread
+from typing import Any
 from sys import stderr
 
 from supvisors.rpcrequests import getRPCInterface
 from supvisors.supvisorszmq import SupvisorsZmq
 from supvisors.ttypes import AddressStates
-from supvisors.utils import (DeferredRequestHeaders,
-                             RemoteCommEvents)
+from supvisors.utils import DeferredRequestHeaders, RemoteCommEvents
 
 
 class SupvisorsMainLoop(Thread):
@@ -36,10 +36,12 @@ class SupvisorsMainLoop(Thread):
 
     Attributes:
         - supvisors: a reference to the Supvisors context,
-        - loop: the infinite loop flag.
+        - stop_event: the event used to stop the thread,
+        - env: the environment variables linked to Supervisor security access,
+        - proxy: the proxy to the internal RPC interface.
     """
 
-    def __init__(self, supvisors):
+    def __init__(self, supvisors: Any) -> None:
         """ Initialization of the attributes. """
         # thread attributes
         Thread.__init__(self)
@@ -89,25 +91,20 @@ def run(self):
 
     def check_events(self, subscriber, socks):
         """ Forward external Supervisor events to main thread. """
-        if subscriber.socket in socks and \
-                socks[subscriber.socket] == zmq.POLLIN:
+        if subscriber.socket in socks and socks[subscriber.socket] == zmq.POLLIN:
             try:
                 message = subscriber.receive()
             except:
                 print('[ERROR] failed to get data from subscriber', file=stderr)
             else:
-                # The events received are not processed directly in this thread
-                # because it would conflict with the processing in the
-                # Supervisor thread, as they use the same data.
-                # That's why a RemoteCommunicationEvent is used to push the
-                # event in the Supervisor thread.
-                self.send_remote_comm_event(RemoteCommEvents.SUPVISORS_EVENT,
-                                            json.dumps(message))
+                # The events received are not processed directly in this thread because it would conflict
+                # with the processing in the Supervisor thread, as they use the same data.
+                # That's why a RemoteCommunicationEvent is used to push the event in the Supervisor thread.
+                self.send_remote_comm_event(RemoteCommEvents.SUPVISORS_EVENT, json.dumps(message))
 
     def check_requests(self, zmq_sockets, socks):
         """ Defer internal requests. """
-        if zmq_sockets.puller.socket in socks and \
-                socks[zmq_sockets.puller.socket] == zmq.POLLIN:
+        if zmq_sockets.puller.socket in socks and socks[zmq_sockets.puller.socket] == zmq.POLLIN:
             try:
                 header, body = zmq_sockets.puller.receive()
             except:
@@ -142,10 +139,11 @@ def check_address(self, address_name):
         """ Check isolation and get all process info asynchronously. """
         try:
             remote_proxy = getRPCInterface(address_name, self.env)
+            # get remote perception of master node
+            master_address = remote_proxy.supvisors.get_master_address()
             # check authorization
             status = remote_proxy.supvisors.get_address_info(address_name)
-            authorized = status['statecode'] not in [AddressStates.ISOLATING,
-                                                     AddressStates.ISOLATED]
+            authorized = status['statecode'] not in [AddressStates.ISOLATING, AddressStates.ISOLATED]
             # get process info if authorized
             if authorized:
                 # get information about all processes handled by Supervisor
@@ -155,8 +153,8 @@ def check_address(self, address_name):
                                             json.dumps((address_name, all_info)))
             # inform local Supvisors that authorization is available
             self.send_remote_comm_event(RemoteCommEvents.SUPVISORS_AUTH,
-                                        'address_name:{} authorized:{}'
-                                        .format(address_name, authorized))
+                                        'address_name:{} authorized:{} master_address:{}'
+                                        .format(address_name, authorized, master_address))
         except:
             print('[ERROR] failed to check address {}'.format(address_name), file=stderr)
 
@@ -166,8 +164,8 @@ def start_process(self, address_name, namespec, extra_args):
             proxy = getRPCInterface(address_name, self.env)
             proxy.supvisors.start_args(namespec, extra_args, False)
         except:
-            print('[ERROR] failed to start process {} on {} with {}'.format(namespec, address_name, extra_args),
-                  file=stderr)
+            print('[ERROR] failed to start process {} on {} with extra_args="{}"'
+                  .format(namespec, address_name, extra_args), file=stderr)
 
     def stop_process(self, address_name, namespec):
         """ Stop process asynchronously. """
diff --git a/supvisors/options.py b/supvisors/options.py
index facb8bb3..3e3467e9 100644
--- a/supvisors/options.py
+++ b/supvisors/options.py
@@ -36,12 +36,13 @@ class SupvisorsOptions(object):
 
     Attributes are:
 
-        - address_list: list of host names or IP addresses where supvisors will be running,
+        - address_list: list of node names or IP addresses where supvisors will be running,
         - rules_file: absolute or relative path to the XML rules file,
         - internal_port: port number used to publish local events to remote Supvisors instances,
         - event_port: port number used to publish all Supvisors events,
         - auto_fence: when True, Supvisors won't try to reconnect to a Supvisors instance that has been inactive,
         - synchro_timeout: time in seconds that Supvisors waits for all expected Supvisors instances to publish,
+        - force_synchro_if: subset of address_list that will force the end of syncho when all RUNNING,
         - conciliation_strategy: strategy used to solve conflicts when Supvisors has detected multiple running instances of the same program,
         - starting_strategy: strategy used to start processes on addresses,
         - stats_periods: list of periods for which the statistics will be provided in the Supvisors web page,
@@ -53,11 +54,11 @@ class SupvisorsOptions(object):
         - procnumbers: a dictionary giving the number of the program in a homogeneous group.
     """
 
-    _Options = ['address_list', 'rules_file', 'internal_port', 'event_port',
-                'auto_fence', 'synchro_timeout', 'conciliation_strategy',
-                'starting_strategy', 'stats_periods', 'stats_histo',
-                'stats_irix_mode', 'logfile', 'logfile_maxbytes',
-                'logfile_backups', 'loglevel']
+    _Options = ['address_list', 'rules_file', 'internal_port', 'event_port', 'auto_fence',
+                'synchro_timeout', 'force_synchro_if',
+                'conciliation_strategy', 'starting_strategy',
+                'stats_periods', 'stats_histo', 'stats_irix_mode',
+                'logfile', 'logfile_maxbytes', 'logfile_backups', 'loglevel']
 
     def __init__(self):
         """ Initialization of the attributes. """
@@ -69,12 +70,13 @@ def __init__(self):
 
     def __str__(self):
         """ Contents as string. """
-        return ('address_list={} rules_file={} internal_port={} event_port={} '
-                'auto_fence={} synchro_timeout={} conciliation_strategy={} '
+        return ('address_list={} rules_file={} internal_port={} event_port={} auto_fence={} '
+                'synchro_timeout={} force_synchro_if={} conciliation_strategy={} '
                 'starting_strategy={} stats_periods={} stats_histo={} '
                 'stats_irix_mode={} logfile={} logfile_maxbytes={} '
-                'logfile_backups={} loglevel={}'.format(self.address_list, self.rules_file, self.internal_port,
-                                                        self.event_port, self.auto_fence, self.synchro_timeout,
+                'logfile_backups={} loglevel={}'.format(self.address_list, self.rules_file,
+                                                        self.internal_port, self.event_port, self.auto_fence,
+                                                        self.synchro_timeout, self.force_synchro_if,
                                                         self.conciliation_strategy, self.starting_strategy,
                                                         self.stats_periods, self.stats_histo, self.stats_irix_mode,
                                                         self.logfile, self.logfile_maxbytes, self.logfile_backups,
@@ -128,8 +130,8 @@ def server_configs_from_parser(self, parser):
         temp, parser.mysection = parser.mysection, SupvisorsServerOptions._Section
         # get values
         opt = self.supvisors_options
-        opt.address_list = list(OrderedDict.fromkeys(filter(
-            None, list_of_strings(parser.getdefault('address_list', gethostname())))))
+        opt.address_list = filter(None, list_of_strings(parser.getdefault('address_list', gethostname())))
+        opt.address_list = list(OrderedDict.fromkeys(opt.address_list))
         opt.rules_file = parser.getdefault('rules_file', None)
         if opt.rules_file:
             opt.rules_file = existing_dirpath(opt.rules_file)
@@ -137,6 +139,8 @@ def server_configs_from_parser(self, parser):
         opt.event_port = self.to_port_num(parser.getdefault('event_port', '65002'))
         opt.auto_fence = boolean(parser.getdefault('auto_fence', 'false'))
         opt.synchro_timeout = self.to_timeout(parser.getdefault('synchro_timeout', '15'))
+        opt.force_synchro_if = filter(None, list_of_strings(parser.getdefault('force_synchro_if', None)))
+        opt.force_synchro_if = [node for node in opt.force_synchro_if if node in opt.address_list]
         opt.conciliation_strategy = self.to_conciliation_strategy(parser.getdefault('conciliation_strategy', 'USER'))
         opt.starting_strategy = self.to_starting_strategy(parser.getdefault('starting_strategy', 'CONFIG'))
         # configure statistics
@@ -154,20 +158,28 @@ def server_configs_from_parser(self, parser):
 
     # conversion utils (completion of supervisor.datatypes)
     @staticmethod
-    def to_port_num(value):
-        """ Convert a string into a port number. """
+    def to_port_num(value: str) -> int:
+        """ Convert a string into a port number, in [1;65535].
+
+        :param value: the port number as a string
+        :return: the port number as an integer
+        """
         value = integer(value)
         if 0 < value <= 65535:
             return value
         raise ValueError('invalid value for port: %d. expected in [1;65535]' % value)
 
     @staticmethod
-    def to_timeout(value):
-        """ Convert a string into a timeout value. """
+    def to_timeout(value: str) -> int:
+        """ Convert a string into a timeout value, in [15;1200].
+
+        :param value: the timeout as a string
+        :return: the timeout as an integer
+        """
         value = integer(value)
-        if 0 < value <= 1000:
+        if 15 <= value <= 1200:
             return value
-        raise ValueError('invalid value for synchro_timeout: %d. expected in [1;1000] (seconds)' % value)
+        raise ValueError('invalid value for synchro_timeout: %d. expected in [15;1200] (seconds)' % value)
 
     @staticmethod
     def to_conciliation_strategy(value):
@@ -207,8 +219,12 @@ def to_periods(value):
         return sorted(filter(None, periods))
 
     @staticmethod
-    def to_histo(value):
-        """ Convert a string into a value of historic depth. """
+    def to_histo(value: str) -> int:
+        """ Convert a string into a value of historic depth, in [10;1500].
+
+        :param value: the historic size as a string
+        :return: the historic size as an integer
+        """
         histo = integer(value)
         if 10 <= histo <= 1500:
             return histo
diff --git a/supvisors/process.py b/supvisors/process.py
index 0dec8f45..100579f3 100644
--- a/supvisors/process.py
+++ b/supvisors/process.py
@@ -17,11 +17,13 @@
 # limitations under the License.
 # ======================================================================
 
+from typing import AbstractSet, Any, Optional
+
 from supervisor.options import make_namespec
 from supervisor.rpcinterface import SupervisorNamespaceRPCInterface
 from supervisor.states import RUNNING_STATES, STOPPED_STATES
 
-from supvisors.ttypes import ProcessStates, RunningFailureStrategies
+from supvisors.ttypes import Payload, ProcessStates, RunningFailureStrategies
 from supvisors.utils import *
 
 
@@ -29,8 +31,8 @@ class ProcessRules(object):
     """ Defines the rules for starting a process, iaw rules file.
 
     Attributes are:
-
-        - addresses: the addresses where the process can be started (all by default),
+        - addresses: the nodes where the process can be started (all by default),
+        - hash_addresses: when # rule is used, the process can be started on one of these nodes (to be resolved),
         - start_sequence: the order in the starting sequence of the application,
         - stop_sequence: the order in the stopping sequence of the application,
         - required: a status telling if the process is required within the application,
@@ -39,16 +41,18 @@ class ProcessRules(object):
         - running_failure_strategy: supersedes the application rule and defines the strategy to apply when the process crashes when the application is running.
     """
 
-    def __init__(self, supvisors):
-        """ Initialization of the attributes. """
-        # TODO: think about adding a period for tasks
-        # period should be greater than startsecs
-        # autorestart should be False
+    def __init__(self, supvisors: Any) -> None:
+        """ Initialization of the attributes.
+
+        :param supvisors: the global Supvisors structure.
+        """
+        # TODO: think about adding a period for tasks (period > startsecs / autorestart = False)
         # keep a reference to the Supvisors data
         self.supvisors = supvisors
         supvisors_shortcuts(self, ['info_source', 'logger'])
         # attributes
         self.addresses = ['*']
+        self.hash_addresses = None
         self.start_sequence = 0
         self.stop_sequence = 0
         self.required = False
@@ -56,44 +60,46 @@ def __init__(self, supvisors):
         self.expected_loading = 1
         self.running_failure_strategy = RunningFailureStrategies.CONTINUE
 
-    def check_dependencies(self, namespec):
+    def check_dependencies(self, namespec: str) -> None:
         """ Update rules after they have been read from the rules file.
-
         A required process that is not in the starting sequence is forced to optional.
-        If addresses are not defined, all addresses are applicable.
         Supervisor autorestart is not compatible with RunningFailureStrategy STOP / RESTART.
+
+        :param namespec: the namespec of the program considered.
         """
         # required MUST have start_sequence, so force to optional if
         # start_sequence is not set
         if self.required and self.start_sequence == 0:
-            self.logger.warn('{} - required forced to False because no start_sequence defined'
-                             .format(namespec))
+            self.logger.warn('ProcessRules.check_dependencies: {} - required forced to False because '
+                             'no start_sequence defined'.format(namespec))
             self.required = False
-        # if no addresses, consider all addresses
-        if not self.addresses:
-            self.addresses = ['*']
-            self.logger.warn('{} - no address defined so all Supvisors'
-                             ' addresses are applicable'.format(namespec))
         # disable autorestart when RunningFailureStrategies is not CONTINUE
         if self.running_failure_strategy != RunningFailureStrategies.CONTINUE:
             if self.info_source.autorestart(namespec):
                 self.info_source.disable_autorestart(namespec)
-                self.logger.warn('{} - autorestart disabled due to running failure strategy {}'
+                self.logger.warn('ProcessRules.check_dependencies: {} - autorestart disabled due to '
+                                 'running failure strategy {}'
                                  .format(namespec,
                                          RunningFailureStrategies.to_string(self.running_failure_strategy)))
 
-    def __str__(self):
-        """ Contents as string. """
-        return 'addresses={} start_sequence={} stop_sequence={} required={}' \
+    def __str__(self) -> str:
+        """ Get the process rules as string.
+
+        :return: the printable process rules
+        """
+        return 'addresses={} hash_addresses={} start_sequence={} stop_sequence={} required={}' \
                ' wait_exit={} expected_loading={} running_failure_strategy={}'. \
-            format(self.addresses,
+            format(self.addresses, self.hash_addresses,
                    self.start_sequence, self.stop_sequence, self.required,
                    self.wait_exit, self.expected_loading,
                    RunningFailureStrategies.to_string(self.running_failure_strategy))
 
-    # serialization
-    def serial(self):
-        """ Return a serializable form of the ProcessRules. """
+    def serial(self) -> Payload:
+        """ Get a serializable form of the process rules.
+        hash_addresses is not exported as used internally to resolve addresses.
+
+        :return: the process rules in a dictionary
+        """
         return {'addresses': self.addresses,
                 'start_sequence': self.start_sequence,
                 'stop_sequence': self.stop_sequence,
@@ -107,7 +113,6 @@ class ProcessStatus(object):
     """ Class defining the status of a process of Supvisors.
 
     Attributes are:
-
         - application_name: the application name, or group name from a Supervisor point of view,
         - process_name: the process name,
         - state: the synthetic state of the process, same enumeration as Supervisor,
@@ -119,12 +124,17 @@ class ProcessStatus(object):
         - rules: the rules related to this process.
     """
 
-    def __init__(self, application_name, process_name, supvisors):
-        """ Initialization of the attributes. """
+    def __init__(self, application_name: str, process_name: str, supvisors: Any) -> None:
+        """ Initialization of the attributes.
+
+        :param application_name: the name of the application the process belongs to
+        :param process_name: the name of the process
+        :param supvisors: the global Supvisors structure
+        """
         # keep a reference of the Supvisors data
         self.supvisors = supvisors
         supvisors_shortcuts(self, ['address_mapper', 'info_source', 'logger', 'options'])
-        # copy Supervisor method to self
+        # copy to self the Supervisor method use to describe the process status
         ProcessStatus.update_description = SupervisorNamespaceRPCInterface._interpretProcessInfo
         # attributes
         self.application_name = application_name
@@ -139,30 +149,49 @@ def __init__(self, application_name, process_name, supvisors):
         # rules part
         self.rules = ProcessRules(supvisors)
 
-    # property for state access
     @property
-    def state(self):
+    def state(self) -> int:
+        """ Getter of state attribute.
+
+        :return: the process state
+        """
         return self._state
 
     @state.setter
-    def state(self, new_state):
+    def state(self, new_state: int) -> None:
+        """ Setter of state attribute.
+
+        :param new_state: the new process state
+        :return: None
+        """
         if self._state != new_state:
             self._state = new_state
+            self.logger.info('ProcessStatus.state: Process {} is {}'.format(self.namespec(), self.state_string()))
 
-    # property for extra_args access
     @property
-    def extra_args(self):
+    def extra_args(self) -> str:
+        """ Getter of extra arguments attribute.
+
+        :return: the extra arguments applicable to the command line of the process
+        """
         return self._extra_args
 
     @extra_args.setter
-    def extra_args(self, new_args):
+    def extra_args(self, new_args: str) -> None:
+        """ Setter of extra arguments attribute.
+
+        :param new_args: the extra arguments applicable to the command line of the process
+        :return: None
+        """
         if self._extra_args != new_args:
             self._extra_args = new_args
             self.info_source.update_extra_args(self.namespec(), new_args)
 
-    # serialization
-    def serial(self):
-        """ Return a serializable form of the ProcessStatus. """
+    def serial(self) -> Payload:
+        """ Get a serializable form of the ProcessStatus.
+
+        :return: the process status in a dictionary
+        """
         return {'application_name': self.application_name,
                 'process_name': self.process_name,
                 'statecode': self.state,
@@ -173,67 +202,121 @@ def serial(self):
                 'extra_args': self.extra_args}
 
     # access
-    def namespec(self):
-        """ Returns a namespec from application and process names. """
+    def namespec(self) -> str:
+        """ Get the process namespec from application and process names.
+
+        :return: the process namespec
+        """
         return make_namespec(self.application_name, self.process_name)
 
-    def crashed(self):
-        """ Return True if process has crashed or has exited unexpectedly. """
-        return (self.state == ProcessStates.FATAL or
-                (self.state == ProcessStates.EXITED and not self.expected_exit))
+    def crashed(self) -> bool:
+        """ Return True if the process has crashed or has exited unexpectedly.
 
-    def stopped(self):
-        """ Return True if process is stopped, as designed in Supervisor. """
+        :return: the crash status of the process
+        """
+        return (self.state == ProcessStates.FATAL or (self.state == ProcessStates.EXITED and not self.expected_exit))
+
+    def stopped(self) -> bool:
+        """ Return True if the process is stopped, as designed in Supervisor.
+
+        :return: the stopped status of the process
+        """
         return self.state in STOPPED_STATES
 
-    def running(self):
-        """ Return True if process is running, as designed in Supervisor. """
+    def running(self) -> bool:
+        """ Return True if the process is running, as designed in Supervisor.
+
+        :return: the running status of the process
+        """
         return self.state in RUNNING_STATES
 
-    def running_on(self, address):
-        """ Return True if process is running on address. """
+    def running_on(self, address: str) -> bool:
+        """ Return True if the process is running on node.
+
+        :param address: the node name
+        :return: the running status of the process on the considered node
+        """
         return self.running() and address in self.addresses
 
-    def pid_running_on(self, address):
+    def pid_running_on(self, address: str) -> bool:
         """ Return True if process is RUNNING on address.
         Different from running_on as it considers only the RUNNING state and not STARTING or BACKOFF.
-        This is used by the statistics module that requires an existing PID. """
+        This is used by the statistics module that requires an existing PID.
+
+        :param address: the node name
+        :return: the true running status of the process on the considered node
+        """
         return self.state == ProcessStates.RUNNING and address in self.addresses
 
-    def conflicting(self):
-        """ Return True if the process is in a conflicting state (more than one instance running). """
+    def conflicting(self) -> bool:
+        """ Return True if the process is in a conflicting state (more than one instance running).
+
+        :return: the conflict status of the process
+        """
         return len(self.addresses) > 1
 
     # methods
-    def state_string(self):
-        """ Return the state as a string. """
+    def state_string(self) -> str:
+        """ Get the process state as a string.
+
+        :return: the process state as a string
+        """
         return ProcessStates.to_string(self.state)
 
-    def add_info(self, address, process_info):
+    def add_info(self, address: str, process_info: Payload) -> None:
         """ Insert a new process information in internal list.
-        process_info is a subset of the dict received from Supervisor.getProcessInfo.
-        it contains the attributes declared in supvisors.utils.__Payload_Keys.
+
+        :param address: the name of the node from which the information has been received
+        :param process_info: a subset of the dict received from Supervisor.getProcessInfo.
+        :return: None
         """
         # keep date of last information received
-        # use local time here as there is no guarantee that addresses will be time synchonized
+        # use local time here as there is no guarantee that addresses will be time synchronized
         self.last_event_time = int(time())
         # store information
         info = self.infos[address] = process_info
         info['local_time'] = self.last_event_time
         self.update_uptime(info)
-        self.logger.debug('adding {} at {}'.format(info, address))
+        self.logger.debug('ProcessStatus.add_info: adding {} at {}'.format(info, address))
         # reset extra_args
         info['extra_args'] = ''
         self.extra_args = ''
         # update process status
         self.update_status(address, info['state'], info['expected'])
-        # fix address rule
-        if self.rules.addresses == ['#']:
-            if self.address_mapper.addresses.index(address) == self.options.procnumbers[self.process_name]:
+        # fix address rule iaw '#' option
+        if self.rules.hash_addresses:
+            self.resolve_hash_address(address)
+
+    def resolve_hash_address(self, address: str) -> None:
+        """ When a '#' is set in program rules, an association has to be done between the procnumber of the process
+        and the index of the process_info address in the applicable address list.
+        In this case, rules.hash_addresses is expected to contain:
+            - either ['*'] when all nodes are applicable
+            - or any subset of these nodes.
+
+        :param address: the node from which the process information has been received
+        :return: None
+        """
+        procnumber = self.options.procnumbers[self.process_name]
+        self.logger.debug('ProcessStatus.resolve_hash_address: namespec={} address={} procnumber={}'
+                          .format(self.namespec(), address, procnumber))
+        if '*' in self.rules.hash_addresses:
+            # all nodes defined in the supvisors section of the supervisor configuration file are applicable
+            if self.address_mapper.addresses.index(address) == procnumber:
                 self.rules.addresses = [address]
+        else:
+            # the subset of applicable nodes is the second element of rules addresses
+            if address in self.rules.hash_addresses:
+                if self.rules.hash_addresses.index(address) == procnumber:
+                    self.rules.addresses = [address]
+
+    def update_info(self, address: str, payload: Payload) -> None:
+        """ Update the internal process information with event payload.
 
-    def update_info(self, address, payload):
-        """ Update the internal process information with event payload. """
+        :param address: the name of the node from which the information has been received
+        :param payload: the process information used to refresh internal data
+        :return: None
+        """
         # do not consider process event while not added through tick
         if address in self.infos:
             # keep date of last information received
@@ -244,7 +327,8 @@ def update_info(self, address, payload):
             # refresh internal information
             info = self.infos[address]
             info['local_time'] = self.last_event_time
-            self.logger.trace('inserting {} into {} at {}'.format(payload, info, address))
+            self.logger.trace('ProcessStatus.update_info: inserting {} into {} at {}'
+                              .format(payload, info, address))
             info.update(payload)
             # re-evaluate description using Supervisor function
             info['description'] = self.update_description(info)
@@ -257,14 +341,18 @@ def update_info(self, address, payload):
             self.update_uptime(info)
             # update / check running addresses
             self.update_status(address, new_state, info['expected'])
-            self.logger.debug('new process info: {}'.format(info))
+            self.logger.debug('ProcessStatus.update_info: new process info: {}'.format(info))
         else:
-            self.logger.warn('ProcessEvent rejected for {}. wait for tick from {}'
+            self.logger.warn('ProcessStatus.update_info: ProcessEvent rejected for {}. wait for tick from {}'
                              .format(self.process_name, address))
 
-    def update_times(self, address, remote_time):
-        """ Update the internal process information when
-        a new tick is received from the remote Supvisors instance. """
+    def update_times(self, address: str, remote_time: int) -> None:
+        """ Update the internal process information when a new tick is received from the remote Supvisors instance.
+
+        :param address: the name of the node from which the tick has been received
+        :param remote_time: the timestamp (seconds from Epoch) of the tick in the reference time of the node
+        :return: None
+        """
         if address in self.infos:
             info = self.infos[address]
             info['now'] = remote_time
@@ -273,15 +361,26 @@ def update_times(self, address, remote_time):
             info['description'] = self.update_description(info)
 
     @staticmethod
-    def update_uptime(info):
-        """ Update uptime entry of a process information. """
+    def update_uptime(info: Payload) -> None:
+        """ Update uptime entry of a process information.
+
+        :param info: the process information related to a given node
+        :return: None
+        """
         info['uptime'] = (info['now'] - info['start']) \
             if info['state'] in [ProcessStates.RUNNING, ProcessStates.STOPPING] \
             else 0
 
-    def invalidate_address(self, address, is_master):
-        """ Update status of a process that was running on a lost address. """
-        self.logger.debug('{} invalidateAddress {} / {}'.format(self.namespec(), self.addresses, address))
+    def invalidate_address(self, address: str, is_master: bool) -> None:
+        """ Update the status of a process that was running on a lost / non-responsive node.
+        If the present process was running on this node, an action has to be taken by the Supvisors master.
+
+        :param address: the node from which no more information is received
+        :param is_master: True if the local node is the Supvisors master
+        :return: None
+        """
+        self.logger.debug('ProcessStatus.invalidate_address: {} in validate on {} / {}'
+                          .format(self.namespec(), self.addresses, address))
         # reassign the difference between current set and parameter
         if address in self.addresses:
             self.addresses.remove(address)
@@ -293,15 +392,14 @@ def invalidate_address(self, address, is_master):
             if len(self.addresses) == 1:
                 # if process is running on only one address,
                 # the global state is the state of this process
-                self.state = next(self.infos[address]['state']
-                                  for address in self.addresses)
+                self.state = next(self.infos[address]['state'] for address in self.addresses)
             elif self.running():
                 # addresses is empty for a running process
                 # action expected to fix the inconsistency
-                self.logger.warn('no more address for running process {}'.format(self.namespec()))
+                self.logger.warn('ProcessStatus.invalidate_address: no more node for running process {}'
+                                 .format(self.namespec()))
                 self.state = ProcessStates.FATAL
-                # notify the failure to dedicated handler, only if local
-                # address is master
+                # notify the failure to dedicated handler, only if local address is master
                 if is_master:
                     self.supvisors.failure_handler.add_default_job(self)
             elif self.state == ProcessStates.STOPPING:
@@ -309,11 +407,17 @@ def invalidate_address(self, address, is_master):
                 # consider STOPPED now
                 self.state = ProcessStates.STOPPED
         else:
-            self.logger.debug('process {} still in conflict after address invalidation'
+            self.logger.debug('ProcessStatus.invalidate_address: process {} still in conflict after node invalidation'
                               .format(self.namespec()))
 
-    def update_status(self, address, new_state, expected):
-        """ Updates the state and list of running address iaw the new event. """
+    def update_status(self, address: str, new_state: int, expected: bool) -> None:
+        """ Updates the state and list of running address iaw the new event.
+
+        :param address: the node from which new information has been received
+        :param new_state: the new state of the process on the considered node
+        :param expected: the exit status of the process (only valid for an EXITED state)
+        :return: None
+        """
         # update addresses list
         if new_state in STOPPED_STATES:
             self.addresses.discard(address)
@@ -333,19 +437,20 @@ def update_status(self, address, new_state, expected):
                 self.state = new_state
                 self.expected_exit = expected
         # log the new status
-        log_trace = 'Process {} is {}'.format(self.namespec(), self.state_string())
+        log_trace = 'ProcessStatus.update_status: Process {} is {}'.format(self.namespec(), self.state_string())
         if self.addresses:
             log_trace += ' on {}'.format(list(self.addresses))
-        self.logger.info(log_trace)
+        self.logger.debug(log_trace)
+
+    def evaluate_conflict(self) -> Optional[bool]:
+        """ Get a synthetic state if several processes are in a RUNNING-like state.
 
-    def evaluate_conflict(self):
-        """ Gets a synthetic state if several processes are in a RUNNING-like state. """
+        :return: True if a conflict is detected, None otherwise
+        """
         if self.conflicting():
-            # several processes seems to be in a running state
-            # so that becomes tricky
-            states = {self.infos[address]['state']
-                      for address in self.addresses}
-            self.logger.debug('{} multiple states {} for addresses {}'
+            # several processes seems to be in a running state so that becomes tricky
+            states = {self.infos[address]['state'] for address in self.addresses}
+            self.logger.debug('ProcessStatus.evaluate_conflict: {} multiple states {} for nodes {}'
                               .format(self.process_name,
                                       [ProcessStates.to_string(x) for x in states],
                                       list(self.addresses)))
@@ -354,7 +459,11 @@ def evaluate_conflict(self):
             return True
 
     @staticmethod
-    def running_state(states):
-        """ Return the first matching state in RUNNING_STATES. """
-        return next((state for state in RUNNING_STATES if state in states),
-                    ProcessStates.UNKNOWN)
+    def running_state(states: AbstractSet[int]) -> int:
+        """ Return the first matching running state.
+         The sequence defined in the Supervisor RUNNING_STATES is suitable here.
+
+        :param states: a list of all process states of the present process over all nodes
+        :return: a running state if found in list, UNKNOWN otherwise
+        """
+        return next((state for state in RUNNING_STATES if state in states), ProcessStates.UNKNOWN)
diff --git a/supvisors/rpcinterface.py b/supvisors/rpcinterface.py
index 454d1709..e0406480 100644
--- a/supvisors/rpcinterface.py
+++ b/supvisors/rpcinterface.py
@@ -44,8 +44,7 @@ class RPCInterface(object):
     def __init__(self, supervisord):
         # create a new Supvisors instance
         self.supvisors = Supvisors(supervisord)
-        supvisors_shortcuts(self, ['context', 'fsm', 'info_source',
-                                   'logger', 'starter', 'stopper'])
+        supvisors_shortcuts(self, ['context', 'fsm', 'info_source', 'logger', 'starter', 'stopper'])
 
     # RPC Status methods
     def get_api_version(self):
@@ -70,11 +69,11 @@ def get_master_address(self):
         return self.context.master_address
 
     def get_strategies(self):
-        """ Get the strategies applied by **Supvisors**:
+        """ Get the default strategies applied by **Supvisors**:
 
-            * auto-fencing, when an address becomes inactive,
-            * in the ``DEPLOYMENT`` state, to start applications,
-            * in the ``CONCILIATION`` state, to conciliate conflicts.
+            * auto-fencing: node isolation if it becomes inactive,
+            * starting: used in the ``DEPLOYMENT`` state to start applications,
+            * conciliation: used in the ``CONCILIATION`` state to conciliate conflicts.
 
         *@return* ``dict``: a structure containing data about the strategies applied.
         """
@@ -103,7 +102,7 @@ def get_address_info(self, node):
         try:
             status = self.context.addresses[node]
         except KeyError:
-            raise RPCError(Faults.BAD_ADDRESS, 'address {} unknown in Supvisors'.format(node))
+            raise RPCError(Faults.BAD_ADDRESS, 'address {} unknown to Supvisors'.format(node))
         return status.serial()
 
     def get_all_applications_info(self):
@@ -221,8 +220,7 @@ def get_process_rules(self, namespec):
         application, process = self._get_application_process(namespec)
         if process:
             return [self._get_internal_process_rules(process)]
-        return [self._get_internal_process_rules(proc)
-                for proc in application.processes.values()]
+        return [self._get_internal_process_rules(proc) for proc in application.processes.values()]
 
     def get_conflicts(self):
         """ Get the conflicting processes.
@@ -312,8 +310,7 @@ def stop_application(self, application_name, wait=True):
             raise RPCError(Faults.NOT_RUNNING, application_name)
         # stop the application
         done = self.stopper.stop_application(application)
-        self.logger.debug('stop_application {} done={}'
-                          .format(application_name, done))
+        self.logger.debug('stop_application {} done={}'.format(application_name, done))
         # wait until application fully STOPPED
         if wait and not done:
             def onwait():
@@ -321,8 +318,7 @@ def onwait():
                 if self.stopper.in_progress():
                     return NOT_DONE_YET
                 if application.state != ApplicationStates.STOPPED:
-                    raise RPCError(Faults.ABNORMAL_TERMINATION,
-                                   application_name)
+                    raise RPCError(Faults.ABNORMAL_TERMINATION, application_name)
                 return True
 
             onwait.delay = 0.5
@@ -331,8 +327,7 @@ def onwait():
         return not done
 
     def restart_application(self, strategy, application_name, wait=True):
-        """ Restart the application named application_name iaw the strategy
-        and the rules file.
+        """ Restart the application named application_name iaw the strategy and the rules file.
 
         *@param* ``StartingStrategies strategy``: the strategy used to choose addresses.
 
@@ -360,8 +355,7 @@ def onwait():
                 if value is True:
                     # done. request start application
                     onwait.waitstop = False
-                    value = self.start_application(strategy, application_name,
-                                                   wait)
+                    value = self.start_application(strategy, application_name, wait)
                     if type(value) is bool:
                         return value
                     # deferred job to wait for application to be started
@@ -376,12 +370,10 @@ def onwait():
         return onwait  # deferred
 
     def start_args(self, namespec, extra_args='', wait=True):
-        """ Start a local process.
-        The behaviour is different from ``supervisor.startProcess`` as it sets
-        the process state to ``FATAL`` instead of throwing an exception to the
-        RPC client.
-        This RPC makes it also possible to pass extra arguments to the program
-        command line.
+        """ Start a process on the local node.
+        The behaviour is different from ``supervisor.startProcess`` as it sets the process state to ``FATAL``
+        instead of throwing an exception to the RPC client.
+        This RPC makes it also possible to pass extra arguments to the program command line.
 
         *@param* ``str namespec``: the process namespec.
 
@@ -397,30 +389,24 @@ def start_args(self, namespec, extra_args='', wait=True):
 
         *@return* ``bool``: always ``True`` unless error.
         """
-        # WARN: do NOT check OPERATION (it is used internally in DEPLOYMENT)
+        # WARN: do NOT check OPERATION (it is used internally in DEPLOYMENT state)
         application, process = self._get_application_process(namespec)
         # update command line in process config with extra_args
         try:
             self.info_source.update_extra_args(process.namespec(), extra_args)
         except KeyError:
             # process is unknown to the local Supervisor
-            # this is unexpected as Supvisors checks the configuration
-            # before it sends this request
-            self.logger.error('could not find {} in Supervisor processes'
-                              .format(namespec))
-            raise RPCError(Faults.BAD_NAME,
-                           'namespec {} unknown in this Supervisor instance'
-                           .format(namespec))
+            # this is unexpected as Supvisors checks the configuration before it sends this request
+            self.logger.error('could not find {} in Supervisor processes'.format(namespec))
+            raise RPCError(Faults.BAD_NAME, 'namespec {} unknown to this Supervisor instance'.format(namespec))
         # start process with Supervisor internal RPC
         try:
             rpc_interface = self.info_source.supervisor_rpc_interface
             cb = rpc_interface.startProcess(namespec, wait)
         except RPCError as why:
-            self.logger.error('start_process {} failed: {}'
-                              .format(namespec, why))
+            self.logger.error('start_process {} failed: {}'.format(namespec, why))
             if why.code in [Faults.NO_FILE, Faults.NOT_EXECUTABLE]:
-                self.logger.warn('force Supervisor internal state of {} to FATAL'
-                                 .format(namespec))
+                self.logger.warn('force Supervisor internal state of {} to FATAL'.format(namespec))
                 # at this stage, process is known to the local Supervisor
                 self.info_source.force_process_fatal(namespec, why.text)
             # else process is already started
@@ -527,8 +513,7 @@ def onwait():
         return True
 
     def restart_process(self, strategy, namespec, extra_args='', wait=True):
-        """ Restart a process named namespec iaw the strategy and some of the
-        rules defined in the rules file.
+        """ Restart a process named namespec iaw the strategy and some of the rules defined in the rules file.
         WARN: the 'wait_exit' rule is not considered here.
 
         *@param* ``StartingStrategies strategy``: the strategy used to choose addresses.
@@ -558,8 +543,7 @@ def onwait():
                 if value is True:
                     # done. request start application
                     onwait.waitstop = False
-                    value = self.start_process(strategy, namespec,
-                                               extra_args, wait)
+                    value = self.start_process(strategy, namespec, extra_args, wait)
                     if type(value) is bool:
                         return value
                     # deferred job to wait for application to be started
@@ -574,8 +558,7 @@ def onwait():
         return onwait  # deferred
 
     def conciliate(self, strategy):
-        """ Apply the conciliation strategy only if **Supvisors** is in
-        ``CONCILIATION`` state, with a USER strategy.
+        """ Apply the conciliation strategy only if **Supvisors** is in ``CONCILIATION`` state, with a USER strategy.
 
         *@param* ``ConciliationStrategies strategy``: the strategy used to conciliate.
 
@@ -592,14 +575,12 @@ def conciliate(self, strategy):
             raise RPCError(Faults.BAD_STRATEGY, '{}'.format(strategy))
         # trigger conciliation
         if strategy != ConciliationStrategies.USER:
-            conciliate_conflicts(self.supvisors, strategy,
-                                 self.context.conflicts())
+            conciliate_conflicts(self.supvisors, strategy, self.context.conflicts())
             return True
         return False
 
     def restart(self):
-        """ Stops all applications and restart **Supvisors** through all
-        Supervisor daemons.
+        """ Stops all applications and restart **Supvisors** through all Supervisor daemons.
 
         *@throws* ``RPCError``: with code ``Faults.BAD_SUPVISORS_STATE`` if **Supvisors** is still in ``INITIALIZATION`` state.
 
@@ -610,8 +591,7 @@ def restart(self):
         return True
 
     def shutdown(self):
-        """ Stops all applications and shut down **Supvisors** through all
-        Supervisor daemons.
+        """ Stops all applications and shut down **Supvisors** through all Supervisor daemons.
 
         *@throws* ``RPCError``: with code ``Faults.BAD_SUPVISORS_STATE`` if **Supvisors** is still in ``INITIALIZATION`` state.
 
@@ -623,42 +603,34 @@ def shutdown(self):
 
     # utilities
     def _check_from_deployment(self):
-        """ Raises a BAD_SUPVISORS_STATE exception if Supvisors' state is in
-        INITIALIZATION. """
+        """ Raises a BAD_SUPVISORS_STATE exception if Supvisors' state is in INITIALIZATION. """
         self._check_state([SupvisorsStates.DEPLOYMENT,
                            SupvisorsStates.OPERATION, SupvisorsStates.CONCILIATION,
                            SupvisorsStates.RESTARTING, SupvisorsStates.SHUTTING_DOWN])
 
     def _check_operating_conciliation(self):
-        """ Raises a BAD_SUPVISORS_STATE exception if Supvisors' state is NOT
-        in OPERATION or CONCILIATION. """
-        self._check_state([SupvisorsStates.OPERATION,
-                           SupvisorsStates.CONCILIATION])
+        """ Raises a BAD_SUPVISORS_STATE exception if Supvisors' state is NOT in OPERATION or CONCILIATION. """
+        self._check_state([SupvisorsStates.OPERATION, SupvisorsStates.CONCILIATION])
 
     def _check_operating(self):
-        """ Raises a BAD_SUPVISORS_STATE exception if Supvisors' state is NOT
-        in OPERATION. """
+        """ Raises a BAD_SUPVISORS_STATE exception if Supvisors' state is NOT in OPERATION. """
         self._check_state([SupvisorsStates.OPERATION])
 
     def _check_conciliation(self):
-        """ Raises a BAD_SUPVISORS_STATE exception if Supvisors' state is NOT
-        in OPERATION. """
+        """ Raises a BAD_SUPVISORS_STATE exception if Supvisors' state is NOT in OPERATION. """
         self._check_state([SupvisorsStates.CONCILIATION])
 
     def _check_state(self, states):
-        """ Raises a BAD_SUPVISORS_STATE exception if Supvisors' state is NOT
-        in one of the states. """
+        """ Raises a BAD_SUPVISORS_STATE exception if Supvisors' state is NOT in one of the states. """
         if self.fsm.state not in states:
             raise RPCError(Faults.BAD_SUPVISORS_STATE,
-                           'Supvisors (state={}) not in state {} to perform request'.
-                           format(SupvisorsStates.to_string(self.supvisors.fsm.state),
-                                  [SupvisorsStates.to_string(state) for state in states]))
+                           'Supvisors (state={}) not in state {} to perform request'
+                           .format(SupvisorsStates.to_string(self.supvisors.fsm.state),
+                                   [SupvisorsStates.to_string(state) for state in states]))
 
     def _get_application_process(self, namespec):
-        """ Return the ApplicationStatus and ProcessStatus corresponding to the
-        namespec.
-        A BAD_NAME exception is raised if the application or the process is not
-        found. """
+        """ Return the ApplicationStatus and ProcessStatus corresponding to the namespec.
+        A BAD_NAME exception is raised if the application or the process is not found. """
         application_name, process_name = split_namespec(namespec)
         return (self._get_application(application_name),
                 self._get_process(namespec) if process_name else None)
@@ -669,8 +641,7 @@ def _get_application(self, application_name):
         try:
             application = self.context.applications[application_name]
         except KeyError:
-            raise RPCError(Faults.BAD_NAME,
-                           'application {} unknown in Supvisors'.format(application_name))
+            raise RPCError(Faults.BAD_NAME, 'application {} unknown to Supvisors'.format(application_name))
         return application
 
     def _get_process(self, namespec):
@@ -679,15 +650,14 @@ def _get_process(self, namespec):
         try:
             process = self.context.processes[namespec]
         except KeyError:
-            raise RPCError(Faults.BAD_NAME,
-                           'process {} unknown in Supvisors'.format(namespec))
+            raise RPCError(Faults.BAD_NAME, 'process {} unknown to Supvisors'.format(namespec))
         return process
 
-    def _get_internal_process_rules(self, process):
+    @staticmethod
+    def _get_internal_process_rules(process):
         """ Return a dictionary with the rules of the process. """
         result = process.rules.serial()
-        result.update({'application_name': process.application_name,
-                       'process_name': process.process_name})
+        result.update({'application_name': process.application_name, 'process_name': process.process_name})
         return result
 
     def _get_local_info(self, info):
diff --git a/supvisors/sparser.py b/supvisors/sparser.py
index b78b6b46..d1e309f9 100644
--- a/supvisors/sparser.py
+++ b/supvisors/sparser.py
@@ -18,13 +18,16 @@
 # ======================================================================
 
 from collections import OrderedDict
+from distutils.util import strtobool
 from io import StringIO
 from sys import stderr
+from typing import Any, Optional, Union
 
-from supervisor.datatypes import boolean, list_of_strings
+from supervisor.datatypes import list_of_strings
 
-from supvisors.ttypes import (StartingFailureStrategies,
-                              RunningFailureStrategies)
+from supvisors.application import ApplicationStatus, ApplicationRules
+from supvisors.process import ProcessStatus, ProcessRules
+from supvisors.ttypes import StartingFailureStrategies, RunningFailureStrategies
 from supvisors.utils import supvisors_shortcuts
 
 # XSD contents for XML validation
@@ -53,18 +56,16 @@
         </xs:restriction>
     </xs:simpleType>
     <xs:complexType name="ProgramModel">
-        <xs:choice>
-            <xs:element type="xs:string" name="reference"/>
-            <xs:sequence>
-                <xs:element type="xs:string" name="addresses" minOccurs="0" maxOccurs="1"/>
-                <xs:element type="xs:byte" name="start_sequence" minOccurs="0" maxOccurs="1"/>
-                <xs:element type="xs:byte" name="stop_sequence" minOccurs="0" maxOccurs="1"/>
-                <xs:element type="xs:boolean" name="required" minOccurs="0" maxOccurs="1"/>
-                <xs:element type="xs:boolean" name="wait_exit" minOccurs="0" maxOccurs="1"/>
-                <xs:element type="Loading" name="expected_loading" minOccurs="0" maxOccurs="1"/>
-                <xs:element type="RunningFailureStrategy" name="running_failure_strategy" minOccurs="0" maxOccurs="1"/>
-            </xs:sequence>
-        </xs:choice>
+        <xs:sequence>
+            <xs:element type="xs:string" name="reference" minOccurs="0" maxOccurs="1"/>
+            <xs:element type="xs:string" name="addresses" minOccurs="0" maxOccurs="1"/>
+            <xs:element type="xs:byte" name="start_sequence" minOccurs="0" maxOccurs="1"/>
+            <xs:element type="xs:byte" name="stop_sequence" minOccurs="0" maxOccurs="1"/>
+            <xs:element type="xs:boolean" name="required" minOccurs="0" maxOccurs="1"/>
+            <xs:element type="xs:boolean" name="wait_exit" minOccurs="0" maxOccurs="1"/>
+            <xs:element type="Loading" name="expected_loading" minOccurs="0" maxOccurs="1"/>
+            <xs:element type="RunningFailureStrategy" name="running_failure_strategy" minOccurs="0" maxOccurs="1"/>
+        </xs:sequence>
         <xs:attribute type="xs:string" name="name" use="required"/>
     </xs:complexType>
     <xs:complexType name="ApplicationModel">
@@ -95,8 +96,16 @@
 
 
 class Parser(object):
+    """ The Parser class used to get application and program rules from an XML file. """
+
+    # for recursive references to program models, depth is limited to 3
+    LOOP_CHECK = 3
 
-    def __init__(self, supvisors):
+    def __init__(self, supvisors: Any) -> None:
+        """ The constructor parses the XML file and stores references to models and patterns found.
+
+        :param supvisors: the global Supvisors structure.
+        """
         self.supvisors = supvisors
         supvisors_shortcuts(self, ['logger'])
         self.tree = self.parse(supvisors.options.rules_file)
@@ -104,149 +113,247 @@ def __init__(self, supvisors):
         # get models
         elements = self.root.findall("./model[@name]")
         self.models = {element.get('name'): element for element in elements}
-        self.logger.debug(self.models)
+        self.logger.debug('Parser.__init__: found models {}'.format(self.models.keys()))
         # get patterns
         elements = self.root.findall(".//pattern[@name]")
         self.patterns = {element.get('name'): element for element in elements}
-        self.logger.debug(self.patterns)
+        self.logger.debug('Parser.__init__: found patterns {}'.format(self.patterns.keys()))
+
+    def load_application_rules(self, application: ApplicationStatus) -> None:
+        """ Find an entry corresponding to the application in the rules file, then load the parameters found.
 
-    def load_application_rules(self, application):
-        # find application element
-        self.logger.trace('searching application element for {}'.format(application.application_name))
-        application_elt = self.root.find("./application[@name='{}']".format(application.application_name))
+        :param application: the application for which to find rules in the XML rules file
+        :return: None
+        """
+        # find application element using an xpath
+        self.logger.trace('Parser.load_application_rules: searching application element for {}'
+                          .format(application.application_name))
+        application_elt = self.root.find('./application[@name="{}"]'.format(application.application_name))
         if application_elt is not None:
-            # get start_sequence rule
-            value = application_elt.findtext('start_sequence')
-            application.rules.start_sequence = int(value) if value and int(value) > 0 else 0
-            # get stop_sequence rule
-            value = application_elt.findtext('stop_sequence')
-            application.rules.stop_sequence = int(value) if value and int(value) > 0 else 0
-            # get starting_failure_strategy rule
-            value = application_elt.findtext('starting_failure_strategy')
-            if value:
-                strategy = StartingFailureStrategies.from_string(value)
-                if strategy:
-                    application.rules.starting_failure_strategy = strategy
-            # get running_failure_strategy rule
-            value = application_elt.findtext('running_failure_strategy')
-            if value:
-                strategy = RunningFailureStrategies.from_string(value)
-                if strategy:
-                    application.rules.running_failure_strategy = strategy
-            # final print
-            self.logger.debug('application {} - rules {}'.format(application.application_name, application.rules))
-
-    def load_process_rules(self, process):
-        self.logger.trace('searching program element for {}'.format(process.namespec()))
+            rules = application.rules
+            self.load_sequence(application_elt, 'start_sequence', rules)
+            self.load_sequence(application_elt, 'stop_sequence', rules)
+            self.load_enum(application_elt, 'starting_failure_strategy', StartingFailureStrategies, rules)
+            self.load_enum(application_elt, 'running_failure_strategy', RunningFailureStrategies, rules)
+            self.logger.debug('Parser.load_application_rules: application {} - rules {}'
+                              .format(application.application_name, rules))
+
+    def load_process_rules(self, process: ProcessStatus) -> None:
+        """ Find an entry corresponding to the process in the rules, then load the parameters found.
+        A final check is performed to detect inconsistencies.
+
+        :param process: the process for which to find rules in the XML rules file
+        :return: None
+        """
+        self.logger.trace('Parser.load_process_rules: searching program element for {}'
+                          .format(process.namespec()))
         program_elt = self.get_program_element(process)
-        rules = process.rules
         if program_elt is not None:
-            # get addresses rule
-            self.get_program_addresses(program_elt, rules)
-            # get start_sequence rule
-            value = program_elt.findtext('start_sequence')
-            try:
-                rules.start_sequence = int(value)
-                if rules.start_sequence < 0:
-                    raise ValueError
-            except (TypeError, ValueError):
-                rules.start_sequence = 0
-            # get stop_sequence rule
-            value = program_elt.findtext('stop_sequence')
-            try:
-                rules.stop_sequence = int(value)
-                if rules.stop_sequence < 0:
-                    raise ValueError
-            except (TypeError, ValueError):
-                rules.stop_sequence = 0
-            # get required rule
-            value = program_elt.findtext('required')
-            try:
-                rules.required = boolean(value)
-            except (TypeError, ValueError):
-                rules.required = False
-            # get wait_exit rule
-            value = program_elt.findtext('wait_exit')
-            try:
-                rules.wait_exit = boolean(value)
-            except (TypeError, ValueError):
-                rules.wait_exit = False
-            # get expected_loading rule
-            value = program_elt.findtext('expected_loading')
-            try:
-                rules.expected_loading = int(value)
-                if not 0 <= rules.expected_loading <= 100:
-                    raise ValueError
-            except (TypeError, ValueError):
-                rules.expected_loading = 1
-            # get running_failure_strategy rule
-            value = program_elt.findtext('running_failure_strategy')
-            if value:
-                try:
-                    strategy = RunningFailureStrategies.from_string(value)
-                except KeyError:
-                    self.logger.error('strategy {} unknown'.format(value))
-                else:
-                    rules.running_failure_strategy = strategy
+            # load element parameters into rules
+            self.load_model_rules(program_elt, process.rules, Parser.LOOP_CHECK)
             # check that rules are compliant with dependencies
-            rules.check_dependencies(process.namespec())
-            self.logger.debug('process {} - rules {}'.format(process.namespec(), rules))
+            process.rules.check_dependencies(process.namespec())
+            self.logger.debug('Parser.load_process_rules: process {} - rules {}'
+                              .format(process.namespec(), process.rules))
 
-    def get_program_addresses(self, program_elt, rules):
-        value = program_elt.findtext('addresses')
-        if value:
-            # sort and trim
-            addresses = list(OrderedDict.fromkeys(filter(None, list_of_strings(value))))
-            if '*' in addresses:
-                rules.addresses = ['*']
-            elif '#' in addresses:
-                rules.addresses = ['#']
-            else:
-                rules.addresses = self.supvisors.address_mapper.filter(addresses)
+    def load_model_rules(self, program_elt: Any, rules: ProcessRules, loop_check: int) -> None:
+        """ Load the parameters found whatever it is given by a program or a model section.
+        If the section includes a reference to a model, it is loaded first and then eventually superseded
+        by other attributes.
+
+        :param program_elt: the XML element containing rules definition
+        :param rules: the process structure used to store the rules found
+        :param loop_check: the counter used to check the maximum number of recursive model references
+        :return: None
+        """
+        # check if recursive depth has been reached
+        if loop_check == 0:
+            self.logger.warn('Parser.load_model_rules: Maximum number of model referencing reached: {} levels',
+                             Parser.LOOP_CHECK)
+            return
+        # find check if a model is referenced in the program rules
+        model_elt = self.get_model_element(program_elt)
+        if model_elt is not None:
+            self.logger.trace('Parser.load_model_rules: found model {} from program {}'
+                              .format(model_elt.get('name'), program_elt.get('name')))
+            # a model can reference another model
+            # WARN: recursive call, counter decreased
+            self.load_model_rules(model_elt, rules, loop_check - 1)
+        # other attributes found may be used to complete or supersede the possible model
+        self.load_program_addresses(program_elt, rules)
+        self.load_sequence(program_elt, 'start_sequence', rules)
+        self.load_sequence(program_elt, 'stop_sequence', rules)
+        self.load_boolean(program_elt, 'required', rules)
+        self.load_boolean(program_elt, 'wait_exit', rules)
+        self.load_loading(program_elt, rules)
+        self.load_enum(program_elt, 'running_failure_strategy', RunningFailureStrategies, rules)
+
+    def get_program_element(self, process: ProcessStatus) -> Optional[Any]:
+        """ Try to find the definition of a program in rules files.
+        First try to to find the definition based on the exact program name.
+        If not found, second try to find a corresponding pattern.
 
-    def get_program_element(self, process):
+        :param process: the process for which to find rules in the XML rules file
+        :return: the XML element containing rules definition for a program
+        """
         # try to find program name in file
-        program_elt = self.root.find(
-            "./application[@name='{}']/program[@name='{}']".format(process.application_name, process.process_name))
-        self.logger.trace('{} - direct search program element {}'.format(process.namespec(), program_elt))
+        program_elt = self.root.find('./application[@name="{}"]/program[@name="{}"]'
+                                     .format(process.application_name, process.process_name))
+        self.logger.trace('Parser.get_program_element: direct search for program {} found={}'
+                          .format(process.namespec(), program_elt is not None))
         if program_elt is None:
-            # try to find a corresponding pattern
+            # if not found as it is, try to find a corresponding pattern
             patterns = [name for name, element in self.patterns.items() if name in process.namespec()]
-            self.supvisors.logger.trace('{} - found patterns {}'.format(process.namespec(), patterns))
+            self.supvisors.logger.trace('Parser.get_program_element: found patterns {} for program {}'
+                                        .format(patterns, process.namespec()))
             if patterns:
                 pattern = max(patterns, key=len)
                 program_elt = self.patterns[pattern]
-            self.logger.trace('{} - pattern search program element {}'.format(process.namespec(), program_elt))
-        if program_elt is not None:
-            # find if model referenced in element
-            model = program_elt.findtext('reference')
-            if model in self.models.keys():
-                program_elt = self.models[model]
-            self.logger.trace('{} - model search ({}) program element {}'
-                              .format(process.namespec(), model, program_elt))
+                self.logger.trace('Parser.get_program_element: pattern {} chosen for program {}'
+                                  .format(pattern, process.namespec()))
         return program_elt
 
-    def parse(self, filename):
-        """ Parse the file depending on the modules installed. """
+    def get_model_element(self, elt: Any) -> Optional[Any]:
+        """ Find if element references a model
+
+        :param elt: the XML element containing rules definition for a program
+        :return: the XML element containing rules definition for a model
+        """
+        model = elt.findtext('reference')
+        return self.models.get(model, None)
+
+    def load_program_addresses(self, elt: Any, rules: ProcessRules) -> None:
+        """ Get the addresses where the program is authorized to run.
+
+        :param elt: the XML element containing rules definition for a program
+        :param rules: the process structure used to store the rules found
+        :return: None
+        """
+        value = elt.findtext('addresses')
+        if value:
+            # sort and trim list of values
+            addresses = list(OrderedDict.fromkeys(filter(None, list_of_strings(value))))
+            if '#' in addresses:
+                # process cannot be started anywhere until hash address is resolved
+                rules.addresses = []
+                # if '#' is alone or associated to '*', the logic is applicable to all addresses
+                if len(addresses) == 1 or '*' in addresses:
+                    rules.hash_addresses = ['*']
+                else:
+                    # '#' is applicable to a subset of addresses
+                    addresses.remove('#')
+                    rules.hash_addresses = addresses
+            elif '*' in addresses:
+                rules.addresses = ['*']
+            else:
+                rules.addresses = self.supvisors.address_mapper.filter(addresses)
+
+    def load_sequence(self, elt: Any, attr_string: str, rules: Union[ApplicationRules, ProcessRules]) -> None:
+        """ Return the sequence value found from the XML element.
+        The value must be greater than or equal to 0.
+
+        :param elt: the XML element containing rules definition for an application or a program
+        :param attr_string: the XML tag searched and the name of the rule attribute
+        :param rules: the structure used to store the rules found
+        :return: None
+        """
+        str_value = elt.findtext(attr_string)
+        if str_value:
+            try:
+                value = int(str_value)
+                if value >= 0:
+                    setattr(rules, attr_string, value)
+                else:
+                    self.logger.warn('Parser.load_sequence: invalid value for {} {}: {} (expected integer >= 0)'
+                                     .format(elt.get('name'), attr_string, value))
+            except (TypeError, ValueError):
+                self.logger.warn('Parser.load_sequence: not an integer for {} {}: {}'
+                                 .format(elt.get('name'), attr_string, str_value))
+
+    def load_loading(self, elt: Any, rules: ProcessRules) -> None:
+        """ Return the loading value found from the XML element.
+        The value must be in [0 ; 100].
+
+        :param elt: the XML element containing rules definition for an application or a program
+        :param rules: the structure used to store the rules found
+        :return: None
+        """
+        str_value = elt.findtext('expected_loading')
+        if str_value:
+            try:
+                value = int(str_value)
+                if 0 <= value <= 100:
+                    setattr(rules, 'expected_loading', value)
+                else:
+                    self.logger.warn('Parser.load_sequence: invalid value for {} expected_loading: {}'
+                                     '(expected integer in [0;100])'.format(elt.get('name'), value))
+            except (TypeError, ValueError):
+                self.logger.warn('Parser.load_sequence: not an integer for {} expected_loading: {}'
+                                 .format(elt.get('name'), str_value))
+
+    def load_boolean(self, elt: Any, attr_string: str, rules: ProcessRules) -> None:
+        """ Return the boolean value found from XML element.
+
+        :param elt: the XML element containing rules definition for an application or a program
+        :param attr_string: the XML tag searched and the name of the rule attribute
+        :param rules: the structure used to store the rules found
+        :return: None
+        """
+        str_value = elt.findtext(attr_string)
+        if str_value:
+            try:
+                value = bool(strtobool(str_value))
+                setattr(rules, attr_string, value)
+            except ValueError:
+                self.logger.warn('Parser.load_boolean: not a boolean-like for {} {}: {}'
+                                 .format(elt.get('name'), attr_string, str_value))
+
+    def load_enum(self, elt: Any, attr_string: str, klass, rules: Union[ApplicationRules, ProcessRules]) -> None:
+        """ Return the running_failure_strategy value found from XML element.
+        The value MUST correspond to an enumeration value of RunningFailureStrategies.
+
+        :param elt: the XML element containing rules definition for an application
+        :param attr_string: the XML tag searched and the name of the rule attribute
+        :param klass: the enum class to match
+        :param rules: the application structure used to store the rules found
+        :return: None
+        """
+        value = elt.findtext(attr_string)
+        if value:
+            try:
+                setattr(rules, attr_string, klass.from_string(value))
+            except KeyError:
+                self.logger.warn('Pattern.load_enum: invalid value for {} {}: {} (expected in {})'
+                                 .format(elt.get('name'), attr_string, value, klass.strings()))
+
+    def parse(self, filename: str) -> Optional[Any]:
+        """ Parse the file depending on the modules installed.
+        lxml is considered first as it allows to check the XML through a schema.
+        Elementtree is used as an alternative if lxml module cannot be imported.
+
+        :param filename: the path of the XML rules file
+        :return: the XML parser
+        """
         # find parser
+        self.logger.info('Parser.parse: trying to use lxml.etree parser')
         try:
             from lxml.etree import parse, XMLSchema
-            self.logger.info('using lxml.etree parser')
             # parse XML and validate it
             tree = parse(filename)
             # get XSD
             schema_doc = parse(XSDContents)
             schema = XMLSchema(schema_doc)
             if schema.validate(tree):
-                self.logger.info('XML validated')
+                self.logger.info('Parser.parse: XML validated')
                 return tree
             print(schema.error_log, file=stderr)
-            raise ValueError('XML NOT validated: {}'.format(filename))
+            raise ValueError('Parser.parse: XML NOT validated: {}'.format(filename))
         except ImportError:
+            self.logger.info('Parser.parse: failed to import lxml')
+            self.logger.info('Parser.parse: trying to use xml.etree.ElementTree parser')
             try:
                 from xml.etree.ElementTree import parse
-                self.logger.info('using xml.etree.ElementTree parser')
                 return parse(filename)
             except ImportError:
-                self.logger.critical("Failed to import ElementTree from any known place")
+                self.logger.critical('Parser.parse: failed to import ElementTree from any known place')
                 raise
diff --git a/supvisors/statemachine.py b/supvisors/statemachine.py
index c1c5e681..3604a250 100644
--- a/supvisors/statemachine.py
+++ b/supvisors/statemachine.py
@@ -18,56 +18,92 @@
 # ======================================================================
 
 from time import time
+from typing import Any, Callable
 
 from supvisors.strategy import conciliate_conflicts
-from supvisors.ttypes import AddressStates, SupvisorsStates
+from supvisors.ttypes import AddressStates, SupvisorsStates, Payload
 from supvisors.utils import supvisors_shortcuts
 
 
 class AbstractState(object):
-    """ Base class for a state with simple entry / next / exit actions """
+    """ Base class for a state with simple entry / next / exit actions.
 
-    def __init__(self, supvisors):
-        """ Initialization of the attributes. """
+    Attributes are:
+
+        - supvisors: the reference to the global Supvisors structure,
+        - *shortcuts*: the references to Supvisors attributes used here,
+        - address_name: the name of the local node.
+     """
+
+    def __init__(self, supvisors: Any) -> None:
+        """ Initialization of the attributes.
+
+        :param supvisors: the global Supvisors structure
+        """
         self.supvisors = supvisors
-        supvisors_shortcuts(self, ['context', 'failure_handler',
-                                   'logger', 'starter', 'stopper'])
-        self.address = supvisors.address_mapper.local_address
+        supvisors_shortcuts(self, ['context', 'failure_handler', 'logger', 'options',
+                                   'starter', 'stopper'])
+        self.address_name = supvisors.address_mapper.local_address
 
-    def enter(self):
-        """ Actions performed when reaching the state. """
+    def enter(self) -> None:
+        """ Actions performed when entering the state.
+        May be redefined in subclasses.
 
-    def next(self):
-        """ Actions performed upon event. """
+        :return: None
+        """
 
-    def exit(self):
-        """ Actions performed when leaving the state. """
+    def next(self) -> None:
+        """ Actions performed upon reception of an event.
+        May be redefined in subclasses.
+
+        :return: None
+        """
+
+    def exit(self) -> None:
+        """ Actions performed when leaving the state.
+        May be redefined in subclasses.
 
-    def apply_addresses_func(self, func):
+        :return: None
+        """
+
+    def apply_addresses_func(self, func: Callable[[str], None]) -> None:
         """ Perform the action func on all addresses.
-        The local address is the last to be performed. """
+        The local address is the last to be performed.
+
+        :param func: the function callable using a node name as parameter
+        :return: None
+        """
         # send func request to all locals (but self address)
         for status in self.context.addresses.values():
-            if status.address_name != self.address:
+            if status.address_name != self.address_name:
                 if status.state == AddressStates.RUNNING:
                     func(status.address_name)
-                    self.logger.warn('supervisord {} on {}'
-                                     .format(func.__name__, status.address_name))
+                    self.logger.warn('supervisord {} on {}'.format(func.__name__, status.address_name))
                 else:
                     self.logger.info('cannot {} supervisord on {}: Remote state is {}'
-                                     .format(func.__name__, status.address_name,
-                                             status.state_string()))
+                                     .format(func.__name__, status.address_name, status.state_string()))
         # send request to self supervisord
-        func(self.address)
+        func(self.address_name)
 
 
 class InitializationState(AbstractState):
-    """ In the INITIALIZATION state, Supvisors synchronizes to all known
-    instances. """
+    """ In the INITIALIZATION state, Supvisors synchronizes to all known instances.
+
+    Attributes are:
+
+        - start_date: the date when entering this state.
+    """
+
+    def __init__(self, supvisors: Any):
+        """ Initialization of the attributes.
+
+        :param supvisors: the global Supvisors structure
+        """
+        AbstractState.__init__(self, supvisors)
+        self.start_date = 0
 
     def enter(self):
-        """ When entering in the INITIALIZATION state, reset the status of
-        addresses. """
+        """ When entering in the INITIALIZATION state, reset the status of addresses. """
         self.context.master_address = ''
         self.start_date = int(time())
         # clear any existing job
@@ -78,41 +114,49 @@ def enter(self):
                 # do NOT use state setter as transition may be rejected
                 status._state = AddressStates.UNKNOWN
 
-    def next(self):
-        """ Wait for addresses to publish until all are active or timeout. """
+    def next(self) -> int:
+        """ Wait for nodes to publish until:
+            - all are active,
+            - or all defined in the optional *force_synchro_if* option are active,
+            - or timeout is reached.
+
+        :return: the new Supvisors state
+        """
         # cannot get out of this state without local supervisor RUNNING
         addresses = self.context.running_addresses()
-        if self.address in addresses:
+        if self.address_name in addresses:
+            # synchro done if the state of all addresses is known
             if len(self.context.unknown_addresses()) == 0:
-                # synchro done if the state of all addresses is known
+                self.logger.info('InitializationState.next: all nodes are RUNNING')
+                return SupvisorsStates.DEPLOYMENT
+            # synchro done if the state of all forced addresses is known
+            if self.context.forced_addresses and len(self.context.unknown_forced_addresses()) == 0:
+                self.logger.info('InitializationState.next: all forced nodes are RUNNING')
                 return SupvisorsStates.DEPLOYMENT
-            # if synchro timeout reached, stop synchro
-            # and work with known addresses
+            # if synchro timeout reached, stop synchro and work with known addresses
             if (time() - self.start_date) > self.supvisors.options.synchro_timeout:
-                self.logger.warn('synchro timed out')
+                self.logger.warn('InitializationState.next: synchro timed out')
                 return SupvisorsStates.DEPLOYMENT
-            self.logger.debug('still waiting for remote supvisors to synchronize')
+            self.logger.debug('InitializationState.next: still waiting for remote Supvisors instances to publish')
         else:
-            self.logger.debug('local address {} still not RUNNING'
-                              .format(self.address))
+            self.logger.debug('InitializationState.next: local node {} still not RUNNING'.format(self.address_name))
         return SupvisorsStates.INITIALIZATION
 
     def exit(self):
-        """ When leaving the INITIALIZATION state, the working addresses are
-        defined.
+        """ When leaving the INITIALIZATION state, the working addresses are defined.
         One of them is elected as the MASTER. """
         # force state of missing Supvisors instances
         self.context.end_synchro()
-        # arbitrarily choice : master address is the 'lowest' address
-        # among running addresses
+        # arbitrarily choice : master address is the 'lowest' address among running addresses
         addresses = self.context.running_addresses()
-        self.logger.info('working with boards {}'.format(addresses))
-        self.context.master_address = min(addresses)
+        self.logger.info('InitializationState.exit: working with nodes {}'.format(addresses))
+        # elect master node among working addresses only if not fixed before
+        if not self.context.master_address:
+            self.context.master_address = min(addresses)
 
 
 class DeploymentState(AbstractState):
-    """ In the DEPLOYMENT state, Supvisors starts automatically the
-    applications having a starting model. """
+    """ In the DEPLOYMENT state, Supvisors starts automatically the applications having a starting model. """
 
     def enter(self):
         """ When entering in the DEPLOYMENT state, define the start and stop sequences.
@@ -127,9 +171,7 @@ def enter(self):
     def next(self):
         """ Wait for applications to be started. """
         if not self.context.master or self.starter.check_starting():
-            return SupvisorsStates.CONCILIATION \
-                if self.context.conflicting() \
-                else SupvisorsStates.OPERATION
+            return SupvisorsStates.CONCILIATION if self.context.conflicting() else SupvisorsStates.OPERATION
         return SupvisorsStates.DEPLOYMENT
 
 
@@ -138,12 +180,11 @@ class OperationState(AbstractState):
 
     def next(self):
         """ Check that all addresses are still active.
-        Look after possible conflicts due to multiple running instances
-        of the same program. """
+        Look after possible conflicts due to multiple running instances of the same program. """
         # check eventual jobs in progress
         if self.starter.check_starting() and self.stopper.check_stopping():
             # check if master and local are still RUNNING
-            if self.context.addresses[self.address].state != AddressStates.RUNNING:
+            if self.context.addresses[self.address_name].state != AddressStates.RUNNING:
                 return SupvisorsStates.INITIALIZATION
             if self.context.addresses[self.context.master_address].state != AddressStates.RUNNING:
                 return SupvisorsStates.INITIALIZATION
@@ -157,8 +198,7 @@ class ConciliationState(AbstractState):
     """ In the CONCILIATION state, Supvisors conciliates the conflicts. """
 
     def enter(self):
-        """ When entering in the CONCILIATION state,
-        conciliate automatically the conflicts.
+        """ When entering in the CONCILIATION state, conciliate automatically the conflicts.
         Only the MASTER can conciliate conflicts. """
         if self.context.master:
             conciliate_conflicts(self.supvisors,
@@ -171,7 +211,7 @@ def next(self):
         # check eventual jobs in progress
         if self.starter.check_starting() and self.stopper.check_stopping():
             # check if local is still RUNNING
-            local_status = self.context.addresses[self.address]
+            local_status = self.context.addresses[self.address_name]
             if local_status.state != AddressStates.RUNNING:
                 return SupvisorsStates.INITIALIZATION
             # check if master is still RUNNING
@@ -188,8 +228,7 @@ def next(self):
 
 
 class RestartingState(AbstractState):
-    """ In the RESTARTING state, Supvisors stops all applications before
-    triggering a full restart. """
+    """ In the RESTARTING state, Supvisors stops all applications before triggering a full restart. """
 
     def enter(self):
         """ When entering in the RESTARTING state, stop all applications. """
@@ -236,17 +275,17 @@ class ShutdownState(AbstractState):
 
 
 class FiniteStateMachine:
-    """ This class implements a very simple behaviour of FiniteStateMachine
-    based on a single event.
+    """ This class implements a very simple behaviour of FiniteStateMachine based on a single event.
     A state is able to evaluate itself for transitions. """
 
     def __init__(self, supvisors):
         """ Reset the state machine and the associated context """
         self.supvisors = supvisors
-        supvisors_shortcuts(self, ['context', 'failure_handler', 'starter',
-                                   'stopper', 'logger'])
+        supvisors_shortcuts(self, ['context', 'failure_handler', 'starter', 'stopper', 'logger'])
+        self.state = SupvisorsStates.INITIALIZATION
+        self.instance = None
+        # Trigger first state / INITIALIZATION
         self.update_instance(SupvisorsStates.INITIALIZATION)
-        self.instance.enter()
 
     def state_string(self):
         """ Return the supvisors state as a string. """
@@ -263,31 +302,29 @@ def set_state(self, next_state):
         while next_state != self.state and next_state in self.__Transitions[self.state]:
             self.instance.exit()
             self.update_instance(next_state)
-            self.logger.info('Supvisors in {}'.format(self.state_string()))
-            self.instance.enter()
             next_state = self.instance.next()
 
     def update_instance(self, state):
         """ Change the current state.
         The method also triggers the publication of the change. """
         self.state = state
+        self.logger.info('FiniteStateMachine.update_instance: Supvisors in {}'.format(self.state_string()))
         self.instance = self.__StateInstances[state](self.supvisors)
+        self.instance.enter()
         # publish SupvisorsStatus event
-        if hasattr(self.supvisors, 'zmq'):
-            self.supvisors.zmq.publisher.send_supvisors_status(self)
+        # the zmq does not exist yet for the first occurrence here
+        if self.supvisors.zmq:
+            self.supvisors.zmq.publisher.send_supvisors_status(self.serial())
 
     def on_timer_event(self):
-        """ Periodic task used to check if remote Supvisors instances
-        are still active.
+        """ Periodic task used to check if remote Supvisors instances are still active.
         This is also the main event on this state machine. """
         self.context.on_timer_event()
         self.next()
-        # fix failures if any (can happen after an address has been invalidated,
-        # a process crash or a conciliation request)
+        # fix failures if any (can happen after a node invalidation, a process crash or a conciliation request)
         self.failure_handler.trigger_jobs()
-        # check if new isolating remotes and return the list of
-        # newly isolated addresses
-        # FIXME: create an internal event to confirm that socket has been disonnected ?
+        # check if new isolating remotes and return the list of newly isolated addresses
+        # TODO: create an internal event to confirm that socket has been disconnected ?
         return self.context.handle_isolation()
 
     def on_tick_event(self, address, when):
@@ -296,8 +333,7 @@ def on_tick_event(self, address, when):
         # could call the same behaviour as on_timer_event if necessary
 
     def on_process_event(self, address, event):
-        """ This event is used to refresh the process data related
-        to the event and address.
+        """ This event is used to refresh the process data related to the event and address.
         This event also triggers the application starter and/or stopper. """
         process = self.context.on_process_event(address, event)
         if process:
@@ -308,36 +344,65 @@ def on_process_event(self, address, event):
             self.starter.on_event(process)
             # feed stopper with event
             self.stopper.on_event(process)
-            # only the master is allowed to trigger an automatic behaviour
-            # for a running failure
-            if self.context.master and process.crashed() and \
-                    not (starting or stopping):
+            # only the master is allowed to trigger an automatic behaviour for a running failure
+            if self.context.master and process.crashed() and not (starting or stopping):
                 self.failure_handler.add_default_job(process)
                 self.failure_handler.trigger_jobs()
 
-    def on_process_info(self, address_name, info):
-        """ This event is used to fill the internal structures with processes
-        available on address. """
+    def on_process_info(self, address_name: str, info) -> None:
+        """ This event is used to fill the internal structures with processes available on node. """
         self.context.load_processes(address_name, info)
 
-    def on_authorization(self, address_name, authorized):
-        """ This event is used to finalize the port-knocking
-        between Supvisors instances. """
-        self.context.on_authorization(address_name, authorized)
-
-    def on_restart(self):
-        """ This event is used to transition the state machine
-        to the RESTARTING state. """
+    def on_authorization(self, address_name: str, authorized: bool, master_address: str) -> None:
+        """ This event is used to finalize the port-knocking between Supvisors instances.
+        When a new node that has not be part of Supvisors comes in the group, back to INITIALIZATION
+        for a new Master election and a possible deployment
+
+        :param address_name: the node name from which the event comes
+        :param authorized: the authorization status as seen by the remote node
+        :param master_address: the master node perceived by the remote node
+        :return: None
+        """
+        self.logger.info('FiniteStateMachine.on_authorization: address_name={} authorized={} master_address={}'
+                         .format(address_name, authorized, master_address))
+        if self.context.on_authorization(address_name, authorized):
+            if master_address:
+                if not self.context.master_address:
+                    # local Supvisors doesn't know about a master yet but remote Supvisors does
+                    # this is likely due to the fact that the local Supervisor has just been started whereas
+                    # a Supvisors group was already operating
+                    # so accept remote perception in order to avoid going back to INITIALIZATION state for all
+                    self.logger.warn('FiniteStateMachine.on_authorization: accept master={} declared by node={}'
+                                     .format(master_address, address_name))
+                    self.context.master_address = master_address
+                elif master_address != self.context.master_address:
+                    # 2 different perceptions of the master, likely due to a split-brain situation
+                    # so going back to INITIALIZATION to fix
+                    self.logger.warn('FiniteStateMachine.on_authorization: master discrepancy. '
+                                     ' local declares {} - remote ({}) declares {}'
+                                     .format(self.context.master_address, address_name, master_address))
+                    self.set_state(SupvisorsStates.INITIALIZATION)
+
+    def on_restart(self) -> None:
+        """ This event is used to transition the state machine to the RESTARTING state.
+
+        :return: None
+        """
         self.set_state(SupvisorsStates.RESTARTING)
 
-    def on_shutdown(self):
-        """ This event is used to transition the state machine
-        to the SHUTTING_DOWN state. """
+    def on_shutdown(self) -> None:
+        """ This event is used to transition the state machine to the SHUTTING_DOWN state.
+
+        :return: None
+        """
         self.set_state(SupvisorsStates.SHUTTING_DOWN)
 
     # serialization
-    def serial(self):
-        """ Return a serializable form of the SupvisorsState """
+    def serial(self) -> Payload:
+        """ Return a serializable form of the SupvisorsState.
+
+        :return: the Supvisors state as a dictionary
+        """
         return {'statecode': self.state, 'statename': self.state_string()}
 
     # Map between state enumerations and classes
diff --git a/supvisors/strategy.py b/supvisors/strategy.py
index cb977119..8c5b0ded 100644
--- a/supvisors/strategy.py
+++ b/supvisors/strategy.py
@@ -29,7 +29,7 @@ class AbstractStrategy(object):
 
     def __init__(self, supvisors):
         self.supvisors = supvisors
-        supvisors_shortcuts(self, ['context', 'logger'])
+        supvisors_shortcuts(self, ['address_mapper', 'context', 'logger'])
 
 
 # Strategy management for Starting
@@ -37,27 +37,25 @@ class AbstractStartingStrategy(AbstractStrategy):
     """ Base class for a starting strategy. """
 
     def is_loading_valid(self, address, expected_loading):
-        """ Return True and current loading if remote Supvisors instance is
-        active and can support the additional loading. """
+        """ Return True and current loading if remote Supvisors instance is active
+        and can support the additional loading. """
+        self.logger.trace('is_loading_valid address={} expected_loading={}'.format(address, expected_loading))
         if address in self.context.addresses.keys():
             status = self.context.addresses[address]
-            self.logger.trace('address {} state={}'.format(
-                address, status.state_string()))
+            self.logger.trace('address {} state={}'.format(address, status.state_string()))
             if status.state == AddressStates.RUNNING:
                 loading = status.loading()
                 self.logger.debug('address={} loading={} expected_loading={}'
                                   .format(address, loading, expected_loading))
                 return loading + expected_loading < 100, loading
-            self.logger.debug('address {} not RUNNING'.format(address))
+            self.logger.trace('address {} not RUNNING'.format(address))
         return False, 0
 
     def get_loading_and_validity(self, addresses, expected_loading):
-        """ Return the report of loading capability of all addresses iaw the
-        additional loading required. """
+        """ Return the report of loading capability of all addresses iaw the additional loading required. """
         if '*' in addresses:
-            addresses = self.supvisors.address_mapper.addresses
-        loading_validities = {address: self.is_loading_valid(address,
-                                                             expected_loading)
+            addresses = self.address_mapper.addresses
+        loading_validities = {address: self.is_loading_valid(address, expected_loading)
                               for address in addresses}
         self.logger.trace('loading_validities={}'.format(loading_validities))
         return loading_validities
@@ -73,36 +71,23 @@ def sort_valid_by_loading(self, loading_validities):
 
 
 class ConfigStrategy(AbstractStartingStrategy):
-    """ Strategy designed to choose the address using the order defined in the
-    configuration file. """
+    """ Strategy designed to choose the address using the order defined in the configuration file. """
 
     def get_address(self, addresses, expected_loading):
-        """ Choose the first address that can support the additional loading
-        requested. """
-        self.logger.debug('addresses={} expected_loading={}'
+        """ Choose the first address that can support the additional loading requested. """
+        self.logger.debug('ConfigStrategy: addresses={} expected_loading={}'
                           .format(addresses, expected_loading))
-        # returns the first remote in list that is capable of handling
-        # the loading
-        loading_validities = self.get_loading_and_validity(
-            addresses, expected_loading)
-        if '*' in addresses:
-            addresses = self.supvisors.address_mapper.addresses
-        return next((address for address in addresses
-                     if loading_validities[address][0]), None)
+        loading_validities = self.get_loading_and_validity(addresses, expected_loading)
+        return next((address for address, (validity, _) in loading_validities.items() if validity), None)
 
 
 class LessLoadedStrategy(AbstractStartingStrategy):
     """ Strategy designed to share the loading among all the addresses. """
 
     def get_address(self, addresses, expected_loading):
-        """ Choose the address having the lowest loading that can support
-        the additional loading requested """
-        self.logger.trace('addresses={} expectedLoading={}'.format(
-            addresses, expected_loading))
-        # returns the less loaded remote from list that is capable of handling
-        # the loading
-        loading_validities = self.get_loading_and_validity(
-            addresses, expected_loading)
+        """ Choose the address having the lowest loading that can support the additional loading requested. """
+        self.logger.trace('LessLoadedStrategy: addresses={} expectedLoading={}'.format(addresses, expected_loading))
+        loading_validities = self.get_loading_and_validity(addresses, expected_loading)
         sorted_addresses = self.sort_valid_by_loading(loading_validities)
         return sorted_addresses[0][0] if sorted_addresses else None
 
@@ -111,27 +96,34 @@ class MostLoadedStrategy(AbstractStartingStrategy):
     """ Strategy designed to maximize the loading of an address. """
 
     def get_address(self, addresses, expected_loading):
-        """ Choose the address having the highest loading that can support
-        the additional loading requested """
-        self.logger.trace('addresses={} expectedLoading={}'.format(
-            addresses, expected_loading))
-        # returns the most loaded remote from list that is capable of
-        # handling the loading
-        loading_validities = self.get_loading_and_validity(addresses,
-                                                           expected_loading)
+        """ Choose the address having the highest loading that can support the additional loading requested. """
+        self.logger.trace('MostLoadedStrategy: addresses={} expectedLoading={}'.format(addresses, expected_loading))
+        loading_validities = self.get_loading_and_validity(addresses, expected_loading)
         sorted_addresses = self.sort_valid_by_loading(loading_validities)
         return sorted_addresses[-1][0] if sorted_addresses else None
 
 
+class LocalStrategy(AbstractStartingStrategy):
+    """ Strategy designed to start the process on the local address. """
+
+    def get_address(self, addresses, expected_loading):
+        """ Choose the local address provided that it can support the additional loading requested. """
+        self.logger.trace('LocalStrategy: addresses={} expectedLoading={}'.format(addresses, expected_loading))
+        loading_validities = self.get_loading_and_validity(addresses, expected_loading)
+        local_address = self.address_mapper.local_address
+        return local_address if loading_validities.get(local_address, (False,))[0] else None
+
+
 def get_address(supvisors, strategy, addresses, expected_loading):
-    """ Creates a strategy and let it find an address to start a process
-    having a defined loading. """
+    """ Creates a strategy and let it find an address to start a process having a defined loading. """
     if strategy == StartingStrategies.CONFIG:
         instance = ConfigStrategy(supvisors)
     if strategy == StartingStrategies.LESS_LOADED:
         instance = LessLoadedStrategy(supvisors)
     if strategy == StartingStrategies.MOST_LOADED:
         instance = MostLoadedStrategy(supvisors)
+    if strategy == StartingStrategies.LOCAL:
+        instance = LocalStrategy(supvisors)
     # apply strategy result
     return instance.get_address(addresses, expected_loading)
 
@@ -204,12 +196,10 @@ def conciliate(self, conflicts):
 
 
 class RestartStrategy(AbstractStrategy):
-    """ Strategy designed to stop all conflicting processes
-    and to restart a single instance. """
+    """ Strategy designed to stop all conflicting processes and to restart a single instance. """
 
     def conciliate(self, conflicts):
-        """ Conciliate the conflicts by notifying the failure handler
-        to restart the process. """
+        """ Conciliate the conflicts by notifying the failure handler to restart the process. """
         # add all processes to be restarted to the failure handler,
         # as it is in its design to restart a process
         for process in conflicts:
@@ -222,16 +212,16 @@ def conciliate(self, conflicts):
 
 
 class FailureStrategy(AbstractStrategy):
-    """ Strategy designed to stop all conflicting processes and
-    to apply the running failure strategy related to the process. """
+    """ Strategy designed to stop all conflicting processes and to apply the running failure strategy
+    related to the process. """
 
     def conciliate(self, conflicts):
-        """ Conciliate the conflicts by notifying the failure handler
-        to apply the running failure strategy related to the process. """
+        """ Conciliate the conflicts by notifying the failure handler to apply the running failure strategy
+        related to the process. """
         # stop all processes and add them to the failure handler
         for process in conflicts:
             self.supvisors.stopper.stop_process(process)
-            self.logger.warn('failure conciliation: {}'.format(process.namespec()))
+            self.logger.warn('FailureStrategy.conciliate: failure conciliation: {}'.format(process.namespec()))
             self.supvisors.failure_handler.add_default_job(process)
         # trigger the jobs of the failure handler directly (could wait for next tick)
         self.supvisors.failure_handler.trigger_jobs()
@@ -260,19 +250,14 @@ class RunningFailureHandler(AbstractStrategy):
     """ Handler of running failures.
     The strategies are linked to the RunningFailureStrategies enumeration.
 
-    Any Supvisors instance may hold application processes with different
-    running failure strategies.
-    If the Supvisors instance becomes inactive, as seen from another Supvisors
-    instance, it could lead to have all possible strategies to apply on the
-    same application and related processes, which makes no sense.
+    Any Supvisors instance may hold application processes with different running failure strategies.
+    If the Supvisors instance becomes inactive, as seen from another Supvisors instance, it could lead
+    to have all possible strategies to apply on the same application and related processes, which makes no sense.
 
     So it has been chosen to give a priority to the strategies.
-    The highest priority is for the most restricting strategy, consisting in
-    stopping the application.
-    Then, the priority goes to the strategy having the highest impact, i.e.
-    restarting the application.
-    The lowest priority is for the most simple strategy consisting in restarting
-    only the involved process.
+    The highest priority is for the most restricting strategy, consisting in stopping the application.
+    Then, the priority goes to the strategy having the highest impact, i.e. restarting the application.
+    The lowest priority is for the most simple strategy consisting in restarting only the involved process.
 
     Attributes are:
 
@@ -311,24 +296,19 @@ def add_job(self, strategy, process):
         application_name = process.application_name
         if strategy == RunningFailureStrategies.STOP_APPLICATION:
             self.stop_application_jobs.add(application_name)
-            self.restart_application_jobs = set(filter(
-                lambda x: x != application_name,
-                self.restart_application_jobs))
-            self.restart_process_jobs = set(filter(
-                lambda x: x.application_name != application_name,
-                self.restart_process_jobs))
-            self.continue_process_jobs = set(filter(
-                lambda x: x.application_name != application_name,
-                self.continue_process_jobs))
+            self.restart_application_jobs = set(filter(lambda x: x != application_name,
+                                                       self.restart_application_jobs))
+            self.restart_process_jobs = set(filter(lambda x: x.application_name != application_name,
+                                                   self.restart_process_jobs))
+            self.continue_process_jobs = set(filter(lambda x: x.application_name != application_name,
+                                                    self.continue_process_jobs))
         elif strategy == RunningFailureStrategies.RESTART_APPLICATION:
             if application_name not in self.stop_application_jobs:
                 self.restart_application_jobs.add(application_name)
-                self.restart_process_jobs = set(filter(
-                    lambda x: x.application_name != application_name,
-                    self.restart_process_jobs))
-                self.continue_process_jobs = set(filter(
-                    lambda x: x.application_name != application_name,
-                    self.continue_process_jobs))
+                self.restart_process_jobs = set(filter(lambda x: x.application_name != application_name,
+                                                       self.restart_process_jobs))
+                self.continue_process_jobs = set(filter(lambda x: x.application_name != application_name,
+                                                        self.continue_process_jobs))
         elif strategy == RunningFailureStrategies.RESTART_PROCESS:
             if application_name not in self.stop_application_jobs and \
                     application_name not in self.restart_application_jobs:
@@ -346,19 +326,20 @@ def add_default_job(self, process):
         self.add_job(process.rules.running_failure_strategy, process)
 
     def trigger_jobs(self):
-        """ Trigger the configured strategy when a process of a running
-        application crashes. """
+        """ Trigger the configured strategy when a process of a running application crashes. """
         # consider applications to stop
         if self.stop_application_jobs:
             for application_name in self.stop_application_jobs:
-                self.logger.warn('stop application {}'.format(application_name))
+                self.logger.warn('RunningFailureHandler.trigger_jobs: stop application {}'
+                                 .format(application_name))
                 application = self.context.applications[application_name]
                 self.stopper.stop_application(application)
             self.stop_application_jobs = set()
         # consider applications to restart
         if self.restart_application_jobs:
             for application_name in self.restart_application_jobs:
-                self.logger.warn('restart application {}'.format(application_name))
+                self.logger.warn('RunningFailureHandler.trigger_jobs: restart application {}'
+                                 .format(application_name))
                 application = self.context.applications[application_name]
                 self.stopper.stop_application(application)
                 # defer the application starting
@@ -367,7 +348,8 @@ def trigger_jobs(self):
         # consider processes to restart
         if self.restart_process_jobs:
             for process in self.restart_process_jobs:
-                self.logger.warn('restart process {}'.format(process.namespec()))
+                self.logger.warn('RunningFailureHandler.trigger_jobs: restart process {}'
+                                 .format(process.namespec()))
                 self.stopper.stop_process(process)
                 # defer the process starting
                 self.start_process_jobs.add(process)
@@ -376,21 +358,21 @@ def trigger_jobs(self):
         if self.start_application_jobs:
             for application in self.start_application_jobs.copy():
                 if application.stopped():
-                    self.logger.debug('start application {}'.format(
-                        application.application_name))
+                    self.logger.debug('RunningFailureHandler.trigger_jobs: start application {}'
+                                      .format(application.application_name))
                     self.starter.default_start_application(application)
                     self.start_application_jobs.remove(application)
         # consider processes to start
         if self.start_process_jobs:
             for process in self.start_process_jobs.copy():
                 if process.stopped():
-                    self.logger.warn('restart process {}'.format(
-                        process.namespec()))
+                    self.logger.warn('RunningFailureHandler.trigger_jobs: start process {}'
+                                     .format(process.namespec()))
                     self.starter.default_start_process(process)
                     self.start_process_jobs.remove(process)
         # log only the continuation jobs
         if self.continue_process_jobs:
             for process in self.continue_process_jobs:
-                self.logger.info('continue despite of crashed process {}'.format(
-                    process.namespec()))
+                self.logger.info('RunningFailureHandler.trigger_jobs: continue despite of crashed process {}'
+                                 .format(process.namespec()))
             self.continue_process_jobs = set()
diff --git a/supvisors/supvisorsctl.py b/supvisors/supvisorsctl.py
index 0db08b0f..28345ce1 100644
--- a/supvisors/supvisorsctl.py
+++ b/supvisors/supvisorsctl.py
@@ -26,9 +26,7 @@
 from supervisor.supervisorctl import ControllerPluginBase
 
 from supvisors.rpcinterface import API_VERSION
-from supvisors.ttypes import (ProcessStates,
-                              ConciliationStrategies,
-                              StartingStrategies)
+from supvisors.ttypes import (ProcessStates, ConciliationStrategies, StartingStrategies)
 from supvisors.utils import simple_localtime
 
 
@@ -249,18 +247,14 @@ def do_sstatus(self, arg):
                     try:
                         info = self.supvisors().get_process_info(process)
                     except xmlrpclib.Fault as e:
-                        self.ctl.output('{}: ERROR ({})'
-                                        .format(process, e.faultString))
+                        self.ctl.output('{}: ERROR ({})'.format(process, e.faultString))
                     else:
                         info_list.extend(info)
             # print results
             if info_list:
-                max_appli = max(len(info['application_name'])
-                                for info in info_list) + 4
-                max_proc = max(len(info['process_name'])
-                               for info in info_list) + 4
-                template = '%(appli)-{}s%(proc)-{}s%(state)-12s%(addresses)s' \
-                    .format(max_appli, max_proc)
+                max_appli = max(len(info['application_name']) for info in info_list) + 4
+                max_proc = max(len(info['process_name']) for info in info_list) + 4
+                template = '%(appli)-{}s%(proc)-{}s%(state)-12s%(addresses)s'.format(max_appli, max_proc)
                 for info in info_list:
                     line = template % {'appli': info['application_name'],
                                        'proc': info['process_name'],
diff --git a/supvisors/supvisorszmq.py b/supvisors/supvisorszmq.py
index c8128778..36fbd843 100644
--- a/supvisors/supvisorszmq.py
+++ b/supvisors/supvisorszmq.py
@@ -19,6 +19,9 @@
 
 import zmq
 
+from supervisor.loggers import Logger
+
+from supvisors.ttypes import Payload
 from supvisors.utils import *
 
 # Constant for Zmq sockets
@@ -37,12 +40,10 @@ class InternalEventPublisher(object):
 
         - logger: a reference to the Supvisors logger,
         - address: the address name where this process is running,
-        - socket: the ZeroMQ socket with a PUBLISH pattern,
-        bound on the internal_port defined in the ['supvisors'] section
-        of the Supervisor configuration file.
+        - socket: the ZeroMQ socket with a PUBLISH pattern, bound on the internal_port defined in the ['supvisors'] section of the Supervisor configuration file.
     """
 
-    def __init__(self, address, port, logger):
+    def __init__(self, address: str, port: int, logger: Logger) -> None:
         """ Initialization of the attributes. """
         # keep a reference to supvisors
         self.logger = logger
@@ -54,27 +55,24 @@ def __init__(self, address, port, logger):
         self.logger.info('binding InternalEventPublisher to %s' % url)
         self.socket.bind(url)
 
-    def close(self):
+    def close(self) -> None:
         """ This method closes the PyZMQ socket. """
         self.socket.close(ZMQ_LINGER)
 
-    def send_tick_event(self, payload):
+    def send_tick_event(self, payload: Payload) -> None:
         """ Publishes the tick event with ZeroMQ. """
         self.logger.trace('send TickEvent {}'.format(payload))
-        self.socket.send_pyobj((InternalEventHeaders.TICK,
-                                self.address, payload))
+        self.socket.send_pyobj((InternalEventHeaders.TICK, self.address, payload))
 
-    def send_process_event(self, payload):
+    def send_process_event(self, payload: Payload) -> None:
         """ Publishes the process event with ZeroMQ. """
         self.logger.trace('send ProcessEvent {}'.format(payload))
-        self.socket.send_pyobj((InternalEventHeaders.PROCESS,
-                                self.address, payload))
+        self.socket.send_pyobj((InternalEventHeaders.PROCESS, self.address, payload))
 
-    def send_statistics(self, payload):
+    def send_statistics(self, payload: Payload) -> None:
         """ Publishes the statistics with ZeroMQ. """
         self.logger.trace('send Statistics {}'.format(payload))
-        self.socket.send_pyobj((InternalEventHeaders.STATISTICS,
-                                self.address, payload))
+        self.socket.send_pyobj((InternalEventHeaders.STATISTICS, self.address, payload))
 
 
 class InternalEventSubscriber(object):
@@ -85,7 +83,7 @@ class InternalEventSubscriber(object):
         - socket: the PyZMQ subscriber.
     """
 
-    def __init__(self, addresses, port):
+    def __init__(self, addresses, port: int):
         """ Initialization of the attributes. """
         self.port = port
         self.socket = ZmqContext.socket(zmq.SUB)
@@ -95,7 +93,7 @@ def __init__(self, addresses, port):
             self.socket.connect(url)
         self.socket.setsockopt(zmq.SUBSCRIBE, b'')
 
-    def close(self):
+    def close(self) -> None:
         """ This method closes the PyZMQ socket. """
         self.socket.close(ZMQ_LINGER)
 
@@ -103,9 +101,8 @@ def receive(self):
         """ Reception and pyobj de-serialization of one message. """
         return self.socket.recv_pyobj(zmq.NOBLOCK)
 
-    def disconnect(self, addresses):
-        """ This method disconnects from the PyZMQ socket all addresses
-        passed in parameter. """
+    def disconnect(self, addresses) -> None:
+        """ This method disconnects from the PyZMQ socket all addresses passed in parameter. """
         for address in addresses:
             url = 'tcp://{}:{}'.format(address, self.port)
             self.socket.disconnect(url)
@@ -118,38 +115,34 @@ def __init__(self, port, logger):
         """ Initialization of the attributes. """
         self.logger = logger
         self.socket = ZmqContext.socket(zmq.PUB)
-        # WARN: this is a local binding, only visible to processes
-        # located on the same address
+        # WARN: this is a local binding, only visible to processes located on the same address
         url = 'tcp://127.0.0.1:%d' % port
         self.logger.info('binding local Supvisors EventPublisher to %s' % url)
         self.socket.bind(url)
 
-    def close(self):
+    def close(self) -> None:
         """ This method closes the PyZMQ socket. """
         self.socket.close(ZMQ_LINGER)
 
-    def send_supvisors_status(self, status):
-        """ This method sends a serialized form of the supvisors status
-        through the socket. """
+    def send_supvisors_status(self, status: Payload) -> None:
+        """ This method sends a serialized form of the supvisors status through the socket. """
         self.logger.trace('send SupvisorsStatus {}'.format(status))
         self.socket.send_string(EventHeaders.SUPVISORS, zmq.SNDMORE)
-        self.socket.send_json(status.serial())
+        self.socket.send_json(status)
 
-    def send_address_status(self, status):
-        """ This method sends a serialized form of the address status
-        through the socket. """
-        self.logger.trace('send RemoteStatus {}'.format(status))
+    def send_address_status(self, status: Payload) -> None:
+        """ This method sends a serialized form of the address status through the socket. """
+        self.logger.trace('send AddressStatus {}'.format(status))
         self.socket.send_string(EventHeaders.ADDRESS, zmq.SNDMORE)
-        self.socket.send_json(status.serial())
+        self.socket.send_json(status)
 
-    def send_application_status(self, status):
-        """ This method sends a serialized form of the application status
-        through the socket. """
+    def send_application_status(self, status: Payload) -> None:
+        """ This method sends a serialized form of the application status through the socket. """
         self.logger.trace('send ApplicationStatus {}'.format(status))
         self.socket.send_string(EventHeaders.APPLICATION, zmq.SNDMORE)
-        self.socket.send_json(status.serial())
+        self.socket.send_json(status)
 
-    def send_process_event(self, address, event):
+    def send_process_event(self, address: str, event: Payload) -> None:
         """ This method sends a process event through the socket. """
         # build the event before it is sent
         evt = event.copy()
@@ -158,12 +151,11 @@ def send_process_event(self, address, event):
         self.socket.send_string(EventHeaders.PROCESS_EVENT, zmq.SNDMORE)
         self.socket.send_json(evt)
 
-    def send_process_status(self, status):
-        """ This method sends a serialized form of the process status
-        through the socket. """
+    def send_process_status(self, status: Payload) -> None:
+        """ This method sends a serialized form of the process status through the socket. """
         self.logger.trace('send Process Status {}'.format(status))
         self.socket.send_string(EventHeaders.PROCESS_STATUS, zmq.SNDMORE)
-        self.socket.send_json(status.serial())
+        self.socket.send_json(status)
 
 
 class EventSubscriber(object):
@@ -276,10 +268,8 @@ class RequestPuller(object):
         - socket: the PyZMQ puller.
 
     As it uses an inproc transport, this implies the following conditions:
-        - the RequestPusher instance and the RequestPuller instance MUST share
-        the same ZMQ context,
-        - the RequestPusher instance MUST be created before the RequestPuller
-        instance.
+        - the RequestPusher instance and the RequestPuller instance MUST share the same ZMQ context,
+        - the RequestPusher instance MUST be created before the RequestPuller instance.
     """
 
     def __init__(self):
@@ -293,7 +283,7 @@ def close(self):
         self.socket.close(ZMQ_LINGER)
 
     def receive(self):
-        """ Reception and pyobj unserialization of one message. """
+        """ Reception and pyobj deserialization of one message. """
         return self.socket.recv_pyobj()
 
 
@@ -305,10 +295,8 @@ class RequestPusher(object):
         - socket: the PyZMQ pusher.
 
     As it uses an inproc transport, this implies the following conditions:
-        - the RequestPusher instance and the RequestPuller instance MUST share
-        the same ZMQ context,
-        - the RequestPusher instance MUST be created before the RequestPuller
-        instance.
+        - the RequestPusher instance and the RequestPuller instance MUST share the same ZMQ context,
+        - the RequestPusher instance MUST be created before the RequestPuller instance.
     """
 
     def __init__(self, logger):
@@ -323,44 +311,38 @@ def close(self):
         """ This method closes the PyZMQ socket. """
         self.socket.close(ZMQ_LINGER)
 
-    def send_check_address(self, address_name):
+    def send_check_address(self, address_name: str) -> None:
         """ Send request to check address. """
-        self.logger.trace('send CHECK_ADDRESS {}'.format(address_name))
+        self.logger.debug('RequestPusher.send_check_address: address_name={}'.format(address_name))
         try:
-            self.socket.send_pyobj((DeferredRequestHeaders.CHECK_ADDRESS,
-                                    (address_name,)),
+            self.socket.send_pyobj((DeferredRequestHeaders.CHECK_ADDRESS, (address_name,)),
                                    zmq.NOBLOCK)
         except zmq.error.Again:
-            self.logger.error('CHECK_ADDRESS not sent')
+            self.logger.error('RequestPusher.send_check_address: CHECK_ADDRESS not sent')
 
     def send_isolate_addresses(self, address_names):
         """ Send request to isolate address. """
-        self.logger.trace('send ISOLATE_ADDRESSES {}'.format(address_names))
+        self.logger.trace('RequestPusher.send_isolate_addresses: address_names={}'.format(address_names))
         try:
-            self.socket.send_pyobj((DeferredRequestHeaders.ISOLATE_ADDRESSES,
-                                    address_names),
+            self.socket.send_pyobj((DeferredRequestHeaders.ISOLATE_ADDRESSES, address_names),
                                    zmq.NOBLOCK)
         except zmq.error.Again:
-            self.logger.error('ISOLATE_ADDRESSES not sent')
+            self.logger.error('RequestPusher.send_isolate_addresses: ISOLATE_ADDRESSES not sent')
 
     def send_start_process(self, address_name, namespec, extra_args):
         """ Send request to start process. """
-        self.logger.trace('send START_PROCESS {} to {} with {}'.format(
-            namespec, address_name, extra_args))
+        self.logger.trace('send START_PROCESS {} to {} with {}'.format(namespec, address_name, extra_args))
         try:
-            self.socket.send_pyobj((DeferredRequestHeaders.START_PROCESS,
-                                    (address_name, namespec, extra_args)),
+            self.socket.send_pyobj((DeferredRequestHeaders.START_PROCESS, (address_name, namespec, extra_args)),
                                    zmq.NOBLOCK)
         except zmq.error.Again:
             self.logger.error('START_PROCESS not sent')
 
     def send_stop_process(self, address_name, namespec):
         """ Send request to stop process. """
-        self.logger.trace('send STOP_PROCESS {} to {}'.format(
-            namespec, address_name))
+        self.logger.trace('send STOP_PROCESS {} to {}'.format(namespec, address_name))
         try:
-            self.socket.send_pyobj((DeferredRequestHeaders.STOP_PROCESS,
-                                    (address_name, namespec)),
+            self.socket.send_pyobj((DeferredRequestHeaders.STOP_PROCESS, (address_name, namespec)),
                                    zmq.NOBLOCK)
         except zmq.error.Again:
             self.logger.error('STOP_PROCESS not sent')
@@ -369,8 +351,7 @@ def send_restart(self, address_name):
         """ Send request to restart a Supervisor. """
         self.logger.trace('send RESTART {}'.format(address_name))
         try:
-            self.socket.send_pyobj((DeferredRequestHeaders.RESTART,
-                                    (address_name,)),
+            self.socket.send_pyobj((DeferredRequestHeaders.RESTART, (address_name,)),
                                    zmq.NOBLOCK)
         except zmq.error.Again:
             self.logger.error('RESTART not sent')
@@ -379,8 +360,7 @@ def send_shutdown(self, address_name):
         """ Send request to shutdown a Supervisor. """
         self.logger.trace('send SHUTDOWN {}'.format(address_name))
         try:
-            self.socket.send_pyobj((DeferredRequestHeaders.SHUTDOWN,
-                                    (address_name,)),
+            self.socket.send_pyobj((DeferredRequestHeaders.SHUTDOWN, (address_name,)),
                                    zmq.NOBLOCK)
         except zmq.error.Again:
             self.logger.error('SHUTDOWN not sent')
@@ -388,22 +368,16 @@ def send_shutdown(self, address_name):
 
 class SupervisorZmq(object):
     """ Class for PyZmq context and sockets used from the Supervisor thread.
-
-    This instance owns the PyZmq context that is shared between the Supervisor
-    thread and the Supvisors thread.
+    This instance owns the PyZmq context that is shared between the Supervisor thread and the Supvisors thread.
     """
 
     def __init__(self, supvisors):
         """ Create the sockets. """
-        self.publisher = EventPublisher(
-            supvisors.options.event_port,
-            supvisors.logger)
-        self.internal_publisher = InternalEventPublisher(
-            supvisors.address_mapper.local_address,
-            supvisors.options.internal_port,
-            supvisors.logger)
-        self.pusher = RequestPusher(
-            supvisors.logger)
+        self.publisher = EventPublisher(supvisors.options.event_port, supvisors.logger)
+        self.internal_publisher = InternalEventPublisher(supvisors.address_mapper.local_address,
+                                                         supvisors.options.internal_port,
+                                                         supvisors.logger)
+        self.pusher = RequestPusher(supvisors.logger)
 
     def close(self):
         """ Close the sockets. """
@@ -419,9 +393,8 @@ class SupvisorsZmq(object):
     def __init__(self, supvisors):
         """ Create the sockets.
         The Supervisor logger cannot be used here (not thread-safe). """
-        self.internal_subscriber = InternalEventSubscriber(
-            supvisors.address_mapper.addresses,
-            supvisors.options.internal_port)
+        self.internal_subscriber = InternalEventSubscriber(supvisors.address_mapper.addresses,
+                                                           supvisors.options.internal_port)
         self.puller = RequestPuller()
 
     def close(self):
diff --git a/supvisors/ttypes.py b/supvisors/ttypes.py
index b170053b..4bfd3543 100644
--- a/supvisors/ttypes.py
+++ b/supvisors/ttypes.py
@@ -19,6 +19,8 @@
 
 import supervisor.states
 
+from typing import Any, Mapping
+
 from supvisors.utils import *
 
 
@@ -42,7 +44,7 @@ class ApplicationStates:
 @enumeration_tools
 class StartingStrategies:
     """ Applicable strategies that can be applied to start processes. """
-    CONFIG, LESS_LOADED, MOST_LOADED = range(3)
+    CONFIG, LESS_LOADED, MOST_LOADED, LOCAL = range(4)
 
 
 @enumeration_tools
@@ -54,23 +56,20 @@ class ConciliationStrategies:
 
 @enumeration_tools
 class StartingFailureStrategies:
-    """ Applicable strategies that can be applied on a failure of a starting
-    application. """
+    """ Applicable strategies that can be applied on a failure of a starting application. """
     ABORT, STOP, CONTINUE = range(3)
 
 
 @enumeration_tools
 class RunningFailureStrategies:
-    """ Applicable strategies that can be applied on a failure of a running
-    application. """
+    """ Applicable strategies that can be applied on a failure of a running application. """
     CONTINUE, RESTART_PROCESS, STOP_APPLICATION, RESTART_APPLICATION = range(4)
 
 
 @enumeration_tools
 class SupvisorsStates:
     """ Internal state of Supvisors. """
-    INITIALIZATION, DEPLOYMENT, OPERATION, CONCILIATION, RESTARTING, \
-        SHUTTING_DOWN, SHUTDOWN = range(7)
+    INITIALIZATION, DEPLOYMENT, OPERATION, CONCILIATION, RESTARTING, SHUTTING_DOWN, SHUTDOWN = range(7)
 
 
 # Exceptions
@@ -82,3 +81,7 @@ def __init__(self, value):
 
     def __str__(self):
         return self.value
+
+
+# Types for annotations
+Payload = Mapping[str, Any]
diff --git a/supvisors/ui/application.html b/supvisors/ui/application.html
index 69894594..db6d246b 100644
--- a/supvisors/ui/application.html
+++ b/supvisors/ui/application.html
@@ -55,8 +55,9 @@ <h3 class="classState">
                         <tr><th>Starting strategy</th></tr>
                         <tr><td><ul class="linear">
                             <li><a href="#" meld:id="config_a_mid" class="button on">CONFIG</a></li>
-                            <li><a href="#" meld:id="most_a_mid" class="button on">MOST_LOADED</a></li>
-                            <li><a href="#" meld:id="less_a_mid" class="button on">LESS_LOADED</a></li>
+                            <li><a href="#" meld:id="most_loaded_a_mid" class="button on">MOST_LOADED</a></li>
+                            <li><a href="#" meld:id="less_loaded_a_mid" class="button on">LESS_LOADED</a></li>
+                            <li><a href="#" meld:id="local_a_mid" class="button on">LOCAL</a></li>
                         </ul></td></tr>
                     </table>
                 </div>
@@ -100,7 +101,7 @@ <h3 class="classState">
                             <tr>
                                 <th class="name">Name</th><th class="state">State</th>
                                 <th class="addr">Address</th><th>Description</th>
-                                <th class="load">Load</th><th class="cpu">CPU</th><th class="mem">MEM</th>
+                                <th class="load">Load</th><th class="mem">MEM</th><th class="cpu">CPU</th>
                                 <th colspan="3">Actions</th><th colspan="3">Log</th>
                             </tr>
                         </thead>
@@ -108,7 +109,7 @@ <h3 class="classState">
                             <tr>
                                 <th>Name</th><th>State</th>
                                 <th>Address</th><th>Description</th>
-                                <th>Load</th><th>CPU</th><th>MEM</th>
+                                <th>Load</th><th>MEM</th><th>CPU</th>
                                 <th colspan="3">Actions</th><th colspan="3">Log</th>
                             </tr>
                         </tfoot>
@@ -125,8 +126,8 @@ <h3 class="classState">
                                 </td>
                                 <td meld:id="desc_td_mid">Description</td>
                                 <td meld:id="load_td_mid">5%</td>
-                                <td><a href="#" meld:id="pcpu_a_mid" class="stable">--</a></td>
                                 <td><a href="#" meld:id="pmem_a_mid" class="increase">--</a></td>
+                                <td><a href="#" meld:id="pcpu_a_mid" class="stable">--</a></td>
                                 <td meld:id="start_td_mid"><a href="#" meld:id="start_a_mid" class="button off">Start</a></td>
                                 <td meld:id="stop_td_mid"><a href="#" meld:id="stop_a_mid" class="button off">Stop</a></td>
                                 <td meld:id="restart_td_mid"><a href="#" meld:id="restart_a_mid" class="button off">Restart</a></td>
@@ -144,8 +145,8 @@ <h4>Detailed Statistics -- <span meld:id="process_h_mid">*</span> on <span meld:
                     <div>
                         <table>
                             <tr>
-                                <th colspan="4">CPU</th>
                                 <th colspan="4">MEM</th>
+                                <th colspan="4">CPU</th>
                             </tr>
                             <tr>
                                 <th>%</th>
@@ -158,24 +159,24 @@ <h4>Detailed Statistics -- <span meld:id="process_h_mid">*</span> on <span meld:
                                 <th>SD %</th>
                             </tr>
                             <tr class="brightened">
-                                <td meld:id="pcpuval_td_mid">--</td>
-                                <td meld:id="pcpuavg_td_mid">--</td>
-                                <td meld:id="pcpuslope_td_mid">--</td>
-                                <td meld:id="pcpudev_td_mid">--</td>
                                 <td meld:id="pmemval_td_mid">--</td>
                                 <td meld:id="pmemavg_td_mid">--</td>
                                 <td meld:id="pmemslope_td_mid">--</td>
                                 <td meld:id="pmemdev_td_mid">--</td>
+                                <td meld:id="pcpuval_td_mid">--</td>
+                                <td meld:id="pcpuavg_td_mid">--</td>
+                                <td meld:id="pcpuslope_td_mid">--</td>
+                                <td meld:id="pcpudev_td_mid">--</td>
                             </tr>
                         </table>
                     </div>
 
                     <figure>
-                        <img src="process_cpu.png" alt="Process CPU Graph"/>
+                        <img src="process_mem.png" alt="Process Memory Graph"/>
                     </figure>
 
                     <figure>
-                        <img src="process_mem.png" alt="Process Memory Graph"/>
+                        <img src="process_cpu.png" alt="Process CPU Graph"/>
                     </figure>
                 </div>
             </div>
diff --git a/supvisors/ui/css/ui_style.css b/supvisors/ui/css/ui_style.css
index c8d9e15c..741c6d84 100644
--- a/supvisors/ui/css/ui_style.css
+++ b/supvisors/ui/css/ui_style.css
@@ -179,7 +179,7 @@ nav {
 .vertical_contents {
     display: flex;
     flex-direction: column;
-    justify-content: space-around;
+    justify-content: start;
 }
 
 
@@ -503,7 +503,12 @@ table .desc, table.addr {
  * State gradients
  */
 table .UNKNOWN {
-    background-image: linear-gradient(165deg, grey, white);
+    background-image: linear-gradient(165deg, white, grey, grey, white);
+    color: black;
+}
+
+table .CHECKING {
+    background-image: linear-gradient(165deg, white, grey, green, white);
     color: black;
 }
 
@@ -513,12 +518,12 @@ table .STOPPED, table .EXITED, table .SILENT {
 }
 
 table .STARTING, table .BACKOFF {
-    background-image: linear-gradient(165deg, #cc0, green, white);
+    background-image: linear-gradient(165deg, white, #cc0, green, white);
     color: black;
 }
 
 table .STOPPING {
-    background-image: linear-gradient(165deg, green, #cc0, white);
+    background-image: linear-gradient(165deg, white, green, #cc0, white);
     color: black;
 }
 
diff --git a/supvisors/ui/hostaddress.html b/supvisors/ui/hostaddress.html
index 5a41aca2..5dc0037d 100644
--- a/supvisors/ui/hostaddress.html
+++ b/supvisors/ui/hostaddress.html
@@ -94,6 +94,36 @@ <h3><code meld:id="state_mid" class="classState">RemoteStates</code> at <span me
             <div id="address_contents" meld:id="stats_div_mid">
                 <div class="horizontal_contents">
                     <div class="vertical_contents">
+                        <figure>
+                            <img src="address_mem.png" alt="Address Memory Graph"/>
+                            <figcaption>Memory occupation</figcaption>
+                        </figure>
+                        <div>
+                            <table>
+                                <caption>Memory Statistics</caption>
+                                <tr>
+                                    <th rowspan="2">
+                                        <a href="#" class="button off active">mem</a>
+                                    </th>
+                                    <th>Occ %</th>
+                                    <th>Mean %</th>
+                                    <th>Slope %</th>
+                                    <th>SD %</th>
+                                </tr>
+                                <tr class="brightened">
+                                    <td meld:id="memval_td_mid">--</td>
+                                    <td meld:id="memavg_td_mid">--</td>
+                                    <td meld:id="memslope_td_mid">--</td>
+                                    <td meld:id="memdev_td_mid">--</td>
+                                </tr>
+                            </table>
+                        </div>
+                    </div>
+                    <div class="vertical_contents">
+                        <figure>
+                            <img src="address_cpu.png" alt="Address CPU Graph"/>
+                            <figcaption>CPU loading</figcaption>
+                        </figure>
                         <div>
                             <table>
                                 <caption>Processor Statistics</caption>
@@ -115,41 +145,12 @@ <h3><code meld:id="state_mid" class="classState">RemoteStates</code> at <span me
                                 </tr>
                             </table>
                         </div>
-                        <div>
-                            <table>
-                                <caption>Memory Statistics</caption>
-                                <tr>
-                                    <th rowspan="2">
-                                        <a href="#" class="button off active">mem</a>
-                                    </th>
-                                    <th>Occ %</th>
-                                    <th>Mean %</th>
-                                    <th>Slope %</th>
-                                    <th>SD %</th>
-                                </tr>
-                                <tr class="brightened">
-                                    <td meld:id="memval_td_mid">--</td>
-                                    <td meld:id="memavg_td_mid">--</td>
-                                    <td meld:id="memslope_td_mid">--</td>
-                                    <td meld:id="memdev_td_mid">--</td>
-                                </tr>
-                            </table>
-                        </div>
                     </div>
-
-                    <figure>
-                        <img src="address_cpu.png" alt="Address CPU Graph"/>
-                        <figcaption>CPU loading</figcaption>
-                    </figure>
-
-                    <figure>
-                        <img src="address_mem.png" alt="Address Memory Graph"/>
-                        <figcaption>Memory occupation</figcaption>
-                    </figure>
-                </div>
-
-                <div class="horizontal_contents">
                     <div class="vertical_contents">
+                        <figure>
+                            <img src="address_io.png" alt="Address Network Graph"/>
+                            <figcaption>Network activity</figcaption>
+                        </figure>
                         <table>
                             <caption>Network Statistics</caption>
                             <tr>
@@ -172,11 +173,6 @@ <h3><code meld:id="state_mid" class="classState">RemoteStates</code> at <span me
                             </tr>
                         </table>
                     </div>
-
-                    <figure>
-                        <img src="address_io.png" alt="Address Network Graph"/>
-                        <figcaption>Network activity</figcaption>
-                    </figure>
                 </div>
             </div>
 
diff --git a/supvisors/ui/procaddress.html b/supvisors/ui/procaddress.html
index 5cd62ae6..d12ee6d0 100644
--- a/supvisors/ui/procaddress.html
+++ b/supvisors/ui/procaddress.html
@@ -103,14 +103,14 @@ <h3><code meld:id="state_mid" class="classState">RemoteStates</code> at <span me
                         <thead>
                             <tr>
                                 <th class="name">Name</th><th class="state">State</th><th class="desc">Description</th>
-                                <th class="load">Load</th><th class="cpu">CPU</th><th class="mem">MEM</th>
+                                <th class="load">Load</th><th class="mem">MEM</th><th class="cpu">CPU</th>
                                 <th colspan="3">Actions</th><th colspan="3">Log</th>
                             </tr>
                         </thead>
                         <tfoot>
                             <tr>
                                 <th>Name</th><th>State</th><th>Description</th>
-                                <th>Load</th><th>CPU</th><th>MEM</th>
+                                <th>Load</th><th>MEM</th><th>CPU</th>
                                 <th colspan="3">Actions</th><th colspan="3">Log</th>
                             </tr>
                         </tfoot>
@@ -120,8 +120,8 @@ <h3><code meld:id="state_mid" class="classState">RemoteStates</code> at <span me
                                 <td meld:id="state_td_mid">State</td>
                                 <td meld:id="desc_td_mid">Description</td>
                                 <td meld:id="load_td_mid">5%</td>
-                                <td><a href="#" meld:id="pcpu_a_mid">--</a></td>
                                 <td><a href="#" meld:id="pmem_a_mid">--</a></td>
+                                <td><a href="#" meld:id="pcpu_a_mid">--</a></td>
                                 <td meld:id="start_td_mid"><a href="#" meld:id="start_a_mid" class="button off">Start</a></td>
                                 <td meld:id="stop_td_mid"><a href="#" meld:id="stop_a_mid" class="button off">Stop</a></td>
                                 <td meld:id="restart_td_mid"><a href="#" meld:id="restart_a_mid" class="button off">Restart</a></td>
@@ -139,8 +139,8 @@ <h4>Detailed Statistics -- <span meld:id="process_h_mid">*</span></h4>
                     <div>
                         <table>
                             <tr>
-                                <th colspan="4">CPU</th>
                                 <th colspan="4">MEM</th>
+                                <th colspan="4">CPU</th>
                             </tr>
                             <tr>
                                 <th>%</th>
@@ -153,24 +153,24 @@ <h4>Detailed Statistics -- <span meld:id="process_h_mid">*</span></h4>
                                 <th>SD %</th>
                             </tr>
                             <tr class="brightened">
-                                <td meld:id="pcpuval_td_mid">--</td>
-                                <td meld:id="pcpuavg_td_mid">--</td>
-                                <td meld:id="pcpuslope_td_mid">--</td>
-                                <td meld:id="pcpudev_td_mid">--</td>
                                 <td meld:id="pmemval_td_mid">--</td>
                                 <td meld:id="pmemavg_td_mid">--</td>
                                 <td meld:id="pmemslope_td_mid">--</td>
                                 <td meld:id="pmemdev_td_mid">--</td>
+                                <td meld:id="pcpuval_td_mid">--</td>
+                                <td meld:id="pcpuavg_td_mid">--</td>
+                                <td meld:id="pcpuslope_td_mid">--</td>
+                                <td meld:id="pcpudev_td_mid">--</td>
                             </tr>
                         </table>
                     </div>
 
                     <figure>
-                        <img src="process_cpu.png" alt="Process CPU Graph"/>
+                        <img src="process_mem.png" alt="Process Memory Graph"/>
                     </figure>
 
                     <figure>
-                        <img src="process_mem.png" alt="Process Memory Graph"/>
+                        <img src="process_cpu.png" alt="Process CPU Graph"/>
                     </figure>
                 </div>
             </div>
diff --git a/supvisors/utils.py b/supvisors/utils.py
index 5bfc3d35..0c321d80 100644
--- a/supvisors/utils.py
+++ b/supvisors/utils.py
@@ -47,8 +47,7 @@ class EventHeaders:
 
 # for deferred XML-RPC requests
 class DeferredRequestHeaders:
-    """ Enumeration class for the headers of deferred XML-RPC messages
-    sent to MainLoop."""
+    """ Enumeration class for the headers of deferred XML-RPC messages sent to MainLoop."""
     CHECK_ADDRESS, ISOLATE_ADDRESSES, START_PROCESS, STOP_PROCESS, RESTART, SHUTDOWN = range(6)
 
 
@@ -144,8 +143,7 @@ def get_linear_regression(xdata, ydata):
         sum_xx = float(sum(map(lambda x: x * x, xdata)))
         sum_products = float(sum([xdata[i] * ydata[i]
                                   for i in range(datasize)]))
-        a = (sum_products - sum_x * sum_y / datasize) / (
-                sum_xx - (sum_x * sum_x) / datasize)
+        a = (sum_products - sum_x * sum_y / datasize) / (sum_xx - (sum_x * sum_x) / datasize)
         b = (sum_y - a * sum_x) / datasize
         return a, b
 
@@ -164,7 +162,7 @@ def get_stats(lst):
     - the mean value,
     - the instant rate between the two last values,
     - the coefficients of the linear regression,
-    - the standard deviation, """
+    - the standard deviation. """
     rate, a, b, dev = (None,) * 4
     # calculate mean value
     avg = mean(lst)
diff --git a/supvisors/version.txt b/supvisors/version.txt
index 1646df26..7d566bf6 100644
--- a/supvisors/version.txt
+++ b/supvisors/version.txt
@@ -1,1 +1,1 @@
-version=0.4
+version=0.5
diff --git a/supvisors/viewapplication.py b/supvisors/viewapplication.py
index 11e7b516..7d6baded 100644
--- a/supvisors/viewapplication.py
+++ b/supvisors/viewapplication.py
@@ -82,29 +82,15 @@ def write_header(self, root):
     def write_starting_strategy(self, root):
         """ Write applicable starting strategies. """
         # get the current strategy
-        strategy = self.supvisors.starter.strategy
+        strategy = self.view_ctx.parameters[STRATEGY]
         # set hyperlinks for strategy actions
-        # CONFIG strategy
-        elt = root.findmeld('config_a_mid')
-        if strategy == StartingStrategies.CONFIG:
-            elt.attrib['class'] = 'button off active'
-        else:
-            url = self.view_ctx.format_url('', self.page_name, **{ACTION: 'config'})
-            elt.attributes(href=url)
-        # MOST_LOADED strategy
-        elt = root.findmeld('most_a_mid')
-        if strategy == StartingStrategies.MOST_LOADED:
-            elt.attrib['class'] = 'button off active'
-        else:
-            url = self.view_ctx.format_url('', self.page_name, **{ACTION: 'most'})
-            elt.attributes(href=url)
-        # LESS_LOADED strategy
-        elt = root.findmeld('less_a_mid')
-        if strategy == StartingStrategies.LESS_LOADED:
-            elt.attrib['class'] = 'button off active'
-        else:
-            url = self.view_ctx.format_url('', self.page_name, **{ACTION: 'less'})
-            elt.attributes(href=url)
+        for str_strategy in StartingStrategies.strings():
+            elt = root.findmeld('%s_a_mid' % str_strategy.lower())
+            if strategy == str_strategy:
+                elt.attrib['class'] = 'button off active'
+            else:
+                url = self.view_ctx.format_url('', self.page_name, **{STRATEGY: str_strategy})
+                elt.attributes(href=url)
 
     def write_application_actions(self, root):
         """ Write actions related to the application. """
@@ -167,8 +153,7 @@ def write_process_table(self, root, data):
             iterator = root.findmeld('tr_mid').repeat(data)
             shaded_tr = False  # used to invert background style
             for tr_elt, info in iterator:
-                # write common status
-                # (shared between this application view and address view)
+                # write common status (shared between this application view and address view)
                 self.write_common_process_status(tr_elt, info)
                 # print process name and running addresses
                 self.write_process(tr_elt, info)
@@ -206,14 +191,8 @@ def make_callback(self, namespec, action):
         """ Triggers processing iaw action requested. """
         if action == 'refresh':
             return self.refresh_action()
-        if action == 'config':
-            return self.set_starting_strategy(StartingStrategies.CONFIG)
-        if action == 'most':
-            return self.set_starting_strategy(StartingStrategies.MOST_LOADED)
-        if action == 'less':
-            return self.set_starting_strategy(StartingStrategies.LESS_LOADED)
         # get current strategy
-        strategy = self.supvisors.starter.strategy
+        strategy = StartingStrategies.from_string(self.view_ctx.parameters[STRATEGY])
         if action == 'startapp':
             return self.start_application_action(strategy)
         if action == 'stopapp':
@@ -237,11 +216,6 @@ def refresh_action():
         """ Refresh web page. """
         return delayed_info('Page refreshed')
 
-    def set_starting_strategy(self, strategy):
-        """ Update starting strategy. """
-        self.supvisors.starter.strategy = strategy
-        return delayed_info('Starting strategy set to {}'.format(StartingStrategies.to_string(strategy)))
-
     # Common processing for starting and stopping actions
     def start_action(self, strategy, rpc_name, arg_name, arg_type):
         """ Start/Restart an application or a process iaw the strategy. """
diff --git a/supvisors/viewcontext.py b/supvisors/viewcontext.py
index b6b516bb..49dff35f 100644
--- a/supvisors/viewcontext.py
+++ b/supvisors/viewcontext.py
@@ -20,6 +20,7 @@
 from distutils.util import strtobool
 from urllib.parse import quote
 
+from supvisors.ttypes import StartingStrategies
 from supvisors.utils import supvisors_shortcuts
 from supvisors.webutils import error_message
 
@@ -32,6 +33,7 @@
 ADDRESS = 'address'
 
 PERIOD = 'period'
+STRATEGY = 'strategy'
 APPLI = 'appliname'
 PROCESS = 'processname'
 NAMESPEC = 'namespec'
@@ -64,9 +66,9 @@ def __init__(self, context):
         # initialize parameters
         self.parameters = {}
         # extract parameters from context
-        # WARN: period must be done before processname and cpuid
-        # as it requires to be set to access statistics
+        # WARN: period must be done before processname and cpuid as it requires to be set to access statistics
         self.update_period()
+        self.update_strategy()
         self.update_auto_refresh()
         self.update_address()
         self.update_application_name()
@@ -100,6 +102,11 @@ def update_period(self):
         default_value = next(iter(self.options.stats_periods))
         self._update_integer(PERIOD, self.options.stats_periods, default_value)
 
+    def update_strategy(self):
+        """ Extract starting strategy from context. """
+        self._update_string(STRATEGY, StartingStrategies.strings(),
+                            StartingStrategies.to_string(self.options.starting_strategy))
+
     def update_auto_refresh(self):
         """ Extract auto refresh from context. """
         # assign value found or default
@@ -213,51 +220,49 @@ def get_process_status(self, namespec=None):
 
     def _update_string(self, param, check_list, default_value=None):
         """ Extract information from context based on allowed values in check_list. """
-        value = self.http_context.form.get(param)
-        if value:
+        value = default_value
+        str_value = self.http_context.form.get(param)
+        if str_value:
             # check that value is known to Supvisors
-            if value in check_list:
-                # reset default_value
-                default_value = value
+            if str_value in check_list:
+                value = str_value
             else:
-                self.message(error_message('Incorrect {}: {}'.format(param, value)))
+                self.message(error_message('Incorrect {}: {}'.format(param, str_value)))
         # assign value found or default
-        self.logger.debug('{} set to {}'.format(param, default_value))
-        self.parameters[param] = default_value
+        self.logger.debug('{} set to {}'.format(param, value))
+        self.parameters[param] = value
 
     def _update_integer(self, param, check_list, default_value=0):
         """ Extract information from context and convert to integer based on allowed values in check_list. """
-        param_string = self.http_context.form.get(param)
-        if param_string:
+        value = default_value
+        str_value = self.http_context.form.get(param)
+        if str_value:
             try:
-                value = int(param_string)
+                int_value = int(str_value)
             except ValueError:
-                self.message(error_message('{} is not an integer: {}'.format(param, param_string)))
+                self.message(error_message('{} is not an integer: {}'.format(param, str_value)))
             else:
-                # check that value is defined in check list
-                if value in check_list:
-                    # reset default_value
-                    default_value = value
+                # check that int_value is defined in check list
+                if int_value in check_list:
+                    value = int_value
                 else:
-                    self.message(error_message('Incorrect {}: {}'.format(param, param_string)))
+                    self.message(error_message('Incorrect {}: {}'.format(param, int_value)))
         # assign value found or default
-        self.logger.debug('{} set to {}'.format(param, default_value))
-        self.parameters[param] = default_value
+        self.logger.debug('{} set to {}'.format(param, value))
+        self.parameters[param] = value
 
     def _update_boolean(self, param, default_value=False):
         """ Extract information from context and convert to boolean based on allowed values in check_list. """
-        param_string = self.http_context.form.get(param)
-        if param_string:
+        value = default_value
+        str_value = self.http_context.form.get(param)
+        if str_value:
             try:
-                value = strtobool(param_string)
+                value = strtobool(str_value)
             except ValueError:
-                self.message(error_message('{} is not a boolean-like: {}'.format(param, param_string)))
-            else:
-                # reset default_value
-                default_value = value
+                self.message(error_message('{} is not a boolean-like: {}'.format(param, str_value)))
         # assign value found or default
-        self.logger.debug('{} set to {}'.format(param, default_value))
-        self.parameters[param] = default_value
+        self.logger.debug('{} set to {}'.format(param, value))
+        self.parameters[param] = value
 
 
     @staticmethod
diff --git a/supvisors/viewhandler.py b/supvisors/viewhandler.py
index 7fcb8587..54cbc944 100644
--- a/supvisors/viewhandler.py
+++ b/supvisors/viewhandler.py
@@ -46,8 +46,7 @@ def test_matplotlib_import():
 
 
 class ViewHandler(MeldView):
-    """ Helper class to commonize rendering and behavior between handlers
-    inheriting from MeldView. """
+    """ Helper class to commonize rendering and behavior between handlers inheriting from MeldView. """
 
     def __init__(self, context):
         """ Initialization of the attributes. """
@@ -58,7 +57,7 @@ def __init__(self, context):
         self.supvisors = context.supervisord.supvisors
         supvisors_shortcuts(self, ['address_mapper', 'fsm', 'info_source',
                                    'logger', 'options', 'statistician'])
-        # cannot store context as it is or it would crush the http context
+        # cannot store context as it is named or it would crush the http context
         self.sup_ctx = self.supvisors.context
         # keep reference to the local address
         self.address = self.address_mapper.local_address
@@ -98,7 +97,7 @@ def render(self):
     def handle_parameters(self):
         """ Retrieve the parameters selected on the web page. """
         self.view_ctx = ViewContext(self.context)
-        self.logger.debug('New context: {}'. format(self.view_ctx.__dict__))
+        self.logger.debug('New context: {}'. format(self.view_ctx.parameters))
 
     def write_common(self, root):
         """ Common rendering of the Supvisors pages. """
@@ -158,14 +157,10 @@ def write_nav_addresses(self, root, address):
                 # set hyperlink attributes
                 elt = li_elt.findmeld('address_a_mid')
                 if status.state == AddressStates.RUNNING:
-                    # go to web page located on address,
-                    # so as to reuse Supervisor StatusView
+                    # go to web page located on address, so as to reuse Supervisor StatusView
                     url = self.view_ctx.format_url(item, PROC_ADDRESS_PAGE)
                     elt.attributes(href=url)
-                    elt.attrib['class'] = 'on' \
-                                          + (' master'
-                                             if item == self.sup_ctx.master_address
-                                             else '')
+                    elt.attrib['class'] = 'on' + (' master' if item == self.sup_ctx.master_address else '')
                 else:
                     elt.attrib['class'] = 'off'
                 elt.content(item)
@@ -184,8 +179,7 @@ def write_nav_applications(self, root, appli):
             if self.fsm.state == SupvisorsStates.INITIALIZATION:
                 elt.attrib['class'] = 'off'
             else:
-                parameters = {APPLI: item.application_name}
-                url = self.view_ctx.format_url('', APPLICATION_PAGE, **parameters)
+                url = self.view_ctx.format_url('', APPLICATION_PAGE, **{APPLI: item.application_name})
                 elt.attributes(href=url)
                 elt.attrib['class'] = 'on'
             elt.content(item.application_name)
@@ -204,8 +198,7 @@ def write_periods(self, root):
             if item == self.view_ctx.parameters[PERIOD]:
                 elt.attrib['class'] = 'button off active'
             else:
-                parameters = {PERIOD: item}
-                url = self.view_ctx.format_url('', self.page_name, **parameters)
+                url = self.view_ctx.format_url('', self.page_name, **{PERIOD: item})
                 elt.attributes(href=url)
             elt.content('{}s'.format(item))
 
@@ -437,6 +430,10 @@ def handle_action(self):
             if message is not None:
                 self.view_ctx.message(format_gravity_message(message))
 
+    def make_callback(self, namespec, action):
+        """ Triggers processing iaw action requested.
+        Subclasses will define what's to be done. """
+
     @staticmethod
     def set_slope_class(elt, value):
         """ Set attribute class iaw positive or negative slope. """
diff --git a/supvisors/viewsupvisors.py b/supvisors/viewsupvisors.py
index e9a93c53..1106e615 100644
--- a/supvisors/viewsupvisors.py
+++ b/supvisors/viewsupvisors.py
@@ -33,11 +33,11 @@
 class SupvisorsView(ViewHandler):
     """ Class ensuring the rendering of the Supvisors main page with:
 
-        * a navigation menu towards addresses contents and applications,
-        * the state of Supvisors,
-        * actions on Supvisors,
-        * a synoptic of the processes running on the different addresses,
-        * in CONCILIATION state only, the synoptic is replaced by a table of conflicts with tools to solve them.
+        - a navigation menu towards addresses contents and applications,
+        - the state of Supvisors,
+        - actions on Supvisors,
+        - a synoptic of the processes running on the different addresses,
+        - in CONCILIATION state only, the synoptic is replaced by a table of conflicts with tools to solve them.
     """
 
     def __init__(self, context):
@@ -88,10 +88,10 @@ def _write_node_box_title(self, node_div_elt, status):
         # set node name
         elt = node_div_elt.findmeld('node_tda_mid')
         if status.state == AddressStates.RUNNING:
-            # go to web page located on address, so as to reuse Supervisor StatusView
+            # go to web page located on address
             url = self.view_ctx.format_url(status.address_name, PROC_ADDRESS_PAGE)
             elt.attributes(href=url)
-            elt.attrib['class'] = 'on'
+            elt.attrib['class'] = 'on' + (' master' if status.address_name == self.sup_ctx.master_address else '')
         elt.content(status.address_name)
         # set state
         elt = node_div_elt.findmeld('state_td_mid')
@@ -101,7 +101,8 @@ def _write_node_box_title(self, node_div_elt, status):
         elt = node_div_elt.findmeld('percent_td_mid')
         elt.content('{}%'.format(status.loading()))
 
-    def _write_node_box_processes(self, node_div_elt, status):
+    @staticmethod
+    def _write_node_box_processes(node_div_elt, status):
         """ Rendering of the node box running processes. """
         appli_tr_mid = node_div_elt.findmeld('appli_tr_mid')
         running_processes = status.running_processes()
diff --git a/tox.ini b/tox.ini
index 98386c84..8672f99d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,7 +4,7 @@ envlist =
 
 [testenv]
 commands =
-    python setup.py test -q
+    pytest --cov=supvisors --cov-report=xml --cov-report=term-missing
 deps =
     supervisor >= 4.2.1
     pyzmq >= 20.0.0
@@ -12,16 +12,8 @@ deps =
     psutil >= 5.7.3
     matplotlib >= 3.3.3
     lxml >= 4.6.2
-
-[testenv:cover]
-basepython = python3.6
-commands =
-    python setup.py nosetests --with-xunit --with-xcoverage --cover-package=supvisors --cover-erase
-deps =
-    {[testenv]deps}
-    nose
-    coverage
-    nosexcover
+    pytest
+    pytest-cov
 
 [testenv:docs]
 deps =


Test patch:
diff --git a/supvisors/test/etc/import_database/program_copy_error.ini b/supvisors/test/etc/import_database/program_copy_error.ini
index 7a9a97a2..f0b86cd7 100644
--- a/supvisors/test/etc/import_database/program_copy_error.ini
+++ b/supvisors/test/etc/import_database/program_copy_error.ini
@@ -1,8 +1,8 @@
 [program:copy_error]
-command=python ./scripts/process_app.py -n %(program_name)s -x 1
+command=python ./scripts/process_app.py -n %(program_name)s -x 10
 autostart=false
 autorestart=false
-startsecs=4
+startsecs=15
 startretries=0
 redirect_stderr=true
 stdout_logfile=./log/%(program_name)s_%(host_node_name)s.log
diff --git a/supvisors/test/etc/import_database/program_mount_disk.ini b/supvisors/test/etc/import_database/program_mount_disk.ini
index 37413214..da5f54bd 100644
--- a/supvisors/test/etc/import_database/program_mount_disk.ini
+++ b/supvisors/test/etc/import_database/program_mount_disk.ini
@@ -1,5 +1,7 @@
 [program:mount_disk]
 command=python ./scripts/process_app.py -n %(program_name)s
+process_name=mount_disk_%(process_num)02d
+numprocs=2
 autostart=false
 autorestart=false
 redirect_stderr=true
diff --git a/supvisors/test/etc/my_movies.xml b/supvisors/test/etc/my_movies.xml
index ddcf270c..1c03336f 100644
--- a/supvisors/test/etc/my_movies.xml
+++ b/supvisors/test/etc/my_movies.xml
@@ -22,6 +22,11 @@
         <expected_loading>5</expected_loading>
     </model>
 
+    <model name="converter">
+        <addresses>*</addresses>
+        <expected_loading>25</expected_loading>
+    </model>
+
     <!-- starter checking application -->
     <application name="test">
         <start_sequence>1</start_sequence>
@@ -44,13 +49,13 @@
         <start_sequence>2</start_sequence>
         <starting_failure_strategy>STOP</starting_failure_strategy>
 
-        <program name="mount_disk">
-            <addresses>cliche81</addresses>
+        <pattern name="mount_disk_">
+            <addresses>#,cliche82,cliche83,cliche84</addresses>
             <start_sequence>1</start_sequence>
             <stop_sequence>2</stop_sequence>
             <required>true</required>
             <expected_loading>0</expected_loading>
-        </program>
+        </pattern>
 
         <program name="copy_error">
             <addresses>cliche81</addresses>
@@ -108,7 +113,7 @@
         </program>
 
         <program name="hmi">
-            <addresses>cliche82, cliche81</addresses>
+            <addresses>cliche82,cliche81</addresses>
             <start_sequence>3</start_sequence>
             <stop_sequence>1</stop_sequence>
             <expected_loading>10</expected_loading>
@@ -132,17 +137,17 @@
         </pattern>
 
         <program name="converter_04">
+            <reference>converter</reference>
             <addresses>cliche83,cliche81,cliche82</addresses>
-            <expected_loading>25</expected_loading>
         </program>
 
         <program name="converter_07">
+            <reference>converter</reference>
             <addresses>cliche81,cliche83,cliche82</addresses>
-            <expected_loading>25</expected_loading>
         </program>
 
         <pattern name="converter_">
-            <expected_loading>25</expected_loading>
+            <reference>converter</reference>
         </pattern>
 
      </application>
diff --git a/supvisors/test/etc/supervisord.conf b/supvisors/test/etc/supervisord.conf
index 2e8961f9..811a3c4c 100644
--- a/supvisors/test/etc/supervisord.conf
+++ b/supvisors/test/etc/supervisord.conf
@@ -28,7 +28,8 @@ rules_file=./etc/my_movies.xml
 auto_fence=false
 internal_port=60001
 event_port=60002
-synchro_timeout=10
+synchro_timeout=20
+force_synchro_if=cliche81,cliche82
 starting_strategy=CONFIG
 conciliation_strategy=USER
 stats_periods=5,60,600
@@ -38,6 +39,7 @@ logfile=AUTO
 ;logfile=./log/supvisors.log
 ;logfile_maxbytes=50MB
 ;logfile_backups=10
+;loglevel=debug
 
 [rpcinterface:supvisors]
 supervisor.rpcinterface_factory = supvisors.plugin:make_supvisors_rpcinterface
diff --git a/supvisors/test/scripts/check_conciliation_strategy.py b/supvisors/test/scripts/check_conciliation_strategy.py
index 1e691d0d..63fdf42e 100644
--- a/supvisors/test/scripts/check_conciliation_strategy.py
+++ b/supvisors/test/scripts/check_conciliation_strategy.py
@@ -32,8 +32,7 @@
 
 class ConciliationStrategyTest(RunningAddressesTest):
     """ Test case to check the conciliation of Supvisors.
-    The aim is to test user and auto conciliation, depending on the
-    configuration.
+    The aim is to test user and auto conciliation, depending on the configuration.
     """
 
     def setUp(self):
@@ -44,8 +43,7 @@ def setUp(self):
         # expected to be {'movie_server_01': ['cliche01'],
         #                 'movie_server_02': ['cliche03'],
         #                 'movie_server_03': ['cliche02']}
-        self.assertItemsEqual(self.running_processes.keys(),
-            ['movie_server_01', 'movie_server_02', 'movie_server_03'])
+        self.assertItemsEqual(self.running_processes.keys(), ['movie_server_01', 'movie_server_02', 'movie_server_03'])
         running_addresses = set()
         for addresses in self.running_processes.values():
             self.assertEqual(1, len(addresses))
@@ -66,7 +64,7 @@ def test_conciliation(self):
         """ Check depending on the configuration. """
         # test depends on configuration
         strategies = self.local_proxy.supvisors.get_strategies()
-        strategy = ConciliationStrategies._from_string(strategies['conciliation'])
+        strategy = ConciliationStrategies.from_string(strategies['conciliation'])
         if strategy == ConciliationStrategies.USER:
             print('### Testing USER conciliation')
             self._check_conciliation_user_manual()
diff --git a/supvisors/test/scripts/check_start_sequence.py b/supvisors/test/scripts/check_start_sequence.py
index 2c16c333..2a0091e5 100644
--- a/supvisors/test/scripts/check_start_sequence.py
+++ b/supvisors/test/scripts/check_start_sequence.py
@@ -62,13 +62,13 @@ def check_self_starting(self):
 
     def check_import_database_starting(self):
         """ Check the starting of the import_database application.
-        The mount_disk process is started first, then the copy_error
-        (failure expected).
+        The mount_disk process is started first, then the copy_error (failure expected).
         The application is configured to be stopped because of the failure. """
         # define 'import_database' application
         application = Application('import_database')
         self.context.add_application(application)
-        application.add_program(Program('mount_disk', True))
+        for program_name in ['mount_disk_00', 'mount_disk_01']:
+            application.add_program(Program(program_name, True))
         application.add_program(Program('copy_error', True))
         # check events
         self.check_mount_disk_starting()
@@ -77,31 +77,44 @@ def check_import_database_starting(self):
 
     def check_mount_disk_starting(self):
         """ Check the starting of the mount_disk program. """
+        config = [('mount_disk_00', self.HOST_02),
+                  ('mount_disk_01', self.HOST_03)]
         # define the expected events for the mount_disk program
-        program = self.context.get_program('import_database:mount_disk')
-        program.add_event(ProcessStateEvent(ProcessStates.STARTING, self.HOST_01))
-        program.add_event(ProcessStateEvent(ProcessStates.RUNNING, self.HOST_01))
+        application = self.context.get_application('import_database')
+        for program_name, address in config:
+            program = application.get_program(program_name)
+            if address in self.addresses:
+                program.add_event(ProcessStateEvent(ProcessStates.STARTING, address))
+                program.add_event(ProcessStateEvent(ProcessStates.RUNNING, address))
+            else:
+                program.add_event(ProcessStateEvent(ProcessStates.FATAL, address))
         # check that the events received correspond to the expected
-        self.check_events()
-        self.assertFalse(self.context.has_events())
+        self.check_events('import_database')
+        self.assertFalse(self.context.has_events('import_database'))
 
     def check_copy_error_starting(self):
         """ Check the starting of the copy_error program. """
         # define the expected events for the copy_error program
-        program = self.context.get_program('import_database:copy_error')
-        program.add_event(ProcessStateEvent(ProcessStates.STARTING, self.HOST_01))
-        program.add_event(ProcessStateEvent(ProcessStates.BACKOFF, self.HOST_01))
-        program.add_event(ProcessStateEvent(ProcessStates.FATAL))
-        # test the events received are compliant
-        self.check_events()
-        self.assertFalse(self.context.has_events())
+        if all(node in self.addresses for node in [self.HOST_02, self.HOST_03]):
+            program = self.context.get_program('import_database:copy_error')
+            program.add_event(ProcessStateEvent(ProcessStates.STARTING, self.HOST_01))
+            program.add_event(ProcessStateEvent(ProcessStates.BACKOFF, self.HOST_01))
+            program.add_event(ProcessStateEvent(ProcessStates.FATAL))
+            # test the events received are compliant
+            self.check_events()
+            self.assertFalse(self.context.has_events())
 
     def check_mount_disk_stopping(self):
         """ Program the stopping of the program.state program. """
+        config = [('mount_disk_00', self.HOST_02),
+                  ('mount_disk_01', self.HOST_03)]
         # define the expected events for the mount_disk program
-        program = self.context.get_program('import_database:mount_disk')
-        program.add_event(ProcessStateEvent(ProcessStates.STOPPING, self.HOST_01))
-        program.add_event(ProcessStateEvent(ProcessStates.STOPPED))
+        application = self.context.get_application('import_database')
+        for program_name, address in config:
+            if address in self.addresses:
+                program = application.get_program(program_name)
+                program.add_event(ProcessStateEvent(ProcessStates.STOPPING, address))
+                program.add_event(ProcessStateEvent(ProcessStates.STOPPED, address))
         # do NOT check the events received at this stage
         # stopping events will be mixed with the starting events
         # of the following applications
@@ -114,13 +127,9 @@ def check_database_starting(self):
         # define 'import_database' application
         application = Application('database')
         self.context.add_application(application)
-        for program_name in ['movie_server_01',
-                             'movie_server_02',
-                             'movie_server_03']:
+        for program_name in ['movie_server_01', 'movie_server_02', 'movie_server_03']:
             application.add_program(Program(program_name))
-        for program_name in ['register_movies_01',
-                             'register_movies_02',
-                             'register_movies_03']:
+        for program_name in ['register_movies_01', 'register_movies_02', 'register_movies_03']:
             application.add_program(Program(program_name, False, True))
         # check events
         self.check_movie_server_starting()
diff --git a/supvisors/test/scripts/check_starting_strategy.py b/supvisors/test/scripts/check_starting_strategy.py
index 7abe4520..296cf81b 100644
--- a/supvisors/test/scripts/check_starting_strategy.py
+++ b/supvisors/test/scripts/check_starting_strategy.py
@@ -37,19 +37,18 @@ def setUp(self):
         """ Get initial status. """
         RunningAddressesTest.setUp(self)
         # check the loading on running addresses
+        # initial state is cliche81=15% cliche82=15% cliche83=5%
         self._refresh_loading()
         self.assertItemsEqual([15, 15, 5], self.loading.values())
         # check that 10 converter programs are STOPPED
         processes_info = self.local_supvisors.get_process_info('my_movies:*')
         converters = [info for info in processes_info
-                      if info['process_name'].startswith('converter') and
-                      info['statecode'] in STOPPED_STATES]
+                      if info['process_name'].startswith('converter') and info['statecode'] in STOPPED_STATES]
         self.assertEqual(10, len(converters))
         # check that 10 converter programs are configured with loading 25
         processes_rules = self.local_supvisors.get_process_rules('my_movies:*')
         converters = [rules for rules in processes_rules
-                      if rules['process_name'].startswith('converter') and
-                      rules['expected_loading'] == 25]
+                      if rules['process_name'].startswith('converter') and rules['expected_loading'] == 25]
         self.assertEqual(10, len(converters))
 
     def tearDown(self):
@@ -59,7 +58,7 @@ def tearDown(self):
             try:
                 program = 'my_movies:converter_0%d' % idx
                 self.local_supvisors.stop_process(program)
-            except:
+            except Exception:
                 pass
         # call parent
         RunningAddressesTest.tearDown(self)
@@ -89,12 +88,13 @@ def _start_converter(self, idx):
 
     def _start_converter_failed(self, idx):
         """ Get the current loading status. """
-        self.local_supvisors.start_process(self.strategy, 'my_movies:converter_0%d' % idx)
+        with self.assertRaises(xmlrpclib.Fault) as exc:
+            self.local_supvisors.start_process(self.strategy, 'my_movies:converter_0%d' % idx)
+        self.assertEqual(Faults.ABNORMAL_TERMINATION, exc.exception.faultCode)
+        self.assertEqual('ABNORMAL_TERMINATION: my_movies:converter_0%d' % idx, exc.exception.faultString)
         # wait for event FATAL
         event = self._get_next_process_event()
-        self.assertDictContainsSubset({'group': 'my_movies',
-                                       'name': 'converter_0%d' % idx,
-                                       'state': 200}, event)
+        self.assertDictContainsSubset({'group': 'my_movies', 'name': 'converter_0%d' % idx, 'state': 200}, event)
         # refresh the address loadings
         self._refresh_loading()
 
@@ -107,7 +107,6 @@ def test_config(self):
         # no address config for almost all converters (excepted 04 and 07)
         # so applicable order is the one defined in the supvisors section,
         # i.e. cliche81, cliche82, cliche83, cliche84 (not running)
-        # cliche81 is at 15% initially
         self._start_converter(0)
         self.assertItemsEqual([40, 15, 5], self.loading.values())
         # continue with cliche81
@@ -194,6 +193,22 @@ def test_most_loaded(self):
         self._start_converter_failed(9)
         self.assertItemsEqual([90, 90, 80], self.loading.values())
 
+    def test_local(self):
+        """ Test the LOCAL starting strategy.
+        Start converters and check they have been started on the address having the highest loading. """
+        print('### Testing LOCAL starting strategy')
+        self.strategy = StartingStrategies.LOCAL
+        # this test should be started only from cliche81 so processes should be started only on cliche81
+        self._start_converter(0)
+        self.assertItemsEqual([40, 15, 5], self.loading.values())
+        self._start_converter(1)
+        self.assertItemsEqual([65, 15, 5], self.loading.values())
+        self._start_converter(2)
+        self.assertItemsEqual([90, 15, 5], self.loading.values())
+        # next converter cannot be started: no resource left
+        self._start_converter_failed(3)
+        self.assertItemsEqual([90, 15, 5], self.loading.values())
+
 
 def test_suite():
     return unittest.findTestCases(sys.modules[__name__])
@@ -202,9 +217,8 @@ def test_suite():
 if __name__ == '__main__':
     # get arguments
     import argparse
-
     parser = argparse.ArgumentParser(description='Check the Supvisors starting strategies.')
-    parser.add_argument('-p', '--port', type=int, default=60002, help="the event port of Supvisors")
+    parser.add_argument('-p', '--port', type=int, default=60002, help='the event port of Supvisors')
     args = parser.parse_args()
     SupvisorsEventQueues.PORT = args.port
     # start unittest
diff --git a/supvisors/test/scripts/running_addresses.py b/supvisors/test/scripts/running_addresses.py
index fe25d415..b2e3baca 100644
--- a/supvisors/test/scripts/running_addresses.py
+++ b/supvisors/test/scripts/running_addresses.py
@@ -63,11 +63,13 @@ def setUp(self):
         self.evloop = SupvisorsEventQueues(self.zcontext, self.logger)
         # start the thread
         self.evloop.start()
+        self.logger.info('Event loop created')
 
     def tearDown(self):
         """ The tearDown stops the subscriber to the Supvisors events. """
         self.evloop.stop()
         self.evloop.join()
+        self.logger.info('Event loop ended')
         # close resources
         self.logger.close()
         self.zcontext.term()
diff --git a/supvisors/test/scripts/sequence_checker.py b/supvisors/test/scripts/sequence_checker.py
index 1a8863c8..48aadd49 100644
--- a/supvisors/test/scripts/sequence_checker.py
+++ b/supvisors/test/scripts/sequence_checker.py
@@ -171,8 +171,7 @@ def get_program(self, namespec):
         return self.get_application(application_name).get_program(process_name)
 
     def has_events(self, application_name=None):
-        """ Return True if the programs of the application contain events
-        not received yet. """
+        """ Return True if the programs of the application contain events not received yet. """
         application_list = [self.get_application(application_name)] \
             if application_name else self.applications.values()
         for application in application_list:
@@ -292,8 +291,7 @@ def check_process_event(self, event):
         self.assertEqual(state_event.statename, event['statename'])
         self.assertEqual(state_event.statecode, event['statecode'])
         # check the running address
-        if state_event.statecode in [ProcessStates.STOPPING] \
-                + list(RUNNING_STATES):
+        if state_event.statecode in [ProcessStates.STOPPING] + list(RUNNING_STATES):
             self.assertListEqual([state_event.address], event['addresses'])
             program.address = state_event.address
         # update program state
diff --git a/supvisors/tests/__init__.py b/supvisors/tests/__init__.py
index e69de29b..de40ea7c 100644
--- a/supvisors/tests/__init__.py
+++ b/supvisors/tests/__init__.py
@@ -0,0 +1,1 @@
+__import__('pkg_resources').declare_namespace(__name__)
diff --git a/supvisors/tests/base.py b/supvisors/tests/base.py
index 074e6c82..b47c16f7 100644
--- a/supvisors/tests/base.py
+++ b/supvisors/tests/base.py
@@ -56,6 +56,7 @@ def __init__(self):
         self.internal_port = 65100
         self.event_port = 65200
         self.synchro_timeout = 10
+        self.force_synchro_if = []
         self.auto_fence = True
         self.rules_file = ''
         self.starting_strategy = 0
@@ -135,8 +136,7 @@ def create_supvisors(arg):
             assert(arg == supervisord)
             return MockedSupvisors()
 
-        with patch('supvisors.rpcinterface.Supvisors',
-                   side_effect=create_supvisors):
+        with patch('supvisors.rpcinterface.Supvisors', side_effect=create_supvisors):
             self.supvisors = RPCInterface(supervisord)
 
 
@@ -163,7 +163,7 @@ def __init__(self):
         self.mood = 'mood'
         self.nodaemon = True
         self.silent = False
-        self.logger = Mock(spec=Logger)
+        self.logger = Mock(handlers=[Mock()])
         # build a fake http config
         self.httpservers = [[None, DummyHttpServer()]]
         self.httpserver = self.httpservers[0][1]
diff --git a/supvisors/tests/configurations.py b/supvisors/tests/configurations.py
index 0eaf3036..21355690 100644
--- a/supvisors/tests/configurations.py
+++ b/supvisors/tests/configurations.py
@@ -76,6 +76,7 @@
 internal_port=60001
 event_port=60002
 synchro_timeout=20
+force_synchro_if=cliche01,cliche03
 starting_strategy=MOST_LOADED
 conciliation_strategy=SENICIDE
 stats_periods=5,60,600
@@ -112,7 +113,7 @@
         <expected_loading>10</expected_loading>
     </model>
 
-     <model name="dummy_model_04">
+    <model name="dummy_model_04">
         <reference>dummy_model_01</reference>
     </model>
 
@@ -200,7 +201,6 @@
             <reference>dummy_model_03</reference>
             <addresses>#</addresses>
             <start_sequence>3</start_sequence>
-            <stop_sequence>50</stop_sequence>
             <required>true</required>
             <wait_exit>false</wait_exit>
             <expected_loading>5</expected_loading>
@@ -261,8 +261,14 @@
         <expected_loading>10</expected_loading>
     </model>
 
-     <model name="dummy_model_04">
+    <model name="dummy_model_04">
         <reference>dummy_model_01</reference>
+        <expected_loading>20</expected_loading>
+    </model>
+
+    <model name="dummy_model_05">
+        <reference>dummy_model_04</reference>
+        <expected_loading>15</expected_loading>
     </model>
 
     <application name="dummy_application_A">
@@ -339,10 +345,11 @@
 
         <pattern name="dummies_">
             <reference>dummy_model_03</reference>
+            <start_sequence>50</start_sequence>
         </pattern>
 
         <pattern name="dummies_01_">
-            <addresses>#</addresses>
+            <addresses>#, 10.0.0.1, 10.0.0.5</addresses>
             <start_sequence>1</start_sequence>
             <stop_sequence>1</stop_sequence>
             <required>false</required>
@@ -356,5 +363,11 @@
 
     </application>
 
+    <application name="dummy_application_E">
+        <program name="dummy_program_E">
+            <reference>dummy_model_05</reference>
+        </program>
+    </application>
+
 </root>
 '''
diff --git a/supvisors/tests/test_addressmapper.py b/supvisors/tests/test_addressmapper.py
index 071e4803..e5d758e0 100644
--- a/supvisors/tests/test_addressmapper.py
+++ b/supvisors/tests/test_addressmapper.py
@@ -92,7 +92,7 @@ def test_filter(self):
         # test that the same list with a different sequence is not filtered
         shuffle_lst1 = address_lst[:]
         random.shuffle(shuffle_lst1)
-        self.assertListEqual(shuffle_lst1, mapper.filter(shuffle_lst1))
+        self.assertEqual(shuffle_lst1, mapper.filter(shuffle_lst1))
         # test that an subset of the sequence is not filtered
         shuffle_lst2 = shuffle_lst1[:]
         shuffle_lst2.pop()
diff --git a/supvisors/tests/test_commander.py b/supvisors/tests/test_commander.py
index 97ece445..9ae749a6 100644
--- a/supvisors/tests/test_commander.py
+++ b/supvisors/tests/test_commander.py
@@ -32,9 +32,19 @@ class ProcessCommandTest(CompatTestCase):
     def test_creation(self):
         """ Test the values set at construction. """
         from supvisors.commander import ProcessCommand
+        from supvisors.ttypes import StartingStrategies
         process = 'a process'
+        # test default strategy
         command = ProcessCommand(process)
         self.assertIs(process, command.process)
+        self.assertEqual(None, command.strategy)
+        self.assertEqual(0, command.request_time)
+        self.assertFalse(command.ignore_wait_exit)
+        self.assertEqual('', command.extra_args)
+        # test strategy in parameter
+        command = ProcessCommand(process, StartingStrategies.MOST_LOADED)
+        self.assertIs(process, command.process)
+        self.assertEqual(StartingStrategies.MOST_LOADED, command.strategy)
         self.assertEqual(0, command.request_time)
         self.assertFalse(command.ignore_wait_exit)
         self.assertEqual('', command.extra_args)
@@ -42,110 +52,122 @@ def test_creation(self):
     def test_str(self):
         """ Test the output string of the ProcessCommand. """
         from supvisors.commander import ProcessCommand
+        from supvisors.ttypes import StartingStrategies
         process = Mock(state='RUNNING', last_event_time=1234,
                        **{'namespec.return_value': 'proc_1'})
-        command = ProcessCommand(process)
+        command = ProcessCommand(process, StartingStrategies.CONFIG)
         command.request_time = 4321
         command.ignore_wait_exit = True
         command.extra_args = '-s test args'
         self.assertEqual('process=proc_1 state=RUNNING last_event_time=1234 '
-                         'request_time=4321 ignore_wait_exit=True '
+                         'strategy=0 request_time=4321 ignore_wait_exit=True '
                          'extra_args="-s test args"', str(command))
 
-    def test_timeout(self):
-        """ Test the timeout method. """
+    def test_timed_out(self):
+        """ Test the timed_out method. """
         from supvisors.commander import ProcessCommand
         command = ProcessCommand(Mock(last_event_time=100))
         command.request_time = 95
-        self.assertFalse(command.timeout(102))
+        self.assertFalse(command.timed_out(102))
         command.request_time = 101
-        self.assertFalse(command.timeout(102))
-        self.assertTrue(command.timeout(107))
+        self.assertFalse(command.timed_out(108))
+        self.assertTrue(command.timed_out(112))
         command.request_time = 99
-        self.assertTrue(command.timeout(106))
+        self.assertTrue(command.timed_out(111))
 
 
-def _create_process_command(group, name, supvisors):
-    """ Create a ProcessCommand from process info. """
-    from supvisors.commander import ProcessCommand
-    from supvisors.process import ProcessStatus
-    process = ProcessStatus(group, name, supvisors)
-    return ProcessCommand(process)
+class CommanderContextTest(CompatTestCase):
 
+    def setUp(self):
+        """ Create a Supvisors-like structure and test processes. """
+        self.supvisors = MockedSupvisors()
+        # store list for tests
+        self.command_list = []
+        for info in database_copy():
+            command = self._create_process_command(info['group'], info['name'])
+            command.process.add_info('10.0.0.1', info)
+            self.command_list.append(command)
 
-class CommanderTest(CompatTestCase):
+    def _create_process_command(self, group, name):
+        """ Create a ProcessCommand from process info. """
+        from supvisors.commander import ProcessCommand
+        from supvisors.process import ProcessStatus
+        process = ProcessStatus(group, name, self.supvisors)
+        return ProcessCommand(process)
+
+    def _get_test_command(self, process_name):
+        """ Return the first process corresponding to process_name. """
+        return next(command for command in self.command_list
+                    if command.process.process_name == process_name)
+
+
+class CommanderTest(CommanderContextTest):
     """ Test case for the Commander class of the commander module. """
 
     def setUp(self):
         """ Create a Supvisors-like structure and test processes. """
-        from supvisors.process import ProcessStatus
-        self.supvisors = MockedSupvisors()
+        CommanderContextTest.setUp(self)
+        # create the instance to test
+        from supvisors.commander import Commander
+        self.commander = Commander(self.supvisors)
         # store lists for tests
-        self.command_list_1 = [
-            _create_process_command('appli_A', 'dummy_A1', self.supvisors),
-            _create_process_command('appli_A', 'dummy_A2', self.supvisors),
-            _create_process_command('appli_A', 'dummy_A3', self.supvisors)]
-        self.command_list_2 = [
-            _create_process_command('appli_B', 'dummy_B1', self.supvisors)]
+        self.command_list_1 = [self._create_process_command('appli_A', 'dummy_A1'),
+                               self._create_process_command('appli_A', 'dummy_A2'),
+                               self._create_process_command('appli_A', 'dummy_A3')]
+        self.command_list_2 = [self._create_process_command('appli_B', 'dummy_B1')]
 
     def test_creation(self):
         """ Test the values set at construction. """
-        from supvisors.commander import Commander
-        commander = Commander(self.supvisors)
-        self.assertIs(self.supvisors, commander.supvisors)
-        self.assertIs(self.supvisors.logger, commander.logger)
-        self.assertDictEqual({}, commander.planned_sequence)
-        self.assertDictEqual({}, commander.planned_jobs)
-        self.assertDictEqual({}, commander.current_jobs)
+        self.assertIs(self.supvisors, self.commander.supvisors)
+        self.assertIs(self.supvisors.logger, self.commander.logger)
+        self.assertDictEqual({}, self.commander.planned_sequence)
+        self.assertDictEqual({}, self.commander.planned_jobs)
+        self.assertDictEqual({}, self.commander.current_jobs)
 
     def test_in_progress(self):
         """ Test the in_progress method. """
-        from supvisors.commander import Commander
-        commander = Commander(self.supvisors)
-        self.assertFalse(commander.in_progress())
-        commander.planned_sequence = {0: {'if': {0: self.command_list_1}}}
-        self.assertTrue(commander.in_progress())
-        commander.planned_jobs = {'then': {1: self.command_list_2}}
-        self.assertTrue(commander.in_progress())
-        commander.current_jobs = {'else': []}
-        self.assertTrue(commander.in_progress())
-        commander.planned_sequence = {}
-        self.assertTrue(commander.in_progress())
-        commander.planned_jobs = {}
-        self.assertTrue(commander.in_progress())
-        commander.current_jobs = {}
-        self.assertFalse(commander.in_progress())
+        self.assertFalse(self.commander.in_progress())
+        self.commander.planned_sequence = {0: {'if': {0: self.command_list_1}}}
+        self.assertTrue(self.commander.in_progress())
+        self.commander.planned_jobs = {'then': {1: self.command_list_2}}
+        self.assertTrue(self.commander.in_progress())
+        self.commander.current_jobs = {'else': []}
+        self.assertTrue(self.commander.in_progress())
+        self.commander.planned_sequence = {}
+        self.assertTrue(self.commander.in_progress())
+        self.commander.planned_jobs = {}
+        self.assertTrue(self.commander.in_progress())
+        self.commander.current_jobs = {}
+        self.assertFalse(self.commander.in_progress())
 
     def test_has_application(self):
         """ Test the has_application method. """
-        from supvisors.commander import Commander
-        commander = Commander(self.supvisors)
-        self.assertFalse(commander.has_application('if'))
-        self.assertFalse(commander.has_application('then'))
-        self.assertFalse(commander.has_application('else'))
-        commander.planned_sequence = {0: {'if': {0: self.command_list_1}}}
-        self.assertTrue(commander.has_application('if'))
-        self.assertFalse(commander.has_application('then'))
-        commander.planned_jobs = {'then': {1: self.command_list_2}}
-        self.assertTrue(commander.has_application('if'))
-        self.assertTrue(commander.has_application('then'))
-        self.assertFalse(commander.has_application('else'))
-        commander.current_jobs = {'else': []}
-        self.assertTrue(commander.has_application('if'))
-        self.assertTrue(commander.has_application('then'))
-        self.assertTrue(commander.has_application('else'))
-        commander.planned_sequence = {}
-        self.assertFalse(commander.has_application('if'))
-        self.assertTrue(commander.has_application('then'))
-        self.assertTrue(commander.has_application('else'))
-        commander.planned_jobs = {}
-        self.assertFalse(commander.has_application('if'))
-        self.assertFalse(commander.has_application('then'))
-        self.assertTrue(commander.has_application('else'))
-        commander.current_jobs = {}
-        self.assertFalse(commander.has_application('if'))
-        self.assertFalse(commander.has_application('then'))
-        self.assertFalse(commander.has_application('else'))
+        self.assertFalse(self.commander.has_application('if'))
+        self.assertFalse(self.commander.has_application('then'))
+        self.assertFalse(self.commander.has_application('else'))
+        self.commander.planned_sequence = {0: {'if': {0: self.command_list_1}}}
+        self.assertTrue(self.commander.has_application('if'))
+        self.assertFalse(self.commander.has_application('then'))
+        self.commander.planned_jobs = {'then': {1: self.command_list_2}}
+        self.assertTrue(self.commander.has_application('if'))
+        self.assertTrue(self.commander.has_application('then'))
+        self.assertFalse(self.commander.has_application('else'))
+        self.commander.current_jobs = {'else': []}
+        self.assertTrue(self.commander.has_application('if'))
+        self.assertTrue(self.commander.has_application('then'))
+        self.assertTrue(self.commander.has_application('else'))
+        self.commander.planned_sequence = {}
+        self.assertFalse(self.commander.has_application('if'))
+        self.assertTrue(self.commander.has_application('then'))
+        self.assertTrue(self.commander.has_application('else'))
+        self.commander.planned_jobs = {}
+        self.assertFalse(self.commander.has_application('if'))
+        self.assertFalse(self.commander.has_application('then'))
+        self.assertTrue(self.commander.has_application('else'))
+        self.commander.current_jobs = {}
+        self.assertFalse(self.commander.has_application('if'))
+        self.assertFalse(self.commander.has_application('then'))
+        self.assertFalse(self.commander.has_application('else'))
 
     def test_printable_command_list(self):
         """ Test the printable_process_list method. """
@@ -158,42 +180,34 @@ def test_printable_command_list(self):
         self.assertListEqual(['appli_B:dummy_B1'], printable)
         # test with list having multiple elements
         printable = Commander.printable_command_list(self.command_list_1)
-        self.assertListEqual(['appli_A:dummy_A1',
-                              'appli_A:dummy_A2',
-                              'appli_A:dummy_A3'], printable)
+        self.assertListEqual(['appli_A:dummy_A1', 'appli_A:dummy_A2', 'appli_A:dummy_A3'], printable)
 
     def test_printable_current_jobs(self):
         """ Test the printable_current_jobs method. """
-        from supvisors.commander import Commander
-        commander = Commander(self.supvisors)
         # test with empty structure
-        commander.current_jobs = {}
-        printable = commander.printable_current_jobs()
+        self.commander.current_jobs = {}
+        printable = self.commander.printable_current_jobs()
         self.assertDictEqual({}, printable)
         # test with complex structure
-        commander.current_jobs = {'if': [],
-                                  'then': self.command_list_1,
-                                  'else': self.command_list_2}
-        printable = commander.printable_current_jobs()
+        self.commander.current_jobs = {'if': [],
+                                       'then': self.command_list_1,
+                                       'else': self.command_list_2}
+        printable = self.commander.printable_current_jobs()
         self.assertDictEqual({'if': [],
-                              'then': ['appli_A:dummy_A1',
-                                       'appli_A:dummy_A2',
-                                       'appli_A:dummy_A3'],
+                              'then': ['appli_A:dummy_A1', 'appli_A:dummy_A2', 'appli_A:dummy_A3'],
                               'else': ['appli_B:dummy_B1']}, printable)
 
     def test_printable_planned_jobs(self):
         """ Test the printable_planned_jobs method. """
-        from supvisors.commander import Commander
-        commander = Commander(self.supvisors)
         # test with empty structure
-        commander.planned_jobs = {}
-        printable = commander.printable_planned_jobs()
+        self.commander.planned_jobs = {}
+        printable = self.commander.printable_planned_jobs()
         self.assertDictEqual({}, printable)
         # test with complex structure
-        commander.planned_jobs = {'if': {0: self.command_list_1, 1: []},
-                                  'then': {2: self.command_list_2},
-                                  'else': {}}
-        printable = commander.printable_planned_jobs()
+        self.commander.planned_jobs = {'if': {0: self.command_list_1, 1: []},
+                                       'then': {2: self.command_list_2},
+                                       'else': {}}
+        printable = self.commander.printable_planned_jobs()
         self.assertDictEqual({'if': {0: ['appli_A:dummy_A1',
                                          'appli_A:dummy_A2',
                                          'appli_A:dummy_A3'], 1: []},
@@ -201,568 +215,582 @@ def test_printable_planned_jobs(self):
 
     def test_printable_planned_sequence(self):
         """ Test the printable_planned_sequence method. """
-        from supvisors.commander import Commander
-        commander = Commander(self.supvisors)
         # test with empty structure
-        commander.planned_sequence = {}
-        printable = commander.printable_planned_sequence()
+        self.commander.planned_sequence = {}
+        printable = self.commander.printable_planned_sequence()
         self.assertDictEqual({}, printable)
         # test with complex structure
-        commander.planned_sequence = {0: {'if': {-1: [],
-                                                 0: self.command_list_1},
-                                          'then': {2: self.command_list_2}}, 3: {'else': {}}}
-        printable = commander.printable_planned_sequence()
-        self.assertDictEqual({0: {'if': {-1: [],
-                                         0: ['appli_A:dummy_A1',
-                                             'appli_A:dummy_A2',
-                                             'appli_A:dummy_A3']},
-                                  'then': {2: ['appli_B:dummy_B1']}}, 3: {'else': {}}}, printable)
+        self.commander.planned_sequence = {0: {'if': {-1: [], 0: self.command_list_1},
+                                               'then': {2: self.command_list_2}},
+                                           3: {'else': {}}}
+        printable = self.commander.printable_planned_sequence()
+        self.assertDictEqual({0: {'if': {-1: [], 0: ['appli_A:dummy_A1', 'appli_A:dummy_A2', 'appli_A:dummy_A3']},
+                                  'then': {2: ['appli_B:dummy_B1']}},
+                              3: {'else': {}}}, printable)
 
     def test_process_application_jobs(self):
         """ Test the process_application_jobs method. """
-        from supvisors.commander import Commander
-        commander = Commander(self.supvisors)
         # fill planned_jobs
-        commander.planned_jobs = {'if': {0: self.command_list_1, 1: []},
-                                  'then': {2: self.command_list_2},
-                                  'else': {}}
+        self.commander.planned_jobs = {'if': {0: self.command_list_1, 1: []},
+                                       'then': {2: self.command_list_2},
+                                       'else': {}}
 
         # define patch function
         def fill_jobs(*args, **kwargs):
             args[1].append(args[0])
 
-        with patch.object(commander, 'process_job',
-                          side_effect=fill_jobs) as mocked_job:
+        with patch.object(self.commander, 'process_job', side_effect=fill_jobs) as mocked_job:
             # test with unknown application
-            commander.process_application_jobs('while')
-            self.assertDictEqual({}, commander.current_jobs)
+            self.commander.process_application_jobs('while')
+            self.assertDictEqual({}, self.commander.current_jobs)
             self.assertDictEqual({'if': {0: self.command_list_1, 1: []},
                                   'then': {2: self.command_list_2},
                                   'else': {}},
-                                 commander.planned_jobs)
+                                 self.commander.planned_jobs)
             self.assertEqual(0, mocked_job.call_count)
             # test with known application: sequence 0 of 'if' application is popped
-            commander.process_application_jobs('if')
+            self.commander.process_application_jobs('if')
             self.assertDictEqual({'if': {1: []},
                                   'then': {2: self.command_list_2},
-                                  'else': {}}, commander.planned_jobs)
-            self.assertDictEqual({'if': self.command_list_1},
-                                 commander.current_jobs)
+                                  'else': {}}, self.commander.planned_jobs)
+            self.assertDictEqual({'if': self.command_list_1}, self.commander.current_jobs)
             self.assertEqual(3, mocked_job.call_count)
             # test with known application: sequence 1 of 'if' application is popped
             mocked_job.reset_mock()
-            commander.process_application_jobs('if')
-            self.assertDictEqual({'then': {2: self.command_list_2},
-                                  'else': {}}, commander.planned_jobs)
-            self.assertDictEqual({}, commander.current_jobs)
+            self.commander.process_application_jobs('if')
+            self.assertDictEqual({'then': {2: self.command_list_2}, 'else': {}},
+                                 self.commander.planned_jobs)
+            self.assertDictEqual({}, self.commander.current_jobs)
             self.assertEqual(0, mocked_job.call_count)
         # test that process_job method must be implemented
         with self.assertRaises(NotImplementedError):
-            commander.process_application_jobs('then')
-        self.assertDictEqual({'then': {}, 'else': {}}, commander.planned_jobs)
-        self.assertDictEqual({'then': []}, commander.current_jobs)
+            self.commander.process_application_jobs('then')
+        self.assertDictEqual({'then': {}, 'else': {}}, self.commander.planned_jobs)
+        self.assertDictEqual({'then': []}, self.commander.current_jobs)
 
-    def test_initial_jobs(self):
-        """ Test the initial_jobs method. """
-        from supvisors.commander import Commander
-        commander = Commander(self.supvisors)
+    def test_trigger_jobs(self):
+        """ Test the trigger_jobs method. """
         # test with empty structure
-        commander.planned_sequence = {}
-        commander.initial_jobs()
-        self.assertDictEqual({}, commander.planned_jobs)
+        self.commander.planned_sequence = {}
+        self.commander.trigger_jobs()
+        self.assertDictEqual({}, self.commander.planned_jobs)
         # test with complex structure
-        commander.planned_sequence = {
-            0: {'if': {2: [], 0: self.command_list_1},
-                'then': {2: self.command_list_2}}, 3: {'else': {}}}
+        self.commander.planned_sequence = {0: {'if': {2: [], 0: self.command_list_1},
+                                               'then': {2: self.command_list_2}},
+                                           3: {'else': {}}}
 
         # define patch function
         def fill_jobs(*args, **kwargs):
             args[1].append(args[0])
 
-        with patch.object(commander, 'process_job',
-                          side_effect=fill_jobs) as mocked_job:
-            commander.initial_jobs()
+        with patch.object(self.commander, 'process_job', side_effect=fill_jobs) as mocked_job:
+            self.commander.trigger_jobs()
             # test impact on internal attributes
-            self.assertDictEqual({3: {'else': {}}}, commander.planned_sequence)
-            self.assertDictEqual({'if': {2: []}}, commander.planned_jobs)
-            self.assertDictEqual({'if': self.command_list_1,
-                                  'then': self.command_list_2},
-                                 commander.current_jobs)
+            self.assertDictEqual({3: {'else': {}}}, self.commander.planned_sequence)
+            self.assertDictEqual({'if': {2: []}}, self.commander.planned_jobs)
+            self.assertDictEqual({'if': self.command_list_1, 'then': self.command_list_2},
+                                 self.commander.current_jobs)
             self.assertEqual(4, mocked_job.call_count)
 
-
-class StarterTest(CompatTestCase):
+    @patch('supvisors.commander.Commander.trigger_jobs')
+    @patch('supvisors.commander.Commander.force_process_state')
+    @patch('supvisors.commander.Commander.after_event')
+    def test_check_progress(self, mocked_after: Mock, mocked_force: Mock, mocked_trigger: Mock):
+        """ Test the check_progress method. """
+        from supvisors.ttypes import ProcessStates
+        # test with no sequence in progress
+        self.assertTrue(self.commander.check_progress('stopped', ProcessStates.FATAL))
+        # test with no current jobs but planned sequence and no planned jobs
+        self.commander.planned_sequence = {3: {'else': {}}}
+        self.assertFalse(self.commander.check_progress('stopped', ProcessStates.FATAL))
+        self.assertEqual([call()], mocked_trigger.call_args_list)
+        mocked_trigger.reset_mock()
+        # test with no current jobs but planned sequence and planned jobs (unexpected case)
+        self.commander.planned_jobs = {'if': {2: []}}
+        self.assertFalse(self.commander.check_progress('stopped', ProcessStates.FATAL))
+        self.assertFalse(mocked_trigger.called)
+        # set test current_jobs
+        # xfontsel is RUNNING, xlogo is STOPPED, yeux_00 is EXITED, yeux_01 is RUNNING
+        self.commander.current_jobs = {'sample_test_1': [self._get_test_command('xfontsel'),
+                                                         self._get_test_command('xlogo')],
+                                       'sample_test_2': [self._get_test_command('yeux_00'),
+                                                         self._get_test_command('yeux_01')]}
+        # assign request_time to processes in current_jobs
+        for command_list in self.commander.current_jobs.values():
+            for command in command_list:
+                command.request_time = time.time()
+        # stopped processes have a recent request time: nothing done
+        completed = self.commander.check_progress('stopped', ProcessStates.FATAL)
+        self.assertFalse(completed)
+        self.assertDictEqual({'sample_test_1': ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
+                              'sample_test_2': ['sample_test_2:yeux_00', 'sample_test_2:yeux_01']},
+                             self.commander.printable_current_jobs())
+        self.assertFalse(mocked_force.called)
+        self.assertFalse(mocked_after.called)
+        self.assertFalse(mocked_trigger.called)
+        # re-assign last_event_time and request_time to processes in current_jobs
+        for command_list in self.commander.current_jobs.values():
+            for command in command_list:
+                command.process.last_event_time = 0
+                command.request_time = 0
+        # stopped processes have an old request time: actions taken on state and sequence
+        completed = self.commander.check_progress('stopped', ProcessStates.FATAL)
+        self.assertFalse(completed)
+        self.assertEqual({'sample_test_1': ['sample_test_1:xfontsel'], 'sample_test_2': ['sample_test_2:yeux_01']},
+                         self.commander.printable_current_jobs())
+        reason = 'Still stopped 10 seconds after request'
+        self.assertEqual([call('sample_test_1:xlogo', ProcessStates.FATAL, reason),
+                          call('sample_test_2:yeux_00', ProcessStates.FATAL, reason)],
+                         mocked_force.call_args_list)
+        self.assertFalse(mocked_after.called)
+        self.assertFalse(mocked_trigger.called)
+        # reset mocks
+        mocked_force.reset_mock()
+        # re-assign request_time to processes in remaining current_jobs
+        for command_list in self.commander.current_jobs.values():
+            for command in command_list:
+                command.request_time = time.time()
+        # stopped processes have a recent request time: nothing done
+        completed = self.commander.check_progress('running', ProcessStates.UNKNOWN)
+        self.assertFalse(completed)
+        self.assertEqual({'sample_test_1': ['sample_test_1:xfontsel'], 'sample_test_2': ['sample_test_2:yeux_01']},
+                         self.commander.printable_current_jobs())
+        self.assertFalse(mocked_force.called)
+        self.assertFalse(mocked_after.called)
+        self.assertFalse(mocked_trigger.called)
+        # re-assign last_event_time and request_time to processes in current_jobs
+        for command_list in self.commander.current_jobs.values():
+            for command in command_list:
+                command.process.last_event_time = 0
+                command.request_time = 0
+        # stopped processes have an old request time: actions taken on state and sequence
+        completed = self.commander.check_progress('running', ProcessStates.UNKNOWN)
+        self.assertFalse(completed)
+        self.assertEqual({'sample_test_1': [], 'sample_test_2': []},
+                         self.commander.printable_current_jobs())
+        reason = 'Still running 10 seconds after request'
+        self.assertEqual([call('sample_test_1:xfontsel', ProcessStates.UNKNOWN, reason),
+                          call('sample_test_2:yeux_01', ProcessStates.UNKNOWN, reason)],
+                         mocked_force.call_args_list)
+        self.assertEqual([call('sample_test_1'), call('sample_test_2')], mocked_after.call_args_list)
+        self.assertFalse(mocked_trigger.called)
+
+    @patch('supvisors.infosource.SupervisordSource.force_process_fatal')
+    @patch('supvisors.listener.SupervisorListener.force_process_fatal')
+    def test_force_process_fatal(self, mocked_listener, mocked_source):
+        """ Test the force_process_state method with a FATAL parameter. """
+        from supvisors.ttypes import ProcessStates
+        # test with FATAL and no info_source KeyError
+        self.commander.force_process_state('proc', ProcessStates.FATAL, 'any reason')
+        self.assertEqual([call(self.supvisors.info_source, 'proc', 'any reason')], mocked_source.call_args_list)
+        self.assertFalse(mocked_listener.called)
+        mocked_source.reset_mock()
+        # test with FATAL and info_source KeyError
+        mocked_source.side_effect = KeyError
+        self.commander.force_process_state('proc', ProcessStates.FATAL, 'any reason')
+        self.assertEqual([call(self.supvisors.info_source, 'proc', 'any reason')], mocked_source.call_args_list)
+        self.assertEqual([call(self.supvisors.listener, 'proc')], mocked_listener.call_args_list)
+
+    @patch('supvisors.infosource.SupervisordSource.force_process_unknown')
+    @patch('supvisors.listener.SupervisorListener.force_process_unknown')
+    def test_force_process_unknown(self, mocked_listener, mocked_source):
+        """ Test the force_process_state method with an UNKNOWN parameter. """
+        from supvisors.ttypes import ProcessStates
+        # test with UNKNOWN and no info_source KeyError
+        self.commander.force_process_state('proc', ProcessStates.UNKNOWN, 'any reason')
+        self.assertEqual([call(self.supvisors.info_source, 'proc', 'any reason')], mocked_source.call_args_list)
+        self.assertFalse(mocked_listener.called)
+        mocked_source.reset_mock()
+        # test with UNKNOWN and info_source KeyError
+        mocked_source.side_effect = KeyError
+        self.commander.force_process_state('proc', ProcessStates.UNKNOWN, 'any reason')
+        self.assertEqual([call(self.supvisors.info_source, 'proc', 'any reason')], mocked_source.call_args_list)
+        self.assertEqual([call(self.supvisors.listener, 'proc')], mocked_listener.call_args_list)
+
+    @patch('supvisors.commander.Commander.process_application_jobs')
+    @patch('supvisors.commander.Commander.trigger_jobs')
+    def test_after_event(self, mocked_trigger: Mock, mocked_process: Mock):
+        """ Test the after_event method. """
+        # prepare some context
+        self.commander.planned_jobs = {'if': {2: []}}
+        self.commander.current_jobs = {'if': [], 'then': [], 'else': []}
+        # test after_event when there are still planned jobs for application
+        self.commander.after_event('if')
+        self.assertNotIn('if', self.commander.current_jobs)
+        self.assertEqual([call('if')], mocked_process.call_args_list)
+        self.assertFalse(mocked_trigger.called)
+        # reset mocks
+        mocked_process.reset_mock()
+        # test after_event when there's no more planned jobs for this application
+        self.commander.after_event('then')
+        self.assertNotIn('then', self.commander.current_jobs)
+        self.assertFalse(mocked_process.called)
+        self.assertFalse(mocked_trigger.called)
+        # test after_event when there's no more planned jobs at all
+        self.commander.planned_jobs = {}
+        self.commander.after_event('else')
+        self.assertNotIn('else', self.commander.current_jobs)
+        self.assertFalse(mocked_process.called)
+        self.assertFalse(mocked_process.called)
+        self.assertEqual([call()], mocked_trigger.call_args_list)
+
+
+class StarterTest(CommanderContextTest):
     """ Test case for the Starter class of the commander module. """
 
     def setUp(self):
         """ Create a Supvisors-like structure and test processes. """
-        from supvisors.process import ProcessStatus
-        self.supvisors = MockedSupvisors()
-        # store list for tests
-        self.command_list = []
-        for info in database_copy():
-            command = _create_process_command(info['group'], info['name'],
-                                              self.supvisors)
-            command.process.add_info('10.0.0.1', info)
-            self.command_list.append(command)
-
-    def _get_test_command(self, process_name):
-        """ Return the process in database corresponding to process_name. """
-        return next(command for command in self.command_list
-                    if command.process.process_name == process_name)
+        CommanderContextTest.setUp(self)
+        from supvisors.commander import Starter
+        self.starter = Starter(self.supvisors)
 
     def test_creation(self):
         """ Test the values set at construction. """
-        from supvisors.commander import Commander, Starter
-        from supvisors.ttypes import StartingStrategies
-        starter = Starter(self.supvisors)
-        self.assertIsInstance(starter, Commander)
-        self.assertEqual(StartingStrategies.CONFIG, starter._strategy)
-        starter.strategy = StartingStrategies.LESS_LOADED
-        self.assertEqual(StartingStrategies.LESS_LOADED, starter.strategy)
+        from supvisors.commander import Commander
+        self.assertIsInstance(self.starter, Commander)
 
     def test_abort(self):
         """ Test the abort method. """
-        from supvisors.commander import Starter
-        starter = Starter(self.supvisors)
-        # fill attributes
-        starter.planned_sequence = {3: {'else': {}}}
-        starter.planned_jobs = {'if': {2: []}}
-        starter.current_jobs = {'if': ['dummy_1', 'dummy_2'],
-                                'then': ['dummy_3']}
+        # prepare some context
+        self.starter.planned_sequence = {3: {'else': {}}}
+        self.starter.planned_jobs = {'if': {2: []}}
+        self.starter.current_jobs = {'if': ['dummy_1', 'dummy_2'], 'then': ['dummy_3']}
         # call abort and check attributes
-        starter.abort()
-        self.assertDictEqual({}, starter.planned_sequence)
-        self.assertDictEqual({}, starter.planned_jobs)
-        self.assertDictEqual({}, starter.current_jobs)
+        self.starter.abort()
+        self.assertDictEqual({}, self.starter.planned_sequence)
+        self.assertDictEqual({}, self.starter.planned_jobs)
+        self.assertDictEqual({}, self.starter.current_jobs)
 
     def test_store_application_start_sequence(self):
         """ Test the store_application_start_sequence method. """
         from supvisors.application import ApplicationStatus
-        from supvisors.commander import Starter
-        starter = Starter(self.supvisors)
+        from supvisors.ttypes import StartingStrategies
         # create 2 application start_sequences
-        application1 = ApplicationStatus('sample_test_1',
-                                         self.supvisors.logger)
+        appli1 = ApplicationStatus('sample_test_1', self.supvisors.logger)
         for command in self.command_list:
             if command.process.application_name == 'sample_test_1':
-                application1.start_sequence.setdefault(
-                    len(command.process.namespec()) % 3, []).append(command.process)
-        application2 = ApplicationStatus('sample_test_2', self.supvisors.logger)
+                appli1.start_sequence.setdefault(len(command.process.namespec()) % 3, []).append(command.process)
+        appli2 = ApplicationStatus('sample_test_2', self.supvisors.logger)
         for command in self.command_list:
             if command.process.application_name == 'sample_test_2':
-                application2.start_sequence.setdefault(
-                    len(command.process.namespec()) % 3, []).append(command.process)
+                appli2.start_sequence.setdefault(len(command.process.namespec()) % 3, []).append(command.process)
         # call method and check result
-        starter.store_application_start_sequence(application1)
+        self.starter.store_application_start_sequence(appli1, StartingStrategies.LESS_LOADED)
         # check that application sequence 0 is not in starter planned sequence
-        self.assertDictEqual({0: {'sample_test_1': {
-            1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
-            2: ['sample_test_1:xclock']}}},
-            starter.printable_planned_sequence())
+        self.assertDictEqual({0: {'sample_test_1': {1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
+                                                    2: ['sample_test_1:xclock']}}},
+                             self.starter.printable_planned_sequence())
+        # check strategy applied
+        for proc_list in self.starter.planned_sequence[0]['sample_test_1'].values():
+            for proc in proc_list:
+                self.assertEqual(StartingStrategies.LESS_LOADED, proc.strategy)
         # call method a second time and check result
-        starter.store_application_start_sequence(application2)
+        self.starter.store_application_start_sequence(appli2, StartingStrategies.LOCAL)
         # check that application sequence 0 is not in starter planned sequence
-        self.assertDictEqual({0: {'sample_test_1': {
-            1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
-            2: ['sample_test_1:xclock']},
-            'sample_test_2': {1: ['sample_test_2:sleep']}}},
-            starter.printable_planned_sequence())
+        self.assertDictEqual({0: {'sample_test_1': {1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
+                                                    2: ['sample_test_1:xclock']},
+                                  'sample_test_2': {1: ['sample_test_2:sleep']}}},
+                             self.starter.printable_planned_sequence())
+        # check strategy applied
+        for proc_list in self.starter.planned_sequence[0]['sample_test_1'].values():
+            for proc in proc_list:
+                self.assertEqual(StartingStrategies.LESS_LOADED, proc.strategy)
+        for proc_list in self.starter.planned_sequence[0]['sample_test_2'].values():
+            for proc in proc_list:
+                self.assertEqual(StartingStrategies.LOCAL, proc.strategy)
 
     def test_process_failure_optional(self):
         """ Test the process_failure method with an optional process. """
-        from supvisors.commander import Starter
-        # create the instance
-        starter = Starter(self.supvisors)
         # prepare context
         process = Mock()
         process.rules = Mock(required=False)
-        test_planned_jobs = {'appli_1': {0: ['proc_1']},
-                             'appli_2': {1: ['proc_2']}}
+        test_planned_jobs = {'appli_1': {0: ['proc_1']}, 'appli_2': {1: ['proc_2']}}
         # get the patch for stopper / stop_application
         mocked_stopper = self.supvisors.stopper.stop_application
         # test with a process not required
-        starter.planned_jobs = test_planned_jobs.copy()
-        starter.process_failure(process)
+        self.starter.planned_jobs = test_planned_jobs.copy()
+        self.starter.process_failure(process)
         # test planned_jobs is unchanged
-        self.assertDictEqual(test_planned_jobs, starter.planned_jobs)
+        self.assertDictEqual(test_planned_jobs, self.starter.planned_jobs)
         # test stop_application is not called
         self.assertEqual(0, mocked_stopper.call_count)
 
     def test_process_failure_required(self):
         """ Test the process_failure method with a required process. """
-        from supvisors.commander import Starter
         from supvisors.ttypes import StartingFailureStrategies
-        # create the instance
-        starter = Starter(self.supvisors)
         # prepare context
         process = Mock(application_name='appli_1')
         process.rules = Mock(required=True)
         application = Mock()
-        self.supvisors.context.applications = {'appli_1': application,
-                                               'proc_2': None}
-        test_planned_jobs = {'appli_1': {0: ['proc_1']},
-                             'appli_2': {1: ['proc_2']}}
+        self.supvisors.context.applications = {'appli_1': application, 'proc_2': None}
+        test_planned_jobs = {'appli_1': {0: ['proc_1']}, 'appli_2': {1: ['proc_2']}}
         # get the patch for stopper / stop_application
         mocked_stopper = self.supvisors.stopper.stop_application
         # test ABORT starting strategy
-        starter.planned_jobs = test_planned_jobs.copy()
-        application.rules = Mock(
-            starting_failure_strategy=StartingFailureStrategies.ABORT)
-        starter.process_failure(process)
+        self.starter.planned_jobs = test_planned_jobs.copy()
+        application.rules = Mock(starting_failure_strategy=StartingFailureStrategies.ABORT)
+        self.starter.process_failure(process)
         # check that application has been removed from planned jobs and stopper wasn't called
-        self.assertDictEqual({'appli_2': {1: ['proc_2']}}, starter.planned_jobs)
+        self.assertDictEqual({'appli_2': {1: ['proc_2']}}, self.starter.planned_jobs)
         self.assertEqual(0, mocked_stopper.call_count)
         # test CONTINUE starting strategy
-        starter.planned_jobs = test_planned_jobs.copy()
-        application.rules = Mock(
-            starting_failure_strategy=StartingFailureStrategies.CONTINUE)
-        starter.process_failure(process)
-        # check that application has NOT been removed from planned jobs
-        # and stopper wasn't called
+        self.starter.planned_jobs = test_planned_jobs.copy()
+        application.rules = Mock(starting_failure_strategy=StartingFailureStrategies.CONTINUE)
+        self.starter.process_failure(process)
+        # check that application has NOT been removed from planned jobs and stopper wasn't called
         self.assertDictEqual({'appli_1': {0: ['proc_1']}, 'appli_2': {1: ['proc_2']}},
-                             starter.planned_jobs)
+                             self.starter.planned_jobs)
         self.assertEqual(0, mocked_stopper.call_count)
         # test STOP starting strategy
-        starter.planned_jobs = test_planned_jobs.copy()
-        application.rules = Mock(
-            starting_failure_strategy=StartingFailureStrategies.STOP)
-        starter.process_failure(process)
-        # check that application has been removed from planned jobs
-        # and stopper has been called
-        self.assertDictEqual({'appli_2': {1: ['proc_2']}}, starter.planned_jobs)
+        self.starter.planned_jobs = test_planned_jobs.copy()
+        application.rules = Mock(starting_failure_strategy=StartingFailureStrategies.STOP)
+        self.starter.process_failure(process)
+        # check that application has been removed from planned jobs and stopper has been called
+        self.assertDictEqual({'appli_2': {1: ['proc_2']}}, self.starter.planned_jobs)
         self.assertEqual([call(application)], mocked_stopper.call_args_list)
 
-    def test_force_process_fatal(self):
-        """ Test the force_process_fatal method. """
-        from supvisors.commander import Starter
-        starter = Starter(self.supvisors)
-        # get patches
-        mocked_listener = self.supvisors.listener.force_process_fatal
-        mocked_source = self.supvisors.info_source.force_process_fatal
-        # test with no info_source exception
-        starter.force_process_fatal('proc', 'any reason')
-        self.assertEqual([call('proc', 'any reason')],
-                         mocked_source.call_args_list)
-        self.assertEqual(0, mocked_listener.call_count)
-        mocked_source.reset_mock()
-        # test with info_source exception
-        mocked_source.side_effect = KeyError
-        starter.force_process_fatal('proc', 'any reason')
-        self.assertEqual([call('proc', 'any reason')],
-                         mocked_source.call_args_list)
-        self.assertEqual([call('proc')],
-                         mocked_listener.call_args_list)
+    @patch('supvisors.commander.Commander.check_progress', return_value=True)
+    def test_check_starting(self, mocked_check: Mock):
+        """ Test the check_starting method. """
+        from supvisors.ttypes import ProcessStates
+        self.assertTrue(self.starter.check_starting())
+        self.assertEqual([call('stopped', ProcessStates.FATAL)], mocked_check.call_args_list)
 
     def test_on_event(self):
         """ Test the on_event method. """
-        from supvisors.commander import Starter
-        starter = Starter(self.supvisors)
         # apply patches
-        with patch.object(starter, 'on_event_in_sequence') as mocked_in:
-            with patch.object(starter, 'on_event_out_of_sequence') as mocked_out:
+        with patch.object(self.starter, 'on_event_in_sequence') as mocked_in:
+            with patch.object(self.starter, 'on_event_out_of_sequence') as mocked_out:
                 # set test current_jobs
                 for command in self.command_list:
-                    starter.current_jobs.setdefault(
-                        command.process.application_name,
-                        []).append(command)
-                self.assertIn('sample_test_1', starter.current_jobs)
+                    self.starter.current_jobs.setdefault(command.process.application_name, []).append(command)
+                self.assertIn('sample_test_1', self.starter.current_jobs)
                 # test that on_event_out_of_sequence is called when process
                 # is not in current jobs due to unknown application
                 process = Mock(application_name='unknown_application')
-                starter.on_event(process)
+                self.starter.on_event(process)
                 self.assertEqual(0, mocked_in.call_count)
-                self.assertEqual([(call(process))],
-                                 mocked_out.call_args_list)
+                self.assertEqual([(call(process))], mocked_out.call_args_list)
                 mocked_out.reset_mock()
                 # test that on_event_out_of_sequence is called when process
                 # is not in current jobs due to unknown process
                 process = Mock(application_name='sample_test_1')
-                starter.on_event(process)
+                self.starter.on_event(process)
                 self.assertEqual(0, mocked_in.call_count)
-                self.assertEqual([(call(process))],
-                                 mocked_out.call_args_list)
+                self.assertEqual([(call(process))], mocked_out.call_args_list)
                 mocked_out.reset_mock()
                 # test that on_event_in_sequence is called when process is in list
-                jobs = starter.current_jobs['sample_test_1']
+                jobs = self.starter.current_jobs['sample_test_1']
                 command = next(iter(jobs))
-                starter.on_event(command.process)
+                self.starter.on_event(command.process)
                 self.assertEqual(0, mocked_out.call_count)
-                self.assertEqual([(call(command, jobs))],
-                                 mocked_in.call_args_list)
+                self.assertEqual([(call(command, jobs))], mocked_in.call_args_list)
 
-    def test_on_event_in_sequence(self):
+    @patch('supvisors.commander.Starter.process_failure')
+    @patch('supvisors.commander.Commander.after_event')
+    def test_on_event_in_sequence(self, mocked_after: Mock, mocked_failure: Mock):
         """ Test the on_event_in_sequence method. """
         from supvisors.application import ApplicationStatus
-        from supvisors.commander import Starter
         from supvisors.ttypes import StartingFailureStrategies
-        starter = Starter(self.supvisors)
-        # set test planned_jobs and current_jobs
-        starter.planned_jobs = {'sample_test_1': {}}
+        # set context for current_jobs
         for command in self.command_list:
-            starter.current_jobs.setdefault(
-                command.process.application_name,
-                []).append(command)
+            self.starter.current_jobs.setdefault(command.process.application_name, []).append(command)
         # add application context
-        application = ApplicationStatus('sample_test_1',
-                                        self.supvisors.logger)
+        application = ApplicationStatus('sample_test_1', self.supvisors.logger)
         application.rules.starting_failure_strategy = StartingFailureStrategies.CONTINUE
         self.supvisors.context.applications['sample_test_1'] = application
         application = ApplicationStatus('sample_test_2', self.supvisors.logger)
         application.rules.starting_failure_strategy = StartingFailureStrategies.ABORT
         self.supvisors.context.applications['sample_test_2'] = application
-        # add patches to simplify test
-        with patch.object(starter, 'process_application_jobs') as mocked_process_jobs:
-            with patch.object(starter, 'initial_jobs') as mocked_init_jobs:
-                # with sample_test_1 application
-                # test STOPPED process
-                command = self._get_test_command('xlogo')
-                jobs = starter.current_jobs['sample_test_1']
-                self.assertIn(command, jobs)
-                starter.on_event_in_sequence(command, jobs)
-                self.assertFalse(command.ignore_wait_exit)
-                self.assertNotIn(command, jobs)
-                self.assertEqual(0, mocked_process_jobs.call_count)
-                self.assertEqual(0, mocked_init_jobs.call_count)
-                # test STOPPING process: xclock
-                command = self._get_test_command('xclock')
-                self.assertIn(command, jobs)
-                starter.on_event_in_sequence(command, jobs)
-                self.assertFalse(command.ignore_wait_exit)
-                self.assertNotIn(command, jobs)
-                self.assertEqual(0, mocked_process_jobs.call_count)
-                self.assertEqual(0, mocked_init_jobs.call_count)
-                # test RUNNING process: xfontsel (last process of this application)
-                command = self._get_test_command('xfontsel')
-                self.assertIn(command, jobs)
-                starter.on_event_in_sequence(command, jobs)
-                self.assertFalse(command.ignore_wait_exit)
-                self.assertNotIn('sample_test_1', starter.current_jobs)
-                self.assertEqual(1, mocked_process_jobs.call_count)
-                self.assertEqual(call('sample_test_1'), mocked_process_jobs.call_args)
-                self.assertEqual(0, mocked_init_jobs.call_count)
-                # reset resources
-                mocked_process_jobs.reset_mock()
-                # with sample_test_2 application
-                # test RUNNING process: yeux_01
-                command = self._get_test_command('yeux_01')
-                jobs = starter.current_jobs['sample_test_2']
-                command.process.rules.wait_exit = True
-                command.ignore_wait_exit = True
-                self.assertIn(command, jobs)
-                starter.on_event_in_sequence(command, jobs)
-                self.assertNotIn(command, jobs)
-                self.assertEqual(0, mocked_process_jobs.call_count)
-                self.assertEqual(0, mocked_init_jobs.call_count)
-                # test EXITED / expected process: yeux_00
-                command = self._get_test_command('yeux_00')
-                command.process.rules.wait_exit = True
-                command.process.expected_exit = True
-                self.assertIn(command, jobs)
-                starter.on_event_in_sequence(command, jobs)
-                self.assertNotIn(command, jobs)
-                self.assertEqual(0, mocked_process_jobs.call_count)
-                self.assertEqual(0, mocked_init_jobs.call_count)
-                # test FATAL process: sleep (last process of this application)
-                command = self._get_test_command('sleep')
-                self.assertIn(command, jobs)
-                starter.on_event_in_sequence(command, jobs)
-                self.assertFalse(command.ignore_wait_exit)
-                self.assertNotIn('sample_test_2', starter.current_jobs)
-                self.assertEqual(0, mocked_process_jobs.call_count)
-                self.assertEqual(0, mocked_init_jobs.call_count)
-                # with crash application
-                # test STARTING process: late_segv
-                command = self._get_test_command('late_segv')
-                jobs = starter.current_jobs['crash']
-                self.assertIn(command, jobs)
-                starter.on_event_in_sequence(command, jobs)
-                self.assertIn(command, jobs)
-                self.assertEqual(0, mocked_process_jobs.call_count)
-                self.assertEqual(0, mocked_init_jobs.call_count)
-                # test BACKOFF process: segv (last process of this application)
-                command = self._get_test_command('segv')
-                self.assertIn(command, jobs)
-                starter.on_event_in_sequence(command, jobs)
-                self.assertIn(command, jobs)
-                self.assertEqual(0, mocked_process_jobs.call_count)
-                self.assertEqual(0, mocked_init_jobs.call_count)
-                # with firefox application
-                # empty planned_jobs to trigger another behaviour
-                starter.planned_jobs = {}
-                # test EXITED / unexpected process: firefox
-                command = self._get_test_command('firefox')
-                jobs = starter.current_jobs['firefox']
-                command.process.rules.wait_exit = True
-                command.process.expected_exit = False
-                self.assertIn(command, jobs)
-                starter.on_event_in_sequence(command, jobs)
-                self.assertNotIn('firefox', starter.current_jobs)
-                self.assertEqual(0, mocked_process_jobs.call_count)
-                self.assertEqual(1, mocked_init_jobs.call_count)
+        # with sample_test_1 application
+        # test STOPPED process
+        command = self._get_test_command('xlogo')
+        jobs = self.starter.current_jobs['sample_test_1']
+        self.assertIn(command, jobs)
+        self.starter.on_event_in_sequence(command, jobs)
+        self.assertFalse(command.ignore_wait_exit)
+        self.assertNotIn(command, jobs)
+        self.assertEqual([call(command.process)], mocked_failure.call_args_list)
+        self.assertFalse(mocked_after.called)
+        # reset mocks
+        mocked_failure.reset_mock()
+        # test STOPPING process: xclock
+        command = self._get_test_command('xclock')
+        self.assertIn(command, jobs)
+        self.starter.on_event_in_sequence(command, jobs)
+        self.assertFalse(command.ignore_wait_exit)
+        self.assertNotIn(command, jobs)
+        self.assertEqual([call(command.process)], mocked_failure.call_args_list)
+        self.assertFalse(mocked_after.called)
+        # reset mocks
+        mocked_failure.reset_mock()
+        # test RUNNING process: xfontsel (last process of this application)
+        command = self._get_test_command('xfontsel')
+        self.assertIn(command, jobs)
+        self.assertFalse(command.process.rules.wait_exit)
+        self.assertFalse(command.ignore_wait_exit)
+        self.starter.on_event_in_sequence(command, jobs)
+        self.assertNotIn(command, jobs)
+        self.assertFalse(mocked_failure.called)
+        self.assertEqual([call('sample_test_1')], mocked_after.call_args_list)
+        # reset mocks
+        mocked_after.reset_mock()
+        # with sample_test_2 application
+        # test RUNNING process: yeux_01
+        command = self._get_test_command('yeux_01')
+        jobs = self.starter.current_jobs['sample_test_2']
+        command.process.rules.wait_exit = True
+        command.ignore_wait_exit = True
+        self.assertIn(command, jobs)
+        self.starter.on_event_in_sequence(command, jobs)
+        self.assertNotIn(command, jobs)
+        self.assertFalse(mocked_failure.called)
+        self.assertFalse(mocked_after.called)
+        # test EXITED / expected process: yeux_00
+        command = self._get_test_command('yeux_00')
+        command.process.rules.wait_exit = True
+        command.process.expected_exit = True
+        self.assertIn(command, jobs)
+        self.starter.on_event_in_sequence(command, jobs)
+        self.assertNotIn(command, jobs)
+        self.assertFalse(mocked_failure.called)
+        self.assertFalse(mocked_after.called)
+        # test FATAL process: sleep (last process of this application)
+        command = self._get_test_command('sleep')
+        self.assertIn(command, jobs)
+        self.starter.on_event_in_sequence(command, jobs)
+        self.assertFalse(command.ignore_wait_exit)
+        self.assertNotIn(command, jobs)
+        self.assertEqual([call(command.process)], mocked_failure.call_args_list)
+        self.assertEqual([call('sample_test_2')], mocked_after.call_args_list)
+        # reset mocks
+        mocked_failure.reset_mock()
+        mocked_after.reset_mock()
+        # with crash application
+        # test STARTING process: late_segv
+        command = self._get_test_command('late_segv')
+        jobs = self.starter.current_jobs['crash']
+        self.assertIn(command, jobs)
+        self.starter.on_event_in_sequence(command, jobs)
+        self.assertIn(command, jobs)
+        self.assertFalse(mocked_failure.called)
+        self.assertFalse(mocked_after.called)
+        # test BACKOFF process: segv (last process of this application)
+        command = self._get_test_command('segv')
+        self.assertIn(command, jobs)
+        self.starter.on_event_in_sequence(command, jobs)
+        self.assertIn(command, jobs)
+        self.assertFalse(mocked_failure.called)
+        self.assertFalse(mocked_after.called)
+        # with firefox application
+        # test EXITED / unexpected process: firefox
+        command = self._get_test_command('firefox')
+        jobs = self.starter.current_jobs['firefox']
+        command.process.rules.wait_exit = True
+        command.process.expected_exit = False
+        self.assertIn(command, jobs)
+        self.starter.on_event_in_sequence(command, jobs)
+        self.assertIn('firefox', self.starter.current_jobs)
+        self.assertEqual([call(command.process)], mocked_failure.call_args_list)
+        self.assertEqual([call('firefox')], mocked_after.call_args_list)
 
     def test_on_event_out_of_sequence(self):
         """ Test how failure are raised in on_event_out_of_sequence method. """
-        from supvisors.commander import Starter
-        starter = Starter(self.supvisors)
         # set test planned_jobs and current_jobs
-        starter.planned_jobs = {'sample_test_2': {1: []}}
+        self.starter.planned_jobs = {'sample_test_2': {1: []}}
         for command in self.command_list:
-            starter.current_jobs.setdefault(
-                command.process.application_name,
-                []).append(command)
+            self.starter.current_jobs.setdefault(command.process.application_name, []).append(command)
         # apply patch
-        with patch.object(starter, 'process_failure') as mocked_failure:
+        with patch.object(self.starter, 'process_failure') as mocked_failure:
             # test that process_failure is not called if process is not crashed
             process = next(command.process
                            for command in self.command_list
                            if not command.process.crashed())
-            starter.on_event_out_of_sequence(process)
+            self.starter.on_event_out_of_sequence(process)
             self.assertEqual(0, mocked_failure.call_count)
-            # test that process_failure is not called if process is not
-            # in planned jobs
+            # test that process_failure is not called if process is not in planned jobs
             process = next(command.process
                            for command in self.command_list
                            if command.process.application_name == 'sample_test_1')
-            starter.on_event_out_of_sequence(process)
+            self.starter.on_event_out_of_sequence(process)
             self.assertEqual(0, mocked_failure.call_count)
             # get a command related to a process crashed and in planned jobs
             command = next(command
                            for command in self.command_list
-                           if command.process.crashed() and
-                           command.process.application_name == 'sample_test_2')
-            # test that process_failure is called if process' starting
-            # is not planned
-            starter.on_event_out_of_sequence(command.process)
-            self.assertEqual([(call(command.process))],
-                             mocked_failure.call_args_list)
+                           if command.process.crashed() and command.process.application_name == 'sample_test_2')
+            # test that process_failure is called if process' starting is not planned
+            self.starter.on_event_out_of_sequence(command.process)
+            self.assertEqual([(call(command.process))], mocked_failure.call_args_list)
             mocked_failure.reset_mock()
-            # test that process_failure is not called if process' starting
-            # is still planned
-            starter.planned_jobs = {'sample_test_2': {1: [command]}}
-            starter.on_event_out_of_sequence(command.process)
+            # test that process_failure is not called if process' starting is still planned
+            self.starter.planned_jobs = {'sample_test_2': {1: [command]}}
+            self.starter.on_event_out_of_sequence(command.process)
             self.assertEqual(0, mocked_failure.call_count)
 
-    def test_check_starting(self):
-        """ Test the check_starting method. """
-        from supvisors.commander import Starter
-        starter = Starter(self.supvisors)
-        # test with no jobs
-        completed = starter.check_starting()
-        self.assertTrue(completed)
-        # set test current_jobs
-        # xfontsel is RUNNING, xlogo is STOPPED, yeux_00 is EXITED, yeux_01 is RUNNING
-        starter.current_jobs = {'sample_test_1': [
-            self._get_test_command('xfontsel'),
-            self._get_test_command('xlogo')],
-            'sample_test_2': [self._get_test_command('yeux_00'),
-                              self._get_test_command('yeux_01')]}
-        # assign request_time to processes in current_jobs
-        for command_list in starter.current_jobs.values():
-            for command in command_list:
-                command.process.request_time = time.time()
-        # stopped processes have a recent request time: nothing done
-        with patch.object(starter, 'force_process_fatal') as mocked_force:
-            completed = starter.check_starting()
-            self.assertFalse(completed)
-            self.assertDictEqual(
-                {'sample_test_1': ['sample_test_1:xfontsel',
-                                   'sample_test_1:xlogo'],
-                 'sample_test_2': ['sample_test_2:yeux_00',
-                                   'sample_test_2:yeux_01']},
-                starter.printable_current_jobs())
-            self.assertEqual(0, mocked_force.call_count)
-        # re-assign last_event_time and request_time to processes
-        # in current_jobs
-        for command_list in starter.current_jobs.values():
-            for command in command_list:
-                command.process.last_event_time = 0
-                command.request_time = 0
-        # stopped processes have an old request time: process_failure called
-        with patch.object(starter, 'force_process_fatal') as mocked_force:
-            completed = starter.check_starting()
-            self.assertFalse(completed)
-            self.assertDictEqual(
-                {'sample_test_1': ['sample_test_1:xfontsel',
-                                   'sample_test_1:xlogo'],
-                 'sample_test_2': ['sample_test_2:yeux_00',
-                                   'sample_test_2:yeux_01']},
-                starter.printable_current_jobs())
-            str_error = 'Still stopped 5 seconds after start request'
-            self.assertItemsEqual([call('sample_test_1:xlogo', str_error),
-                                   call('sample_test_2:yeux_00', str_error)],
-                                  mocked_force.call_args_list)
-
-    @patch('supvisors.commander.Starter.force_process_fatal')
-    def test_process_job(self, mocked_force):
+    @patch('supvisors.commander.Commander.force_process_state')
+    @patch('supvisors.commander.get_address')
+    def test_process_job(self, mocked_address: Mock, mocked_force: Mock):
         """ Test the process_job method. """
-        from supvisors.commander import Starter
-        starter = Starter(self.supvisors)
+        from supvisors.ttypes import ProcessStates
         # get patches
         mocked_pusher = self.supvisors.zmq.pusher.send_start_process
         # test with a possible starting address
-        with patch('supvisors.commander.get_address',
-                   return_value='10.0.0.1') as mocked_address:
-            # test with running process
-            command = self._get_test_command('xfontsel')
-            command.ignore_wait_exit = True
-            jobs = []
-            # call the process_jobs
-            starter.process_job(command, jobs)
-            # starting methods are not called
-            self.assertListEqual([], jobs)
-            self.assertEqual(0, mocked_address.call_count)
-            self.assertEqual(0, mocked_pusher.call_count)
-            # failure method is not called
-            self.assertEqual(0, mocked_force.call_count)
-            # test with stopped process
-            command = self._get_test_command('xlogo')
-            command.ignore_wait_exit = True
-            jobs = []
-            # call the process_jobs
-            starter.process_job(command, jobs)
-            # starting methods are called
-            self.assertListEqual([command], jobs)
-            self.assertEqual(1, mocked_address.call_count)
-            self.assertEqual(1, mocked_pusher.call_count)
-            self.assertEqual(call('10.0.0.1', 'sample_test_1:xlogo', ''),
-                             mocked_pusher.call_args)
-            mocked_pusher.reset_mock()
-            # failure method is not called
-            self.assertEqual(0, mocked_force.call_count)
+        mocked_address.return_value = '10.0.0.1'
+        # 1. test with running process
+        command = self._get_test_command('xfontsel')
+        command.ignore_wait_exit = True
+        jobs = []
+        # call the process_jobs
+        self.starter.process_job(command, jobs)
+        # starting methods are not called
+        self.assertListEqual([], jobs)
+        self.assertEqual(0, mocked_address.call_count)
+        self.assertEqual(0, mocked_pusher.call_count)
+        # failure method is not called
+        self.assertEqual(0, mocked_force.call_count)
+        # 2. test with stopped process
+        command = self._get_test_command('xlogo')
+        command.ignore_wait_exit = True
+        jobs = []
+        # call the process_jobs
+        self.starter.process_job(command, jobs)
+        # starting methods are called
+        self.assertListEqual([command], jobs)
+        self.assertEqual([call(self.supvisors, None, ['*'], 1)], mocked_address.call_args_list)
+        self.assertEqual(1, mocked_pusher.call_count)
+        self.assertEqual(call('10.0.0.1', 'sample_test_1:xlogo', ''), mocked_pusher.call_args)
+        mocked_pusher.reset_mock()
+        # failure method is not called
+        self.assertEqual(0, mocked_force.call_count)
         # test with no starting address
-        with patch('supvisors.commander.get_address', return_value=None):
-            # test with stopped process
-            command = self._get_test_command('xlogo')
-            command.ignore_wait_exit = True
-            jobs = []
-            # call the process_jobs
-            starter.process_job(command, jobs)
-            # starting methods are not called
-            self.assertListEqual([], jobs)
-            self.assertEqual(0, mocked_pusher.call_count)
-            # failure method is called
-            self.assertEqual([call('sample_test_1:xlogo',
-                                   'no resource available')],
-                             mocked_force.call_args_list)
+        mocked_address.return_value = None
+        # test with stopped process
+        command = self._get_test_command('xlogo')
+        command.ignore_wait_exit = True
+        jobs = []
+        # call the process_jobs
+        self.starter.process_job(command, jobs)
+        # starting methods are not called but job is in list though
+        self.assertListEqual([command], jobs)
+        self.assertEqual(0, mocked_pusher.call_count)
+        # failure method is called
+        self.assertEqual([call('sample_test_1:xlogo', ProcessStates.FATAL, 'no resource available')],
+                         mocked_force.call_args_list)
 
     def test_start_process(self):
         """ Test the start_process method. """
-        from supvisors.commander import Starter
         from supvisors.ttypes import StartingStrategies
-        starter = Starter(self.supvisors)
         # get any process
         xlogo_command = self._get_test_command('xlogo')
         # test failure
-        with patch.object(starter, 'process_job',
-                          return_value=False) as mocked_jobs:
-            start_result = starter.start_process(StartingStrategies.CONFIG,
-                                                 xlogo_command.process,
-                                                 'extra_args')
-            self.assertEqual(StartingStrategies.CONFIG, starter.strategy)
-            self.assertDictEqual({}, starter.current_jobs)
+        with patch.object(self.starter, 'process_job', return_value=False) as mocked_jobs:
+            start_result = self.starter.start_process(StartingStrategies.CONFIG,
+                                                      xlogo_command.process,
+                                                      'extra_args')
+            self.assertDictEqual({}, self.starter.current_jobs)
             self.assertEqual(1, mocked_jobs.call_count)
             args, kwargs = mocked_jobs.call_args
+            self.assertEqual(StartingStrategies.CONFIG, args[0].strategy)
             self.assertEqual('extra_args', args[0].extra_args)
             self.assertTrue(args[0].ignore_wait_exit)
             self.assertTrue(start_result)
@@ -772,32 +800,29 @@ def success_job(*args, **kwargs):
             args[1].append(args[0])
             return True
 
-        with patch.object(starter, 'process_job',
-                          side_effect=success_job) as mocked_jobs:
-            start_result = starter.start_process(StartingStrategies.CONFIG,
-                                                 xlogo_command.process,
-                                                 'extra_args')
-            self.assertEqual(StartingStrategies.CONFIG, starter.strategy)
+        with patch.object(self.starter, 'process_job', side_effect=success_job) as mocked_jobs:
+            start_result = self.starter.start_process(StartingStrategies.CONFIG,
+                                                      xlogo_command.process,
+                                                      'extra_args')
             self.assertEqual(1, mocked_jobs.call_count)
             args1, _ = mocked_jobs.call_args
+            self.assertEqual(StartingStrategies.CONFIG, args1[0].strategy)
             self.assertEqual('extra_args', args1[0].extra_args)
             self.assertTrue(args1[0].ignore_wait_exit)
-            self.assertDictEqual({'sample_test_1': [args1[0]]},
-                                 starter.current_jobs)
+            self.assertDictEqual({'sample_test_1': [args1[0]]}, self.starter.current_jobs)
             self.assertFalse(start_result)
             mocked_jobs.reset_mock()
             # get any other process
             yeux_command = self._get_test_command('yeux_00')
             # test that success complements current_jobs
-            start_result = starter.start_process(2, yeux_command.process, '')
-            self.assertEqual(2, starter.strategy)
+            start_result = self.starter.start_process(2, yeux_command.process, '')
             self.assertEqual(1, mocked_jobs.call_count)
             args2, _ = mocked_jobs.call_args
+            self.assertEqual(2, args2[0].strategy)
             self.assertEqual('', args2[0].extra_args)
             self.assertTrue(args2[0].ignore_wait_exit)
-            self.assertDictEqual({'sample_test_1': [args1[0]],
-                                  'sample_test_2': [args2[0]]},
-                                 starter.current_jobs)
+            self.assertDictEqual({'sample_test_1': [args1[0]], 'sample_test_2': [args2[0]]},
+                                 self.starter.current_jobs)
             self.assertFalse(start_result)
 
     def test_default_start_process(self):
@@ -805,51 +830,45 @@ def test_default_start_process(self):
         from supvisors.commander import Starter
         starter = Starter(self.supvisors)
         with patch.object(starter, 'start_process', return_value=True) as mocked_start:
-            # test that default_start_process just calls start_process
-            # with the default strategy
+            # test that default_start_process just calls start_process with the default strategy
             process = Mock()
             result = starter.default_start_process(process)
             self.assertTrue(result)
-            self.assertEqual([call(self.supvisors.options.starting_strategy,
-                                   process)], mocked_start.call_args_list)
+            self.assertEqual([call(self.supvisors.options.starting_strategy, process)],
+                             mocked_start.call_args_list)
 
     def test_start_application(self):
         """ Test the start_application method. """
         from supvisors.application import ApplicationStatus
-        from supvisors.commander import Starter
-        from supvisors.ttypes import ApplicationStates
-        starter = Starter(self.supvisors)
+        from supvisors.ttypes import ApplicationStates, StartingStrategies
         # create application start_sequence
-        application = ApplicationStatus('sample_test_1', self.supvisors.logger)
+        appli = ApplicationStatus('sample_test_1', self.supvisors.logger)
         for command in self.command_list:
             if command.process.application_name == 'sample_test_1':
-                application.start_sequence.setdefault(
-                    len(command.process.namespec()) % 3,
-                    []).append(command.process)
+                appli.start_sequence.setdefault(len(command.process.namespec()) % 3, []).append(command.process)
         # patch the starter.process_application_jobs
-        with patch.object(starter, 'process_application_jobs') as mocked_jobs:
+        with patch.object(self.starter, 'process_application_jobs') as mocked_jobs:
             # test start_application on a running application
-            application._state = ApplicationStates.RUNNING
-            test_result = starter.start_application(1, application)
+            appli._state = ApplicationStates.RUNNING
+            test_result = self.starter.start_application(1, appli)
             self.assertTrue(test_result)
-            self.assertEqual(1, starter.strategy)
-            self.assertDictEqual({}, starter.planned_sequence)
-            self.assertDictEqual({}, starter.planned_jobs)
+            self.assertDictEqual({}, self.starter.planned_sequence)
+            self.assertDictEqual({}, self.starter.planned_jobs)
             self.assertEqual(0, mocked_jobs.call_count)
             # test start_application on a stopped application
-            application._state = ApplicationStates.STOPPED
-            test_result = starter.start_application(1, application)
+            appli._state = ApplicationStates.STOPPED
+            test_result = self.starter.start_application(1, appli)
             self.assertFalse(test_result)
-            self.assertEqual(1, starter.strategy)
-            # only planned jobs and not current jobs because of
-            # process_application_jobs patch
-            self.assertDictEqual({}, starter.planned_sequence)
-            self.assertDictEqual({'sample_test_1': {
-                1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
-                2: ['sample_test_1:xclock']}},
-                starter.printable_planned_jobs())
-            self.assertEqual([call('sample_test_1')],
-                             mocked_jobs.call_args_list)
+            # only planned jobs and not current jobs because of process_application_jobs patch
+            self.assertDictEqual({}, self.starter.planned_sequence)
+            self.assertDictEqual({'sample_test_1': {1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
+                                                    2: ['sample_test_1:xclock']}},
+                                 self.starter.printable_planned_jobs())
+            self.assertEqual([call('sample_test_1')], mocked_jobs.call_args_list)
+            # check strategy applied
+            for proc_list in self.starter.planned_jobs['sample_test_1'].values():
+                for proc in proc_list:
+                    self.assertEqual(StartingStrategies.LESS_LOADED, proc.strategy)
 
     def test_default_start_application(self):
         """ Test the default_start_application method. """
@@ -857,13 +876,12 @@ def test_default_start_application(self):
         starter = Starter(self.supvisors)
         with patch.object(starter, 'start_application',
                           return_value=True) as mocked_start:
-            # test that default_start_application just calls start_application
-            # with the default strategy
+            # test that default_start_application just calls start_application with the default strategy
             application = Mock()
             result = starter.default_start_application(application)
             self.assertTrue(result)
-            self.assertEqual([call(self.supvisors.options.starting_strategy,
-                                   application)], mocked_start.call_args_list)
+            self.assertEqual([call(self.supvisors.options.starting_strategy, application)],
+                             mocked_start.call_args_list)
 
     def test_start_applications(self):
         """ Test the start_applications method. """
@@ -880,16 +898,13 @@ def test_start_applications(self):
         application.rules.start_sequence = 2
         for command in self.command_list:
             if command.process.application_name == 'sample_test_2':
-                application.start_sequence.setdefault(
-                    len(command.process.namespec()) % 3,
-                    []).append(command.process)
+                application.start_sequence.setdefault(len(command.process.namespec()) % 3, []).append(command.process)
         self.supvisors.context.applications['sample_test_2'] = application
         # create one stopped application with a start_sequence == 0
         application = ApplicationStatus('crash', self.supvisors.logger)
         application.rules.start_sequence = 0
         self.supvisors.context.applications['crash'] = application
-        # call starter start_applications and check that only sample_test_2
-        # is triggered
+        # call starter start_applications and check that only sample_test_2 is triggered
         with patch.object(starter, 'process_application_jobs') as mocked_jobs:
             starter.start_applications()
             self.assertDictEqual({}, starter.planned_sequence)
@@ -897,283 +912,160 @@ def test_start_applications(self):
                                  starter.printable_planned_jobs())
             # current jobs is empty because of process_application_jobs mocking
             self.assertDictEqual({}, starter.printable_current_jobs())
-            self.assertEqual(0, starter.strategy)
             self.assertEqual(1, mocked_jobs.call_count)
             self.assertEqual(call('sample_test_2'), mocked_jobs.call_args)
 
 
-class StopperTest(CompatTestCase):
+class StopperTest(CommanderContextTest):
     """ Test case for the Stopper class of the commander module. """
 
     def setUp(self):
         """ Create a Supvisors-like structure and test processes. """
-        from supvisors.process import ProcessStatus
-        self.supvisors = MockedSupvisors()
-        # store list for tests
-        self.command_list = []
-        for info in database_copy():
-            command = _create_process_command(
-                info['group'], info['name'], self.supvisors)
-            command.process.add_info('10.0.0.1', info)
-            self.command_list.append(command)
-
-    def _get_test_command(self, process_name):
-        """ Return the first process corresponding to process_name. """
-        return next(command for command in self.command_list
-                    if command.process.process_name == process_name)
+        CommanderContextTest.setUp(self)
+        from supvisors.commander import Stopper
+        self.stopper = Stopper(self.supvisors)
 
     def test_creation(self):
         """ Test the values set at construction. """
-        from supvisors.commander import Commander, Stopper
-        stopper = Stopper(self.supvisors)
-        self.assertIsInstance(stopper, Commander)
+        from supvisors.commander import Commander
+        self.assertIsInstance(self.stopper, Commander)
 
-    @patch('supvisors.commander.Stopper.force_process_unknown')
-    def test_check_stopping(self, mocked_force):
+    @patch('supvisors.commander.Commander.check_progress', return_value=True)
+    def test_check_stopping(self, mocked_check: Mock):
         """ Test the check_stopping method. """
-        from supvisors.commander import Stopper
-        stopper = Stopper(self.supvisors)
-        # test with no jobs
-        completed = stopper.check_stopping()
-        self.assertTrue(completed)
-        # set test current_jobs
-        # xfontsel is RUNNING, xlogo is STOPPED, yeux_00 is EXITED, yeux_01 is RUNNING
-        stopper.current_jobs = {
-            'sample_test_1': [self._get_test_command('xfontsel'),
-                              self._get_test_command('xlogo')],
-            'sample_test_2': [self._get_test_command('yeux_00'),
-                              self._get_test_command('yeux_01')]}
-        # assign request_time to processes in current_jobs
-        for command_list in stopper.current_jobs.values():
-            for command in command_list:
-                command.request_time = time.time()
-        # processes have a recent request time: nothing done
-        completed = stopper.check_stopping()
-        self.assertFalse(completed)
-        self.assertDictEqual({'sample_test_1': ['sample_test_1:xfontsel',
-                                                'sample_test_1:xlogo'],
-                              'sample_test_2': ['sample_test_2:yeux_00',
-                                                'sample_test_2:yeux_01']},
-                             stopper.printable_current_jobs())
-        self.assertEqual(0, mocked_force.call_count)
-        # re-assign request_time to processes in current_jobs
-        for command_list in stopper.current_jobs.values():
-            for command in command_list:
-                command.process.last_event_time = 0
-                command.request_time = 0
-        # processes have an old request time: process_failure called
-        completed = stopper.check_stopping()
-        self.assertFalse(completed)
-        self.assertDictEqual({'sample_test_1': ['sample_test_1:xfontsel',
-                                                'sample_test_1:xlogo'],
-                              'sample_test_2': ['sample_test_2:yeux_00',
-                                                'sample_test_2:yeux_01']},
-                             stopper.printable_current_jobs())
-        str_error = 'Still running 5 seconds after stop request'
-        self.assertItemsEqual([call('sample_test_1:xfontsel', str_error),
-                               call('sample_test_2:yeux_01', str_error)],
-                              mocked_force.call_args_list)
-
-    @patch('supvisors.commander.Stopper.process_application_jobs')
-    @patch('supvisors.commander.Stopper.initial_jobs')
-    def test_on_event(self, mocked_init, mocked_process):
+        from supvisors.ttypes import ProcessStates
+        self.assertTrue(self.stopper.check_stopping())
+        self.assertEqual([call('running', ProcessStates.UNKNOWN)], mocked_check.call_args_list)
+
+    @patch('supvisors.commander.Stopper.after_event')
+    def test_on_event(self, mocked_after: Mock):
         """ Test the on_event method. """
         from supvisors.application import ApplicationStatus
-        from supvisors.commander import Stopper
         from supvisors.process import ProcessStatus
         from supvisors.ttypes import ProcessStates
-        stopper = Stopper(self.supvisors)
-        # set test planned_jobs and current_jobs
-        stopper.planned_jobs = {'sample_test_2': {}}
+        # set context in current_jobs
         for command in self.command_list:
-            stopper.current_jobs.setdefault(
-                command.process.application_name,
-                []).append(command)
+            self.stopper.current_jobs.setdefault(command.process.application_name, []).append(command)
         # add application context
-        application = ApplicationStatus('sample_test_1',
-                                        self.supvisors.logger)
+        application = ApplicationStatus('sample_test_1', self.supvisors.logger)
         self.supvisors.context.applications['sample_test_1'] = application
-        application = ApplicationStatus('sample_test_2',
-                                        self.supvisors.logger)
+        application = ApplicationStatus('sample_test_2', self.supvisors.logger)
         self.supvisors.context.applications['sample_test_2'] = application
-        # try with unknown process
-        process = ProcessStatus('dummy_application',
-                                'dummy_process',
-                                self.supvisors)
-        stopper.on_event(process)
-        self.assertEqual(0, mocked_process.call_count)
-        self.assertEqual(0, mocked_init.call_count)
+        # try with unknown application
+        process = ProcessStatus('dummy_application', 'dummy_process', self.supvisors)
+        self.stopper.on_event(process)
+        self.assertFalse(mocked_after.called)
         # with sample_test_1 application
         # test STOPPED process
         command = self._get_test_command('xlogo')
-        self.assertIn(command, stopper.current_jobs['sample_test_1'])
-        stopper.on_event(command.process)
-        self.assertNotIn(command.process, stopper.current_jobs['sample_test_1'])
-        self.assertEqual(0, mocked_process.call_count)
-        self.assertEqual(0, mocked_init.call_count)
+        self.assertIn(command, self.stopper.current_jobs['sample_test_1'])
+        self.stopper.on_event(command.process)
+        self.assertNotIn(command.process, self.stopper.current_jobs['sample_test_1'])
+        self.assertFalse(mocked_after.called)
         # test STOPPING process: xclock
         command = self._get_test_command('xclock')
-        self.assertIn(command, stopper.current_jobs['sample_test_1'])
-        stopper.on_event(command.process)
-        self.assertIn(command, stopper.current_jobs['sample_test_1'])
-        self.assertEqual(0, mocked_process.call_count)
-        self.assertEqual(0, mocked_init.call_count)
+        self.assertIn(command, self.stopper.current_jobs['sample_test_1'])
+        self.stopper.on_event(command.process)
+        self.assertIn(command, self.stopper.current_jobs['sample_test_1'])
+        self.assertFalse(mocked_after.called)
         # test RUNNING process: xfontsel
         command = self._get_test_command('xfontsel')
-        self.assertIn(command, stopper.current_jobs['sample_test_1'])
-        stopper.on_event(command.process)
-        self.assertIn('sample_test_1', stopper.current_jobs.keys())
-        self.assertEqual(0, mocked_process.call_count)
-        self.assertEqual(0, mocked_init.call_count)
+        self.assertIn(command, self.stopper.current_jobs['sample_test_1'])
+        self.stopper.on_event(command.process)
+        self.assertIn('sample_test_1', self.stopper.current_jobs.keys())
+        self.assertFalse(mocked_after.called)
         # with sample_test_2 application
         # test EXITED / expected process: yeux_00
         command = self._get_test_command('yeux_00')
-        self.assertIn(command, stopper.current_jobs['sample_test_2'])
-        stopper.on_event(command.process)
-        self.assertNotIn(command, stopper.current_jobs['sample_test_2'])
-        self.assertEqual(0, mocked_process.call_count)
-        self.assertEqual(0, mocked_init.call_count)
+        self.assertIn(command, self.stopper.current_jobs['sample_test_2'])
+        self.stopper.on_event(command.process)
+        self.assertNotIn(command, self.stopper.current_jobs['sample_test_2'])
+        self.assertFalse(mocked_after.called)
         # test FATAL process: sleep
         command = self._get_test_command('sleep')
-        self.assertIn(command, stopper.current_jobs['sample_test_2'])
-        stopper.on_event(command.process)
-        self.assertIn('sample_test_2', stopper.current_jobs.keys())
-        self.assertEqual(0, mocked_process.call_count)
-        self.assertEqual(0, mocked_init.call_count)
+        self.assertIn(command, self.stopper.current_jobs['sample_test_2'])
+        self.stopper.on_event(command.process)
+        self.assertIn('sample_test_2', self.stopper.current_jobs.keys())
+        self.assertFalse(mocked_after.called)
         # test RUNNING process: yeux_01
         command = self._get_test_command('yeux_01')
-        self.assertIn(command, stopper.current_jobs['sample_test_2'])
-        stopper.on_event(command.process)
-        self.assertIn(command, stopper.current_jobs['sample_test_2'])
-        self.assertEqual(0, mocked_process.call_count)
-        self.assertEqual(0, mocked_init.call_count)
+        self.assertIn(command, self.stopper.current_jobs['sample_test_2'])
+        self.stopper.on_event(command.process)
+        self.assertIn(command, self.stopper.current_jobs['sample_test_2'])
+        self.assertFalse(mocked_after.called)
         # force yeux_01 state and re-test
         command.process._state = ProcessStates.STOPPED
-        self.assertIn(command, stopper.current_jobs['sample_test_2'])
-        stopper.on_event(command.process)
-        self.assertNotIn('sample_test_2', stopper.current_jobs.keys())
-        self.assertEqual(1, mocked_process.call_count)
-        self.assertEqual(0, mocked_init.call_count)
+        self.assertIn(command, self.stopper.current_jobs['sample_test_2'])
+        self.stopper.on_event(command.process)
+        self.assertNotIn(command, self.stopper.current_jobs['sample_test_2'])
+        self.assertEqual([call('sample_test_2')], mocked_after.call_args_list)
         # reset resources
-        mocked_process.reset_mock()
+        mocked_after.reset_mock()
         # with crash application
         # test STARTING process: late_segv
         command = self._get_test_command('late_segv')
-        self.assertIn(command, stopper.current_jobs['crash'])
-        stopper.on_event(command.process)
-        self.assertIn(command, stopper.current_jobs['crash'])
-        self.assertEqual(0, mocked_process.call_count)
-        self.assertEqual(0, mocked_init.call_count)
+        self.assertIn(command, self.stopper.current_jobs['crash'])
+        self.stopper.on_event(command.process)
+        self.assertIn(command, self.stopper.current_jobs['crash'])
+        self.assertFalse(mocked_after.called)
         # test BACKOFF process: segv (last process of this application)
         command = self._get_test_command('segv')
-        self.assertIn(command, stopper.current_jobs['crash'])
-        stopper.on_event(command.process)
-        self.assertIn(command, stopper.current_jobs['crash'])
-        self.assertEqual(0, mocked_process.call_count)
-        self.assertEqual(0, mocked_init.call_count)
-        # with firefox application
-        # empty planned_jobs to trigger another behaviour
-        stopper.planned_jobs = {}
-        # test EXITED / unexpected process: firefox
-        command = self._get_test_command('firefox')
-        self.assertIn(command, stopper.current_jobs['firefox'])
-        stopper.on_event(command.process)
-        self.assertNotIn('firefox', stopper.current_jobs.keys())
-        self.assertEqual(0, mocked_process.call_count)
-        self.assertEqual(1, mocked_init.call_count)
+        self.assertIn(command, self.stopper.current_jobs['crash'])
+        self.stopper.on_event(command.process)
+        self.assertIn(command, self.stopper.current_jobs['crash'])
+        self.assertFalse(mocked_after.called)
 
     def test_store_application_stop_sequence(self):
         """ Test the store_application_stop_sequence method. """
         from supvisors.application import ApplicationStatus
-        from supvisors.commander import Stopper
-        stopper = Stopper(self.supvisors)
         # create 2 application start_sequences
-        application1 = ApplicationStatus('sample_test_1', self.supvisors.logger)
+        appli1 = ApplicationStatus('sample_test_1', self.supvisors.logger)
         for command in self.command_list:
             if command.process.application_name == 'sample_test_1':
-                application1.stop_sequence.setdefault(
-                    len(command.process.namespec()) % 3,
-                    []).append(command.process)
-        application2 = ApplicationStatus('sample_test_2', self.supvisors.logger)
+                appli1.stop_sequence.setdefault(len(command.process.namespec()) % 3, []).append(command.process)
+        appli2 = ApplicationStatus('sample_test_2', self.supvisors.logger)
         for command in self.command_list:
             if command.process.application_name == 'sample_test_2':
-                application2.stop_sequence.setdefault(
-                    len(command.process.namespec()) % 3,
-                    []).append(command.process)
+                appli2.stop_sequence.setdefault( len(command.process.namespec()) % 3, []).append(command.process)
         # call method and check result
-        stopper.store_application_stop_sequence(application1)
+        self.stopper.store_application_stop_sequence(appli1)
         # check application sequence in stopper planned sequence
-        self.assertDictEqual(
-            {0: {'sample_test_1': {
-                1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
-                2: ['sample_test_1:xclock']}}},
-            stopper.printable_planned_sequence())
+        self.assertDictEqual({0: {'sample_test_1': {1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
+                                                    2: ['sample_test_1:xclock']}}},
+                             self.stopper.printable_planned_sequence())
         # call method a second time and check result
-        stopper.store_application_stop_sequence(application2)
+        self.stopper.store_application_stop_sequence(appli2)
         # check application sequence in stopper planned sequence
-        self.assertDictEqual(
-            {0: {'sample_test_1': {
-                1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
-                2: ['sample_test_1:xclock']},
-                'sample_test_2': {
-                    0: ['sample_test_2:yeux_00', 'sample_test_2:yeux_01'],
-                    1: ['sample_test_2:sleep']}}},
-            stopper.printable_planned_sequence())
-
-    def test_force_process_unknown(self):
-        """ Test the force_process_unknown method. """
-        from supvisors.commander import Stopper
-        # create the instance
-        stopper = Stopper(self.supvisors)
-        # get patches
-        mocked_listener = self.supvisors.listener.force_process_unknown
-        mocked_source = self.supvisors.info_source.force_process_unknown
-        # test with no info_source KeyError
-        stopper.force_process_unknown('proc', 'any reason')
-        self.assertEqual(1, mocked_source.call_count)
-        self.assertEqual([call('proc', 'any reason')],
-                         mocked_source.call_args_list)
-        self.assertEqual(0, mocked_listener.call_count)
-        mocked_source.reset_mock()
-        # test force_process_unknown with info_source KeyError
-        mocked_source.side_effect = KeyError
-        stopper.force_process_unknown('proc', 'any reason')
-        self.assertEqual(1, mocked_source.call_count)
-        self.assertEqual([call('proc', 'any reason')],
-                         mocked_source.call_args_list)
-        self.assertEqual([call('proc')], mocked_listener.call_args_list)
-
+        self.assertDictEqual({0: {'sample_test_1': {1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
+                                                    2: ['sample_test_1:xclock']},
+                                  'sample_test_2': {0: ['sample_test_2:yeux_00', 'sample_test_2:yeux_01'],
+                                                    1: ['sample_test_2:sleep']}}},
+                             self.stopper.printable_planned_sequence())
     def test_process_job(self):
         """ Test the process_job method. """
-        from supvisors.commander import Stopper
-        stopper = Stopper(self.supvisors)
         # get patches
         mocked_pusher = self.supvisors.zmq.pusher.send_stop_process
         # test with stopped process
         process = self._get_test_command('xlogo')
         jobs = []
-        stopper.process_job(process, jobs)
+        self.stopper.process_job(process, jobs)
         self.assertListEqual([], jobs)
         self.assertEqual(0, mocked_pusher.call_count)
         # test with running process
         process = self._get_test_command('xfontsel')
         jobs = []
-        stopper.process_job(process, jobs)
+        self.stopper.process_job(process, jobs)
         self.assertListEqual([process], jobs)
-        self.assertEqual([call('10.0.0.1', 'sample_test_1:xfontsel')],
-                         mocked_pusher.call_args_list)
+        self.assertEqual([call('10.0.0.1', 'sample_test_1:xfontsel')], mocked_pusher.call_args_list)
 
     def test_stop_process(self):
         """ Test the stop_process method. """
-        from supvisors.commander import Stopper
-        stopper = Stopper(self.supvisors)
         # get any process
         xlogo_command = self._get_test_command('xlogo')
         # test failure
-        with patch.object(stopper, 'process_job',
-                          return_value=False) as mocked_jobs:
-            start_result = stopper.stop_process(xlogo_command.process)
-            self.assertDictEqual({}, stopper.current_jobs)
+        with patch.object(self.stopper, 'process_job', return_value=False) as mocked_jobs:
+            start_result = self.stopper.stop_process(xlogo_command.process)
+            self.assertDictEqual({}, self.stopper.current_jobs)
             self.assertEqual(1, mocked_jobs.call_count)
             self.assertTrue(start_result)
 
@@ -1182,109 +1074,93 @@ def success_job(*args, **kwargs):
             args[1].append(args[0])
             return True
 
-        with patch.object(stopper, 'process_job',
-                          side_effect=success_job) as mocked_jobs:
-            start_result = stopper.stop_process(xlogo_command.process)
+        with patch.object(self.stopper, 'process_job', side_effect=success_job) as mocked_jobs:
+            start_result = self.stopper.stop_process(xlogo_command.process)
             self.assertEqual(1, mocked_jobs.call_count)
             args1, _ = mocked_jobs.call_args
-            self.assertDictEqual({'sample_test_1': [args1[0]]},
-                                 stopper.current_jobs)
+            self.assertDictEqual({'sample_test_1': [args1[0]]}, self.stopper.current_jobs)
             self.assertFalse(start_result)
             mocked_jobs.reset_mock()
             # get any other process
             yeux_command = self._get_test_command('yeux_00')
             # test that success complements current_jobs
-            start_result = stopper.stop_process(yeux_command.process)
+            start_result = self.stopper.stop_process(yeux_command.process)
             self.assertEqual(1, mocked_jobs.call_count)
             args2, _ = mocked_jobs.call_args
-            self.assertDictEqual({'sample_test_1': [args1[0]],
-                                  'sample_test_2': [args2[0]]},
-                                 stopper.current_jobs)
+            self.assertDictEqual({'sample_test_1': [args1[0]], 'sample_test_2': [args2[0]]},
+                                 self.stopper.current_jobs)
             self.assertFalse(start_result)
 
     def test_stop_application(self):
         """ Test the stop_application method. """
         from supvisors.application import ApplicationStatus
-        from supvisors.commander import Stopper
         from supvisors.ttypes import ApplicationStates
-        stopper = Stopper(self.supvisors)
         # create application start_sequence
-        application = ApplicationStatus('sample_test_1', self.supvisors.logger)
+        appli = ApplicationStatus('sample_test_1', self.supvisors.logger)
         for command in self.command_list:
             if command.process.application_name == 'sample_test_1':
-                application.stop_sequence.setdefault(
-                    len(command.process.namespec()) % 3,
-                    []).append(command.process)
+                appli.stop_sequence.setdefault(len(command.process.namespec()) % 3, []).append(command.process)
 
         # patch the starter.process_application_jobs
         def success_job(*args, **kwargs):
             args[1].append(args[0])
 
-        with patch.object(stopper, 'process_job',
-                          side_effect=success_job) as mocked_jobs:
+        with patch.object(self.stopper, 'process_job', side_effect=success_job) as mocked_jobs:
             # test start_application on a stopped application
-            application._state = ApplicationStates.STOPPED
-            test_result = stopper.stop_application(application)
+            appli._state = ApplicationStates.STOPPED
+            test_result = self.stopper.stop_application(appli)
             self.assertTrue(test_result)
-            self.assertDictEqual({}, stopper.planned_sequence)
-            self.assertDictEqual({}, stopper.planned_jobs)
-            self.assertDictEqual({}, stopper.current_jobs)
+            self.assertDictEqual({}, self.stopper.planned_sequence)
+            self.assertDictEqual({}, self.stopper.planned_jobs)
+            self.assertDictEqual({}, self.stopper.current_jobs)
             self.assertEqual(0, mocked_jobs.call_count)
             # test start_application on a stopped application
-            application._state = ApplicationStates.RUNNING
-            test_result = stopper.stop_application(application)
+            appli._state = ApplicationStates.RUNNING
+            test_result = self.stopper.stop_application(appli)
             self.assertFalse(test_result)
             # only planned jobs and not current jobs because of
             # process_application_jobs patch
-            self.assertDictEqual({}, stopper.planned_sequence)
-            self.assertDictEqual({'sample_test_1': {2: ['sample_test_1:xclock']}}, stopper.printable_planned_jobs())
+            self.assertDictEqual({}, self.stopper.planned_sequence)
+            self.assertDictEqual({'sample_test_1': {2: ['sample_test_1:xclock']}},
+                                 self.stopper.printable_planned_jobs())
             self.assertDictEqual({'sample_test_1': ['sample_test_1:xfontsel', 'sample_test_1:xlogo']},
-                                 stopper.printable_current_jobs())
+                                 self.stopper.printable_current_jobs())
             self.assertEqual(2, mocked_jobs.call_count)
 
     def test_stop_applications(self):
         """ Test the stop_applications method. """
         from supvisors.application import ApplicationStatus
-        from supvisors.commander import Stopper
         from supvisors.ttypes import ApplicationStates
-        stopper = Stopper(self.supvisors)
         # create one running application with a start_sequence > 0
-        application = ApplicationStatus('sample_test_1', self.supvisors.logger)
-        application._state = ApplicationStates.RUNNING
-        application.rules.stop_sequence = 2
-        self.supvisors.context.applications['sample_test_1'] = application
+        appli = ApplicationStatus('sample_test_1', self.supvisors.logger)
+        appli._state = ApplicationStates.RUNNING
+        appli.rules.stop_sequence = 2
+        self.supvisors.context.applications['sample_test_1'] = appli
         for command in self.command_list:
             if command.process.application_name == 'sample_test_1':
-                application.stop_sequence.setdefault(
-                    len(command.process.namespec()) % 3,
-                    []).append(command.process)
+                appli.stop_sequence.setdefault(len(command.process.namespec()) % 3, []).append(command.process)
         # create one stopped application
-        application = ApplicationStatus('sample_test_2', self.supvisors.logger)
-        self.supvisors.context.applications['sample_test_2'] = application
+        appli = ApplicationStatus('sample_test_2', self.supvisors.logger)
+        self.supvisors.context.applications['sample_test_2'] = appli
         # create one running application with a start_sequence == 0
-        application = ApplicationStatus('crash', self.supvisors.logger)
-        application._state = ApplicationStates.RUNNING
-        application.rules.stop_sequence = 0
-        self.supvisors.context.applications['crash'] = application
+        appli = ApplicationStatus('crash', self.supvisors.logger)
+        appli._state = ApplicationStates.RUNNING
+        appli.rules.stop_sequence = 0
+        self.supvisors.context.applications['crash'] = appli
         for command in self.command_list:
             if command.process.application_name == 'crash':
-                application.stop_sequence.setdefault(
-                    len(command.process.namespec()) % 3,
-                    []).append(command.process)
+                appli.stop_sequence.setdefault(len(command.process.namespec()) % 3, []).append(command.process)
         # call starter start_applications and check that only sample_test_2
         # is triggered
-        with patch.object(stopper, 'process_application_jobs') as mocked_jobs:
-            stopper.stop_applications()
-            self.assertDictEqual({2: {'sample_test_1': {
-                1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
-                2: ['sample_test_1:xclock']}}},
-                stopper.printable_planned_sequence())
-            self.assertDictEqual({'crash': {
-                0: ['crash:late_segv'],
-                1: ['crash:segv']}},
-                stopper.printable_planned_jobs())
+        with patch.object(self.stopper, 'process_application_jobs') as mocked_jobs:
+            self.stopper.stop_applications()
+            self.assertDictEqual({2: {'sample_test_1': {1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
+                                                        2: ['sample_test_1:xclock']}}},
+                                 self.stopper.printable_planned_sequence())
+            self.assertDictEqual({'crash': {0: ['crash:late_segv'], 1: ['crash:segv']}},
+                                 self.stopper.printable_planned_jobs())
             # current jobs is empty because of process_application_jobs mocking
-            self.assertDictEqual({}, stopper.printable_current_jobs())
+            self.assertDictEqual({}, self.stopper.printable_current_jobs())
             self.assertEqual(1, mocked_jobs.call_count)
             self.assertEqual(call('crash'), mocked_jobs.call_args)
 
diff --git a/supvisors/tests/test_context.py b/supvisors/tests/test_context.py
index 534a44d1..ef11b63a 100644
--- a/supvisors/tests/test_context.py
+++ b/supvisors/tests/test_context.py
@@ -76,13 +76,14 @@ def test_addresses_by_state(self):
         from supvisors.ttypes import AddressStates
         context = Context(self.supvisors)
         # test initial states
-        self.assertListEqual(DummyAddressMapper().addresses, context.unknown_addresses())
-        self.assertListEqual([], context.running_addresses())
-        self.assertListEqual([], context.isolating_addresses())
-        self.assertListEqual([], context.isolation_addresses())
-        self.assertListEqual([], context.addresses_by_states([AddressStates.RUNNING, AddressStates.ISOLATED]))
-        self.assertListEqual([], context.addresses_by_states([AddressStates.SILENT]))
-        self.assertListEqual(DummyAddressMapper().addresses, context.addresses_by_states([AddressStates.UNKNOWN]))
+        self.assertEqual(DummyAddressMapper().addresses, context.unknown_addresses())
+        self.assertEqual([], context.unknown_forced_addresses())
+        self.assertEqual([], context.running_addresses())
+        self.assertEqual([], context.isolating_addresses())
+        self.assertEqual([], context.isolation_addresses())
+        self.assertEqual([], context.addresses_by_states([AddressStates.RUNNING, AddressStates.ISOLATED]))
+        self.assertEqual([], context.addresses_by_states([AddressStates.SILENT]))
+        self.assertEqual(DummyAddressMapper().addresses, context.addresses_by_states([AddressStates.UNKNOWN]))
         # change states
         context.addresses['127.0.0.1']._state = AddressStates.RUNNING
         context.addresses['10.0.0.1']._state = AddressStates.SILENT
@@ -90,17 +91,41 @@ def test_addresses_by_state(self):
         context.addresses['10.0.0.3']._state = AddressStates.ISOLATED
         context.addresses['10.0.0.4']._state = AddressStates.RUNNING
         # test new states
-        self.assertListEqual(['10.0.0.5'], context.unknown_addresses())
-        self.assertListEqual(['127.0.0.1', '10.0.0.4'], context.running_addresses())
-        self.assertListEqual(['10.0.0.2'], context.isolating_addresses())
-        self.assertListEqual(['10.0.0.2', '10.0.0.3'], context.isolation_addresses())
-        self.assertListEqual(['127.0.0.1', '10.0.0.3', '10.0.0.4'],
-                             context.addresses_by_states([AddressStates.RUNNING,
-                                                          AddressStates.ISOLATED]))
-        self.assertListEqual(['10.0.0.1'], context.addresses_by_states([AddressStates.SILENT]))
-        self.assertListEqual(['10.0.0.5'], context.addresses_by_states([AddressStates.UNKNOWN]))
+        self.assertEqual(['10.0.0.5'], context.unknown_addresses())
+        self.assertEqual([], context.unknown_forced_addresses())
+        self.assertEqual(['127.0.0.1', '10.0.0.4'], context.running_addresses())
+        self.assertEqual(['10.0.0.2'], context.isolating_addresses())
+        self.assertEqual(['10.0.0.2', '10.0.0.3'], context.isolation_addresses())
+        self.assertEqual(['127.0.0.1', '10.0.0.3', '10.0.0.4'],
+                         context.addresses_by_states([AddressStates.RUNNING, AddressStates.ISOLATED]))
+        self.assertEqual(['10.0.0.1'], context.addresses_by_states([AddressStates.SILENT]))
+        self.assertEqual(['10.0.0.5'], context.addresses_by_states([AddressStates.UNKNOWN]))
 
-    def random_fill_processes(self, context):
+    def test_unknown_forced_addresses(self):
+        """ Test the access to addresses in unknown state. """
+        from supvisors.context import Context
+        from supvisors.ttypes import AddressStates
+        self.supvisors.options.force_synchro_if = ['10.0.0.1', '10.0.0.4']
+        context = Context(self.supvisors)
+        # test initial states
+        self.assertEqual(DummyAddressMapper().addresses, context.unknown_addresses())
+        self.assertEqual(['10.0.0.1', '10.0.0.4'], context.unknown_forced_addresses())
+        # change states
+        context.addresses['127.0.0.1']._state = AddressStates.RUNNING
+        context.addresses['10.0.0.2']._state = AddressStates.ISOLATING
+        context.addresses['10.0.0.3']._state = AddressStates.ISOLATED
+        context.addresses['10.0.0.4']._state = AddressStates.RUNNING
+        # test new states
+        self.assertEqual(['10.0.0.1', '10.0.0.5'], context.unknown_addresses())
+        self.assertEqual(['10.0.0.1'], context.unknown_forced_addresses())
+        # change states
+        context.addresses['10.0.0.1']._state = AddressStates.SILENT
+        # test new states
+        self.assertEqual(['10.0.0.5'], context.unknown_addresses())
+        self.assertEqual([], context.unknown_forced_addresses())
+
+    @staticmethod
+    def random_fill_processes( context):
         """ Pushes ProcessInfoDatabase process info in AddressStatus. """
         for info in database_copy():
             process = context.setdefault_process(info)
@@ -393,173 +418,179 @@ def test_authorization(self):
                 self.assertEqual(AddressStates.SILENT,
                                  context.addresses['10.0.0.5'].state)
 
-    def test_tick_event(self):
+    @patch('supvisors.context.time', return_value=3600)
+    def test_tick_event(self, _):
         """ Test the handling of a timer event. """
         from supvisors.context import Context
         from supvisors.ttypes import AddressStates
         context = Context(self.supvisors)
-        with patch.object(self.supvisors.zmq.pusher,
-                          'send_check_address') as mocked_check:
-            with patch.object(self.supvisors.zmq.publisher,
-                              'send_address_status') as mocked_send:
-                # check no exception with unknown address
-                context.on_tick_event('10.0.0.0', {})
-                self.assertEqual(0, mocked_check.call_count)
-                self.assertEqual(0, mocked_send.call_count)
-                # get address status used for tests
-                address = context.addresses['10.0.0.1']
-                # check no change with known address in isolation
-                for state in [AddressStates.ISOLATING, AddressStates.ISOLATED]:
-                    address._state = state
-                    context.on_tick_event('10.0.0.1', {})
-                    self.assertEqual(state, address.state)
-                    self.assertEqual(0, mocked_check.call_count)
-                    self.assertEqual(0, mocked_send.call_count)
-                # check that address is CHECKING and check_address is called
-                # before address time is updated and address status is sent
-                for state in [AddressStates.UNKNOWN, AddressStates.SILENT]:
-                    address._state = state
-                    context.on_tick_event('10.0.0.1', {'when': 1234})
-                    self.assertEqual(AddressStates.CHECKING, address.state)
-                    self.assertEqual(call('10.0.0.1'), mocked_check.call_args)
-                    self.assertEqual(call(address), mocked_send.call_args)
-                    self.assertEqual(1234, address.remote_time)
-                # check that address time is updated and address status is sent
-                mocked_check.reset_mock()
-                mocked_send.reset_mock()
-                for state in [AddressStates.CHECKING, AddressStates.RUNNING]:
-                    address._state = state
-                    context.on_tick_event('10.0.0.1', {'when': 5678})
-                    self.assertEqual(state, address.state)
-                    self.assertEqual(0, mocked_check.call_count)
-                    self.assertEqual(call(address), mocked_send.call_args)
-                    self.assertEqual(5678, address.remote_time)
+        mocked_check = self.supvisors.zmq.pusher.send_check_address
+        mocked_send = self.supvisors.zmq.publisher.send_address_status
+        # check no exception with unknown address
+        context.on_tick_event('10.0.0.0', {})
+        self.assertEqual(0, mocked_check.call_count)
+        self.assertEqual(0, mocked_send.call_count)
+        # get address status used for tests
+        address = context.addresses['10.0.0.1']
+        # check no change with known address in isolation
+        for state in [AddressStates.ISOLATING, AddressStates.ISOLATED]:
+            address._state = state
+            context.on_tick_event('10.0.0.1', {})
+            self.assertEqual(state, address.state)
+            self.assertEqual(0, mocked_check.call_count)
+            self.assertEqual(0, mocked_send.call_count)
+        # check that address is CHECKING and check_address is called
+        # before address time is updated and address status is sent
+        for state in [AddressStates.UNKNOWN, AddressStates.SILENT]:
+            address._state = state
+            context.on_tick_event('10.0.0.1', {'when': 1234})
+            self.assertEqual(AddressStates.CHECKING, address.state)
+            self.assertEqual(1234, address.remote_time)
+            self.assertEqual(call('10.0.0.1'), mocked_check.call_args)
+            self.assertEqual(call({'address_name': '10.0.0.1', 'statecode': 1, 'statename': 'CHECKING',
+                                   'remote_time': 1234, 'local_time': 3600, 'loading': 0}),
+                             mocked_send.call_args)
+        # check that address time is updated and address status is sent
+        mocked_check.reset_mock()
+        mocked_send.reset_mock()
+        for state in [AddressStates.CHECKING, AddressStates.RUNNING]:
+            address._state = state
+            context.on_tick_event('10.0.0.1', {'when': 5678})
+            self.assertEqual(state, address.state)
+            self.assertEqual(5678, address.remote_time)
+            self.assertEqual(0, mocked_check.call_count)
+            self.assertEqual(call({'address_name': '10.0.0.1', 'statecode': state,
+                                   'statename': AddressStates.to_string(state),
+                                   'remote_time': 5678, 'local_time': 3600, 'loading': 0}),
+                             mocked_send.call_args)
 
-    def test_process_event(self):
+    @patch('supvisors.process.time', return_value=1234)
+    def test_process_event(self, _):
         """ Test the handling of a process event. """
         from supvisors.context import Context
         from supvisors.ttypes import AddressStates, ApplicationStates
         context = Context(self.supvisors)
-        with patch.object(self.supvisors.zmq.publisher,
-                          'send_application_status') as mocked_appli:
-            with patch.object(self.supvisors.zmq.publisher,
-                              'send_process_status') as mocked_proc:
-                # check no exception with unknown address
-                result = context.on_process_event('10.0.0.0', {})
-                self.assertIsNone(result)
-                self.assertEqual(0, mocked_appli.call_count)
-                self.assertEqual(0, mocked_proc.call_count)
-                # get address status used for tests
-                address = context.addresses['10.0.0.1']
-                # check no change with known address in isolation
-                for state in [AddressStates.ISOLATING, AddressStates.ISOLATED]:
-                    address._state = state
-                    result = context.on_process_event('10.0.0.1', {})
-                    self.assertIsNone(result)
-                    self.assertEqual(0, mocked_appli.call_count)
-                    self.assertEqual(0, mocked_proc.call_count)
-                # check no exception with unknown process
-                for state in [AddressStates.UNKNOWN, AddressStates.SILENT, AddressStates.CHECKING,
-                              AddressStates.RUNNING]:
-                    address._state = state
-                    result = context.on_process_event(
-                        '10.0.0.1', {'groupname': 'dummy_application',
-                                     'processname': 'dummy_process'})
-                    self.assertIsNone(result)
-                    self.assertEqual(0, mocked_appli.call_count)
-                    self.assertEqual(0, mocked_proc.call_count)
-                # fill context with one process
-                dummy_info = {'group': 'dummy_application',
-                              'name': 'dummy_process',
-                              'expected': True, 'now': 1234, 'state': 0}
-                process = context.setdefault_process(dummy_info)
-                process.add_info('10.0.0.1', dummy_info)
-                application = context.applications['dummy_application']
-                self.assertEqual(ApplicationStates.STOPPED, application.state)
-                # check normal behaviour with known process
-                dummy_event = {'group': 'dummy_application',
-                               'name': 'dummy_process',
-                               'state': 10,
-                               'now': 2345,
-                               'extra_args': ''}
-                for state in [AddressStates.UNKNOWN, AddressStates.SILENT,
-                              AddressStates.CHECKING, AddressStates.RUNNING]:
-                    address._state = state
-                    result = context.on_process_event('10.0.0.1', dummy_event)
-                    self.assertIs(process, result)
-                    self.assertEqual(10, process.state)
-                    self.assertEqual(ApplicationStates.STARTING, application.state)
-                    self.assertEqual(call(application), mocked_appli.call_args)
-                    self.assertEqual(call(process), mocked_proc.call_args)
+        mocked_publisher = self.supvisors.zmq.publisher
+        # check no exception with unknown address
+        result = context.on_process_event('10.0.0.0', {})
+        self.assertIsNone(result)
+        self.assertEqual(0, mocked_publisher.send_application_status.call_count)
+        self.assertEqual(0, mocked_publisher.send_process_status.call_count)
+        # get address status used for tests
+        address = context.addresses['10.0.0.1']
+        # check no change with known address in isolation
+        for state in [AddressStates.ISOLATING, AddressStates.ISOLATED]:
+            address._state = state
+            result = context.on_process_event('10.0.0.1', {})
+            self.assertIsNone(result)
+            self.assertEqual(0, mocked_publisher.send_application_status.call_count)
+            self.assertEqual(0, mocked_publisher.send_process_status.call_count)
+        # check no exception with unknown process
+        for state in [AddressStates.UNKNOWN, AddressStates.SILENT,
+                      AddressStates.CHECKING, AddressStates.RUNNING]:
+            address._state = state
+            result = context.on_process_event('10.0.0.1', {'groupname': 'dummy_application',
+                                                           'processname': 'dummy_process'})
+            self.assertIsNone(result)
+            self.assertEqual(0, mocked_publisher.send_application_status.call_count)
+            self.assertEqual(0, mocked_publisher.send_process_status.call_count)
+        # fill context with one process
+        dummy_info = {'group': 'dummy_application',
+                      'name': 'dummy_process',
+                      'expected': True, 'now': 1234, 'state': 0}
+        process = context.setdefault_process(dummy_info)
+        process.add_info('10.0.0.1', dummy_info)
+        application = context.applications['dummy_application']
+        self.assertEqual(ApplicationStates.STOPPED, application.state)
+        # check normal behaviour with known process
+        dummy_event = {'group': 'dummy_application',
+                       'name': 'dummy_process',
+                       'state': 10,
+                       'now': 2345,
+                       'extra_args': ''}
+        for state in [AddressStates.UNKNOWN, AddressStates.SILENT, AddressStates.CHECKING, AddressStates.RUNNING]:
+            address._state = state
+            result = context.on_process_event('10.0.0.1', dummy_event)
+            self.assertIs(process, result)
+            self.assertEqual(10, process.state)
+            self.assertEqual(ApplicationStates.STARTING, application.state)
+            self.assertEqual(call({'application_name': 'dummy_application',
+                                   'statecode': 1, 'statename': 'STARTING',
+                                   'major_failure': False, 'minor_failure': False}),
+                             mocked_publisher.send_application_status.call_args)
+            self.assertEqual(call({'application_name': 'dummy_application', 'process_name': 'dummy_process',
+                                   'statecode': 10, 'statename': 'STARTING', 'expected_exit': True,
+                                   'last_event_time': 1234, 'addresses': ['10.0.0.1'],
+                                   'extra_args': ''}),
+                             mocked_publisher.send_process_status.call_args)
 
-    def test_timer_event(self):
+    @patch('supvisors.context.time', return_value=3600)
+    def test_timer_event(self, mocked_time):
         """ Test the handling of a timer event. """
         from supvisors.context import Context
         from supvisors.ttypes import AddressStates
         context = Context(self.supvisors)
-        with patch.object(self.supvisors.zmq.publisher, 'send_address_status') as mocked_send:
-            # test address states excepting RUNNING: nothing happens
-            for _ in [x for x in AddressStates.values() if x != AddressStates.RUNNING]:
-                context.on_timer_event()
-                for address in context.addresses.values():
-                    self.assertEqual(AddressStates.UNKNOWN, address.state)
-                self.assertEqual(0, mocked_send.call_count)
-            # test RUNNING address state with recent local_time
-            test_addresses = ['10.0.0.1', '10.0.0.3', '10.0.0.5']
-            for address_name in test_addresses:
-                address = context.addresses[address_name]
-                address._state = AddressStates.RUNNING
-                address.local_time = time.time()
+        mocked_send = self.supvisors.zmq.publisher.send_address_status
+        # test address states excepting RUNNING: nothing happens
+        for _ in [x for x in AddressStates.values() if x != AddressStates.RUNNING]:
             context.on_timer_event()
-            for address_name in test_addresses:
-                self.assertEqual(AddressStates.RUNNING,
-                                 context.addresses[address_name].state)
-            for address_name in [x for x in context.addresses.keys()
-                                 if x not in test_addresses]:
-                self.assertEqual(AddressStates.UNKNOWN,
-                                 context.addresses[address_name].state)
+            for address in context.addresses.values():
+                self.assertEqual(AddressStates.UNKNOWN, address.state)
             self.assertEqual(0, mocked_send.call_count)
-            # test RUNNING address state with one recent local_time and with
-            # auto_fence activated
-            address1 = context.addresses['10.0.0.3']
-            address1.local_time = time.time() - 100
-            context.on_timer_event()
-            self.assertEqual(AddressStates.ISOLATING, address1.state)
-            for address_name in [x for x in test_addresses if x != '10.0.0.3']:
-                self.assertEqual(AddressStates.RUNNING,
-                                 context.addresses[address_name].state)
-            for address_name in [x for x in context.addresses.keys()
-                                 if x not in test_addresses]:
-                self.assertEqual(AddressStates.UNKNOWN,
-                                 context.addresses[address_name].state)
-            self.assertEqual(call(address1), mocked_send.call_args)
-            # test with one other recent local_time and with auto_fence
-            # deactivated
-            with patch.object(self.supvisors.options, 'auto_fence', False):
-                mocked_send.reset_mock()
-                address2 = context.addresses['10.0.0.5']
-                address2.local_time = time.time() - 100
-                address3 = context.addresses['10.0.0.1']
-                address3.local_time = time.time() - 100
-                context.on_timer_event()
-                self.assertEqual(AddressStates.SILENT, address2.state)
-                self.assertEqual(AddressStates.SILENT, address3.state)
-                self.assertEqual(AddressStates.ISOLATING, address1.state)
-                for address_name in [x for x in context.addresses.keys()
-                                     if x not in test_addresses]:
-                    self.assertEqual(AddressStates.UNKNOWN,
-                                     context.addresses[address_name].state)
-                send_calls = mocked_send.call_args_list
-                self.assertTrue([call(address2), call(address3)] == send_calls or
-                                [call(address3), call(address2)] == send_calls)
+        # test RUNNING address state with recent local_time
+        test_addresses = ['10.0.0.1', '10.0.0.3', '10.0.0.5']
+        for address_name in test_addresses:
+            address = context.addresses[address_name]
+            address._state = AddressStates.RUNNING
+            address.local_time = time.time()
+        context.on_timer_event()
+        for address_name in test_addresses:
+            self.assertEqual(AddressStates.RUNNING, context.addresses[address_name].state)
+        for address_name in [x for x in context.addresses.keys()
+                             if x not in test_addresses]:
+            self.assertEqual(AddressStates.UNKNOWN, context.addresses[address_name].state)
+        self.assertEqual(0, mocked_send.call_count)
+        # test RUNNING address state with one recent local_time and with auto_fence activated
+        address1 = context.addresses['10.0.0.3']
+        address1.local_time = mocked_time.return_value - 100
+        context.on_timer_event()
+        self.assertEqual(AddressStates.ISOLATING, address1.state)
+        for address_name in [x for x in test_addresses if x != '10.0.0.3']:
+            self.assertEqual(AddressStates.RUNNING, context.addresses[address_name].state)
+        for address_name in [x for x in context.addresses.keys()
+                             if x not in test_addresses]:
+            self.assertEqual(AddressStates.UNKNOWN, context.addresses[address_name].state)
+        self.assertEqual(call({'address_name': '10.0.0.3', 'statecode': 4, 'statename': 'ISOLATING',
+                               'remote_time': 0, 'local_time': address1.local_time, 'loading': 0}),
+                         mocked_send.call_args)
+        # test with one other recent local_time and with auto_fence deactivated
+        self.supvisors.options.auto_fence = False
+        mocked_send.reset_mock()
+        address2 = context.addresses['10.0.0.5']
+        address2.local_time = mocked_time.return_value - 100
+        address3 = context.addresses['10.0.0.1']
+        address3.local_time = mocked_time.return_value - 100
+        context.on_timer_event()
+        self.assertEqual(AddressStates.SILENT, address2.state)
+        self.assertEqual(AddressStates.SILENT, address3.state)
+        self.assertEqual(AddressStates.ISOLATING, address1.state)
+        for address_name in [x for x in context.addresses.keys()
+                             if x not in test_addresses]:
+            self.assertEqual(AddressStates.UNKNOWN, context.addresses[address_name].state)
+        send_calls = mocked_send.call_args_list
+        payload2 = {'address_name': '10.0.0.5', 'statecode': 3, 'statename': 'SILENT',
+                    'remote_time': 0, 'local_time': address2.local_time, 'loading': 0}
+        payload3 = {'address_name': '10.0.0.1', 'statecode': 3, 'statename': 'SILENT',
+                    'remote_time': 0, 'local_time': address3.local_time, 'loading': 0}
+        self.assertTrue([call(payload2), call(payload3)] == send_calls or
+                        [call(payload3), call(payload2)] == send_calls)
 
     def test_handle_isolation(self):
         """ Test the isolation of addresses. """
         from supvisors.context import Context
         from supvisors.ttypes import AddressStates
         context = Context(self.supvisors)
-        with patch.object(self.supvisors.zmq.publisher,
-                          'send_address_status') as mocked_send:
+        with patch.object(self.supvisors.zmq.publisher, 'send_address_status') as mocked_send:
             # update address states
             context.addresses['127.0.0.1']._state = AddressStates.CHECKING
             context.addresses['10.0.0.1']._state = AddressStates.RUNNING
@@ -569,22 +600,18 @@ def test_handle_isolation(self):
             context.addresses['10.0.0.5']._state = AddressStates.ISOLATING
             # call method and check result
             result = context.handle_isolation()
-            self.assertEqual(AddressStates.CHECKING,
-                             context.addresses['127.0.0.1'].state)
-            self.assertEqual(AddressStates.RUNNING,
-                             context.addresses['10.0.0.1'].state)
-            self.assertEqual(AddressStates.SILENT,
-                             context.addresses['10.0.0.2'].state)
-            self.assertEqual(AddressStates.ISOLATED,
-                             context.addresses['10.0.0.3'].state)
-            self.assertEqual(AddressStates.ISOLATED,
-                             context.addresses['10.0.0.4'].state)
-            self.assertEqual(AddressStates.ISOLATED,
-                             context.addresses['10.0.0.5'].state)
+            self.assertEqual(AddressStates.CHECKING, context.addresses['127.0.0.1'].state)
+            self.assertEqual(AddressStates.RUNNING, context.addresses['10.0.0.1'].state)
+            self.assertEqual(AddressStates.SILENT, context.addresses['10.0.0.2'].state)
+            self.assertEqual(AddressStates.ISOLATED, context.addresses['10.0.0.3'].state)
+            self.assertEqual(AddressStates.ISOLATED, context.addresses['10.0.0.4'].state)
+            self.assertEqual(AddressStates.ISOLATED, context.addresses['10.0.0.5'].state)
             self.assertListEqual(['10.0.0.4', '10.0.0.5'], result)
             # check calls to publisher.send_address_status
-            self.assertListEqual([call(context.addresses['10.0.0.4']),
-                                  call(context.addresses['10.0.0.5'])],
+            self.assertListEqual([call({'address_name': '10.0.0.4', 'statecode': 5, 'statename': 'ISOLATED',
+                                        'remote_time': 0, 'local_time': 0, 'loading': 0}),
+                                  call({'address_name': '10.0.0.5', 'statecode': 5, 'statename': 'ISOLATED',
+                                        'remote_time': 0, 'local_time': 0, 'loading': 0})],
                                  mocked_send.call_args_list)
 
 
diff --git a/supvisors/tests/test_initializer.py b/supvisors/tests/test_initializer.py
index 4978cfc5..f210c3ed 100644
--- a/supvisors/tests/test_initializer.py
+++ b/supvisors/tests/test_initializer.py
@@ -77,8 +77,7 @@ def test_create_logger(self):
         from supvisors.initializer import Supvisors
         # create mocked supvisors options
         mocked_options = Mock(supvisors_options=DummyOptions())
-        with patch('supvisors.initializer.SupvisorsServerOptions',
-                   return_value=mocked_options):
+        with patch('supvisors.initializer.SupvisorsServerOptions', return_value=mocked_options):
             # create Supvisors instance
             supervisord = DummySupervisor()
             supvisors = Supvisors(supervisord)
@@ -86,7 +85,7 @@ def test_create_logger(self):
             mocked_options.supvisors_options.logfile = Automatic
             self.assertIs(supervisord.options.logger, supvisors.create_logger(supervisord))
             # for the following, supervisord must be silent because of logger
-            # for unknown reason test_initalizer got this exception
+            # for unknown reason test_initializer got this exception
             # ValueError: I/O operation on closed file
             supervisord.options.silent = True
             # test defined logfile
diff --git a/supvisors/tests/test_listener.py b/supvisors/tests/test_listener.py
index 9c329c24..3901e5a0 100644
--- a/supvisors/tests/test_listener.py
+++ b/supvisors/tests/test_listener.py
@@ -207,9 +207,8 @@ def test_authorization(self):
         """ Test the processing of a Supvisors authorization. """
         from supvisors.listener import SupervisorListener
         listener = SupervisorListener(self.supvisors)
-        listener.authorization('address_name:10.0.0.5 authorized:False')
-        self.assertEqual([call('10.0.0.5', False)],
-                         listener.fsm.on_authorization.call_args_list)
+        listener.authorization('address_name:10.0.0.5 authorized:False master_address:10.0.0.1')
+        self.assertEqual([call('10.0.0.5', False, '10.0.0.1')], listener.fsm.on_authorization.call_args_list)
 
     def test_on_remote_event(self):
         """ Test the reception of a Supervisor remote comm event. """
diff --git a/supvisors/tests/test_mainloop.py b/supvisors/tests/test_mainloop.py
index c761e5d7..0045904e 100644
--- a/supvisors/tests/test_mainloop.py
+++ b/supvisors/tests/test_mainloop.py
@@ -172,65 +172,52 @@ def test_check_requests(self, mocked_send, mocked_stderr):
         self.assertEqual(0, mocked_send.call_count)
 
     @patch('supvisors.mainloop.stderr')
-    def test_check_address(self, mocked_stderr):
-        """ Test the protocol to get the processes handled by a remote
-        Supervisor. """
+    @patch('supvisors.mainloop.SupvisorsMainLoop.send_remote_comm_event')
+    def test_check_address(self, mocked_evt: Mock, mocked_stderr: Mock):
+        """ Test the protocol to get the processes handled by a remote Supervisor. """
         from supvisors.mainloop import SupvisorsMainLoop
         from supvisors.ttypes import AddressStates
         main_loop = SupvisorsMainLoop(self.supvisors)
-        # patch the main loop send_remote_comm_event
-        # test the check_address behaviour through the calls to internal events
-        with patch.object(main_loop, 'send_remote_comm_event') as mocked_evt:
-            # test rpc error: no event is sent to local Supervisor
-            self.mocked_rpc.side_effect = Exception
+        # test rpc error: no event is sent to local Supervisor
+        self.mocked_rpc.side_effect = Exception
+        main_loop.check_address('10.0.0.1')
+        self.assertEqual(2, self.mocked_rpc.call_count)
+        self.assertEqual(call('10.0.0.1', main_loop.env), self.mocked_rpc.call_args)
+        self.assertEqual(0, mocked_evt.call_count)
+        # test with a mocked rpc interface
+        dummy_info = [{'name': 'proc', 'group': 'appli', 'state': 10, 'start': 5,
+                       'now': 10, 'pid': 1234, 'spawnerr': ''}]
+        rpc_intf = DummyRpcInterface()
+        mocked_all = rpc_intf.supervisor.getAllProcessInfo = Mock()
+        mocked_local = rpc_intf.supvisors.get_all_local_process_info = Mock(return_value=dummy_info)
+        mocked_addr = rpc_intf.supvisors.get_address_info = Mock()
+        rpc_intf.supvisors.get_master_address = Mock(return_value='10.0.0.5')
+        self.mocked_rpc.return_value = rpc_intf
+        self.mocked_rpc.side_effect = None
+        self.mocked_rpc.reset_mock()
+        # test with address in isolation
+        for state in [AddressStates.ISOLATING, AddressStates.ISOLATED]:
+            mocked_addr.return_value = {'statecode': state}
             main_loop.check_address('10.0.0.1')
-            self.assertEqual(2, self.mocked_rpc.call_count)
-            self.assertEqual(call('10.0.0.1', main_loop.env),
-                             self.mocked_rpc.call_args)
-            self.assertEqual(0, mocked_evt.call_count)
-            # test with a mocked rpc interface
-            rpc_intf = DummyRpcInterface()
-            self.mocked_rpc.side_effect = None
-            self.mocked_rpc.return_value = rpc_intf
+            self.assertEqual([call('10.0.0.1', main_loop.env)], self.mocked_rpc.call_args_list)
+            self.assertEqual([call('auth', 'address_name:10.0.0.1 authorized:False master_address:10.0.0.5')],
+                             mocked_evt.call_args_list)
+            self.assertFalse(mocked_all.called)
+            # reset counters
+            mocked_evt.reset_mock()
+            self.mocked_rpc.reset_mock()
+        # test with address not in isolation
+        for state in [AddressStates.UNKNOWN, AddressStates.CHECKING, AddressStates.RUNNING, AddressStates.SILENT]:
+            mocked_addr.return_value = {'statecode': state}
+            main_loop.check_address('10.0.0.1')
+            self.assertEqual(1, self.mocked_rpc.call_count)
+            self.assertEqual(call('10.0.0.1', main_loop.env), self.mocked_rpc.call_args)
+            self.assertEqual(2, mocked_evt.call_count)
+            self.assertEqual(1, mocked_local.call_count)
+            # reset counters
+            mocked_evt.reset_mock()
+            mocked_local.reset_mock()
             self.mocked_rpc.reset_mock()
-            # test with address in isolation
-            with patch.object(rpc_intf.supervisor,
-                              'getAllProcessInfo') as mocked_supervisor:
-                for state in [AddressStates.ISOLATING, AddressStates.ISOLATED]:
-                    with patch.object(rpc_intf.supvisors, 'get_address_info',
-                                      return_value={'statecode': state}):
-                        main_loop.check_address('10.0.0.1')
-                        self.assertEqual(1, self.mocked_rpc.call_count)
-                        self.assertEqual(call('10.0.0.1', main_loop.env),
-                                         self.mocked_rpc.call_args)
-                        self.assertEqual(1, mocked_evt.call_count)
-                        self.assertEqual(call(
-                            'auth', 'address_name:10.0.0.1 authorized:False'),
-                            mocked_evt.call_args)
-                        self.assertEqual(0, mocked_supervisor.call_count)
-                        # reset counters
-                        mocked_evt.reset_mock()
-                        self.mocked_rpc.reset_mock()
-            # test with address not in isolation
-            dummy_info = [{'name': 'proc', 'group': 'appli',
-                           'state': 10, 'start': 5,
-                           'now': 10, 'pid': 1234, 'spawnerr': ''}]
-            with patch.object(rpc_intf.supvisors, 'get_all_local_process_info',
-                              return_value=dummy_info) as mocked_supervisor:
-                for state in [AddressStates.UNKNOWN, AddressStates.CHECKING,
-                              AddressStates.RUNNING, AddressStates.SILENT]:
-                    with patch.object(rpc_intf.supvisors, 'get_address_info',
-                                      return_value={'statecode': state}):
-                        main_loop.check_address('10.0.0.1')
-                        self.assertEqual(1, self.mocked_rpc.call_count)
-                        self.assertEqual(call('10.0.0.1', main_loop.env),
-                                         self.mocked_rpc.call_args)
-                        self.assertEqual(2, mocked_evt.call_count)
-                        self.assertEqual(1, mocked_supervisor.call_count)
-                        # reset counters
-                        mocked_evt.reset_mock()
-                        mocked_supervisor.reset_mock()
-                        self.mocked_rpc.reset_mock()
 
     @patch('supvisors.mainloop.stderr')
     def test_start_process(self, mocked_stderr):
diff --git a/supvisors/tests/test_options.py b/supvisors/tests/test_options.py
index 80d1d7e3..e82c3bca 100644
--- a/supvisors/tests/test_options.py
+++ b/supvisors/tests/test_options.py
@@ -42,6 +42,7 @@ def test_creation(self):
         self.assertIsNone(opt.event_port)
         self.assertIsNone(opt.auto_fence)
         self.assertIsNone(opt.synchro_timeout)
+        self.assertIsNone(opt.force_synchro_if)
         self.assertIsNone(opt.conciliation_strategy)
         self.assertIsNone(opt.starting_strategy)
         self.assertIsNone(opt.stats_periods)
@@ -58,7 +59,7 @@ def test_str(self):
         opt = SupvisorsOptions()
         self.assertEqual('address_list=None rules_file=None '
                          'internal_port=None event_port=None auto_fence=None '
-                         'synchro_timeout=None conciliation_strategy=None '
+                         'synchro_timeout=None force_synchro_if=None conciliation_strategy=None '
                          'starting_strategy=None stats_periods=None stats_histo=None '
                          'stats_irix_mode=None logfile=None logfile_maxbytes=None '
                          'logfile_backups=None loglevel=None', str(opt))
@@ -94,10 +95,12 @@ def test_timeout(self):
         with self.assertRaisesRegex(ValueError, error_message):
             SupvisorsServerOptions.to_timeout('0')
         with self.assertRaisesRegex(ValueError, error_message):
-            SupvisorsServerOptions.to_timeout('1001')
+            SupvisorsServerOptions.to_timeout('14')
+        with self.assertRaisesRegex(ValueError, error_message):
+            SupvisorsServerOptions.to_timeout('1201')
         # test valid values
-        self.assertEqual(1, SupvisorsServerOptions.to_timeout('1'))
-        self.assertEqual(1000, SupvisorsServerOptions.to_timeout('1000'))
+        self.assertEqual(15, SupvisorsServerOptions.to_timeout('15'))
+        self.assertEqual(1200, SupvisorsServerOptions.to_timeout('1200'))
 
     def test_conciliation_strategy(self):
         """ Test the conversion of a string to a conciliation strategy. """
@@ -201,6 +204,7 @@ def test_default_options(self):
         self.assertEqual(65002, opt.event_port)
         self.assertFalse(opt.auto_fence)
         self.assertEqual(15, opt.synchro_timeout)
+        self.assertEqual([], opt.force_synchro_if)
         self.assertEqual(ConciliationStrategies.USER, opt.conciliation_strategy)
         self.assertEqual(StartingStrategies.CONFIG, opt.starting_strategy)
         self.assertListEqual([10], opt.stats_periods)
@@ -216,12 +220,13 @@ def test_defined_options(self):
         from supvisors.ttypes import ConciliationStrategies, StartingStrategies
         server = self.create_server(DefinedOptionConfiguration)
         opt = server.supvisors_options
-        self.assertListEqual(['cliche01', 'cliche03', 'cliche02'], opt.address_list)
+        self.assertEqual(['cliche01', 'cliche03', 'cliche02'], opt.address_list)
         self.assertEqual('my_movies.xml', opt.rules_file)
         self.assertEqual(60001, opt.internal_port)
         self.assertEqual(60002, opt.event_port)
         self.assertTrue(opt.auto_fence)
         self.assertEqual(20, opt.synchro_timeout)
+        self.assertEqual(['cliche01', 'cliche03'], opt.force_synchro_if)
         self.assertEqual(ConciliationStrategies.SENICIDE, opt.conciliation_strategy)
         self.assertEqual(StartingStrategies.MOST_LOADED, opt.starting_strategy)
         self.assertListEqual([5, 60, 600], opt.stats_periods)
@@ -242,8 +247,10 @@ def create_server(self, *args, **keywargs):
         server = SupvisorsServerOptions()
         # this flag is required for supervisor to cope with unittest arguments
         server.positional_args_allowed = 1
-        with patch.object(ServerOptions, 'open', return_value=args[0]):
-            server.realize()
+        # remove pytest cov options
+        with patch.object(sys, 'argv', [sys.argv[0]]):
+            with patch.object(ServerOptions, 'open', return_value=args[0]):
+                server.realize()
         return server
 
 
diff --git a/supvisors/tests/test_process.py b/supvisors/tests/test_process.py
index 195544fe..22d039c2 100644
--- a/supvisors/tests/test_process.py
+++ b/supvisors/tests/test_process.py
@@ -20,7 +20,7 @@
 import sys
 import unittest
 
-from unittest.mock import call
+from unittest.mock import call, patch
 
 from supvisors.tests.base import (MockedSupvisors,
                                   any_stopped_process_info,
@@ -52,14 +52,15 @@ def test_str(self):
         """ Test the string output. """
         from supvisors.process import ProcessRules
         rules = ProcessRules(self.supvisors)
-        self.assertEqual("addresses=['*'] start_sequence=0 stop_sequence=0 required=False"
+        self.assertEqual("addresses=['*'] hash_addresses=None start_sequence=0 stop_sequence=0 required=False"
                          " wait_exit=False expected_loading=1 running_failure_strategy=CONTINUE", str(rules))
 
     def test_serial(self):
         """ Test the serialization of the ProcessRules object. """
         from supvisors.process import ProcessRules
         rules = ProcessRules(self.supvisors)
-        self.assertDictEqual({'addresses': ['*'], 'start_sequence': 0, 'stop_sequence': 0,
+        self.assertDictEqual({'addresses': ['*'],
+                              'start_sequence': 0, 'stop_sequence': 0,
                               'required': False, 'wait_exit': False, 'expected_loading': 1,
                               'running_failure_strategy': 'CONTINUE'}, rules.serial())
 
@@ -115,22 +116,6 @@ def test_dependency_rules(self):
         self.assertFalse(rules.required)
         self.assertFalse(rules.wait_exit)
         self.assertEqual(5, rules.expected_loading)
-        # test empty addresses
-        rules.addresses = []
-        rules.start_sequence = 0
-        rules.stop_sequence = 0
-        rules.required = False
-        rules.wait_exit = False
-        rules.expected_loading = 0
-        # check dependencies
-        rules.check_dependencies('dummy')
-        # test that all addresses are applicable
-        self.assertListEqual(['*'], rules.addresses)
-        self.assertEqual(0, rules.start_sequence)
-        self.assertEqual(0, rules.stop_sequence)
-        self.assertFalse(rules.required)
-        self.assertFalse(rules.wait_exit)
-        self.assertEqual(0, rules.expected_loading)
 
     def test_dependency_rules_running_failure(self):
         """ Test the dependency related to running failure strategy in process rules.
@@ -275,7 +260,8 @@ def test_serialization(self):
         loaded = pickle.loads(dumped)
         self.assertDictEqual(serialized, loaded)
 
-    def test_add_info(self):
+    @patch('supvisors.process.ProcessStatus.resolve_hash_address')
+    def test_add_info(self, mocked_resolve):
         """ Test the addition of a process info into the ProcessStatus. """
         from supervisor.states import ProcessStates
         from supvisors.process import ProcessStatus
@@ -283,7 +269,6 @@ def test_add_info(self):
         info = process_info_by_name('xclock')
         info['extra_args'] = '-x dummy'
         self.assertNotIn('uptime', info)
-
         # 1. create ProcessStatus instance
         process = ProcessStatus(info['group'], info['name'], self.supvisors)
         process.extra_args = 'something else'
@@ -302,12 +287,8 @@ def test_add_info(self):
         # extra_args are reset when using add_info
         self.assertEqual('', process.extra_args)
         self.assertEqual('', info['extra_args'])
-
-        # 2. test that address is unchanged
-        self.assertListEqual(['*'], process.rules.addresses)
-        # update rules to test '#'
-        process.rules.addresses = ['#']
-        # replace with an EXITED process info
+        self.assertFalse(mocked_resolve.called)
+        # 2. replace with an EXITED process info
         info = any_process_info_by_state(ProcessStates.EXITED)
         process.add_info('10.0.0.1', info)
         # check last event info
@@ -321,11 +302,9 @@ def test_add_info(self):
         self.assertFalse(process.addresses)
         self.assertEqual(ProcessStates.EXITED, process.state)
         self.assertTrue(process.expected_exit)
-        # the firefox process has a procnumber of 0 and address '10.0.0.1'
-        # has an index of 1
-        # address rule remains unchanged
-        self.assertListEqual(['#'], process.rules.addresses)
-
+        self.assertFalse(mocked_resolve.called)
+        # update rules to test '#'
+        process.rules.hash_addresses = ['*']
         # 3. add a RUNNING process info
         info = any_process_info_by_state(ProcessStates.RUNNING)
         process.add_info('10.0.0.2', info)
@@ -339,9 +318,36 @@ def test_add_info(self):
         self.assertEqual({'10.0.0.2'}, process.addresses)
         self.assertEqual(ProcessStates.RUNNING, process.state)
         self.assertTrue(process.expected_exit)
-        # the xfontsel process has a procnumber of 2 and address '10.0.0.2' has an index of 2
-        # address rule changes to '10.0.0.2'
+        self.assertTrue(mocked_resolve.called)
+
+    def test_resolve_hash_address(self):
+        """ Test the resolution of addresses when hash_address is set. """
+        from supvisors.process import ProcessStatus
+        # get a process info
+        # in mocked supvisors, xclock has a procnumber of 2
+        info = process_info_by_name('xclock')
+        process = ProcessStatus(info['group'], info['name'], self.supvisors)
+        # 1. update rules to test '#' with all nodes available
+        process.rules.hash_addresses = ['*']
+        process.rules.addresses = []
+        # address '10.0.0.1' has an index of 1 so address rule remains unchanged
+        process.resolve_hash_address('10.0.0.1')
+        self.assertEqual([], process.rules.addresses)
+        # address '10.0.0.2' has an index of 2 so address rule is set
+        process.resolve_hash_address('10.0.0.2')
         self.assertListEqual(['10.0.0.2'], process.rules.addresses)
+        # 2. update rules to test '#' with a subset of nodes available
+        process.rules.hash_addresses = ['10.0.0.0', '10.0.0.3', '10.0.0.5']
+        process.rules.addresses = []
+        # here, address '10.0.0.2' is not even in list so address rule remains unchanged
+        process.resolve_hash_address('10.0.0.2')
+        self.assertListEqual([], process.rules.addresses)
+        # address '10.0.0.3' is in list but index is 1 so address rule remains unchanged
+        process.resolve_hash_address('10.0.0.3')
+        self.assertListEqual([], process.rules.addresses)
+        # address '10.0.0.5' is in list at index 2 so address rule is set
+        process.resolve_hash_address('10.0.0.5')
+        self.assertListEqual(['10.0.0.5'], process.rules.addresses)
 
     def test_update_info(self):
         """ Test the update of the ProcessStatus upon reception of a process event. """
diff --git a/supvisors/tests/test_rpcinterface.py b/supvisors/tests/test_rpcinterface.py
index cd03175c..b251280f 100644
--- a/supvisors/tests/test_rpcinterface.py
+++ b/supvisors/tests/test_rpcinterface.py
@@ -104,20 +104,18 @@ def test_address_info(self):
         with self.assertRaises(RPCError) as exc:
             rpc.get_address_info('10.0.0.0')
         self.assertEqual(Faults.BAD_ADDRESS, exc.exception.code)
-        self.assertEqual('BAD_ADDRESS: address 10.0.0.0 unknown in Supvisors',
+        self.assertEqual('BAD_ADDRESS: address 10.0.0.0 unknown to Supvisors',
                          exc.exception.text)
 
     def test_all_addresses_info(self):
         """ Test the get_all_addresses_info RPC. """
         from supvisors.rpcinterface import RPCInterface
         # prepare context
-        self.supervisor.supvisors.context.addresses = {
-            '10.0.0.1': Mock(**{'serial.return_value': 'address_info_1'}),
-            '10.0.0.2': Mock(**{'serial.return_value': 'address_info_2'})}
+        self.supervisor.supvisors.context.addresses = {'10.0.0.1': Mock(**{'serial.return_value': 'address_info_1'}),
+                                                       '10.0.0.2': Mock(**{'serial.return_value': 'address_info_2'})}
         # create RPC instance
         rpc = RPCInterface(self.supervisor)
-        self.assertListEqual(['address_info_1', 'address_info_2'],
-                             rpc.get_all_addresses_info())
+        self.assertListEqual(['address_info_1', 'address_info_2'], rpc.get_all_addresses_info())
 
     @patch('supvisors.rpcinterface.RPCInterface._check_from_deployment')
     @patch('supvisors.rpcinterface.RPCInterface._get_application',
@@ -139,16 +137,14 @@ def test_all_applications_info(self, mocked_get, mocked_check):
         """ Test the get_all_applications_info RPC. """
         from supvisors.rpcinterface import RPCInterface
         # prepare context
-        self.supervisor.supvisors.context.applications = {
-            'dummy_1': None, 'dummy_2': None}
+        self.supervisor.supvisors.context.applications = {'dummy_1': None, 'dummy_2': None}
         # create RPC instance
         rpc = RPCInterface(self.supervisor)
         # test RPC call
         self.assertListEqual([{'name': 'appli_1'}, {'name': 'appli_2'}],
                              rpc.get_all_applications_info())
         self.assertEqual([call()], mocked_check.call_args_list)
-        self.assertListEqual([call('dummy_1'), call('dummy_2')],
-                             mocked_get.call_args_list)
+        self.assertListEqual([call('dummy_1'), call('dummy_2')], mocked_get.call_args_list)
 
     @patch('supvisors.rpcinterface.RPCInterface._check_from_deployment')
     @patch('supvisors.rpcinterface.RPCInterface._get_application_process',
@@ -162,22 +158,16 @@ def test_process_info(self, mocked_get, mocked_check):
         # create RPC instance
         rpc = RPCInterface(self.supervisor)
         # test first RPC call with process namespec
-        self.assertEqual([{'name': 'proc'}],
-                         rpc.get_process_info('appli:proc'))
-        self.assertEqual([call()],
-                         mocked_check.call_args_list)
-        self.assertEqual([call('appli:proc')],
-                         mocked_get.call_args_list)
+        self.assertEqual([{'name': 'proc'}], rpc.get_process_info('appli:proc'))
+        self.assertEqual([call()], mocked_check.call_args_list)
+        self.assertEqual([call('appli:proc')], mocked_get.call_args_list)
         # reset patches
         mocked_check.reset_mock()
         mocked_get.reset_mock()
         # test second RPC call with group namespec
-        self.assertEqual([{'name': 'proc_1'}, {'name': 'proc_2'}],
-                         rpc.get_process_info('appli:*'))
-        self.assertEqual([call()],
-                         mocked_check.call_args_list)
-        self.assertEqual([call('appli:*')],
-                         mocked_get.call_args_list)
+        self.assertEqual([{'name': 'proc_1'}, {'name': 'proc_2'}], rpc.get_process_info('appli:*'))
+        self.assertEqual([call()], mocked_check.call_args_list)
+        self.assertEqual([call('appli:*')], mocked_get.call_args_list)
 
     @patch('supvisors.rpcinterface.RPCInterface._check_from_deployment')
     def test_all_process_info(self, mocked_check):
@@ -210,8 +200,7 @@ def test_local_process_info(self, mocked_get):
         self.assertEqual({'group': 'group', 'name': 'name'},
                          rpc.get_local_process_info('appli:proc'))
         self.assertEqual([call('appli:proc')], mocked_rpc.call_args_list)
-        self.assertEqual([call({'group': 'dummy_group',
-                                'name': 'dummy_name'})],
+        self.assertEqual([call({'group': 'dummy_group', 'name': 'dummy_name'})],
                          mocked_get.call_args_list)
 
     @patch('supvisors.rpcinterface.RPCInterface._get_local_info',
@@ -222,16 +211,14 @@ def test_all_local_process_info(self, mocked_get):
         # prepare context
         info_source = self.supervisor.supvisors.info_source
         mocked_rpc = info_source.supervisor_rpc_interface.getAllProcessInfo
-        mocked_rpc.return_value = [{'group': 'dummy_group',
-                                    'name': 'dummy_name'}]
+        mocked_rpc.return_value = [{'group': 'dummy_group', 'name': 'dummy_name'}]
         # create RPC instance
         rpc = RPCInterface(self.supervisor)
         # test RPC call with process namespec
         self.assertEqual([{'group': 'group', 'name': 'name'}],
                          rpc.get_all_local_process_info())
         self.assertEqual([call()], mocked_rpc.call_args_list)
-        self.assertEqual([call({'group': 'dummy_group',
-                                'name': 'dummy_name'})],
+        self.assertEqual([call({'group': 'dummy_group', 'name': 'dummy_name'})],
                          mocked_get.call_args_list)
 
     @patch('supvisors.rpcinterface.RPCInterface._check_from_deployment')
@@ -567,8 +554,7 @@ def test_start_args(self, mocked_proc):
         with self.assertRaises(RPCError) as exc:
             rpc.start_args('appli:proc', 'dummy arguments')
         self.assertEqual(Faults.BAD_NAME, exc.exception.code)
-        self.assertEqual('BAD_NAME: namespec appli:proc unknown in this '
-                         'Supervisor instance', exc.exception.text)
+        self.assertEqual('BAD_NAME: namespec appli:proc unknown to this Supervisor instance', exc.exception.text)
         self.assertEqual([call('appli:proc', 'dummy arguments')],
                          info_source.update_extra_args.call_args_list)
         self.assertEqual(0, mocked_startProcess.call_count)
@@ -1029,8 +1015,7 @@ def test_get_application(self):
         with self.assertRaises(RPCError) as exc:
             rpc._get_application('app')
         self.assertEqual(Faults.BAD_NAME, exc.exception.code)
-        self.assertEqual('BAD_NAME: application app unknown in Supvisors',
-                         exc.exception.text)
+        self.assertEqual('BAD_NAME: application app unknown to Supvisors', exc.exception.text)
 
     def test_get_process(self):
         """ Test the _get_process utility. """
@@ -1046,17 +1031,14 @@ def test_get_process(self):
         with self.assertRaises(RPCError) as exc:
             rpc._get_process('proc')
         self.assertEqual(Faults.BAD_NAME, exc.exception.code)
-        self.assertEqual('BAD_NAME: process proc unknown in Supvisors',
-                         exc.exception.text)
+        self.assertEqual('BAD_NAME: process proc unknown to Supvisors', exc.exception.text)
 
     def test_get_application_process(self):
         """ Test the _get_application_process utility. """
         from supvisors.rpcinterface import RPCInterface
         # prepare context
-        self.supervisor.supvisors.context.applications = {
-            'appli_1': 'first application'}
-        self.supervisor.supvisors.context.processes = {
-            'appli_1:proc_1': 'first process'}
+        self.supervisor.supvisors.context.applications = {'appli_1': 'first application'}
+        self.supervisor.supvisors.context.processes = {'appli_1:proc_1': 'first process'}
         # create RPC instance
         rpc = RPCInterface(self.supervisor)
         # test with full namespec
diff --git a/supvisors/tests/test_sparser.py b/supvisors/tests/test_sparser.py
index 325c0968..25fc6ff7 100644
--- a/supvisors/tests/test_sparser.py
+++ b/supvisors/tests/test_sparser.py
@@ -42,7 +42,7 @@ def check_valid(self, parser):
                                       StartingFailureStrategies)
         # test models & patterns
         self.assertListEqual(['dummy_model_01', 'dummy_model_02',
-                              'dummy_model_03', 'dummy_model_04'],
+                              'dummy_model_03', 'dummy_model_04', 'dummy_model_05'],
                              sorted(parser.models.keys()))
         self.assertListEqual(['dummies_', 'dummies_01_', 'dummies_02_'],
                              sorted(parser.patterns.keys()))
@@ -72,6 +72,10 @@ def check_valid(self, parser):
         self.assert_application_rules(application.rules, 0, 100,
                                       StartingFailureStrategies.CONTINUE,
                                       RunningFailureStrategies.RESTART_APPLICATION)
+        # check loop application
+        application = ApplicationStatus('dummy_application_E', self.supvisors.logger)
+        parser.load_application_rules(application)
+        self.assert_default_application_rules(application.rules)
         # check program from unknown application: all default
         process = ProcessStatus('dummy_application_X', 'dummy_program_X0', self.supvisors)
         parser.load_process_rules(process)
@@ -82,88 +86,83 @@ def check_valid(self, parser):
         self.assert_default_process_rules(process.rules)
         # check known program from known but not related application:
         # all default
-        process = ProcessStatus('dummy_application_A', 'dummy_program_B1',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_A', 'dummy_program_B1', self.supvisors)
         parser.load_process_rules(process)
         self.assert_default_process_rules(process.rules)
         # check known empty program
-        process = ProcessStatus('dummy_application_B', 'dummy_program_B0',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_B', 'dummy_program_B0', self.supvisors)
         parser.load_process_rules(process)
         self.assert_default_process_rules(process.rules)
         # check dash addresses and valid other values
-        process = ProcessStatus('dummy_application_B', 'dummy_program_B1',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_B', 'dummy_program_B1', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['#'], 3, 50, True, False, 5,
+        self.assert_process_rules(process.rules,
+                                  [], ['*'], 3, 50, True, False, 5,
                                   RunningFailureStrategies.CONTINUE)
-        # check single address with required not applicable and out of
-        # range loading
-        process = ProcessStatus('dummy_application_B', 'dummy_program_B2',
-                                self.supvisors)
+        # check single address with required not applicable and out of range loading
+        process = ProcessStatus('dummy_application_B', 'dummy_program_B2', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['10.0.0.3'], 0, 0,
-                                  False, False, 1,
+        self.assert_process_rules(process.rules,
+                                  ['10.0.0.3'], None, 0, 0, False, False, 1,
                                   RunningFailureStrategies.RESTART_PROCESS)
         # check wildcard address, optional and max loading
-        process = ProcessStatus('dummy_application_B', 'dummy_program_B3',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_B', 'dummy_program_B3', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['*'], 0, 0,
-                                  False, False, 100,
+        self.assert_process_rules(process.rules,
+                                  ['*'], None, 0, 0, False, False, 100,
                                   RunningFailureStrategies.STOP_APPLICATION)
         # check multiple addresses, all other incorrect values
-        process = ProcessStatus('dummy_application_B', 'dummy_program_B4',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_B', 'dummy_program_B4', self.supvisors)
         parser.load_process_rules(process)
         self.assert_process_rules(process.rules,
-                                  ['10.0.0.3', '10.0.0.1', '10.0.0.5'], 0, 0,
-                                  False, False, 1,
+                                  ['10.0.0.3', '10.0.0.1', '10.0.0.5'], None, 0, 0, False, False, 1,
                                   RunningFailureStrategies.RESTART_APPLICATION)
         # check empty reference
-        process = ProcessStatus('dummy_application_C', 'dummy_program_C0',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_C', 'dummy_program_C0', self.supvisors)
         parser.load_process_rules(process)
         self.assert_default_process_rules(process.rules)
         # check unknown reference
-        process = ProcessStatus('dummy_application_C', 'dummy_program_C1',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_C', 'dummy_program_C1', self.supvisors)
         parser.load_process_rules(process)
         self.assert_default_process_rules(process.rules)
         # check known reference
-        process = ProcessStatus('dummy_application_C', 'dummy_program_C2',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_C', 'dummy_program_C2', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['*'], 0, 0,
-                                  False, False, 25,
+        self.assert_process_rules(process.rules,
+                                  ['*'], None, 0, 0, False, False, 25,
                                   RunningFailureStrategies.STOP_APPLICATION)
         # check other known reference
-        process = ProcessStatus('dummy_application_C', 'dummy_program_C3',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_C', 'dummy_program_C3', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['#'], 1, 0,
-                                  True, True, 1,
+        self.assert_process_rules(process.rules,
+                                  [], ['*'], 1, 0, True, True, 1,
                                   RunningFailureStrategies.CONTINUE)
         # check pattern with single matching and reference
-        process = ProcessStatus('dummy_application_D', 'dummies_any',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_D', 'dummies_any', self.supvisors)
         parser.load_process_rules(process)
         self.assert_process_rules(process.rules,
-                                  ['10.0.0.4', '10.0.0.2'], 0, 100,
-                                  False, False, 10,
+                                  ['10.0.0.4', '10.0.0.2'], None, 50, 100, False, False, 10,
                                   RunningFailureStrategies.CONTINUE)
         # check pattern with multiple matching and configuration
-        process = ProcessStatus('dummy_application_D', 'dummies_01_any',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_D', 'dummies_01_any', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['#'], 1, 1,
-                                  False, True, 75,
+        self.assert_process_rules(process.rules,
+                                  [], ['10.0.0.1', '10.0.0.5'], 1, 1, False, True, 75,
                                   RunningFailureStrategies.CONTINUE)
-        # check pattern with multiple matching and incorrect reference
-        process = ProcessStatus('dummy_application_D', 'any_dummies_02_',
-                                self.supvisors)
+        # check pattern with multiple matching and incorrect reference (model calling for another model)
+        # this is valid since Supvisors 0.5
+        process = ProcessStatus('dummy_application_D', 'any_dummies_02_', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_default_process_rules(process.rules)
+        self.assert_process_rules(process.rules,
+                                  ['*'], None, 0, 0, False, False, 20,
+                                  RunningFailureStrategies.STOP_APPLICATION)
+        # check multiple reference (over the maximum defined)
+        # almost all rules set to default, despite enf of chain is on dummy_model_01
+        process = ProcessStatus('dummy_application_E', 'dummy_program_E', self.supvisors)
+        parser.load_process_rules(process)
+        self.assert_process_rules(process.rules,
+                                  ['*'], None, 0, 0, False, False, 15,
+                                  RunningFailureStrategies.CONTINUE)
 
     def assert_default_application_rules(self, rules):
         """ Check that rules contains default values. """
@@ -183,13 +182,15 @@ def assert_application_rules(self, rules, start, stop,
     def assert_default_process_rules(self, rules):
         """ Check that rules contains default values. """
         from supvisors.ttypes import RunningFailureStrategies
-        self.assert_process_rules(rules, ['*'], 0, 0, False, False, 1,
+        self.assert_process_rules(rules,
+                                  ['*'], None, 0, 0, False, False, 1,
                                   RunningFailureStrategies.CONTINUE)
 
-    def assert_process_rules(self, rules, addresses, start, stop, required,
+    def assert_process_rules(self, rules, addresses, hash_addresses, start, stop, required,
                              wait, loading, running_strategy):
         """ Test the process rules. """
-        self.assertListEqual(addresses, rules.addresses)
+        self.assertEqual(addresses, rules.addresses)
+        self.assertEqual(hash_addresses, rules.hash_addresses)
         self.assertEqual(start, rules.start_sequence)
         self.assertEqual(stop, rules.stop_sequence)
         self.assertEqual(required, rules.required)
@@ -220,7 +221,7 @@ def test_valid_lxml(self):
         self.check_valid(parser)
 
     @patch('supvisors.sparser.stderr')
-    def test_invalid_lxml(self, mocked_print):
+    def test_invalid_lxml(self, _):
         """ Test the parsing of an invalid XML using lxml (optional dependency). """
         # perform the test
         from supvisors.sparser import Parser
@@ -247,14 +248,14 @@ def setUp(self):
         CommonParserTest.setUp(self)
 
     @patch('xml.etree.ElementTree.parse', side_effect=ImportError)
-    def test_no_parser(self, *args, **keywargs):
+    def test_no_parser(self, _):
         """ Test the exception when no parser is available. """
         from supvisors.sparser import Parser
         # create Parser instance
         with self.assertRaises(ImportError):
             Parser(self.supvisors)
 
-    def test_valid_element_tree(self, *args, **keywargs):
+    def test_valid_element_tree(self):
         """ Test the parsing of a valid XML using ElementTree. """
         from supvisors.sparser import Parser
         # create Parser instance
@@ -262,7 +263,7 @@ def test_valid_element_tree(self, *args, **keywargs):
             parser = Parser(self.supvisors)
         self.check_valid(parser)
 
-    def test_invalid_element_tree(self, *args, **keywargs):
+    def test_invalid_element_tree(self):
         """ Test the parsing of an invalid XML using ElementTree. """
         from supvisors.sparser import Parser
         # create Parser instance
@@ -274,158 +275,133 @@ def check_invalid(self, parser):
         """ Test the parsing of an invalid XML. """
         from supvisors.application import ApplicationStatus
         from supvisors.process import ProcessStatus
-        from supvisors.ttypes import (RunningFailureStrategies,
-                                      StartingFailureStrategies)
+        from supvisors.ttypes import RunningFailureStrategies, StartingFailureStrategies
         # test models & patterns
-        self.assertListEqual(['dummy_model_01', 'dummy_model_02',
-                              'dummy_model_03', 'dummy_model_04'],
+        self.assertListEqual(['dummy_model_01', 'dummy_model_02', 'dummy_model_03', 'dummy_model_04'],
                              sorted(parser.models.keys()))
         self.assertListEqual(['dummies_', 'dummies_01_', 'dummies_02_'],
                              sorted(parser.patterns.keys()))
         # check unknown application
-        application = ApplicationStatus('dummy_application_X',
-                                        self.supvisors.logger)
+        application = ApplicationStatus('dummy_application_X', self.supvisors.logger)
         parser.load_application_rules(application)
         self.assert_default_application_rules(application.rules)
         # check first application
-        application = ApplicationStatus('dummy_application_A',
-                                        self.supvisors.logger)
+        application = ApplicationStatus('dummy_application_A', self.supvisors.logger)
         parser.load_application_rules(application)
         self.assert_default_application_rules(application.rules)
         # check second application
-        application = ApplicationStatus('dummy_application_B',
-                                        self.supvisors.logger)
+        application = ApplicationStatus('dummy_application_B', self.supvisors.logger)
         parser.load_application_rules(application)
         self.assert_application_rules(application.rules, 1, 4,
                                       StartingFailureStrategies.STOP,
                                       RunningFailureStrategies.RESTART_PROCESS)
         # check third application
-        application = ApplicationStatus('dummy_application_C',
-                                        self.supvisors.logger)
+        application = ApplicationStatus('dummy_application_C', self.supvisors.logger)
         parser.load_application_rules(application)
         self.assert_application_rules(application.rules, 20, 0,
                                       StartingFailureStrategies.ABORT,
                                       RunningFailureStrategies.STOP_APPLICATION)
         # check fourth application
-        application = ApplicationStatus('dummy_application_D',
-                                        self.supvisors.logger)
+        application = ApplicationStatus('dummy_application_D', self.supvisors.logger)
         parser.load_application_rules(application)
         self.assert_application_rules(application.rules, 0, 100,
                                       StartingFailureStrategies.CONTINUE,
                                       RunningFailureStrategies.RESTART_APPLICATION)
         # check program from unknown application: all default
-        process = ProcessStatus('dummy_application_X', 'dummy_program_X0',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_X', 'dummy_program_X0', self.supvisors)
         parser.load_process_rules(process)
         self.assert_default_process_rules(process.rules)
         # check unknown program from known application: all default
-        process = ProcessStatus('dummy_application_A', 'dummy_program_A0',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_A', 'dummy_program_A0', self.supvisors)
         parser.load_process_rules(process)
         self.assert_default_process_rules(process.rules)
         # check known program from known but not related application: all default
-        process = ProcessStatus('dummy_application_A', 'dummy_program_B1',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_A', 'dummy_program_B1', self.supvisors)
         parser.load_process_rules(process)
         self.assert_default_process_rules(process.rules)
         # check known empty program
-        process = ProcessStatus('dummy_application_B', 'dummy_program_B0',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_B', 'dummy_program_B0', self.supvisors)
         parser.load_process_rules(process)
         self.assert_default_process_rules(process.rules)
         # check dash addresses and valid other values
-        process = ProcessStatus('dummy_application_B', 'dummy_program_B1',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_B', 'dummy_program_B1', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['#'], 3, 50,
-                                  True, False, 5,
+        self.assert_process_rules(process.rules,
+                                  [], ['*'], 3, 50, True, False, 5,
                                   RunningFailureStrategies.CONTINUE)
-        # check single address with required not applicable and out of
-        # range loading
-        process = ProcessStatus('dummy_application_B', 'dummy_program_B2',
-                                self.supvisors)
+        # check single address with required not applicable and out of range loading
+        process = ProcessStatus('dummy_application_B', 'dummy_program_B2', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['10.0.0.3'], 0, 0,
-                                  False, False, 1,
+        self.assert_process_rules(process.rules,
+                                  ['10.0.0.3'], None, 0, 0, False, False, 1,
                                   RunningFailureStrategies.RESTART_PROCESS)
         # check wildcard address, optional and max loading
-        process = ProcessStatus('dummy_application_B', 'dummy_program_B3',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_B', 'dummy_program_B3', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['*'], 0, 0,
-                                  False, False, 100,
+        self.assert_process_rules(process.rules,
+                                  ['*'], None, 0, 0, False, False, 100,
                                   RunningFailureStrategies.STOP_APPLICATION)
         # check multiple addresses, all other incorrect values
-        process = ProcessStatus('dummy_application_B', 'dummy_program_B4',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_B', 'dummy_program_B4', self.supvisors)
         parser.load_process_rules(process)
         self.assert_process_rules(process.rules,
-                                  ['10.0.0.1', '10.0.0.2'],
-                                  0, 0, False, False, 1,
+                                  ['10.0.0.1', '10.0.0.2'], None, 0, 0, False, False, 1,
                                   RunningFailureStrategies.RESTART_APPLICATION)
         # check multiple addresses, all other incorrect values
-        process = ProcessStatus('dummy_application_B', 'dummy_program_B5',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_B', 'dummy_program_B5', self.supvisors)
         parser.load_process_rules(process)
         self.assert_process_rules(process.rules,
-                                  ['10.0.0.3', '10.0.0.1', '10.0.0.5'],
-                                  0, 0, False, False, 1,
+                                  ['10.0.0.3', '10.0.0.1', '10.0.0.5'], None, 0, 0, False, False, 1,
                                   RunningFailureStrategies.CONTINUE)
         # check empty reference
-        process = ProcessStatus('dummy_application_C', 'dummy_program_C0',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_C', 'dummy_program_C0', self.supvisors)
         parser.load_process_rules(process)
         self.assert_default_process_rules(process.rules)
         # check unknown reference
-        process = ProcessStatus('dummy_application_C', 'dummy_program_C1',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_C', 'dummy_program_C1', self.supvisors)
         parser.load_process_rules(process)
         self.assert_default_process_rules(process.rules)
         # check known reference
-        process = ProcessStatus('dummy_application_C', 'dummy_program_C2',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_C', 'dummy_program_C2', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['*'], 0, 0,
-                                  False, False, 25,
+        self.assert_process_rules(process.rules,
+                                  ['*'], None, 0, 0, False, False, 25,
                                   RunningFailureStrategies.STOP_APPLICATION)
         # check other known reference
-        process = ProcessStatus('dummy_application_C', 'dummy_program_C3',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_C', 'dummy_program_C3', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['#'], 1, 0,
-                                  True, True, 1,
+        self.assert_process_rules(process.rules,
+                                  [], ['*'], 1, 0, True, True, 1,
                                   RunningFailureStrategies.CONTINUE)
         # check other known reference with additional unexpected configuration
-        process = ProcessStatus('dummy_application_C', 'dummy_program_C4',
-                                self.supvisors)
+        # WARN: this is valid since Supvisors 0.5
+        process = ProcessStatus('dummy_application_C', 'dummy_program_C4', self.supvisors)
         parser.load_process_rules(process)
         self.assert_process_rules(process.rules,
-                                  ['10.0.0.4', '10.0.0.2'],
-                                  0, 100, False, False, 10,
+                                  [], ['*'], 3, 100, True, False, 5,
                                   RunningFailureStrategies.CONTINUE)
         # check pattern with single matching and reference
         # test that strategy value set before load is not crushed by default
-        process = ProcessStatus('dummy_application_D', 'dummies_any',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_D', 'dummies_any', self.supvisors)
         process.rules.running_failure_strategy = \
             RunningFailureStrategies.RESTART_APPLICATION
         parser.load_process_rules(process)
         self.assert_process_rules(process.rules,
-                                  ['10.0.0.4', '10.0.0.2'],
-                                  0, 100, False, False, 10,
+                                  ['10.0.0.4', '10.0.0.2'], None, 0, 100, False, False, 10,
                                   RunningFailureStrategies.RESTART_APPLICATION)
         # check pattern with multiple matching and configuration
-        process = ProcessStatus('dummy_application_D', 'dummies_01_any',
-                                self.supvisors)
+        process = ProcessStatus('dummy_application_D', 'dummies_01_any', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_process_rules(process.rules, ['#'], 1, 1,
-                                  False, True, 75,
+        self.assert_process_rules(process.rules,
+                                  [], ['*'], 1, 1, False, True, 75,
                                   RunningFailureStrategies.CONTINUE)
-        # check pattern with multiple matching and incorrect reference
-        process = ProcessStatus('dummy_application_D', 'any_dummies_02_',
-                                self.supvisors)
+        # check pattern with multiple matching and recursive reference
+        # WARN: this is valid since Supvisors 0.5
+        process = ProcessStatus('dummy_application_D', 'any_dummies_02_', self.supvisors)
         parser.load_process_rules(process)
-        self.assert_default_process_rules(process.rules)
+        self.assert_process_rules(process.rules,
+                                  ['*'], None, 0, 0, False, False, 25,
+                                  RunningFailureStrategies.STOP_APPLICATION)
 
 
 def test_suite():
diff --git a/supvisors/tests/test_statemachine.py b/supvisors/tests/test_statemachine.py
index c5ee3fa4..d710b232 100644
--- a/supvisors/tests/test_statemachine.py
+++ b/supvisors/tests/test_statemachine.py
@@ -37,8 +37,7 @@ def setUp(self):
         # assign addresses in context
         addresses = self.supvisors.context.addresses
         for address_name in self.supvisors.address_mapper.addresses:
-            addresses[address_name] = AddressStatus(address_name,
-                                                    self.supvisors.logger)
+            addresses[address_name] = AddressStatus(address_name, self.supvisors.logger)
         addresses['127.0.0.1']._state = AddressStates.RUNNING
         addresses['10.0.0.1']._state = AddressStates.SILENT
         addresses['10.0.0.2']._state = AddressStates.RUNNING
@@ -47,12 +46,12 @@ def setUp(self):
         addresses['10.0.0.5']._state = AddressStates.ISOLATED
 
     def test_abstract_state(self):
-        """ Test the Abstract state of the FSM. """
+        """ Test the Abstract state of the self.fsm. """
         from supvisors.statemachine import AbstractState
         state = AbstractState(self.supvisors)
         # check attributes at creation
         self.assertIs(self.supvisors, state.supvisors)
-        self.assertEqual('127.0.0.1', state.address)
+        self.assertEqual('127.0.0.1', state.address_name)
         # call empty methods
         state.enter()
         state.next()
@@ -65,12 +64,12 @@ def test_abstract_state(self):
                              mock_function.call_args_list)
 
     def test_initialization_state(self):
-        """ Test the Initialization state of the FSM. """
+        """ Test the Initialization state of the fsm. """
         from supvisors.statemachine import AbstractState, InitializationState
         from supvisors.ttypes import AddressStates, SupvisorsStates
         state = InitializationState(self.supvisors)
         self.assertIsInstance(state, AbstractState)
-        # test enter method: master and start_date are reset
+        # 1. test enter method: master and start_date are reset
         # test that all addresses that are not in an isolation state are reset to UNKNOWN
         state.enter()
         self.assertEqual('', state.context.master_address)
@@ -81,35 +80,51 @@ def test_initialization_state(self):
         self.assertEqual(AddressStates.ISOLATING, self.supvisors.context.addresses['10.0.0.3'].state)
         self.assertEqual(AddressStates.UNKNOWN, self.supvisors.context.addresses['10.0.0.4'].state)
         self.assertEqual(AddressStates.ISOLATED, self.supvisors.context.addresses['10.0.0.5'].state)
-        # test next method
+        # 2. test next method
         # test that Supvisors wait for all addresses to be running or a given timeout is reached
-        with patch.object(self.supvisors.context, 'running_addresses', return_value=[]):
-            # test case no address is running, especially local address
-            result = state.next()
-            self.assertEqual(SupvisorsStates.INITIALIZATION, result)
-        with patch.object(self.supvisors.context, 'running_addresses', return_value=['127.0.0.1', '10.0.0.2']):
-            with patch.object(self.supvisors.context, 'unknown_addresses', return_value=['10.0.0.1', '10.0.0.3']):
-                # test case where addresses are still unknown and timeout is not reached
-                result = state.next()
-                self.assertEqual(SupvisorsStates.INITIALIZATION, result)
-                # test case where addresses are still unknown and timeout is reached
-                state.start_date = time.time() - 11
-                result = state.next()
-                self.assertEqual(SupvisorsStates.DEPLOYMENT, result)
-            with patch.object(self.supvisors.context, 'unknown_addresses', return_value=[]):
-                result = state.next()
-                self.assertEqual(SupvisorsStates.DEPLOYMENT, result)
-        # test exit method
+        self.supvisors.context.forced_addresses = []
+        self.supvisors.context.running_addresses.return_value = []
+        # test case no address is running, especially local address
+        result = state.next()
+        self.assertEqual(SupvisorsStates.INITIALIZATION, result)
+        # test case where addresses are still unknown and timeout is not reached
+        self.supvisors.context.running_addresses.return_value = ['127.0.0.1', '10.0.0.2']
+        self.supvisors.context.unknown_addresses.return_value = ['10.0.0.1', '10.0.0.3']
+        self.supvisors.context.unknown_forced_addresses.return_value = []
+        result = state.next()
+        self.assertEqual(SupvisorsStates.INITIALIZATION, result)
+        # test case where no addresses are still unknown
+        self.supvisors.context.running_addresses.return_value = ['127.0.0.1', '10.0.0.2']
+        self.supvisors.context.unknown_addresses.return_value = []
+        self.supvisors.context.unknown_forced_addresses.return_value = []
+        result = state.next()
+        self.assertEqual(SupvisorsStates.DEPLOYMENT, result)
+        # test case where end of synchro is forced based on a subset of addresses
+        self.supvisors.context.forced_addresses = ['10.0.0.2', '10.0.0.4']
+        self.supvisors.context.running_addresses.return_value = ['127.0.0.1', '10.0.0.2']
+        self.supvisors.context.unknown_addresses.return_value = ['10.0.0.1', '10.0.0.3']
+        self.supvisors.context.unknown_forced_addresses.return_value = []
+        result = state.next()
+        self.assertEqual(SupvisorsStates.DEPLOYMENT, result)
+        # test case where addresses are still unknown and timeout is reached
+        self.supvisors.context.forced_addresses = []
+        state.start_date = time.time() - 11
+        result = state.next()
+        self.assertEqual(SupvisorsStates.DEPLOYMENT, result)
+        self.supvisors.context.unknown_addresses.return_value = []
+        result = state.next()
+        self.assertEqual(SupvisorsStates.DEPLOYMENT, result)
+        # 3. test exit method
         # test that context end_synchro is called and master is the lowest string among address names
-        with patch.object(self.supvisors.context, 'running_addresses',
-                          return_value=['127.0.0.1', '10.0.0.2', '10.0.0.4']):
-            with patch.object(self.supvisors.context, 'end_synchro') as mocked_synchro:
-                state.exit()
-                self.assertEqual(1, mocked_synchro.call_count)
-                self.assertEqual('10.0.0.2', self.supvisors.context.master_address)
+        self.supvisors.context.running_addresses.return_value = ['127.0.0.1', '10.0.0.2', '10.0.0.4']
+        self.supvisors.context.end_synchro.return_value = ['127.0.0.1', '10.0.0.2', '10.0.0.4']
+        mocked_synchro = self.supvisors.context.end_synchro
+        state.exit()
+        self.assertEqual(1, mocked_synchro.call_count)
+        self.assertEqual('10.0.0.2', self.supvisors.context.master_address)
 
     def test_deployment_state(self):
-        """ Test the Deployment state of the FSM. """
+        """ Test the Deployment state of the fsm. """
         from supvisors.application import ApplicationStatus
         from supvisors.process import ProcessStatus
         from supvisors.statemachine import AbstractState, DeploymentState
@@ -201,7 +216,7 @@ def test_deployment_state(self):
         state.exit()
 
     def test_operation_state(self):
-        """ Test the Operation state of the FSM. """
+        """ Test the Operation state of the fsm. """
         from supvisors.address import AddressStatus
         from supvisors.statemachine import AbstractState, OperationState
         from supvisors.ttypes import AddressStates, SupvisorsStates
@@ -248,7 +263,7 @@ def test_operation_state(self):
         state.exit()
 
     def test_conciliation_state(self):
-        """ Test the Conciliation state of the FSM. """
+        """ Test the Conciliation state of the fsm. """
         from supvisors.address import AddressStatus
         from supvisors.statemachine import AbstractState, ConciliationState
         from supvisors.ttypes import AddressStates, SupvisorsStates
@@ -285,22 +300,18 @@ def test_conciliation_state(self):
         addresses['127.0.0.1']._state = AddressStates.RUNNING
         addresses['10.0.0.3']._state = AddressStates.RUNNING
         # consider that no starting or stopping is in progress
-        with patch.object(self.supvisors.starter, 'check_starting',
-                          return_value=True):
-            with patch.object(self.supvisors.stopper, 'check_stopping',
-                              return_value=True):
+        with patch.object(self.supvisors.starter, 'check_starting', return_value=True):
+            with patch.object(self.supvisors.stopper, 'check_stopping', return_value=True):
                 # if local address and master address are RUNNING and
                 # conflict still detected, re-enter CONCILIATION
-                with patch.object(self.supvisors.context, 'conflicting',
-                                  return_value=True):
+                with patch.object(self.supvisors.context, 'conflicting', return_value=True):
                     with patch.object(state, 'enter') as mocked_enter:
                         result = state.next()
                         self.assertEqual(1, mocked_enter.call_count)
                         self.assertEqual(SupvisorsStates.CONCILIATION, result)
                 # transit to OPERATION if local address and master address
                 # are RUNNING and no conflict detected
-                with patch.object(self.supvisors.context, 'conflicting',
-                                  return_value=False):
+                with patch.object(self.supvisors.context, 'conflicting', return_value=False):
                     result = state.next()
                     self.assertEqual(SupvisorsStates.OPERATION, result)
                 # transit to INITIALIZATION state if the local address
@@ -316,7 +327,7 @@ def test_conciliation_state(self):
         state.exit()
 
     def test_restarting_state(self):
-        """ Test the Restarting state of the FSM. """
+        """ Test the Restarting state of the fsm. """
         from supvisors.statemachine import AbstractState, RestartingState
         from supvisors.ttypes import SupvisorsStates
         state = RestartingState(self.supvisors)
@@ -341,7 +352,7 @@ def test_restarting_state(self):
             self.assertEqual(call(self.supvisors.zmq.pusher.send_restart), mocked_apply.call_args)
 
     def test_shutting_down_state(self):
-        """ Test the ShuttingDown state of the FSM. """
+        """ Test the ShuttingDown state of the fsm. """
         from supvisors.statemachine import AbstractState, ShuttingDownState
         from supvisors.ttypes import SupvisorsStates
         state = ShuttingDownState(self.supvisors)
@@ -366,7 +377,7 @@ def test_shutting_down_state(self):
             self.assertEqual(call(self.supvisors.zmq.pusher.send_shutdown), mocked_apply.call_args)
 
     def test_shutdown_state(self):
-        """ Test the ShutDown state of the FSM. """
+        """ Test the ShutDown state of the fsm. """
         from supvisors.statemachine import AbstractState, ShutdownState
         state = ShutdownState(self.supvisors)
         self.assertIsInstance(state, AbstractState)
@@ -381,44 +392,37 @@ class FiniteStateMachineTest(unittest.TestCase):
 
     def setUp(self):
         """ Create a Supvisors-like structure. """
+        from supvisors.statemachine import FiniteStateMachine
+        # create state machine instance to be tested
         self.supvisors = MockedSupvisors()
+        self.fsm = FiniteStateMachine(self.supvisors)
 
-    def test_creation(self):
+    @patch('supvisors.statemachine.FiniteStateMachine.update_instance')
+    def test_creation(self, mocked_update):
         """ Test the values set at construction. """
-        from supvisors.statemachine import InitializationState, FiniteStateMachine
+        from supvisors.statemachine import InitializationState
         from supvisors.ttypes import SupvisorsStates
         # test that the INITIALIZATION state is triggered at creation
-        with patch('supvisors.statemachine.InitializationState.enter') as mocked_enter:
-            with patch.object(self.supvisors.zmq.publisher, 'send_supvisors_status') as mocked_publisher:
-                fsm = FiniteStateMachine(self.supvisors)
-                self.assertIs(self.supvisors, fsm.supvisors)
-                self.assertEqual(SupvisorsStates.INITIALIZATION, fsm.state)
-                self.assertIsInstance(fsm.instance, InitializationState)
-                self.assertEqual(1, mocked_enter.call_count)
-                self.assertEqual(1, mocked_publisher.call_count)
-                self.assertEqual(call(fsm), mocked_publisher.call_args)
+        mocked_publisher = self.supvisors.zmq.publisher.send_supvisors_status
+        self.assertIs(self.supvisors, self.fsm.supvisors)
+        self.assertEqual(SupvisorsStates.INITIALIZATION, self.fsm.state)
+        self.assertIsInstance(self.fsm.instance, InitializationState)
 
     def test_state_string(self):
         """ Test the string conversion of state machine. """
-        from supvisors.statemachine import FiniteStateMachine
         from supvisors.ttypes import SupvisorsStates
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
         # test string conversion for all states
         for state in SupvisorsStates.values():
-            fsm.state = state
-            self.assertEqual(SupvisorsStates.to_string(state), fsm.state_string())
+            self.fsm.state = state
+            self.assertEqual(SupvisorsStates.to_string(state), self.fsm.state_string())
 
     def test_serial(self):
         """ Test the serialization of state machine. """
-        from supvisors.statemachine import FiniteStateMachine
         from supvisors.ttypes import SupvisorsStates
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
         # test serialization for all states
         for state in SupvisorsStates.values():
-            fsm.state = state
-            self.assertDictEqual({'statecode': state, 'statename': SupvisorsStates.to_string(state)}, fsm.serial())
+            self.fsm.state = state
+            self.assertDictEqual({'statecode': state, 'statename': SupvisorsStates.to_string(state)}, self.fsm.serial())
 
     @patch('supvisors.statemachine.DeploymentState.exit')
     @patch('supvisors.statemachine.DeploymentState.next')
@@ -428,7 +432,6 @@ def test_serial(self):
     @patch('supvisors.statemachine.InitializationState.enter')
     def test_simple_set_state(self, *args, **kwargs):
         """ Test single transitions of the state machine using set_state method. """
-        from supvisors.statemachine import FiniteStateMachine
         from supvisors.ttypes import SupvisorsStates
 
         # function to compare call counts of mocked methods
@@ -438,24 +441,22 @@ def compare_calls(call_counts):
                 mocked.reset_mock()
 
         # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
-        compare_calls([1, 0, 0, 0, 0, 0])
-        instance_ref = fsm.instance
+        instance_ref = self.fsm.instance
         # test set_state with identical state parameter
-        fsm.set_state(SupvisorsStates.INITIALIZATION)
+        self.fsm.set_state(SupvisorsStates.INITIALIZATION)
         compare_calls([0, 0, 0, 0, 0, 0])
-        self.assertIs(instance_ref, fsm.instance)
-        self.assertEqual(SupvisorsStates.INITIALIZATION, fsm.state)
+        self.assertIs(instance_ref, self.fsm.instance)
+        self.assertEqual(SupvisorsStates.INITIALIZATION, self.fsm.state)
         # test set_state with not authorized transition
-        fsm.set_state(SupvisorsStates.OPERATION)
+        self.fsm.set_state(SupvisorsStates.OPERATION)
         compare_calls([0, 0, 0, 0, 0, 0])
-        self.assertIs(instance_ref, fsm.instance)
-        self.assertEqual(SupvisorsStates.INITIALIZATION, fsm.state)
+        self.assertIs(instance_ref, self.fsm.instance)
+        self.assertEqual(SupvisorsStates.INITIALIZATION, self.fsm.state)
         # test set_state with authorized transition
-        fsm.set_state(SupvisorsStates.DEPLOYMENT)
+        self.fsm.set_state(SupvisorsStates.DEPLOYMENT)
         compare_calls([0, 0, 1, 1, 1, 0])
-        self.assertIsNot(instance_ref, fsm.instance)
-        self.assertEqual(SupvisorsStates.DEPLOYMENT, fsm.state)
+        self.assertIsNot(instance_ref, self.fsm.instance)
+        self.assertEqual(SupvisorsStates.DEPLOYMENT, self.fsm.state)
 
     @patch('supvisors.statemachine.OperationState.exit')
     @patch('supvisors.statemachine.OperationState.next')
@@ -468,7 +469,6 @@ def compare_calls(call_counts):
     @patch('supvisors.statemachine.InitializationState.enter')
     def test_complex_set_state(self, *args, **kwargs):
         """ Test multiple transitions of the state machine using set_state method. """
-        from supvisors.statemachine import FiniteStateMachine
         from supvisors.ttypes import SupvisorsStates
 
         # function to compare call counts of mocked methods
@@ -477,22 +477,18 @@ def compare_calls(call_counts):
                 self.assertEqual(call_count, mocked.call_count)
                 mocked.reset_mock()
 
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
-        compare_calls([1, 0, 0, 0, 0, 0, 0, 0, 0])
-        instance_ref = fsm.instance
+        instance_ref = self.fsm.instance
         # test set_state with authorized transition
-        fsm.set_state(SupvisorsStates.DEPLOYMENT)
+        self.fsm.set_state(SupvisorsStates.DEPLOYMENT)
         compare_calls([0, 0, 1, 1, 1, 1, 1, 1, 0])
-        self.assertIsNot(instance_ref, fsm.instance)
-        self.assertEqual(SupvisorsStates.OPERATION, fsm.state)
+        self.assertIsNot(instance_ref, self.fsm.instance)
+        self.assertEqual(SupvisorsStates.OPERATION, self.fsm.state)
 
     @patch('supvisors.statemachine.InitializationState.exit')
     @patch('supvisors.statemachine.InitializationState.next', return_value=0)
     @patch('supvisors.statemachine.InitializationState.enter')
     def test_no_next(self, *args, **kwargs):
         """ Test no transition of the state machine using next_method. """
-        from supvisors.statemachine import FiniteStateMachine
         from supvisors.ttypes import SupvisorsStates
 
         # function to compare call counts of mocked methods
@@ -501,15 +497,12 @@ def compare_calls(call_counts):
                 self.assertEqual(call_count, mocked.call_count)
                 mocked.reset_mock()
 
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
-        compare_calls([1, 0, 0])
-        instance_ref = fsm.instance
+        instance_ref = self.fsm.instance
         # test set_state with authorized transition
-        fsm.next()
+        self.fsm.next()
         compare_calls([0, 1, 0])
-        self.assertIs(instance_ref, fsm.instance)
-        self.assertEqual(SupvisorsStates.INITIALIZATION, fsm.state)
+        self.assertIs(instance_ref, self.fsm.instance)
+        self.assertEqual(SupvisorsStates.INITIALIZATION, self.fsm.state)
 
     @patch('supvisors.statemachine.DeploymentState.exit')
     @patch('supvisors.statemachine.DeploymentState.next', return_value=1)
@@ -519,7 +512,6 @@ def compare_calls(call_counts):
     @patch('supvisors.statemachine.InitializationState.enter')
     def test_simple_next(self, *args, **kwargs):
         """ Test single transition of the state machine using next_method. """
-        from supvisors.statemachine import FiniteStateMachine
         from supvisors.ttypes import SupvisorsStates
 
         # function to compare call counts of mocked methods
@@ -528,15 +520,12 @@ def compare_calls(call_counts):
                 self.assertEqual(call_count, mocked.call_count)
                 mocked.reset_mock()
 
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
-        compare_calls([1, 0, 0, 0, 0, 0])
-        instance_ref = fsm.instance
+        instance_ref = self.fsm.instance
         # test set_state with authorized transition
-        fsm.next()
+        self.fsm.next()
         compare_calls([0, 1, 1, 1, 1, 0])
-        self.assertIsNot(instance_ref, fsm.instance)
-        self.assertEqual(SupvisorsStates.DEPLOYMENT, fsm.state)
+        self.assertIsNot(instance_ref, self.fsm.instance)
+        self.assertEqual(SupvisorsStates.DEPLOYMENT, self.fsm.state)
 
     @patch('supvisors.statemachine.ConciliationState.exit')
     @patch('supvisors.statemachine.ConciliationState.next')
@@ -549,7 +538,6 @@ def compare_calls(call_counts):
     @patch('supvisors.statemachine.InitializationState.enter')
     def test_complex_next(self, *args, **kwargs):
         """ Test multiple transitions of the state machine using next_method. """
-        from supvisors.statemachine import FiniteStateMachine
         from supvisors.ttypes import SupvisorsStates
 
         # function to compare call counts of mocked methods
@@ -558,15 +546,12 @@ def compare_calls(call_counts):
                 self.assertEqual(call_count, mocked.call_count)
                 mocked.reset_mock()
 
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
-        compare_calls([1, 0, 0, 0, 0, 0, 0, 0, 0])
-        instance_ref = fsm.instance
+        instance_ref = self.fsm.instance
         # test set_state with authorized transition
-        fsm.next()
+        self.fsm.next()
         compare_calls([0, 1, 1, 1, 1, 1, 1, 1, 0])
-        self.assertIsNot(instance_ref, fsm.instance)
-        self.assertEqual(SupvisorsStates.CONCILIATION, fsm.state)
+        self.assertIsNot(instance_ref, self.fsm.instance)
+        self.assertEqual(SupvisorsStates.CONCILIATION, self.fsm.state)
 
     @patch('supvisors.statemachine.ShutdownState.exit')
     @patch('supvisors.statemachine.ShutdownState.next')
@@ -588,7 +573,6 @@ def compare_calls(call_counts):
     @patch('supvisors.statemachine.InitializationState.enter')
     def test_very_complex_next(self, *args, **kwargs):
         """ Test multiple transitions of the state machine using next_method. """
-        from supvisors.statemachine import FiniteStateMachine
         from supvisors.ttypes import SupvisorsStates
 
         # function to compare call counts of mocked methods
@@ -597,59 +581,50 @@ def compare_calls(call_counts):
                 self.assertEqual(call_count, mocked.call_count)
                 mocked.reset_mock()
 
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
-        compare_calls([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
-        instance_ref = fsm.instance
+        instance_ref = self.fsm.instance
         # test set_state with authorized transition
-        fsm.next()
+        self.fsm.next()
         compare_calls([1, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 1, 1, 1, 1, 1, 0])
-        self.assertIsNot(instance_ref, fsm.instance)
-        self.assertEqual(SupvisorsStates.SHUTDOWN, fsm.state)
+        self.assertIsNot(instance_ref, self.fsm.instance)
+        self.assertEqual(SupvisorsStates.SHUTDOWN, self.fsm.state)
 
     def test_update_instance(self):
         """ Test the recreation of the state instance, depending on the state. """
-        from supvisors.statemachine import (
-            InitializationState, DeploymentState, OperationState,
-            ConciliationState, RestartingState, ShuttingDownState,
-            ShutdownState, FiniteStateMachine)
+        from supvisors.statemachine import (InitializationState, DeploymentState, OperationState,
+                                            ConciliationState, RestartingState, ShuttingDownState, ShutdownState)
         from supvisors.ttypes import SupvisorsStates
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
-        with patch.object(self.supvisors.zmq.publisher,
-                          'send_supvisors_status') as mocked_publisher:
-            # create function for test comparison
-            def compare_state(state, klass):
-                fsm.update_instance(state)
-                # test the type of internal instance
-                self.assertIsInstance(fsm.instance, klass)
-                # test that internal instance is always recreated
-                self.assertIsNot(compare_state.instance_ref, fsm.instance)
-                compare_state.instance_ref = fsm.instance
-                # test that the state is reassigned
-                self.assertEqual(state, fsm.state)
-                # test that the state machine object is published
-                compare_state.cpt = compare_state.cpt + 1
-                self.assertEqual(compare_state.cpt, mocked_publisher.call_count)
-
-            # add internal persistent variables to function
-            compare_state.instance_ref = fsm.instance
-            compare_state.cpt = 0
-            # test all states
-            compare_state(SupvisorsStates.INITIALIZATION, InitializationState)
-            compare_state(SupvisorsStates.DEPLOYMENT, DeploymentState)
-            compare_state(SupvisorsStates.OPERATION, OperationState)
-            compare_state(SupvisorsStates.CONCILIATION, ConciliationState)
-            compare_state(SupvisorsStates.RESTARTING, RestartingState)
-            compare_state(SupvisorsStates.SHUTTING_DOWN, ShuttingDownState)
-            compare_state(SupvisorsStates.SHUTDOWN, ShutdownState)
+        # patch context
+        mocked_publisher = self.supvisors.zmq.publisher.send_supvisors_status = Mock()
+        self.supvisors.context.master = False
+
+        # create function for test comparison
+        def compare_state(state, klass):
+            self.fsm.update_instance(state)
+            # test the type of internal instance
+            self.assertIsInstance(self.fsm.instance, klass)
+            # test that internal instance is always recreated
+            self.assertIsNot(compare_state.instance_ref, self.fsm.instance)
+            compare_state.instance_ref = self.fsm.instance
+            # test that the state is reassigned
+            self.assertEqual(state, self.fsm.state)
+            # test that the state machine object is published
+            compare_state.cpt = compare_state.cpt + 1
+            self.assertEqual(compare_state.cpt, mocked_publisher.call_count)
+
+        # add internal persistent variables to function
+        compare_state.instance_ref = self.fsm.instance
+        compare_state.cpt = 0
+        # test all states
+        compare_state(SupvisorsStates.INITIALIZATION, InitializationState)
+        compare_state(SupvisorsStates.DEPLOYMENT, DeploymentState)
+        compare_state(SupvisorsStates.OPERATION, OperationState)
+        compare_state(SupvisorsStates.CONCILIATION, ConciliationState)
+        compare_state(SupvisorsStates.RESTARTING, RestartingState)
+        compare_state(SupvisorsStates.SHUTTING_DOWN, ShuttingDownState)
+        compare_state(SupvisorsStates.SHUTDOWN, ShutdownState)
 
     def test_timer_event(self):
-        """ Test the actions triggered in state machine upon reception
-        of a timer event. """
-        from supvisors.statemachine import FiniteStateMachine
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
+        """ Test the actions triggered in state machine upon reception of a timer event. """
         # apply patches
         mocked_isolation = self.supvisors.context.handle_isolation
         mocked_isolation.return_value = [2, 3]
@@ -658,8 +633,8 @@ def test_timer_event(self):
         # test that context on_timer_event is always called
         # test that fsm next is always called
         # test that result of context handle_isolation is always returned
-        with patch.object(fsm, 'next') as mocked_next:
-            result = fsm.on_timer_event()
+        with patch.object(self.fsm, 'next') as mocked_next:
+            result = self.fsm.on_timer_event()
             # check result: marked processes are started
             self.assertEqual([2, 3], result)
             self.assertEqual(1, mocked_next.call_count)
@@ -668,24 +643,16 @@ def test_timer_event(self):
             self.assertEqual(1, mocked_isolation.call_count)
 
     def test_tick_event(self):
-        """ Test the actions triggered in state machine upon reception
-        of a tick event. """
-        from supvisors.statemachine import FiniteStateMachine
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
+        """ Test the actions triggered in state machine upon reception of a tick event. """
         # inject tick event and test call to context on_tick_event
         with patch.object(self.supvisors.context, 'on_tick_event') as mocked_evt:
-            fsm.on_tick_event('10.0.0.1', 1234)
+            self.fsm.on_tick_event('10.0.0.1', 1234)
             self.assertEqual(1, mocked_evt.call_count)
             self.assertEqual(call('10.0.0.1', 1234), mocked_evt.call_args)
 
-    # FIWME: test calls to failure_handler + master + crashed
+    # FIXME: test calls to failure_handler + master + crashed
     def test_process_event(self):
-        """ Test the actions triggered in state machine upon reception
-        of a process event. """
-        from supvisors.statemachine import FiniteStateMachine
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
+        """ Test the actions triggered in state machine upon reception of a process event. """
         # prepare context
         process = Mock(application_name='appli')
         # get patches
@@ -699,9 +666,8 @@ def test_process_event(self):
         mocked_start_has.return_value = False
         mocked_stop_has.return_value = False
         # test that context on_process_event is always called
-        # test that starter and stopper are not involved when corresponding
-        # process is not found
-        fsm.on_process_event('10.0.0.1', ['dummy_event'])
+        # test that starter and stopper are not involved when corresponding process is not found
+        self.fsm.on_process_event('10.0.0.1', ['dummy_event'])
         self.assertEqual([call('10.0.0.1', ['dummy_event'])], mocked_ctx.call_args_list)
         self.assertEqual(0, mocked_start_has.call_count)
         self.assertEqual(0, mocked_stop_has.call_count)
@@ -713,9 +679,8 @@ def test_process_event(self):
         # test that context on_process_event is always called
         # test that event is not pushed to starter and stopper
         # when a starting or stopping is not in progress
-        fsm.on_process_event('10.0.0.1', ['dummy_event'])
-        self.assertEqual([call('10.0.0.1', ['dummy_event'])],
-                         mocked_ctx.call_args_list)
+        self.fsm.on_process_event('10.0.0.1', ['dummy_event'])
+        self.assertEqual([call('10.0.0.1', ['dummy_event'])], mocked_ctx.call_args_list)
         self.assertEqual([call('appli')], mocked_start_has.call_args_list)
         self.assertEqual([call('appli')], mocked_stop_has.call_args_list)
         self.assertEqual([call(process)], mocked_start_evt.call_args_list)
@@ -731,68 +696,73 @@ def test_process_event(self):
         # test that context on_process_event is always called
         # test that event is pushed to starter and stopper
         # when a starting or stopping is in progress
-        fsm.on_process_event('10.0.0.1', ['dummy_event'])
-        self.assertEqual([call('10.0.0.1', ['dummy_event'])],
-                         mocked_ctx.call_args_list)
+        self.fsm.on_process_event('10.0.0.1', ['dummy_event'])
+        self.assertEqual([call('10.0.0.1', ['dummy_event'])], mocked_ctx.call_args_list)
         self.assertEqual([call('appli')], mocked_start_has.call_args_list)
         self.assertEqual([call('appli')], mocked_stop_has.call_args_list)
         self.assertEqual([call(process)], mocked_start_evt.call_args_list)
         self.assertEqual([call(process)], mocked_stop_evt.call_args_list)
 
     def test_process_info(self):
-        """ Test the actions triggered in state machine upon reception
-        of a process information. """
-        from supvisors.statemachine import FiniteStateMachine
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
+        """ Test the actions triggered in state machine upon reception of a process information. """
         # inject process info and test call to context load_processes
-        with patch.object(self.supvisors.context,
-                          'load_processes') as mocked_load:
-            fsm.on_process_info('10.0.0.1', {'info': 'dummy_info'})
+        with patch.object(self.supvisors.context, 'load_processes') as mocked_load:
+            self.fsm.on_process_info('10.0.0.1', {'info': 'dummy_info'})
             self.assertEqual(1, mocked_load.call_count)
-            self.assertEqual(call('10.0.0.1', {'info': 'dummy_info'}),
-                             mocked_load.call_args)
+            self.assertEqual(call('10.0.0.1', {'info': 'dummy_info'}), mocked_load.call_args)
 
-    def test_authorization(self):
-        """ Test the actions triggered in state machine upon reception
-        of an authorization event. """
-        from supvisors.statemachine import FiniteStateMachine
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
-        # inject authorization event and test call to context on_authorization
-        with patch.object(self.supvisors.context,
-                          'on_authorization') as mocked_auth:
-            fsm.on_authorization('10.0.0.1', True)
-            self.assertEqual(1, mocked_auth.call_count)
-            self.assertEqual(call('10.0.0.1', True), mocked_auth.call_args)
+    def test_on_authorization(self):
+        """ Test the actions triggered in state machine upon reception of an authorization event. """
+        from supvisors.ttypes import SupvisorsStates
+        self.fsm.set_state(SupvisorsStates.OPERATION)
+        # prepare context
+        self.supvisors.context.master_address = ''
+        mocked_auth = self.fsm.context.on_authorization
+        mocked_auth.return_value = False
+        # test rejected authorization
+        self.fsm.on_authorization('10.0.0.1', False, '10.0.0.5')
+        self.assertEqual([call('10.0.0.1', False)], mocked_auth.call_args_list)
+        self.assertEqual('', self.supvisors.context.master_address)
+        # reset mocks
+        mocked_auth.reset_mock()
+        mocked_auth.return_value = True
+        # test authorization when to master address provided
+        self.fsm.on_authorization('10.0.0.1', True, '')
+        self.assertEqual(call('10.0.0.1', True), mocked_auth.call_args)
+        self.assertEqual('', self.supvisors.context.master_address)
+        # reset mocks
+        mocked_auth.reset_mock()
+        # test authorization and master address assignment
+        self.fsm.on_authorization('10.0.0.1', True, '10.0.0.5')
+        self.assertEqual(call('10.0.0.1', True), mocked_auth.call_args)
+        self.assertEqual('10.0.0.5', self.supvisors.context.master_address)
+        # reset mocks
+        mocked_auth.reset_mock()
+        # test authorization and master address assignment
+        self.fsm.on_authorization('10.0.0.1', True, '10.0.0.4')
+        self.assertEqual(call('10.0.0.1', True), mocked_auth.call_args)
+        self.assertEqual('10.0.0.5', self.supvisors.context.master_address)
+        self.assertEqual(SupvisorsStates.INITIALIZATION, self.fsm.state)
 
     def test_restart_event(self):
         """ Test the actions triggered in state machine upon reception
         of a restart event. """
-        from supvisors.statemachine import FiniteStateMachine
         from supvisors.ttypes import SupvisorsStates
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
         # inject restart event and test call to fsm set_state RESTARTING
-        with patch.object(fsm, 'set_state') as mocked_fsm:
-            fsm.on_restart()
+        with patch.object(self.fsm, 'set_state') as mocked_fsm:
+            self.fsm.on_restart()
             self.assertEqual(1, mocked_fsm.call_count)
-            self.assertEqual(call(SupvisorsStates.RESTARTING),
-                             mocked_fsm.call_args)
+            self.assertEqual(call(SupvisorsStates.RESTARTING), mocked_fsm.call_args)
 
     def test_shutdown_event(self):
         """ Test the actions triggered in state machine upon reception
         of a shutdown event. """
-        from supvisors.statemachine import FiniteStateMachine
         from supvisors.ttypes import SupvisorsStates
-        # create state machine instance
-        fsm = FiniteStateMachine(self.supvisors)
         # inject shutdown event and test call to fsm set_state SHUTTING_DOWN
-        with patch.object(fsm, 'set_state') as mocked_fsm:
-            fsm.on_shutdown()
+        with patch.object(self.fsm, 'set_state') as mocked_fsm:
+            self.fsm.on_shutdown()
             self.assertEqual(1, mocked_fsm.call_count)
-            self.assertEqual(call(SupvisorsStates.SHUTTING_DOWN),
-                             mocked_fsm.call_args)
+            self.assertEqual(call(SupvisorsStates.SHUTTING_DOWN), mocked_fsm.call_args)
 
 
 def test_suite():
diff --git a/supvisors/tests/test_strategy.py b/supvisors/tests/test_strategy.py
index 29b6dd81..b29e3e57 100644
--- a/supvisors/tests/test_strategy.py
+++ b/supvisors/tests/test_strategy.py
@@ -49,8 +49,9 @@ def create_status(name, address_state, loading):
         addresses['10.0.0.3'] = create_status('10.0.0.3', AddressStates.RUNNING, 20)
         addresses['10.0.0.4'] = create_status('10.0.0.4', AddressStates.UNKNOWN, 0)
         addresses['10.0.0.5'] = create_status('10.0.0.5', AddressStates.RUNNING, 80)
-        # initialize dummy address mapper with all address names (keep the alpha order)
+        # initialize dummy address mapper with all address names (keep the alphabetic order)
         self.supvisors.address_mapper.addresses = sorted(addresses.keys())
+        self.supvisors.address_mapper.local_address = '10.0.0.1'
 
     def test_is_loading_valid(self):
         """ Test the validity of an address with an additional loading. """
@@ -84,11 +85,9 @@ def test_get_loading_and_validity(self):
                               '10.0.0.2': (False, 0), '10.0.0.3': (True, 20), '10.0.0.4': (False, 0),
                               '10.0.0.5': (False, 80)},
                              strategy.get_loading_and_validity(self.supvisors.context.addresses.keys(), 45))
-        self.assertDictEqual({'10.0.0.1': (False, 50), '10.0.0.3': (True, 20),
-                              '10.0.0.5': (False, 80)},
+        self.assertDictEqual({'10.0.0.1': (False, 50), '10.0.0.3': (True, 20), '10.0.0.5': (False, 80)},
                              strategy.get_loading_and_validity(['10.0.0.1', '10.0.0.3', '10.0.0.5'], 75))
-        self.assertDictEqual({'10.0.0.1': (False, 50), '10.0.0.3': (False, 20),
-                              '10.0.0.5': (False, 80)},
+        self.assertDictEqual({'10.0.0.1': (False, 50), '10.0.0.3': (False, 20), '10.0.0.5': (False, 80)},
                              strategy.get_loading_and_validity(['10.0.0.1', '10.0.0.3', '10.0.0.5'], 85))
 
     def test_sort_valid_by_loading(self):
@@ -98,8 +97,7 @@ def test_sort_valid_by_loading(self):
         self.assertListEqual([('10.0.0.3', 20), ('10.0.0.1', 50), ('10.0.0.5', 80)],
                              strategy.sort_valid_by_loading({'10.0.0.0': (False, 0), '10.0.0.1': (True, 50),
                                                              '10.0.0.2': (False, 0), '10.0.0.3': (True, 20),
-                                                             '10.0.0.4': (False, 0),
-                                                             '10.0.0.5': (True, 80)}))
+                                                             '10.0.0.4': (False, 0), '10.0.0.5': (True, 80)}))
         self.assertListEqual([('10.0.0.3', 20)],
                              strategy.sort_valid_by_loading({'10.0.0.1': (False, 50), '10.0.0.3': (True, 20),
                                                              '10.0.0.5': (False, 80)}))
@@ -137,31 +135,35 @@ def test_most_loaded_strategy(self):
         self.assertEqual('10.0.0.3', strategy.get_address('*', 75))
         self.assertIsNone(strategy.get_address('*', 85))
 
+    def test_local_strategy(self):
+        """ Test the choice of an address according to the LOCAL strategy. """
+        from supvisors.strategy import LocalStrategy
+        strategy = LocalStrategy(self.supvisors)
+        # test LOCAL strategy with different values
+        self.assertEqual('10.0.0.1', strategy.address_mapper.local_address)
+        self.assertEqual('10.0.0.1', strategy.get_address('*', 15))
+        self.assertEqual('10.0.0.1', strategy.get_address('*', 45))
+        self.assertIsNone(strategy.get_address('*', 75))
+
     def test_get_address(self):
         """ Test the choice of an address according to a strategy. """
         from supvisors.ttypes import StartingStrategies
         from supvisors.strategy import get_address
         # test CONFIG strategy
-        self.assertEqual('10.0.0.1', get_address(self.supvisors,
-                                                 StartingStrategies.CONFIG, '*', 15))
-        self.assertEqual('10.0.0.3', get_address(self.supvisors,
-                                                 StartingStrategies.CONFIG, '*', 75))
-        self.assertIsNone(get_address(self.supvisors,
-                                      StartingStrategies.CONFIG, '*', 85))
+        self.assertEqual('10.0.0.1', get_address(self.supvisors, StartingStrategies.CONFIG, '*', 15))
+        self.assertEqual('10.0.0.3', get_address(self.supvisors, StartingStrategies.CONFIG, '*', 75))
+        self.assertIsNone(get_address(self.supvisors, StartingStrategies.CONFIG, '*', 85))
         # test LESS_LOADED strategy
-        self.assertEqual('10.0.0.3', get_address(self.supvisors,
-                                                 StartingStrategies.LESS_LOADED, '*', 15))
-        self.assertEqual('10.0.0.3', get_address(self.supvisors,
-                                                 StartingStrategies.LESS_LOADED, '*', 75))
-        self.assertIsNone(get_address(self.supvisors,
-                                      StartingStrategies.LESS_LOADED, '*', 85))
+        self.assertEqual('10.0.0.3', get_address(self.supvisors, StartingStrategies.LESS_LOADED, '*', 15))
+        self.assertEqual('10.0.0.3', get_address(self.supvisors, StartingStrategies.LESS_LOADED, '*', 75))
+        self.assertIsNone(get_address(self.supvisors, StartingStrategies.LESS_LOADED, '*', 85))
         # test MOST_LOADED strategy
-        self.assertEqual('10.0.0.5', get_address(self.supvisors,
-                                                 StartingStrategies.MOST_LOADED, '*', 15))
-        self.assertEqual('10.0.0.3', get_address(self.supvisors,
-                                                 StartingStrategies.MOST_LOADED, '*', 75))
-        self.assertIsNone(get_address(self.supvisors,
-                                      StartingStrategies.MOST_LOADED, '*', 85))
+        self.assertEqual('10.0.0.5', get_address(self.supvisors, StartingStrategies.MOST_LOADED, '*', 15))
+        self.assertEqual('10.0.0.3', get_address(self.supvisors, StartingStrategies.MOST_LOADED, '*', 75))
+        self.assertIsNone(get_address(self.supvisors, StartingStrategies.MOST_LOADED, '*', 85))
+        # test LOCAL strategy
+        self.assertEqual('10.0.0.1', get_address(self.supvisors, StartingStrategies.LOCAL, '*', 15))
+        self.assertIsNone(get_address(self.supvisors, StartingStrategies.LOCAL, '*', 75))
 
 
 class ConciliationStrategyTest(CompatTestCase):
@@ -181,10 +183,8 @@ def create_process_status(name, timed_addresses):
             process_status.namespec.return_value = name
             return process_status
 
-        self.conflicts = [create_process_status('conflict_1',
-                                                {'10.0.0.1': 5, '10.0.0.2': 10, '10.0.0.3': 15}),
-                          create_process_status('conflict_2',
-                                                {'10.0.0.4': 6, '10.0.0.2': 5, '10.0.0.0': 4})]
+        self.conflicts = [create_process_status('conflict_1', {'10.0.0.1': 5, '10.0.0.2': 10, '10.0.0.3': 15}),
+                          create_process_status('conflict_2', {'10.0.0.4': 6, '10.0.0.2': 5, '10.0.0.0': 4})]
 
     def test_senicide_strategy(self):
         """ Test the strategy that consists in stopping the oldest processes. """
@@ -454,10 +454,8 @@ def mocked_process(namespec, stopped):
         continue_process = mocked_process('continue_process', False)
 
         # pre-fill sets
-        handler.stop_application_jobs = {'stop_application_A',
-                                         'stop_application_B'}
-        handler.restart_application_jobs = {'restart_application_A',
-                                            'restart_application_B'}
+        handler.stop_application_jobs = {'stop_application_A', 'stop_application_B'}
+        handler.restart_application_jobs = {'restart_application_A', 'restart_application_B'}
         handler.restart_process_jobs = {restart_process_1, restart_process_2}
         handler.continue_process_jobs = {continue_process}
         handler.start_application_jobs = {start_appli_A, start_appli_B}
diff --git a/supvisors/tests/test_supvisorsctl.py b/supvisors/tests/test_supvisorsctl.py
index 006fa3bc..aa398a3b 100644
--- a/supvisors/tests/test_supvisorsctl.py
+++ b/supvisors/tests/test_supvisorsctl.py
@@ -24,6 +24,16 @@
 
 from unittest.mock import call, patch, Mock
 
+from supervisor.compat import xmlrpclib
+from supervisor.supervisorctl import Controller
+from supervisor.xmlrpc import Faults
+import errno
+import socket
+import sys
+import unittest
+
+from unittest.mock import call, patch, Mock
+
 from supervisor.compat import xmlrpclib
 from supervisor.supervisorctl import Controller
 from supervisor.xmlrpc import Faults
diff --git a/supvisors/tests/test_supvisorszmq.py b/supvisors/tests/test_supvisorszmq.py
index c57ea8c4..b838a192 100644
--- a/supvisors/tests/test_supvisorszmq.py
+++ b/supvisors/tests/test_supvisorszmq.py
@@ -359,19 +359,6 @@ def test_shutdown(self):
             self.fail('unexpected exception')
 
 
-class Payload:
-    """ Dummy class just implementing a serial method. """
-
-    def __init__(self, data):
-        self.data = data
-
-    def copy(self):
-        return self.data.copy()
-
-    def serial(self):
-        return self.data
-
-
 class EventTest(unittest.TestCase):
     """ Test case for the EventPublisher and EventSubscriber classes
     of the supvisorszmq module. """
@@ -385,8 +372,7 @@ def setUp(self):
         self.supvisors = MockedSupvisors()
         # create the ZeroMQ context
         # create publisher and subscriber
-        self.publisher = EventPublisher(self.supvisors.options.event_port,
-                                        self.supvisors.logger)
+        self.publisher = EventPublisher(self.supvisors.options.event_port, self.supvisors.logger)
         self.subscriber = EventSubscriber(zmq.Context.instance(),
                                           self.supvisors.options.event_port,
                                           self.supvisors.logger)
@@ -398,21 +384,12 @@ def setUp(self):
         # so a timeout is set for reception
         self.subscriber.socket.setsockopt(zmq.RCVTIMEO, 1000)
         # create test payloads
-        self.supvisors_payload = Payload({'state': 'running',
-                                          'version': '1.0'})
-        self.address_payload = Payload({'state': 'silent',
-                                        'name': 'cliche01',
-                                        'date': 1234})
-        self.application_payload = Payload({'state': 'starting',
-                                            'name': 'supvisors'})
-        self.process_payload = Payload({'state': 'running',
-                                        'process_name': 'plugin',
-                                        'application_name': 'supvisors',
-                                        'date': 1230})
-        self.event_payload = Payload({'state': 20,
-                                      'name': 'plugin',
-                                      'group': 'supvisors',
-                                      'now': 1230})
+        self.supvisors_payload = {'state': 'running', 'version': '1.0'}
+        self.address_payload = {'state': 'silent', 'name': 'cliche01', 'date': 1234}
+        self.application_payload = {'state': 'starting', 'name': 'supvisors'}
+        self.process_payload = {'state': 'running', 'process_name': 'plugin', 'application_name': 'supvisors',
+                                'date': 1230}
+        self.event_payload = {'state': 20, 'name': 'plugin', 'group': 'supvisors', 'now': 1230}
 
     def tearDown(self):
         """ Close the sockets. """
@@ -420,8 +397,7 @@ def tearDown(self):
         self.subscriber.close()
 
     def check_reception(self, header=None, data=None):
-        """ The method tests that the message is received correctly
-        or not received at all. """
+        """ The method tests that the message is received correctly or not received at all. """
         if header and data:
             # check that subscriber receives the message
             try:
@@ -440,8 +416,7 @@ def check_supvisors_status(self, subscribed):
         from supvisors.utils import EventHeaders
         self.publisher.send_supvisors_status(self.supvisors_payload)
         if subscribed:
-            self.check_reception(EventHeaders.SUPVISORS,
-                                 self.supvisors_payload.data)
+            self.check_reception(EventHeaders.SUPVISORS, self.supvisors_payload)
         else:
             self.check_reception()
 
@@ -451,8 +426,7 @@ def check_address_status(self, subscribed):
         from supvisors.utils import EventHeaders
         self.publisher.send_address_status(self.address_payload)
         if subscribed:
-            self.check_reception(EventHeaders.ADDRESS,
-                                 self.address_payload.data)
+            self.check_reception(EventHeaders.ADDRESS, self.address_payload)
         else:
             self.check_reception()
 
@@ -462,8 +436,7 @@ def check_application_status(self, subscribed):
         from supvisors.utils import EventHeaders
         self.publisher.send_application_status(self.application_payload)
         if subscribed:
-            self.check_reception(EventHeaders.APPLICATION,
-                                 self.application_payload.data)
+            self.check_reception(EventHeaders.APPLICATION, self.application_payload)
         else:
             self.check_reception()
 
@@ -473,7 +446,7 @@ def check_process_event(self, subscribed):
         from supvisors.utils import EventHeaders
         self.publisher.send_process_event('local_address', self.event_payload)
         if subscribed:
-            expected = self.event_payload.data
+            expected = self.event_payload
             expected['address'] = 'local_address'
             self.check_reception(EventHeaders.PROCESS_EVENT, expected)
         else:
@@ -485,8 +458,7 @@ def check_process_status(self, subscribed):
         from supvisors.utils import EventHeaders
         self.publisher.send_process_status(self.process_payload)
         if subscribed:
-            self.check_reception(EventHeaders.PROCESS_STATUS,
-                                 self.process_payload.data)
+            self.check_reception(EventHeaders.PROCESS_STATUS, self.process_payload)
         else:
             self.check_reception()
 
@@ -503,8 +475,7 @@ def check_subscription(self, supvisors_subscribed, address_subscribed,
 
     def test_no_subscription(self):
         """ Test the non-reception of messages when subscription is not set. """
-        # at this stage, no subscription has been set
-        # so nothing should be received
+        # at this stage, no subscription has been set so nothing should be received
         self.check_subscription(False, False, False, False, False)
 
     def test_subscription_supvisors_status(self):
diff --git a/supvisors/tests/test_viewapplication.py b/supvisors/tests/test_viewapplication.py
index 8bf89c69..6c0e5cf7 100644
--- a/supvisors/tests/test_viewapplication.py
+++ b/supvisors/tests/test_viewapplication.py
@@ -160,54 +160,28 @@ def test_write_header(self, mocked_strategy, mocked_period, mocked_action):
     def test_write_starting_strategy(self):
         """ Test the write_starting_strategy method. """
         from supvisors.ttypes import StartingStrategies
+        from supvisors.viewcontext import STRATEGY
         # patch the view context
-        self.view.view_ctx = Mock(**{'format_url.return_value': 'an url'})
+        self.view.view_ctx = Mock(parameters={STRATEGY: 'CONFIG'}, **{'format_url.return_value': 'an url'})
         # patch the meld elements
-        config_mid = Mock(attrib={'class': ''})
-        most_mid = Mock(attrib={'class': ''})
-        less_mid = Mock(attrib={'class': ''})
-        mocked_root = Mock(**{'findmeld.side_effect': [config_mid,
-                                                       most_mid,
-                                                       less_mid] * 3})
-        # test call with CONFIG strategy
-        self.view.supvisors.starter.strategy = StartingStrategies.CONFIG
-        self.view.write_starting_strategy(mocked_root)
-        self.assertEqual('button off active', config_mid.attrib['class'])
-        self.assertEqual([], config_mid.attributes.call_args_list)
-        self.assertEqual('', most_mid.attrib['class'])
-        self.assertEqual([call(href='an url')],
-                         most_mid.attributes.call_args_list)
-        self.assertEqual('', less_mid.attrib['class'])
-        self.assertEqual([call(href='an url')],
-                         less_mid.attributes.call_args_list)
-        config_mid.attrib['class'] = ''
-        most_mid.attributes.reset_mock()
-        less_mid.attributes.reset_mock()
-        # test call with MOST_LOADED strategy
-        self.view.supvisors.starter.strategy = StartingStrategies.MOST_LOADED
-        self.view.write_starting_strategy(mocked_root)
-        self.assertEqual('', config_mid.attrib['class'])
-        self.assertEqual([call(href='an url')],
-                         config_mid.attributes.call_args_list)
-        self.assertEqual('button off active', most_mid.attrib['class'])
-        self.assertEqual([], most_mid.attributes.call_args_list)
-        self.assertEqual('', less_mid.attrib['class'])
-        self.assertEqual([call(href='an url')],
-                         less_mid.attributes.call_args_list)
-        most_mid.attrib['class'] = ''
-        config_mid.attributes.reset_mock()
-        less_mid.attributes.reset_mock()
-        # test call with LESS_LOADED strategy
-        self.view.supvisors.starter.strategy = StartingStrategies.LESS_LOADED
-        self.view.write_starting_strategy(mocked_root)
-        self.assertEqual('', config_mid.attrib['class'])
-        self.assertEqual([call(href='an url')],
-                         config_mid.attributes.call_args_list)
-        self.assertEqual('', most_mid.attrib['class'])
-        self.assertEqual([call(href='an url')],
-                         most_mid.attributes.call_args_list)
-        self.assertEqual('button off active', less_mid.attrib['class'])
-        self.assertEqual([], less_mid.attributes.call_args_list)
+        strategy_mids = [Mock(attrib={'class': ''}) for _ in StartingStrategies.values()]
+        mocked_root = Mock(**{'findmeld.side_effect': strategy_mids * len(strategy_mids)})
+        # test all strategies in loop
+        for index, strategy in enumerate(StartingStrategies.strings()):
+            self.view.view_ctx.parameters[STRATEGY] = strategy
+            self.view.write_starting_strategy(mocked_root)
+            # other strategy_mids are not selected
+            for idx in range(len(strategy_mids)):
+                if idx == index:
+                    # strategy_mid at same index is selected
+                    self.assertEqual('button off active', strategy_mids[idx].attrib['class'])
+                    self.assertEqual([], strategy_mids[idx].attributes.call_args_list)
+                else:
+                    self.assertEqual('', strategy_mids[idx].attrib['class'])
+                    self.assertEqual([call(href='an url')], strategy_mids[idx].attributes.call_args_list)
+                # reset mocks
+                strategy_mids[idx].attrib['class'] = ''
+                strategy_mids[idx].attributes.reset_mock()
 
     def test_write_application_actions(self):
         """ Test the write_application_actions method. """
@@ -448,48 +422,37 @@ def test_write_process_table(self, mocked_common, mocked_process):
            return_value='Stop application')
     @patch('supvisors.viewapplication.ApplicationView.start_application_action',
            return_value='Start application')
-    @patch('supvisors.viewapplication.ApplicationView.set_starting_strategy',
-           side_effect=['Config', 'Most', 'Less'])
     @patch('supvisors.viewapplication.ApplicationView.refresh_action',
            return_value='Refresh')
-    def test_make_callback(self, mocked_refresh, mocked_strategy,
-                           mocked_start_app, mocked_stop_app,
-                           mocked_restart_app, mocked_start_proc,
-                           mocked_stop_proc, mocked_restart_proc,
-                           mocked_clear_proc, mocked_delayed):
+    def test_make_callback(self, mocked_refresh, mocked_start_app, mocked_stop_app,
+                           mocked_restart_app, mocked_start_proc, mocked_stop_proc,
+                           mocked_restart_proc, mocked_clear_proc, mocked_delayed):
         """ Test the make_callback method. """
+        from supvisors.ttypes import StartingStrategies
+        from supvisors.viewcontext import STRATEGY
         # patch view context
-        self.view.supvisors.starter.strategy = 'starter strategy'
-        self.view.view_ctx = Mock(**{'get_process_status.return_value': None})
+        self.view.view_ctx = Mock(parameters={STRATEGY: 'LOCAL'},
+                                  **{'get_process_status.return_value': None})
         # test calls for different actions
         self.assertEqual('Refresh', self.view.make_callback('', 'refresh'))
-        self.assertEqual('Config', self.view.make_callback('', 'config'))
-        self.assertEqual('Most', self.view.make_callback('', 'most'))
-        self.assertEqual('Less', self.view.make_callback('', 'less'))
-        self.assertEqual('Start application',
-                         self.view.make_callback('', 'startapp'))
-        self.assertEqual([call('starter strategy')],
-                         mocked_start_app.call_args_list)
-        self.assertEqual('Stop application',
-                         self.view.make_callback('', 'stopapp'))
-        self.assertEqual([call()],
-                         mocked_stop_app.call_args_list)
-        self.assertEqual('Restart application',
-                         self.view.make_callback('', 'restartapp'))
-        self.assertEqual([call('starter strategy')],
-                         mocked_restart_app.call_args_list)
+        self.assertEqual('Start application', self.view.make_callback('', 'startapp'))
+        self.assertEqual([call(StartingStrategies.LOCAL)], mocked_start_app.call_args_list)
+        self.assertEqual('Stop application', self.view.make_callback('', 'stopapp'))
+        self.assertEqual([call()], mocked_stop_app.call_args_list)
+        self.assertEqual('Restart application', self.view.make_callback('', 'restartapp'))
+        self.assertEqual([call(StartingStrategies.LOCAL)], mocked_restart_app.call_args_list)
         self.assertEqual('Delayed', self.view.make_callback('dummy', 'anything'))
         # change view context for the remaining actions
         self.view.view_ctx.get_process_status.return_value = 'None'
         # test start process
         self.assertEqual('Start process', self.view.make_callback('dummy', 'start'))
-        self.assertEqual([call('starter strategy', 'dummy')], mocked_start_proc.call_args_list)
+        self.assertEqual([call(StartingStrategies.LOCAL, 'dummy')], mocked_start_proc.call_args_list)
         # test stop process
         self.assertEqual('Stop process', self.view.make_callback('dummy', 'stop'))
         self.assertEqual([call('dummy')], mocked_stop_proc.call_args_list)
         # test restart process
         self.assertEqual('Restart process', self.view.make_callback('dummy', 'restart'))
-        self.assertEqual([call('starter strategy', 'dummy')], mocked_restart_proc.call_args_list)
+        self.assertEqual([call(StartingStrategies.LOCAL, 'dummy')], mocked_restart_proc.call_args_list)
         # test clear logs process
         self.assertEqual('Clear process logs', self.view.make_callback('dummy', 'clearlog'))
         self.assertEqual([call('dummy')], mocked_clear_proc.call_args_list)
@@ -498,38 +461,23 @@ def test_make_callback(self, mocked_refresh, mocked_strategy,
     def test_refresh_action(self, mocked_delayed):
         """ Test the refresh_action method. """
         self.assertEqual('Delayed', self.view.refresh_action())
-        self.assertEqual([call('Page refreshed')],
-                         mocked_delayed.call_args_list)
-
-    @patch('supvisors.viewapplication.delayed_info', return_value='Delayed')
-    def test_set_starting_strategy(self, mocked_delayed):
-        """ Test the set_starting_strategy method. """
-        self.assertEqual('Delayed', self.view.set_starting_strategy(1))
-        self.assertEqual([call('Starting strategy set to LESS_LOADED')],
-                         mocked_delayed.call_args_list)
+        self.assertEqual([call('Page refreshed')], mocked_delayed.call_args_list)
 
 
 class ViewApplicationActionTest(unittest.TestCase):
-    """ Test case for the start/stop methods of the ApplicationView class of
-    the viewapplication module. """
+    """ Test case for the start/stop methods of the ApplicationView class of the viewapplication module. """
 
     def setUp(self):
         """ Create a common context and apply common patches. """
         from supvisors.viewapplication import ApplicationView
         self.view = ApplicationView(DummyHttpContext('ui/application.html'))
         # add the common patches
-        self.patches = [patch('supvisors.viewapplication.delayed_error',
-                              return_value='Delay err'),
-                        patch('supvisors.viewapplication.delayed_warn',
-                              return_value='Delay warn'),
-                        patch('supvisors.viewapplication.delayed_info',
-                              return_value='Delay info'),
-                        patch('supvisors.viewapplication.error_message',
-                              return_value='Msg err'),
-                        patch('supvisors.viewapplication.warn_message',
-                              return_value='Msg warn'),
-                        patch('supvisors.viewapplication.info_message',
-                              return_value='Msg info')]
+        self.patches = [patch('supvisors.viewapplication.delayed_error', return_value='Delay err'),
+                        patch('supvisors.viewapplication.delayed_warn', return_value='Delay warn'),
+                        patch('supvisors.viewapplication.delayed_info', return_value='Delay info'),
+                        patch('supvisors.viewapplication.error_message', return_value='Msg err'),
+                        patch('supvisors.viewapplication.warn_message', return_value='Msg warn'),
+                        patch('supvisors.viewapplication.info_message', return_value='Msg info')]
         self.mocked = [p.start() for p in self.patches]
 
     def tearDown(self):
@@ -538,8 +486,7 @@ def tearDown(self):
 
     def test_start_application_action(self):
         """ Test the start_application_action method. """
-        self.check_start_action('start_application',
-                                'start_application_action')
+        self.check_start_action('start_application', 'start_application_action')
 
     def test_stop_application_action(self):
         """ Test the stop_application_action method. """
@@ -547,8 +494,7 @@ def test_stop_application_action(self):
 
     def test_restart_application_action(self):
         """ Test the restart_application_action method. """
-        self.check_start_action('restart_application',
-                                'restart_application_action')
+        self.check_start_action('restart_application', 'restart_application_action')
 
     def test_start_process_action(self):
         """ Test the start_process_action method. """
diff --git a/supvisors/tests/test_viewcontext.py b/supvisors/tests/test_viewcontext.py
index d6bc649d..8ef2e135 100644
--- a/supvisors/tests/test_viewcontext.py
+++ b/supvisors/tests/test_viewcontext.py
@@ -34,78 +34,67 @@ class ViewContextTest(unittest.TestCase):
     url_attr_template = r'(.+=.+)'
 
     def setUp(self):
-        """ Create a logger that stores log traces. """
+        """ Create the instance to be tested. """
+        from supvisors.viewcontext import ViewContext
         self.http_context = DummyHttpContext('')
+        self.ctx = ViewContext(self.http_context)
+        self.maxDiff = None
 
     def test_init(self):
         """ Test the values set at construction. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
-        self.assertIs(ctx.http_context, self.http_context)
-        self.assertIs(ctx.supvisors, self.http_context.supervisord.supvisors)
-        self.assertEqual(DummyAddressMapper().local_address, ctx.local_address)
+        self.assertIs(self.ctx.http_context, self.http_context)
+        self.assertIs(self.ctx.supvisors, self.http_context.supervisord.supvisors)
+        self.assertEqual(DummyAddressMapper().local_address, self.ctx.local_address)
         self.assertDictEqual({'address': '10.0.0.4', 'namespec': None, 'period': 5,
                               'appliname': None, 'processname': None, 'cpuid': 0,
-                              'intfname': None, 'auto': False}, ctx.parameters)
+                              'intfname': None, 'auto': False, 'strategy': 'CONFIG'},
+                             self.ctx.parameters)
 
     def test_get_server_port(self):
         """ Test the get_server_port method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
-        self.assertEqual(7777, ctx.get_server_port())
+        self.assertEqual(7777, self.ctx.get_server_port())
 
     def test_get_action(self):
         """ Test the get_action method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
-        self.assertEqual('test', ctx.get_action())
+        self.assertEqual('test', self.ctx.get_action())
 
     def test_get_address(self):
         """ Test the get_address method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
-        self.assertEqual('10.0.0.4', ctx.get_address())
+        self.assertEqual('10.0.0.4', self.ctx.get_address())
 
     def test_get_message(self):
         """ Test the get_message method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
-        self.assertEqual('hi chaps', ctx.get_message())
+        self.assertEqual('hi chaps', self.ctx.get_message())
 
     def test_get_gravity(self):
         """ Test the get_gravity method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
-        self.assertEqual('none', ctx.get_gravity())
+        self.assertEqual('none', self.ctx.get_gravity())
 
     def test_url_parameters(self):
         """ Test the get_nb_cores method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
         # test default
-        self.assertEqual(ctx.url_parameters(),
-                         'period=5')
+        self.assertEqual(self.ctx.url_parameters(), 'period=5')
         # update internal parameters
-        ctx.parameters.update({'processname': 'dummy_proc',
-                               'namespec': 'dummy_ns',
-                               'address': '10.0.0.1',
-                               'cpuid': 3,
-                               'intfname': 'eth0',
-                               'appliname': 'dummy_appli',
-                               'period': 8})
+        self.ctx.parameters.update({'processname': 'dummy_proc',
+                                    'namespec': 'dummy_ns',
+                                    'address': '10.0.0.1',
+                                    'cpuid': 3,
+                                    'intfname': 'eth0',
+                                    'appliname': 'dummy_appli',
+                                    'period': 8})
         # test default
-        self.assertEqual(ctx.url_parameters(),
+        self.assertEqual(self.ctx.url_parameters(),
                          'processname=dummy_proc&amp;namespec=dummy_ns&amp;'
                          'address=10.0.0.1&amp;cpuid=3&amp;intfname=eth0&amp;'
                          'appliname=dummy_appli&amp;period=8')
         # test with extra arguments, overloading some
-        self.assertEqual(ctx.url_parameters(**{'extra': 'args',
-                                               'processname': 'cat',
-                                               'address': '127.0.0.1',
-                                               'cpuid': 1,
-                                               'intfname': 'lo',
-                                               'appliname': '',
-                                               'period': 0}),
+        self.assertEqual(self.ctx.url_parameters(**{'extra': 'args',
+                                                    'processname': 'cat',
+                                                    'address': '127.0.0.1',
+                                                    'cpuid': 1,
+                                                    'intfname': 'lo',
+                                                    'appliname': '',
+                                                    'period': 0}),
                          'namespec=dummy_ns&amp;extra=args&amp;'
                          'processname=cat&amp;address=127.0.0.1&amp;'
                          'cpuid=1&amp;intfname=lo')
@@ -366,22 +355,21 @@ def test_update_interface_name(self, mocked_stats):
 
     def test_url_parameters(self):
         """ Test the url_parameters method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
         # test default
-        self.assertEqual('period=5&amp;address=10.0.0.4', ctx.url_parameters())
+        self.assertEqual('period=5&amp;strategy=CONFIG&amp;address=10.0.0.4', self.ctx.url_parameters())
         # update internal parameters
-        ctx.parameters.update({'processname': 'dummy_proc',
-                               'namespec': 'dummy_ns',
-                               'address': '10.0.0.1',
-                               'cpuid': 3,
-                               'intfname': 'eth0',
-                               'appliname': 'dummy_appli',
-                               'period': 8})
+        self.ctx.parameters.update({'processname': 'dummy_proc',
+                                    'namespec': 'dummy_ns',
+                                    'address': '10.0.0.1',
+                                    'cpuid': 3,
+                                    'intfname': 'eth0',
+                                    'appliname': 'dummy_appli',
+                                    'period': 8,
+                                    'strategy': 'CONFIG'})
         # test without additional parameters
-        url = ctx.url_parameters()
+        url = self.ctx.url_parameters()
         # result depends on dict contents so ordering is unreliable
-        regexp = r'&amp;'.join([self.url_attr_template for _ in range(7)])
+        regexp = r'&amp;'.join([self.url_attr_template for _ in range(8)])
         matches = re.match(regexp, url)
         self.assertIsNotNone(matches)
         self.assertSequenceEqual(sorted(matches.groups()),
@@ -391,12 +379,11 @@ def test_url_parameters(self):
                                          'cpuid=3',
                                          'intfname=eth0',
                                          'appliname=dummy_appli',
-                                         'period=8')))
+                                         'period=8',
+                                         'strategy=CONFIG')))
         # test with additional parameters
-        url = ctx.url_parameters(**{'address': '127.0.0.1',
-                                    'intfname': 'lo',
-                                    'extra': 'args'})
-        regexp = r'&amp;'.join([self.url_attr_template for _ in range(8)])
+        url = self.ctx.url_parameters(**{'address': '127.0.0.1', 'intfname': 'lo', 'extra': 'args'})
+        regexp = r'&amp;'.join([self.url_attr_template for _ in range(9)])
         matches = re.match(regexp, url)
         self.assertIsNotNone(matches)
         self.assertSequenceEqual(sorted(matches.groups()),
@@ -407,20 +394,17 @@ def test_url_parameters(self):
                                          'intfname=lo',
                                          'extra=args',
                                          'appliname=dummy_appli',
-                                         'period=8')))
+                                         'period=8',
+                                         'strategy=CONFIG')))
 
     def test_format_url(self):
         """ Test the format_url method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
         # test without address or arguments
-        self.assertEqual('index.html?period=5&amp;address=10.0.0.4',
-                         ctx.format_url(0, 'index.html'))
+        self.assertEqual('index.html?period=5&amp;strategy=CONFIG&amp;address=10.0.0.4',
+                         self.ctx.format_url(0, 'index.html'))
         # test with address and arguments
-        url = ctx.format_url('10.0.0.1', 'index.html',
-                             **{'period': 10,
-                                'appliname': 'dummy_appli',
-                                'extra': 'args'})
+        url = self.ctx.format_url('10.0.0.1', 'index.html',
+                                  **{'period': 10, 'appliname': 'dummy_appli', 'extra': 'args'})
         # result depends on dict contents so ordering is unreliable
         base_address = r'http://10.0.0.1:7777/index.html\?'
         parameters = r'&amp;'.join([self.url_attr_template for _ in range(3)])
@@ -429,14 +413,12 @@ def test_format_url(self):
         self.assertIsNotNone(matches)
         self.assertSequenceEqual(sorted(matches.groups()),
                                  sorted(('extra=args',
-                                         'period=10&amp;address=10.0.0.4',
+                                         'period=10&amp;strategy=CONFIG&amp;address=10.0.0.4',
                                          'appliname=dummy_appli')))
 
     def test_message(self):
         """ Test the message method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
-        ctx.message(('warning', 'not as expected'))
+        self.ctx.message(('warning', 'not as expected'))
         # result depends on dict contents so ordering is unreliable
         url = self.http_context.response['headers']['Location']
         base_address = r'http://10.0.0.1:7777/index.html\?'
@@ -446,32 +428,29 @@ def test_message(self):
         self.assertIsNotNone(matches)
         self.assertSequenceEqual(sorted(matches.groups()),
                                  sorted(('message=not%20as%20expected',
-                                         'period=5&amp;address=10.0.0.4',
+                                         'period=5&amp;strategy=CONFIG&amp;address=10.0.0.4',
                                          'gravity=warning')))
 
     def test_get_nbcores(self):
         """ Test the get_nb_cores method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
         # test default
-        self.assertEqual(0, ctx.get_nbcores())
+        self.assertEqual(0, self.ctx.get_nbcores())
         # mock the structure
         stats = self.http_context.supervisord.supvisors.statistician
-        stats.nbcores[ctx.local_address] = 4
+        stats.nbcores[self.ctx.local_address] = 4
         # test new call
-        self.assertEqual(4, ctx.get_nbcores())
+        self.assertEqual(4, self.ctx.get_nbcores())
         # test with unknown address
-        self.assertEqual(0, ctx.get_nbcores('10.0.0.1'))
+        self.assertEqual(0, self.ctx.get_nbcores('10.0.0.1'))
         # test with known address
         stats.nbcores['10.0.0.1'] = 8
-        self.assertEqual(8, ctx.get_nbcores('10.0.0.1'))
+        self.assertEqual(8, self.ctx.get_nbcores('10.0.0.1'))
 
     def test_get_address_stats(self):
         """ Test the get_address_stats method. """
-        from supvisors.viewcontext import ViewContext, PERIOD
-        ctx = ViewContext(self.http_context)
+        from supvisors.viewcontext import PERIOD
         # test default
-        self.assertIsNone(ctx.get_address_stats())
+        self.assertIsNone(self.ctx.get_address_stats())
         # add statistics data
         stats_data = self.http_context.supervisord.supvisors.statistician.data
         stats_data['127.0.0.1'] = {5: 'data for period 5 at 127.0.0.1',
@@ -479,25 +458,20 @@ def test_get_address_stats(self):
         stats_data['10.0.0.1'] = {5: 'data for period 5 at 10.0.0.1',
                                   10: 'data for period 10 at 10.0.0.1'}
         # test with default address
-        self.assertEqual('data for period 5 at 127.0.0.1',
-                         ctx.get_address_stats())
+        self.assertEqual('data for period 5 at 127.0.0.1', self.ctx.get_address_stats())
         # test with unknown address parameter
-        self.assertIsNone(ctx.get_address_stats('10.0.0.2'))
+        self.assertIsNone(self.ctx.get_address_stats('10.0.0.2'))
         # test with known address parameter and existing period
-        self.assertEqual('data for period 5 at 10.0.0.1',
-                         ctx.get_address_stats('10.0.0.1'))
+        self.assertEqual('data for period 5 at 10.0.0.1', self.ctx.get_address_stats('10.0.0.1'))
         # update period
-        ctx.parameters[PERIOD] = 8
+        self.ctx.parameters[PERIOD] = 8
         # test with default address and existing period
-        self.assertEqual('data for period 8 at 127.0.0.1',
-                         ctx.get_address_stats())
+        self.assertEqual('data for period 8 at 127.0.0.1', self.ctx.get_address_stats())
         # test with known address parameter but missing period
-        self.assertIsNone(ctx.get_address_stats('10.0.0.1'))
+        self.assertIsNone(self.ctx.get_address_stats('10.0.0.1'))
 
     def test_get_process_last_desc(self):
         """ Test the get_process_last_desc method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
         # build common Mock
         mocked_process = Mock(addresses=set(),
                               infos={'10.0.0.1': {'local_time': 10, 'description': 'desc1'},
@@ -507,80 +481,69 @@ def test_get_process_last_desc(self):
         with patch('supvisors.viewcontext.ViewContext.get_process_status',
                    return_value=mocked_process):
             self.assertTupleEqual((None, None),
-                                  ctx.get_process_last_desc('dummy_proc', True))
+                                  self.ctx.get_process_last_desc('dummy_proc', True))
         # test method return on non-running process and running not requested
         # the method returns the
-        with patch('supvisors.viewcontext.ViewContext.get_process_status',
-                   return_value=mocked_process):
+        with patch('supvisors.viewcontext.ViewContext.get_process_status', return_value=mocked_process):
             self.assertTupleEqual(('10.0.0.2', 'desc2'),
-                                  ctx.get_process_last_desc('dummy_proc'))
+                                  self.ctx.get_process_last_desc('dummy_proc'))
         # test method return on running process and running requested
         mocked_process.addresses.add('10.0.0.3')
-        with patch('supvisors.viewcontext.ViewContext.get_process_status',
-                   return_value=mocked_process):
+        with patch('supvisors.viewcontext.ViewContext.get_process_status', return_value=mocked_process):
             self.assertTupleEqual(('10.0.0.3', 'desc3'),
-                                  ctx.get_process_last_desc('dummy_proc', True))
+                                  self.ctx.get_process_last_desc('dummy_proc', True))
         # test method return on running process and running not requested
         # same result as previous
-        with patch('supvisors.viewcontext.ViewContext.get_process_status',
-                   return_value=mocked_process):
+        with patch('supvisors.viewcontext.ViewContext.get_process_status', return_value=mocked_process):
             self.assertTupleEqual(('10.0.0.3', 'desc3'),
-                                  ctx.get_process_last_desc('dummy_proc'))
+                                  self.ctx.get_process_last_desc('dummy_proc'))
         # test method return on multiple running processes and running requested
         mocked_process.addresses.add('10.0.0.2')
-        with patch('supvisors.viewcontext.ViewContext.get_process_status',
-                   return_value=mocked_process):
+        with patch('supvisors.viewcontext.ViewContext.get_process_status', return_value=mocked_process):
             self.assertTupleEqual(('10.0.0.2', 'desc2'),
-                                  ctx.get_process_last_desc('dummy_proc', True))
+                                  self.ctx.get_process_last_desc('dummy_proc', True))
         # test method return on running process and running not requested
         # same result as previous
-        with patch('supvisors.viewcontext.ViewContext.get_process_status',
-                   return_value=mocked_process):
+        with patch('supvisors.viewcontext.ViewContext.get_process_status', return_value=mocked_process):
             self.assertTupleEqual(('10.0.0.2', 'desc2'),
-                                  ctx.get_process_last_desc('dummy_proc'))
+                                  self.ctx.get_process_last_desc('dummy_proc'))
 
     @patch('supvisors.viewcontext.ViewContext.get_nbcores', return_value=4)
     def test_get_process_stats(self, mocked_core):
         """ Test the get_process_stats method. """
-        from supvisors.viewcontext import ViewContext
-        ctx = ViewContext(self.http_context)
         # reset mocks that have been called in constructor
         mocked_core.reset_mock()
         # patch get_address_stats so that it returns no result
-        with patch.object(ctx, 'get_address_stats', return_value=None) as mocked_stats:
-            self.assertEqual((4, None), ctx.get_process_stats('dummy_proc'))
+        with patch.object(self.ctx, 'get_address_stats', return_value=None) as mocked_stats:
+            self.assertEqual((4, None), self.ctx.get_process_stats('dummy_proc'))
             self.assertEqual([call('127.0.0.1')], mocked_stats.call_args_list)
         mocked_core.reset_mock()
         # patch get_address_stats
         mocked_find = Mock(**{'find_process_stats.return_value': 'mock stats'})
-        with patch.object(ctx, 'get_address_stats', return_value=mocked_find) as mocked_stats:
-            self.assertEqual((4, 'mock stats'), ctx.get_process_stats('dummy_proc', '10.0.0.1'))
+        with patch.object(self.ctx, 'get_address_stats', return_value=mocked_find) as mocked_stats:
+            self.assertEqual((4, 'mock stats'), self.ctx.get_process_stats('dummy_proc', '10.0.0.1'))
             self.assertEqual([call('10.0.0.1')], mocked_stats.call_args_list)
             self.assertEqual([call('10.0.0.1')], mocked_core.call_args_list)
             self.assertEqual([call('dummy_proc')], mocked_find.find_process_stats.call_args_list)
 
     def test_get_process_status(self):
         """ Test the get_process_status method. """
-        from supvisors.viewcontext import ViewContext, NAMESPEC
-        ctx = ViewContext(self.http_context)
+        from supvisors.viewcontext import NAMESPEC
         # test with empty context and no process in http form
-        self.assertFalse(ctx.get_process_status())
-        self.assertFalse(ctx.get_process_status('abc'))
+        self.assertFalse(self.ctx.get_process_status())
+        self.assertFalse(self.ctx.get_process_status('abc'))
         # test with context
-        with patch.dict(ctx.context.processes,
+        with patch.dict(self.ctx.context.processes,
                         {'abc': {'process': 'abc'},
                          'dummy_proc': {'process': 'dummy_proc'}},
                         clear=True):
             # test with nothing in http form
-            self.assertFalse(ctx.get_process_status())
-            self.assertDictEqual({'process': 'abc'},
-                                 ctx.get_process_status('abc'))
+            self.assertFalse(self.ctx.get_process_status())
+            self.assertDictEqual({'process': 'abc'}, self.ctx.get_process_status('abc'))
             # test with namespec in http form
-            ctx.parameters[NAMESPEC] = 'abc'
-            self.assertDictEqual({'process': 'abc'},
-                                 ctx.get_process_status())
-            self.assertDictEqual({'process': 'dummy_proc'},
-                                 ctx.get_process_status('dummy_proc'))
+            self.ctx.parameters[NAMESPEC] = 'abc'
+            self.assertDictEqual({'process': 'abc'}, self.ctx.get_process_status())
+            self.assertDictEqual({'process': 'dummy_proc'}, self.ctx.get_process_status('dummy_proc'))
 
 
 def test_suite():